Files
knowledge-governance/internal/librarydiff/diff_test.go
T
姚章浩 e313ae12f2
build / build (push) Successful in 1m18s
feat(library): 真实差异比对 + 孤儿作业回收 + 验收矩阵与台账
为什么:发布管理的差异一直显示"未接入",业务作业也可能因队列丢作业而永久占锁;
本轮把这两件做成真实实现,并把 30 个验收项与 CI/版本身份落成可审计文档。

- internal/librarydiff:稳定文档 ID + 排除易变列的整行摘要做增改撤销;overview 在
  生产权威版本与测试封存都在时给出 computed 差异并标注两侧快照,缺一侧仍是 unknown。
- 发布管理"本次变更"列文档身份与比对版本;无差异时明确写"没有文档差异"。
- ReconcileLibraryJobs:worker 启动清理队列已无对应作业的孤儿,避免永久互斥。
- docs/library-acceptance-matrix.md 与 docs/release-ledger.md 固定证据与版本事实。
2026-10-28 14:39:17 +08:00

82 lines
2.7 KiB
Go

package librarydiff
import (
"strings"
"testing"
)
func rows(t *testing.T, body string) []map[string]any {
t.Helper()
parsed, err := ReadDocuments(strings.NewReader(body))
if err != nil {
t.Fatalf("ReadDocuments: %v", err)
}
return parsed
}
func TestCompareUsesStableIdentityAndIgnoresVolatileColumns(t *testing.T) {
base := rows(t, `[
{"id":"doc-a","title":"机械臂报警","content":"正文一","updated_at":"2026-10-01T00:00:00Z"},
{"id":"doc-b","title":"示教器","content":"正文二","updated_at":"2026-10-01T00:00:00Z"},
{"id":"doc-c","title":"将删除","content":"正文三"}
]`)
target := rows(t, `[
{"id":"doc-a","title":"机械臂报警","content":"正文一","updated_at":"2026-10-28T00:00:00Z"},
{"id":"doc-b","title":"示教器","content":"正文二(已更新)","updated_at":"2026-10-01T00:00:00Z"},
{"id":"doc-d","title":"新增文档","content":"正文四"}
]`)
result, err := Compare(base, target)
if err != nil {
t.Fatalf("Compare: %v", err)
}
// doc-a 只有 updated_at 变了:不算更新。
if len(result.Added) != 1 || result.Added[0].ID != "doc-d" {
t.Fatalf("added = %+v, want doc-d", result.Added)
}
if len(result.Updated) != 1 || result.Updated[0].ID != "doc-b" {
t.Fatalf("updated = %+v, want doc-b", result.Updated)
}
if len(result.Removed) != 1 || result.Removed[0].ID != "doc-c" {
t.Fatalf("removed = %+v, want doc-c", result.Removed)
}
if result.BaseCount != 3 || result.TargetCount != 3 {
t.Fatalf("counts = %d/%d, want 3/3", result.BaseCount, result.TargetCount)
}
}
func TestCompareTreatsTitleChangeAsUpdate(t *testing.T) {
base := rows(t, `[{"id":"doc-a","title":"旧标题","content":"正文"}]`)
target := rows(t, `[{"id":"doc-a","title":"新标题","content":"正文"}]`)
result, err := Compare(base, target)
if err != nil {
t.Fatalf("Compare: %v", err)
}
if len(result.Updated) != 1 || result.Updated[0].Title != "新标题" {
t.Fatalf("title change must count as update: %+v", result.Updated)
}
}
func TestCompareRejectsDuplicateIDs(t *testing.T) {
base := rows(t, `[{"id":"doc-a","title":"x"}]`)
dup := rows(t, `[{"id":"doc-a","title":"x"},{"id":"doc-a","title":"y"}]`)
if _, err := Compare(base, dup); err == nil {
t.Fatal("duplicate knowledge IDs must be rejected, not silently collapsed")
}
}
func TestCompareEmptyBaseMarksEverythingAdded(t *testing.T) {
result, err := Compare(nil, rows(t, `[{"id":"doc-a","title":"x"}]`))
if err != nil {
t.Fatalf("Compare: %v", err)
}
if len(result.Added) != 1 || len(result.Removed) != 0 {
t.Fatalf("empty base must be all added: %+v", result)
}
}
func TestReadDocumentsRejectsNonArray(t *testing.T) {
if _, err := ReadDocuments(strings.NewReader(`{"id":"x"}`)); err == nil {
t.Fatal("object payload must be rejected")
}
}