@@ -24,6 +24,102 @@ const createTrackEventMock = () =>
2424 mock ( ( _event : AnalyticsEvent , _properties ?: Record < string , any > ) => { } )
2525
2626describe ( 'login-flow analytics instrumentation' , ( ) => {
27+ test ( 'plain and TUI callers carry one explicit CLI UUID without changing authentication' , async ( ) => {
28+ const cliFlowId = 'a0000000-0000-4000-8000-000000000001'
29+ for ( const via of [ 'modal' , 'plain_command' ] as const ) {
30+ const data = {
31+ loginUrl : 'https://freebuff.test/login?auth_code=opaque-auth-code' ,
32+ fingerprintHash : 'private-hash' ,
33+ expiresAt : '2030-01-01T00:00:00Z' ,
34+ }
35+ const loginCode = mock ( async ( ) => ( {
36+ ok : true as const ,
37+ status : 200 ,
38+ data,
39+ } ) )
40+ const result = await generateLoginUrl (
41+ {
42+ logger : createMockLogger ( ) ,
43+ apiClient : createMockApiClient ( { loginCode } ) ,
44+ } ,
45+ {
46+ baseUrl : 'https://freebuff.test' ,
47+ fingerprintId : 'private-fingerprint' ,
48+ via,
49+ cliFlowId,
50+ } ,
51+ )
52+ const browserUrl = new URL ( result . loginUrl )
53+ expect ( browserUrl . searchParams . get ( 'cli_flow' ) ) . toBe ( cliFlowId )
54+ expect ( browserUrl . searchParams . getAll ( 'cli_flow' ) ) . toEqual ( [ cliFlowId ] )
55+ expect ( browserUrl . searchParams . get ( 'auth_code' ) ) . toBe ( 'opaque-auth-code' )
56+ expect ( result . fingerprintHash ) . toBe ( data . fingerprintHash )
57+ expect ( result . expiresAt ) . toBe ( data . expiresAt )
58+ expect ( loginCode ) . toHaveBeenCalledWith ( {
59+ fingerprintId : 'private-fingerprint' ,
60+ } )
61+ expect ( data . loginUrl ) . not . toContain ( 'cli_flow' )
62+ }
63+ } )
64+
65+ test ( 'auth codes and invalid correlation values never imply CLI origin' , async ( ) => {
66+ const data = {
67+ loginUrl :
68+ 'https://freebuff.test/login?auth_code=opaque-auth-code&native_apple=1' ,
69+ fingerprintHash : 'private-hash' ,
70+ expiresAt : '2030-01-01T00:00:00Z' ,
71+ }
72+ const loginCode = mock ( async ( ) => ( {
73+ ok : true as const ,
74+ status : 200 ,
75+ data,
76+ } ) )
77+ const apiClient = createMockApiClient ( { loginCode } )
78+ for ( const cliFlowId of [
79+ undefined ,
80+ 'private-fingerprint' ,
81+ 'opaque-auth-code' ,
82+ ] ) {
83+ const result = await generateLoginUrl (
84+ { logger : createMockLogger ( ) , apiClient } ,
85+ {
86+ baseUrl : 'https://freebuff.test' ,
87+ fingerprintId : 'private-fingerprint' ,
88+ cliFlowId,
89+ } ,
90+ )
91+ expect ( result ) . toEqual ( data )
92+ }
93+ } )
94+
95+ test ( 'optional correlation never rejects an unusable or non-web URL' , async ( ) => {
96+ for ( const loginUrl of [ 'not a URL' , 'freebuff://login?auth_code=opaque' ] ) {
97+ const data = {
98+ loginUrl,
99+ fingerprintHash : 'hash' ,
100+ expiresAt : '2030-01-01T00:00:00Z' ,
101+ }
102+ const loginCode = mock ( async ( ) => ( {
103+ ok : true as const ,
104+ status : 200 ,
105+ data,
106+ } ) )
107+ expect (
108+ await generateLoginUrl (
109+ {
110+ logger : createMockLogger ( ) ,
111+ apiClient : createMockApiClient ( { loginCode } ) ,
112+ } ,
113+ {
114+ baseUrl : 'https://freebuff.test' ,
115+ fingerprintId : 'fingerprint' ,
116+ cliFlowId : 'a0000000-0000-4000-8000-000000000001' ,
117+ } ,
118+ ) ,
119+ ) . toEqual ( data )
120+ }
121+ } )
122+
27123 test ( 'generateLoginUrl emits LOGIN_STARTED on entry and nothing else on success' , async ( ) => {
28124 const trackEvent = createTrackEventMock ( )
29125 const loginCode = mock ( async ( ) => ( {
@@ -84,13 +180,20 @@ describe('login-flow analytics instrumentation', () => {
84180 test ( 'pollLoginStatus emits LOGIN_TIMEOUT with attempts + via' , async ( ) => {
85181 const trackEvent = createTrackEventMock ( )
86182 const loginStatus = mock (
87- async ( ) => ( { ok : false , status : 401 } ) as ApiResponse < { user ?: unknown } > ,
183+ async ( ) =>
184+ ( { ok : false , status : 401 } ) as ApiResponse < { user ?: unknown } > ,
88185 )
89186 const apiClient = createMockApiClient ( { loginStatus } )
90187 const clock = createClock ( )
91188
92189 const result = await pollLoginStatus (
93- { sleep : clock . sleep , logger : createMockLogger ( ) , now : clock . now , apiClient, trackEvent } ,
190+ {
191+ sleep : clock . sleep ,
192+ logger : createMockLogger ( ) ,
193+ now : clock . now ,
194+ apiClient,
195+ trackEvent,
196+ } ,
94197 {
95198 baseUrl : 'https://cli.test' ,
96199 fingerprintId : 'finger-1' ,
@@ -115,7 +218,13 @@ describe('login-flow analytics instrumentation', () => {
115218 const clock = createClock ( )
116219
117220 const result = await pollLoginStatus (
118- { sleep : clock . sleep , logger : createMockLogger ( ) , now : clock . now , apiClient, trackEvent } ,
221+ {
222+ sleep : clock . sleep ,
223+ logger : createMockLogger ( ) ,
224+ now : clock . now ,
225+ apiClient,
226+ trackEvent,
227+ } ,
119228 {
120229 baseUrl : 'https://cli.test' ,
121230 fingerprintId : 'finger-1' ,
0 commit comments