Files
knowledge-governance/internal/releaseadapter/v071/document_export_test.go
T
zheromiku dcdc8d1ddd feat: 知识治理平台(含已验证发布/回滚契约)
全栈知识治理平台首次入库(P1-P10 + 发布链路模拟验证闭环):

- 治理核心:源目录/变更集/人工审核/发布批次/发布执行(fail-closed 闸口)
- 测试通道:WeKnora 测试 KB 上传/轮询/验收;poll 完成时写入
  parser_generation/index_generation,修复 manual/seed 路径发布闸口永久拦截
- 发布适配器 v071(本地 source=测试角色 / 云端 target=生产角色 模拟验证通过):
  * ES 导出/导入 mapping 自适应(.keyword 子字段、可选 tenant_id)
  * OSS locator 支持嵌套 scheme;chunks.seq_id 由目标序列分配
  * bundle 归档同身份重试原子替换;verifier 按 bundle chunk_type 计数
  * 回滚 job payload 支持 prior_execution_id(strict 解码)
- 三通道发布+回滚演练实测:publish 4/4 verified,检索命中,
  rollback 4/4 rolled_back,目标端 PG is_enabled=false、ES is_enabled=false
- 前端 A·Data-console 风格全站统一,120 用例通过
- 门禁:go vet / go test 全包通过,gofmt 干净
2026-09-30 10:54:06 +08:00

102 lines
5.9 KiB
Go

