package docnotify import ( "context" "testing" "chat-agent-system/internal/docgov" "chat-agent-system/internal/docrevision" "chat-agent-system/internal/notification" ) type stubNotify struct { available bool calls int lastType string } func (s *stubNotify) Available() bool { return s != nil && s.available } func (s *stubNotify) EmitDocumentEvent(_ context.Context, eventType, _, _, _, _ string, _ int64, _ notification.DocumentEventPayload) (bool, error) { s.calls++ s.lastType = eventType return true, nil } type stubDocs struct { doc *docgov.DocumentRecord } func (s stubDocs) GetDocument(_ context.Context, _ string) (*docgov.DocumentRecord, error) { return s.doc, nil } func TestBindOnce(t *testing.T) { rev := &docrevision.Service{} sub := &Subscriber{Notify: &stubNotify{available: true}} if !Bind(rev, sub) { t.Fatal("first bind") } if Bind(rev, sub) { t.Fatal("second bind must be a no-op") } if rev.OnOutbox == nil { t.Fatal("hook missing") } } func TestHandleCandidateReady(t *testing.T) { n := &stubNotify{available: true} sub := &Subscriber{ Notify: n, Docs: stubDocs{doc: &docgov.DocumentRecord{AuthorID: 9, ProjectID: 4}}, } if err := sub.Handle(context.Background(), notification.EventDocAgentCandidateReady, map[string]any{ "doc_id": "d1", "candidate_id": int64(3), }); err != nil { t.Fatal(err) } if n.calls != 1 || n.lastType != notification.EventDocAgentCandidateReady { t.Fatalf("calls=%d type=%s", n.calls, n.lastType) } } func TestHandleSkipsWhenConsumerLive(t *testing.T) { n := &stubNotify{available: true} sub := &Subscriber{ Notify: n, Docs: stubDocs{doc: &docgov.DocumentRecord{AuthorID: 9, ProjectID: 4}}, SkipWrite: func(context.Context) bool { return true }, } if err := sub.Handle(context.Background(), notification.EventDocAgentCandidateReady, map[string]any{ "doc_id": "d1", "candidate_id": int64(3), }); err != nil { t.Fatal(err) } if n.calls != 0 { t.Fatalf("live consumer must skip hook write, calls=%d", n.calls) } } func TestHandleWritesWhenNotLive(t *testing.T) { n := &stubNotify{available: true} sub := &Subscriber{ Notify: n, Docs: stubDocs{doc: &docgov.DocumentRecord{AuthorID: 9, ProjectID: 4}}, SkipWrite: func(context.Context) bool { return false }, } if err := sub.Handle(context.Background(), notification.EventDocAgentCandidateReady, map[string]any{ "doc_id": "d1", "candidate_id": int64(3), }); err != nil { t.Fatal(err) } if n.calls != 1 { t.Fatalf("off/shadow must keep hook write, calls=%d", n.calls) } } func TestHandleSkipsUnknownAndNoRecipient(t *testing.T) { n := &stubNotify{available: true} sub := &Subscriber{Notify: n} if err := sub.Handle(context.Background(), "document.unknown", map[string]any{"doc_id": "d1"}); err != nil { t.Fatal(err) } if n.calls != 0 { t.Fatalf("unknown event must not emit, calls=%d", n.calls) } }