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 }} sourceID := bindSourceFixture(t, &config, store, &target) 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(sourceRequestBody(t, `{"scope":"internal","target_snapshot_id":"`+target+`"}`, sourceID))), 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") } }) } }