package rpc import ( "context" "testing" "time" "github.com/iamxvbaba/td/tg" "go.uber.org/zap/zaptest" "telesrv/internal/domain" ) type loginEmailAccountService struct { AccountService verifiedEmail string } func (s loginEmailAccountService) VerifyLoginEmail(context.Context, int64, string, string, string, bool) (string, error) { return s.verifiedEmail, nil } func TestEmailSentCodeUsesDeliveryLength(t *testing.T) { authSvc := &captureAuthService{ codeDelivery: domain.AuthCodeDelivery{ Kind: domain.AuthCodeDeliveryEmail, EmailPattern: "a***e@example.test", Length: 6, }, } r := New(Config{}, Deps{Auth: authSvc}, zaptest.NewLogger(t), fixedClock{now: time.Unix(1700000000, 0)}) sent, err := r.tgSentCodeForHash(context.Background(), "hash-email") if err != nil { t.Fatalf("tgSentCodeForHash: %v", err) } code, ok := sent.(*tg.AuthSentCode) if !ok { t.Fatalf("sent = %T, want *tg.AuthSentCode", sent) } emailType, ok := code.Type.(*tg.AuthSentCodeTypeEmailCode) if !ok { t.Fatalf("sent type = %T, want *tg.AuthSentCodeTypeEmailCode", code.Type) } if emailType.Length != 6 { t.Fatalf("email sent code length = %d, want 6", emailType.Length) } } // TestEmailSentCodeOmitsResetPeriodWhenUnavailable locks down the client // signal for "this server can't service auth.resetLoginEmail" (no real SMS // sender, or login email is this account's actual identity): the flags- // optional reset_available_period field must be entirely absent, not merely // 0, since 0 is also what "available right now" looks like on the wire. func TestEmailSentCodeOmitsResetPeriodWhenUnavailable(t *testing.T) { for _, tc := range []struct { name string resetAvailable bool }{ {name: "unavailable", resetAvailable: false}, {name: "available", resetAvailable: true}, } { t.Run(tc.name, func(t *testing.T) { authSvc := &captureAuthService{ codeDelivery: domain.AuthCodeDelivery{ Kind: domain.AuthCodeDeliveryEmail, EmailPattern: "a***e@example.test", Length: 6, }, resetAvailable: tc.resetAvailable, } r := New(Config{}, Deps{Auth: authSvc}, zaptest.NewLogger(t), fixedClock{now: time.Unix(1700000000, 0)}) sent, err := r.tgSentCodeForHash(context.Background(), "hash-email") if err != nil { t.Fatalf("tgSentCodeForHash: %v", err) } code := sent.(*tg.AuthSentCode) emailType := code.Type.(*tg.AuthSentCodeTypeEmailCode) _, ok := emailType.GetResetAvailablePeriod() if ok != tc.resetAvailable { t.Fatalf("reset_available_period present = %v, want %v", ok, tc.resetAvailable) } }) } } func TestAuthSignInRoutesOfficialEmailCodeCarriers(t *testing.T) { const ( phone = "+86 188 0000 0021" hash = "hash-email-login" code = "654321" ) tests := []struct { name string request func() *tg.AuthSignInRequest wantPhoneCodeCalls int wantEmailCodeCalls int }{ { name: "webk_phone_code", request: func() *tg.AuthSignInRequest { return &tg.AuthSignInRequest{PhoneNumber: phone, PhoneCodeHash: hash, PhoneCode: code} }, wantPhoneCodeCalls: 1, }, { name: "tdesktop_android_email_verification", request: func() *tg.AuthSignInRequest { req := &tg.AuthSignInRequest{PhoneNumber: phone, PhoneCodeHash: hash} req.SetEmailVerification(&tg.EmailVerificationCode{Code: code}) return req }, wantEmailCodeCalls: 1, }, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { authSvc := &captureAuthService{signInUser: domain.User{ID: 100200301, Phone: "8618800000021", FirstName: "Alice"}} r := New(Config{}, Deps{Auth: authSvc}, zaptest.NewLogger(t), fixedClock{now: time.Unix(1700000000, 0)}) if _, err := r.onAuthSignIn(context.Background(), tc.request()); err != nil { t.Fatalf("onAuthSignIn: %v", err) } if authSvc.signInCount != tc.wantPhoneCodeCalls || authSvc.signInWithEmailCount != tc.wantEmailCodeCalls { t.Fatalf("SignIn/SignInWithEmail calls=%d/%d, want %d/%d", authSvc.signInCount, authSvc.signInWithEmailCount, tc.wantPhoneCodeCalls, tc.wantEmailCodeCalls) } if tc.wantPhoneCodeCalls == 1 && (authSvc.signInPhone != phone || authSvc.signInHash != hash || authSvc.signInCode != code) { t.Fatalf("SignIn proof=%q/%q/%q, want %q/%q/%q", authSvc.signInPhone, authSvc.signInHash, authSvc.signInCode, phone, hash, code) } if tc.wantEmailCodeCalls == 1 && (authSvc.signInWithEmailPhone != phone || authSvc.signInWithEmailHash != hash || authSvc.signInWithEmailCode != code) { t.Fatalf("SignInWithEmail proof=%q/%q/%q, want %q/%q/%q", authSvc.signInWithEmailPhone, authSvc.signInWithEmailHash, authSvc.signInWithEmailCode, phone, hash, code) } }) } } func TestAccountVerifyEmailLoginSetupReturnsSentCodeSuccess(t *testing.T) { user := domain.User{ ID: 100200300, AccessHash: 900100200, Phone: "8618800000020", FirstName: "Alice", } authSvc := &captureAuthService{signInUser: user} r := New(Config{}, Deps{ Auth: authSvc, Account: loginEmailAccountService{verifiedEmail: "alice@example.test"}, }, zaptest.NewLogger(t), fixedClock{now: time.Unix(1700000000, 0)}) got, err := r.onAccountVerifyEmail(context.Background(), &tg.AccountVerifyEmailRequest{ Purpose: &tg.EmailVerifyPurposeLoginSetup{ PhoneNumber: "+86 188 0000 0020", PhoneCodeHash: "hash-email-setup", }, Verification: &tg.EmailVerificationCode{Code: "654321"}, }) if err != nil { t.Fatalf("onAccountVerifyEmail: %v", err) } verified, ok := got.(*tg.AccountEmailVerifiedLogin) if !ok { t.Fatalf("verified = %T, want *tg.AccountEmailVerifiedLogin", got) } if verified.Email != "alice@example.test" { t.Fatalf("verified email = %q", verified.Email) } success, ok := verified.SentCode.(*tg.AuthSentCodeSuccess) if !ok { t.Fatalf("sent code = %T, want *tg.AuthSentCodeSuccess", verified.SentCode) } authorization, ok := success.Authorization.(*tg.AuthAuthorization) if !ok { t.Fatalf("authorization = %T, want *tg.AuthAuthorization", success.Authorization) } self, ok := authorization.User.(*tg.User) if !ok { t.Fatalf("authorization user = %T, want *tg.User", authorization.User) } if self.ID != user.ID || !self.Self { t.Fatalf("authorization user = %+v, want self user %d", self, user.ID) } if authSvc.signInWithEmailCount != 1 { t.Fatalf("SignInWithEmail calls = %d, want 1", authSvc.signInWithEmailCount) } } func TestAccountVerifyEmailLoginSetupAndroidReturnsEmailSentCode(t *testing.T) { authSvc := &captureAuthService{} r := New(Config{}, Deps{ Auth: authSvc, Account: loginEmailAccountService{verifiedEmail: "alice@example.test"}, }, zaptest.NewLogger(t), fixedClock{now: time.Unix(1700000000, 0)}) ctx := WithClientInfo(context.Background(), ClientInfo{ Type: ClientTypeAndroid, AppVersion: "12.8.1 (69169) pbeta", }) got, err := r.onAccountVerifyEmail(ctx, &tg.AccountVerifyEmailRequest{ Purpose: &tg.EmailVerifyPurposeLoginSetup{ PhoneNumber: "+86 188 0000 0020", PhoneCodeHash: "hash-email-setup", }, Verification: &tg.EmailVerificationCode{Code: "654321"}, }) if err != nil { t.Fatalf("onAccountVerifyEmail: %v", err) } verified, ok := got.(*tg.AccountEmailVerifiedLogin) if !ok { t.Fatalf("verified = %T, want *tg.AccountEmailVerifiedLogin", got) } sent, ok := verified.SentCode.(*tg.AuthSentCode) if !ok { t.Fatalf("sent code = %T, want *tg.AuthSentCode", verified.SentCode) } if sent.PhoneCodeHash != "hash-email-setup" { t.Fatalf("phone_code_hash = %q", sent.PhoneCodeHash) } emailType, ok := sent.Type.(*tg.AuthSentCodeTypeEmailCode) if !ok { t.Fatalf("sent type = %T, want *tg.AuthSentCodeTypeEmailCode", sent.Type) } if emailType.EmailPattern != "a***e@example.test" { t.Fatalf("email pattern = %q", emailType.EmailPattern) } if emailType.Length != 6 { t.Fatalf("email code length = %d, want 6", emailType.Length) } if authSvc.signInWithEmailCount != 0 { t.Fatalf("SignInWithEmail calls = %d, want 0 for Android compat downgrade", authSvc.signInWithEmailCount) } }