142 lines
5.4 KiB
Go
142 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.cin.su/m/Assistant/contracts/cognitive"
|
|
)
|
|
|
|
type stubAdvisoryProvider struct {
|
|
called bool
|
|
request cognitive.AdvisoryRequest
|
|
response cognitive.AdvisoryResponse
|
|
err error
|
|
}
|
|
|
|
func (s *stubAdvisoryProvider) Advise(_ context.Context, request cognitive.AdvisoryRequest) (cognitive.AdvisoryResponse, error) {
|
|
s.called = true
|
|
s.request = request
|
|
return s.response, s.err
|
|
}
|
|
|
|
func TestDeterministicAdvisoryRejectsUnknownJSONField(t *testing.T) {
|
|
body := `{"Version":"creator-advisory-v1","Unexpected":"provider override"}`
|
|
request := httptest.NewRequest(http.MethodPost, "/v1/advisories/deterministic", strings.NewReader(body))
|
|
response := httptest.NewRecorder()
|
|
deterministicAdvisory(response, request)
|
|
if response.Code != http.StatusBadRequest {
|
|
t.Fatalf("status=%d, want 400", response.Code)
|
|
}
|
|
}
|
|
|
|
func TestDeterministicAdvisoryReturnsUnavailableContract(t *testing.T) {
|
|
body := `{"Version":"creator-advisory-v1","RequestID":"request-1","ActorID":"actor","PrincipalID":"principal","ScopeID":"scope","Message":"help","Context":[],"Locale":"en","MaxOutputBytes":4096,"ProviderBindingID":"fake-binding","ProviderBindingRevision":1}`
|
|
request := httptest.NewRequest(http.MethodPost, "/v1/advisories/deterministic", strings.NewReader(body))
|
|
response := httptest.NewRecorder()
|
|
deterministicAdvisory(response, request)
|
|
if response.Code != http.StatusOK {
|
|
t.Fatalf("status=%d body=%s", response.Code, response.Body.String())
|
|
}
|
|
var advisory cognitive.AdvisoryResponse
|
|
if err := json.NewDecoder(response.Body).Decode(&advisory); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if advisory.FinishReason != cognitive.FinishProviderUnavailable || advisory.AdvisoryText != "" {
|
|
t.Fatalf("unexpected response: %+v", advisory)
|
|
}
|
|
}
|
|
|
|
func TestOpenAIAdvisoryIsUnavailableBeforeBodyParsingWhenDisabled(t *testing.T) {
|
|
request := httptest.NewRequest(http.MethodPost, "/v1/advisories/openai", strings.NewReader(`not-json`))
|
|
response := httptest.NewRecorder()
|
|
openAIAdvisory(nil)(response, request)
|
|
if response.Code != http.StatusServiceUnavailable {
|
|
t.Fatalf("status=%d, want 503", response.Code)
|
|
}
|
|
}
|
|
|
|
func TestOpenAIAdvisoryStrictlyDecodesBeforeProvider(t *testing.T) {
|
|
provider := &stubAdvisoryProvider{}
|
|
request := httptest.NewRequest(http.MethodPost, "/v1/advisories/openai", strings.NewReader(`{"Version":"creator-advisory-v1","Unexpected":"provider override"}`))
|
|
response := httptest.NewRecorder()
|
|
openAIAdvisory(provider)(response, request)
|
|
if response.Code != http.StatusBadRequest || provider.called {
|
|
t.Fatalf("status=%d provider_called=%t", response.Code, provider.called)
|
|
}
|
|
}
|
|
|
|
func TestOpenAIAdvisoryReturnsProviderNeutralResponse(t *testing.T) {
|
|
provider := &stubAdvisoryProvider{response: cognitive.AdvisoryResponse{
|
|
Version: cognitive.CreatorAdvisoryV1,
|
|
RequestID: "request-1",
|
|
ProviderBindingID: "binding-under-test",
|
|
ProviderBindingRevision: 1,
|
|
AdvisoryText: "advice",
|
|
FinishReason: cognitive.FinishCompleted,
|
|
}}
|
|
body := `{"Version":"creator-advisory-v1","RequestID":"request-1","ActorID":"actor","PrincipalID":"principal","ScopeID":"scope","Message":"help","Context":[],"Locale":"en","MaxOutputBytes":4096,"ProviderBindingID":"openai-responses-gpt-5.4-mini-2026-03-17","ProviderBindingRevision":1}`
|
|
request := httptest.NewRequest(http.MethodPost, "/v1/advisories/openai", strings.NewReader(body))
|
|
response := httptest.NewRecorder()
|
|
openAIAdvisory(provider)(response, request)
|
|
if response.Code != http.StatusOK || !provider.called || provider.request.Message != "help" {
|
|
t.Fatalf("status=%d provider_called=%t", response.Code, provider.called)
|
|
}
|
|
var advisory cognitive.AdvisoryResponse
|
|
if err := json.NewDecoder(response.Body).Decode(&advisory); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if advisory.AdvisoryText != "advice" || advisory.FinishReason != cognitive.FinishCompleted {
|
|
t.Fatalf("unexpected response: %+v", advisory)
|
|
}
|
|
}
|
|
|
|
func TestLoadOpenAIAdvisoryProviderDefaultsDisabled(t *testing.T) {
|
|
t.Setenv("OPENAI_API_KEY", "")
|
|
provider, err := loadOpenAIAdvisoryProvider("")
|
|
if err != nil || provider != nil {
|
|
t.Fatalf("provider=%v error=%v", provider, err)
|
|
}
|
|
}
|
|
|
|
func TestLoadOpenAIAdvisoryProviderRejectsDirectEnvironmentCredential(t *testing.T) {
|
|
t.Setenv("OPENAI_API_KEY", "must-not-be-read")
|
|
if _, err := loadOpenAIAdvisoryProvider(""); err == nil || strings.Contains(err.Error(), "must-not-be-read") {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestLoadOpenAIAdvisoryProviderUsesBoundedSecretFile(t *testing.T) {
|
|
t.Setenv("OPENAI_API_KEY", "")
|
|
directory := t.TempDir()
|
|
validPath := filepath.Join(directory, "valid")
|
|
if err := os.WriteFile(validPath, []byte("test-project-key\n"), 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
provider, err := loadOpenAIAdvisoryProvider(validPath)
|
|
if err != nil || provider == nil {
|
|
t.Fatalf("provider=%v error=%v", provider, err)
|
|
}
|
|
for name, content := range map[string][]byte{
|
|
"empty": nil,
|
|
"embedded whitespace": []byte("invalid key"),
|
|
"oversized": []byte(strings.Repeat("x", maxOpenAISecretBytes+1)),
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
path := filepath.Join(directory, name)
|
|
if err := os.WriteFile(path, content, 0o600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := loadOpenAIAdvisoryProvider(path); err == nil {
|
|
t.Fatal("invalid secret file was accepted")
|
|
}
|
|
})
|
|
}
|
|
}
|