PSA/tools/workflow-harness/tests/runner-stubbed.test.cjs
Hermes 284313f908
Some checks are pending
Bidi Control Character Guard / bidi-control-guard (push) Waiting to run
Circular Dependency Check / Check for new circular dependencies (push) Waiting to run
Citus Migration Smoke / Combined migrations on single-node Citus (push) Waiting to run
E2E Fresh Install Tests / fresh-install-e2e (push) Waiting to run
ext-v2 guardrails / Run ext-v2 guard and ESLint (push) Waiting to run
Integration Tests / Check for relevant changes (push) Waiting to run
Integration Tests / ${{ (github.event_name == 'schedule' || github.event.inputs.suite == 'full') && 'Full integration suite' || 'Tier-1 integration subset' }} (push) Blocked by required conditions
Mobile checks / Mobile lint + typecheck (push) Waiting to run
Mobile checks / Mobile unit tests (push) Waiting to run
Mobile checks / Mobile dependency audit (report) (push) Waiting to run
Mobile checks / Mobile reproducibility checks (push) Waiting to run
Secrets guard (env backups) / Ensure no tracked env backup files (push) Waiting to run
Temporal Readiness / fast-readiness (push) Waiting to run
Temporal Readiness / docker-parity (push) Waiting to run
TypeScript Type Check / Nx affected typecheck (push) Waiting to run
Unit Tests / Skipped-test budget (push) Waiting to run
Unit Tests / Nx affected unit tests (push) Waiting to run
Unit Tests / Server unit coverage (informational) (push) Waiting to run
Validate Tenant Management Schema / Check for relevant changes (push) Waiting to run
Validate Tenant Management Schema / Validate Tenant Management Schema (push) Blocked by required conditions
EE Workflows Build Guard / ee-workflows-build-guard (push) Waiting to run
Initial import of AlgaPSA codebase from PSA server
Excluded: .git, node_modules, secrets/, compose.env, assemblyscript tgz

Source: /opt/alga-psa on psa.joliet.tech
2026-06-22 16:12:17 -05:00

