182 lines
8.3 KiB
Go
182 lines
8.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.cin.su/m/Assistant/contracts/capability"
|
|
"git.cin.su/m/Assistant/contracts/cognitive"
|
|
"git.cin.su/m/Assistant/contracts/identity"
|
|
"git.cin.su/m/Assistant/contracts/interaction"
|
|
interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain"
|
|
)
|
|
|
|
func TestReviewResponseUsesCognitiveGatewayAndReturnsOnlyDraftAnswer(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var request cognitive.GatewayAdvisoryRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
|
t.Fatalf("decode review request: %v", err)
|
|
}
|
|
if request.Message == "" || request.Meaning.SpeechAct == "" {
|
|
t.Fatalf("review request did not carry semantic context: %+v", request)
|
|
}
|
|
if !strings.Contains(request.Message, "ответ начинается с содержания") || !strings.Contains(request.Message, "Не повторяй вопрос пользователя") || !strings.Contains(request.Message, "только если пользователь сам спросил") {
|
|
t.Fatalf("review request lacks the conversational quality contract: %q", request.Message)
|
|
}
|
|
if len(request.Context) != 1 || !strings.Contains(request.Context[0].Text, "красный цвет") {
|
|
t.Fatalf("review request did not carry compact personal memory: %+v", request.Context)
|
|
}
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(cognitive.GatewayAdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: request.RequestID, AdvisoryText: "Меня зовут Дед. Я Домовой.", FinishReason: cognitive.FinishCompleted})
|
|
}))
|
|
defer server.Close()
|
|
client, err := newCognitiveInterpretationClient(server.URL, server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
reviewed, err := client.ReviewResponse(context.Background(), identity.CreatorSession{ActorID: "actor", PrincipalID: "principal"}, "кто ты?", "Я — Домовой.", "Предпочтение пользователя (его сообщение): красный цвет", cognitive.TaskMeaning{SpeechAct: "QUESTION"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if reviewed != "Меня зовут Дед. Я Домовой." {
|
|
t.Fatalf("unexpected reviewed response %q", reviewed)
|
|
}
|
|
}
|
|
|
|
func TestAnswerStableKnowledgeUsesLocalOnlyCognitiveRoute(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
var request cognitive.GatewayAdvisoryRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
|
t.Fatalf("decode stable knowledge request: %v", err)
|
|
}
|
|
if request.DataPolicy != capability.DataPolicyLocalOnly || request.Meaning.SpeechAct != "STABLE_KNOWLEDGE_ANSWER" {
|
|
t.Fatalf("unexpected stable knowledge request: %+v", request)
|
|
}
|
|
_ = json.NewEncoder(w).Encode(cognitive.GatewayAdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: request.RequestID, AdvisoryText: "Санкт-Петербург — город в России.", FinishReason: cognitive.FinishCompleted})
|
|
}))
|
|
defer server.Close()
|
|
client, err := newCognitiveInterpretationClient(server.URL, server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
answer, err := client.AnswerStableKnowledge(context.Background(), identity.CreatorSession{ActorID: "actor", PrincipalID: "principal"}, "Питер — это где?", "", cognitive.TaskMeaning{SpeechAct: "QUESTION"})
|
|
if err != nil || answer != "Санкт-Петербург — город в России." {
|
|
t.Fatalf("answer=%q err=%v", answer, err)
|
|
}
|
|
}
|
|
|
|
func TestGenerateImageUsesDedicatedCognitiveMediaRoute(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost || r.URL.Path != "/v1/multimodal/generate-image" {
|
|
t.Fatalf("unexpected image route: %s %s", r.Method, r.URL.Path)
|
|
}
|
|
if err := r.ParseMultipartForm(1 << 20); err != nil {
|
|
t.Fatalf("parse image request: %v", err)
|
|
}
|
|
if !strings.Contains(r.FormValue("prompt"), "собаку") {
|
|
t.Fatalf("prompt was not preserved: %q", r.FormValue("prompt"))
|
|
}
|
|
_ = json.NewEncoder(w).Encode(capability.MultimodalResponse{Operation: capability.MultimodalGenerateImage, ContentType: "image/png", DataBase64: "cG5n"})
|
|
}))
|
|
defer server.Close()
|
|
client, err := newCognitiveInterpretationClient(server.URL, server.Client())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
image, err := client.GenerateImage(context.Background(), "нарисуй собаку", "")
|
|
if err != nil || image.DataBase64 != "cG5n" || image.ContentType != "image/png" {
|
|
t.Fatalf("image=%+v err=%v", image, err)
|
|
}
|
|
}
|
|
|
|
func TestReviewPreservesResearchEvidence(t *testing.T) {
|
|
draft := "Нашёл вариант. Источник: Aviasales (https://www.aviasales.ru/search)"
|
|
if reviewPreservesResearchEvidence("Нашёл вариант на Авиасейлс.", draft) {
|
|
t.Fatal("review without the direct source must be rejected")
|
|
}
|
|
if !reviewPreservesResearchEvidence("Нашёл вариант. Источник: Aviasales (https://www.aviasales.ru/search)", draft) {
|
|
t.Fatal("review with the direct source must be accepted")
|
|
}
|
|
}
|
|
|
|
func TestSimpleQuestionUsesStrictInterpretationForSemanticRouting(t *testing.T) {
|
|
result := interactiondomain.SemanticResult{
|
|
Interpretation: interaction.UtteranceInterpretation{
|
|
SpeechAct: interaction.SpeechActQuestion,
|
|
Applicability: interaction.SemanticNeedsBroaderInterpret,
|
|
Clarification: interaction.ClarificationNotRequired,
|
|
},
|
|
CapabilityHints: []string{"COGNITIVE_REASONING", "VERIFY_FACT"},
|
|
}
|
|
if !shouldRequestBroaderInterpretation(result) {
|
|
t.Fatal("simple question must reach semantic interpretation before any answer")
|
|
}
|
|
}
|
|
|
|
func TestOperationalQuestionStillUsesStrictInterpretation(t *testing.T) {
|
|
result := interactiondomain.SemanticResult{
|
|
Interpretation: interaction.UtteranceInterpretation{
|
|
SpeechAct: interaction.SpeechActQuestion,
|
|
Applicability: interaction.SemanticNeedsBroaderInterpret,
|
|
Clarification: interaction.ClarificationNotRequired,
|
|
},
|
|
CapabilityHints: []string{"MODIFY_CODE", "RUN_TESTS"},
|
|
}
|
|
if !shouldRequestBroaderInterpretation(result) {
|
|
t.Fatal("operational question must retain strict interpretation routing")
|
|
}
|
|
}
|
|
|
|
func TestStructuredInventoryUsesDirectReadOnlySemanticRoute(t *testing.T) {
|
|
result := interactiondomain.SemanticResult{
|
|
Interpretation: interaction.UtteranceInterpretation{
|
|
SpeechAct: interaction.SpeechActDirective,
|
|
Applicability: interaction.SemanticApplicable,
|
|
ConfidenceMillis: 980,
|
|
},
|
|
InformationRequestKind: interaction.InformationRequestCognitiveModelInventory,
|
|
}
|
|
if shouldRequestSemanticInterpretation(result) {
|
|
t.Fatal("structured model inventory must use the direct read-only semantic route")
|
|
}
|
|
}
|
|
|
|
func TestStableReflexIsPeriodicallySampledForDrift(t *testing.T) {
|
|
result := interactiondomain.SemanticResult{Interpretation: interaction.UtteranceInterpretation{
|
|
SpeechAct: interaction.SpeechActConversational, Applicability: interaction.SemanticApplicable,
|
|
}}
|
|
found := false
|
|
for minute := 0; minute < 160; minute++ {
|
|
if shouldAuditDeterministicReflex(result, "utterance-drift-probe", time.Unix(int64(minute)*60, 0)) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("stable reflex was never sampled during the audit window")
|
|
}
|
|
}
|
|
|
|
func TestMeaningfulSignalsAreNotReflexOnly(t *testing.T) {
|
|
for _, act := range []interaction.SpeechAct{interaction.SpeechActQuestion, interaction.SpeechActRequestToInvestigate, interaction.SpeechActDirective} {
|
|
result := interactiondomain.SemanticResult{Interpretation: interaction.UtteranceInterpretation{SpeechAct: act, Applicability: interaction.SemanticApplicable}}
|
|
if !shouldRequestSemanticInterpretation(result) {
|
|
t.Fatalf("%s must prefer cognitive interpretation", act)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestExplicitPreferenceIsNotReinterpretedByCognitiveAudit(t *testing.T) {
|
|
result := interactiondomain.SemanticResult{Interpretation: interaction.UtteranceInterpretation{
|
|
SpeechAct: interaction.SpeechActPreference, Applicability: interaction.SemanticApplicable,
|
|
}}
|
|
if shouldRequestSemanticInterpretation(result) || shouldAuditDeterministicReflex(result, "preference", time.Unix(0, 0)) {
|
|
t.Fatal("explicit preference must remain on the deterministic durable-context path")
|
|
}
|
|
}
|