1+ import { vi , describe , it , expect , beforeEach } from 'vitest'
2+ // Import testing functions from vitest library
3+ import { PasskeyAuthService } from '../PasskeyAuthService'
4+ // Import the class we want to test
5+ import { AuthenticationResult , SetupResult } from '../types'
6+ // Import authentication and setup result types
7+ import { Capacitor } from '@capacitor/core'
8+
9+ // Replace real Capacitor with our mock to control its behavior
10+ vi . mock ( '@capacitor/core' , ( ) => ( {
11+ Capacitor : {
12+ isNativePlatform : vi . fn ( )
13+ }
14+ } ) )
15+
16+ // Get mocked Capacitor for type safety
17+ const mockCapacitor = vi . mocked ( Capacitor )
18+
19+ // Mock WebAuthn API for testing passkey functionality
20+ const mockCredentials = {
21+ get : vi . fn ( ) , // For authentication with existing passkey
22+ create : vi . fn ( ) // For creating new passkey
23+ }
24+
25+ describe ( 'PasskeyAuthService' , ( ) => {
26+ let passkeyAuth : PasskeyAuthService
27+ // Declare variable for the service instance we're testing
28+
29+ beforeEach ( ( ) => {
30+ // This function runs before each test for clean state
31+ passkeyAuth = new PasskeyAuthService ( )
32+ // Create new service instance
33+ vi . clearAllMocks ( )
34+ // Reset all mocks to initial state
35+
36+ // Set up global browser objects for each test
37+ Object . defineProperty ( global , 'navigator' , {
38+ value : { credentials : mockCredentials } ,
39+ writable : true
40+ } )
41+ // Mock navigator.credentials (WebAuthn API)
42+
43+ Object . defineProperty ( global , 'window' , {
44+ value : {
45+ PublicKeyCredential : function ( ) { } , // Mock constructor for WebAuthn support
46+ location : { hostname : 'messenger.adamant.im' } , // Mock site domain
47+ crypto : {
48+ getRandomValues : vi . fn ( ( ) => new Uint8Array ( 32 ) ) // Mock random number generator
49+ }
50+ } ,
51+ writable : true
52+ } )
53+ } )
54+
55+ describe ( 'User tries to authenticate with passkey in web browser' , ( ) => {
56+ beforeEach ( ( ) => {
57+ // For these tests simulate web browser (not mobile app)
58+ mockCapacitor . isNativePlatform . mockReturnValue ( false )
59+ } )
60+
61+ it ( 'should successfully authenticate when user confirms passkey' , async ( ) => {
62+ // Simulate successful passkey confirmation by user
63+ mockCredentials . get . mockResolvedValue ( { id : 'credential-id' , type : 'public-key' } )
64+
65+ // User clicks login button and confirms through passkey
66+ const result = await passkeyAuth . authorizeUser ( )
67+
68+ // Check that credentials.get was called with correct WebAuthn parameters
69+ expect ( mockCredentials . get ) . toHaveBeenCalledWith ( {
70+ publicKey : {
71+ challenge : expect . any ( Uint8Array ) ,
72+ rpId : 'messenger.adamant.im' ,
73+ userVerification : 'preferred' ,
74+ timeout : 30000
75+ }
76+ } )
77+ // Check that authentication was successful
78+ expect ( result ) . toBe ( AuthenticationResult . Success )
79+ } )
80+
81+ it ( 'should return cancel when user cancels passkey prompt with NotAllowedError' , async ( ) => {
82+ // Simulate user cancellation (clicked "Cancel" in system dialog)
83+ const cancelError = new Error ( 'User cancelled' )
84+ cancelError . name = 'NotAllowedError'
85+ mockCredentials . get . mockRejectedValue ( cancelError )
86+
87+ // User starts authentication but cancels it in system dialog
88+ const result = await passkeyAuth . authorizeUser ( )
89+
90+ // Expect "cancel" result, not error
91+ expect ( result ) . toBe ( AuthenticationResult . Cancel )
92+ } )
93+
94+ it ( 'should return cancel when user cancels passkey prompt with AbortError' , async ( ) => {
95+ // Simulate user cancellation with different error type
96+ const cancelError = new Error ( 'User aborted' )
97+ cancelError . name = 'AbortError'
98+ mockCredentials . get . mockRejectedValue ( cancelError )
99+
100+ // User starts authentication but cancels it
101+ const result = await passkeyAuth . authorizeUser ( )
102+
103+ // Should also return Cancel for AbortError
104+ expect ( result ) . toBe ( AuthenticationResult . Cancel )
105+ } )
106+
107+ it ( 'should return failed when error is not cancellation' , async ( ) => {
108+ // Simulate error that is NOT NotAllowedError or AbortError
109+ const authError = new Error ( 'Authentication failed' )
110+ authError . name = 'InvalidStateError'
111+ mockCredentials . get . mockRejectedValue ( authError )
112+
113+ // User tries to login but gets non-cancellation error
114+ const result = await passkeyAuth . authorizeUser ( )
115+
116+ // Should return Failed (not Cancel) because error is not cancellation type
117+ expect ( result ) . toBe ( AuthenticationResult . Failed )
118+ } )
119+
120+ it ( 'should return failed when browser does not support WebAuthn' , async ( ) => {
121+ // Mock old browser without WebAuthn API support
122+ Object . defineProperty ( global , 'navigator' , {
123+ value : { } , // Remove credentials from navigator
124+ writable : true
125+ } )
126+
127+ // User tries to use passkey in incompatible browser
128+ const result = await passkeyAuth . authorizeUser ( )
129+
130+ // Expect failure due to lack of support
131+ expect ( result ) . toBe ( AuthenticationResult . Failed )
132+ } )
133+ } )
134+
135+ describe ( 'User tries to authenticate with passkey in mobile app' , ( ) => {
136+ beforeEach ( ( ) => {
137+ // Simulate mobile application
138+ mockCapacitor . isNativePlatform . mockReturnValue ( true )
139+ } )
140+
141+ it ( 'should return failed as passkeys not supported in mobile app' , async ( ) => {
142+ // Passkeys are not yet implemented in mobile app
143+ const result = await passkeyAuth . authorizeUser ( )
144+
145+ // Expect failure as feature is only available in web version
146+ expect ( result ) . toBe ( AuthenticationResult . Failed )
147+ } )
148+ } )
149+
150+ describe ( 'User wants to setup passkey authentication' , ( ) => {
151+ beforeEach ( ( ) => {
152+ // Setup is only possible in browser
153+ mockCapacitor . isNativePlatform . mockReturnValue ( false )
154+ } )
155+
156+ it ( 'should successfully create passkey when user confirms' , async ( ) => {
157+ // Simulate successful passkey creation (user confirmed via biometrics/PIN)
158+ mockCredentials . create . mockResolvedValue ( {
159+ id : 'new-credential-id' ,
160+ type : 'public-key'
161+ } )
162+
163+ // User clicked "Setup passkey" and completed creation process
164+ const result = await passkeyAuth . setupPasskey ( )
165+
166+ // Setup should succeed
167+ expect ( result ) . toBe ( SetupResult . Success )
168+ } )
169+
170+ it ( 'should return cancel when user cancels passkey creation' , async ( ) => {
171+ // Simulate user cancelling passkey creation
172+ const cancelError = new Error ( 'User cancelled setup' )
173+ cancelError . name = 'AbortError'
174+ mockCredentials . create . mockRejectedValue ( cancelError )
175+
176+ // User starts setup but cancels it
177+ const result = await passkeyAuth . setupPasskey ( )
178+
179+ // Expect "cancel" result
180+ expect ( result ) . toBe ( SetupResult . Cancel )
181+ } )
182+
183+ it ( 'should return failed when passkey creation fails' , async ( ) => {
184+ // Simulate creation error (e.g., device issues)
185+ const creationError = new Error ( 'Creation failed' )
186+ mockCredentials . create . mockRejectedValue ( creationError )
187+
188+ // User tries to setup passkey but process fails with error
189+ const result = await passkeyAuth . setupPasskey ( )
190+
191+ // Expect failed setup
192+ expect ( result ) . toBe ( SetupResult . Failed )
193+ } )
194+
195+ it ( 'should return failed when trying to setup in mobile app' , async ( ) => {
196+ // Simulate setup attempt in mobile app
197+ mockCapacitor . isNativePlatform . mockReturnValue ( true )
198+
199+ // User tries to setup passkey in mobile app
200+ const result = await passkeyAuth . setupPasskey ( )
201+
202+ // Expect failure as feature is not supported in mobile apps
203+ expect ( result ) . toBe ( SetupResult . Failed )
204+ } )
205+ } )
206+ } )
0 commit comments