package docrevision_test import ( "context" "testing" "chat-agent-system/internal/docrevision" ) func TestCommitAgentVersion_IdempotentWhenAlreadyCommitted(t *testing.T) { ctx := context.Background() svc := docrevision.NewService(nil) docID := "doc_commit_idem" base := "# base\n\n法规 GB9706\n" st, err := svc.OpenTemplateVersion(ctx, docID, base, docrevision.ActorSnapshot{DisplayName: "t"}, 1, "") if err != nil { t.Fatal(err) } cand, _, err := svc.CreateAgentCandidate(ctx, docrevision.AgentCandidateRequest{ DocID: docID, RunID: "idem-1", InputSnapshotID: st.HeadSnapshotID, OutputMarkdown: "# base\n\n", ActorKind: docrevision.ActorAgentSquad, }) if err != nil && cand == nil { t.Fatal(err) } if cand.Status != docrevision.CandidateNeedsConfirmation { t.Fatalf("want needs_confirmation got %s", cand.Status) } st1, err := svc.CommitAgentVersion(ctx, docID, cand.ID, docrevision.CommitAgentDecision{ Decision: "commit", Confirmed: true, ActorUserID: 1, ExpectedInputSHA: st.HeadSHA256, }) if err != nil { t.Fatalf("first commit: %v", err) } got, err := svc.GetCandidate(ctx, docID, cand.ID) if err != nil || got.Status != docrevision.CandidateCommitted { t.Fatalf("want committed got %+v err=%v", got, err) } // Retry with same hashes must succeed (projection/governance compensation path). st2, err := svc.CommitAgentVersion(ctx, docID, cand.ID, docrevision.CommitAgentDecision{ Decision: "commit", Confirmed: true, ActorUserID: 1, ExpectedInputSHA: st.HeadSHA256, }) if err != nil { t.Fatalf("idempotent recommit: %v", err) } if st2 == nil || st2.HeadSHA256 == "" || st2.HeadSHA256 != st1.HeadSHA256 { t.Fatalf("head mismatch after idempotent commit: %#v vs %#v", st2, st1) } // Wrong expected output hash must not silently succeed. outSHA := "" if got.OutputSnapshotID != nil { // CommitAgentVersion validates ExpectedOutputSHA against output snapshot. // Use a bogus hash. outSHA = "deadbeef" } _, err = svc.CommitAgentVersion(ctx, docID, cand.ID, docrevision.CommitAgentDecision{ Decision: "commit", Confirmed: true, ActorUserID: 1, ExpectedInputSHA: st.HeadSHA256, ExpectedOutputSHA: outSHA, }) if err == nil { t.Fatal("mismatched expected_output_sha must fail on idempotent path") } _, err = svc.CommitAgentVersion(ctx, docID, cand.ID, docrevision.CommitAgentDecision{ Decision: "reject", Confirmed: true, ActorUserID: 1, }) if err == nil { t.Fatal("reject after commit must fail") } }