65 lines
3.2 KiB
Go
65 lines
3.2 KiB
Go
package llm
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"git.sundynix.cn/Blizzard/Veyra/internal/model"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestProtocolsAndFailureIsolation(t *testing.T) {
|
|
output := `{"schema_version":"candidate_analysis.v1","symbol":"000042","exchange":"SZSE","decision":"APPROVE","summary":"均线交叉观察","risks":"未复权日线","evidence_indices":[0],"missing_information":["基本面未提供"]}`
|
|
for _, protocol := range []string{"responses", "chat_completions"} {
|
|
t.Run(protocol, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Header.Get("Authorization") != "Bearer fixture-secret" {
|
|
t.Error("missing bearer authentication")
|
|
}
|
|
var body map[string]any
|
|
if json.NewDecoder(r.Body).Decode(&body) != nil {
|
|
t.Error("invalid request")
|
|
}
|
|
if body["store"] != false || body["model"] != "fixture-model" {
|
|
t.Error("model/storage config not inherited")
|
|
}
|
|
if protocol == "responses" {
|
|
if r.URL.Path != "/v1/responses" || body["text"] == nil {
|
|
t.Error("invalid responses request")
|
|
}
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"status": "completed", "output": []any{map[string]any{"type": "message", "content": []any{map[string]string{"type": "output_text", "text": output}}}}})
|
|
} else {
|
|
if r.URL.Path != "/v1/chat/completions" || body["response_format"] == nil {
|
|
t.Error("invalid chat request")
|
|
}
|
|
_ = json.NewEncoder(w).Encode(map[string]any{"choices": []any{map[string]any{"finish_reason": "stop", "message": map[string]string{"content": output}}}})
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
p := model.ProviderConfig{Enabled: true, ModelName: "fixture-model", BaseURL: server.URL + "/v1", WireAPI: protocol, TimeoutSeconds: 2, RequiresOpenAIAuth: true, DisableResponseStorage: true}
|
|
a, err := New().Analyze(context.Background(), p, "fixture-secret", json.RawMessage(`{}`))
|
|
if err != nil || a.Decision != "APPROVE" {
|
|
t.Fatalf("call failed: %v", err)
|
|
}
|
|
})
|
|
}
|
|
for _, response := range []string{`{"status":"incomplete","output":[]}`, `{"status":"completed","output":[{"content":[{"type":"refusal"}]}]}`, `{"status":"completed","output":[{"content":[{"type":"output_text","text":"{} {}"}]}]}`} {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte(response)) }))
|
|
_, err := New().Analyze(context.Background(), model.ProviderConfig{Enabled: true, ModelName: "x", BaseURL: server.URL, WireAPI: "responses", TimeoutSeconds: 2}, "", json.RawMessage(`{}`))
|
|
server.Close()
|
|
if err == nil {
|
|
t.Fatal("invalid response accepted")
|
|
}
|
|
}
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "fixture-secret upstream internal detail", 503)
|
|
}))
|
|
defer server.Close()
|
|
_, err := New().Analyze(context.Background(), model.ProviderConfig{Enabled: true, ModelName: "x", BaseURL: server.URL, WireAPI: "responses", TimeoutSeconds: 2}, "", json.RawMessage(`{}`))
|
|
if err == nil || strings.Contains(err.Error(), "secret") || err.Error() != "MODEL_HTTP_503" {
|
|
t.Fatal("unsafe provider error")
|
|
}
|
|
}
|