503 lines
17 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const os = require('node:os');
const path = require('node:path');
function writeFixture({ name, bundle, testSource }) {
const dir = fs.mkdtempSync(path.join(os.tmpdir(), `workflow-harness-${name}-`));
fs.writeFileSync(path.join(dir, 'bundle.json'), JSON.stringify(bundle, null, 2), 'utf8');
fs.writeFileSync(path.join(dir, 'test.cjs'), testSource, 'utf8');
return {
dir,
bundlePath: path.join(dir, 'bundle.json'),
testPath: path.join(dir, 'test.cjs')
};
}
function loadHarnessWithStubs(stubs) {
const harnessRoot = path.resolve(__dirname, '..');
const runPath = path.join(harnessRoot, 'run.cjs');
const depPaths = {
db: path.join(harnessRoot, 'lib', 'db.cjs'),
http: path.join(harnessRoot, 'lib', 'http.cjs'),
workflow: path.join(harnessRoot, 'lib', 'workflow.cjs'),
runs: path.join(harnessRoot, 'lib', 'runs.cjs'),
};
const saved = {};
for (const [key, p] of Object.entries(depPaths)) {
saved[p] = require.cache[p];
if (stubs[key]) {
require.cache[p] = { id: p, filename: p, loaded: true, exports: stubs[key] };
}
}
delete require.cache[runPath];
// eslint-disable-next-line global-require, import/no-dynamic-require
const mod = require(runPath);
return {
mod,
restore() {
delete require.cache[runPath];
for (const p of Object.values(depPaths)) {
if (saved[p]) require.cache[p] = saved[p];
else delete require.cache[p];
}
}
};
}
test('T004: imports bundle with --force and reports workflow id/key used', async () => {
const importCalls = [];
const { dir, bundlePath, testPath } = writeFixture({
name: 't004',
bundle: {
format: 'alga-psa.workflow-bundle',
formatVersion: 1,
exportedAt: new Date().toISOString(),
workflows: [{ key: 'fixture.t004', metadata: {}, dependencies: { actions: [], nodeTypes: [], schemaRefs: [] }, draft: { draftVersion: 1, definition: {} }, publishedVersions: [] }]
},
testSource: `
module.exports = async (ctx) => {
ctx.expect.equal(ctx.workflow.key, 'fixture.t004', 'workflow key');
ctx.expect.equal(ctx.workflow.id, 'wf-123', 'workflow id');
};
`
});
const harness = loadHarnessWithStubs({
http: { createHttpClient: () => ({ request: async () => ({ json: {} }) }) },
db: { createDbClient: async () => ({ query: async () => [], close: async () => {} }) },
workflow: {
importWorkflowBundleV1: async ({ force }) => {
importCalls.push({ force });
return { createdWorkflows: [{ key: 'fixture.t004', workflowId: 'wf-123' }] };
},
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => {
throw new Error('waitForRun should not be called for this test');
},
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
const res = await runFixture({
testDir: dir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
assert.equal(res.state.workflowId, 'wf-123');
assert.equal(res.state.workflowKey, 'fixture.t004');
assert.deepEqual(importCalls, [{ force: true }]);
} finally {
harness.restore();
}
});
test('T005: surfaces thrown error as FAIL and writes stack trace artifacts', async () => {
const { dir, bundlePath, testPath } = writeFixture({
name: 't005',
bundle: {
format: 'alga-psa.workflow-bundle',
formatVersion: 1,
exportedAt: new Date().toISOString(),
workflows: [{ key: 'fixture.t005', metadata: {}, dependencies: { actions: [], nodeTypes: [], schemaRefs: [] }, draft: { draftVersion: 1, definition: {} }, publishedVersions: [] }]
},
testSource: `
module.exports = async () => {
throw new Error('boom');
};
`
});
test('T006: waitForRun timeout produces helpful diagnostic in artifacts', async () => {
const { dir, bundlePath, testPath } = writeFixture({
name: 't006',
bundle: {
format: 'alga-psa.workflow-bundle',
formatVersion: 1,
exportedAt: new Date().toISOString(),
workflows: [{ key: 'fixture.t006', metadata: {}, dependencies: { actions: [], nodeTypes: [], schemaRefs: [] }, draft: { draftVersion: 1, definition: {} }, publishedVersions: [] }]
},
testSource: `
module.exports = async (ctx) => {
await ctx.waitForRun({ startedAfter: ctx.triggerStartedAt, timeoutMs: 5 });
};
`
});
const timeoutError = new Error('Timed out waiting for workflow run');
timeoutError.details = { lastSeen: null, recentRuns: [] };
const harness = loadHarnessWithStubs({
http: { createHttpClient: () => ({ request: async () => ({ json: {} }) }) },
db: { createDbClient: async () => ({ query: async () => [], close: async () => {} }) },
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.t006', workflowId: 'wf-006' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => {
throw timeoutError;
},
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await assert.rejects(
() =>
runFixture({
testDir: dir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
}),
(err) => {
assert.ok(err.artifactsDir, 'expected err.artifactsDir to be set');
const ctxPath = path.join(err.artifactsDir, 'failure.context.json');
const parsed = JSON.parse(fs.readFileSync(ctxPath, 'utf8'));
assert.equal(parsed.error.message, 'Timed out waiting for workflow run');
assert.deepEqual(parsed.error.details, { lastSeen: null, recentRuns: [] });
return true;
}
);
} finally {
harness.restore();
}
});
test('T007: captures run and step status summary on success', async () => {
const { dir, bundlePath, testPath } = writeFixture({
name: 't007',
bundle: {
format: 'alga-psa.workflow-bundle',
formatVersion: 1,
exportedAt: new Date().toISOString(),
workflows: [{ key: 'fixture.t007', metadata: {}, dependencies: { actions: [], nodeTypes: [], schemaRefs: [] }, draft: { draftVersion: 1, definition: {} }, publishedVersions: [] }]
},
testSource: `
module.exports = async (ctx) => {
const run = await ctx.waitForRun({ startedAfter: ctx.triggerStartedAt, timeoutMs: 5 });
ctx.expect.equal(run.status, 'SUCCEEDED', 'run status');
};
`
});
const steps = [
{ step_id: 's1', run_id: 'run-007', step_path: '/0', definition_step_id: 'a', status: 'SUCCEEDED', attempt: 1 }
];
const harness = loadHarnessWithStubs({
http: { createHttpClient: () => ({ request: async () => ({ json: {} }) }) },
db: { createDbClient: async () => ({ query: async () => [], close: async () => {} }) },
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.t007', workflowId: 'wf-007' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-007', status: 'SUCCEEDED' }),
getRunSteps: async () => steps,
getRunLogs: async () => [],
summarizeSteps: (s) => ({ counts: { SUCCEEDED: s.length }, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
const res = await runFixture({
testDir: dir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
assert.deepEqual(res.state.run, { run_id: 'run-007', status: 'SUCCEEDED' });
assert.deepEqual(res.state.steps, steps);
} finally {
harness.restore();
}
});
test('T008: prints single-line PASS/FAIL summary and correct exit code', async () => {
const { dir } = writeFixture({
name: 't008',
bundle: {
format: 'alga-psa.workflow-bundle',
formatVersion: 1,
exportedAt: new Date().toISOString(),
workflows: [{ key: 'fixture.t008', metadata: {}, dependencies: { actions: [], nodeTypes: [], schemaRefs: [] }, draft: { draftVersion: 1, definition: {} }, publishedVersions: [] }]
},
testSource: `module.exports = async () => {};`
});
const harness = loadHarnessWithStubs({
http: { createHttpClient: () => ({ request: async () => ({ json: {} }) }) },
db: { createDbClient: async () => ({ query: async () => [], close: async () => {} }) },
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.t008', workflowId: 'wf-008' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-008', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runCliOnceForTests } = harness.mod;
const res = await runCliOnceForTests([
'--test',
dir,
'--base-url',
'http://localhost:3010',
'--tenant',
'tenant',
'--cookie',
'cookie',
'--force',
'--timeout-ms',
'1000'
]);
assert.equal(res.exitCode, 0);
assert.match(res.stdout[0], /^PASS workflow-harness-t008-/);
} finally {
harness.restore();
}
});
test('T010: supports --debug to print verbose logs', async () => {
const { dir, bundlePath, testPath } = writeFixture({
name: 't010',
bundle: {
format: 'alga-psa.workflow-bundle',
formatVersion: 1,
exportedAt: new Date().toISOString(),
workflows: [{ key: 'fixture.t010', metadata: {}, dependencies: { actions: [], nodeTypes: [], schemaRefs: [] }, draft: { draftVersion: 1, definition: {} }, publishedVersions: [] }]
},
testSource: `module.exports = async () => {};`
});
const harness = loadHarnessWithStubs({
http: { createHttpClient: () => ({ request: async () => ({ json: {} }) }) },
db: { createDbClient: async () => ({ query: async () => [], close: async () => {} }) },
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.t010', workflowId: 'wf-010' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-010', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
const captured = [];
const orig = console.error;
console.error = (...args) => captured.push(args.join(' '));
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: dir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: true,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
console.error = orig;
harness.restore();
}
assert.ok(captured.some((l) => l.includes('[harness] importSummary')), 'expected importSummary debug log');
assert.ok(captured.some((l) => l.includes('[harness] workflow')), 'expected workflow debug log');
});
test('T011: runs registered cleanup hooks on PASS and on FAIL', async () => {
const cleanupDir = fs.mkdtempSync(path.join(os.tmpdir(), 'workflow-harness-cleanup-'));
const passMarker = path.join(cleanupDir, 'pass.txt');
const failMarker = path.join(cleanupDir, 'fail.txt');
const passFixture = writeFixture({
name: 't011-pass',
bundle: {
format: 'alga-psa.workflow-bundle',
formatVersion: 1,
exportedAt: new Date().toISOString(),
workflows: [{ key: 'fixture.t011', metadata: {}, dependencies: { actions: [], nodeTypes: [], schemaRefs: [] }, draft: { draftVersion: 1, definition: {} }, publishedVersions: [] }]
},
testSource: `
const fs = require('node:fs');
module.exports = async (ctx) => {
ctx.onCleanup(async () => fs.writeFileSync(${JSON.stringify(passMarker)}, 'ok', 'utf8'));
};
`
});
const failFixture = writeFixture({
name: 't011-fail',
bundle: {
format: 'alga-psa.workflow-bundle',
formatVersion: 1,
exportedAt: new Date().toISOString(),
workflows: [{ key: 'fixture.t011', metadata: {}, dependencies: { actions: [], nodeTypes: [], schemaRefs: [] }, draft: { draftVersion: 1, definition: {} }, publishedVersions: [] }]
},
testSource: `
const fs = require('node:fs');
module.exports = async (ctx) => {
ctx.onCleanup(async () => fs.writeFileSync(${JSON.stringify(failMarker)}, 'ok', 'utf8'));
throw new Error('boom');
};
`
});
const baseStubs = {
http: { createHttpClient: () => ({ request: async () => ({ json: {} }) }) },
db: { createDbClient: async () => ({ query: async () => [], close: async () => {} }) },
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.t011', workflowId: 'wf-011' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-011', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
};
const harness = loadHarnessWithStubs(baseStubs);
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: passFixture.dir,
bundlePath: passFixture.bundlePath,
testPath: passFixture.testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
assert.ok(fs.existsSync(passMarker), 'expected pass cleanup marker to be written');
await assert.rejects(
() =>
runFixture({
testDir: failFixture.dir,
bundlePath: failFixture.bundlePath,
testPath: failFixture.testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
}),
/boom/
);
assert.ok(fs.existsSync(failMarker), 'expected fail cleanup marker to be written');
} finally {
harness.restore();
}
});
const harness = loadHarnessWithStubs({
http: { createHttpClient: () => ({ request: async () => ({ json: {} }) }) },
db: { createDbClient: async () => ({ query: async () => [], close: async () => {} }) },
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.t005', workflowId: 'wf-005' }] }),
exportWorkflowBundleV1: async () => ({ exported: true })
},
runs: {
waitForRun: async () => {
throw new Error('waitForRun should not be called for this test');
},
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await assert.rejects(
() =>
runFixture({
testDir: dir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
}),
(err) => {
assert.match(String(err.message), /boom/);
assert.ok(err.artifactsDir, 'expected err.artifactsDir to be set');
const ctxPath = path.join(err.artifactsDir, 'failure.context.json');
const errPath = path.join(err.artifactsDir, 'failure.error.txt');
assert.ok(fs.existsSync(ctxPath), 'expected failure.context.json');
assert.ok(fs.existsSync(errPath), 'expected failure.error.txt');
assert.match(fs.readFileSync(errPath, 'utf8'), /boom/);
return true;
}
);
} finally {
harness.restore();
}
});