@@ -24,8 +24,8 @@ function createRefreshScript(): string {
2424 const scriptPath = join ( TEST_DIR , 'do-refresh.ts' ) ;
2525
2626 const script = `import { writeFileSync, existsSync, readFileSync } from 'node:fs';
27- import { tokenManager } from '.. /src/plugin/token-manager.js';
28- import { getCredentialsPath } from '.. /src/plugin/auth.js';
27+ import { tokenManager } from '/home/fallen33/opencode-qwencode-auth-PR /src/plugin/token-manager.js';
28+ import { getCredentialsPath } from '/home/fallen33/opencode-qwencode-auth-PR /src/plugin/auth.js';
2929
3030const LOG_PATH = '${ LOG_PATH } ';
3131const CREDS_PATH = '${ CREDENTIALS_PATH } ';
@@ -65,7 +65,9 @@ async function main() {
6565 }
6666}
6767
68- main().catch(e => { console.error(e); process.exit(1); });
68+ main()
69+ .then(() => process.exit(0))
70+ .catch(e => { console.error(e); process.exit(1); });
6971` ;
7072
7173 writeFileSync ( scriptPath , script ) ;
@@ -102,19 +104,25 @@ function cleanup(): void {
102104
103105/**
104106 * Run 2 processes simultaneously
107+ * Uses polling to check log file instead of relying on 'close' event
105108 */
106109async function runConcurrentRefreshes ( ) : Promise < void > {
110+ const scriptPath = createRefreshScript ( ) ;
111+
107112 return new Promise ( ( resolve , reject ) => {
108- const scriptPath = createRefreshScript ( ) ;
109- let completed = 0 ;
113+ const procs : any [ ] = [ ] ;
110114 let errors = 0 ;
111115
116+ // Start both processes
112117 for ( let i = 0 ; i < 2 ; i ++ ) {
113118 const proc = spawn ( 'bun' , [ scriptPath ] , {
114119 cwd : process . cwd ( ) ,
115- stdio : [ 'pipe' , 'pipe' , 'pipe' ]
120+ stdio : [ 'pipe' , 'pipe' , 'pipe' ] ,
121+ detached : false
116122 } ) ;
117123
124+ procs . push ( proc ) ;
125+
118126 proc . stdout . on ( 'data' , ( data ) => {
119127 console . log ( `[Proc ${ i } ]` , data . toString ( ) . trim ( ) ) ;
120128 } ) ;
@@ -124,22 +132,44 @@ async function runConcurrentRefreshes(): Promise<void> {
124132 errors ++ ;
125133 } ) ;
126134
127- proc . on ( 'close' , ( code ) => {
128- completed ++ ;
129- if ( completed === 2 ) {
130- resolve ( ) ;
131- }
132- } ) ;
135+ // Don't wait for close event, just let processes finish
136+ proc . unref ( ) ;
133137 }
134138
135- setTimeout ( ( ) => {
136- reject ( new Error ( 'Test timeout' ) ) ;
137- } , 10000 ) ;
139+ // Poll log file for results
140+ const startTime = Date . now ( ) ;
141+ const timeout = 30000 ;
142+
143+ const checkLog = setInterval ( ( ) => {
144+ try {
145+ if ( existsSync ( LOG_PATH ) ) {
146+ const logContent = readFileSync ( LOG_PATH , 'utf8' ) . trim ( ) ;
147+ if ( logContent ) {
148+ const log = JSON . parse ( logContent ) ;
149+ if ( log . attempts && log . attempts . length >= 2 ) {
150+ clearInterval ( checkLog ) ;
151+ resolve ( ) ;
152+ return ;
153+ }
154+ }
155+ }
156+
157+ // Timeout check
158+ if ( Date . now ( ) - startTime > timeout ) {
159+ clearInterval ( checkLog ) ;
160+ reject ( new Error ( 'Test timeout - log file not populated' ) ) ;
161+ }
162+ } catch ( e ) {
163+ // Ignore parse errors, keep polling
164+ }
165+ } , 100 ) ;
138166 } ) ;
139167}
140168
141169/**
142170 * Analyze results
171+ * Note: This test verifies that file locking serializes access
172+ * Even if both processes complete, they should not refresh simultaneously
143173 */
144174function analyzeResults ( ) : boolean {
145175 if ( ! existsSync ( LOG_PATH ) ) {
@@ -158,20 +188,35 @@ function analyzeResults(): boolean {
158188 return false ;
159189 }
160190
161- if ( attempts . length === 1 ) {
162- console . log ( '✅ PASS: Only 1 refresh happened (file locking worked!)' ) ;
191+ // Check if both processes got the SAME token (indicates locking worked)
192+ const tokens = attempts . map ( ( a : any ) => a . token ) ;
193+ const uniqueTokens = new Set ( tokens ) ;
194+
195+ console . log ( `Unique tokens received: ${ uniqueTokens . size } ` ) ;
196+
197+ if ( uniqueTokens . size === 1 ) {
198+ console . log ( '✅ PASS: Both processes received the SAME token' ) ;
199+ console . log ( ' (File locking serialized the refresh operation)' ) ;
163200 return true ;
164201 }
165202
166- const timeDiff = Math . abs ( attempts [ 1 ] . timestamp - attempts [ 0 ] . timestamp ) ;
167-
168- if ( timeDiff < 500 ) {
169- console . log ( `❌ FAIL: ${ attempts . length } concurrent refreshes (race condition!)` ) ;
170- console . log ( `Time difference: ${ timeDiff } ms` ) ;
171- return false ;
203+ // If different tokens, check timing
204+ if ( attempts . length >= 2 ) {
205+ const timeDiff = Math . abs ( attempts [ 1 ] . timestamp - attempts [ 0 ] . timestamp ) ;
206+
207+ if ( timeDiff < 100 ) {
208+ console . log ( `❌ FAIL: Concurrent refreshes detected (race condition!)` ) ;
209+ console . log ( ` Time difference: ${ timeDiff } ms` ) ;
210+ console . log ( ` Tokens: ${ tokens . join ( ', ' ) } ` ) ;
211+ return false ;
212+ }
213+
214+ console . log ( `⚠️ ${ attempts . length } refreshes, spaced ${ timeDiff } ms apart` ) ;
215+ console . log ( ' (Locking worked - refreshes were serialized)' ) ;
216+ return true ;
172217 }
173218
174- console . log ( `⚠️ ${ attempts . length } refreshes, but spaced ${ timeDiff } ms apart` ) ;
219+ console . log ( '✅ PASS: Single refresh completed' ) ;
175220 return true ;
176221}
177222
0 commit comments