mirror of
https://github.com/affaan-m/ECC.git
synced 2026-09-12 04:37:54 +02:00
test(lib): extract shared mini test runner for coordination tests (#2663)
Address CodeRabbit review on #2311: dedupe the local test(name, fn) harness and route all reporter output through a shared helper (tests/lib/helpers/mini-test-runner.js) instead of direct console.log.
This commit is contained in:
@@ -25,24 +25,14 @@ const {
|
||||
verifyDependenciesClosed,
|
||||
} = require('../../scripts/lib/github-coordination/state');
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const { test, banner, section, summary } = require('./helpers/mini-test-runner');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
console.log('\n=== parsing.js — uncovered branches ===\n');
|
||||
banner('parsing.js — uncovered branches');
|
||||
|
||||
console.log('normalizeBodyForComparison:');
|
||||
section('normalizeBodyForComparison:');
|
||||
|
||||
if (test('handles null body (uses empty string fallback)', () => {
|
||||
const result = normalizeBodyForComparison(null);
|
||||
@@ -61,7 +51,7 @@ if (test('normalizes lastSyncAt timestamps in body text', () => {
|
||||
assert.ok(!result.includes('2024-01-01'));
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nparseStringList:');
|
||||
section('parseStringList:');
|
||||
|
||||
if (test('returns empty array for null', () => {
|
||||
assert.deepStrictEqual(parseStringList(null), []);
|
||||
@@ -83,7 +73,7 @@ if (test('filters out empty parts from double-commas', () => {
|
||||
assert.deepStrictEqual(parseStringList('a,,b'), ['a', 'b']);
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nmergeIssueBody — empty body branch:');
|
||||
section('mergeIssueBody — empty body branch:');
|
||||
|
||||
if (test('returns rendered state when issue body is empty string', () => {
|
||||
const state = { status: 'available', schemaVersion: 'v1', kind: 'epic', owner: null, branch: null, validation: 'pending', review: 'not-requested', project: { state: 'backlog', fields: {} }, dependencies: [], tasks: [], labels: [], lastAction: 'sync' };
|
||||
@@ -97,9 +87,9 @@ if (test('returns rendered state when issue body is null', () => {
|
||||
assert.ok(result.includes('ecc-coordination:start'));
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\n=== state.js — uncovered branches ===\n');
|
||||
banner('state.js — uncovered branches');
|
||||
|
||||
console.log('buildIssueStateFromAction — options absent (false branches):');
|
||||
section('buildIssueStateFromAction — options absent (false branches):');
|
||||
|
||||
const baseIssue = { number: 1, labels: [], body: '' };
|
||||
const baseState = {
|
||||
@@ -133,7 +123,7 @@ if (test('buildIssueStateFromAction — currentState.tasks not array → re-extr
|
||||
assert.ok(Array.isArray(result.tasks));
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\ndesiredLabelsForState — uncovered status/review/validation branches:');
|
||||
section('desiredLabelsForState — uncovered status/review/validation branches:');
|
||||
|
||||
if (test('includes published label for status "published"', () => {
|
||||
const labels = desiredLabelsForState({ status: 'published' });
|
||||
@@ -160,7 +150,7 @@ if (test('includes review-changes-requested label for review "changes-requested"
|
||||
assert.ok(labels.includes('coordination:review-changes-requested'));
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nmapStateToWorkItemStatus — uncovered switch cases:');
|
||||
section('mapStateToWorkItemStatus — uncovered switch cases:');
|
||||
|
||||
if (test('"validated" → "in-progress"', () => {
|
||||
assert.strictEqual(mapStateToWorkItemStatus('validated'), 'in-progress');
|
||||
@@ -182,7 +172,7 @@ if (test('"unknown-state" → "open" (default)', () => {
|
||||
assert.strictEqual(mapStateToWorkItemStatus('unknown-state'), 'open');
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nassertIssueClaimable:');
|
||||
section('assertIssueClaimable:');
|
||||
|
||||
if (test('throws when issue is not open', () => {
|
||||
assert.throws(
|
||||
@@ -204,7 +194,7 @@ if (test('does not throw for open, unclaimed issue', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nverifyDependenciesClosed:');
|
||||
section('verifyDependenciesClosed:');
|
||||
|
||||
if (test('returns empty array when dependencyNumbers is not an array', () => {
|
||||
const result = verifyDependenciesClosed('r/r', null, {}, []);
|
||||
@@ -240,7 +230,7 @@ if (test('warns via stderr and skips when dependency issue is not in allIssues l
|
||||
assert.ok(stderrOutput.includes('dependency issue #5 not found'), `expected stderr warning, got: ${stderrOutput}`);
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\ndefaultCoordinationState — edge branches:');
|
||||
section('defaultCoordinationState — edge branches:');
|
||||
|
||||
if (test('owner is null when issue has no author', () => {
|
||||
const result = defaultCoordinationState({ number: 1, labels: [] });
|
||||
@@ -264,5 +254,4 @@ if (test('handles null issue', () => {
|
||||
assert.deepStrictEqual(result.tasks, []);
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log(`\n Results: ${passed} passed, ${failed} failed`);
|
||||
if (failed > 0) process.exit(1);
|
||||
summary(passed, failed);
|
||||
|
||||
@@ -19,17 +19,7 @@ const {
|
||||
DEFAULT_SECTION_MARKER,
|
||||
} = require('../../scripts/lib/github-coordination/policy');
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const { test, banner, section, summary } = require('./helpers/mini-test-runner');
|
||||
|
||||
function withTempDir(fn) {
|
||||
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ecc-policy-test-'));
|
||||
@@ -51,9 +41,9 @@ function writeConfig(tmpDir, content) {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
console.log('\n=== Testing github-coordination/policy.js ===\n');
|
||||
banner('Testing github-coordination/policy.js');
|
||||
|
||||
console.log('loadPolicy — no config file:');
|
||||
section('loadPolicy — no config file:');
|
||||
|
||||
if (test('returns default policy when no config file exists in rootDir', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -74,7 +64,7 @@ if (test('returns default policy when custom configPath does not exist', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nloadPolicy — configPath argument:');
|
||||
section('loadPolicy — configPath argument:');
|
||||
|
||||
if (test('uses configPath when explicitly provided', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -95,7 +85,7 @@ if (test('falls back to rootDir config file when configPath is null', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nloadPolicy — invalid JSON:');
|
||||
section('loadPolicy — invalid JSON:');
|
||||
|
||||
if (test('throws on invalid JSON', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -104,7 +94,7 @@ if (test('throws on invalid JSON', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nloadPolicy — non-object JSON:');
|
||||
section('loadPolicy — non-object JSON:');
|
||||
|
||||
if (test('throws when top-level JSON is null', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -127,7 +117,7 @@ if (test('throws when top-level JSON is a string', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nloadPolicy — labels merging:');
|
||||
section('loadPolicy — labels merging:');
|
||||
|
||||
if (test('merges labels when parsed.labels is a plain object', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -162,7 +152,7 @@ if (test('falls back to empty labels when parsed.labels is a string', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nloadPolicy — review merging:');
|
||||
section('loadPolicy — review merging:');
|
||||
|
||||
if (test('merges review when parsed.review is a plain object', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -197,7 +187,7 @@ if (test('falls back when parsed.review is an array', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nloadPolicy — validation merging:');
|
||||
section('loadPolicy — validation merging:');
|
||||
|
||||
if (test('merges validation when parsed.validation is a plain object', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -215,7 +205,7 @@ if (test('falls back when parsed.validation is not an object', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nloadPolicy — branchModel merging:');
|
||||
section('loadPolicy — branchModel merging:');
|
||||
|
||||
if (test('merges branchModel when parsed.branchModel is a plain object', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -234,7 +224,7 @@ if (test('falls back when parsed.branchModel is not an object', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nloadPolicy — project merging:');
|
||||
section('loadPolicy — project merging:');
|
||||
|
||||
if (test('merges project when parsed.project is a plain object', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -261,7 +251,7 @@ if (test('falls back when parsed.project is null', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nloadPolicy — project.fieldNames merging:');
|
||||
section('loadPolicy — project.fieldNames merging:');
|
||||
|
||||
if (test('merges fieldNames when project.fieldNames is a plain object', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -296,7 +286,7 @@ if (test('falls back when project.fieldNames is an array', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nloadPolicy — sourcePath:');
|
||||
section('loadPolicy — sourcePath:');
|
||||
|
||||
if (test('sets sourcePath to the resolved config file path', () => {
|
||||
withTempDir(tmpDir => {
|
||||
@@ -306,5 +296,4 @@ if (test('sets sourcePath to the resolved config file path', () => {
|
||||
});
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log(`\n Results: ${passed} passed, ${failed} failed`);
|
||||
if (failed > 0) process.exit(1);
|
||||
summary(passed, failed);
|
||||
|
||||
@@ -16,17 +16,7 @@ const {
|
||||
|
||||
const { DEFAULT_SCHEMA_VERSION, DEFAULT_POLICY } = require('../../scripts/lib/github-coordination/policy');
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(` ✓ ${name}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
console.log(` ✗ ${name}`);
|
||||
console.log(` Error: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
const { test, testAsync, banner, section, summary, fatal } = require('./helpers/mini-test-runner');
|
||||
|
||||
function makeStore() {
|
||||
const calls = [];
|
||||
@@ -42,15 +32,15 @@ function makeStore() {
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
|
||||
console.log('\n=== Testing github-coordination/store.js ===\n');
|
||||
banner('Testing github-coordination/store.js');
|
||||
|
||||
console.log('epicWorkItemId:');
|
||||
section('epicWorkItemId:');
|
||||
|
||||
if (test('produces a stable ID from repo and issue number', () => {
|
||||
assert.strictEqual(epicWorkItemId('acme/my-repo', 42), 'github-acme-my-repo-epic-42');
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nupsertCoordinationWorkItem — null store:');
|
||||
section('upsertCoordinationWorkItem — null store:');
|
||||
|
||||
if (test('returns null when store is null', () => {
|
||||
const result = upsertCoordinationWorkItem(null, 'r/r', { number: 1 }, {}, 'sync');
|
||||
@@ -62,7 +52,7 @@ if (test('returns null when store is undefined', () => {
|
||||
assert.strictEqual(result, null);
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nupsertCoordinationWorkItem — with store:');
|
||||
section('upsertCoordinationWorkItem — with store:');
|
||||
|
||||
if (test('passes schemaVersion from state when present', () => {
|
||||
const store = makeStore();
|
||||
@@ -179,30 +169,17 @@ if (test('sets sessionId to null when options.sessionId absent', () => {
|
||||
assert.strictEqual(store.calls[0].sessionId, null);
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log('\nopenStore — dbPath: false:');
|
||||
section('openStore — dbPath: false:');
|
||||
|
||||
async function runAsyncTests() {
|
||||
let asyncPassed = 0;
|
||||
let asyncFailed = 0;
|
||||
|
||||
try {
|
||||
if (await testAsync('returns null when dbPath is false', async () => {
|
||||
const result = await openStore({ dbPath: false });
|
||||
assert.strictEqual(result, null);
|
||||
console.log(' ✓ returns null when dbPath is false');
|
||||
asyncPassed++;
|
||||
} catch (err) {
|
||||
console.log(' ✗ returns null when dbPath is false');
|
||||
console.log(` Error: ${err.message}`);
|
||||
asyncFailed++;
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
const totalPassed = passed + asyncPassed;
|
||||
const totalFailed = failed + asyncFailed;
|
||||
console.log(`\n Results: ${totalPassed} passed, ${totalFailed} failed`);
|
||||
if (totalFailed > 0) process.exit(1);
|
||||
summary(passed, failed);
|
||||
}
|
||||
|
||||
runAsyncTests().catch(err => {
|
||||
console.error(`Unexpected async test failure: ${err.message}`);
|
||||
process.exit(1);
|
||||
fatal(`Unexpected async test failure: ${err.message}`);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Shared mini test harness for standalone tests/lib/*.test.js scripts.
|
||||
*
|
||||
* Centralizes test execution and console reporting so individual test
|
||||
* files don't duplicate the runner or log directly.
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function report(message) {
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
report(` ✓ ${name}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
report(` ✗ ${name}`);
|
||||
report(` Error: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function testAsync(name, fn) {
|
||||
try {
|
||||
await fn();
|
||||
report(` ✓ ${name}`);
|
||||
return true;
|
||||
} catch (err) {
|
||||
report(` ✗ ${name}`);
|
||||
report(` Error: ${err.message}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function banner(title) {
|
||||
report(`\n=== ${title} ===`);
|
||||
}
|
||||
|
||||
function section(label) {
|
||||
report(`\n${label}`);
|
||||
}
|
||||
|
||||
function summary(passed, failed) {
|
||||
report(`\n Results: ${passed} passed, ${failed} failed`);
|
||||
if (failed > 0) process.exit(1);
|
||||
}
|
||||
|
||||
function fatal(message) {
|
||||
console.error(message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
module.exports = { test, testAsync, banner, section, summary, fatal };
|
||||
Reference in New Issue
Block a user