Files
knowledge-governance/internal/httpapi/dingtalk_session_test.go
T
姚章浩 e60b10256a fix(settings): add DingTalk spaces and render the real login QR
Address two gaps in the first DingTalk-session UI:
- add an explicit admin-only '+ 新增钉钉知识库' action on the data-source
  panel; it accepts an alidocs space URL, resolves the live name/root through
  the authenticated sidecar, writes the inventory root atomically, upserts a
  deterministic source connection, and enqueues its first catalog refresh
- read DingTalk's real QR canvas pixels rather than taking a DOM screenshot;
  select by dark-pixel content, composite onto a white quiet zone, and render
  at 320x320 so the dialog contains a large complete code, not a page thumbnail

The live refresh path resolves registered roots from the connection locator,
while existing name-based DingTalk sources remain compatible.
2026-09-30 10:54:07 +08:00

95 lines
3.6 KiB
Go

package httpapi
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func TestDingtalkSessionProxyRelaysSidecarStates(t *testing.T) {
sidecar := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch {
case r.Method == http.MethodPost && r.URL.Path == "/sessions":
w.WriteHeader(http.StatusCreated)
_ = json.NewEncoder(w).Encode(map[string]any{"id": "abc123", "state": "qr", "created_at": 1, "expires_at": 2})
case r.Method == http.MethodGet && r.URL.Path == "/sessions/abc123":
_ = json.NewEncoder(w).Encode(map[string]any{"id": "abc123", "state": "success"})
case r.Method == http.MethodGet && r.URL.Path == "/sessions/missing":
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"error":"session_not_found"}`))
default:
w.WriteHeader(http.StatusNotFound)
}
}))
defer sidecar.Close()
t.Setenv("DINGTALK_LOGIN_SIDECAR_URL", sidecar.URL)
start := httptest.NewRequest(http.MethodPost, "/api/v1/dingtalk-session", strings.NewReader(`{}`))
start = start.WithContext(context.WithValue(start.Context(), sessionKey{}, sessionIdentity{Username: "tester", Role: roleAdmin}))
rec := httptest.NewRecorder()
startDingtalkSession(rec, start)
if rec.Code != http.StatusCreated {
t.Fatalf("start proxy status = %d body=%s", rec.Code, rec.Body.String())
}
var created map[string]any
if err := json.Unmarshal(rec.Body.Bytes(), &created); err != nil {
t.Fatal(err)
}
if created["state"] != "qr" {
t.Fatalf("relayed state = %v", created["state"])
}
poll := httptest.NewRequest(http.MethodGet, "/api/v1/dingtalk-session/abc123", nil)
poll = poll.WithContext(context.WithValue(poll.Context(), sessionKey{}, sessionIdentity{Username: "tester", Role: roleAdmin}))
poll.SetPathValue("id", "abc123")
rec = httptest.NewRecorder()
getDingtalkSession(rec, poll)
if !strings.Contains(rec.Body.String(), `"success"`) {
t.Fatalf("poll body = %s", rec.Body.String())
}
missing := httptest.NewRequest(http.MethodGet, "/api/v1/dingtalk-session/missing", nil)
missing = missing.WithContext(context.WithValue(missing.Context(), sessionKey{}, sessionIdentity{Username: "tester", Role: roleAdmin}))
missing.SetPathValue("id", "missing")
rec = httptest.NewRecorder()
getDingtalkSession(rec, missing)
if rec.Code != http.StatusNotFound {
t.Fatalf("missing status = %d", rec.Code)
}
}
func TestDingtalkSessionRejectsUnauthenticatedAndHostileIDs(t *testing.T) {
rec := httptest.NewRecorder()
startDingtalkSession(rec, httptest.NewRequest(http.MethodPost, "/api/v1/dingtalk-session", nil))
if rec.Code != http.StatusUnauthorized {
t.Fatalf("unauthenticated start = %d, want 401", rec.Code)
}
if got := safeSessionID("../etc/passwd"); got != "" {
t.Fatalf("hostile id accepted: %q", got)
}
for _, test := range []struct{ raw, want string }{
{"https://alidocs.dingtalk.com/i/spaces/9JOGOQneRgkneX4Q/overview?rnd=1", "9JOGOQneRgkneX4Q"},
{"9JOGOQneRgkneX4Q", "9JOGOQneRgkneX4Q"},
{"https://example.com/not-a-space", ""},
} {
if got := extractDingtalkSpaceID(test.raw); got != test.want {
t.Fatalf("extractDingtalkSpaceID(%q) = %q, want %q", test.raw, got, test.want)
}
}
}
func TestDingtalkSessionUnavailableWithoutSidecar(t *testing.T) {
t.Setenv("DINGTALK_LOGIN_SIDECAR_URL", "")
req := httptest.NewRequest(http.MethodGet, "/api/v1/dingtalk-session/abc", nil)
req = req.WithContext(context.WithValue(req.Context(), sessionKey{}, sessionIdentity{Username: "tester", Role: roleAdmin}))
req.SetPathValue("id", "abc")
rec := httptest.NewRecorder()
getDingtalkSession(rec, req)
if rec.Code != http.StatusServiceUnavailable {
t.Fatalf("sidecar down status = %d, want 503", rec.Code)
}
}