Files
knowledge-governance/internal/httpapi/library_overview_empty_test.go
T
姚章浩 56f8f4fd7e
build / build (push) Successful in 1m41s
fix(library): 统一空目标概览与首次同步准入
2026-10-31 02:42:01 +08:00

102 lines
3.8 KiB
Go

package httpapi
import (
"context"
"encoding/json"
"errors"
"knowledge-governance/internal/governance"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
)
func TestEmptyTargetOverviewAndEnqueueAgree(t *testing.T) {
for _, failed := range []bool{false, true} {
root := libraryFixtureRoot(t)
target := sealSharedVersions(t, root, 1, 4)[0]
digest, err := librarySyncParamsDigest("internal", target)
if err != nil {
t.Fatal(err)
}
now := time.Now().UTC()
store := &fakeLibraryStore{versions: []governance.LibraryVersion{{ID: "seal", Scope: "internal", Kind: governance.LibraryVersionTestSealed, SnapshotID: target, CreatedAt: now}}}
store.enqueueEchoRequestedID = true
store.grant = &governance.LibraryReleaseAuthorization{ID: "grant", Scope: "internal", SnapshotID: target, ParamsDigest: digest, AuthorizedBy: "admin", ExpiresAt: now.Add(time.Hour), CreatedAt: now}
config := Config{Store: store, LibraryArchiveRoot: root, ProductionReleaseRequestsEnabled: true, ProductionBackupRestoreVerified: true, LibraryProductionDatabaseURL: "postgres://unused.invalid/test", LibraryProductionInternalKB: "kb", LibraryCapacityBudgetBytes: 1 << 40, LibraryScopeProbe: func(context.Context, string) (bool, map[string]int64, error) {
if failed {
return false, nil, errors.New("offline")
}
return true, nil, nil
}}
rec := httptest.NewRecorder()
libraryOverviewHandler(rec, httptest.NewRequest(http.MethodGet, "/api/v1/library/overview?scope=internal", nil), config)
var overview libraryOverview
if err := json.Unmarshal(rec.Body.Bytes(), &overview); err != nil {
t.Fatal(err)
}
if overview.Gate.SyncAllowed == failed {
t.Fatalf("failed=%v gate=%+v", failed, overview.Gate)
}
rec = httptest.NewRecorder()
librarySyncEnqueue(rec, httptest.NewRequest(http.MethodPost, "/api/v1/library/syncs", strings.NewReader(`{"scope":"internal","target_snapshot_id":"`+target+`"}`)), config)
if failed {
if rec.Code != http.StatusServiceUnavailable || store.createN != 0 {
t.Fatalf("unreachable target enqueued: %d %s", rec.Code, rec.Body.String())
}
} else if rec.Code != http.StatusAccepted || store.createN != 1 {
t.Fatalf("empty target rejected: %d %s", rec.Code, rec.Body.String())
}
}
}
func TestLibraryOverviewProbesUnknownTarget(t *testing.T) {
for _, tc := range []struct {
name string
empty bool
err error
want string
}{
{name: "empty", empty: true},
{name: "nonempty", want: "目标非空"},
{name: "unreachable", err: errors.New("connection unavailable"), want: "无法读取目标整库状态"},
} {
t.Run(tc.name, func(t *testing.T) {
calls := 0
config := Config{Store: &fakeLibraryStore{}, LibraryArchiveRoot: t.TempDir(), LibraryScopeProbe: func(ctx context.Context, scope string) (bool, map[string]int64, error) {
calls++
if scope != "internal" {
t.Fatalf("scope=%s", scope)
}
return tc.empty, nil, tc.err
}}
recorder := httptest.NewRecorder()
libraryOverviewHandler(recorder, httptest.NewRequest(http.MethodGet, "/api/v1/library/overview?scope=internal", nil), config)
if recorder.Code != http.StatusOK {
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
}
var body libraryOverview
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
if calls != 1 {
t.Fatalf("target probe calls=%d, want 1", calls)
}
if body.Production.EmptyBaseline != tc.empty {
t.Fatalf("production=%+v", body.Production)
}
reasons := strings.Join(body.Gate.Reasons, "|")
if tc.want != "" && !strings.Contains(reasons, tc.want) {
t.Fatalf("reasons=%s", reasons)
}
if (tc.empty || tc.err != nil) && strings.Contains(reasons, "目标非空") {
t.Fatalf("not proven nonempty: %s", reasons)
}
if body.Gate.SyncAllowed {
t.Fatal("empty target must not bypass authorization and write gates")
}
})
}
}