package httpapi import ( "context" "knowledge-governance/internal/dobotaiops" "knowledge-governance/internal/qaopsmirror" "knowledge-governance/internal/qaopsquality" "net/http" "net/http/httptest" "strings" "testing" "time" ) type dashboardReaderSpy struct { attributedReader calls int masterCalls int query dobotaiops.RecordQuery err error } func (s *dashboardReaderSpy) ReadDashboard(_ context.Context, q dobotaiops.RecordQuery) (qaopsmirror.Dashboard, error) { s.calls++ s.query = q return qaopsmirror.Dashboard{TotalIssues: 7, NewThisWeek: 2}, s.err } func (s *dashboardReaderSpy) ReadMasterDashboard(ctx context.Context, q dobotaiops.RecordQuery) (qaopsmirror.Dashboard, error) { s.masterCalls++ return s.ReadDashboard(ctx, q) } func TestQADashboardIsFixedTenantAdminAndReadOnly(t *testing.T) { reader := &dashboardReaderSpy{} auth := newFakeAuthStore() auth.sessions["dash-admin"] = "usr-boss" auth.sessions["dash-viewer"] = "usr-eye" h, e := NewHandler(Config{Store: &fakeJobStore{}, QAOps: reader, QAOpsTenantKey: "fixed-dashboard-tenant", Now: func() time.Time { return time.Date(2026, 10, 18, 12, 0, 0, 0, time.UTC) }, Auth: &AuthConfig{Store: auth}}) if e != nil { t.Fatal(e) } good := `{"start":"2026-10-05T00:00:00+08:00","end":"2026-10-12T00:00:00+08:00","keyword":"100%_"}` for _, c := range []struct { token, action, origin, body string status int }{ {"", "qa-dashboard", "", good, 401}, {"dash-viewer", "qa-dashboard", "", good, 403}, {"dash-admin", "", "", good, 403}, {"dash-admin", "qa-dashboard", "https://untrusted.invalid", good, 403}, {"dash-admin", "qa-dashboard", "", `null`, 400}, {"dash-admin", "qa-dashboard", "", `{"start":null,"end":"2026-10-12T00:00:00+08:00"}`, 400}, {"dash-admin", "qa-dashboard", "", `{"tenant":"other","start":"2026-10-05T00:00:00+08:00","end":"2026-10-12T00:00:00+08:00"}`, 400}, {"dash-admin", "qa-dashboard", "", good, 200}, } { r := httptest.NewRequest(http.MethodPost, "/api/v1/qa-ops/dashboard", strings.NewReader(c.body)) if c.token != "" { r.AddCookie(&http.Cookie{Name: sessionCookieName, Value: c.token}) } r.Header.Set("X-Governance-Action", c.action) r.Header.Set("Origin", c.origin) w := httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code != c.status { t.Fatalf("dashboard status=%d want=%d", w.Code, c.status) } if w.Code == 200 && w.Header().Get("Cache-Control") != "no-store" { t.Fatal("private dashboard response may be cached") } } if reader.masterCalls != 1 || reader.calls != 1 || reader.query.Window.TenantKey != "fixed-dashboard-tenant" || reader.query.Keyword != "100%_" { t.Fatal("rejected requests reached the reader or browser controlled the tenant") } reader.err = qaopsquality.ErrConflict r := httptest.NewRequest(http.MethodPost, "/api/v1/qa-ops/dashboard", strings.NewReader(good)) r.AddCookie(&http.Cookie{Name: sessionCookieName, Value: "dash-admin"}) r.Header.Set("X-Governance-Action", "qa-dashboard") w := httptest.NewRecorder() h.ServeHTTP(w, r) if w.Code != http.StatusConflict { t.Fatalf("quality input drift status=%d want409", w.Code) } }