已验证的问题状态: 1. P0-07补偿处理器 - 已集成到main.go ✅ 2. P0-09外键校验器 - 已集成到main.go并调用 ✅ 3. 幂等协议Idempotency-Key - 已在idempotency.go实现 ✅ 4. 幂等唯一索引 - 已在SQL中定义 ✅ Gateway修复: - 修复cors.go语法错误(重复函数定义) - 修复middleware_test.go参数不匹配问题 - 修复go.mod降级到go 1.21解决依赖问题
309 lines
7.0 KiB
Go
309 lines
7.0 KiB
Go
package adapter
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
)
|
|
|
|
func TestProviderError_Error(t *testing.T) {
|
|
err := ProviderError{
|
|
Code: "TEST_ERROR",
|
|
Message: "test error message",
|
|
HTTPStatus: 500,
|
|
Retryable: true,
|
|
}
|
|
|
|
if err.Error() != "TEST_ERROR: test error message" {
|
|
t.Errorf("unexpected error string: %s", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestProviderError_IsRetryable(t *testing.T) {
|
|
t.Run("retryable true", func(t *testing.T) {
|
|
err := ProviderError{
|
|
Code: "TEST_ERROR",
|
|
Message: "test",
|
|
HTTPStatus: 500,
|
|
Retryable: true,
|
|
}
|
|
if !err.IsRetryable() {
|
|
t.Error("expected IsRetryable to be true")
|
|
}
|
|
})
|
|
|
|
t.Run("retryable false", func(t *testing.T) {
|
|
err := ProviderError{
|
|
Code: "TEST_ERROR",
|
|
Message: "test",
|
|
HTTPStatus: 400,
|
|
Retryable: false,
|
|
}
|
|
if err.IsRetryable() {
|
|
t.Error("expected IsRetryable to be false")
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestReadCloser_Close(t *testing.T) {
|
|
t.Run("close with callback", func(t *testing.T) {
|
|
called := false
|
|
rc := &ReadCloser{
|
|
OnClose: func() error {
|
|
called = true
|
|
return nil
|
|
},
|
|
}
|
|
|
|
err := rc.Close()
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
if !called {
|
|
t.Error("OnClose was not called")
|
|
}
|
|
})
|
|
|
|
t.Run("close without callback", func(t *testing.T) {
|
|
rc := &ReadCloser{}
|
|
err := rc.Close()
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCompletionOptions(t *testing.T) {
|
|
opts := CompletionOptions{
|
|
Temperature: 0.7,
|
|
MaxTokens: 100,
|
|
TopP: 0.9,
|
|
Stream: true,
|
|
Stop: []string{"stop"},
|
|
}
|
|
|
|
if opts.Temperature != 0.7 {
|
|
t.Errorf("expected 0.7, got %f", opts.Temperature)
|
|
}
|
|
if opts.MaxTokens != 100 {
|
|
t.Errorf("expected 100, got %d", opts.MaxTokens)
|
|
}
|
|
if opts.TopP != 0.9 {
|
|
t.Errorf("expected 0.9, got %f", opts.TopP)
|
|
}
|
|
if !opts.Stream {
|
|
t.Error("expected Stream to be true")
|
|
}
|
|
if len(opts.Stop) != 1 || opts.Stop[0] != "stop" {
|
|
t.Error("unexpected Stop value")
|
|
}
|
|
}
|
|
|
|
func TestCompletionResponse(t *testing.T) {
|
|
resp := CompletionResponse{
|
|
ID: "test-id",
|
|
Object: "chat.completion",
|
|
Created: 1234567890,
|
|
Model: "gpt-4",
|
|
Choices: []Choice{
|
|
{
|
|
Index: 0,
|
|
Message: &Message{
|
|
Role: "assistant",
|
|
Content: "Hello",
|
|
},
|
|
FinishReason: "stop",
|
|
},
|
|
},
|
|
Usage: Usage{
|
|
PromptTokens: 10,
|
|
CompletionTokens: 5,
|
|
TotalTokens: 15,
|
|
},
|
|
}
|
|
|
|
if resp.ID != "test-id" {
|
|
t.Errorf("unexpected ID: %s", resp.ID)
|
|
}
|
|
if resp.Object != "chat.completion" {
|
|
t.Errorf("unexpected Object: %s", resp.Object)
|
|
}
|
|
if len(resp.Choices) != 1 {
|
|
t.Errorf("expected 1 choice, got %d", len(resp.Choices))
|
|
}
|
|
if resp.Choices[0].Message.Content != "Hello" {
|
|
t.Errorf("unexpected content: %s", resp.Choices[0].Message.Content)
|
|
}
|
|
if resp.Usage.TotalTokens != 15 {
|
|
t.Errorf("unexpected TotalTokens: %d", resp.Usage.TotalTokens)
|
|
}
|
|
}
|
|
|
|
func TestStreamChunk(t *testing.T) {
|
|
chunk := StreamChunk{
|
|
ID: "chunk-id",
|
|
Object: "chat.completion.chunk",
|
|
Created: 1234567890,
|
|
Model: "gpt-4",
|
|
Choices: []StreamChoice{
|
|
{
|
|
Index: 0,
|
|
Delta: &Delta{
|
|
Role: "assistant",
|
|
Content: "Hi",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
if chunk.ID != "chunk-id" {
|
|
t.Errorf("unexpected ID: %s", chunk.ID)
|
|
}
|
|
if len(chunk.Choices) != 1 {
|
|
t.Errorf("expected 1 choice, got %d", len(chunk.Choices))
|
|
}
|
|
if chunk.Choices[0].Delta.Content != "Hi" {
|
|
t.Errorf("unexpected content: %s", chunk.Choices[0].Delta.Content)
|
|
}
|
|
}
|
|
|
|
func TestMessage(t *testing.T) {
|
|
msg := Message{
|
|
Role: "user",
|
|
Content: "test message",
|
|
Name: "John",
|
|
}
|
|
|
|
if msg.Role != "user" {
|
|
t.Errorf("unexpected Role: %s", msg.Role)
|
|
}
|
|
if msg.Content != "test message" {
|
|
t.Errorf("unexpected Content: %s", msg.Content)
|
|
}
|
|
if msg.Name != "John" {
|
|
t.Errorf("unexpected Name: %s", msg.Name)
|
|
}
|
|
}
|
|
|
|
func TestUsage(t *testing.T) {
|
|
usage := Usage{
|
|
PromptTokens: 100,
|
|
CompletionTokens: 50,
|
|
TotalTokens: 150,
|
|
}
|
|
|
|
if usage.PromptTokens != 100 {
|
|
t.Errorf("unexpected PromptTokens: %d", usage.PromptTokens)
|
|
}
|
|
if usage.CompletionTokens != 50 {
|
|
t.Errorf("unexpected CompletionTokens: %d", usage.CompletionTokens)
|
|
}
|
|
if usage.TotalTokens != 150 {
|
|
t.Errorf("unexpected TotalTokens: %d", usage.TotalTokens)
|
|
}
|
|
}
|
|
|
|
func TestDelta(t *testing.T) {
|
|
delta := Delta{
|
|
Role: "assistant",
|
|
Content: "response",
|
|
}
|
|
|
|
if delta.Role != "assistant" {
|
|
t.Errorf("unexpected Role: %s", delta.Role)
|
|
}
|
|
if delta.Content != "response" {
|
|
t.Errorf("unexpected Content: %s", delta.Content)
|
|
}
|
|
}
|
|
|
|
// MockProviderForTesting 用于测试的Mock Provider
|
|
type MockProviderForTesting struct {
|
|
NameFunc func() string
|
|
SupportedModelsFunc func() []string
|
|
ChatCompletionFunc func(ctx context.Context, model string, messages []Message, options CompletionOptions) (*CompletionResponse, error)
|
|
HealthCheckFunc func(ctx context.Context) bool
|
|
}
|
|
|
|
func (m *MockProviderForTesting) ChatCompletion(ctx context.Context, model string, messages []Message, options CompletionOptions) (*CompletionResponse, error) {
|
|
if m.ChatCompletionFunc != nil {
|
|
return m.ChatCompletionFunc(ctx, model, messages, options)
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockProviderForTesting) ChatCompletionStream(ctx context.Context, model string, messages []Message, options CompletionOptions) (<-chan *StreamChunk, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (m *MockProviderForTesting) GetUsage(response *CompletionResponse) Usage {
|
|
return Usage{}
|
|
}
|
|
|
|
func (m *MockProviderForTesting) MapError(err error) ProviderError {
|
|
return ProviderError{}
|
|
}
|
|
|
|
func (m *MockProviderForTesting) HealthCheck(ctx context.Context) bool {
|
|
if m.HealthCheckFunc != nil {
|
|
return m.HealthCheckFunc(ctx)
|
|
}
|
|
return true
|
|
}
|
|
|
|
func (m *MockProviderForTesting) ProviderName() string {
|
|
if m.NameFunc != nil {
|
|
return m.NameFunc()
|
|
}
|
|
return "mock"
|
|
}
|
|
|
|
func (m *MockProviderForTesting) SupportedModels() []string {
|
|
if m.SupportedModelsFunc != nil {
|
|
return m.SupportedModelsFunc()
|
|
}
|
|
return []string{}
|
|
}
|
|
|
|
func TestMockProviderForTesting(t *testing.T) {
|
|
called := false
|
|
provider := &MockProviderForTesting{
|
|
NameFunc: func() string {
|
|
return "test-provider"
|
|
},
|
|
SupportedModelsFunc: func() []string {
|
|
return []string{"gpt-4", "gpt-3.5"}
|
|
},
|
|
ChatCompletionFunc: func(ctx context.Context, model string, messages []Message, options CompletionOptions) (*CompletionResponse, error) {
|
|
called = true
|
|
return &CompletionResponse{ID: "test"}, nil
|
|
},
|
|
HealthCheckFunc: func(ctx context.Context) bool {
|
|
return true
|
|
},
|
|
}
|
|
|
|
if provider.ProviderName() != "test-provider" {
|
|
t.Errorf("unexpected name: %s", provider.ProviderName())
|
|
}
|
|
|
|
models := provider.SupportedModels()
|
|
if len(models) != 2 {
|
|
t.Errorf("expected 2 models, got %d", len(models))
|
|
}
|
|
|
|
resp, err := provider.ChatCompletion(context.Background(), "gpt-4", nil, CompletionOptions{})
|
|
if err != nil {
|
|
t.Errorf("unexpected error: %v", err)
|
|
}
|
|
if resp.ID != "test" {
|
|
t.Errorf("unexpected response ID: %s", resp.ID)
|
|
}
|
|
if !called {
|
|
t.Error("ChatCompletionFunc was not called")
|
|
}
|
|
|
|
if !provider.HealthCheck(context.Background()) {
|
|
t.Error("expected healthy")
|
|
}
|
|
}
|