package v071
import (
"context"
"strings"
"testing"
"time"
"knowledge-governance/internal/releasebundle"
releaseworkflow "knowledge-governance/internal/workflows/release"
)
func TestDocumentExporterBuildsSealedThreeLaneBundle(t *testing.T) {
pg := PostgresSnapshot{Tables: []TableSnapshot{
newTableSnapshot("knowledges", mustCanonicalRows(t, `{"id":"knowledge-safe"}`)),
newTableSnapshot("chunks", mustCanonicalRows(t, `{"id":"chunk-safe"}`)),
newTableSnapshot("knowledge_tag_relations", nil),
newTableSnapshot("resources", nil),
newTableSnapshot("resource_bindings", nil),
}}
search := SearchSnapshot{Documents: []SearchDocument{{ID: "es-safe", Source: mustCanonicalRows(t, `{"knowledge_id":"knowledge-safe"}`)[0]}}, MappingSHA256: strings.Repeat("a", 64), DocumentsSHA256: strings.Repeat("b", 64)}
objects := ObjectSnapshot{Objects: []AssetObject{}, TotalBytes: 0, SHA256: strings.Repeat("c", 64)}
exporter, err := NewDocumentExporter(DocumentExporterConfig{Contract: &stubSourceContract{contract: sourceContractFixture(strings.Repeat("a", 64))}, Postgres: &stubPGExporter{snapshot: pg}, Search: &stubSearchExporter{snapshot: search}, Objects: &stubObjectExporter{snapshot: objects}, TenantID: 10000, EmbeddingModel: "bge-m3", Now: func() time.Time { return time.Date(2026, 9, 6, 10, 0, 0, 0, time.UTC) }})
if err != nil {
t.Fatal(err)
}
bundleValue, err := exporter.Export(context.Background(), releaseworkflow.PlanItem{ReleaseExecutionID: "rx-safe", PublicationID: "tp-safe", SourceItemVersionID: "sv-safe", SourceRevision: "source-v1", ParseRevision: "parser-v1@index-v1", ReleaseRevision: "review-v1", TestKnowledgeBaseID: "kb-safe", KnowledgeID: "knowledge-safe", DecidedVisibility: "internal"})
if err != nil {
t.Fatal(err)
}
bundle, ok := bundleValue.(*DocumentBundle)
if !ok {
t.Fatalf("bundle type=%T", bundleValue)
}
manifest := bundle.ReleaseManifest()
if len(manifest.BundleSHA256) != 64 || manifest.Contract.MigrationVersion != 74 || manifest.Contract.EmbeddingDimension != 1024 || manifest.Search.DocumentCount != 1 || len(manifest.Tables) != 5 {
t.Fatalf("manifest=%#v", manifest)
}
if bundle.Postgres.Table("chunks").RowCount != 1 || len(bundle.Search.Documents) != 1 || len(bundle.Objects.Objects) != 0 {
t.Fatalf("bundle=%#v", bundle)
}
}
func TestDocumentExporterRejectsMissingSearchDocuments(t *testing.T) {
pg := PostgresSnapshot{Tables: []TableSnapshot{newTableSnapshot("knowledges", mustCanonicalRows(t, `{"id":"knowledge-safe"}`)), newTableSnapshot("chunks", mustCanonicalRows(t, `{"id":"chunk-safe"}`))}}
exporter, _ := NewDocumentExporter(DocumentExporterConfig{Contract: &stubSourceContract{contract: sourceContractFixture(strings.Repeat("a", 64))}, Postgres: &stubPGExporter{snapshot: pg}, Search: &stubSearchExporter{snapshot: SearchSnapshot{MappingSHA256: strings.Repeat("a", 64), DocumentsSHA256: strings.Repeat("b", 64)}}, Objects: &stubObjectExporter{snapshot: ObjectSnapshot{SHA256: strings.Repeat("c", 64)}}, TenantID: 10000, EmbeddingModel: "bge-m3", Now: time.Now})
_, err := exporter.Export(context.Background(), releaseworkflow.PlanItem{ReleaseExecutionID: "rx-safe", PublicationID: "tp-safe", SourceItemVersionID: "sv-safe", SourceRevision: "source-v1", ParseRevision: "parser-v1@index-v1", ReleaseRevision: "review-v1", TestKnowledgeBaseID: "kb-safe", KnowledgeID: "knowledge-safe", DecidedVisibility: "internal"})
if err == nil {
t.Fatal("missing search documents accepted")
}
}
func TestDocumentExporterRejectsSourceContractMappingDrift(t *testing.T) {
pg := PostgresSnapshot{Tables: []TableSnapshot{newTableSnapshot("knowledges", mustCanonicalRows(t, `{"id":"knowledge-safe"}`)), newTableSnapshot("chunks", mustCanonicalRows(t, `{"id":"chunk-safe"}`))}}
search := SearchSnapshot{Documents: []SearchDocument{{ID: "es-safe", Source: mustCanonicalRows(t, `{"knowledge_id":"knowledge-safe"}`)[0]}}, MappingSHA256: strings.Repeat("a", 64), DocumentsSHA256: strings.Repeat("b", 64)}
exporter, _ := NewDocumentExporter(DocumentExporterConfig{Contract: &stubSourceContract{contract: sourceContractFixture(strings.Repeat("c", 64))}, Postgres: &stubPGExporter{snapshot: pg}, Search: &stubSearchExporter{snapshot: search}, Objects: &stubObjectExporter{snapshot: ObjectSnapshot{SHA256: strings.Repeat("d", 64)}}, TenantID: 10000, EmbeddingModel: "bge-m3", Now: time.Now})
_, err := exporter.Export(context.Background(), releaseworkflow.PlanItem{ReleaseExecutionID: "rx-safe", PublicationID: "tp-safe", SourceItemVersionID: "sv-safe", SourceRevision: "source-v1", ParseRevision: "parser-v1@index-v1", ReleaseRevision: "review-v1", TestKnowledgeBaseID: "kb-safe", KnowledgeID: "knowledge-safe", DecidedVisibility: "internal"})
if err == nil {
t.Fatal("source mapping drift accepted")
}
}
type stubSourceContract struct {
contract releasebundle.TargetContract
err error
}
func (s *stubSourceContract) Contract(context.Context, releaseworkflow.PlanItem) (releasebundle.TargetContract, error) {
return s.contract, s.err
}
func sourceContractFixture(mapping string) releasebundle.TargetContract {
return releasebundle.TargetContract{WeKnoraVersion: releasebundle.WeKnoraVersionV071, MigrationVersion: releasebundle.WeKnoraMigrationV071, TenantID: 10000, KnowledgeBaseID: "kb-safe", RetrieverEngine: "elasticsearch_v8", EmbeddingModel: "bge-m3", EmbeddingDimension: releasebundle.EmbeddingDimensionV071, ElasticsearchMappingSHA256: mapping}
}
type stubPGExporter struct {
snapshot PostgresSnapshot
err error
}
func (s *stubPGExporter) Export(context.Context, PostgresIdentity) (PostgresSnapshot, error) {
return s.snapshot, s.err
}
type stubSearchExporter struct {
snapshot SearchSnapshot
err error
}
func (s *stubSearchExporter) Export(context.Context, SearchIdentity) (SearchSnapshot, error) {
return s.snapshot, s.err
}
type stubObjectExporter struct {
snapshot ObjectSnapshot
err error
}
func (s *stubObjectExporter) Export(context.Context, PostgresSnapshot) (ObjectSnapshot, error) {
return s.snapshot, s.err
}