|
8 | 8 |
|
9 | 9 | import { getDb, useDb, insertRun, updateRunOnFinish, insertResult, insertResultsBatch, |
10 | 10 | getRun, findRunByKey, listRuns, getLatestResultPerNode, |
11 | | - getNodeHistory, getNetworkStats, insertErrorLog, getNodeErrors, closeDb } from '../core/db.js'; |
| 11 | + getNodeHistory, getNetworkStats, insertErrorLog, getNodeErrors, closeDb, |
| 12 | + insertBatch, insertBatchResult, updateBatchOnFinish, getBatchResults, |
| 13 | + getActiveBatch, getLastBatch, pruneBatchResults } from '../core/db.js'; |
12 | 14 |
|
13 | 15 | // ─── Use a fresh in-memory DB for this test run ─────────────────────────────── |
14 | 16 | // `useDb` sets the module singleton so all exported helpers target this handle. |
@@ -207,6 +209,65 @@ assert( |
207 | 209 | `log_snippet capped at <= 16384 chars (got ${capErrors[0].log_snippet?.length})`, |
208 | 210 | ); |
209 | 211 |
|
| 212 | +// 14. pruneBatchResults — keep active + last-finished + last K batches |
| 213 | +console.log('14. pruneBatchResults retention...'); |
| 214 | +// Build 6 batches with staggered started_at so recency is unambiguous. |
| 215 | +// b1..b5 are finished (oldest→newest); b6 is left ACTIVE (finished_at NULL) |
| 216 | +// but with the OLDEST started_at so it would fall outside a recency window — |
| 217 | +// proving the active-batch keep is independent of the last-K window. |
| 218 | +const KEEP = 3; |
| 219 | +const batchIds = []; |
| 220 | +for (let i = 0; i < 6; i++) { |
| 221 | + const bid = Number(insertBatch({ |
| 222 | + started_at: NOW + i * 1000, // b1 oldest .. b6 newest |
| 223 | + snapshot_size: 1, |
| 224 | + mode: 'p2p', |
| 225 | + })); |
| 226 | + batchIds.push(bid); |
| 227 | + // One node result per batch, distinct address so we can verify survival. |
| 228 | + insertBatchResult(bid, { |
| 229 | + address: `sentnode1batch${i}`, |
| 230 | + actualMbps: 10 + i, |
| 231 | + testedAt: NOW + i * 1000, |
| 232 | + }); |
| 233 | +} |
| 234 | +const [b1, b2, b3, b4, b5, b6] = batchIds; |
| 235 | +// Finish b1..b5 (oldest→newest by finished_at). Leave b6 ACTIVE. |
| 236 | +[b1, b2, b3, b4, b5].forEach((bid, idx) => { |
| 237 | + updateBatchOnFinish(bid, { finished_at: NOW + 10_000 + idx * 1000, passed: 1, failed: 0 }); |
| 238 | +}); |
| 239 | +// Make b6 ACTIVE but the OLDEST by started_at so it's outside the last-K window. |
| 240 | +db.prepare('UPDATE batches SET started_at = @ts, finished_at = NULL WHERE id = @id') |
| 241 | + .run({ ts: NOW - 100_000, id: b6 }); |
| 242 | + |
| 243 | +// Sanity: active = b6, last-finished = b5 (latest finished_at). |
| 244 | +eq(getActiveBatch().batch.id, b6, 'active batch is b6 (finished_at NULL)'); |
| 245 | +eq(getLastBatch().batch.id, b5, 'last-finished batch is b5'); |
| 246 | + |
| 247 | +const summary = pruneBatchResults({ keepBatches: KEEP }); |
| 248 | +// keep-set: last-K by started_at (b5,b4,b3) ∪ active(b6) ∪ last-finished(b5) = {b3,b4,b5,b6} |
| 249 | +eq(summary.keptBatches, 4, 'keep-set size = last-K(3) ∪ active ∪ last-finished = 4'); |
| 250 | + |
| 251 | +// (a) active batch rows survive |
| 252 | +eq(getBatchResults(b6).results.length, 1, 'active batch (b6) rows survive'); |
| 253 | +// (b) most-recent finished batch rows survive |
| 254 | +eq(getBatchResults(b5).results.length, 1, 'last-finished batch (b5) rows survive'); |
| 255 | +// (c) batches inside the last-K window survive |
| 256 | +eq(getBatchResults(b4).results.length, 1, 'recent batch (b4) rows survive'); |
| 257 | +eq(getBatchResults(b3).results.length, 1, 'recent batch (b3) rows survive'); |
| 258 | +// (d) old batches outside the keep-set are gone (rows AND parent batch row) |
| 259 | +eq(getBatchResults(b2).results.length, 0, 'old batch (b2) rows pruned'); |
| 260 | +eq(getBatchResults(b1).results.length, 0, 'old batch (b1) rows pruned'); |
| 261 | +assert(getBatchResults(b1).batch == null, 'old batch (b1) parent row deleted'); |
| 262 | +assert(getBatchResults(b2).batch == null, 'old batch (b2) parent row deleted'); |
| 263 | +// Two batches deleted (b1, b2), each with one batch_results row. |
| 264 | +eq(summary.deletedBatches, 2, 'deletedBatches = 2 (b1, b2)'); |
| 265 | +eq(summary.deletedBatchResults, 2, 'deletedBatchResults = 2'); |
| 266 | +// FK integrity intact after prune. |
| 267 | +assert(db.prepare('PRAGMA foreign_key_check').all().length === 0, 'foreign_key_check clean after prune'); |
| 268 | +// Active batch never deleted as a parent even if outside window — already |
| 269 | +// asserted via b6 survival above. |
| 270 | + |
210 | 271 | // ─── Results ────────────────────────────────────────────────────────────────── |
211 | 272 |
|
212 | 273 | console.log(`\n${'='.repeat(50)}`); |
|
0 commit comments