Skip to content

Commit 5ca04f2

Browse files
committed
fix: replace fixed timeout with polling in test setup — fixes CI race condition
1 parent 5fde320 commit 5ca04f2

File tree

1 file changed

+34
-9
lines changed

1 file changed

+34
-9
lines changed

tests/helpers/setup.js

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ let _tmpDir = null
4343
export const getApp = () => _app
4444
export const getDb = () => _db
4545

46-
// Schema mirrors db.js exactly, DROP+CREATE gives a clean slate every time
46+
// Schema -- mirrors db.js exactly, DROP+CREATE gives a clean slate every time
4747
const SCHEMA = `
4848
DROP TABLE IF EXISTS items;
4949
DROP TABLE IF EXISTS channels;
@@ -83,13 +83,39 @@ function dbRun(db, sql, params = []) {
8383
})
8484
}
8585

86+
function dbGet(db, sql, params = []) {
87+
return new Promise((resolve, reject) => {
88+
db.get(sql, params, (err, row) => err ? reject(err) : resolve(row))
89+
})
90+
}
91+
8692
async function seedDefaultChannels(db) {
8793
const defaults = ['general', 'projects', 'assets', 'temp']
8894
for (const name of defaults) {
8995
await dbRun(db, 'INSERT INTO channels (name) VALUES (?)', [name])
9096
}
9197
}
9298

99+
/**
100+
* Wait until db.js has finished its own async initialisation.
101+
* db.js uses db.serialize() to queue CREATE TABLE + INSERT channel statements.
102+
* We poll until those statements have landed rather than using a fixed timeout,
103+
* which makes this reliable across machines of different speeds (Windows, Linux CI).
104+
*/
105+
async function waitForDbReady(db, maxWaitMs = 5000) {
106+
const start = Date.now()
107+
while (Date.now() - start < maxWaitMs) {
108+
try {
109+
const row = await dbGet(db, "SELECT COUNT(*) as count FROM channels")
110+
if (row && row.count >= 4) return // default channels are seeded, ready
111+
} catch (e) {
112+
// table may not exist yet -- keep waiting
113+
}
114+
await new Promise(resolve => setTimeout(resolve, 20))
115+
}
116+
throw new Error('DB did not become ready within ' + maxWaitMs + 'ms')
117+
}
118+
93119
/**
94120
* Wipe all tables, recreate schema, re-seed default channels.
95121
* Call in beforeEach to give every test a perfectly clean slate.
@@ -100,7 +126,7 @@ export async function resetDb() {
100126
}
101127

102128
/**
103-
* Insert a text item directly into the DB bypasses HTTP layer.
129+
* Insert a text item directly into the DB -- bypasses HTTP layer.
104130
* Useful for setting up preconditions quickly.
105131
*/
106132
export function insertItem(fields) {
@@ -141,7 +167,7 @@ export function insertChannel(name, pinned = 0) {
141167
export function setup() {
142168
beforeAll(async () => {
143169

144-
// Step 1: isolated temp directory nothing touches real instbyte-data/
170+
// Step 1: isolated temp directory -- nothing touches real instbyte-data/
145171
_tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'instbyte-test-'))
146172
const uploadsDir = path.join(_tmpDir, 'uploads')
147173
fs.mkdirSync(uploadsDir)
@@ -168,17 +194,16 @@ export function setup() {
168194
_app = mod.app
169195
_db = require('../../server/db.js')
170196

171-
// Step 6: wait for db.js to finish its own async init.
197+
// Step 6: wait for db.js to finish its own async init by polling.
172198
// db.js uses db.serialize() which queues CREATE TABLE + INSERT channel
173-
// statements asynchronously. We wait for those to land before our first
174-
// resetDb() call wipes them — otherwise resetDb() races with the seeding
175-
// and causes a UNIQUE constraint error.
176-
await new Promise(resolve => setTimeout(resolve, 300))
199+
// statements asynchronously. We poll until the channels exist rather than
200+
// using a fixed timeout -- this is reliable across Windows and Linux CI.
201+
await waitForDbReady(_db)
177202
})
178203

179204
afterAll(async () => {
180205
// Close the SQLite connection before deleting the temp folder.
181-
// On Windows the .sqlite file stays locked until explicitly closed
206+
// On Windows the .sqlite file stays locked until explicitly closed --
182207
// fs.rmSync throws EBUSY without this step.
183208
if (_db) {
184209
await new Promise(resolve => _db.close(() => resolve()))

0 commit comments

Comments
 (0)