package main import ( "regexp" "testing" "time" "github.com/tinode/chat/server/store" "github.com/tinode/chat/server/store/types" ) type registrationTestValidator struct{} func (registrationTestValidator) Init(string) error { return nil } func (registrationTestValidator) IsInitialized() bool { return true } func (registrationTestValidator) PreCheck(value string, _ map[string]any) (string, error) { return "tel:" + value, nil } func (registrationTestValidator) Request(types.TenantID, types.Uid, string, string, string, []byte) (bool, error) { return true, nil } func (registrationTestValidator) ResetSecret(types.TenantID, string, string, string, []byte, map[string]any) error { return nil } func (registrationTestValidator) Check(types.TenantID, types.Uid, string) (string, error) { return "", nil } func (registrationTestValidator) Remove(types.TenantID, types.Uid, string) error { return nil } func (registrationTestValidator) Delete(types.TenantID, types.Uid) error { return nil } func (registrationTestValidator) TempAuthScheme() (string, error) { return "code", nil } func TestResolveRegistrationAccount(t *testing.T) { tests := []struct { name string username string phone string wantAccount string wantPhone string wantSecret string wantPhoneFlow bool wantGenerated bool wantErr bool }{ {name: "username", username: "alice1", wantAccount: "alice1", wantSecret: "alice1"}, {name: "numeric username", username: "12345", wantAccount: "12345", wantSecret: "12345"}, {name: "username and phone", username: "alice1", phone: "13800138000", wantAccount: "alice1", wantPhone: "+8613800138000", wantSecret: "alice1", wantPhoneFlow: true}, {name: "phone only", phone: "13800138000", wantPhone: "+8613800138000", wantSecret: "+8613800138000", wantPhoneFlow: true, wantGenerated: true}, {name: "uppercase with phone rejected", username: "Alice", phone: "13800138000", wantErr: true}, {name: "uppercase rejected", username: "Alice", wantErr: true}, {name: "long username rejected", username: "abcdefghijk", wantErr: true}, {name: "malformed phone does not fall back", username: "alice", phone: "1380013800", wantErr: true}, {name: "phone whitespace rejected", username: "alice", phone: " 13800138000", wantErr: true}, } for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { account, phone, secretAccount, phoneFlow, err := resolveRegistrationAccount(tc.username, tc.phone) if (err != nil) != tc.wantErr || phone != tc.wantPhone || secretAccount != tc.wantSecret || phoneFlow != tc.wantPhoneFlow { t.Fatalf("got account=%q phone=%q secret=%q phoneFlow=%v err=%v", account, phone, secretAccount, phoneFlow, err) } if tc.wantGenerated { if !regexp.MustCompile(`^[a-z]{3}[0-9]{6}$`).MatchString(account) { t.Fatalf("generated account %q does not match expected format", account) } } else if account != tc.wantAccount { t.Fatalf("got account=%q want %q", account, tc.wantAccount) } }) } } func TestRegistrationSecretMatchesSelectedAccount(t *testing.T) { if !registrationSecretMatches([]byte("alice:password:with-colon"), "alice") { t.Fatal("matching account was rejected") } if registrationSecretMatches([]byte("bob:password"), "alice") || registrationSecretMatches([]byte("password"), "alice") { t.Fatal("mismatched or malformed secret was accepted") } } func TestRegistrationSecretForAccount(t *testing.T) { if got := string(registrationSecretForAccount([]byte("+8613800138000:secret"), "abcxyz123456")); got != "abcxyz123456:secret" { t.Fatalf("got %q", got) } } func TestRegistrationPublicWithUsername(t *testing.T) { if got := registrationPublicWithUsername(nil, "alice").(map[string]any)["fn"]; got != "alice" { t.Fatalf("got fn=%v", got) } public := map[string]any{"fn": "old", "photo": "file1"} got := registrationPublicWithUsername(public, "bob").(map[string]any) if got["fn"] != "bob" || got["photo"] != "file1" { t.Fatalf("got public=%v", got) } } func TestValidateRegistrationInviteStates(t *testing.T) { oldInvites := store.RegistrationInvites defer func() { store.RegistrationInvites = oldInvites }() now := time.Now().UTC() tests := []struct { status types.RegistrationInviteStatus expires time.Time want error }{ {types.RegistrationInviteActive, now.Add(time.Minute), nil}, {types.RegistrationInviteActive, now, types.ErrInviteExpired}, {types.RegistrationInviteRevoked, now.Add(time.Minute), types.ErrInviteRevoked}, {types.RegistrationInviteExpired, now.Add(time.Minute), types.ErrInviteExpired}, } for _, tc := range tests { store.RegistrationInvites = &testRegistrationInviteStore{invite: &types.RegistrationInviteCode{Status: tc.status, ExpiresAt: tc.expires}} if got := validateRegistrationInvite(7, "AbCdE", now); got != tc.want { t.Fatalf("status=%q: got %v want %v", tc.status, got, tc.want) } } }