系统性地修复、补充和强化项目的自动化测试能力: 1. 测试基础设施修复 - 修复 stubConcurrencyCache 缺失方法和构造函数参数不匹配 - 创建 testutil 共享包(stubs.go, fixtures.go, httptest.go) - 为所有 Stub 添加编译期接口断言 2. 中间件测试补充 - 新增 JWT 认证中间件测试(有效/过期/篡改/缺失 Token) - 补充 rate_limiter 和 recovery 中间件测试场景 3. 网关核心路径测试 - 新增账户选择、等待队列、流式响应、并发控制、计费、Claude Code 检测测试 - 覆盖负载均衡、粘性会话、SSE 转发、槽位管理等关键逻辑 4. 前端测试体系(11个新测试文件,163个测试用例) - Pinia stores: auth, app, subscriptions - API client: 请求拦截器、响应拦截器、401 刷新 - Router guards: 认证重定向、管理员权限、简易模式限制 - Composables: useForm, useTableLoader, useClipboard - Components: LoginForm, ApiKeyCreate, Dashboard 5. CI/CD 流水线重构 - 重构 backend-ci.yml 为统一的 ci.yml - 前后端 4 个并行 Job + Postgres/Redis services - Race 检测、覆盖率收集与门禁、Docker 构建验证 6. E2E 自动化测试 - e2e-test.sh 自动化脚本(Docker 启动→健康检查→测试→清理) - 用户注册→登录→API Key→网关调用完整链路测试 - Mock 模式和 API Key 脱敏支持 7. 修复预存问题 - tlsfingerprint dialer_test.go 缺失 build tag 导致集成测试编译冲突 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
115 lines
3.3 KiB
Go
115 lines
3.3 KiB
Go
//go:build unit
|
|
|
|
package service
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/Wei-Shaw/sub2api/internal/config"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var _ SoraClient = (*stubSoraClientForPoll)(nil)
|
|
|
|
type stubSoraClientForPoll struct {
|
|
imageStatus *SoraImageTaskStatus
|
|
videoStatus *SoraVideoTaskStatus
|
|
imageCalls int
|
|
videoCalls int
|
|
}
|
|
|
|
func (s *stubSoraClientForPoll) Enabled() bool { return true }
|
|
func (s *stubSoraClientForPoll) UploadImage(ctx context.Context, account *Account, data []byte, filename string) (string, error) {
|
|
return "", nil
|
|
}
|
|
func (s *stubSoraClientForPoll) CreateImageTask(ctx context.Context, account *Account, req SoraImageRequest) (string, error) {
|
|
return "task-image", nil
|
|
}
|
|
func (s *stubSoraClientForPoll) CreateVideoTask(ctx context.Context, account *Account, req SoraVideoRequest) (string, error) {
|
|
return "task-video", nil
|
|
}
|
|
func (s *stubSoraClientForPoll) GetImageTask(ctx context.Context, account *Account, taskID string) (*SoraImageTaskStatus, error) {
|
|
s.imageCalls++
|
|
return s.imageStatus, nil
|
|
}
|
|
func (s *stubSoraClientForPoll) GetVideoTask(ctx context.Context, account *Account, taskID string) (*SoraVideoTaskStatus, error) {
|
|
s.videoCalls++
|
|
return s.videoStatus, nil
|
|
}
|
|
|
|
func TestSoraGatewayService_PollImageTaskCompleted(t *testing.T) {
|
|
client := &stubSoraClientForPoll{
|
|
imageStatus: &SoraImageTaskStatus{
|
|
Status: "completed",
|
|
URLs: []string{"https://example.com/a.png"},
|
|
},
|
|
}
|
|
cfg := &config.Config{
|
|
Sora: config.SoraConfig{
|
|
Client: config.SoraClientConfig{
|
|
PollIntervalSeconds: 1,
|
|
MaxPollAttempts: 1,
|
|
},
|
|
},
|
|
}
|
|
service := NewSoraGatewayService(client, nil, nil, cfg)
|
|
|
|
urls, err := service.pollImageTask(context.Background(), nil, &Account{ID: 1}, "task", false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, []string{"https://example.com/a.png"}, urls)
|
|
require.Equal(t, 1, client.imageCalls)
|
|
}
|
|
|
|
func TestSoraGatewayService_PollVideoTaskFailed(t *testing.T) {
|
|
client := &stubSoraClientForPoll{
|
|
videoStatus: &SoraVideoTaskStatus{
|
|
Status: "failed",
|
|
ErrorMsg: "reject",
|
|
},
|
|
}
|
|
cfg := &config.Config{
|
|
Sora: config.SoraConfig{
|
|
Client: config.SoraClientConfig{
|
|
PollIntervalSeconds: 1,
|
|
MaxPollAttempts: 1,
|
|
},
|
|
},
|
|
}
|
|
service := NewSoraGatewayService(client, nil, nil, cfg)
|
|
|
|
urls, err := service.pollVideoTask(context.Background(), nil, &Account{ID: 1}, "task", false)
|
|
require.Error(t, err)
|
|
require.Empty(t, urls)
|
|
require.Contains(t, err.Error(), "reject")
|
|
require.Equal(t, 1, client.videoCalls)
|
|
}
|
|
|
|
func TestSoraGatewayService_BuildSoraMediaURLSigned(t *testing.T) {
|
|
cfg := &config.Config{
|
|
Gateway: config.GatewayConfig{
|
|
SoraMediaSigningKey: "test-key",
|
|
SoraMediaSignedURLTTLSeconds: 600,
|
|
},
|
|
}
|
|
service := NewSoraGatewayService(nil, nil, nil, cfg)
|
|
|
|
url := service.buildSoraMediaURL("/image/2025/01/01/a.png", "")
|
|
require.Contains(t, url, "/sora/media-signed")
|
|
require.Contains(t, url, "expires=")
|
|
require.Contains(t, url, "sig=")
|
|
}
|
|
|
|
func TestDecodeSoraImageInput_BlockPrivateURL(t *testing.T) {
|
|
_, _, err := decodeSoraImageInput(context.Background(), "http://127.0.0.1/internal.png")
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestDecodeSoraImageInput_DataURL(t *testing.T) {
|
|
encoded := "data:image/png;base64,aGVsbG8="
|
|
data, filename, err := decodeSoraImageInput(context.Background(), encoded)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, data)
|
|
require.Contains(t, filename, ".png")
|
|
}
|