package domain import ( "testing" "time" "git.cin.su/m/Assistant/contracts/identity" "git.cin.su/m/Assistant/contracts/interaction" ) func TestNewHumanUtteranceClassifiesQuestionAndRequiresCognition(t *testing.T) { conversation := interaction.Conversation{ID: "conversation-1", ActorID: "actor-1", PrincipalID: "principal-1", Channel: interaction.ChannelWebChat, State: interaction.ConversationOpen} utterance, intent, relations, err := NewHumanUtterance(conversation, interaction.ChatMessageRequest{Message: "Кто ты?"}, time.Unix(10, 0)) if err != nil { t.Fatal(err) } if utterance.SpeechAct != interaction.SpeechActQuestion || !NeedsCognition(utterance.SpeechAct) { t.Fatalf("expected cognitive question, got %s", utterance.SpeechAct) } if intent.Text != "Кто ты?" || len(relations) != 0 { t.Fatalf("unexpected intent or relations") } } func TestNewHumanUtteranceAppendsCorrectionRelation(t *testing.T) { conversation := interaction.Conversation{ID: "conversation-1", ActorID: "actor-1", PrincipalID: "principal-1", Channel: interaction.ChannelWebChat, State: interaction.ConversationOpen} utterance, _, relations, err := NewHumanUtterance(conversation, interaction.ChatMessageRequest{Message: "исправь прошлое", CorrectsUtteranceID: "utterance-0"}, time.Unix(10, 0)) if err != nil { t.Fatal(err) } if utterance.SpeechAct != interaction.SpeechActCorrection { t.Fatalf("expected correction, got %s", utterance.SpeechAct) } if len(relations) != 1 || relations[0].Kind != interaction.RelationCorrects || relations[0].ToUtteranceID != "utterance-0" { t.Fatalf("unexpected correction relation: %#v", relations) } } func TestNewHumanUtteranceClassifiesReminderAndCapabilityConnectionAsDirective(t *testing.T) { conversation := interaction.Conversation{ID: "conversation-1", ActorID: "actor-1", PrincipalID: "principal-1", Channel: interaction.ChannelWebChat, State: interaction.ConversationOpen} for _, message := range []string{"Напомни завтра позвонить врачу.", "Подключи интеграцию с неизвестным сервисом."} { utterance, _, _, err := NewHumanUtterance(conversation, interaction.ChatMessageRequest{Message: message}, time.Unix(10, 0)) if err != nil { t.Fatal(err) } if utterance.SpeechAct != interaction.SpeechActDirective { t.Fatalf("expected directive for %q, got %s", message, utterance.SpeechAct) } } } func TestDeterministicCandidateIsInvariantToMissingQuestionMarkForIdentityAndArithmetic(t *testing.T) { cases := []struct { message string act interaction.SpeechAct }{ {"ты кто", interaction.SpeechActQuestion}, {"Ты кто?", interaction.SpeechActQuestion}, {"а ты кто", interaction.SpeechActQuestion}, {"сколько будет 2 + 2", interaction.SpeechActQuestion}, {"сколько будет 2 + 2?", interaction.SpeechActQuestion}, {"2 плюс 2 сколько", interaction.SpeechActQuestion}, } for _, tc := range cases { candidate := BuildDeterministicCandidate(tc.message, "", time.Unix(10, 0).UnixMilli()) if candidate.PrimarySpeechAct != tc.act { t.Fatalf("expected %s for %q, got %s (%+v)", tc.act, tc.message, candidate.PrimarySpeechAct, candidate) } if candidate.Applicability == interaction.SemanticApplicable && candidate.NeedsCognition { continue } if !candidate.NeedsCognition { t.Fatalf("expected cognition-worthy candidate for %q, got %+v", tc.message, candidate) } } } func TestDeterministicCandidateTreatsModalExplanationRequestsAsCognitionWorthWithoutQuestionMark(t *testing.T) { for _, message := range []string{ "можешь рассказать об утре", "можешь расказать об утре", "можете объяснить это проще", "расскажи об утре", "про утро расскажешь", } { candidate := BuildDeterministicCandidate(message, "", time.Unix(10, 0).UnixMilli()) if candidate.PrimarySpeechAct != interaction.SpeechActQuestion && candidate.PrimarySpeechAct != interaction.SpeechActRequestToInvestigate { t.Fatalf("expected broader cognitive speech act for %q, got %+v", message, candidate) } if !candidate.NeedsCognition { t.Fatalf("expected cognition for %q, got %+v", message, candidate) } } } func TestDeterministicCandidateTreatsModelRequestAsDirectiveNeedingCognition(t *testing.T) { candidate := BuildDeterministicCandidate("обратись к модели gemma4 и спроси сколько звезд на небе", "", time.Unix(10, 0).UnixMilli()) if candidate.PrimarySpeechAct != interaction.SpeechActDirective { t.Fatalf("expected directive, got %+v", candidate) } if !candidate.NeedsCognition { t.Fatalf("expected cognition-worthy model request, got %+v", candidate) } } func TestInformationRequestsAreClassifiedOnceByInteraction(t *testing.T) { cases := []struct { message string kind interaction.InformationRequestKind }{ {"какие модели доступны?", interaction.InformationRequestCognitiveModelInventory}, {"что ты умеешь?", interaction.InformationRequestCapabilityInventory}, {"проверь состояние локального канала", interaction.InformationRequestCognitiveChannelStatus}, {"нет связи с ИИ, восстанови канал", interaction.InformationRequestCognitiveRecovery}, } for _, test := range cases { result, err := InterpretUtterance(interaction.HumanUtterance{Content: test.message}, nil, nil, time.Unix(10, 0)) if err != nil { t.Fatalf("interpret %q: %v", test.message, err) } if result.InformationRequestKind != test.kind { t.Fatalf("message=%q kind=%q want=%q", test.message, result.InformationRequestKind, test.kind) } } } func TestDeterministicCandidateKeepsFirstPersonCompletedWorkAsAssertion(t *testing.T) { candidate := BuildDeterministicCandidate("Я всё уже настроил.", "", time.Unix(10, 0).UnixMilli()) if candidate.PrimarySpeechAct != interaction.SpeechActAssertion { t.Fatalf("expected assertion, got %+v", candidate) } } func TestNewConversationRequiresActorAndPrincipal(t *testing.T) { _, err := NewConversation(identity.ActorID(""), identity.PrincipalID("principal-1"), time.Unix(10, 0)) if err == nil { t.Fatal("expected invalid principal") } }