@bongos/core 1.19.204 → 1.19.206

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -212,6 +212,69 @@ test('no push credential → no-op (skipped: unconfigured), never touches the DB
212
212
  assert.equal(deps._calls.status.length, 0, 'must not call GitHub when unconfigured');
213
213
  });
214
214
 
215
+ // task 1003049 — an App with id + key but no repo installation passes isConfigured,
216
+ // then every per-branch call throws APP_NOT_INSTALLED into a best-effort catch:
217
+ // silent stranding. The sweep must instead say it loudly ONCE and park.
218
+ test('1003049 App configured but NOT installed → loud one-time WARN, sweep parked, heals when installed', async () => {
219
+ const deps = mkDeps({ configured: true, rows: [{ task_id: 1, builder_id: 9, worktree_name: 'wt' }] });
220
+ deps.warnState = {};
221
+ let installed = false;
222
+ deps.githubPush.appConfigured = () => true;
223
+ deps.githubPush.getInstallationToken = async () => {
224
+ if (!installed) {
225
+ throw Object.assign(new Error('APP_NOT_INSTALLED'), {
226
+ code: 'APP_NOT_INSTALLED', owner: 'someowner', repo: 'somerepo',
227
+ install_url: 'https://github.com/apps/x/installations/new',
228
+ });
229
+ }
230
+ return 'tok';
231
+ };
232
+ const r1 = await reconciler.runSweep(deps);
233
+ assert.equal(r1.skipped, 'app_not_installed');
234
+ assert.equal(deps._calls.status.length, 0, 'no per-branch GitHub calls while uninstallable');
235
+ const warns = () => deps._calls.errors.filter((m) => /not installed/.test(m));
236
+ assert.equal(warns().length, 1, 'warned exactly once');
237
+ assert.match(warns()[0], /someowner\/somerepo/, 'names the repo');
238
+ assert.match(warns()[0], /installations\/new/, 'carries the install URL');
239
+ const r2 = await reconciler.runSweep(deps);
240
+ assert.equal(r2.skipped, 'app_not_installed');
241
+ assert.equal(warns().length, 1, 'the streak stays quiet');
242
+ installed = true;
243
+ const r3 = await reconciler.runSweep(deps);
244
+ assert.ok(!r3.skipped, 'an installed App lets the sweep run again');
245
+ });
246
+
247
+ test('1003049 foreign-install watch: WARNs once per streak on installs beyond the repo owner, re-arms when clean', async () => {
248
+ const deps = mkDeps({ configured: true, rows: [] });
249
+ deps.warnState = {};
250
+ let roster = [{ id: 2, account: 'stranger' }];
251
+ deps.githubPush.appConfigured = () => true;
252
+ deps.githubPush.getInstallationToken = async () => 'tok';
253
+ deps.githubPush.foreignInstallations = async () => roster;
254
+ const warns = () => deps._calls.errors.filter((m) => /beyond the repo owner/.test(m));
255
+ await reconciler.runSweep(deps);
256
+ assert.equal(warns().length, 1, 'a foreign install is surfaced loudly');
257
+ assert.match(warns()[0], /stranger/, 'names the account');
258
+ await reconciler.runSweep(deps);
259
+ assert.equal(warns().length, 1, 'the streak stays quiet');
260
+ roster = [];
261
+ await reconciler.runSweep(deps);
262
+ roster = [{ id: 9, account: 'other' }];
263
+ await reconciler.runSweep(deps);
264
+ assert.equal(warns().length, 2, 'a clean roster re-arms the warning');
265
+ });
266
+
267
+ test('1003049 a transient App-token exchange failure does NOT park the sweep', async () => {
268
+ const deps = mkDeps({ configured: true, rows: [] });
269
+ deps.warnState = {};
270
+ deps.githubPush.appConfigured = () => true;
271
+ deps.githubPush.getInstallationToken = async () => {
272
+ throw Object.assign(new Error('boom'), { code: 'APP_TOKEN_FAILED', status: 500 });
273
+ };
274
+ const r = await reconciler.runSweep(deps);
275
+ assert.ok(!r.skipped, 'transient exchange failures are per-branch territory, not a park');
276
+ });
277
+
215
278
  test('a landed branch → shipTask + broadcast fire, reconciled=1, attributed to the claim holder', async () => {
216
279
  const deps = mkDeps({
217
280
  rows: [{ task_id: 1096, builder_id: 7, worktree_name: 't1096-helpers', published_head_sha: ON_MAIN }],
@@ -60,8 +60,10 @@ async function test(name, fn) {
60
60
 
61
61
  const pens = [];
62
62
  const outsideDirs = [];
63
+ const tmpDirs = [];
63
64
  function tmp(prefix) {
64
65
  const d = fs.realpathSync(fs.mkdtempSync(path.join(os.tmpdir(), prefix)));
66
+ tmpDirs.push(d);
65
67
  return d;
66
68
  }
67
69
 
@@ -327,6 +329,17 @@ await test('fixture B: a clean scan normalizes into a persistable row', async ()
327
329
 
328
330
  for (const q of pens) { try { q.cleanup(); } catch { /* best effort */ } }
329
331
  for (const d of outsideDirs) { try { fs.rmSync(d, { recursive: true, force: true }); } catch { /* best effort */ } }
332
+ // tmpDirs are the OUTER wrappers (pen tmpRoots + victim homes) — pen.cleanup()
333
+ // removes only the pen root inside, so these must be swept explicitly.
334
+ for (const d of tmpDirs) { try { fs.rmSync(d, { recursive: true, force: true }); } catch { /* best effort */ } }
335
+
336
+ // The wrapper dirs tmp() creates OUTLIVE pen.cleanup() (which removes only the
337
+ // pen root inside them) — 24 empty bongos-scan-host-* leftovers piled up in
338
+ // Temp before this was verified (task 1003058). Prove the teardown is total.
339
+ await test('teardown leaves none of this run\'s temp dirs behind', () => {
340
+ const leaked = tmpDirs.filter((d) => fs.existsSync(d));
341
+ assert.deepEqual(leaked, [], `leaked temp dirs: ${leaked.join(', ')}`);
342
+ });
330
343
 
331
344
  console.log('');
332
345
  console.log(`tool_scan_engine: ${passed} passed, ${failed} failed`);