Files
knowledge-governance/internal/platform/postgres/library_legacy_source_test.go
T
姚章浩 8febe12d7d test(library): 隔离数据契约与 r11 身份自然键矩阵
真实本机 PG/ES/MinIO:v071 目录镜像 10 模式、结构引用/游标 15 模式、
8 表×2 外部范围身份矩阵、13 模式自然键(含软删除与合法同 parent 正例)、
备份期间三注入、收尾故障与授权撤销两个真实治理 PG 用例。

需隔离服务的用例在 test-scope.json 显式声明:普通 CI 跳过不算通过。
2026-11-01 11:35:09 +08:00

153 lines
6.6 KiB
Go

package postgres
import (
"context"
"os"
"path/filepath"
"strings"
"testing"
"time"
"knowledge-governance/internal/governance"
"knowledge-governance/internal/libraryagent"
"knowledge-governance/internal/librarysnapshot"
)
func legacySourceFixture(t *testing.T, store *Store) (string, string, governance.LibrarySourceIdentity) {
t.Helper()
ctx := context.Background()
root := t.TempDir()
if e := os.Chmod(root, 0700); e != nil {
t.Fatal(e)
}
archive, e := librarysnapshot.Open(root, 1024*1024)
if e != nil {
t.Fatal(e)
}
defer archive.Close()
m := librarysnapshot.Manifest{ValidationPolicy: librarysnapshot.ArtifactAcceptancePolicy, Format: librarysnapshot.Format, Scope: "internal", TenantID: 10000, KnowledgeBaseID: "kb-legacy", CreatedAt: time.Now().UTC(), Compatibility: librarysnapshot.Compatibility{WeKnoraVersion: "v0.7.1", SchemaVersion: 74, RetrieverEngine: "elasticsearch_v8", EmbeddingModel: "text-embedding-v4", EmbeddingDimension: 1024, IndexMappingSHA256: strings.Repeat("a", 64)}}
for _, entry := range []string{"database/knowledges", "database/chunks", "database/resources", "database/resource_bindings", "database/knowledge_tags", "database/knowledge_tag_relations", "wiki/wiki_pages", "wiki/wiki_folders", "wiki/wiki_page_issues", "wiki/wiki_log_entries", "index/chunks"} {
p, e := archive.Put(strings.NewReader("[]"))
if e != nil {
t.Fatal(e)
}
x := strings.Split(entry, "/")
p.Kind = x[0]
p.Name = x[1]
m.Parts = append(m.Parts, p)
}
snapshot, e := archive.Seal(m)
if e != nil {
t.Fatal(e)
}
source := governance.LibrarySourceIdentity{Project: "historical-project", App: "historical-app", Scope: m.Scope, TenantID: m.TenantID, KnowledgeBaseID: m.KnowledgeBaseID}
exp := libraryagent.NewExpectation(source.Project, source.App, "/original/archive")
e = store.RecordLibraryVersion(ctx, governance.LibraryVersion{ID: governance.LibraryVersionID(governance.LibraryVersionTestSealed, m.Scope, snapshot), Scope: m.Scope, Kind: governance.LibraryVersionTestSealed, SnapshotID: snapshot, SourceJobID: "legacy-capture", CreatedAt: m.CreatedAt, Evidence: map[string]any{"expectation_fingerprint": exp.Fingerprint, "capture_job": "legacy", "agent_result": map[string]any{"snapshot_id": snapshot, "agent_identity": map[string]any{"project": source.Project, "source_app": source.App, "archive_root": "/original/archive", "expectation_fingerprint": exp.Fingerprint}}}})
if e != nil {
t.Fatal(e)
}
return root, snapshot, source
}
func TestContractLegacySourceAssociationPreservesOriginalEvidence(t *testing.T) {
store, _ := newIdentityStore(t)
ctx := context.Background()
root, snapshot, source := legacySourceFixture(t, store)
binder, ok := any(store).(interface {
BindLegacyLibrarySource(context.Context, string, string, governance.LibrarySourceIdentity, string, time.Time) error
})
if !ok {
t.Fatal("verified legacy source association unavailable")
}
original, e := os.ReadFile(filepath.Join(root, "versions", snapshot+".json"))
if e != nil {
t.Fatal(e)
}
if _, err := store.db.Exec(ctx, `UPDATE library_versions SET evidence=evidence || '{"large_proof":9223372036854775807}'::jsonb`); err != nil {
t.Fatal(err)
}
var originalEvidence string
if err := store.db.QueryRow(ctx, `SELECT evidence::text FROM library_versions`).Scan(&originalEvidence); err != nil {
t.Fatal(err)
}
id, _ := source.ID()
if _, found, e := store.LibrarySourceCandidate(ctx, source.Scope, id); e != nil || found {
t.Fatal("legacy unbound candidate adopted without explicit association", e)
}
now := time.Now().UTC()
if e = binder.BindLegacyLibrarySource(ctx, snapshot, root, source, "admin", now); e != nil {
t.Fatal(e)
}
candidate, found, e := store.LibrarySourceCandidate(ctx, source.Scope, id)
if e != nil || !found || candidate.SnapshotID != snapshot || candidate.SourceJobID != "legacy-capture" {
t.Fatal("association not readable", e)
}
if candidate.Evidence["capture_job"] != "legacy" {
t.Fatal("original evidence overwritten")
}
if e = binder.BindLegacyLibrarySource(ctx, snapshot, root, source, "another-admin", now.Add(time.Second)); e != nil {
t.Fatal("idempotent association", e)
}
var preserved bool
if err := store.db.QueryRow(ctx, `SELECT (evidence - 'source_id' - 'source_identity' - 'legacy_source_binding')=$1::jsonb FROM library_versions`, originalEvidence).Scan(&preserved); err != nil || !preserved {
t.Fatal("historical evidence changed, including bigint", err)
}
var bindingActor string
if err := store.db.QueryRow(ctx, `SELECT evidence->'legacy_source_binding'->>'actor' FROM library_versions`).Scan(&bindingActor); err != nil || bindingActor != "admin" {
t.Fatal("association replay rewrote audit actor", err)
}
changed := source
changed.App = "other-app"
if e = binder.BindLegacyLibrarySource(ctx, snapshot, root, changed, "admin", now); e == nil {
t.Fatal("source rebound")
}
after, e := os.ReadFile(filepath.Join(root, "versions", snapshot+".json"))
if e != nil || string(original) != string(after) {
t.Fatal("historical manifest changed", e)
}
}
func TestContractLegacySourceAssociationRejectsInsufficientProof(t *testing.T) {
for _, scenario := range []string{"project", "tenant", "fingerprint", "missing-origin", "snapshot", "partial-binding"} {
t.Run(scenario, func(t *testing.T) {
store, _ := newIdentityStore(t)
ctx := context.Background()
root, snapshot, source := legacySourceFixture(t, store)
var mutation string
switch scenario {
case "project":
source.Project = "other-project"
case "tenant":
source.TenantID++
case "fingerprint":
mutation = `UPDATE library_versions SET evidence=jsonb_set(evidence,'{expectation_fingerprint}','"wrong"'::jsonb)`
case "missing-origin":
mutation = `UPDATE library_versions SET evidence=evidence-'agent_result'`
case "snapshot":
mutation = `UPDATE library_versions SET evidence=jsonb_set(evidence,'{agent_result,snapshot_id}','"different"'::jsonb)`
case "partial-binding":
mutation = `UPDATE library_versions SET evidence=evidence || '{"source_identity":{}}'::jsonb`
}
if mutation != "" {
if _, e := store.db.Exec(ctx, mutation); e != nil {
t.Fatal(e)
}
}
var before string
if e := store.db.QueryRow(ctx, `SELECT evidence::text FROM library_versions`).Scan(&before); e != nil {
t.Fatal(e)
}
if e := store.BindLegacyLibrarySource(ctx, snapshot, root, source, "admin", time.Now()); e == nil {
t.Fatal("unproven source accepted")
}
var after string
if e := store.db.QueryRow(ctx, `SELECT evidence::text FROM library_versions`).Scan(&after); e != nil {
t.Fatal(e)
}
if before != after {
t.Fatal("rejected association changed historical evidence")
}
})
}
}