feat: 增加管理员 AI 模型配置 (#200)
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"cmautobuy/admin/model"
|
||||
)
|
||||
|
||||
func TestParseConfidenceThresholdBPS_精确转换(t *testing.T) {
|
||||
for raw, want := range map[string]int{"0": 0, "85": 8500, "85.5": 8550, "99.99": 9999, "100.00": 10000} {
|
||||
got, err := ParseConfidenceThresholdBPS(raw)
|
||||
if err != nil || got != want {
|
||||
t.Errorf("%q => %d,%v,期望 %d", raw, got, err, want)
|
||||
}
|
||||
}
|
||||
for _, raw := range []string{"", "-1", "100.01", "1.234", "abc"} {
|
||||
if _, err := ParseConfidenceThresholdBPS(raw); err == nil {
|
||||
t.Errorf("非法置信度 %q 应被拒绝", raw)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAIEndpointPolicy_阻止凭据和内网地址(t *testing.T) {
|
||||
policy := NewAIEndpointPolicy(nil)
|
||||
for _, raw := range []string{
|
||||
"http://api.example.com/v1", "https://user:pass@api.example.com/v1",
|
||||
"https://127.0.0.1/v1", "https://169.254.169.254/latest", "https://localhost/v1",
|
||||
} {
|
||||
if _, err := policy.ValidateSyntax(raw); err == nil {
|
||||
t.Errorf("危险地址 %q 应被拒绝", raw)
|
||||
}
|
||||
}
|
||||
got, err := policy.ValidateSyntax("https://api.example.com/v1/")
|
||||
if err != nil || got != "https://api.example.com/v1" {
|
||||
t.Fatalf("公网 HTTPS 地址应通过并去掉尾斜杠: %q %v", got, err)
|
||||
}
|
||||
allowed := NewAIEndpointPolicy([]string{"10.0.0.8"})
|
||||
if _, err := allowed.ValidateSyntax("https://10.0.0.8/v1"); err != nil {
|
||||
t.Fatalf("部署允许的私有端点应通过: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSafeAIHTTPClient_不使用环境代理(t *testing.T) {
|
||||
client := NewSafeAIHTTPClient(NewAIEndpointPolicy(nil), time.Second)
|
||||
transport, ok := client.Transport.(*http.Transport)
|
||||
if !ok {
|
||||
t.Fatalf("Transport 类型 = %T", client.Transport)
|
||||
}
|
||||
if transport.Proxy != nil {
|
||||
t.Fatal("AI HTTP 客户端不能使用环境代理,否则目标地址会绕过拨号阶段的 SSRF 校验")
|
||||
}
|
||||
}
|
||||
|
||||
type recordingAIHTTPDoer struct {
|
||||
request *http.Request
|
||||
status int
|
||||
}
|
||||
|
||||
func (d *recordingAIHTTPDoer) Do(request *http.Request) (*http.Response, error) {
|
||||
d.request = request
|
||||
return &http.Response{StatusCode: d.status, Body: io.NopCloser(strings.NewReader(`{"ok":true}`))}, nil
|
||||
}
|
||||
|
||||
func TestCallAIHealthCheck_最小请求且错误不泄露密钥(t *testing.T) {
|
||||
doer := &recordingAIHTTPDoer{status: http.StatusUnauthorized}
|
||||
const secret = "test-secret-never-log"
|
||||
err := callAIHealthCheck(context.Background(), doer, model.AIProviderConfig{
|
||||
BaseURL: "https://api.example.com/v1", Model: "test-model",
|
||||
}, secret)
|
||||
if err == nil || strings.Contains(err.Error(), secret) || !strings.Contains(err.Error(), "401") {
|
||||
t.Fatalf("错误必须脱敏且保留 HTTP 状态: %v", err)
|
||||
}
|
||||
if got := doer.request.Header.Get("Authorization"); got != "Bearer "+secret {
|
||||
t.Fatalf("测试请求未使用密钥: %q", got)
|
||||
}
|
||||
body, _ := io.ReadAll(doer.request.Body)
|
||||
text := string(body)
|
||||
for _, forbidden := range []string{"order_no", "syb_id", "address", secret} {
|
||||
if strings.Contains(text, forbidden) {
|
||||
t.Fatalf("最小测试请求包含业务数据或密钥 %q: %s", forbidden, text)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user