@umituz/react-native-auth 1.12.0 → 2.0.1
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.
- package/README.md +0 -0
- package/lib/__tests__/services/AuthCoreService.test.d.ts +4 -0
- package/lib/__tests__/services/AuthCoreService.test.js +198 -0
- package/lib/__tests__/services/AuthPackage.test.d.ts +4 -0
- package/lib/__tests__/services/AuthPackage.test.js +177 -0
- package/lib/__tests__/services/GuestModeService.test.d.ts +4 -0
- package/lib/__tests__/services/GuestModeService.test.js +141 -0
- package/lib/__tests__/utils/AuthValidation.test.d.ts +4 -0
- package/lib/__tests__/utils/AuthValidation.test.js +222 -0
- package/lib/application/ports/IAuthProvider.d.ts +42 -0
- package/lib/application/ports/IAuthProvider.js +5 -0
- package/lib/application/ports/IAuthService.d.ts +48 -0
- package/lib/application/ports/IAuthService.js +5 -0
- package/lib/domain/entities/AuthUser.d.ts +12 -0
- package/lib/domain/entities/AuthUser.js +5 -0
- package/lib/domain/errors/AuthError.d.ts +36 -0
- package/lib/domain/errors/AuthError.js +76 -0
- package/lib/domain/value-objects/AuthConfig.d.ts +16 -0
- package/lib/domain/value-objects/AuthConfig.js +14 -0
- package/lib/index.d.ts +45 -0
- package/lib/index.js +59 -0
- package/lib/infrastructure/adapters/StorageProviderAdapter.d.ts +16 -0
- package/lib/infrastructure/adapters/StorageProviderAdapter.js +72 -0
- package/lib/infrastructure/adapters/UIProviderAdapter.d.ts +18 -0
- package/lib/infrastructure/adapters/UIProviderAdapter.js +28 -0
- package/lib/infrastructure/providers/FirebaseAuthProvider.d.ts +19 -0
- package/lib/infrastructure/providers/FirebaseAuthProvider.js +94 -0
- package/lib/infrastructure/services/AuthCoreService.d.ts +22 -0
- package/lib/infrastructure/services/AuthCoreService.js +102 -0
- package/lib/infrastructure/services/AuthEventService.d.ts +28 -0
- package/lib/infrastructure/services/AuthEventService.js +88 -0
- package/lib/infrastructure/services/AuthPackage.d.ts +62 -0
- package/lib/infrastructure/services/AuthPackage.js +91 -0
- package/lib/infrastructure/services/AuthService.d.ts +42 -0
- package/lib/infrastructure/services/AuthService.js +123 -0
- package/lib/infrastructure/services/GuestModeService.d.ts +23 -0
- package/lib/infrastructure/services/GuestModeService.js +69 -0
- package/lib/infrastructure/storage/GuestModeStorage.d.ts +16 -0
- package/lib/infrastructure/storage/GuestModeStorage.js +73 -0
- package/lib/infrastructure/utils/AuthErrorMapper.d.ts +8 -0
- package/lib/infrastructure/utils/AuthErrorMapper.js +51 -0
- package/lib/infrastructure/utils/AuthEventEmitter.d.ts +12 -0
- package/lib/infrastructure/utils/AuthEventEmitter.js +25 -0
- package/lib/infrastructure/utils/AuthValidation.d.ts +49 -0
- package/lib/infrastructure/utils/AuthValidation.js +133 -0
- package/lib/infrastructure/utils/UserMapper.d.ts +15 -0
- package/lib/infrastructure/utils/UserMapper.js +16 -0
- package/lib/presentation/components/AuthContainer.d.ts +10 -0
- package/lib/presentation/components/AuthContainer.js +27 -0
- package/lib/presentation/components/AuthDivider.d.ts +6 -0
- package/lib/presentation/components/AuthDivider.js +36 -0
- package/lib/presentation/components/AuthErrorDisplay.d.ts +10 -0
- package/lib/presentation/components/AuthErrorDisplay.js +24 -0
- package/lib/presentation/components/AuthFormCard.d.ts +10 -0
- package/lib/presentation/components/AuthFormCard.js +19 -0
- package/lib/presentation/components/AuthGradientBackground.d.ts +6 -0
- package/lib/presentation/components/AuthGradientBackground.js +8 -0
- package/lib/presentation/components/AuthHeader.d.ts +11 -0
- package/lib/presentation/components/AuthHeader.js +38 -0
- package/lib/presentation/components/AuthLegalLinks.d.ts +28 -0
- package/lib/presentation/components/AuthLegalLinks.js +54 -0
- package/lib/presentation/components/AuthLink.d.ts +13 -0
- package/lib/presentation/components/AuthLink.js +27 -0
- package/lib/presentation/components/LoginForm.d.ts +10 -0
- package/lib/presentation/components/LoginForm.js +27 -0
- package/lib/presentation/components/PasswordMatchIndicator.d.ts +9 -0
- package/lib/presentation/components/PasswordMatchIndicator.js +30 -0
- package/lib/presentation/components/PasswordStrengthIndicator.d.ts +11 -0
- package/lib/presentation/components/PasswordStrengthIndicator.js +60 -0
- package/lib/presentation/components/RegisterForm.d.ts +14 -0
- package/lib/presentation/components/RegisterForm.js +30 -0
- package/lib/presentation/hooks/useAuth.d.ts +44 -0
- package/lib/presentation/hooks/useAuth.js +38 -0
- package/lib/presentation/hooks/useAuthActions.d.ts +15 -0
- package/lib/presentation/hooks/useAuthActions.js +162 -0
- package/lib/presentation/hooks/useAuthState.d.ts +19 -0
- package/lib/presentation/hooks/useAuthState.js +79 -0
- package/lib/presentation/hooks/useLoginForm.d.ts +21 -0
- package/lib/presentation/hooks/useLoginForm.js +131 -0
- package/lib/presentation/hooks/useRegisterForm.d.ts +31 -0
- package/lib/presentation/hooks/useRegisterForm.js +136 -0
- package/lib/presentation/navigation/AuthNavigator.d.ts +28 -0
- package/lib/presentation/navigation/AuthNavigator.js +37 -0
- package/lib/presentation/screens/LoginScreen.d.ts +6 -0
- package/lib/presentation/screens/LoginScreen.js +15 -0
- package/lib/presentation/screens/RegisterScreen.d.ts +12 -0
- package/lib/presentation/screens/RegisterScreen.js +15 -0
- package/lib/presentation/utils/getAuthErrorMessage.d.ts +8 -0
- package/lib/presentation/utils/getAuthErrorMessage.js +69 -0
- package/package.json +12 -4
- package/src/__tests__/services/AuthCoreService.test.ts +247 -0
- package/src/__tests__/services/AuthPackage.test.ts +226 -0
- package/src/__tests__/services/GuestModeService.test.ts +194 -0
- package/src/__tests__/utils/AuthValidation.test.ts +270 -0
- package/src/application/ports/IAuthProvider.ts +0 -0
- package/src/application/ports/IAuthService.ts +0 -0
- package/src/domain/entities/AuthUser.ts +0 -0
- package/src/domain/errors/AuthError.ts +0 -0
- package/src/domain/value-objects/AuthConfig.ts +0 -0
- package/src/index.ts +0 -0
- package/src/infrastructure/adapters/StorageProviderAdapter.ts +73 -0
- package/src/infrastructure/adapters/UIProviderAdapter.ts +39 -0
- package/src/infrastructure/providers/FirebaseAuthProvider.ts +10 -2
- package/src/infrastructure/services/AuthCoreService.ts +138 -0
- package/src/infrastructure/services/AuthEventService.ts +115 -0
- package/src/infrastructure/services/AuthPackage.ts +148 -0
- package/src/infrastructure/services/AuthService.ts +62 -128
- package/src/infrastructure/services/GuestModeService.ts +86 -0
- package/src/infrastructure/storage/GuestModeStorage.ts +40 -14
- package/src/infrastructure/utils/AuthErrorMapper.ts +7 -3
- package/src/infrastructure/utils/AuthEventEmitter.ts +0 -0
- package/src/infrastructure/utils/AuthValidation.ts +47 -17
- package/src/infrastructure/utils/UserMapper.ts +0 -0
- package/src/presentation/components/AuthContainer.tsx +0 -0
- package/src/presentation/components/AuthDivider.tsx +0 -0
- package/src/presentation/components/AuthErrorDisplay.tsx +0 -0
- package/src/presentation/components/AuthFormCard.tsx +0 -0
- package/src/presentation/components/AuthGradientBackground.tsx +0 -0
- package/src/presentation/components/AuthHeader.tsx +0 -0
- package/src/presentation/components/AuthLegalLinks.tsx +0 -0
- package/src/presentation/components/AuthLink.tsx +0 -0
- package/src/presentation/components/LoginForm.tsx +0 -0
- package/src/presentation/components/PasswordMatchIndicator.tsx +2 -2
- package/src/presentation/components/PasswordStrengthIndicator.tsx +0 -0
- package/src/presentation/components/RegisterForm.tsx +0 -0
- package/src/presentation/hooks/useAuth.ts +0 -0
- package/src/presentation/hooks/useAuthActions.ts +8 -11
- package/src/presentation/hooks/useAuthState.ts +10 -0
- package/src/presentation/hooks/useLoginForm.ts +0 -0
- package/src/presentation/hooks/useRegisterForm.ts +16 -17
- package/src/presentation/navigation/AuthNavigator.tsx +2 -2
- package/src/presentation/screens/LoginScreen.tsx +3 -6
- package/src/presentation/screens/RegisterScreen.tsx +3 -6
- package/src/presentation/utils/getAuthErrorMessage.ts +0 -0
- package/src/types/external.d.ts +68 -0
- package/LICENSE +0 -22
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useNavigation } from "@react-navigation/native";
|
|
3
|
+
import { useLocalization } from "@umituz/react-native-localization";
|
|
4
|
+
import { AuthContainer } from "../components/AuthContainer";
|
|
5
|
+
import { AuthHeader } from "../components/AuthHeader";
|
|
6
|
+
import { AuthFormCard } from "../components/AuthFormCard";
|
|
7
|
+
import { RegisterForm } from "../components/RegisterForm";
|
|
8
|
+
export const RegisterScreen = ({ termsUrl, privacyUrl, onTermsPress, onPrivacyPress, }) => {
|
|
9
|
+
const { t } = useLocalization();
|
|
10
|
+
const navigation = useNavigation();
|
|
11
|
+
const handleNavigateToLogin = () => {
|
|
12
|
+
navigation.navigate("Login");
|
|
13
|
+
};
|
|
14
|
+
return (_jsxs(AuthContainer, { children: [_jsx(AuthHeader, { title: t("auth.createAccount") }), _jsx(AuthFormCard, { children: _jsx(RegisterForm, { onNavigateToLogin: handleNavigateToLogin, termsUrl: termsUrl, privacyUrl: privacyUrl, onTermsPress: onTermsPress, onPrivacyPress: onPrivacyPress }) })] }));
|
|
15
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get localized error message from AuthError
|
|
3
|
+
* Maps error codes to localization keys
|
|
4
|
+
*/
|
|
5
|
+
import { AuthError } from "../../domain/errors/AuthError";
|
|
6
|
+
/**
|
|
7
|
+
* Map AuthError code to localization key
|
|
8
|
+
*/
|
|
9
|
+
export function getAuthErrorLocalizationKey(error) {
|
|
10
|
+
const code = error instanceof AuthError ? error.code : undefined;
|
|
11
|
+
// Map error codes to localization keys
|
|
12
|
+
const errorCodeMap = {
|
|
13
|
+
AUTH_INVALID_EMAIL: "auth.errors.invalidEmail",
|
|
14
|
+
AUTH_WEAK_PASSWORD: "auth.errors.weakPassword",
|
|
15
|
+
AUTH_USER_NOT_FOUND: "auth.errors.userNotFound",
|
|
16
|
+
AUTH_WRONG_PASSWORD: "auth.errors.wrongPassword",
|
|
17
|
+
AUTH_EMAIL_ALREADY_IN_USE: "auth.errors.emailAlreadyInUse",
|
|
18
|
+
AUTH_NETWORK_ERROR: "auth.errors.networkError",
|
|
19
|
+
AUTH_CONFIG_ERROR: "auth.errors.configurationError",
|
|
20
|
+
AUTH_TOO_MANY_REQUESTS: "auth.errors.tooManyRequests",
|
|
21
|
+
AUTH_USER_DISABLED: "auth.errors.userDisabled",
|
|
22
|
+
AUTH_NOT_INITIALIZED: "auth.errors.authNotInitialized",
|
|
23
|
+
};
|
|
24
|
+
// Check error name for specific error types
|
|
25
|
+
if (error.name === "AuthInvalidEmailError") {
|
|
26
|
+
return "auth.errors.invalidEmail";
|
|
27
|
+
}
|
|
28
|
+
if (error.name === "AuthWeakPasswordError") {
|
|
29
|
+
return "auth.errors.weakPassword";
|
|
30
|
+
}
|
|
31
|
+
if (error.name === "AuthUserNotFoundError") {
|
|
32
|
+
return "auth.errors.userNotFound";
|
|
33
|
+
}
|
|
34
|
+
if (error.name === "AuthWrongPasswordError") {
|
|
35
|
+
return "auth.errors.wrongPassword";
|
|
36
|
+
}
|
|
37
|
+
if (error.name === "AuthEmailAlreadyInUseError") {
|
|
38
|
+
return "auth.errors.emailAlreadyInUse";
|
|
39
|
+
}
|
|
40
|
+
if (error.name === "AuthNetworkError") {
|
|
41
|
+
return "auth.errors.networkError";
|
|
42
|
+
}
|
|
43
|
+
if (error.name === "AuthConfigurationError") {
|
|
44
|
+
return "auth.errors.configurationError";
|
|
45
|
+
}
|
|
46
|
+
if (error.name === "AuthInitializationError") {
|
|
47
|
+
return "auth.errors.authNotInitialized";
|
|
48
|
+
}
|
|
49
|
+
// Use code if available
|
|
50
|
+
if (code && errorCodeMap[code]) {
|
|
51
|
+
return errorCodeMap[code];
|
|
52
|
+
}
|
|
53
|
+
// Check error message for specific patterns
|
|
54
|
+
const message = error.message.toLowerCase();
|
|
55
|
+
if (message.includes("too many requests")) {
|
|
56
|
+
return "auth.errors.tooManyRequests";
|
|
57
|
+
}
|
|
58
|
+
if (message.includes("user account has been disabled") || message.includes("user disabled")) {
|
|
59
|
+
return "auth.errors.userDisabled";
|
|
60
|
+
}
|
|
61
|
+
if (message.includes("not properly configured") || message.includes("configuration")) {
|
|
62
|
+
return "auth.errors.configurationError";
|
|
63
|
+
}
|
|
64
|
+
if (message.includes("not enabled") || message.includes("operation not allowed")) {
|
|
65
|
+
return "auth.errors.operationNotAllowed";
|
|
66
|
+
}
|
|
67
|
+
// Default to unknown error
|
|
68
|
+
return "auth.errors.unknownError";
|
|
69
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-auth",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design
|
|
5
|
-
"main": "./
|
|
6
|
-
"types": "./
|
|
3
|
+
"version": "2.0.1",
|
|
4
|
+
"description": "Authentication service for React Native apps - Secure, type-safe, and production-ready. Provider-agnostic design with dependency injection, configurable validation, and comprehensive error handling.",
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
|
+
"types": "./lib/index.d.ts",
|
|
7
7
|
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
8
9
|
"typecheck": "tsc --noEmit",
|
|
9
10
|
"lint": "tsc --noEmit",
|
|
11
|
+
"prepublishOnly": "npm run build",
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"test:watch": "jest --watch",
|
|
14
|
+
"test:coverage": "jest --coverage",
|
|
10
15
|
"version:patch": "npm version patch -m 'chore: release v%s'",
|
|
11
16
|
"version:minor": "npm version minor -m 'chore: release v%s'",
|
|
12
17
|
"version:major": "npm version major -m 'chore: release v%s'"
|
|
@@ -49,6 +54,8 @@
|
|
|
49
54
|
"firebase": "^11.10.0",
|
|
50
55
|
"@types/react": "^18.2.45",
|
|
51
56
|
"@types/react-native": "^0.73.0",
|
|
57
|
+
"@types/jest": "^29.5.0",
|
|
58
|
+
"jest": "^29.5.0",
|
|
52
59
|
"react": "^18.2.0",
|
|
53
60
|
"react-native": "^0.74.0",
|
|
54
61
|
"typescript": "^5.3.3"
|
|
@@ -57,6 +64,7 @@
|
|
|
57
64
|
"access": "public"
|
|
58
65
|
},
|
|
59
66
|
"files": [
|
|
67
|
+
"lib",
|
|
60
68
|
"src",
|
|
61
69
|
"README.md",
|
|
62
70
|
"LICENSE"
|
|
@@ -0,0 +1,247 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AuthPackage Tests
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
AuthPackage,
|
|
7
|
+
initializeAuthPackage,
|
|
8
|
+
getAuthPackage,
|
|
9
|
+
resetAuthPackage,
|
|
10
|
+
DEFAULT_AUTH_PACKAGE_CONFIG
|
|
11
|
+
} from '../../../src/infrastructure/services/AuthPackage';
|
|
12
|
+
import type { IStorageProvider, IUIProvider, IValidationProvider } from '../../../src/infrastructure/services/AuthPackage';
|
|
13
|
+
|
|
14
|
+
describe('AuthPackage', () => {
|
|
15
|
+
let mockStorageProvider: jest.Mocked<IStorageProvider>;
|
|
16
|
+
let mockUIProvider: jest.Mocked<IUIProvider>;
|
|
17
|
+
let mockValidationProvider: jest.Mocked<IValidationProvider>;
|
|
18
|
+
|
|
19
|
+
beforeEach(() => {
|
|
20
|
+
mockStorageProvider = {
|
|
21
|
+
get: jest.fn(),
|
|
22
|
+
set: jest.fn(),
|
|
23
|
+
remove: jest.fn(),
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
mockUIProvider = {
|
|
27
|
+
getTheme: jest.fn(),
|
|
28
|
+
getLocalization: jest.fn(),
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
mockValidationProvider = {
|
|
32
|
+
validateEmail: jest.fn(),
|
|
33
|
+
validatePassword: jest.fn(),
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// Reset package state
|
|
37
|
+
resetAuthPackage();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe('constructor', () => {
|
|
41
|
+
it('should initialize with default config', () => {
|
|
42
|
+
const authPackage = new AuthPackage();
|
|
43
|
+
const config = authPackage.getConfig();
|
|
44
|
+
|
|
45
|
+
expect(config).toEqual(DEFAULT_AUTH_PACKAGE_CONFIG);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('should merge custom config with defaults', () => {
|
|
49
|
+
const customConfig = {
|
|
50
|
+
storageKeys: {
|
|
51
|
+
guestMode: '@custom_guest_mode',
|
|
52
|
+
showRegister: 'custom_show_register',
|
|
53
|
+
},
|
|
54
|
+
features: {
|
|
55
|
+
guestMode: false,
|
|
56
|
+
registration: false,
|
|
57
|
+
passwordStrength: false,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const authPackage = new AuthPackage(customConfig);
|
|
62
|
+
const config = authPackage.getConfig();
|
|
63
|
+
|
|
64
|
+
expect(config.storageKeys).toEqual(customConfig.storageKeys);
|
|
65
|
+
expect(config.features).toEqual(customConfig.features);
|
|
66
|
+
expect(config.validation).toEqual(DEFAULT_AUTH_PACKAGE_CONFIG.validation);
|
|
67
|
+
expect(config.ui).toEqual(DEFAULT_AUTH_PACKAGE_CONFIG.ui);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should deeply merge nested config', () => {
|
|
71
|
+
const customConfig = {
|
|
72
|
+
validation: {
|
|
73
|
+
passwordConfig: {
|
|
74
|
+
minLength: 12,
|
|
75
|
+
requireUppercase: true,
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const authPackage = new AuthPackage(customConfig);
|
|
81
|
+
const config = authPackage.getConfig();
|
|
82
|
+
|
|
83
|
+
expect(config.validation.passwordConfig.minLength).toBe(12);
|
|
84
|
+
expect(config.validation.passwordConfig.requireUppercase).toBe(true);
|
|
85
|
+
expect(config.validation.passwordConfig.requireLowercase).toBe(true); // Should keep default
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
describe('provider setters and getters', () => {
|
|
90
|
+
let authPackage: AuthPackage;
|
|
91
|
+
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
authPackage = new AuthPackage();
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should set and get storage provider', () => {
|
|
97
|
+
authPackage.setStorageProvider(mockStorageProvider);
|
|
98
|
+
expect(authPackage.getStorageProvider()).toBe(mockStorageProvider);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should set and get UI provider', () => {
|
|
102
|
+
authPackage.setUIProvider(mockUIProvider);
|
|
103
|
+
expect(authPackage.getUIProvider()).toBe(mockUIProvider);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should set and get validation provider', () => {
|
|
107
|
+
authPackage.setValidationProvider(mockValidationProvider);
|
|
108
|
+
expect(authPackage.getValidationProvider()).toBe(mockValidationProvider);
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
describe('isFeatureEnabled', () => {
|
|
113
|
+
let authPackage: AuthPackage;
|
|
114
|
+
|
|
115
|
+
beforeEach(() => {
|
|
116
|
+
authPackage = new AuthPackage();
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should return feature status from config', () => {
|
|
120
|
+
expect(authPackage.isFeatureEnabled('guestMode')).toBe(true);
|
|
121
|
+
expect(authPackage.isFeatureEnabled('registration')).toBe(true);
|
|
122
|
+
expect(authPackage.isFeatureEnabled('passwordStrength')).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('should return custom feature status', () => {
|
|
126
|
+
const customConfig = {
|
|
127
|
+
features: {
|
|
128
|
+
guestMode: false,
|
|
129
|
+
registration: false,
|
|
130
|
+
passwordStrength: false,
|
|
131
|
+
},
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const customPackage = new AuthPackage(customConfig);
|
|
135
|
+
|
|
136
|
+
expect(customPackage.isFeatureEnabled('guestMode')).toBe(false);
|
|
137
|
+
expect(customPackage.isFeatureEnabled('registration')).toBe(false);
|
|
138
|
+
expect(customPackage.isFeatureEnabled('passwordStrength')).toBe(false);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe('global package instance', () => {
|
|
143
|
+
it('should initialize package globally', () => {
|
|
144
|
+
const customConfig = {
|
|
145
|
+
storageKeys: {
|
|
146
|
+
guestMode: '@global_guest_mode',
|
|
147
|
+
},
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const packageInstance = initializeAuthPackage(customConfig);
|
|
151
|
+
expect(packageInstance.getConfig().storageKeys.guestMode).toBe('@global_guest_mode');
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should return existing package instance', () => {
|
|
155
|
+
const firstInstance = initializeAuthPackage();
|
|
156
|
+
const secondInstance = getAuthPackage();
|
|
157
|
+
|
|
158
|
+
expect(firstInstance).toBe(secondInstance);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should return null when no package initialized', () => {
|
|
162
|
+
resetAuthPackage();
|
|
163
|
+
const packageInstance = getAuthPackage();
|
|
164
|
+
expect(packageInstance).toBeNull();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it('should not reinitialize when already initialized', () => {
|
|
168
|
+
const firstInstance = initializeAuthPackage();
|
|
169
|
+
const secondInstance = initializeAuthPackage({
|
|
170
|
+
storageKeys: { guestMode: '@different' },
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
expect(firstInstance).toBe(secondInstance);
|
|
174
|
+
expect(secondInstance.getConfig().storageKeys.guestMode).toBe('@auth_guest_mode'); // Should keep original
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it('should reset package instance', () => {
|
|
178
|
+
initializeAuthPackage();
|
|
179
|
+
expect(getAuthPackage()).not.toBeNull();
|
|
180
|
+
|
|
181
|
+
resetAuthPackage();
|
|
182
|
+
expect(getAuthPackage()).toBeNull();
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
describe('provider integration', () => {
|
|
187
|
+
let authPackage: AuthPackage;
|
|
188
|
+
|
|
189
|
+
beforeEach(() => {
|
|
190
|
+
authPackage = new AuthPackage();
|
|
191
|
+
authPackage.setStorageProvider(mockStorageProvider);
|
|
192
|
+
authPackage.setUIProvider(mockUIProvider);
|
|
193
|
+
authPackage.setValidationProvider(mockValidationProvider);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it('should integrate all providers', () => {
|
|
197
|
+
expect(authPackage.getStorageProvider()).toBe(mockStorageProvider);
|
|
198
|
+
expect(authPackage.getUIProvider()).toBe(mockUIProvider);
|
|
199
|
+
expect(authPackage.getValidationProvider()).toBe(mockValidationProvider);
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
describe('config validation', () => {
|
|
204
|
+
it('should handle empty config', () => {
|
|
205
|
+
const authPackage = new AuthPackage({});
|
|
206
|
+
const config = authPackage.getConfig();
|
|
207
|
+
|
|
208
|
+
expect(config).toEqual(DEFAULT_AUTH_PACKAGE_CONFIG);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it('should handle partial config', () => {
|
|
212
|
+
const partialConfig = {
|
|
213
|
+
features: {
|
|
214
|
+
guestMode: false,
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
const authPackage = new AuthPackage(partialConfig);
|
|
219
|
+
const config = authPackage.getConfig();
|
|
220
|
+
|
|
221
|
+
expect(config.features.guestMode).toBe(false);
|
|
222
|
+
expect(config.features.registration).toBe(true); // Should keep default
|
|
223
|
+
expect(config.features.passwordStrength).toBe(true); // Should keep default
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
});
|