(sessions: Session[])
| 151 | } |
| 152 | |
| 153 | async function drain(sessions: Session[]) { |
| 154 | scenario('drain: reap + flush until nothing remains'); |
| 155 | console.log(` …waiting ${IDLE_WAIT_MS}ms for all sessions to idle out`); |
| 156 | await sleep(IDLE_WAIT_MS); |
| 157 | |
| 158 | const ids = sessions.map((s) => s.sessionId); |
| 159 | const target = ids.length; |
| 160 | const deadline = Date.now() + DRAIN_TIMEOUT_MS; |
| 161 | let ends = 0; |
| 162 | let remaining = Number.POSITIVE_INFINITY; |
| 163 | |
| 164 | while (Date.now() < deadline) { |
| 165 | await triggerReaper(); // enqueue session_end for idle sessions |
| 166 | await sleep(1000); // let the worker process the close jobs |
| 167 | await triggerCron('flushEvents'); // push session_start/end + events → CH |
| 168 | await triggerCron('flushSessions'); // push sessions table rows → CH |
| 169 | |
| 170 | ends = await countByName(ids, 'session_end'); |
| 171 | remaining = await redis.zcard(wallclockKey); |
| 172 | console.log(` …session_end ${ends}/${target}, wallclock remaining ${remaining}`); |
| 173 | if (ends >= target && remaining === 0) break; |
| 174 | await sleep(1500); |
| 175 | } |
| 176 | |
| 177 | check('clickhouse: one session_end per session', ends === target, `got ${ends}`); |
| 178 | check('redis: wallclock index fully drained', remaining === 0, `${remaining} left`); |
| 179 | |
| 180 | const bufLen = await redis.llen(SESSION_BUFFER_LIST); |
| 181 | check('redis: session buffer drained to ClickHouse', bufLen === 0, `${bufLen} rows pending`); |
| 182 | |
| 183 | // Spot-check a sample of devices: no session blob should linger after close. |
| 184 | const sample = sessions.filter((_, k) => k % Math.ceil(sessions.length / 20) === 0); |
| 185 | let leaked = 0; |
| 186 | for (const s of sample) { |
| 187 | if (await getBlob(s.deviceId)) leaked++; |
| 188 | } |
| 189 | check('redis: no session blobs leaked (sampled)', leaked === 0, `${leaked}/${sample.length} leaked`); |
| 190 | } |
| 191 | |
| 192 | async function reconcile(sessions: Session[]) { |
| 193 | scenario('reconcile: ClickHouse event counts'); |
no test coverage detected