Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
//go:build unit
|
||||
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/imroc/req/v3"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// shadowSkipTestRepo 是满足 AccountRepository 接口的最小 stub(只实现 GetByID)。
|
||||
// 其他方法通过嵌入 nil 接口值满足编译,若被误调则 panic,便于发现意外调用路径。
|
||||
type shadowSkipTestRepo struct {
|
||||
AccountRepository
|
||||
account *Account
|
||||
}
|
||||
|
||||
func (r *shadowSkipTestRepo) GetByID(_ context.Context, id int64) (*Account, error) {
|
||||
if r.account == nil || r.account.ID != id {
|
||||
return nil, ErrAccountNotFound
|
||||
}
|
||||
return r.account, nil
|
||||
}
|
||||
|
||||
func newShadowTestGinCtx() *gin.Context {
|
||||
gin.SetMode(gin.TestMode)
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
c.Request = httptest.NewRequest(http.MethodPost, "/api/v1/admin/accounts/200/test", nil)
|
||||
return c
|
||||
}
|
||||
|
||||
// --- 1. CanRefresh 守卫 ---
|
||||
|
||||
// TestOpenAITokenRefresherSkipsShadow 验证影子账号不被后台 token 刷新器处理。
|
||||
func TestOpenAITokenRefresherSkipsShadow(t *testing.T) {
|
||||
pid := int64(100)
|
||||
r := NewOpenAITokenRefresher(nil, nil)
|
||||
// 影子账号:ParentAccountID 非 nil → CanRefresh 应返回 false
|
||||
require.False(t, r.CanRefresh(&Account{ID: 200, Platform: PlatformOpenAI, Type: AccountTypeOAuth, ParentAccountID: &pid}))
|
||||
// 普通账号:有 refresh_token → CanRefresh 应返回 true
|
||||
require.True(t, r.CanRefresh(&Account{ID: 100, Platform: PlatformOpenAI, Type: AccountTypeOAuth, Credentials: map[string]any{"refresh_token": "RT"}}))
|
||||
}
|
||||
|
||||
// --- 2. TestAccountConnection 影子凭据解析 ---
|
||||
|
||||
// TestAccountTestServiceSkipsShadow 验证影子账号连接测试不再早拒,而是尝试解析母账号凭据。
|
||||
func TestAccountTestServiceSkipsShadow(t *testing.T) {
|
||||
pid := int64(100)
|
||||
shadow := &Account{
|
||||
ID: 200,
|
||||
Platform: PlatformOpenAI,
|
||||
Type: AccountTypeOAuth,
|
||||
ParentAccountID: &pid,
|
||||
}
|
||||
repo := &shadowSkipTestRepo{account: shadow}
|
||||
svc := &AccountTestService{accountRepo: repo}
|
||||
c := newShadowTestGinCtx()
|
||||
|
||||
err := svc.TestAccountConnection(c, 200, "", "", "")
|
||||
require.Error(t, err)
|
||||
require.Contains(t, err.Error(), "resolve spark shadow parent")
|
||||
}
|
||||
|
||||
// --- 3. EnsureOpenAIPrivacy 守卫 ---
|
||||
|
||||
// TestEnsureOpenAIPrivacySkipsShadow 验证影子账号跳过隐私设置(不调用 privacyClientFactory)。
|
||||
// 影子账号透传母账号凭据,但 Extra 通常为空,需给它一个 access_token 才能让
|
||||
// 现有的 token=="" 提前返回路径失效,从而真实验证 IsCredentialShadow 守卫。
|
||||
func TestEnsureOpenAIPrivacySkipsShadow(t *testing.T) {
|
||||
pid := int64(100)
|
||||
shadow := &Account{
|
||||
ID: 200,
|
||||
Platform: PlatformOpenAI,
|
||||
Type: AccountTypeOAuth,
|
||||
ParentAccountID: &pid,
|
||||
// 提供 access_token:没有影子守卫时会进入 factory 调用
|
||||
Credentials: map[string]any{"access_token": "shadow-passthrough-token"},
|
||||
}
|
||||
privacyCalled := false
|
||||
svc := &adminServiceImpl{
|
||||
privacyClientFactory: func(proxyURL string) (*req.Client, error) {
|
||||
privacyCalled = true
|
||||
return nil, errors.New("should not reach factory for shadow account")
|
||||
},
|
||||
}
|
||||
got := svc.EnsureOpenAIPrivacy(context.Background(), shadow)
|
||||
require.Equal(t, "", got)
|
||||
require.False(t, privacyCalled, "privacyClientFactory 不应被影子账号触发")
|
||||
}
|
||||
Reference in New Issue
Block a user