@umituz/react-native-auth 3.4.21 → 3.4.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,38 +1,17 @@
1
- /**
2
- * Auth Validation Utilities
3
- * Single Responsibility: Email and password validation for authentication
4
- */
5
-
6
1
  import type { PasswordConfig } from "../../domain/value-objects/AuthConfig";
7
2
  import { getAuthPackage } from "../services/AuthPackage";
8
3
 
9
- export interface ValidationResult {
10
- isValid: boolean;
11
- error?: string; // This should be a localization key
12
- }
13
-
14
- export interface PasswordStrengthResult extends ValidationResult {
15
- requirements: PasswordRequirements;
16
- }
17
-
4
+ export interface ValidationResult { isValid: boolean; error?: string; }
5
+ export interface PasswordStrengthResult extends ValidationResult { requirements: PasswordRequirements; }
18
6
  export interface PasswordRequirements {
19
- hasMinLength: boolean;
20
- hasUppercase: boolean;
21
- hasLowercase: boolean;
22
- hasNumber: boolean;
23
- hasSpecialChar: boolean;
7
+ hasMinLength: boolean; hasUppercase: boolean; hasLowercase: boolean; hasNumber: boolean; hasSpecialChar: boolean;
24
8
  }
25
-
26
9
  export interface ValidationConfig {
27
- emailRegex: RegExp;
28
- uppercaseRegex: RegExp;
29
- lowercaseRegex: RegExp;
30
- numberRegex: RegExp;
31
- specialCharRegex: RegExp;
32
- displayNameMinLength: number;
10
+ emailRegex: RegExp; uppercaseRegex: RegExp; lowercaseRegex: RegExp;
11
+ numberRegex: RegExp; specialCharRegex: RegExp; displayNameMinLength: number;
33
12
  }
34
13
 
