package ai_mate_ser import ( "errors" "testing" "91porn-server/models/v/usermod" "91porn-server/models/v/walletmod" "go.mongodb.org/mongo-driver/bson/primitive" ) func TestLoginCreatesAiMateUserAndCreditsLocalBalance(t *testing.T) { const uid = uint64(32239213) var ( updatedAiMateUID string accessAiMateUID string creditedBalance float64 ) deps := loginDependencies{ findUser: func(gotUID uint64) (*usermod.User, error) { if gotUID != uid { t.Fatalf("findUser uid = %d, want %d", gotUID, uid) } return &usermod.User{ ID: primitive.NewObjectID(), UID: uid, Name: "tester", }, nil }, updateUser: func(gotUID uint64, selector usermod.UserSelector) (*usermod.User, error) { if gotUID != uid || selector.AiMateUid == nil { t.Fatalf("unexpected updateUser arguments: uid=%d selector=%+v", gotUID, selector) } updatedAiMateUID = *selector.AiMateUid return &usermod.User{}, nil }, getWallet: func(uint64) (*walletmod.Wallet, error) { return &walletmod.Wallet{AiMateBalance: 12}, nil }, getRemoteBalance: func(uint64, string) (float64, error) { t.Fatal("new AI mate user must not query a remote balance") return 0, nil }, getAccessURL: func(gotUID uint64, aiMateUID, name string, balance float64) (string, error) { if gotUID != uid || name != "tester" { t.Fatalf("unexpected access URL arguments: uid=%d name=%q", gotUID, name) } accessAiMateUID = aiMateUID creditedBalance = balance return "https://example.com/login", nil }, newUUID: func() string { return "new-ai-mate-uid" }, } got, err := login(uid, deps) if err != nil { t.Fatalf("login() error = %v", err) } if got.URL != "https://example.com/login" { t.Fatalf("login() URL = %q", got.URL) } if updatedAiMateUID != "new-ai-mate-uid" || accessAiMateUID != updatedAiMateUID { t.Fatalf("AI mate uid update=%q access=%q", updatedAiMateUID, accessAiMateUID) } if creditedBalance != 12 { t.Fatalf("credited balance = %v, want 12", creditedBalance) } } func TestLoginOnlyCreditsBalanceDifference(t *testing.T) { const uid = uint64(32239213) var creditedBalance float64 deps := loginDependencies{ findUser: func(uint64) (*usermod.User, error) { return &usermod.User{ ID: primitive.NewObjectID(), UID: uid, Name: "tester", AiMateUid: "existing-ai-mate-uid", }, nil }, updateUser: func(uint64, usermod.UserSelector) (*usermod.User, error) { t.Fatal("existing AI mate user must not be updated") return nil, nil }, getWallet: func(uint64) (*walletmod.Wallet, error) { return &walletmod.Wallet{AiMateBalance: 15}, nil }, getRemoteBalance: func(gotUID uint64, aiMateUID string) (float64, error) { if gotUID != uid || aiMateUID != "existing-ai-mate-uid" { t.Fatalf("unexpected remote balance arguments: uid=%d aiMateUID=%q", gotUID, aiMateUID) } return 9, nil }, getAccessURL: func(_ uint64, _ string, _ string, balance float64) (string, error) { creditedBalance = balance return "https://example.com/login", nil }, newUUID: func() string { t.Fatal("existing AI mate user must not generate another uid") return "" }, } if _, err := login(uid, deps); err != nil { t.Fatalf("login() error = %v", err) } if creditedBalance != 6 { t.Fatalf("credited balance = %v, want 6", creditedBalance) } } func TestLoginStopsWhenRemoteBalanceCannotBeRead(t *testing.T) { expectedErr := errors.New("remote unavailable") accessCalled := false deps := loginDependencies{ findUser: func(uint64) (*usermod.User, error) { return &usermod.User{ ID: primitive.NewObjectID(), UID: 1, AiMateUid: "existing-ai-mate-uid", }, nil }, getWallet: func(uint64) (*walletmod.Wallet, error) { return &walletmod.Wallet{AiMateBalance: 10}, nil }, getRemoteBalance: func(uint64, string) (float64, error) { return 0, expectedErr }, getAccessURL: func(uint64, string, string, float64) (string, error) { accessCalled = true return "", nil }, } if _, err := login(1, deps); !errors.Is(err, expectedErr) { t.Fatalf("login() error = %v, want %v", err, expectedErr) } if accessCalled { t.Fatal("access URL must not be requested when the remote balance is unknown") } } func TestBalanceTopUp(t *testing.T) { tests := []struct { name string local float64 remote float64 want float64 }{ {name: "new credit", local: 10, remote: 4, want: 6}, {name: "already synchronized", local: 10, remote: 10, want: 0}, {name: "remote ahead", local: 8, remote: 10, want: 0}, {name: "empty", local: 0, remote: 0, want: 0}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { if got := balanceTopUp(test.local, test.remote); got != test.want { t.Fatalf("balanceTopUp(%v, %v) = %v, want %v", test.local, test.remote, got, test.want) } }) } }