package main import ( "context" "encoding/json" "errors" "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/interaction" ) type registryStub struct { selected capability.ResolvedCognitiveBinding err error requirement capability.CognitiveRequirement observation *capability.CognitiveUsageObservation } type failoverRegistry struct { primary, fallback capability.ResolvedCognitiveBinding observations []capability.CognitiveUsageObservation } func (s *failoverRegistry) Resolve(_ context.Context, requirement capability.CognitiveRequirement) (capability.ResolvedCognitiveBinding, error) { if len(requirement.ExcludedBindingIDs) > 0 { return s.fallback, nil } return s.primary, nil } func (s *failoverRegistry) Observe(_ context.Context, value capability.CognitiveUsageObservation) error { s.observations = append(s.observations, value) return nil } func (s *failoverRegistry) RegisterDiscovery(context.Context, capability.CognitiveDiscoverySnapshot) (capability.CognitiveDiscoveryRegistration, error) { return capability.CognitiveDiscoveryRegistration{}, nil } func (s *registryStub) Resolve(_ context.Context, requirement capability.CognitiveRequirement) (capability.ResolvedCognitiveBinding, error) { s.requirement = requirement return s.selected, s.err } func (s *registryStub) Observe(_ context.Context, observation capability.CognitiveUsageObservation) error { s.observation = &observation return nil } func (s *registryStub) RegisterDiscovery(_ context.Context, snapshot capability.CognitiveDiscoverySnapshot) (capability.CognitiveDiscoveryRegistration, error) { return capability.CognitiveDiscoveryRegistration{}, s.err } func gatewaySelection() capability.ResolvedCognitiveBinding { return capability.ResolvedCognitiveBinding{ Binding: capability.CognitiveBinding{ID: "binding-1", Revision: 2, CapabilityID: creatorAdvisoryCapabilityID, OwnerPrincipalID: "principal-1", ProviderInstanceID: "provider-1", ProviderRevision: 3, ModelDescriptorID: "model-1", ModelRevision: 4, DataPolicy: capability.DataPolicyExternalMessageOnly, Qualification: capability.QualificationValidated, Activation: capability.ActivationActive, Health: capability.HealthAvailable, LimitPolicyRef: "limits-1"}, Provider: capability.ProviderInstance{ID: "provider-1", Revision: 3, Kind: "provider-kind-1", EndpointRef: "endpoint-1", CredentialRef: "credential-1", Enabled: true, Qualification: capability.QualificationValidated, Health: capability.HealthAvailable, ObservedAtUnixMillis: 1}, Model: capability.ModelDescriptor{ID: "model-1", Revision: 4, ProviderInstanceID: "provider-1", ProviderModelID: "snapshot-1", Fingerprint: "fingerprint-1", MaxContextBytes: 32000, Modalities: []capability.ModelModality{capability.ModalityText}, Capabilities: []capability.ModelCapability{capability.CapabilityAdvisory}, Qualification: capability.QualificationValidated, Health: capability.HealthAvailable, ObservedAtUnixMillis: 1}, } } func gatewayRequest() string { value := cognitive.GatewayAdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: "request-1", ActorID: "actor-1", PrincipalID: "principal-1", ScopeID: "scope-1", Message: "help", Context: []cognitive.ContextExcerpt{}, Locale: "en", MaxOutputBytes: 4096, DataPolicy: capability.DataPolicyExternalMessageOnly, EgressAcknowledged: true} payload, _ := json.Marshal(value) return string(payload) } func TestActiveRegistryBindingReachesProviderAdapter(t *testing.T) { selected := gatewaySelection() registry := ®istryStub{selected: selected} provider := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: "request-1", ProviderBindingID: selected.Binding.ID, ProviderBindingRevision: selected.Binding.Revision, AdvisoryText: "advice", FinishReason: cognitive.FinishCompleted, Usage: &cognitive.ProviderUsage{InputUnits: 2, OutputUnits: 3}}} gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{"provider-kind-1": {{EndpointRef: "endpoint-1", CredentialRef: "credential-1", Provider: provider}}}, now: func() time.Time { return time.UnixMilli(1000) }} request := httptest.NewRequest(http.MethodPost, "/v1/advisories", strings.NewReader(gatewayRequest())) response := httptest.NewRecorder() gateway.advisoryHandler(response, request) if response.Code != http.StatusOK || !provider.called { t.Fatalf("status=%d provider_called=%t body=%s", response.Code, provider.called, response.Body.String()) } if provider.request.ProviderBindingID != selected.Binding.ID || provider.request.ProviderModelID != selected.Model.ProviderModelID { t.Fatalf("invocation was not assembled from registry: %+v", provider.request) } if registry.observation == nil || registry.observation.BindingID != selected.Binding.ID || registry.observation.InputUnits != 2 || registry.observation.OutputUnits != 3 { t.Fatalf("usage observation=%+v", registry.observation) } } func TestCanaryFlagIsForwardedToCapabilityResolver(t *testing.T) { registry := ®istryStub{err: errors.New("stop after resolve")} gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{}, now: time.Now, allowCanary: true} request := httptest.NewRequest(http.MethodPost, "/v1/advisories", strings.NewReader(gatewayRequest())) gateway.advisoryHandler(httptest.NewRecorder(), request) if !registry.requirement.AllowCanary { t.Fatal("server-side canary flag was not forwarded") } gateway.allowCanary = false gateway.advisoryHandler(httptest.NewRecorder(), httptest.NewRequest(http.MethodPost, "/v1/advisories", strings.NewReader(gatewayRequest()))) if registry.requirement.AllowCanary { t.Fatal("canary was allowed by default") } } func TestCanaryOutputLimitIsClamped(t *testing.T) { selected := gatewaySelection() selected.Binding.DataPolicy = capability.DataPolicyLocalOnly registry := ®istryStub{selected: selected} provider := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: "request-1", ProviderBindingID: selected.Binding.ID, ProviderBindingRevision: selected.Binding.Revision, AdvisoryText: "ok", FinishReason: cognitive.FinishCompleted}} gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{"provider-kind-1": {{EndpointRef: "endpoint-1", CredentialRef: "credential-1", Provider: provider}}}, now: time.Now, allowCanary: true, canaryMaxOutputBytes: 4096} gateway.advisoryHandler(httptest.NewRecorder(), httptest.NewRequest(http.MethodPost, "/v1/advisories", strings.NewReader(gatewayRequest()))) if registry.requirement.MaxOutputBytes != 4096 || provider.request.MaxOutputBytes != 4096 { t.Fatalf("canary limit not clamped: requirement=%d invocation=%d", registry.requirement.MaxOutputBytes, provider.request.MaxOutputBytes) } } func TestSelectionObjectiveForMessage(t *testing.T) { cases := []struct { message string objective capability.SelectionObjective }{ {"Сделай глубокий анализ кода", capability.SelectionQuality}, {"Нужен быстрый статус", capability.SelectionSpeed}, {"Выбери бесплатную модель", capability.SelectionEconomy}, {"Помоги сформулировать ответ", capability.SelectionBalanced}, } for _, test := range cases { if actual := selectionObjectiveForMessage(test.message); actual != test.objective { t.Errorf("message=%q objective=%q, want %q", test.message, actual, test.objective) } } } func TestSelectionObjectiveUsesTrustedMeaningBeforeText(t *testing.T) { meaning := cognitive.TaskMeaning{CapabilityKind: capability.CapabilityKindModifyCode, SpeechAct: "DIRECTIVE"} if actual := selectionObjectiveForMeaning(meaning, "сделай быстро и дёшево"); actual != capability.SelectionQuality { t.Fatalf("objective=%q, want quality", actual) } if actual := selectionObjectiveForMeaning(cognitive.TaskMeaning{}, "сделай быстро"); actual != capability.SelectionSpeed { t.Fatalf("fallback objective=%q, want speed", actual) } } func TestSelectionObjectiveForInterpretationUsesSemanticSignals(t *testing.T) { request := cognitive.GatewayInterpretationRequest{RequestedOutcome: "сравнить варианты", DeterministicApplicability: cognitive.InterpretationApplicable, DeterministicConfidence: 900, DeterministicSpeechAct: "QUESTION"} if actual := selectionObjectiveForInterpretation(request); actual != capability.SelectionQuality { t.Fatalf("objective=%q, want quality", actual) } request = cognitive.GatewayInterpretationRequest{DeterministicApplicability: cognitive.InterpretationApplicable, DeterministicConfidence: 900, DeterministicSpeechAct: "ASSERTION"} if actual := selectionObjectiveForInterpretation(request); actual != capability.SelectionSpeed { t.Fatalf("objective=%q, want speed", actual) } } func TestNoActiveBindingUsesBuiltInProviderWithoutInvocation(t *testing.T) { registry := ®istryStub{err: errors.New("no active binding")} provider := &stubAdvisoryProvider{} gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{"unused": {{Provider: provider}}}, now: time.Now} request := httptest.NewRequest(http.MethodPost, "/v1/advisories", strings.NewReader(gatewayRequest())) response := httptest.NewRecorder() gateway.advisoryHandler(response, request) var result cognitive.GatewayAdvisoryResponse if err := json.NewDecoder(response.Body).Decode(&result); err != nil { t.Fatal(err) } if response.Code != http.StatusOK || result.FinishReason != cognitive.FinishCompleted || result.AdvisoryText == "" || provider.called { t.Fatalf("status=%d result=%+v provider_called=%t", response.Code, result, provider.called) } } func TestBuiltInAdvisoryUsesTrustedInformationIntentInsteadOfMessageKeywords(t *testing.T) { request := cognitive.GatewayAdvisoryRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: "request-1", Message: "arbitrary text", Meaning: cognitive.TaskMeaning{InformationRequestKind: interaction.InformationRequestCognitiveRecovery}} response := builtinAdvisory(request) if !strings.Contains(response.AdvisoryText, "восстановление ИИ") { t.Fatalf("unexpected recovery response: %q", response.AdvisoryText) } } func TestNoActiveBindingInvokesBuiltInLocalProvider(t *testing.T) { registry := ®istryStub{err: errors.New("no active binding")} provider := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: "request-1", AdvisoryText: "локальный ответ", FinishReason: cognitive.FinishCompleted}} builtin := providerRegistration{ProviderInstanceID: "local-lifeboat", Kind: "openai-compatible-chat-v1", EndpointRef: "local-lifeboat-v1", Provider: provider} gateway := cognitiveGateway{resolver: registry, builtinLocal: &builtin, now: time.Now} request := httptest.NewRequest(http.MethodPost, "/v1/advisories", strings.NewReader(gatewayRequest())) response := httptest.NewRecorder() gateway.advisoryHandler(response, request) if response.Code != http.StatusOK || !provider.called || !strings.Contains(response.Body.String(), "локальный ответ") { t.Fatalf("status=%d provider_called=%t body=%s", response.Code, provider.called, response.Body.String()) } if registry.observation == nil || registry.observation.BindingID != "builtin-local-binding-v1" || registry.observation.ProviderInstanceID != "builtin-local-lifeboat-v1" || registry.observation.ModelDescriptorID != "builtin-local-model-v1" || registry.observation.ResultClass != capability.CognitiveResultClass(cognitive.FinishCompleted) { t.Fatalf("built-in usage observation=%+v", registry.observation) } } func TestBuiltInProviderFailureIsObservedWithoutBlockingFallback(t *testing.T) { registry := ®istryStub{err: errors.New("no active binding")} provider := &stubAdvisoryProvider{err: errors.New("local timeout")} builtin := providerRegistration{Kind: "ollama-v1", Provider: provider} gateway := cognitiveGateway{resolver: registry, builtinLocal: &builtin, now: time.Now} request := httptest.NewRequest(http.MethodPost, "/v1/advisories", strings.NewReader(gatewayRequest())) response := httptest.NewRecorder() gateway.advisoryHandler(response, request) if response.Code != http.StatusOK || registry.observation == nil { t.Fatalf("status=%d observation=%+v body=%s", response.Code, registry.observation, response.Body.String()) } if registry.observation.ResultClass != capability.CognitiveResultClass(cognitive.FinishInvalidProviderResponse) || registry.observation.FailureType != capability.CognitiveFailureType(cognitive.FinishInvalidProviderResponse) { t.Fatalf("failure observation=%+v", registry.observation) } } func TestBuiltInInterpretationIsObserved(t *testing.T) { registry := ®istryStub{err: errors.New("no active binding")} provider := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{FinishReason: cognitive.FinishCompleted, AdvisoryText: `{"primary_speech_act":"QUESTION","secondary_speech_act":"","confidence_millis":900,"applicability":"APPLICABLE","requested_outcome":"","needs_cognition":false,"clarification":"NOT_REQUIRED","selection_reason":"local","capability_hints":[]}`}} builtin := providerRegistration{Kind: "openai-compatible-chat-v1", Provider: provider} gateway := cognitiveGateway{resolver: registry, builtinLocal: &builtin, now: time.Now} request := cognitive.GatewayInterpretationRequest{Version: cognitive.CreatorAdvisoryV1, RequestID: "interpretation-1", ActorID: "actor-1", PrincipalID: "principal-1", ScopeID: "scope-1", Message: "что доступно", MaxOutputBytes: 4096, DeterministicSpeechAct: "QUESTION", DeterministicApplicability: cognitive.InterpretationApplicable} result := gateway.interpretWithBuiltin(context.Background(), request, request.MaxOutputBytes) if result.FinishReason != cognitive.FinishCompleted || registry.observation == nil || registry.observation.RequestID != "interpretation-1" { t.Fatalf("result=%+v observation=%+v", result, registry.observation) } } func TestProviderLossBetweenRoutingAndInvocationFallsBackOnce(t *testing.T) { primary := gatewaySelection() primary.Binding.ID = "gpu-binding" primary.Provider.ID = "gpu-provider" primary.Provider.Kind = "gpu-kind" primary.Provider.EndpointRef = "gpu-endpoint" fallback := gatewaySelection() fallback.Binding.ID = "cpu-binding" fallback.Provider.ID = "cpu-provider" fallback.Provider.Kind = "cpu-kind" fallback.Provider.EndpointRef = "cpu-endpoint" registry := &failoverRegistry{primary: primary, fallback: fallback} gpu := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: "request-1", ProviderBindingID: primary.Binding.ID, ProviderBindingRevision: primary.Binding.Revision, FinishReason: cognitive.FinishProviderUnavailable}} cpu := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: "request-1", ProviderBindingID: fallback.Binding.ID, ProviderBindingRevision: fallback.Binding.Revision, AdvisoryText: "cpu advice", FinishReason: cognitive.FinishCompleted}} gateway := cognitiveGateway{resolver: registry, providers: map[capability.ProviderKind][]providerRegistration{"gpu-kind": {{EndpointRef: "gpu-endpoint", CredentialRef: primary.Provider.CredentialRef, Provider: gpu}}, "cpu-kind": {{EndpointRef: "cpu-endpoint", CredentialRef: fallback.Provider.CredentialRef, Provider: cpu}}}, now: time.Now} request := httptest.NewRequest(http.MethodPost, "/v1/advisories", strings.NewReader(gatewayRequest())) response := httptest.NewRecorder() gateway.advisoryHandler(response, request) if !gpu.called || !cpu.called || !strings.Contains(response.Body.String(), "cpu advice") || len(registry.observations) != 2 { t.Fatalf("gpu=%t cpu=%t observations=%d body=%s", gpu.called, cpu.called, len(registry.observations), response.Body.String()) } }