A candidate used the same report later attached to the version, so a preview could be treated as the formal result. Proposed reports now close on replace or reject, and only the commit writes an authoritative successor.
151 lines
5.0 KiB
Go
151 lines
5.0 KiB
Go
package docrevision_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"chat-agent-system/internal/docrevision"
|
|
)
|
|
|
|
func TestProposedReportStaysInactiveUntilCommit(t *testing.T) {
|
|
svc := docrevision.NewService(nil)
|
|
svc.Notify = true
|
|
var events []string
|
|
committed := false
|
|
svc.OnOutbox = func(_ context.Context, eventType string, payload map[string]any) error {
|
|
events = append(events, eventType)
|
|
if !committed && (eventType == "document.downstream_impact_detected" || eventType == "document.agent_version_committed") {
|
|
t.Fatalf("candidate path emitted %s payload=%v", eventType, payload)
|
|
}
|
|
return nil
|
|
}
|
|
ctx := context.Background()
|
|
docID := "doc_proposed_split"
|
|
st, err := svc.OpenTemplateVersion(ctx, docID, "# base\n\nbody\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
first, firstRep, err := svc.CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
|
DocID: docID, RunID: "run-a", InputSnapshotID: st.HeadSnapshotID,
|
|
OutputMarkdown: "# base\n\nbody changed\n",
|
|
ActorKind: docrevision.ActorAgent,
|
|
})
|
|
if err != nil || first == nil || firstRep == nil {
|
|
t.Fatalf("first candidate: cand=%v rep=%v err=%v", first, firstRep, err)
|
|
}
|
|
if firstRep.Authority != docrevision.ReportProposed || firstRep.Lifecycle != docrevision.ReportActive || !firstRep.AllowsSuggestedActions() {
|
|
t.Fatalf("first report = %+v", firstRep)
|
|
}
|
|
for _, ev := range events {
|
|
if ev != "document.agent_candidate_ready" {
|
|
t.Fatalf("unexpected event before commit: %s", ev)
|
|
}
|
|
}
|
|
|
|
second, secondRep, err := svc.CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
|
DocID: docID, RunID: "run-b", InputSnapshotID: st.HeadSnapshotID,
|
|
OutputMarkdown: "# base\n\nbody changed again\n",
|
|
ActorKind: docrevision.ActorAgent,
|
|
})
|
|
if err != nil || second == nil {
|
|
t.Fatalf("second candidate: %v err=%v", second, err)
|
|
}
|
|
again, err := svc.GetChangeReport(ctx, docID, firstRep.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if again.Lifecycle != docrevision.ReportSuperseded || again.AllowsSuggestedActions() {
|
|
t.Fatalf("older estimate must be closed: %+v", again)
|
|
}
|
|
if secondRep.Lifecycle != docrevision.ReportActive || !secondRep.AllowsSuggestedActions() {
|
|
t.Fatalf("newer estimate must stay active: %+v", secondRep)
|
|
}
|
|
|
|
events = nil
|
|
committed = true
|
|
st2, err := svc.CommitAgentVersion(ctx, docID, second.ID, docrevision.CommitAgentDecision{
|
|
Decision: "commit", ActorUserID: 1, ExpectedInputSHA: st.HeadSHA256,
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if st2.CurrentVersionNo != 2 {
|
|
t.Fatalf("version = %+v", st2)
|
|
}
|
|
committedProposed, err := svc.GetChangeReport(ctx, docID, secondRep.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if committedProposed.Lifecycle != docrevision.ReportCommitted || committedProposed.AllowsSuggestedActions() {
|
|
t.Fatalf("proposed report must stop accepting actions: %+v", committedProposed)
|
|
}
|
|
if committedProposed.SuccessorReportID == nil || *committedProposed.SuccessorReportID == secondRep.ID {
|
|
t.Fatalf("missing distinct successor: %+v", committedProposed)
|
|
}
|
|
auth, err := svc.GetChangeReport(ctx, docID, *committedProposed.SuccessorReportID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if auth.Authority != docrevision.ReportAuthoritative || !auth.AllowsSuggestedActions() {
|
|
t.Fatalf("authoritative report = %+v", auth)
|
|
}
|
|
versions, err := svc.ListVersions(ctx, docID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var sawAuth bool
|
|
for _, v := range versions {
|
|
if v.ChangeReportID == nil {
|
|
continue
|
|
}
|
|
if *v.ChangeReportID == secondRep.ID {
|
|
t.Fatal("version must not point at the proposed estimate")
|
|
}
|
|
if *v.ChangeReportID == auth.ID {
|
|
sawAuth = true
|
|
}
|
|
}
|
|
if !sawAuth {
|
|
t.Fatalf("version missing authoritative report id %d in %+v", auth.ID, versions)
|
|
}
|
|
for _, ev := range events {
|
|
if ev == "document.agent_version_committed" {
|
|
t.Fatal("docrevision must not emit the formal commit event itself")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestRejectedProposedReportCannotSuggest(t *testing.T) {
|
|
svc := docrevision.NewService(nil)
|
|
ctx := context.Background()
|
|
docID := "doc_proposed_reject"
|
|
st, err := svc.OpenTemplateVersion(ctx, docID, "# base\n\n法规引用 GB9706\n", docrevision.ActorSnapshot{DisplayName: "t"}, 1, "")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cand, rep, err := svc.CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{
|
|
DocID: docID, RunID: "run-reject", InputSnapshotID: st.HeadSnapshotID,
|
|
OutputMarkdown: "# base\n\n",
|
|
ActorKind: docrevision.ActorAgent,
|
|
})
|
|
if cand == nil || rep == nil {
|
|
t.Fatalf("cand=%v rep=%v err=%v", cand, rep, err)
|
|
}
|
|
if _, err := svc.CommitAgentVersion(ctx, docID, cand.ID, docrevision.CommitAgentDecision{Decision: "reject", ActorUserID: 1}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := svc.GetChangeReport(ctx, docID, rep.ID)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got.Lifecycle != docrevision.ReportRejected || got.AllowsSuggestedActions() {
|
|
t.Fatalf("rejected report = %+v", got)
|
|
}
|
|
versions, _ := svc.ListVersions(ctx, docID)
|
|
for _, v := range versions {
|
|
if v.VersionNo > 1 {
|
|
t.Fatalf("reject must not open a new version: %+v", versions)
|
|
}
|
|
}
|
|
}
|