Files
ECC/tests/skills/typescript-patterns.test.js
T
haelyra 6b8c757e54 fix(skills): validate TypeScript example arrays and request identifiers
Verify and address PR #3809 discussions 4419228221, 4419257840 and 4419257844. Iterate string-array assertions through sparse positions, validate the declared example identifier grammar before request construction, and refresh Turkish/Chinese reviewer references. Regenerate the canonical Pi copy.

Compile and execute the actual typed fixture; keep published validation bodies aligned and prove invalid identifiers never call fetch. RED: 28 pass, 2 fail. GREEN: 30 pass, 0 fail. Strict tsc, changed ESLint/Markdownlint, Unicode/agent/skill validators and deterministic Pi checks pass. Parent independently reviewed all six paths. Registry rebinding is deferred to the combined integration.
2026-10-29 17:25:42 -07:00

325 lines
13 KiB
JavaScript

'use strict';
// Tests for the typescript-patterns skill: the SKILL.md examples must match
// their strict-TypeScript claims. Compile the real fixture and execute its
// emitted functions so the type checks and runtime regressions share one source.
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const ts = require('typescript');
const repoRoot = path.resolve(__dirname, '..', '..');
const skillPath = path.join(repoRoot, 'skills', 'typescript-patterns', 'SKILL.md');
const skill = fs.readFileSync(skillPath, 'utf8');
let passed = 0;
let failed = 0;
const pending = [];
function test(name, fn) {
try {
const result = fn();
if (result && typeof result.then === 'function') {
pending.push(result.then(
() => { passed += 1; console.log(` ✓ ${name}`); },
error => { failed += 1; console.log(` ✗ ${name}`); console.log(` ${error.message}`); }
));
return;
}
passed += 1;
console.log(` ✓ ${name}`);
} catch (error) {
failed += 1;
console.log(` ✗ ${name}`);
console.log(` ${error.message}`);
}
}
console.log('\n=== TypeScript patterns skill tests ===\n');
// ---- Skill document claims ---------------------------------------------
test('loadUser validates instead of asserting the payload', () => {
assert.doesNotMatch(skill, /response\.json\(\) as Promise<User>/);
assert.match(skill, /function isUser\(value: unknown\): value is User/);
});
test('config example validates every consumed field', () => {
assert.doesNotMatch(skill, /value as Config/);
assert.match(skill, /toPositiveInt\(raw\['timeout'\]/);
assert.match(skill, /toPositiveInt\(raw\['retries'\]/);
assert.match(skill, /DEFAULT_CONFIG/);
});
test('concurrency example narrows indexed access', () => {
assert.doesNotMatch(skill, /mapper\(items\[index\]\)/);
assert.match(skill, /Number\.isInteger\(limit\) \|\| limit < 1/);
assert.match(skill, /noUncheckedIndexedAccess/);
});
test('async error prose states the real semantics', () => {
assert.match(skill, /always surfaces throws as rejections/);
assert.doesNotMatch(skill, /lets them escape as rejected promises inconsistently/);
});
// ---- Compile and execute the actual strict TypeScript fixture ----------
const fixturePath = path.join(repoRoot, 'tests', 'fixtures', 'typescript-patterns', 'examples.ts');
const configPath = path.join(path.dirname(fixturePath), 'tsconfig.json');
const config = ts.readConfigFile(configPath, ts.sys.readFile);
const parsedConfig = ts.parseJsonConfigFileContent(config.config, ts.sys, path.dirname(configPath));
const program = ts.createProgram(parsedConfig.fileNames, parsedConfig.options);
test('actual fixture compiles with the recommended index-signature baseline', () => {
assert.strictEqual(parsedConfig.options.noPropertyAccessFromIndexSignature, true);
const diagnostics = [config.error, ...parsedConfig.errors, ...ts.getPreEmitDiagnostics(program)].filter(Boolean);
assert.deepStrictEqual(diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n')), []);
});
test('compiled validation and concurrency bodies match the published examples', () => {
const printer = ts.createPrinter({ removeComments: true });
function bodies(source, filename) {
const file = ts.createSourceFile(filename, source, ts.ScriptTarget.Latest, true);
const found = new Map();
function visit(node) {
if (ts.isFunctionDeclaration(node) && node.name && node.body) {
found.set(node.name.text, printer.printNode(ts.EmitHint.Unspecified, node.body, file));
}
ts.forEachChild(node, visit);
}
visit(file);
return found;
}
const documented = [...skill.matchAll(/```typescript\n([\s\S]*?)```/g)].map(match => match[1]).join('\n');
const docs = bodies(documented, 'skill.ts');
const fixtures = bodies(fs.readFileSync(fixturePath, 'utf8'), fixturePath);
for (const name of ['parseConfig', 'assertIsStringArray', 'assertUserId', 'isUser', 'loadUser', 'mapWithConcurrency']) {
assert.ok(docs.has(name), `${name} is missing from the skill`);
const fixtureBody = name === 'loadUser' ? fixtures.get(name).replace(/\bfetchJson\b/g, 'fetch') : fixtures.get(name);
assert.strictEqual(fixtureBody, docs.get(name), `${name} fixture drifted from the published example`);
}
});
const emitted = ts.transpileModule(fs.readFileSync(fixturePath, 'utf8'), {
compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS },
}).outputText;
const fixtureExports = {};
// Only checked-in fixture code is evaluated; no filesystem or network access.
new Function('exports', emitted)(fixtureExports);
const { parseConfig } = fixtureExports.ConfigExample;
const { assertIsStringArray } = fixtureExports.AssertionExample;
const { loadUser } = fixtureExports.UserExample;
const { mapWithConcurrency } = fixtureExports.ConcurrencyExample;
const okFetch = payload => async () => ({ ok: true, status: 200, json: async () => payload });
const badFetch = async () => ({ ok: false, status: 404, json: async () => ({}) });
// ---- parseConfig --------------------------------------------------------
test('parseConfig accepts a valid configuration', () => {
assert.deepStrictEqual(parseConfig({ timeout: 1000, retries: 5 }), { timeout: 1000, retries: 5 });
});
test('parseConfig rejects a string-valued retries field', () => {
assert.throws(() => parseConfig({ timeout: 1000, retries: '3' }), TypeError);
});
test('parseConfig applies declared defaults for missing fields', () => {
assert.deepStrictEqual(parseConfig({}), { timeout: 30_000, retries: 3 });
assert.deepStrictEqual(parseConfig(null), { timeout: 30_000, retries: 3 });
});
test('parseConfig rejects zero and negative values', () => {
assert.throws(() => parseConfig({ timeout: 0, retries: 3 }), TypeError);
assert.throws(() => parseConfig({ timeout: 1000, retries: -1 }), TypeError);
assert.throws(() => parseConfig({ timeout: 1.5, retries: 3 }), TypeError);
});
// ---- assertIsStringArray -------------------------------------------------
test('assertIsStringArray rejects wholly and partially sparse arrays', () => {
assert.throws(() => assertIsStringArray(new Array(3)), TypeError);
const partial = ['first', 'second'];
delete partial[1];
assert.throws(() => assertIsStringArray(partial), TypeError);
});
test('assertIsStringArray accepts dense strings and rejects present non-strings', () => {
assert.doesNotThrow(() => assertIsStringArray([]));
assert.doesNotThrow(() => assertIsStringArray(['', 'hello']));
for (const invalid of [['hello', undefined], ['hello', 1], null, 'hello']) {
assert.throws(() => assertIsStringArray(invalid), TypeError);
}
});
// ---- loadUser / isUser ----------------------------------------------------
test('loadUser returns a valid user', async () => {
const user = await loadUser('u_1', okFetch({ id: 'u_1', email: 'a@example.com' }));
assert.deepStrictEqual(user, { id: 'u_1', email: 'a@example.com' });
});
test('loadUser rejects invalid identifiers without making a request', async () => {
const invalidIds = ['', '../profile', '..', 'a/b', 'a\\b', '%2e%2e', 'u_1?admin=1', 'u_1#bio', 'u_1\n', 'u_\u{1F600}', 1, null];
for (const userId of invalidIds) {
let requests = 0;
const fetchJson = async () => {
requests += 1;
return { ok: true, status: 200, json: async () => ({ id: 'u_1', email: 'a@example.com' }) };
};
await assert.rejects(loadUser(userId, fetchJson), TypeError);
assert.strictEqual(requests, 0, `request made for invalid ID ${JSON.stringify(userId)}`);
}
});
test('loadUser preserves valid identifiers as one endpoint segment', async () => {
for (const userId of ['u_1', 'User-123', '42']) {
let requested;
const user = await loadUser(userId, async url => {
requested = url;
return { ok: true, status: 200, json: async () => ({ id: userId, email: 'a@example.com' }) };
});
assert.strictEqual(requested, `/users/${userId}`);
assert.strictEqual(user.id, userId);
}
});
test('loadUser rejects a response missing email', async () => {
await assert.rejects(loadUser('u_1', okFetch({ id: 'u_1' })), TypeError);
});
test('loadUser rejects fields with incorrect types', async () => {
await assert.rejects(loadUser('u_1', okFetch({ id: 1, email: 'a@example.com' })), TypeError);
await assert.rejects(loadUser('u_1', okFetch({ id: 'u_1', email: 'not-an-email' })), TypeError);
});
test('loadUser rejects null, arrays, and other malformed payloads', async () => {
await assert.rejects(loadUser('u_1', okFetch(null)), TypeError);
await assert.rejects(loadUser('u_1', okFetch([{ id: 'u_1', email: 'a@example.com' }])), TypeError);
await assert.rejects(loadUser('u_1', okFetch('oops')), TypeError);
});
test('loadUser rejects failed fetches and invalid JSON', async () => {
await assert.rejects(loadUser('u_1', badFetch), /Failed to load user/);
await assert.rejects(loadUser('u_1', async () => { throw new Error('bad json'); }), /bad json/);
});
// ---- mapWithConcurrency ----------------------------------------------------
test('mapWithConcurrency handles an empty input array', async () => {
assert.deepStrictEqual(await mapWithConcurrency([], 4, async x => x), []);
});
test('mapWithConcurrency maps with a valid limit', async () => {
assert.deepStrictEqual(await mapWithConcurrency([1, 2, 3], 2, async x => x * 2), [2, 4, 6]);
});
test('mapWithConcurrency accepts limits greater than the input length', async () => {
assert.deepStrictEqual(await mapWithConcurrency([1, 2], 10, async x => x + 1), [2, 3]);
});
test('mapWithConcurrency rejects zero and negative limits', async () => {
await assert.rejects(mapWithConcurrency([1], 0, async x => x), RangeError);
await assert.rejects(mapWithConcurrency([1], -2, async x => x), RangeError);
});
test('mapWithConcurrency rejects fractional and non-finite limits', async () => {
await assert.rejects(mapWithConcurrency([1], 1.5, async x => x), RangeError);
await assert.rejects(mapWithConcurrency([1], NaN, async x => x), RangeError);
await assert.rejects(mapWithConcurrency([1], Infinity, async x => x), RangeError);
});
test('mapWithConcurrency maps a present undefined element', async () => {
const seen = [];
const out = await mapWithConcurrency([undefined, 1], 2, async x => {
seen.push(x);
return x === undefined ? 0 : x;
});
assert.deepStrictEqual(seen, [undefined, 1]);
assert.deepStrictEqual(out, [0, 1]);
});
test('mapWithConcurrency skips only genuine sparse-array holes', async () => {
// Index 1 is a real hole; index 0 holds a present `undefined` value.
const input = new Array(3);
input[0] = undefined;
input[2] = 3;
const seen = [];
const out = await mapWithConcurrency(input, 3, async x => {
seen.push(x);
return x === undefined ? 'was-undefined' : x;
});
// The hole never reaches the mapper; the present undefined does.
assert.deepStrictEqual(seen, [undefined, 3]);
assert.strictEqual(out[0], 'was-undefined');
assert.strictEqual(out[2], 3);
assert.ok(!(1 in out), 'hole at index 1 should stay a hole in the result, not become a value');
assert.strictEqual(out.length, 3);
});
function deferred() {
let release;
const promise = new Promise(resolve => { release = resolve; });
return { promise, release };
}
test('mapWithConcurrency preserves order when mappers complete out of their input order', async () => {
const gates = [deferred(), deferred(), deferred(), deferred()];
const started = [];
const completed = [];
const resultPromise = mapWithConcurrency([0, 1, 2, 3], 4, async x => {
started.push(x);
await gates[x].promise;
completed.push(x);
return x * 10;
});
// No gate has been released yet, so having every mapper started proves
// concurrency happened before any completion.
await Promise.resolve();
assert.deepStrictEqual(started, [0, 1, 2, 3]);
// Release the gates in reverse input order and record the actual
// completion order; determinism comes from the gates, not timers.
for (let i = 3; i >= 0; i -= 1) {
gates[i].release();
}
const out = await resultPromise;
assert.deepStrictEqual(completed, [3, 2, 1, 0]);
// The completion order differs from input order, yet results still land
// in input order.
assert.deepStrictEqual(out, [0, 10, 20, 30]);
});
test('mapWithConcurrency propagates mapper rejection', async () => {
await assert.rejects(
mapWithConcurrency([1, 2, 3], 2, async x => { if (x === 2) throw new Error('boom'); return x; }),
/boom/
);
});
test('mapWithConcurrency never exceeds the configured limit', async () => {
let active = 0;
let maxActive = 0;
await mapWithConcurrency([1, 2, 3, 4, 5, 6], 2, async x => {
active += 1;
maxActive = Math.max(maxActive, active);
await new Promise(resolve => setTimeout(resolve, 5));
active -= 1;
return x;
});
assert.ok(maxActive <= 2, `peak concurrency ${maxActive} exceeded limit 2`);
assert.ok(maxActive > 1, 'workers never overlapped; pool may be serial');
});
test('async functions surface throws as rejections, sync returns do not', async () => {
const { asyncFails, lazyFails } = fixtureExports.AsyncErrorsExample;
await assert.rejects(asyncFails(), /becomes a rejection/);
assert.throws(() => lazyFails(), /throws synchronously/);
});
Promise.allSettled(pending).then(() => {
console.log(`\nPassed: ${passed}`);
console.log(`Failed: ${failed}`);
process.exit(failed > 0 ? 1 : 0);
});