Files
Agent-System/internal/supplementimpact/service_test.go
T
weichen b5a782bb38 feat(chat): dialogue triggers for comments/reopen/pending-supplements with mistouch guards
Wire intent→coordinator DocResolver confirm cards→action_executor kinds for comment, reopen, pending-supplements query, and real submit-review execution.
2026-09-25 20:14:10 +00:00

180 lines
5.8 KiB
Go

package supplementimpact_test
import (
"context"
"testing"
"time"
"chat-agent-system/internal/docgov"
"chat-agent-system/internal/notification"
"chat-agent-system/internal/supplementimpact"
"chat-agent-system/internal/traceimpact"
)
type stubAnalyzer struct{ report *traceimpact.Report }
func (s stubAnalyzer) Analyze(_ context.Context, _ int64, _ traceimpact.DocRef, _ []traceimpact.DocRef) (*traceimpact.Report, error) {
return s.report, nil
}
type stubDocs struct {
docs map[string]*docgov.DocumentRecord
}
func (s stubDocs) GetDocument(_ context.Context, id string) (*docgov.DocumentRecord, error) {
return s.docs[id], nil
}
func TestCreateBatchSnapshotsClaimAndBusy(t *testing.T) {
store := supplementimpact.NewMemStore()
claimedAt := time.Now()
svc := &supplementimpact.Service{
Store: store,
Analyze: stubAnalyzer{report: &traceimpact.Report{
CoverageStatus: "confirmed_impact",
Impacts: []traceimpact.Impact{
{DocID: "doc_a", ItemKey: "UR-1", ImpactType: "confirmed_impact", Depth: 1, Reason: "hit"},
},
}},
Docs: stubDocs{docs: map[string]*docgov.DocumentRecord{
"doc_a": {DocID: "doc_a", Title: "需求", AuthorID: 9, AuthorClaimed: true, AuthorClaimedAt: &claimedAt},
}},
}
b, err := svc.CreateFromSupplement(context.Background(), 1, 2, "sess", "补充市场", "UR-1 目标市场改为欧盟")
if err != nil {
t.Fatal(err)
}
if len(b.Items) != 1 || !b.Items[0].AuthorClaimedSnapshot {
t.Fatalf("want claimed snapshot, got %+v", b.Items)
}
}
type stubPropose struct {
lastDoc string
prop *docgov.ChangeProposal
}
func (s *stubPropose) ProposeModification(_ context.Context, docID, _, _, _, _ string) (*docgov.ChangeProposal, error) {
s.lastDoc = docID
if s.prop == nil {
s.prop = &docgov.ChangeProposal{ProposalID: "prop_test_1", DocID: docID, Status: docgov.ProposalPendingAuthorApproval}
}
return s.prop, nil
}
type stubNotify struct{ n int }
func (s *stubNotify) UpsertDocumentDigest(_ context.Context, _ notification.UpsertDigestParams) error {
s.n++
return nil
}
func TestDecideClaimedProducesProposal(t *testing.T) {
store := supplementimpact.NewMemStore()
claimedAt := time.Now()
prop := &stubPropose{}
notify := &stubNotify{}
svc := &supplementimpact.Service{
Store: store,
Analyze: stubAnalyzer{report: &traceimpact.Report{
CoverageStatus: "confirmed_impact",
Impacts: []traceimpact.Impact{
{DocID: "doc_a", ItemKey: "UR-1", ImpactType: "confirmed_impact", Depth: 1, Reason: "hit"},
},
}},
Docs: stubDocs{docs: map[string]*docgov.DocumentRecord{
"doc_a": {DocID: "doc_a", Title: "需求", AuthorID: 9, AuthorClaimed: true, AuthorClaimedAt: &claimedAt, CurrentState: docgov.StateDrafting},
}},
Propose: prop,
Notify: notify,
}
b, err := svc.CreateFromSupplement(context.Background(), 1, 2, "sess", "补充市场", "UR-1 目标市场改为欧盟")
if err != nil {
t.Fatal(err)
}
out, err := svc.Decide(context.Background(), supplementimpact.DecisionCommand{
BatchID: b.BatchID, ActorUserID: 2, Kind: "confirm",
SelectedDocIDs: []string{"doc_a"}, DecisionNonce: b.DecisionNonce,
DecisionDigest: "d1", ExpectedRevision: b.Revision,
})
if err != nil {
t.Fatal(err)
}
if len(out.Items) != 1 || out.Items[0].Outcome != supplementimpact.OutcomeProposalPending {
t.Fatalf("want proposal_pending, got %+v", out.Items)
}
if prop.lastDoc != "doc_a" || notify.n != 1 {
t.Fatalf("propose/notify not called: doc=%s notify=%d", prop.lastDoc, notify.n)
}
}
type stubPending struct {
n int
last docgov.RecordPendingSupplementInput
}
func (s *stubPending) RecordPendingSupplement(_ context.Context, in docgov.RecordPendingSupplementInput) (*docgov.PendingSupplement, error) {
s.n++
s.last = in
return &docgov.PendingSupplement{DocID: in.DocID, Status: docgov.PendingSupplementPending}, nil
}
type capturingNotify struct {
params []notification.UpsertDigestParams
}
func (s *capturingNotify) UpsertDocumentDigest(_ context.Context, p notification.UpsertDigestParams) error {
s.params = append(s.params, p)
return nil
}
func TestDecideLockedSkipsAndRecordsPending(t *testing.T) {
store := supplementimpact.NewMemStore()
pending := &stubPending{}
notify := &capturingNotify{}
svc := &supplementimpact.Service{
Store: store,
Analyze: stubAnalyzer{report: &traceimpact.Report{
CoverageStatus: "confirmed_impact",
Impacts: []traceimpact.Impact{
{DocID: "doc_locked", ItemKey: "UR-1", ImpactType: "confirmed_impact", Depth: 1, Reason: "locked hit"},
},
}},
Docs: stubDocs{docs: map[string]*docgov.DocumentRecord{
"doc_locked": {
DocID: "doc_locked", Title: "锁定文档", AuthorID: 1, ReviewerID: 7,
CurrentState: docgov.StateReviewing, IsLocked: true, AuthorClaimed: true,
},
}},
Pending: pending,
Notify: notify,
}
b, err := svc.CreateFromSupplement(context.Background(), 1, 2, "sess", "锁定期补充", "UR-1 变更")
if err != nil {
t.Fatal(err)
}
if len(b.Items) != 1 || !b.Items[0].LockedSnapshot || b.Items[0].BusySnapshot {
t.Fatalf("want locked snapshot without busy, got %+v", b.Items[0])
}
out, err := svc.Decide(context.Background(), supplementimpact.DecisionCommand{
BatchID: b.BatchID, ActorUserID: 2, Kind: "confirm",
SelectedDocIDs: []string{"doc_locked"}, DecisionNonce: b.DecisionNonce,
DecisionDigest: "d1", ExpectedRevision: b.Revision,
})
if err != nil {
t.Fatal(err)
}
if out.Items[0].Outcome != supplementimpact.OutcomeSkippedLocked {
t.Fatalf("want skipped_locked, got %s", out.Items[0].Outcome)
}
if pending.n != 1 || pending.last.DocID != "doc_locked" {
t.Fatalf("pending ledger not recorded: %+v", pending)
}
if len(notify.params) != 1 || notify.params[0].UserID != 7 {
t.Fatalf("want reviewer notified, got %+v", notify.params)
}
if notify.params[0].DigestKind != notification.DigestKindPendingSupplements {
t.Fatalf("want pending_supplements digest, got %s", notify.params[0].DigestKind)
}
}