package docrevision_test import ( "context" "testing" "chat-agent-system/internal/docrevision" ) func TestEnsureBootstrapIdempotentAndReadable(t *testing.T) { svc := docrevision.NewService(nil) ctx := context.Background() st, err := svc.EnsureBootstrap(ctx, docrevision.BootstrapRequest{ DocID: "doc_proj143_srs", Kind: "controlled", Markdown: "# SRS\n\nbody\n", ProjectID: 143, WorkDir: "workspaces/p143/srs", LifecycleState: "drafting", Actor: docrevision.ActorSnapshot{DisplayName: "bootstrap"}, }) if err != nil { t.Fatal(err) } if st.CurrentVersionNo != 1 || st.HeadRevisionNo != 0 { t.Fatalf("unexpected state %+v", st) } st2, err := svc.EnsureBootstrap(ctx, docrevision.BootstrapRequest{ DocID: "doc_proj143_srs", Markdown: "# other\n", ProjectID: 143, }) if err != nil { t.Fatal(err) } if st2.HeadSHA256 != st.HeadSHA256 { t.Fatalf("bootstrap should be idempotent") } got, err := svc.GetRevisionState(ctx, "doc_proj143_srs") if err != nil || got.ETag == "" { t.Fatalf("get after bootstrap: %v %+v", err, got) } } func TestEnsureBootstrapNeedsContent(t *testing.T) { svc := docrevision.NewService(nil) _, err := svc.EnsureBootstrap(context.Background(), docrevision.BootstrapRequest{DocID: "doc-upload-x"}) if err != docrevision.ErrNeedsContent { t.Fatalf("want ErrNeedsContent got %v", err) } } func TestDeterministicChangeReportNoStaticFiction(t *testing.T) { before := docrevision.CanonicalizeMarkdown("# A\n\n旧文\n") after := docrevision.CanonicalizeMarkdown("# A\n\n新文 REQ-1\n") fb := docrevision.BuildBlockMap(before, nil) ab := docrevision.BuildBlockMap(after, fb) diff := docrevision.ComputeDiff(before, after, fb, ab) from := docrevision.SHA256Hex(before) to := docrevision.SHA256Hex(after) raw := docrevision.BuildDeterministicChangeReportJSON(from, to, docrevision.RiskHigh, nil, diff) if raw == "" { t.Fatal("empty report") } rep, err := docrevision.ValidateChangeReportJSON(raw, from, to, docrevision.RiskHigh, nil, diff.Hunks) if err != nil { t.Fatal(err) } if len(rep.Summary) == 0 { t.Fatal("expected deterministic summary") } for _, s := range rep.Summary { if s == "智能体交付物已生成" || s == "系统需求规格已更新" { t.Fatalf("static fiction summary leaked: %q", s) } } } func TestHumanSaveAfterBootstrap(t *testing.T) { svc := docrevision.NewService(nil) ctx := context.Background() st, _ := svc.EnsureBootstrap(ctx, docrevision.BootstrapRequest{ DocID: "doc_edit", Markdown: "# A\n", ProjectID: 1, LifecycleState: "drafting", }) r, err := svc.CommitHumanRevision(ctx, docrevision.HumanRevisionRequest{ DocID: "doc_edit", Markdown: "# A\n\nedit\n", ExpectedHeadSnapshot: &st.HeadSnapshotID, ExpectedRevisionNo: &st.HeadRevisionNo, IdempotencyKey: "e1", ActorUserID: 9, LifecycleState: "drafting", }) if err != nil { t.Fatal(err) } if r.State.HeadRevisionNo != 1 { t.Fatalf("want r1 got %d", r.State.HeadRevisionNo) } ov, err := svc.ManualOverlay(ctx, "doc_edit") if err != nil || ov == nil || (ov.Summary.Modifies+ov.Summary.Inserts) == 0 { t.Fatalf("manual overlay empty: %+v %v", ov, err) } }