package aiser import ( "context" "errors" "testing" "time" "91porn-server/app/appg" "91porn-server/common/laosiji_app" "91porn-server/models/v/fundtransferlogmod" "91porn-server/models/v/usermod" "91porn-server/models/v/walletmod" ) func TestGetAuthURLTransfersWalletAmount(t *testing.T) { originalConfig := appg.Conf appg.Conf = &appg.GlobalConfig{} appg.Conf.Base.Env = "test" t.Cleanup(func() { appg.Conf = originalConfig }) var ( gotAsset string gotDebit int64 gotUser string debitTime time.Time ) now := time.Date(2026, 7, 27, 12, 0, 0, 0, time.UTC) deps := transferDependencies{ findLatest: func(uint64, fundtransferlogmod.Category) (*fundtransferlogmod.FundTransferLog, error) { return nil, nil }, findLatestOut: func(uint64, fundtransferlogmod.Category) (*fundtransferlogmod.FundTransferLog, error) { return &fundtransferlogmod.FundTransferLog{Remainder: 0.04}, nil }, getWallet: func(uint64) (*walletmod.Wallet, error) { return &walletmod.Wallet{Amount: 123}, nil }, findUser: func(uint64) (*usermod.User, error) { return &usermod.User{Name: "测试", Portrait: "avatar"}, nil }, getAuthURL: func(_ context.Context, req laosiji_app.GetAiMateURLReq) (laosiji_app.GetAiMateURLResp, error) { gotAsset = req.Asset gotUser = req.Username return laosiji_app.GetAiMateURLResp{AuthURL: "https://example.com/auth"}, nil }, bringOut: func(context.Context, laosiji_app.AiMateBringOutReq) (laosiji_app.AiMateBringOutResp, error) { t.Fatal("bringOut must not be called without pending transfer") return laosiji_app.AiMateBringOutResp{}, nil }, debitAndLog: func(_ uint64, amount int64, at time.Time) error { gotDebit, debitTime = amount, at return nil }, creditAndLog: func(uint64, int64, float64, string, time.Time) error { t.Fatal("creditAndLog must not be called") return nil }, now: func() time.Time { return now }, } resp, err := getAuthURL(context.Background(), 99, deps) if err != nil { t.Fatalf("getAuthURL() error = %v", err) } if resp.URL != "https://example.com/auth" { t.Fatalf("URL = %q", resp.URL) } if gotAsset != "12.34" { t.Fatalf("asset = %q, want 12.34", gotAsset) } if gotUser != "TEST-204_99" { t.Fatalf("username = %q", gotUser) } if gotDebit != 123 || !debitTime.Equal(now) { t.Fatalf("debit = %d at %s", gotDebit, debitTime) } } func TestGetAuthURLDoesNotDebitWhenThirdPartyFails(t *testing.T) { originalConfig := appg.Conf appg.Conf = &appg.GlobalConfig{} t.Cleanup(func() { appg.Conf = originalConfig }) deps := transferDependencies{ findLatest: func(uint64, fundtransferlogmod.Category) (*fundtransferlogmod.FundTransferLog, error) { return nil, nil }, findLatestOut: func(uint64, fundtransferlogmod.Category) (*fundtransferlogmod.FundTransferLog, error) { return nil, nil }, getWallet: func(uint64) (*walletmod.Wallet, error) { return &walletmod.Wallet{Amount: 100}, nil }, findUser: func(uint64) (*usermod.User, error) { return nil, nil }, getAuthURL: func(context.Context, laosiji_app.GetAiMateURLReq) (laosiji_app.GetAiMateURLResp, error) { return laosiji_app.GetAiMateURLResp{}, errors.New("remote failed") }, debitAndLog: func(uint64, int64, time.Time) error { t.Fatal("debitAndLog must not be called after remote failure") return nil }, now: time.Now, } if _, err := getAuthURL(context.Background(), 99, deps); err == nil { t.Fatal("getAuthURL() error = nil") } } func TestGetAuthURLSettlesPendingTransferBeforeNewTransfer(t *testing.T) { originalConfig := appg.Conf appg.Conf = &appg.GlobalConfig{} appg.Conf.Base.Env = "test" t.Cleanup(func() { appg.Conf = originalConfig }) steps := make([]string, 0, 5) deps := transferDependencies{ findLatest: func(uint64, fundtransferlogmod.Category) (*fundtransferlogmod.FundTransferLog, error) { steps = append(steps, "find-pending") return &fundtransferlogmod.FundTransferLog{FundType: fundtransferlogmod.FundTypeIn}, nil }, bringOut: func(context.Context, laosiji_app.AiMateBringOutReq) (laosiji_app.AiMateBringOutResp, error) { steps = append(steps, "bring-out") return laosiji_app.AiMateBringOutResp{Balance: "1.00"}, nil }, creditAndLog: func(uint64, int64, float64, string, time.Time) error { steps = append(steps, "credit") return nil }, getWallet: func(uint64) (*walletmod.Wallet, error) { steps = append(steps, "wallet") return &walletmod.Wallet{Amount: 20}, nil }, findLatestOut: func(uint64, fundtransferlogmod.Category) (*fundtransferlogmod.FundTransferLog, error) { return nil, nil }, findUser: func(uint64) (*usermod.User, error) { return nil, nil }, getAuthURL: func(context.Context, laosiji_app.GetAiMateURLReq) (laosiji_app.GetAiMateURLResp, error) { steps = append(steps, "auth") return laosiji_app.GetAiMateURLResp{AuthURL: "https://example.com/auth"}, nil }, debitAndLog: func(uint64, int64, time.Time) error { return nil }, now: time.Now, } if _, err := getAuthURL(context.Background(), 99, deps); err != nil { t.Fatalf("getAuthURL() error = %v", err) } want := []string{"find-pending", "bring-out", "credit", "wallet", "auth"} if len(steps) != len(want) { t.Fatalf("steps = %v, want %v", steps, want) } for index := range want { if steps[index] != want[index] { t.Fatalf("steps = %v, want %v", steps, want) } } } func TestSettleDownCreditsConvertedBalance(t *testing.T) { originalConfig := appg.Conf appg.Conf = &appg.GlobalConfig{} appg.Conf.Base.Env = "prod" t.Cleanup(func() { appg.Conf = originalConfig }) var ( gotAmount int64 gotRemainder float64 gotDesc string gotUsername string ) deps := transferDependencies{ findLatest: func(uint64, fundtransferlogmod.Category) (*fundtransferlogmod.FundTransferLog, error) { return &fundtransferlogmod.FundTransferLog{FundType: fundtransferlogmod.FundTypeIn}, nil }, bringOut: func(_ context.Context, req laosiji_app.AiMateBringOutReq) (laosiji_app.AiMateBringOutResp, error) { gotUsername = req.Username return laosiji_app.AiMateBringOutResp{Balance: "1.23"}, nil }, creditAndLog: func(_ uint64, amount int64, remainder float64, desc string, _ time.Time) error { gotAmount, gotRemainder, gotDesc = amount, remainder, desc return nil }, now: time.Now, } if err := settleDown(context.Background(), 88, deps); err != nil { t.Fatalf("settleDown() error = %v", err) } if gotUsername != "JHA-204_88" { t.Fatalf("username = %q", gotUsername) } if gotAmount != 12 || gotRemainder != 0.03 || gotDesc != "AI女友下分" { t.Fatalf("credit amount=%d remainder=%v desc=%q", gotAmount, gotRemainder, gotDesc) } } func TestBalanceToWallet(t *testing.T) { tests := []struct { balance string amount int64 remainder float64 wantErr bool }{ {balance: "0", amount: 0, remainder: 0}, {balance: "1.20", amount: 12, remainder: 0}, {balance: "1.29", amount: 12, remainder: 0.09}, {balance: "-1", wantErr: true}, {balance: "invalid", wantErr: true}, } for _, test := range tests { amount, remainder, err := balanceToWallet(test.balance) if (err != nil) != test.wantErr { t.Fatalf("balanceToWallet(%q) error = %v", test.balance, err) } if amount != test.amount || remainder != test.remainder { t.Fatalf("balanceToWallet(%q) = (%d,%v), want (%d,%v)", test.balance, amount, remainder, test.amount, test.remainder) } } }