fix(sys1): count project system types in the restore guard and admit the CLI verbs
Sol review sys1-review-r1 (changes 3): - P1: project_system_types joins stack-release.sh COUNTED and the T45 LISTED pin; the post-backup refusal and in-run recount tests now cover it. - P2: the CLI channel admits project.system-types and project.system-types.set; a new test drives both through run() and the real channel module (1/1 red without the fix). - P2: the #811 migration-reader pin moves to 19 steps ending 3t-3u.
This commit was merged in pull request #1890.
This commit is contained in:
1 parent
f4bfd3a744
commit
2c6556ab1e
4 files changed
+42
-6
No files matched your search
@@ -306,7 +306,7 @@ test('#811 migration readers follow the engine order and refuse malformed pairs'
|
||||
assert.notEqual(engineSource,source);
|
||||
const engine=await import(`data:text/javascript;base64,${Buffer.from(engineSource).toString('base64')}`);
|
||||
const pairs=engine.migrations.map(({from,to})=>[from,to]);
|
||||
assert.equal(pairs.length,18);assert.deepEqual(pairs.at(-1),['3s','3t']);
|
||||
assert.equal(pairs.length,19);assert.deepEqual(pairs.at(-1),['3t','3u']);
|
||||
const release=readFileSync(join(ROOT,'scripts/stack-release.sh'),'utf8');
|
||||
const fn=(name)=>{const m=release.match(new RegExp(`^${name}\\(\\) \{[\\s\\S]*?^\}`, 'm'));assert.ok(m,name);return m[0]};
|
||||
const functions=['copy_schema','target_schema','plan_steps'].map(fn).join('\n');
|
||||
@@ -885,7 +885,7 @@ const COUNTED=/^COUNTED="([^"]*)"$/m.exec(srText)[1].split(' ');
|
||||
const STORE_JS=/^STORE_JS='([\s\S]*?)'$/m.exec(srText)[1];
|
||||
// sage-1163's list 2, written out so that a table dropped from the script's COUNTED fails T45 as
|
||||
// well as T46.
|
||||
const LISTED=['role_claims','thread_claims','decision_events','messages','deliveries','task_snapshots','projects','project_archives','threads','sessions','session_defaults','thread_opens','thread_shares','instance_names','usage_records','session_ops','message_cancels','request_events','request_effects','request_snapshots','request_snapshot_events','request_project_events'];
|
||||
const LISTED=['role_claims','thread_claims','decision_events','messages','deliveries','task_snapshots','projects','project_archives','threads','sessions','session_defaults','thread_opens','thread_shares','instance_names','usage_records','session_ops','message_cancels','request_events','request_effects','request_snapshots','request_snapshot_events','request_project_events','project_system_types'];
|
||||
const LATE='2999-01-01T00:00:00.000Z';
|
||||
const addRow=(file,table,at=LATE)=>{const db=new DatabaseSync(file);db.exec(`CREATE TABLE IF NOT EXISTS ${table} (seq INTEGER PRIMARY KEY, at TEXT NOT NULL)`);db.prepare(`INSERT INTO ${table} (at) VALUES (?)`).run(at);db.close()};
|
||||
|
||||
@@ -903,7 +903,7 @@ test('T45 row 181 items 2 and 3: the restore guard counts events and every COUNT
|
||||
both(store('3f.sqlite',['events'],['events','instance_names']),'1\t1 event(s)\n');
|
||||
both(store('none.sqlite',[],['instance_names']),'0\t0 event(s)\n');
|
||||
});
|
||||
for(const x of ['instance_names','usage_records'])await t.test(`one ${x} row and no event refuses before any stop; --events 1 restores`,async(t)=>{
|
||||
for(const x of ['instance_names','usage_records','project_system_types'])await t.test(`one ${x} row and no event refuses before any stop; --events 1 restores`,async(t)=>{
|
||||
const e=await mk(t);await seed(e);
|
||||
assert.equal((await run(e,'activate',C,'acme','--migrate')).code,0);const act=pointer(e).activation;
|
||||
addRow(e.store,x);
|
||||
@@ -925,10 +925,10 @@ test('T45 row 181 items 2 and 3: the restore guard counts events and every COUNT
|
||||
assert.equal(r.code,3,r.out+r.err);assert.ok(r.err.endsWith(`stack-release: refusing restore-mismatch: cannot count the rows after ${stamp}\n`),r.err);
|
||||
assert.deepEqual(fp(e),before);assert.deepEqual(startsStops(e,n),[]);assert.equal(logLines(e).at(-1).code,'restore-mismatch');
|
||||
});
|
||||
await t.test('the in-run recount counts the listed tables too',async(t)=>{
|
||||
for(const x of ['thread_opens','project_system_types'])await t.test(`the in-run recount counts the listed tables too (${x})`,async(t)=>{
|
||||
const e=await mk(t);await seed(e);
|
||||
assert.equal((await run(e,'activate',C,'acme','--migrate')).code,0);const act=pointer(e).activation;
|
||||
fault(e,'stop-writes-row','thread_opens');
|
||||
fault(e,'stop-writes-row',x);
|
||||
const r=await run(e,'rollback','acme','--restore',act);assert.equal(r.code,1,r.out+r.err);
|
||||
assert.equal(r.err,rolledBack('post-backup-events',C));
|
||||
assert.ok(r.out.includes('; 0 event(s) were written after the backup\n'),r.out);
|
||||
|
||||
@@ -17,3 +17,37 @@ test('org installed system types and project selection reads print JSON', async
|
||||
assert.deepEqual(calls.map(x => x[0]), ['project.list', 'project.system-types']);
|
||||
assert.deepEqual(JSON.parse(output).active, ['coding']);
|
||||
});
|
||||
|
||||
// sys1-review P2: both commands go through run() and the real CLI channel module, so a verb the
|
||||
// channel does not admit fails here. A disabled channel and an agent run still refuse.
|
||||
test('project system-types and system-types-set pass the CLI channel', async t => {
|
||||
const { run } = await import('../src/cli.mjs');
|
||||
const { fixture, humanEnv, io: mkio, tmp } = await import('./helpers.mjs');
|
||||
const root = tmp(t), f = fixture(root);
|
||||
const P = '10000000-0000-4000-8000-000000000001';
|
||||
const transportFor = channels => {
|
||||
const calls = [];
|
||||
const transport = () => async (verb, args) => {
|
||||
if (verb === 'plugins') return { business: 'acme', revision: 'a'.repeat(64), plugins: { channel: channels } };
|
||||
calls.push([verb, args]);
|
||||
if (verb === 'project.list') return [{ id: P, at: '2026-11-09T00:00:00.000Z', name: 'work', repo: null }];
|
||||
if (verb === 'project.system-types') return { project: P, selected: ['coding'], active: ['coding'], inactive: [] };
|
||||
if (verb === 'project.system-types.set') return { project: P, selected: args.systemTypes, active: args.systemTypes, inactive: [] };
|
||||
throw Object.assign(new Error(verb), { code: 'bus-refused' });
|
||||
};
|
||||
return { calls, transport };
|
||||
};
|
||||
const read = transportFor(['cli']), rx = mkio(humanEnv(f.env));
|
||||
assert.equal(await run(['project', 'system-types', 'work', '--business', 'acme'], rx, { transport: read.transport }), 0, rx.err?.text);
|
||||
assert.deepEqual(read.calls, [['project.list', { includeArchived: true }], ['project.system-types', { project: P }]]);
|
||||
assert.deepEqual(JSON.parse(rx.out.text).active, ['coding']);
|
||||
const write = transportFor(['cli']), wx = mkio(humanEnv(f.env));
|
||||
assert.equal(await run(['project', 'system-types-set', 'work', '--types', 'coding,marketing', '--business', 'acme'], wx, { transport: write.transport }), 0, wx.err?.text);
|
||||
assert.deepEqual(write.calls.at(-1), ['project.system-types.set', { project: P, systemTypes: ['coding', 'marketing'] }]);
|
||||
const off = transportFor(['webui']), ox = mkio(humanEnv(f.env));
|
||||
assert.equal(await run(['project', 'system-types', 'work', '--business', 'acme'], ox, { transport: off.transport }), 3);
|
||||
assert.deepEqual(off.calls, []);
|
||||
const agent = transportFor(['cli']);
|
||||
assert.notEqual(await run(['project', 'system-types-set', 'work', '--types', 'coding', '--business', 'acme'], mkio({ ...humanEnv(f.env), CODEX_THREAD_ID: 'fixture-agent' }), { transport: agent.transport }), 0);
|
||||
assert.deepEqual(agent.calls, []);
|
||||
});
|
||||
@@ -6,6 +6,8 @@ const VERBS = new Set([
|
||||
"project.create",
|
||||
"project.archive",
|
||||
"project.unarchive",
|
||||
"project.system-types",
|
||||
"project.system-types.set",
|
||||
"thread.list",
|
||||
"thread.create",
|
||||
"thread.messages",
|
||||
|
||||
@@ -421,7 +421,7 @@ plugin_checks() {
|
||||
# Row 181: the tables a bus writer fills without an event in the same transaction, in schema
|
||||
# order. A restore drops their rows after STAMP as it drops events, so the guard counts them too.
|
||||
# BUILD.md for row 181 names each writer; the T46 guard classifies every table in the schema.
|
||||
COUNTED="role_claims thread_claims decision_events messages deliveries task_snapshots projects project_archives threads sessions session_defaults thread_opens thread_shares instance_names usage_records session_ops message_cancels request_events request_effects request_snapshots request_snapshot_events request_project_events"
|
||||
COUNTED="role_claims thread_claims decision_events messages deliveries task_snapshots projects project_archives threads sessions session_defaults thread_opens thread_shares instance_names usage_records session_ops message_cancels request_events request_effects request_snapshots request_snapshot_events request_project_events project_system_types"
|
||||
|
||||
STORE_JS='
|
||||
const {DatabaseSync}=require("node:sqlite");const {pathToFileURL}=require("node:url");
|
||||
|
||||
Reference in new issue
Block a user