| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596 |
- //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 不应被影子账号触发")
- }
|