@bongos/core 1.19.952 → 1.19.953
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.
- package/.bongos-core.json +58 -53
- package/docs/copy-inventory.md +32 -24
- package/docs/copy-registry.json +100 -28
- package/docs/module-api-changelog.md +2 -0
- package/modules/hall-ui/public/atlas.html +1 -1
- package/modules/hall-ui/public/blockers.html +1 -1
- package/modules/hall-ui/public/board-room.html +1 -1
- package/modules/hall-ui/public/collab.html +1 -1
- package/modules/hall-ui/public/copy-desk.html +1 -1
- package/modules/hall-ui/public/deploy.html +3 -3
- package/modules/hall-ui/public/deploy.js +97 -5
- package/modules/hall-ui/public/diagrams.html +1 -1
- package/modules/hall-ui/public/drachmae.html +1 -1
- package/modules/hall-ui/public/gate.html +1 -1
- package/modules/hall-ui/public/goal-map.html +1 -1
- package/modules/hall-ui/public/goals.html +1 -1
- package/modules/hall-ui/public/government.html +1 -1
- package/modules/hall-ui/public/idea.html +1 -1
- package/modules/hall-ui/public/ideas.html +1 -1
- package/modules/hall-ui/public/index.html +1 -1
- package/modules/hall-ui/public/modules.html +1 -1
- package/modules/hall-ui/public/people.html +1 -1
- package/modules/hall-ui/public/primer.html +1 -1
- package/modules/hall-ui/public/profile.html +1 -1
- package/modules/hall-ui/public/project-settings.css +16 -0
- package/modules/hall-ui/public/project-settings.html +1 -1
- package/modules/hall-ui/public/ranks.html +1 -1
- package/modules/hall-ui/public/roadmap.html +1 -1
- package/modules/hall-ui/public/roster.html +1 -1
- package/modules/hall-ui/public/sessions.html +1 -1
- package/modules/hall-ui/public/settings.html +1 -1
- package/modules/hall-ui/public/shell.js +12 -3
- package/modules/hall-ui/public/sky.html +1 -1
- package/modules/hall-ui/public/studio.html +1 -1
- package/modules/hall-ui/public/style.css +6 -0
- package/modules/hall-ui/public/task.html +1 -1
- package/modules/hall-ui/public/thinking.html +1 -1
- package/modules/hall-ui/public/watch.html +1 -1
- package/modules/hall-ui/public/work.html +1 -1
- package/modules/provisioning/core-upgrade.js +35 -3
- package/modules/provisioning/migrations/provisioning_022_last_core_move_index.sql +25 -0
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/module-api.js +1 -1
- package/tests/core_update_banner.mjs +30 -2
- package/tests/core_upgrade_door.mjs +50 -0
- package/tests/deploy_page_projects.mjs +131 -7
- package/tests/nav_permission_atoms.mjs +1 -1
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.953'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -202,7 +202,7 @@ test('a real request through the assembled router returns the verdict, not a 500
|
|
|
202
202
|
|
|
203
203
|
// ── the banner ────────────────────────────────────────────────────────────────
|
|
204
204
|
|
|
205
|
-
function boot({ permissions, update, status = 200, dismissed = null, provisioning = true } = {}) {
|
|
205
|
+
function boot({ permissions, update, status = 200, dismissed = null, provisioning = true, pathname = '/settings' } = {}) {
|
|
206
206
|
const els = new Map();
|
|
207
207
|
const inserted = [];
|
|
208
208
|
const stored = {};
|
|
@@ -227,7 +227,7 @@ function boot({ permissions, update, status = 200, dismissed = null, provisionin
|
|
|
227
227
|
addEventListener() {}, dispatchEvent() {},
|
|
228
228
|
get cookie() { return ''; }, set cookie(_v) {},
|
|
229
229
|
};
|
|
230
|
-
const location = { hostname: 'h', origin: 'https://h', pathname
|
|
230
|
+
const location = { hostname: 'h', origin: 'https://h', pathname, hash: '', assign() {}, replace() {} };
|
|
231
231
|
const windowObj = {
|
|
232
232
|
__BRANDING__: {}, __MODULES__: { enabled: { provisioning } },
|
|
233
233
|
BongosClient: {
|
|
@@ -334,3 +334,31 @@ test('the banner renders from the projection /me ACTUALLY sends, not one written
|
|
|
334
334
|
'gates on is outside NAV_PERMISSION_ATOMS and the feature is dead for everyone (task 1004118)',
|
|
335
335
|
);
|
|
336
336
|
});
|
|
337
|
+
|
|
338
|
+
// ── the banner must not offer the page you are on (task 1004145) ──────────────
|
|
339
|
+
// The owner pressed this on /deploy and reported "I clicked update, but I didn't see
|
|
340
|
+
// anything happen". Nothing had: it is an <a href="/deploy"> styled as a button and
|
|
341
|
+
// labelled with the verb, so on /deploy it navigates to the current page and does
|
|
342
|
+
// nothing at all. No request was ever made — verified against the live box, which had
|
|
343
|
+
// no core-upgrade POST in three hours and no queued intent from that press.
|
|
344
|
+
|
|
345
|
+
test('on /deploy the banner points at the control instead of linking to /deploy', async () => {
|
|
346
|
+
const b = boot({ pathname: '/deploy', permissions: { 'core.pin.move': true }, update: BEHIND });
|
|
347
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
348
|
+
const html = b.banner().innerHTML;
|
|
349
|
+
assert.doesNotMatch(html, /href="\/deploy"/, 'no link to the page already open');
|
|
350
|
+
assert.match(html, /control below/i, 'it says where the real control is');
|
|
351
|
+
assert.match(html, /1\.19\.871/, 'and still announces the release');
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
test('everywhere else the banner still offers the link', async () => {
|
|
355
|
+
const b = boot({ pathname: '/settings', permissions: { 'core.pin.move': true }, update: BEHIND });
|
|
356
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
357
|
+
assert.match(b.banner().innerHTML, /href="\/deploy"/, 'the action is real from any other page');
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test('a trailing slash is still /deploy', async () => {
|
|
361
|
+
const b = boot({ pathname: '/deploy/', permissions: { 'core.pin.move': true }, update: BEHIND });
|
|
362
|
+
await new Promise((r) => setTimeout(r, 0));
|
|
363
|
+
assert.doesNotMatch(b.banner().innerHTML, /href="\/deploy"/);
|
|
364
|
+
});
|
|
@@ -697,5 +697,55 @@ await t('every strict body schema in this module declares each field its handler
|
|
|
697
697
|
'under strict, a field the schema does not name is a 400 BEFORE the handler runs, so reading it is unreachable code and a broken endpoint');
|
|
698
698
|
});
|
|
699
699
|
|
|
700
|
+
|
|
701
|
+
console.log('\nthe last move, whatever became of it (task 1004145):');
|
|
702
|
+
|
|
703
|
+
const SNAP = { served_version: '1.19.900', available: ['1.19.900', '1.19.949'], recommended: '1.19.949', observed_at: new Date().toISOString() };
|
|
704
|
+
|
|
705
|
+
await t('a FAILED move is visible — the payload is no longer identical to before it', () => {
|
|
706
|
+
// open_intent is `state IN ('pending','running')` by the index's own definition, so an
|
|
707
|
+
// errored intent is not open. Before last_move, the payload after a rolled-back
|
|
708
|
+
// upgrade matched the payload before it exactly, and the owner's only signal was a
|
|
709
|
+
// number that never changed — indistinguishable from never having pressed.
|
|
710
|
+
const failed = { action: 'core-upgrade', state: 'error', target_version: '1.19.949', resolved_at: new Date().toISOString(), last_error: 'working tree not clean — commit/stash first, or pass --force.' };
|
|
711
|
+
const p = CU.previewFromSnapshot(ACTIVE, SNAP, null, failed);
|
|
712
|
+
assert.equal(p.open_intent, null, 'it is not an OPEN intent');
|
|
713
|
+
assert.equal(p.last_move.state, 'error');
|
|
714
|
+
assert.equal(p.last_move.target_version, '1.19.949');
|
|
715
|
+
assert.match(p.last_move.error, /working tree not clean/, 'and the reason travels');
|
|
716
|
+
});
|
|
717
|
+
|
|
718
|
+
await t('a SUCCESSFUL move is attributable, not merely finished', () => {
|
|
719
|
+
const done = { action: 'core-upgrade', state: 'done', target_version: '1.19.949', resolved_at: new Date().toISOString(), last_error: null };
|
|
720
|
+
const p = CU.previewFromSnapshot({ ...ACTIVE }, { ...SNAP, served_version: '1.19.949', recommended: null }, null, done);
|
|
721
|
+
assert.equal(p.last_move.state, 'done');
|
|
722
|
+
assert.equal(p.last_move.target_version, p.served_version, 'the move names the version the box now serves');
|
|
723
|
+
assert.equal(p.last_move.error, null);
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
await t('a long refusal is truncated — it reaches a DOM', () => {
|
|
727
|
+
// The dirty-tree refusal carries a whole `git status`; test4's ran to dozens of lines.
|
|
728
|
+
const huge = { action: 'core-upgrade', state: 'error', target_version: '1.19.949', resolved_at: new Date().toISOString(), last_error: 'x'.repeat(5000) };
|
|
729
|
+
const p = CU.previewFromSnapshot(ACTIVE, SNAP, null, huge);
|
|
730
|
+
assert.ok(p.last_move.error.length <= 600, `bounded — got ${p.last_move.error.length}`);
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
await t('no move ever made is null, not an invented success', () => {
|
|
734
|
+
const p = CU.previewFromSnapshot(ACTIVE, SNAP, null, null);
|
|
735
|
+
assert.equal(p.last_move, null);
|
|
736
|
+
});
|
|
737
|
+
|
|
738
|
+
await t('lastCoreMove reads MOVES only — a preview refusal is not a failed deploy', async () => {
|
|
739
|
+
// Folding core-upgrade-preflight in here would let a dry run's refusal render as
|
|
740
|
+
// "the move did not land". The preview reports itself through `preflight`.
|
|
741
|
+
const seen = [];
|
|
742
|
+
const pool = { query: async (sql, params) => { seen.push({ sql, params }); return { rows: [] }; } };
|
|
743
|
+
await CU.lastCoreMove(pool, 41);
|
|
744
|
+
assert.match(seen[0].sql, /action = 'core-upgrade'/, 'scoped to the move action');
|
|
745
|
+
assert.doesNotMatch(seen[0].sql, /preflight/, 'and never widened to the preview');
|
|
746
|
+
assert.doesNotMatch(seen[0].sql, /state IN/, 'and not filtered to open states — the point is the terminal ones');
|
|
747
|
+
assert.deepEqual(seen[0].params, [41]);
|
|
748
|
+
});
|
|
749
|
+
|
|
700
750
|
console.log(`\ncore_upgrade_door: ${passed} passed, ${failed} failed`);
|
|
701
751
|
if (failed) process.exit(1);
|
|
@@ -32,10 +32,15 @@ const FLEET = [
|
|
|
32
32
|
{ id: '35', slug: 'unreadable-one', status: 'active', hosting_shape: 'standalone' },
|
|
33
33
|
];
|
|
34
34
|
|
|
35
|
-
function boot({ controlPlane = preview('1', 'cloudbongos'), controlPlaneStatus = 200, fleet = FLEET, fleetStatus = 200 } = {}) {
|
|
35
|
+
function boot({ controlPlane = preview('1', 'cloudbongos'), controlPlaneStatus = 200, fleet = FLEET, fleetStatus = 200, instanceOver = null, moveTries = 4, postStatus = 200 } = {}) {
|
|
36
36
|
const els = new Map();
|
|
37
37
|
const posts = [];
|
|
38
38
|
const gets = [];
|
|
39
|
+
// How many times each box has been re-read. A move POLLS, so a test of the move has to
|
|
40
|
+
// be able to change what the server says between reads — otherwise the only reachable
|
|
41
|
+
// assertion is "it asked", never "it noticed".
|
|
42
|
+
const reads = new Map();
|
|
43
|
+
const toasts = [];
|
|
39
44
|
const makeEl = () => ({
|
|
40
45
|
_on: {}, _html: '', hidden: false, textContent: '', disabled: false,
|
|
41
46
|
addEventListener(ev, fn) { this._on[ev] = fn; },
|
|
@@ -48,7 +53,13 @@ function boot({ controlPlane = preview('1', 'cloudbongos'), controlPlaneStatus =
|
|
|
48
53
|
const request = async (method, url, opts) => {
|
|
49
54
|
const p = url.replace('/api/bongos', '');
|
|
50
55
|
if (method === 'GET') gets.push(p);
|
|
51
|
-
if (method === 'POST') {
|
|
56
|
+
if (method === 'POST') {
|
|
57
|
+
posts.push({ path: p, body: opts && opts.body });
|
|
58
|
+
if (postStatus !== 200) {
|
|
59
|
+
return { ok: false, status: postStatus, data: { error: { code: 'intent_conflict', message: 'A core-upgrade run is currently running for this project.' } } };
|
|
60
|
+
}
|
|
61
|
+
return { ok: true, status: 200, data: { message: 'Queued.' } };
|
|
62
|
+
}
|
|
52
63
|
if (p === '/provisioning/control-plane') {
|
|
53
64
|
if (controlPlaneStatus !== 200) {
|
|
54
65
|
return { ok: false, status: controlPlaneStatus, data: { error: { code: 'control_plane_not_enrolled', message: 'This platform has no deploy door yet. Run enroll-control-plane.js once.' } } };
|
|
@@ -66,13 +77,16 @@ function boot({ controlPlane = preview('1', 'cloudbongos'), controlPlaneStatus =
|
|
|
66
77
|
if (!inst || inst.slug === 'unreadable-one') {
|
|
67
78
|
return { ok: false, status: 500, data: { error: { code: 'core_upgrade_preview_failed', message: 'internal error' } } };
|
|
68
79
|
}
|
|
69
|
-
|
|
80
|
+
const n = (reads.get(inst.id) || 0) + 1;
|
|
81
|
+
reads.set(inst.id, n);
|
|
82
|
+
const over = instanceOver ? instanceOver(inst, n) : {};
|
|
83
|
+
return { ok: true, status: 200, data: { preview: preview(inst.id, inst.slug, { hosting_shape: inst.hosting_shape, ...over }) } };
|
|
70
84
|
}
|
|
71
85
|
return { ok: false, status: 404, data: { error: { code: 'not_found' } } };
|
|
72
86
|
};
|
|
73
87
|
const windowObj = {
|
|
74
88
|
BongosClient: { createClient: () => ({ request }) },
|
|
75
|
-
OTB: { escapeHtml: (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])), toast() {} },
|
|
89
|
+
OTB: { escapeHtml: (s) => String(s).replace(/[&<>"]/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c])), toast(m) { toasts.push(String(m)); } },
|
|
76
90
|
location: { assign() {} },
|
|
77
91
|
};
|
|
78
92
|
// The page's poll interval, driven rather than slept through. Injecting the REAL
|
|
@@ -81,13 +95,18 @@ function boot({ controlPlane = preview('1', 'cloudbongos'), controlPlaneStatus =
|
|
|
81
95
|
// flaky against a torn-down sandbox.
|
|
82
96
|
windowObj.__deployPollMs = 0;
|
|
83
97
|
windowObj.__deployPollTries = 3;
|
|
98
|
+
windowObj.__deployMoveTries = moveTries;
|
|
84
99
|
const sandbox = { window: windowObj, document: documentObj, console, setTimeout, clearTimeout, queueMicrotask, Date, Set, Map, Promise };
|
|
85
100
|
sandbox.globalThis = sandbox;
|
|
86
101
|
vm.createContext(sandbox);
|
|
87
102
|
vm.runInContext(SRC, sandbox, { filename: 'deploy.js' });
|
|
88
103
|
documentObj._ready();
|
|
89
104
|
const settle = async () => { for (let i = 0; i < 40; i++) await new Promise((r) => setTimeout(r, 0)); };
|
|
90
|
-
|
|
105
|
+
// A SHORT advance, for observing a state that exists only while the poll is running.
|
|
106
|
+
// `settle` drains the whole loop (POLL_MS is 0 here), so an assertion about the
|
|
107
|
+
// in-flight row has to look before the loop finishes, not after it.
|
|
108
|
+
const tick = async (n = 3) => { for (let i = 0; i < n; i++) await new Promise((r) => setTimeout(r, 0)); };
|
|
109
|
+
return { els, posts, gets, reads, toasts, settle, tick, panel: () => els.get('dp-panel').innerHTML, sub: () => els.get('dp-sub').textContent };
|
|
91
110
|
}
|
|
92
111
|
|
|
93
112
|
test('the platform AND every live hosted project get a row', async () => {
|
|
@@ -189,8 +208,12 @@ test('a move re-reads ONLY the box it moved, not every project again', async ()
|
|
|
189
208
|
b.els.get('dp-confirm-32')._on.click();
|
|
190
209
|
await b.settle();
|
|
191
210
|
const previewReads = b.gets.filter((g) => g.endsWith('/core-upgrade'));
|
|
192
|
-
|
|
193
|
-
|
|
211
|
+
// Since task 1004145 a move POLLS, so the count is no longer one — and the count was
|
|
212
|
+
// only ever a proxy for the thing that matters. Asserted directly instead: every
|
|
213
|
+
// re-read is the moved box, and no other box is touched.
|
|
214
|
+
assert.ok(previewReads.length > 0, 'the moved box is re-read');
|
|
215
|
+
assert.deepEqual([...new Set(previewReads)], ['/provisioning/instances/32/core-upgrade'],
|
|
216
|
+
'and it is the ONLY box re-read — no fan-out across the fleet');
|
|
194
217
|
assert.ok(!b.gets.includes('/provisioning/fleet'), 'the fleet is not re-listed');
|
|
195
218
|
});
|
|
196
219
|
|
|
@@ -290,3 +313,104 @@ test('a verdict already in hand is NOT re-requested', async () => {
|
|
|
290
313
|
assert.equal(b.posts.filter((x) => x.body && x.body.dry_run).length, 0, 'no check requested');
|
|
291
314
|
assert.match(b.panel(), /nothing blocks this move/i, 'the held verdict is shown');
|
|
292
315
|
});
|
|
316
|
+
|
|
317
|
+
// ── the press has to show something (task 1004145) ────────────────────────────
|
|
318
|
+
// The owner pressed Upgrade and reported "I didn't see anything happen". Two reasons:
|
|
319
|
+
// the banner's Upgrade is a link to /deploy (fixed in shell.js), and move() refreshed
|
|
320
|
+
// once — before the runner's tick — then fell silent for the whole upgrade.
|
|
321
|
+
|
|
322
|
+
test('confirming a move says so IMMEDIATELY, naming the version it is taking', async () => {
|
|
323
|
+
const b = boot();
|
|
324
|
+
await b.settle();
|
|
325
|
+
b.els.get('dp-preview-32')._on.click();
|
|
326
|
+
b.els.get('dp-confirm-32')._on.click();
|
|
327
|
+
await b.tick();
|
|
328
|
+
const html = b.panel();
|
|
329
|
+
assert.match(html, /Moving to core/i, 'the row reports the move, rather than snapping back');
|
|
330
|
+
assert.match(html, /1\.19\.871/, 'and names the target');
|
|
331
|
+
assert.match(html, /restarts as part of this/i, 'and warns what it costs');
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
test('the in-flight state survives the poll — it does not close under the reader', async () => {
|
|
335
|
+
// refreshOne() returns a row to step one, which is right after a move and wrong
|
|
336
|
+
// DURING one; without keepStep the poll wipes its own message on the first interval.
|
|
337
|
+
// Observed after a poll has actually run, but before the bound is exhausted.
|
|
338
|
+
// A long bound so the loop cannot exhaust mid-assertion: the state under test exists
|
|
339
|
+
// only WHILE it polls.
|
|
340
|
+
const b = boot({ moveTries: 500, instanceOver: () => ({ open_intent: { action: 'core-upgrade', state: 'running', target_version: '1.19.871' } }) });
|
|
341
|
+
await b.settle();
|
|
342
|
+
b.els.get('dp-preview-32')._on.click();
|
|
343
|
+
b.els.get('dp-confirm-32')._on.click();
|
|
344
|
+
await b.tick(8);
|
|
345
|
+
assert.ok(b.reads.get('32') >= 1, 'at least one poll has landed');
|
|
346
|
+
assert.match(b.panel(), /Moving to core/i, 'still reporting after a refresh');
|
|
347
|
+
assert.doesNotMatch(b.panel(), /id="dp-preview-32"/, 'and has not reverted to step one');
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
test('when the box lands on the new core, it CONFIRMS it', async () => {
|
|
351
|
+
// The whole ask: "confirmation when it updates". A changed number is not a
|
|
352
|
+
// confirmation — nothing tied it to the press.
|
|
353
|
+
const b = boot({
|
|
354
|
+
instanceOver: (inst, n) => (n >= 2
|
|
355
|
+
? { served_version: '1.19.871', behind: false, open_intent: null, last_move: { target_version: '1.19.871', state: 'done', at: new Date().toISOString(), error: null } }
|
|
356
|
+
: { open_intent: { action: 'core-upgrade', state: 'running', target_version: '1.19.871' } }),
|
|
357
|
+
});
|
|
358
|
+
await b.settle();
|
|
359
|
+
b.els.get('dp-preview-32')._on.click();
|
|
360
|
+
b.els.get('dp-confirm-32')._on.click();
|
|
361
|
+
await b.settle();
|
|
362
|
+
const html = b.panel();
|
|
363
|
+
assert.match(html, /Now running core 1\.19\.871/i, 'the move is confirmed, not merely finished');
|
|
364
|
+
assert.doesNotMatch(html, /Moving to core/i, 'and the in-flight state is gone');
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
test('a FAILED move is reported, with the reason, instead of looking untouched', async () => {
|
|
368
|
+
// An errored intent is not an OPEN intent, so before last_move existed the payload
|
|
369
|
+
// after a rolled-back upgrade was identical to the payload before it.
|
|
370
|
+
const b = boot({
|
|
371
|
+
instanceOver: (inst, n) => (n >= 2
|
|
372
|
+
? { open_intent: null, last_move: { target_version: '1.19.871', state: 'error', at: new Date().toISOString(), error: 'working tree not clean — commit/stash first, or pass --force.' } }
|
|
373
|
+
: { open_intent: { action: 'core-upgrade', state: 'running', target_version: '1.19.871' } }),
|
|
374
|
+
});
|
|
375
|
+
await b.settle();
|
|
376
|
+
b.els.get('dp-preview-32')._on.click();
|
|
377
|
+
b.els.get('dp-confirm-32')._on.click();
|
|
378
|
+
await b.settle();
|
|
379
|
+
const html = b.panel();
|
|
380
|
+
assert.match(html, /did not land/i, 'the failure is stated');
|
|
381
|
+
assert.match(html, /working tree not clean/, 'and carries the runner’s own reason');
|
|
382
|
+
assert.match(html, /put back on 1\.19\.800/i, 'and says the project is still running');
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
test('a poll that runs out says it is still working, not that it failed', async () => {
|
|
386
|
+
// MOVE_TRIES is a bound on this page's patience, not on the runner's work.
|
|
387
|
+
const b = boot({ instanceOver: () => ({ open_intent: { action: 'core-upgrade', state: 'running', target_version: '1.19.871' } }) });
|
|
388
|
+
await b.settle();
|
|
389
|
+
b.els.get('dp-preview-32')._on.click();
|
|
390
|
+
b.els.get('dp-confirm-32')._on.click();
|
|
391
|
+
for (let i = 0; i < 6; i++) await b.settle();
|
|
392
|
+
assert.ok(b.toasts.some((t) => /Still working on core 1\.19\.871/i.test(t)),
|
|
393
|
+
`it says where things stand — got ${JSON.stringify(b.toasts)}`);
|
|
394
|
+
assert.doesNotMatch(b.panel(), /did not land/i, 'and never claims a failure it did not see');
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
test('a queue that is REFUSED leaves no in-flight state to wait on', async () => {
|
|
398
|
+
// THIS TEST USED TO PASS WITHOUT CLICKING ANYTHING. Its setup read
|
|
399
|
+
// `b.els.get('dp-confirm-32')._on = el._on` — a self-assignment no-op — and set
|
|
400
|
+
// `b.postFails`, a property the harness never read. So it asserted "no in-flight
|
|
401
|
+
// state" on a row where no move had been attempted, which is true of every row on the
|
|
402
|
+
// page. The grader caught it. It now refuses the POST for real and presses the button.
|
|
403
|
+
const b = boot({ postStatus: 409 });
|
|
404
|
+
await b.settle();
|
|
405
|
+
b.els.get('dp-preview-32')._on.click();
|
|
406
|
+
b.els.get('dp-confirm-32')._on.click();
|
|
407
|
+
await b.settle();
|
|
408
|
+
// Counted on the MOVE specifically: arming the row also POSTs a dry_run pre-flight,
|
|
409
|
+
// so a bare posts.length would have been satisfied by the preview alone — the same
|
|
410
|
+
// class of mistake this test is being fixed for.
|
|
411
|
+
const moves = b.posts.filter((x) => !(x.body && x.body.dry_run));
|
|
412
|
+
assert.equal(moves.length, 1, `the press really happened — posts: ${JSON.stringify(b.posts)}`);
|
|
413
|
+
assert.doesNotMatch(b.panel(), /Moving to core/i, 'nothing was queued, so nothing is watched');
|
|
414
|
+
assert.ok(b.toasts.some((t) => /currently running/i.test(t)), 'and the refusal is reported');
|
|
415
|
+
assert.match(b.panel(), /id="dp-confirm-32"/, 'the button comes back so it can be retried');
|
|
416
|
+
});
|
|
@@ -87,7 +87,7 @@ test('the hall-preview fixture carries every atom /me resolves', () => {
|
|
|
87
87
|
// the file and left the query alone, so a returning browser kept the cached copy —
|
|
88
88
|
// a fix that reaches nobody looks identical to a fix that does not work.
|
|
89
89
|
|
|
90
|
-
const SHELL_SHA = '
|
|
90
|
+
const SHELL_SHA = '02de48ca2ae07995';
|
|
91
91
|
|
|
92
92
|
test('shell.js has not changed without its cache-bust being bumped', () => {
|
|
93
93
|
const actual = crypto.createHash('sha256').update(SHELL).digest('hex').slice(0, 16);
|