35
- const DEFAULT_VALIDATION_CONFIG: ValidationConfig = {
14
+ const DEFAULT_VAL_CONFIG: ValidationConfig = {
36
15
  emailRegex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
37
16
  uppercaseRegex: /[A-Z]/,
38
17
  lowercaseRegex: /[a-z]/,
@@ -41,155 +20,57 @@ const DEFAULT_VALIDATION_CONFIG: ValidationConfig = {
41
20
  displayNameMinLength: 2,
42
21
  };
43
22
 
44
- function getValidationConfig(): ValidationConfig {
45
- const packageConfig = getAuthPackage()?.getConfig();
23
+ function getValConfig(): ValidationConfig {
24
+ const p = getAuthPackage()?.getConfig();
46
25
  return {
47
- emailRegex: packageConfig?.validation.emailRegex || DEFAULT_VALIDATION_CONFIG.emailRegex,
48
- uppercaseRegex: DEFAULT_VALIDATION_CONFIG.uppercaseRegex,
49
- lowercaseRegex: DEFAULT_VALIDATION_CONFIG.lowercaseRegex,
50
- numberRegex: DEFAULT_VALIDATION_CONFIG.numberRegex,
51
- specialCharRegex: DEFAULT_VALIDATION_CONFIG.specialCharRegex,
52
- displayNameMinLength: DEFAULT_VALIDATION_CONFIG.displayNameMinLength,
26
+ emailRegex: p?.validation.emailRegex || DEFAULT_VAL_CONFIG.emailRegex,
27
+ uppercaseRegex: DEFAULT_VAL_CONFIG.uppercaseRegex,
28
+ lowercaseRegex: DEFAULT_VAL_CONFIG.lowercaseRegex,
29
+ numberRegex: DEFAULT_VAL_CONFIG.numberRegex,
30
+ specialCharRegex: DEFAULT_VAL_CONFIG.specialCharRegex,
31
+ displayNameMinLength: DEFAULT_VAL_CONFIG.displayNameMinLength,
53
32
  };
54
33
  }
55
34
 
56
- /**
57
- * Validate email format
58
- */
59
35
  export function validateEmail(email: string): ValidationResult {
60
- if (!email || email.trim() === "") {
61
- return { isValid: false, error: "auth.validation.emailRequired" };
62
- }
63
-
64
- const config = getValidationConfig();
65
- if (!config.emailRegex.test(email.trim())) {
66
- return { isValid: false, error: "auth.validation.invalidEmail" };
67
- }
68
-
36
+ if (!email || email.trim() === "") return { isValid: false, error: "auth.validation.emailRequired" };
37
+ if (!getValConfig().emailRegex.test(email.trim())) return { isValid: false, error: "auth.validation.invalidEmail" };
69
38
  return { isValid: true };
70
39
  }
71
40
 
72
- /**
73
- * Validate password for login - only checks if password is provided
74
- * No strength requirements for login (existing users may have old passwords)
75
- */
76
41
  export function validatePasswordForLogin(password: string): ValidationResult {
77
- if (!password || password.length === 0) {
78
- return { isValid: false, error: "auth.validation.passwordRequired" };
79
- }
80
-
42
+ if (!password || password.length === 0) return { isValid: false, error: "auth.validation.passwordRequired" };
81
43
  return { isValid: true };
82
44
  }
83
45
 
84
- /**
85
- * Validate password strength for registration
86
- * Returns detailed requirements for UI feedback
87
- */
88
- export function validatePasswordForRegister(
89
- password: string,
90
- config: PasswordConfig
91
- ): PasswordStrengthResult {
92
- const validationConfig = getValidationConfig();
93
- const requirements: PasswordRequirements = {
46
+ export function validatePasswordForRegister(password: string, config: PasswordConfig): PasswordStrengthResult {
47
+ const v = getValConfig();
48
+ const req: PasswordRequirements = {
94
49
  hasMinLength: password.length >= config.minLength,
95
- hasUppercase: !config.requireUppercase || validationConfig.uppercaseRegex.test(password),
96
- hasLowercase: !config.requireLowercase || validationConfig.lowercaseRegex.test(password),
97
- hasNumber: !config.requireNumber || validationConfig.numberRegex.test(password),
98
- hasSpecialChar:
99
- !config.requireSpecialChar || validationConfig.specialCharRegex.test(password),
50
+ hasUppercase: !config.requireUppercase || v.uppercaseRegex.test(password),
51
+ hasLowercase: !config.requireLowercase || v.lowercaseRegex.test(password),
52
+ hasNumber: !config.requireNumber || v.numberRegex.test(password),
53
+ hasSpecialChar: !config.requireSpecialChar || v.specialCharRegex.test(password),
100
54
  };
101
55
 
102
- if (!password || password.length === 0) {
103
- return {
104
- isValid: false,
105
- error: "auth.validation.passwordRequired",
106
- requirements,
107
- };
108
- }
109
-
110
- if (!requirements.hasMinLength) {
111
- return {
112
- isValid: false,
113
- error: "auth.validation.passwordTooShort",
114
- requirements,
115
- };
116
- }
117
-
118
- if (config.requireUppercase && !validationConfig.uppercaseRegex.test(password)) {
119
- return {
120
- isValid: false,
121
- error: "auth.validation.passwordRequireUppercase",
122
- requirements,
123
- };
124
- }
56
+ if (!password) return { isValid: false, error: "auth.validation.passwordRequired", requirements: req };
57
+ if (!req.hasMinLength) return { isValid: false, error: "auth.validation.passwordTooShort", requirements: req };
58
+ if (config.requireUppercase && !req.hasUppercase) return { isValid: false, error: "auth.validation.passwordRequireUppercase", requirements: req };
59
+ if (config.requireLowercase && !req.hasLowercase) return { isValid: false, error: "auth.validation.passwordRequireLowercase", requirements: req };
60
+ if (config.requireNumber && !req.hasNumber) return { isValid: false, error: "auth.validation.passwordRequireNumber", requirements: req };
61
+ if (config.requireSpecialChar && !req.hasSpecialChar) return { isValid: false, error: "auth.validation.passwordRequireSpecialChar", requirements: req };
125
62
 
126
- if (config.requireLowercase && !validationConfig.lowercaseRegex.test(password)) {
127
- return {
128
- isValid: false,
129
- error: "auth.validation.passwordRequireLowercase",
130
- requirements,
131
- };
132
- }
133
-
134
- if (config.requireNumber && !validationConfig.numberRegex.test(password)) {
135
- return {
136
- isValid: false,
137
- error: "auth.validation.passwordRequireNumber",
138
- requirements,
139
- };
140
- }
141
-
142
- if (config.requireSpecialChar && !validationConfig.specialCharRegex.test(password)) {
143
- return {
144
- isValid: false,
145
- error: "auth.validation.passwordRequireSpecialChar",
146
- requirements,
147
- };
148
- }
149
-
150
- return { isValid: true, requirements };
63
+ return { isValid: true, requirements: req };
151
64
  }
152
65
 
153
- /**
154
- * Validate password confirmation
155
- */
156
- export function validatePasswordConfirmation(
157
- password: string,
158
- confirmPassword: string
159
- ): ValidationResult {
160
- if (!confirmPassword) {
161
- return { isValid: false, error: "auth.validation.confirmPasswordRequired" };
162
- }
163
-
164
- if (password !== confirmPassword) {
165
- return { isValid: false, error: "auth.validation.passwordsDoNotMatch" };
166
- }
167
-
66
+ export function validatePasswordConfirmation(password: string, confirm: string): ValidationResult {
67
+ if (!confirm) return { isValid: false, error: "auth.validation.confirmPasswordRequired" };
68
+ if (password !== confirm) return { isValid: false, error: "auth.validation.passwordsDoNotMatch" };
168
69
  return { isValid: true };
169
70
  }
170
71
 
171
- /**
172
- * Validate display name
173
- */
174
- export function validateDisplayName(
175
- displayName: string,
176
- minLength?: number
177
- ): ValidationResult {
178
- if (!displayName || displayName.trim() === "") {
179
- return { isValid: false, error: "auth.validation.nameRequired" };
180
- }
181
-
182
- const config = getValidationConfig();
183
- const actualMinLength = minLength ?? config.displayNameMinLength;
184
-
185
- if (displayName.trim().length < actualMinLength) {
186
- return {
187
- isValid: false,
188
- error: "auth.validation.nameTooShort",
189
- };
190
- }
191
-
72
+ export function validateDisplayName(name: string, minLength?: number): ValidationResult {
73
+ if (!name || name.trim() === "") return { isValid: false, error: "auth.validation.nameRequired" };
74
+ if (name.trim().length < (minLength ?? getValConfig().displayNameMinLength)) return { isValid: false, error: "auth.validation.nameTooShort" };
192
75
  return { isValid: true };
193
76
  }
194
-
195
-
@@ -13,23 +13,22 @@ import {
13
13
  BottomSheetModal,
14
14
  } from "@umituz/react-native-design-system";
15
15
  import { useLocalization } from "@umituz/react-native-localization";
16
- import { useAuthBottomSheet } from "../hooks/useAuthBottomSheet";
16
+ import { useAuthBottomSheet, type SocialAuthConfiguration } from "../hooks/useAuthBottomSheet";
17
17
  import { LoginForm } from "./LoginForm";
18
18
  import { RegisterForm } from "./RegisterForm";
19
19
  import { SocialLoginButtons } from "./SocialLoginButtons";
20
20
  import { styles } from "./AuthBottomSheet.styles";
21
- import type { SocialAuthProvider } from "../../domain/value-objects/AuthConfig";
22
21
 
23
22
  export interface AuthBottomSheetProps {
24
23
  termsUrl?: string;
25
24
  privacyUrl?: string;
26
25
  onTermsPress?: () => void;
27
26
  onPrivacyPress?: () => void;
28
- /** Enabled social auth providers */
29
- socialProviders?: SocialAuthProvider[];
30
- /** Called when Google sign-in is requested */
27
+ /** Social auth configuration */
28
+ socialConfig?: SocialAuthConfiguration;
29
+ /** Called when Google sign-in is requested (overrides internal behavior) */
31
30
  onGoogleSignIn?: () => Promise<void>;
32
- /** Called when Apple sign-in is requested */
31
+ /** Called when Apple sign-in is requested (overrides internal behavior) */
33
32
  onAppleSignIn?: () => Promise<void>;
34
33
  }
35
34
 
@@ -38,7 +37,7 @@ export const AuthBottomSheet: React.FC<AuthBottomSheetProps> = ({
38
37
  privacyUrl,
39
38
  onTermsPress,
40
39
  onPrivacyPress,
41
- socialProviders = [],
40
+ socialConfig,
42
41
  onGoogleSignIn,
43
42
  onAppleSignIn,
44
43
  }) => {
@@ -50,13 +49,14 @@ export const AuthBottomSheet: React.FC<AuthBottomSheetProps> = ({
50
49
  googleLoading,
51
50
  appleLoading,
52
51
  mode,
52
+ providers,
53
53
  handleDismiss,
54
54
  handleClose,
55
55
  handleNavigateToRegister,
56
56
  handleNavigateToLogin,
57
57
  handleGoogleSignIn,
58
58
  handleAppleSignIn,
59
- } = useAuthBottomSheet({ onGoogleSignIn, onAppleSignIn });
59
+ } = useAuthBottomSheet({ socialConfig, onGoogleSignIn, onAppleSignIn });
60
60
 
61
61
  return (
62
62
  <BottomSheetModal
@@ -106,9 +106,9 @@ export const AuthBottomSheet: React.FC<AuthBottomSheetProps> = ({
106
106
  />
107
107
  )}
108
108
 
109
- {socialProviders.length > 0 && (
109
+ {providers.length > 0 && (
110
110
  <SocialLoginButtons
111
- enabledProviders={socialProviders}
111
+ enabledProviders={providers}
112
112
  onGooglePress={() => { void handleGoogleSignIn(); }}
113
113
  onApplePress={() => { void handleAppleSignIn(); }}
114
114
  googleLoading={googleLoading}
@@ -1,14 +1,28 @@
1
- import { useCallback, useEffect, useRef, useState } from "react";
1
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
2
+ import { Platform } from "react-native";
2
3
  import type { BottomSheetModalRef } from "@umituz/react-native-design-system";
3
4
  import { useAuthModalStore } from "../stores/authModalStore";
4
5
  import { useAuth } from "../hooks/useAuth";
6
+ import { useGoogleAuth, type GoogleAuthConfig } from "./useGoogleAuth";
7
+ import { useAppleAuth } from "./useAppleAuth";
8
+ import type { SocialAuthProvider } from "../../domain/value-objects/AuthConfig";
5
9
 
6
- interface UseAuthBottomSheetProps {
10
+ declare const __DEV__: boolean;
11
+
12
+ export interface SocialAuthConfiguration {
13
+ google?: GoogleAuthConfig;
14
+ apple?: { enabled: boolean };
15
+ }
16
+
17
+ interface UseAuthBottomSheetParams {
18
+ socialConfig?: SocialAuthConfiguration;
7
19
  onGoogleSignIn?: () => Promise<void>;
8
20
  onAppleSignIn?: () => Promise<void>;
9
21
  }
10
22
 
11
- export function useAuthBottomSheet({ onGoogleSignIn, onAppleSignIn }: UseAuthBottomSheetProps) {
23
+ export function useAuthBottomSheet(params: UseAuthBottomSheetParams = {}) {
24
+ const { socialConfig, onGoogleSignIn, onAppleSignIn } = params;
25
+
12
26
  const modalRef = useRef<BottomSheetModalRef>(null);
13
27
  const [googleLoading, setGoogleLoading] = useState(false);
14
28
  const [appleLoading, setAppleLoading] = useState(false);
@@ -17,6 +31,25 @@ export function useAuthBottomSheet({ onGoogleSignIn, onAppleSignIn }: UseAuthBot
17
31
  useAuthModalStore();
18
32
  const { isAuthenticated, isAnonymous } = useAuth();
19
33
 
34
+ // Social Auth Hooks
35
+ const { signInWithGoogle, googleConfigured } = useGoogleAuth(socialConfig?.google);
36
+ const { signInWithApple, appleAvailable } = useAppleAuth();
37
+
38
+ // Determine enabled providers
39
+ const providers = useMemo<SocialAuthProvider[]>(() => {
40
+ const result: SocialAuthProvider[] = [];
41
+
42
+ if (Platform.OS === "ios" && socialConfig?.apple?.enabled && appleAvailable) {
43
+ result.push("apple");
44
+ }
45
+
46
+ if (googleConfigured) {
47
+ result.push("google");
48
+ }
49
+
50
+ return result;
51
+ }, [socialConfig?.apple?.enabled, appleAvailable, googleConfigured]);
52
+
20
53
  // Handle visibility sync with modalRef
21
54
  useEffect(() => {
22
55
  if (isVisible) {
@@ -41,14 +74,13 @@ export function useAuthBottomSheet({ onGoogleSignIn, onAppleSignIn }: UseAuthBot
41
74
  const prevIsAnonymousRef = useRef(isAnonymous);
42
75
 
43
76
  useEffect(() => {
44
- // Determine if user just successfully authenticated (either A: were not authed at all, or B: were anonymous and now aren't)
45
77
  const justAuthenticated = !prevIsAuthenticatedRef.current && isAuthenticated;
46
78
  const justConvertedFromAnonymous = prevIsAnonymousRef.current && !isAnonymous && isAuthenticated;
47
79
 
48
80
  if ((justAuthenticated || justConvertedFromAnonymous) && isVisible && !isAnonymous) {
49
81
  if (typeof __DEV__ !== "undefined" && __DEV__) {
50
82
  // eslint-disable-next-line no-console
51
- console.log("[AuthBottomSheet] Auto-closing due to successful authentication transition", {
83
+ console.log("[useAuthBottomSheet] Auto-closing due to successful authentication transition", {
52
84
  justAuthenticated,
53
85
  justConvertedFromAnonymous,
54
86
  });
@@ -57,7 +89,6 @@ export function useAuthBottomSheet({ onGoogleSignIn, onAppleSignIn }: UseAuthBot
57
89
  executePendingCallback();
58
90
  }
59
91
 
60
- // Update refs for next render
61
92
  prevIsAuthenticatedRef.current = isAuthenticated;
62
93
  prevIsVisibleRef.current = isVisible;
63
94
  prevIsAnonymousRef.current = isAnonymous;
@@ -71,36 +102,43 @@ export function useAuthBottomSheet({ onGoogleSignIn, onAppleSignIn }: UseAuthBot
71
102
  setMode("login");
72
103
  }, [setMode]);
73
104
 
74
- const handleGoogleSignIn = useCallback(async () => {
75
- if (!onGoogleSignIn) return;
105
+ const handleGoogleSignInInternal = useCallback(async () => {
76
106
  setGoogleLoading(true);
77
107
  try {
78
- await onGoogleSignIn();
108
+ if (onGoogleSignIn) {
109
+ await onGoogleSignIn();
110
+ } else if (signInWithGoogle) {
111
+ await signInWithGoogle();
112
+ }
79
113
  } finally {
80
114
  setGoogleLoading(false);
81
115
  }
82
- }, [onGoogleSignIn]);
116
+ }, [onGoogleSignIn, signInWithGoogle]);
83
117
 
84
- const handleAppleSignIn = useCallback(async () => {
85
- if (!onAppleSignIn) return;
118
+ const handleAppleSignInInternal = useCallback(async () => {
86
119
  setAppleLoading(true);
87
120
  try {
88
- await onAppleSignIn();
121
+ if (onAppleSignIn) {
122
+ await onAppleSignIn();
123
+ } else if (signInWithApple) {
124
+ await signInWithApple();
125
+ }
89
126
  } finally {
90
127
  setAppleLoading(false);
91
128
  }
92
- }, [onAppleSignIn]);
129
+ }, [onAppleSignIn, signInWithApple]);
93
130
 
94
131
  return {
95
132
  modalRef,
96
133
  googleLoading,
97
134
  appleLoading,
98
135
  mode,
136
+ providers,
99
137
  handleDismiss,
100
138
  handleClose,
101
139
  handleNavigateToRegister,
102
140
  handleNavigateToLogin,
103
- handleGoogleSignIn,
104
- handleAppleSignIn,
141
+ handleGoogleSignIn: handleGoogleSignInInternal,
142
+ handleAppleSignIn: handleAppleSignInInternal,
105
143
  };
106
144
  }
@@ -1,247 +0,0 @@
1
- /**
2
- * AuthCoreService Tests
3
- */
4
-
5
- import { AuthCoreService } from '../../../src/infrastructure/services/AuthCoreService';
6
- import { DEFAULT_AUTH_CONFIG } from '../../../src/domain/value-objects/AuthConfig';
7
- import type { IAuthProvider } from '../../../src/application/ports/IAuthProvider';
8
- import type { AuthUser } from '../../../src/domain/entities/AuthUser';
9
-
10
- describe('AuthCoreService', () => {
11
- let authCoreService: AuthCoreService;
12
- let mockProvider: jest.Mocked<IAuthProvider>;
13
-
14
- beforeEach(() => {
15
- mockProvider = {
16
- initialize: jest.fn(),
17
- isInitialized: jest.fn().mockReturnValue(true),
18
- signIn: jest.fn(),
19
- signUp: jest.fn(),
20
- signOut: jest.fn(),
21
- getCurrentUser: jest.fn(),
22
- onAuthStateChange: jest.fn().mockReturnValue(jest.fn()),
23
- };
24
-
25
- authCoreService = new AuthCoreService(DEFAULT_AUTH_CONFIG);
26
- });
27
-
28
- describe('constructor', () => {
29
- it('should initialize with provided config', () => {
30
- const customConfig = {
31
- ...DEFAULT_AUTH_CONFIG,
32
- password: {
33
- ...DEFAULT_AUTH_CONFIG.password,
34
- minLength: 12,
35
- },
36
- };
37
-
38
- const service = new AuthCoreService(customConfig);
39
- expect(service.getConfig()).toEqual(customConfig);
40
- });
41
- });
42
-
43
- describe('initialize', () => {
44
- it('should initialize with IAuthProvider', async () => {
45
- await authCoreService.initialize(mockProvider);
46
- expect(mockProvider.initialize).toHaveBeenCalled();
47
- });
48
-
49
- it('should initialize with Firebase Auth instance', async () => {
50
- const mockFirebaseAuth = {
51
- currentUser: null,
52
- } as any;
53
-
54
- await expect(authCoreService.initialize(mockFirebaseAuth)).rejects.toThrow();
55
- });
56
-
57
- it('should throw error when no provider provided', async () => {
58
- await expect(authCoreService.initialize(null as any)).rejects.toThrow(
59
- 'Auth provider or Firebase Auth instance is required'
60
- );
61
- });
62
- });
63
-
64
- describe('isInitialized', () => {
65
- it('should return false when not initialized', () => {
66
- expect(authCoreService.isInitialized()).toBe(false);
67
- });
68
-
69
- it('should return true when initialized', async () => {
70
- await authCoreService.initialize(mockProvider);
71
- expect(authCoreService.isInitialized()).toBe(true);
72
- });
73
- });
74
-
75
- describe('signUp', () => {
76
- const mockUser: AuthUser = {
77
- uid: 'test-uid',
78
- email: 'test@example.com',
79
- displayName: 'Test User',
80
- isAnonymous: false,
81
- emailVerified: false,
82
- photoURL: null,
83
- };
84
-
85
- beforeEach(async () => {
86
- await authCoreService.initialize(mockProvider);
87
- });
88
-
89
- it('should sign up successfully with valid credentials', async () => {
90
- mockProvider.signUp.mockResolvedValue(mockUser);
91
-
92
- const result = await authCoreService.signUp({
93
- email: 'test@example.com',
94
- password: 'password123',
95
- displayName: 'Test User',
96
- });
97
-
98
- expect(result).toEqual(mockUser);
99
- expect(mockProvider.signUp).toHaveBeenCalledWith({
100
- email: 'test@example.com',
101
- password: 'password123',
102
- displayName: 'Test User',
103
- });
104
- });
105
-
106
- it('should sign up without display name', async () => {
107
- mockProvider.signUp.mockResolvedValue(mockUser);
108
-
109
- await authCoreService.signUp({
110
- email: 'test@example.com',
111
- password: 'password123',
112
- });
113
-
114
- expect(mockProvider.signUp).toHaveBeenCalledWith({
115
- email: 'test@example.com',
116
- password: 'password123',
117
- displayName: undefined,
118
- });
119
- });
120
-
121
- it('should throw error when not initialized', async () => {
122
- const uninitializedService = new AuthCoreService(DEFAULT_AUTH_CONFIG);
123
-
124
- await expect(uninitializedService.signUp({
125
- email: 'test@example.com',
126
- password: 'password123',
127
- })).rejects.toThrow('Auth service is not initialized');
128
- });
129
- });
130
-
131
- describe('signIn', () => {
132
- const mockUser: AuthUser = {
133
- uid: 'test-uid',
134
- email: 'test@example.com',
135
- displayName: 'Test User',
136
- isAnonymous: false,
137
- emailVerified: false,
138
- photoURL: null,
139
- };
140
-
141
- beforeEach(async () => {
142
- await authCoreService.initialize(mockProvider);
143
- });
144
-
145
- it('should sign in successfully with valid credentials', async () => {
146
- mockProvider.signIn.mockResolvedValue(mockUser);
147
-
148
- const result = await authCoreService.signIn({
149
- email: 'test@example.com',
150
- password: 'password123',
151
- });
152
-
153
- expect(result).toEqual(mockUser);
154
- expect(mockProvider.signIn).toHaveBeenCalledWith({
155
- email: 'test@example.com',
156
- password: 'password123',
157
- });
158
- });
159
-
160
- it('should throw error when not initialized', async () => {
161
- const uninitializedService = new AuthCoreService(DEFAULT_AUTH_CONFIG);
162
-
163
- await expect(uninitializedService.signIn({
164
- email: 'test@example.com',
165
- password: 'password123',
166
- })).rejects.toThrow('Auth service is not initialized');
167
- });
168
- });
169
-
170
- describe('signOut', () => {
171
- beforeEach(async () => {
172
- await authCoreService.initialize(mockProvider);
173
- });
174
-
175
- it('should sign out successfully', async () => {
176
- await authCoreService.signOut();
177
- expect(mockProvider.signOut).toHaveBeenCalled();
178
- });
179
-
180
- it('should handle sign out when not initialized', async () => {
181
- const uninitializedService = new AuthCoreService(DEFAULT_AUTH_CONFIG);
182
-
183
- await expect(uninitializedService.signOut()).resolves.not.toThrow();
184
- });
185
- });
186
-
187
- describe('getCurrentUser', () => {
188
- const mockUser: AuthUser = {
189
- uid: 'test-uid',
190
- email: 'test@example.com',
191
- displayName: 'Test User',
192
- isAnonymous: false,
193
- emailVerified: false,
194
- photoURL: null,
195
- };
196
-
197
- it('should return null when not initialized', () => {
198
- const result = authCoreService.getCurrentUser();
199
- expect(result).toBeNull();
200
- });
201
-
202
- it('should return current user when initialized', async () => {
203
- mockProvider.getCurrentUser.mockReturnValue(mockUser);
204
- await authCoreService.initialize(mockProvider);
205
-
206
- const result = authCoreService.getCurrentUser();
207
- expect(result).toEqual(mockUser);
208
- });
209
-
210
- it('should return null when no current user', async () => {
211
- mockProvider.getCurrentUser.mockReturnValue(null);
212
- await authCoreService.initialize(mockProvider);
213
-
214
- const result = authCoreService.getCurrentUser();
215
- expect(result).toBeNull();
216
- });
217
- });
218
-
219
- describe('onAuthStateChange', () => {
220
- it('should return cleanup function when not initialized', () => {
221
- const callback = jest.fn();
222
- const cleanup = authCoreService.onAuthStateChange(callback);
223
-
224
- expect(callback).toHaveBeenCalledWith(null);
225
- expect(typeof cleanup).toBe('function');
226
- });
227
-
228
- it('should subscribe to auth state changes when initialized', async () => {
229
- const callback = jest.fn();
230
- const mockCleanup = jest.fn();
231
- mockProvider.onAuthStateChange.mockReturnValue(mockCleanup);
232
-
233
- await authCoreService.initialize(mockProvider);
234
- const cleanup = authCoreService.onAuthStateChange(callback);
235
-
236
- expect(mockProvider.onAuthStateChange).toHaveBeenCalledWith(callback);
237
- expect(cleanup).toBe(mockCleanup);
238
- });
239
- });
240
-
241
- describe('getConfig', () => {
242
- it('should return the current config', () => {
243
- const config = authCoreService.getConfig();
244
- expect(config).toEqual(DEFAULT_AUTH_CONFIG);
245
- });
246
- });
247
- });