2026-02-19 08:02:56 +08:00
package service
import (
"fmt"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"
2026-02-19 20:04:10 +08:00
"time"
2026-02-19 08:02:56 +08:00
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/require"
)
type queuedHTTPUpstream struct {
responses [ ] * http . Response
requests [ ] * http . Request
tlsFlags [ ] bool
}
func ( u * queuedHTTPUpstream ) Do ( _ * http . Request , _ string , _ int64 , _ int ) ( * http . Response , error ) {
return nil , fmt . Errorf ( "unexpected Do call" )
}
func ( u * queuedHTTPUpstream ) DoWithTLS ( req * http . Request , _ string , _ int64 , _ int , enableTLSFingerprint bool ) ( * http . Response , error ) {
u . requests = append ( u . requests , req )
u . tlsFlags = append ( u . tlsFlags , enableTLSFingerprint )
if len ( u . responses ) == 0 {
return nil , fmt . Errorf ( "no mocked response" )
}
resp := u . responses [ 0 ]
u . responses = u . responses [ 1 : ]
return resp , nil
}
func newJSONResponse ( status int , body string ) * http . Response {
return & http . Response {
StatusCode : status ,
Header : make ( http . Header ) ,
Body : io . NopCloser ( strings . NewReader ( body ) ) ,
}
}
func newJSONResponseWithHeader ( status int , body , key , value string ) * http . Response {
resp := newJSONResponse ( status , body )
resp . Header . Set ( key , value )
return resp
}
func newSoraTestContext ( ) ( * gin . Context , * httptest . ResponseRecorder ) {
gin . SetMode ( gin . TestMode )
rec := httptest . NewRecorder ( )
c , _ := gin . CreateTestContext ( rec )
c . Request = httptest . NewRequest ( http . MethodPost , "/api/v1/admin/accounts/1/test" , nil )
return c , rec
}
func TestAccountTestService_testSoraAccountConnection_WithSubscription ( t * testing . T ) {
upstream := & queuedHTTPUpstream {
responses : [ ] * http . Response {
newJSONResponse ( http . StatusOK , ` { "email":"demo@example.com"} ` ) ,
newJSONResponse ( http . StatusOK , ` { "data":[ { "plan": { "id":"chatgpt_plus","title":"ChatGPT Plus"},"end_ts":"2026-12-31T00:00:00Z"}]} ` ) ,
2026-02-19 08:29:51 +08:00
newJSONResponse ( http . StatusOK , ` { "invite_code":"inv_abc","redeemed_count":3,"total_count":50} ` ) ,
newJSONResponse ( http . StatusOK , ` { "rate_limit_and_credit_balance": { "estimated_num_videos_remaining":27,"rate_limit_reached":false,"access_resets_in_seconds":46833}} ` ) ,
2026-02-19 08:02:56 +08:00
} ,
}
svc := & AccountTestService {
httpUpstream : upstream ,
cfg : & config . Config {
Gateway : config . GatewayConfig {
TLSFingerprint : config . TLSFingerprintConfig {
Enabled : true ,
} ,
} ,
Sora : config . SoraConfig {
Client : config . SoraClientConfig {
DisableTLSFingerprint : false ,
} ,
} ,
} ,
}
account := & Account {
ID : 1 ,
Platform : PlatformSora ,
Type : AccountTypeOAuth ,
Concurrency : 1 ,
Credentials : map [ string ] any {
"access_token" : "test_token" ,
} ,
}
c , rec := newSoraTestContext ( )
err := svc . testSoraAccountConnection ( c , account )
require . NoError ( t , err )
2026-02-19 08:29:51 +08:00
require . Len ( t , upstream . requests , 4 )
2026-02-19 08:02:56 +08:00
require . Equal ( t , soraMeAPIURL , upstream . requests [ 0 ] . URL . String ( ) )
require . Equal ( t , soraBillingAPIURL , upstream . requests [ 1 ] . URL . String ( ) )
2026-02-19 08:29:51 +08:00
require . Equal ( t , soraInviteMineURL , upstream . requests [ 2 ] . URL . String ( ) )
require . Equal ( t , soraRemainingURL , upstream . requests [ 3 ] . URL . String ( ) )
2026-02-19 08:02:56 +08:00
require . Equal ( t , "Bearer test_token" , upstream . requests [ 0 ] . Header . Get ( "Authorization" ) )
require . Equal ( t , "Bearer test_token" , upstream . requests [ 1 ] . Header . Get ( "Authorization" ) )
2026-02-19 08:29:51 +08:00
require . Equal ( t , [ ] bool { true , true , true , true } , upstream . tlsFlags )
2026-02-19 08:02:56 +08:00
body := rec . Body . String ( )
require . Contains ( t , body , ` "type":"test_start" ` )
require . Contains ( t , body , "Sora connection OK - Email: demo@example.com" )
require . Contains ( t , body , "Subscription: ChatGPT Plus | chatgpt_plus | end=2026-12-31T00:00:00Z" )
2026-02-19 08:29:51 +08:00
require . Contains ( t , body , "Sora2: supported | invite=inv_abc | used=3/50" )
require . Contains ( t , body , "Sora2 remaining: 27 | reset_in=46833s" )
2026-02-19 20:04:10 +08:00
require . Contains ( t , body , ` "type":"sora_test_result" ` )
require . Contains ( t , body , ` "status":"success" ` )
2026-02-19 08:02:56 +08:00
require . Contains ( t , body , ` "type":"test_complete","success":true ` )
}
func TestAccountTestService_testSoraAccountConnection_SubscriptionFailedStillSuccess ( t * testing . T ) {
upstream := & queuedHTTPUpstream {
responses : [ ] * http . Response {
newJSONResponse ( http . StatusOK , ` { "name":"demo-user"} ` ) ,
newJSONResponse ( http . StatusForbidden , ` { "error": { "message":"forbidden"}} ` ) ,
2026-02-19 08:29:51 +08:00
newJSONResponse ( http . StatusUnauthorized , ` { "error": { "message":"Unauthorized"}} ` ) ,
newJSONResponse ( http . StatusForbidden , ` { "error": { "message":"forbidden"}} ` ) ,
2026-02-19 08:02:56 +08:00
} ,
}
svc := & AccountTestService { httpUpstream : upstream }
account := & Account {
ID : 1 ,
Platform : PlatformSora ,
Type : AccountTypeOAuth ,
Concurrency : 1 ,
Credentials : map [ string ] any {
"access_token" : "test_token" ,
} ,
}
c , rec := newSoraTestContext ( )
err := svc . testSoraAccountConnection ( c , account )
require . NoError ( t , err )
2026-02-19 08:29:51 +08:00
require . Len ( t , upstream . requests , 4 )
2026-02-19 08:02:56 +08:00
body := rec . Body . String ( )
require . Contains ( t , body , "Sora connection OK - User: demo-user" )
require . Contains ( t , body , "Subscription check returned 403" )
2026-02-19 08:29:51 +08:00
require . Contains ( t , body , "Sora2 invite check returned 401" )
2026-02-19 20:04:10 +08:00
require . Contains ( t , body , ` "type":"sora_test_result" ` )
require . Contains ( t , body , ` "status":"partial_success" ` )
2026-02-19 08:02:56 +08:00
require . Contains ( t , body , ` "type":"test_complete","success":true ` )
}
func TestAccountTestService_testSoraAccountConnection_CloudflareChallenge ( t * testing . T ) {
upstream := & queuedHTTPUpstream {
responses : [ ] * http . Response {
newJSONResponseWithHeader ( http . StatusForbidden , ` <!DOCTYPE html><html><head><title>Just a moment...</title></head><body><script>window._cf_chl_opt= { };</script><noscript>Enable JavaScript and cookies to continue</noscript></body></html> ` , "cf-ray" , "9cff2d62d83bb98d" ) ,
} ,
}
svc := & AccountTestService { httpUpstream : upstream }
account := & Account {
ID : 1 ,
Platform : PlatformSora ,
Type : AccountTypeOAuth ,
Concurrency : 1 ,
Credentials : map [ string ] any {
"access_token" : "test_token" ,
} ,
}
c , rec := newSoraTestContext ( )
err := svc . testSoraAccountConnection ( c , account )
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , "Cloudflare challenge" )
require . Contains ( t , err . Error ( ) , "cf-ray: 9cff2d62d83bb98d" )
body := rec . Body . String ( )
require . Contains ( t , body , ` "type":"error" ` )
require . Contains ( t , body , "Cloudflare challenge" )
require . Contains ( t , body , "cf-ray: 9cff2d62d83bb98d" )
}
2026-02-19 20:04:10 +08:00
func TestAccountTestService_testSoraAccountConnection_CloudflareChallenge429WithHeader ( t * testing . T ) {
upstream := & queuedHTTPUpstream {
responses : [ ] * http . Response {
newJSONResponseWithHeader ( http . StatusTooManyRequests , ` <!DOCTYPE html><html><head><title>Just a moment...</title></head><body></body></html> ` , "cf-mitigated" , "challenge" ) ,
} ,
}
svc := & AccountTestService { httpUpstream : upstream }
account := & Account {
ID : 1 ,
Platform : PlatformSora ,
Type : AccountTypeOAuth ,
Concurrency : 1 ,
Credentials : map [ string ] any {
"access_token" : "test_token" ,
} ,
}
c , rec := newSoraTestContext ( )
err := svc . testSoraAccountConnection ( c , account )
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , "Cloudflare challenge" )
require . Contains ( t , err . Error ( ) , "HTTP 429" )
body := rec . Body . String ( )
require . Contains ( t , body , "Cloudflare challenge" )
}
func TestAccountTestService_testSoraAccountConnection_TokenInvalidated ( t * testing . T ) {
upstream := & queuedHTTPUpstream {
responses : [ ] * http . Response {
newJSONResponse ( http . StatusUnauthorized , ` { "error": { "code":"token_invalidated","message":"Token invalid"}} ` ) ,
} ,
}
svc := & AccountTestService { httpUpstream : upstream }
account := & Account {
ID : 1 ,
Platform : PlatformSora ,
Type : AccountTypeOAuth ,
Concurrency : 1 ,
Credentials : map [ string ] any {
"access_token" : "test_token" ,
} ,
}
c , rec := newSoraTestContext ( )
err := svc . testSoraAccountConnection ( c , account )
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , "token_invalidated" )
body := rec . Body . String ( )
require . Contains ( t , body , ` "type":"sora_test_result" ` )
require . Contains ( t , body , ` "status":"failed" ` )
require . Contains ( t , body , "token_invalidated" )
require . NotContains ( t , body , ` "type":"test_complete","success":true ` )
}
func TestAccountTestService_testSoraAccountConnection_RateLimited ( t * testing . T ) {
upstream := & queuedHTTPUpstream {
responses : [ ] * http . Response {
newJSONResponse ( http . StatusOK , ` { "email":"demo@example.com"} ` ) ,
} ,
}
svc := & AccountTestService {
httpUpstream : upstream ,
soraTestCooldown : time . Hour ,
}
account := & Account {
ID : 1 ,
Platform : PlatformSora ,
Type : AccountTypeOAuth ,
Concurrency : 1 ,
Credentials : map [ string ] any {
"access_token" : "test_token" ,
} ,
}
c1 , _ := newSoraTestContext ( )
err := svc . testSoraAccountConnection ( c1 , account )
require . NoError ( t , err )
c2 , rec2 := newSoraTestContext ( )
err = svc . testSoraAccountConnection ( c2 , account )
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , "测试过于频繁" )
body := rec2 . Body . String ( )
require . Contains ( t , body , ` "type":"sora_test_result" ` )
require . Contains ( t , body , ` "code":"test_rate_limited" ` )
require . Contains ( t , body , ` "status":"failed" ` )
require . NotContains ( t , body , ` "type":"test_complete","success":true ` )
}
2026-02-19 08:02:56 +08:00
func TestAccountTestService_testSoraAccountConnection_SubscriptionCloudflareChallengeWithRay ( t * testing . T ) {
upstream := & queuedHTTPUpstream {
responses : [ ] * http . Response {
newJSONResponse ( http . StatusOK , ` { "name":"demo-user"} ` ) ,
newJSONResponse ( http . StatusForbidden , ` <!DOCTYPE html><html><head><title>Just a moment...</title></head><body><script>window._cf_chl_opt= { cRay: '9cff2d62d83bb98d'};</script><noscript>Enable JavaScript and cookies to continue</noscript></body></html> ` ) ,
2026-02-19 08:29:51 +08:00
newJSONResponse ( http . StatusForbidden , ` <!DOCTYPE html><html><head><title>Just a moment...</title></head><body><script>window._cf_chl_opt= { cRay: '9cff2d62d83bb98d'};</script><noscript>Enable JavaScript and cookies to continue</noscript></body></html> ` ) ,
2026-02-19 08:02:56 +08:00
} ,
}
svc := & AccountTestService { httpUpstream : upstream }
account := & Account {
ID : 1 ,
Platform : PlatformSora ,
Type : AccountTypeOAuth ,
Concurrency : 1 ,
Credentials : map [ string ] any {
"access_token" : "test_token" ,
} ,
}
c , rec := newSoraTestContext ( )
err := svc . testSoraAccountConnection ( c , account )
require . NoError ( t , err )
body := rec . Body . String ( )
require . Contains ( t , body , "Subscription check blocked by Cloudflare challenge (HTTP 403)" )
2026-02-19 08:29:51 +08:00
require . Contains ( t , body , "Sora2 invite check blocked by Cloudflare challenge (HTTP 403)" )
2026-02-19 08:02:56 +08:00
require . Contains ( t , body , "cf-ray: 9cff2d62d83bb98d" )
require . Contains ( t , body , ` "type":"test_complete","success":true ` )
}
2026-02-19 15:09:58 +08:00
func TestSanitizeProxyURLForLog ( t * testing . T ) {
require . Equal ( t , "http://proxy.example.com:8080" , sanitizeProxyURLForLog ( "http://user:pass@proxy.example.com:8080" ) )
require . Equal ( t , "" , sanitizeProxyURLForLog ( "" ) )
require . Equal ( t , "<invalid_proxy_url>" , sanitizeProxyURLForLog ( "://invalid" ) )
}
func TestExtractSoraEgressIPHint ( t * testing . T ) {
h := make ( http . Header )
h . Set ( "x-openai-public-ip" , "203.0.113.10" )
require . Equal ( t , "203.0.113.10" , extractSoraEgressIPHint ( h ) )
h2 := make ( http . Header )
h2 . Set ( "x-envoy-external-address" , "198.51.100.9" )
require . Equal ( t , "198.51.100.9" , extractSoraEgressIPHint ( h2 ) )
require . Equal ( t , "unknown" , extractSoraEgressIPHint ( nil ) )
require . Equal ( t , "unknown" , extractSoraEgressIPHint ( http . Header { } ) )
}