@bongos/core 1.19.705 → 1.19.707

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.
@@ -0,0 +1,110 @@
1
+ // tests/smoke_write_contract.mjs — the write-path smoke's own contract
2
+ // (task 1002018).
3
+ //
4
+ // TWO DEFECTS, ONE FILE. Both were found by trying to RUN scripts/gds/smoke-write.sh
5
+ // rather than by reading it, and neither was visible to any existing test:
6
+ //
7
+ // 1. IT COULD NOT RUN AT ALL. goal_id became required on POST /tasks (task 1003598,
8
+ // ADR 0250 D4) and this script still sent none, so step 1 died on a 400
9
+ // goal_id_required. The production write-path smoke had been dead since that
10
+ // landed, and nothing said so — the same silent-rot shape as the PMS-V1 version
11
+ // literal that tests/version_literals.mjs exists to prevent, one field over.
12
+ //
13
+ // 2. IT LEAKED ITS THROWAWAY TASK. The smoke creates a REAL task to exercise the
14
+ // create API and never disposed of it: 94 rows accumulated under PMS-V1 before
15
+ // the task-2016 sweep cleared them by hand.
16
+ //
17
+ // WHY ASSERT ON SOURCE TEXT. This script talks to a live API, needs a session
18
+ // token and (for the rest of the file) python3, so it cannot be executed in the
19
+ // DB-free unit gate. Source assertions are what CAN run here. They are written
20
+ // narrowly — each one names a specific defect that actually happened, and the
21
+ // resolution assertions deliberately REJECT a hardcoded id rather than merely
22
+ // requiring the field to be present, because "send goal_id" is satisfied just as
23
+ // well by a literal that goes stale the moment the version rolls. That is the
24
+ // over-broad-source-assertion trap this repo keeps relearning (learning 1000196):
25
+ // the cure is specificity, not a bigger regex.
26
+ import { strict as assert } from 'node:assert';
27
+ import { test } from 'node:test';
28
+ import fs from 'node:fs';
29
+ import path from 'node:path';
30
+ import { fileURLToPath } from 'node:url';
31
+
32
+ const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
33
+ const SH = fs.readFileSync(path.join(ROOT, 'scripts', 'gds', 'smoke-write.sh'), 'utf8');
34
+
35
+ // ---- defect 1: the create must carry a goal_id, resolved not hardcoded -----
36
+
37
+ test('the create body sends goal_id at all (the 400 that killed the smoke)', () => {
38
+ assert.match(SH, /'goal_id':/, 'POST /tasks has required goal_id since task 1003598');
39
+ });
40
+
41
+ test('goal_id is the RESOLVED id, never a literal', () => {
42
+ assert.match(SH, /'goal_id': \$\{GOAL_ID\}/, 'the create interpolates the resolved id');
43
+ assert.ok(
44
+ !/'goal_id': *[0-9]/.test(SH),
45
+ 'a numeric goal id in the body would go stale the moment the version rolls'
46
+ );
47
+ });
48
+
49
+ test('GOAL_ID is resolved at runtime, from the is_maintenance flag', () => {
50
+ assert.match(SH, /GOAL_ID=\$\(call GET "\/api\/gds\/goals\?version=\$\{VERSION_ID\}"/,
51
+ 'it asks the API for this version-s goals');
52
+ assert.match(SH, /is_maintenance/, 'and picks the maintenance goal by its flag');
53
+ // Exactly one open maintenance goal exists per version (BV1.R18). Ambiguity here
54
+ // means the instance is misconfigured, and guessing would file the smoke row into
55
+ // a real working area — so the resolution must refuse rather than pick.
56
+ assert.match(SH, /m\.length !== 1/, 'it refuses on anything but exactly one match');
57
+ });
58
+
59
+ // ---- defect 2: the throwaway task is disposed of ---------------------------
60
+
61
+ test('a passing run abandons its throwaway task', () => {
62
+ assert.match(SH, /cleanup_throwaway_task\(\) \{/, 'the cleanup exists');
63
+ assert.match(SH, /tasks\/\$\{TASK_ID\}\/abandon/, 'and it abandons the row it created');
64
+ });
65
+
66
+ test('cleanup runs ONLY on success — a failed run keeps its evidence', () => {
67
+ const guard = SH.slice(SH.indexOf('--- 5. clean up'));
68
+ assert.match(guard, /if \[\[ "\$\{fail\}" -eq 0 \]\]; then\s*\n\s*cleanup_throwaway_task/,
69
+ 'the call is gated on fail==0');
70
+ assert.match(guard, /kept for post-mortem/,
71
+ 'and a failing run says why the row is still there');
72
+ });
73
+
74
+ test('cleanup is best-effort: it can never fail a passing smoke', () => {
75
+ const fn = SH.slice(SH.indexOf('cleanup_throwaway_task() {'), SH.indexOf('--- summary ---'));
76
+ // The three ways this could turn a PASS into a FAIL, each closed:
77
+ assert.match(fn, /\|\| true/, 'the curl cannot trip set -e');
78
+ assert.ok(!/fail=1/.test(fn), 'and it never touches the fail counter');
79
+ // task.abandon is floor-metic (ADR 0152), so a Xenos/Thetes token gets a 403
80
+ // here while every assertion above genuinely passed. That must read as SKIP.
81
+ assert.match(fn, /403\)/, 'a rank refusal is reported as SKIP, not as a failure');
82
+ assert.match(fn, /000\)/, 'so is no response at all');
83
+ });
84
+
85
+ // ---- the id is spliced as CODE, so its SHAPE is a security property ---------
86
+ //
87
+ // Raised by the grader's hacker worker on the first round of this task, and it
88
+ // was right: `'goal_id': ${GOAL_ID},` lands inside a `python3 -c "..."` SOURCE
89
+ // string, unquoted. The resolver validated the goal COUNT and never the id's own
90
+ // shape, so a non-numeric id from /goals — schema drift, a misbehaving API, any
91
+ // future path where an id-like field is influenced — would have been executed as
92
+ // Python rather than read as data. Validating the count is not validating the id.
93
+
94
+ test('the resolver refuses a non-numeric goal id', () => {
95
+ const ext = SH.slice(SH.indexOf('GOAL_ID=$(call GET'), SH.indexOf("') || {"));
96
+ assert.match(ext, /\/\^\[0-9\]\+\$\//, 'the extracted id is shape-checked');
97
+ assert.match(ext, /is not numeric/, 'and says so when it refuses');
98
+ // The check must come BEFORE the value is emitted, or it guards nothing.
99
+ assert.ok(
100
+ ext.indexOf('is not numeric') < ext.lastIndexOf('process.stdout.write'),
101
+ 'the refusal precedes the write'
102
+ );
103
+ });
104
+
105
+ test('and the interpolation site re-checks it', () => {
106
+ // Belt-and-braces where the risk actually is: between resolution and the
107
+ // python3 heredoc the value passes through shell, so it is re-validated there.
108
+ assert.match(SH, /\[\[ "\$\{GOAL_ID\}" =~ \^\[0-9\]\+\$ \]\]/,
109
+ 'bash re-checks the id before the create body interpolates it');
110
+ });