- 新增 domain 层:model、provider、route、stats 实体 - 新增 service 层:models、providers、routing、stats 业务逻辑 - 新增 repository 层:models、providers、stats 数据访问 - 新增 pkg 工具包:errors、logger、validator - 新增中间件:CORS、logging、recovery、request ID - 新增数据库迁移:初始 schema 和索引 - 新增单元测试和集成测试 - 新增规范文档:config-management、database-migration、error-handling、layered-architecture、middleware-system、request-validation、structured-logging、test-coverage - 移除 config 子包和 model_router(已迁移至分层架构)
191 lines
4.5 KiB
Go
191 lines
4.5 KiB
Go
package openai
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
func TestAdapter_PrepareRequest(t *testing.T) {
|
|
adapter := NewAdapter()
|
|
req := &ChatCompletionRequest{
|
|
Model: "gpt-4",
|
|
Messages: []Message{
|
|
{Role: "user", Content: "Hello"},
|
|
},
|
|
}
|
|
|
|
httpReq, err := adapter.PrepareRequest(req, "test-api-key", "https://api.openai.com/v1")
|
|
require.NoError(t, err)
|
|
require.NotNil(t, httpReq)
|
|
|
|
assert.Equal(t, "POST", httpReq.Method)
|
|
assert.Equal(t, "https://api.openai.com/v1/chat/completions", httpReq.URL.String())
|
|
assert.Equal(t, "application/json", httpReq.Header.Get("Content-Type"))
|
|
assert.Equal(t, "Bearer test-api-key", httpReq.Header.Get("Authorization"))
|
|
|
|
// 验证请求体
|
|
var body ChatCompletionRequest
|
|
err = json.NewDecoder(httpReq.Body).Decode(&body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "gpt-4", body.Model)
|
|
}
|
|
|
|
func TestAdapter_ParseResponse(t *testing.T) {
|
|
adapter := NewAdapter()
|
|
resp := &ChatCompletionResponse{
|
|
ID: "chatcmpl-123",
|
|
Object: "chat.completion",
|
|
Created: 1234567890,
|
|
Model: "gpt-4",
|
|
Choices: []Choice{
|
|
{
|
|
Index: 0,
|
|
Message: &Message{Role: "assistant", Content: "Hello!"},
|
|
},
|
|
},
|
|
Usage: Usage{PromptTokens: 10, CompletionTokens: 5, TotalTokens: 15},
|
|
}
|
|
|
|
body, err := json.Marshal(resp)
|
|
require.NoError(t, err)
|
|
|
|
httpResp := &http.Response{
|
|
StatusCode: 200,
|
|
Body: io.NopCloser(bytes.NewReader(body)),
|
|
}
|
|
|
|
result, err := adapter.ParseResponse(httpResp)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "chatcmpl-123", result.ID)
|
|
assert.Equal(t, "gpt-4", result.Model)
|
|
require.Len(t, result.Choices, 1)
|
|
assert.Equal(t, "Hello!", result.Choices[0].Message.Content)
|
|
}
|
|
|
|
func TestAdapter_ParseErrorResponse(t *testing.T) {
|
|
adapter := NewAdapter()
|
|
errResp := &ErrorResponse{
|
|
Error: ErrorDetail{
|
|
Message: "Invalid API key",
|
|
Type: "invalid_request_error",
|
|
Code: "invalid_api_key",
|
|
},
|
|
}
|
|
|
|
body, err := json.Marshal(errResp)
|
|
require.NoError(t, err)
|
|
|
|
httpResp := &http.Response{
|
|
StatusCode: 401,
|
|
Body: io.NopCloser(bytes.NewReader(body)),
|
|
}
|
|
|
|
result, err := adapter.ParseErrorResponse(httpResp)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "Invalid API key", result.Error.Message)
|
|
assert.Equal(t, "invalid_request_error", result.Error.Type)
|
|
}
|
|
|
|
func TestAdapter_ParseStreamChunk(t *testing.T) {
|
|
adapter := NewAdapter()
|
|
chunk := &StreamChunk{
|
|
ID: "chatcmpl-123",
|
|
Object: "chat.completion.chunk",
|
|
Created: 1234567890,
|
|
Model: "gpt-4",
|
|
Choices: []StreamChoice{
|
|
{
|
|
Index: 0,
|
|
Delta: Delta{Content: "Hello"},
|
|
},
|
|
},
|
|
}
|
|
|
|
data, err := json.Marshal(chunk)
|
|
require.NoError(t, err)
|
|
|
|
result, err := adapter.ParseStreamChunk(data)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "chatcmpl-123", result.ID)
|
|
require.Len(t, result.Choices, 1)
|
|
assert.Equal(t, "Hello", result.Choices[0].Delta.Content)
|
|
}
|
|
|
|
func TestParseToolCallArguments(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantErr bool
|
|
}{
|
|
{"有效JSON", `{"key": "value"}`, false},
|
|
{"无效JSON", `not json`, true},
|
|
{"空JSON", `{}`, false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
tc := &ToolCall{
|
|
Function: FunctionCall{Arguments: tt.input},
|
|
}
|
|
args, err := tc.ParseToolCallArguments()
|
|
if tt.wantErr {
|
|
assert.Error(t, err)
|
|
} else {
|
|
assert.NoError(t, err)
|
|
assert.NotNil(t, args)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestSerializeToolCallArguments(t *testing.T) {
|
|
args := map[string]interface{}{"key": "value"}
|
|
result, err := SerializeToolCallArguments(args)
|
|
require.NoError(t, err)
|
|
assert.JSONEq(t, `{"key": "value"}`, result)
|
|
}
|
|
|
|
func TestValidateRequest(t *testing.T) {
|
|
t.Run("有效请求", func(t *testing.T) {
|
|
req := &ChatCompletionRequest{
|
|
Model: "gpt-4",
|
|
Messages: []Message{{Role: "user", Content: "hello"}},
|
|
}
|
|
errs := ValidateRequest(req)
|
|
assert.Nil(t, errs)
|
|
})
|
|
|
|
t.Run("缺少模型", func(t *testing.T) {
|
|
req := &ChatCompletionRequest{
|
|
Messages: []Message{{Role: "user", Content: "hello"}},
|
|
}
|
|
errs := ValidateRequest(req)
|
|
assert.NotNil(t, errs)
|
|
assert.Contains(t, errs["model"], "不能为空")
|
|
})
|
|
|
|
t.Run("缺少消息", func(t *testing.T) {
|
|
req := &ChatCompletionRequest{
|
|
Model: "gpt-4",
|
|
}
|
|
errs := ValidateRequest(req)
|
|
assert.NotNil(t, errs)
|
|
assert.Contains(t, errs["messages"], "不能为空")
|
|
})
|
|
|
|
t.Run("空消息列表", func(t *testing.T) {
|
|
req := &ChatCompletionRequest{
|
|
Model: "gpt-4",
|
|
Messages: []Message{},
|
|
}
|
|
errs := ValidateRequest(req)
|
|
assert.NotNil(t, errs)
|
|
})
|
|
}
|