PR #1916 rule: a refusal names the field or file, never a config value or a host path. readJsonFile's three refusals and parseJsonFileText now name the file by its `what` label only. Callers whose label alone is ambiguous add a name that already passed a strict pattern (business id, role name, org id, turn id, project key) or a fixed path under the data root. Exit codes, refusal codes and check order are unchanged. organization: util, access, project, config-command, library-seats, org-service deleteOrg receipt, agent-levels regex; T1 files role.mjs and organization-config.mjs change only the readJsonFile label. cli: access lock, accounts owner, host state/lock, notifier journal, notify-config, run-evidence, dev-fixture, git-socket, org-bus, org-console. discord: turn record label names the turn id. Sentinel tests (sk-test-NOT-A-SECRET-1234 as a path component) cover each changed refusal and fail on BASE; old path pins updated.
45 lines
2.9 KiB
JavaScript
45 lines
2.9 KiB
JavaScript
import { test } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { mkdtempSync, mkdirSync, writeFileSync, rmSync, readFileSync } from 'node:fs';
|
|
import { tmpdir } from 'node:os';
|
|
import { join } from 'node:path';
|
|
const secret = 'sk-test-NOT-A-SECRET-1234';
|
|
function scratch(t) { const root = mkdtempSync(join(tmpdir(), 'r4-')); t.after(() => rmSync(root, { recursive: true, force: true })); return root; }
|
|
function safe(label, Type, props = {}, omitted = secret) { return error => {
|
|
assert.ok(error instanceof Type, error.stack);
|
|
for (const [key, value] of Object.entries(props)) assert.equal(error[key], value);
|
|
assert.ok(error.message.includes(label), error.message);
|
|
assert.doesNotMatch(error.message + error.stack, /sk-test|NOT-A-SECRET|Unexpected token/);
|
|
assert.ok(!error.message.includes(omitted), error.message);
|
|
assert.equal(error.cause, undefined);
|
|
return true;
|
|
}; }
|
|
import { validateBinding } from '../src/binding.mjs';
|
|
import { createGateway } from '../src/gateway.mjs';
|
|
import { readTurn, listTurns } from '../src/journal.mjs';
|
|
import { DiscordError } from '../src/errors.mjs';
|
|
import { rawBinding } from './helpers.mjs';
|
|
for (const [field, edit] of [
|
|
['guildId', d => { d.guildId = secret; }],
|
|
['name', d => { d.name = secret + '/'; }],
|
|
['path', d => { d.tools = { roots: [{ name: 'root', path: `/x/.${secret}`, write: false }] }; }],
|
|
]) test(`binding ${field} refusal omits its configured value`, () => {
|
|
const d = rawBinding(); edit(d); assert.throws(() => validateBinding(d), safe(field, DiscordError, { exitCode: 2 }));
|
|
});
|
|
test('gateway url refusal omits its value', () => assert.throws(() => createGateway({ url: secret, token: 'fake' }), safe('url', DiscordError, { exitCode: 1 })));
|
|
for (const mode of ['read', 'list']) test(`turn ${mode} parser omits file bytes and keeps SyntaxError`, t => {
|
|
const root = join(scratch(t), secret); mkdirSync(join(root, 'turns'), { recursive: true });
|
|
const file = join(root, 'turns', 'turn-1.json'); writeFileSync(file, secret);
|
|
// The refusal names the turn by its id, never by host path (PR #1916).
|
|
assert.throws(() => mode === 'read' ? readTurn(root, 'turn-1') : listTurns(root), safe('turn record turn-1 is not valid JSON', SyntaxError, {}, file));
|
|
});
|
|
|
|
for (const duplicate of [true, false]) test(`binding approver ${duplicate ? 'duplicate' : 'invalid'} name omits user values`, t => {
|
|
const root = scratch(t), keyFile = join(root, 'fixture-key'); writeFileSync(keyFile, 'fixture\n', { mode: 0o600 });
|
|
const name = secret.toLowerCase() + (duplicate ? '' : '\u0001');
|
|
const users = [{ id: '100000000000000100', name }];
|
|
if (duplicate) users.push({ id: '100000000000000101', name });
|
|
const raw = rawBinding({ users, tools: { roots: [{ name: 'docs', path: root }], setspark: { baseUrl: 'https://api.setspark.io', keyFile, principal: 'sage' } } });
|
|
assert.throws(() => validateBinding(raw), safe(duplicate ? 'users' : 'approvers', DiscordError, { exitCode: 2 }, secret.toLowerCase()));
|
|
});
|