package main import ( "context" "encoding/json" "net/http" "net/http/httptest" "strings" "testing" "time" "git.cin.su/m/Assistant/contracts/continuity" "git.cin.su/m/Assistant/contracts/identity" continuitydomain "git.cin.su/m/Assistant/modules/continuity/domain" ) func TestCreatorResponseEndpointAppliesCompleteThroughContinuitySemantics(t *testing.T) { core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/v1/creator/sessions/current" || r.Header.Get("Authorization") != "Bearer ok" { t.Fatal("unexpected identity validation") } _ = json.NewEncoder(w).Encode(identity.CreatorSession{ActorID: "actor-1", PrincipalID: "principal-1"}) })) defer core.Close() identityClient, err := newCreatorIdentityClient(core.URL, core.Client()) if err != nil { t.Fatal(err) } repository := &fakeResponseRepository{candidates: []continuitydomain.ResponseCandidate{{ Attention: continuity.AttentionItem{ID: "attention-1", OwnerPrincipalID: "principal-1", ScopeID: "scope-1", SourceKind: continuity.AttentionSourceProspectiveReminder, SourceProspectiveID: "prospective-1", State: continuity.AttentionDelivered, Summary: "проверить базу", Revision: 1}, Prospective: continuity.ProspectiveItem{ID: "prospective-1", OwnerPrincipalID: "principal-1", ScopeID: "scope-1", State: continuity.ProspectiveDelivered, Summary: "проверить базу", Revision: 1}, }}} mux := http.NewServeMux() registerCreatorResponseRoute(mux, identityClient, repository, func() time.Time { return time.Unix(100, 0) }) request := httptest.NewRequest(http.MethodPost, "/v1/creator/responses", strings.NewReader(`{"SourceUtteranceID":"utterance-1","SourceIntentID":"intent-1","Message":"Готово","RequestedAtUnixMillis":100000}`)) request.Header.Set("Authorization", "Bearer ok") recorder := httptest.NewRecorder() mux.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated || repository.applied.Operation != continuity.ResponseComplete || repository.applied.Outcome != continuity.ResponseOutcomeUserReportedComplete { t.Fatalf("status=%d applied=%#v body=%s", recorder.Code, repository.applied, recorder.Body.String()) } } func TestCreatorResponseEndpointRecordsAmbiguousWithoutSilentTargetMutation(t *testing.T) { core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _ = json.NewEncoder(w).Encode(identity.CreatorSession{ActorID: "actor-1", PrincipalID: "principal-1"}) })) defer core.Close() identityClient, err := newCreatorIdentityClient(core.URL, core.Client()) if err != nil { t.Fatal(err) } repository := &fakeResponseRepository{candidates: []continuitydomain.ResponseCandidate{ {Attention: continuity.AttentionItem{ID: "attention-1", SourceProspectiveID: "prospective-1", State: continuity.AttentionDelivered, Summary: "первое"}}, {Attention: continuity.AttentionItem{ID: "attention-2", SourceProspectiveID: "prospective-2", State: continuity.AttentionDelivered, Summary: "второе"}}, }} mux := http.NewServeMux() registerCreatorResponseRoute(mux, identityClient, repository, func() time.Time { return time.Unix(100, 0) }) request := httptest.NewRequest(http.MethodPost, "/v1/creator/responses", strings.NewReader(`{"SourceUtteranceID":"utterance-1","SourceIntentID":"intent-1","Message":"Готово","RequestedAtUnixMillis":100000}`)) request.Header.Set("Authorization", "Bearer ok") recorder := httptest.NewRecorder() mux.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated || repository.applied.ResolutionState != continuity.ResponseAmbiguous || repository.applied.TargetAttentionID != "" { t.Fatalf("status=%d applied=%#v body=%s", recorder.Code, repository.applied, recorder.Body.String()) } } func TestCreatorResponseEndpointContinuesDelegationAttentionViaNaturalMessage(t *testing.T) { core := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { _ = json.NewEncoder(w).Encode(identity.CreatorSession{ActorID: "actor-1", PrincipalID: "principal-1"}) })) defer core.Close() identityClient, err := newCreatorIdentityClient(core.URL, core.Client()) if err != nil { t.Fatal(err) } repository := &fakeResponseRepository{candidates: []continuitydomain.ResponseCandidate{ {Attention: continuity.AttentionItem{ ID: "work:delegation:delegation-1", OwnerPrincipalID: "principal-1", ScopeID: "scope-1", SourceKind: continuity.AttentionSourceAgentDelegation, SourceExternalRef: "work:delegation:delegation-1", State: continuity.AttentionPending, Summary: "Нужен твой выбор по задаче coding-agent: docs/PROJECT_STATUS.md", Revision: 1, }}, {Attention: continuity.AttentionItem{ID: "attention-1", SourceProspectiveID: "prospective-1", State: continuity.AttentionDelivered, Summary: "старое напоминание"}}, }} mux := http.NewServeMux() registerCreatorResponseRoute(mux, identityClient, repository, func() time.Time { return time.Unix(100, 0) }) request := httptest.NewRequest(http.MethodPost, "/v1/creator/responses", strings.NewReader(`{"SourceUtteranceID":"utterance-1","SourceIntentID":"intent-1","Message":"Продолжай задачу coding-agent по docs/PROJECT_STATUS.md","RequestedAtUnixMillis":100000}`)) request.Header.Set("Authorization", "Bearer ok") recorder := httptest.NewRecorder() mux.ServeHTTP(recorder, request) if recorder.Code != http.StatusCreated || repository.applied.Operation != continuity.ResponseContinue || repository.applied.TargetExternalRef != "work:delegation:delegation-1" || repository.applied.Outcome != continuity.ResponseOutcomeContinued { t.Fatalf("status=%d applied=%#v body=%s", recorder.Code, repository.applied, recorder.Body.String()) } } type fakeResponseRepository struct { candidates []continuitydomain.ResponseCandidate applied continuity.HumanResponse } func (f *fakeResponseRepository) ResponseCandidates(context.Context, identity.PrincipalID, int) ([]continuitydomain.ResponseCandidate, error) { return f.candidates, nil } func (f *fakeResponseRepository) ApplyHumanResponse(_ context.Context, response continuity.HumanResponse) error { f.applied = response return nil }