PSA/tools/workflow-harness/tests/fixture-golden.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

1419 lines
53 KiB
JavaScript

const test = require('node:test');
const assert = require('node:assert/strict');
const path = require('node:path');
const os = require('node:os');
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('T020: ticket-created-hello fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-created-hello');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
return { json: {} };
}
})
},
db: { createDbClient: async () => ({ query: async () => [], close: async () => {} }) },
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-created-hello', workflowId: 'wf-020' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-020', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
}
assert.equal(requests.length, 1);
assert.equal(requests[0].path, '/api/workflow/events');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.json.eventName, 'TICKET_CREATED');
assert.equal(requests[0].opts.json.payloadSchemaRef, 'payload.TicketCreated.v1');
});
test('T100: ticket-created-triage-comment fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-created-triage-comment');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-100' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-100' }];
if (sql.includes('from boards')) return [{ board_id: 'board-100' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-100' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-100' }];
if (sql.includes('from comments')) {
return [
{
comment_id: 'comment-100',
note: '[fixture ticket-created-triage-comment] ticketId=ticket-100',
is_internal: true,
metadata: null
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-created-triage-comment', workflowId: 'wf-100' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-100', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 2);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[1].path, '/api/v1/tickets/ticket-100');
assert.equal(requests[1].opts.method, 'DELETE');
assert.equal(requests[1].opts.headers['x-api-key'], 'api-key');
});
test('T101: ticket-created-auto-assign-by-priority fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-created-auto-assign-by-priority');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-101' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-101' }];
if (sql.includes('from boards')) return [{ board_id: 'board-101' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-101' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-101' }];
if (sql.includes('from users')) return [{ user_id: 'user-101' }];
if (sql.includes('select assigned_to from tickets')) return [{ assigned_to: 'user-101' }];
if (sql.includes('from comments')) {
return [
{
comment_id: 'comment-101',
note: '[fixture ticket-created-auto-assign-by-priority] assigned_to=user-101',
is_internal: true
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-created-auto-assign-by-priority', workflowId: 'wf-101' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-101', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 2);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[0].opts.json.attributes.fixture_priority, 'high');
assert.equal(requests[0].opts.json.attributes.fixture_assignee_user_id, 'user-101');
assert.equal(requests[1].path, '/api/v1/tickets/ticket-101');
assert.equal(requests[1].opts.method, 'DELETE');
assert.equal(requests[1].opts.headers['x-api-key'], 'api-key');
});
test('T102: ticket-created-vip-notify fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-created-vip-notify');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-102' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-102' }];
if (sql.includes('from boards')) return [{ board_id: 'board-102' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-102' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-102' }];
if (sql.includes('from users')) return [{ user_id: 'user-102' }];
if (sql.includes('from internal_notifications')) {
return [
{
internal_notification_id: 'notif-102',
title: '[fixture ticket-created-vip-notify] VIP ticket created',
message: 'ticketId=ticket-102',
template_name: 'workflow-custom',
is_read: false
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-created-vip-notify', workflowId: 'wf-102' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-102', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 2);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[0].opts.json.attributes.fixture_is_vip, true);
assert.equal(requests[0].opts.json.attributes.fixture_notify_user_id, 'user-102');
assert.equal(requests[1].path, '/api/v1/tickets/ticket-102');
assert.equal(requests[1].opts.method, 'DELETE');
assert.equal(requests[1].opts.headers['x-api-key'], 'api-key');
});
test('T103: ticket-created-outage-escalate fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-created-outage-escalate');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-103' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-103' }];
if (sql.includes('from boards')) return [{ board_id: 'board-103' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-103' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-103' }];
if (sql.includes('from users')) return [{ user_id: 'user-103' }];
if (sql.includes('select attributes from tickets')) {
return [{ attributes: { fixture_escalated: true } }];
}
if (sql.includes('from internal_notifications')) {
return [
{
internal_notification_id: 'notif-103',
title: '[fixture ticket-created-outage-escalate] Outage escalation',
message: 'ticketId=ticket-103'
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-created-outage-escalate', workflowId: 'wf-103' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-103', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 2);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[0].opts.json.attributes.fixture_is_outage, true);
assert.equal(requests[0].opts.json.attributes.fixture_notify_user_id, 'user-103');
assert.equal(requests[1].path, '/api/v1/tickets/ticket-103');
assert.equal(requests[1].opts.method, 'DELETE');
assert.equal(requests[1].opts.headers['x-api-key'], 'api-key');
});
test('T104: ticket-created-create-project-task fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-created-create-project-task');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/projects' && opts?.method === 'POST') {
return { json: { data: { project_id: 'project-104' } } };
}
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-104' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-104' }];
if (sql.includes('from boards')) return [{ board_id: 'board-104' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-104' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-104' }];
if (sql.includes('from project_tasks')) {
return [{ task_id: 'task-104', task_name: '[fixture ticket-created-create-project-task] Follow-up for ticketId=ticket-104' }];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-created-create-project-task', workflowId: 'wf-104' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-104', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 4);
assert.equal(requests[0].path, '/api/v1/projects');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[1].path, '/api/v1/tickets');
assert.equal(requests[1].opts.method, 'POST');
assert.equal(requests[1].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[1].opts.json.attributes.fixture_project_id, 'project-104');
assert.equal(requests[2].path, '/api/v1/tickets/ticket-104');
assert.equal(requests[2].opts.method, 'DELETE');
assert.equal(requests[2].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[3].path, '/api/v1/projects/project-104');
assert.equal(requests[3].opts.method, 'DELETE');
assert.equal(requests[3].opts.headers['x-api-key'], 'api-key');
});
test('T105: ticket-created-assign-trycatch fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-created-assign-trycatch');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-105' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-105' }];
if (sql.includes('from boards')) return [{ board_id: 'board-105' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-105' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-105' }];
if (sql.includes('from users')) return [{ user_id: 'user-105' }];
if (sql.includes('from comments')) {
return [
{
comment_id: 'comment-105',
note: '[fixture ticket-created-assign-trycatch] ticketId=ticket-105 error=User not found',
is_internal: true
}
];
}
if (sql.includes('from internal_notifications')) {
return [
{
internal_notification_id: 'notif-105',
title: '[fixture ticket-created-assign-trycatch] Assign failed',
message: 'ticketId=ticket-105 error=User not found'
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-created-assign-trycatch', workflowId: 'wf-105' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-105', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 2);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[0].opts.json.attributes.fixture_notify_user_id, 'user-105');
assert.ok(requests[0].opts.json.attributes.fixture_bad_assignee_user_id);
assert.equal(requests[1].path, '/api/v1/tickets/ticket-105');
assert.equal(requests[1].opts.method, 'DELETE');
assert.equal(requests[1].opts.headers['x-api-key'], 'api-key');
});
test('T106: ticket-created-notify-multiple fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-created-notify-multiple');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-106' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text, params) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-106' }];
if (sql.includes('from boards')) return [{ board_id: 'board-106' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-106' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-106' }];
if (sql.includes('select user_id') && sql.includes('from users')) {
return [{ user_id: 'user-106-1' }, { user_id: 'user-106-2' }];
}
if (sql.includes('from internal_notifications')) {
const userId = params?.[1];
return [
{
internal_notification_id: `notif-106-${userId}`,
title: '[fixture ticket-created-notify-multiple] Ticket created',
message: 'ticketId=ticket-106'
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-created-notify-multiple', workflowId: 'wf-106' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-106', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 2);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.deepEqual(requests[0].opts.json.attributes.fixture_notify_user_ids, ['user-106-1', 'user-106-2']);
assert.equal(requests[1].path, '/api/v1/tickets/ticket-106');
assert.equal(requests[1].opts.method, 'DELETE');
assert.equal(requests[1].opts.headers['x-api-key'], 'api-key');
});
test('T107: ticket-created-ignore-system fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-created-ignore-system');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
return { json: {} };
}
})
},
db: { createDbClient: async () => ({ query: async () => [], close: async () => {} }) },
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-created-ignore-system', workflowId: 'wf-107' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-107', status: 'SUCCEEDED' }),
getRunSteps: async () => [
{ step_id: 's1', run_id: 'run-107', step_path: '/0', definition_step_id: 'state-ignore-system', status: 'SUCCEEDED', attempt: 1 },
{ step_id: 's2', run_id: 'run-107', step_path: '/1/then/1', definition_step_id: 'return-system', status: 'SUCCEEDED', attempt: 1 }
],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
}
assert.equal(requests.length, 1);
assert.equal(requests[0].path, '/api/workflow/events');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.json.eventName, 'TICKET_CREATED');
assert.equal(requests[0].opts.json.payloadSchemaRef, 'payload.TicketCreated.v1');
assert.equal(requests[0].opts.json.payload.createdByUserId, '00000000-0000-0000-0000-000000000000');
});
test('T108: ticket-assigned-acknowledge fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-assigned-acknowledge');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-108' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-108' }];
if (sql.includes('from boards')) return [{ board_id: 'board-108' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-108' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-108' }];
if (sql.includes('from comments')) {
return [
{
comment_id: 'comment-108',
note: '[fixture ticket-assigned-acknowledge] Assigned ticketId=ticket-108',
is_internal: false
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-assigned-acknowledge', workflowId: 'wf-108' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-108', status: 'SUCCEEDED' }),
getRunSteps: async () => [
{ step_id: 's1', run_id: 'run-108', step_path: '/0', definition_step_id: 'add-public-comment', status: 'SUCCEEDED', attempt: 1 },
{ step_id: 's2', run_id: 'run-108', step_path: '/1', definition_step_id: 'send-email', status: 'SUCCEEDED', attempt: 1 }
],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 3);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[1].path, '/api/workflow/events');
assert.equal(requests[1].opts.method, 'POST');
assert.equal(requests[1].opts.json.eventName, 'TICKET_ASSIGNED');
assert.equal(requests[1].opts.json.payloadSchemaRef, 'payload.TicketAssigned.v1');
assert.equal(requests[1].opts.json.payload.ticketId, 'ticket-108');
assert.equal(requests[2].path, '/api/v1/tickets/ticket-108');
assert.equal(requests[2].opts.method, 'DELETE');
assert.equal(requests[2].opts.headers['x-api-key'], 'api-key');
});
test('T109: ticket-unassigned-return-to-triage fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-unassigned-return-to-triage');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-109' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-109' }];
if (sql.includes('from boards')) return [{ board_id: 'board-109' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-109' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-109' }];
if (sql.includes('from users')) return [{ user_id: 'user-109' }];
if (sql.includes('select status_id from tickets')) return [{ status_id: 'status-109' }];
if (sql.includes('from internal_notifications')) {
return [
{
internal_notification_id: 'notif-109',
title: '[fixture ticket-unassigned-return-to-triage] Ticket unassigned',
message: '[fixture ticket-unassigned-return-to-triage] ticketId=ticket-109'
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-unassigned-return-to-triage', workflowId: 'wf-109' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-109', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 3);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[1].path, '/api/workflow/events');
assert.equal(requests[1].opts.method, 'POST');
assert.equal(requests[1].opts.json.eventName, 'TICKET_UNASSIGNED');
assert.equal(requests[1].opts.json.payloadSchemaRef, 'payload.TicketUnassigned.v1');
assert.equal(requests[1].opts.json.payload.ticketId, 'ticket-109');
assert.equal(requests[2].path, '/api/v1/tickets/ticket-109');
assert.equal(requests[2].opts.method, 'DELETE');
assert.equal(requests[2].opts.headers['x-api-key'], 'api-key');
});
test('T110: ticket-status-waiting-on-customer-reminder fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-status-waiting-on-customer-reminder');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-110' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-110' }];
if (sql.includes('from boards')) return [{ board_id: 'board-110' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-110' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-110' }];
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-status-waiting-on-customer-reminder', workflowId: 'wf-110' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-110', status: 'SUCCEEDED' }),
getRunSteps: async () => [
{ step_id: 's1', run_id: 'run-110', step_path: '/0', definition_step_id: 'wait-for-time-entry', status: 'SUCCEEDED', attempt: 1 },
{ step_id: 's2', run_id: 'run-110', step_path: '/1', definition_step_id: 'send-reminder', status: 'SUCCEEDED', attempt: 1 }
],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 4);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[1].path, '/api/workflow/events');
assert.equal(requests[1].opts.json.eventName, 'TICKET_STATUS_CHANGED');
assert.equal(requests[1].opts.json.payloadSchemaRef, 'payload.TicketStatusChanged.v1');
assert.equal(requests[2].path, '/api/workflow/events');
assert.equal(requests[2].opts.json.eventName, 'TICKET_TIME_ENTRY_ADDED');
assert.equal(requests[2].opts.json.payloadSchemaRef, 'payload.TicketTimeEntryAdded.v1');
assert.equal(requests[3].path, '/api/v1/tickets/ticket-110');
assert.equal(requests[3].opts.method, 'DELETE');
assert.equal(requests[3].opts.headers['x-api-key'], 'api-key');
});
test('T111: ticket-reopened-notify-tech fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-reopened-notify-tech');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-111' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-111' }];
if (sql.includes('from boards')) return [{ board_id: 'board-111' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-111' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-111' }];
if (sql.includes('from users')) return [{ user_id: 'user-111' }];
if (sql.includes('from comments')) {
return [
{
comment_id: 'comment-111',
note: '[fixture ticket-reopened-notify-tech] ticketId=ticket-111',
is_internal: true
}
];
}
if (sql.includes('from internal_notifications')) {
return [
{
internal_notification_id: 'notif-111',
title: '[fixture ticket-reopened-notify-tech] Ticket reopened',
message: '[fixture ticket-reopened-notify-tech] ticketId=ticket-111'
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-reopened-notify-tech', workflowId: 'wf-111' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-111', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 3);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[1].path, '/api/workflow/events');
assert.equal(requests[1].opts.json.eventName, 'TICKET_REOPENED');
assert.equal(requests[1].opts.json.payloadSchemaRef, 'payload.TicketReopened.v1');
assert.equal(requests[1].opts.json.payload.ticketId, 'ticket-111');
assert.equal(requests[2].path, '/api/v1/tickets/ticket-111');
assert.equal(requests[2].opts.method, 'DELETE');
assert.equal(requests[2].opts.headers['x-api-key'], 'api-key');
});
test('T112: ticket-escalated-crm-note fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-escalated-crm-note');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-112' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-112' }];
if (sql.includes('from boards')) return [{ board_id: 'board-112' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-112' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-112' }];
if (sql.includes('from users')) return [{ user_id: 'user-112' }];
if (sql.includes('from interactions')) {
return [{ interaction_id: 'note-112', notes: '[fixture ticket-escalated-crm-note] ticketId=ticket-112' }];
}
if (sql.includes('from internal_notifications')) {
return [
{
internal_notification_id: 'notif-112',
title: '[fixture ticket-escalated-crm-note] Ticket escalated',
message: '[fixture ticket-escalated-crm-note] ticketId=ticket-112'
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-escalated-crm-note', workflowId: 'wf-112' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-112', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 3);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[1].path, '/api/workflow/events');
assert.equal(requests[1].opts.json.eventName, 'TICKET_ESCALATED');
assert.equal(requests[1].opts.json.payloadSchemaRef, 'payload.TicketEscalated.v1');
assert.equal(requests[1].opts.json.payload.ticketId, 'ticket-112');
assert.equal(requests[2].path, '/api/v1/tickets/ticket-112');
assert.equal(requests[2].opts.method, 'DELETE');
assert.equal(requests[2].opts.headers['x-api-key'], 'api-key');
});
test('T113: ticket-queue-after-hours-email fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-queue-after-hours-email');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-113' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-113' }];
if (sql.includes('from boards')) return [{ board_id: 'board-113' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-113' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-113' }];
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-queue-after-hours-email', workflowId: 'wf-113' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-113', status: 'SUCCEEDED' }),
getRunSteps: async () => [
{ step_id: 's1', run_id: 'run-113', step_path: '/0', definition_step_id: 'send-email', status: 'SUCCEEDED', attempt: 1 }
],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 3);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[1].path, '/api/workflow/events');
assert.equal(requests[1].opts.json.eventName, 'TICKET_QUEUE_CHANGED');
assert.equal(requests[1].opts.json.payloadSchemaRef, 'payload.TicketQueueChanged.v1');
assert.equal(requests[1].opts.json.payload.ticketId, 'ticket-113');
assert.equal(requests[2].path, '/api/v1/tickets/ticket-113');
assert.equal(requests[2].opts.method, 'DELETE');
assert.equal(requests[2].opts.headers['x-api-key'], 'api-key');
});
test('T114: ticket-tags-billing-route fixture loads and executes via harness', async () => {
const fixtureDir = path.resolve(process.cwd(), 'ee/test-data/workflow-harness/ticket-tags-billing-route');
const bundlePath = path.join(fixtureDir, 'bundle.json');
const testPath = path.join(fixtureDir, 'test.cjs');
const savedApiKey = process.env.WORKFLOW_HARNESS_API_KEY;
process.env.WORKFLOW_HARNESS_API_KEY = 'api-key';
const requests = [];
const harness = loadHarnessWithStubs({
http: {
createHttpClient: () => ({
request: async (p, opts) => {
requests.push({ path: p, opts });
if (p === '/api/v1/tickets' && opts?.method === 'POST') {
return { json: { data: { ticket_id: 'ticket-114' } } };
}
return { json: { data: {} } };
}
})
},
db: {
createDbClient: async () => ({
query: async (text) => {
const sql = String(text).replace(/\s+/g, ' ').trim().toLowerCase();
if (sql.includes('from clients')) return [{ client_id: 'client-114' }];
if (sql.includes('from boards')) return [{ board_id: 'board-114' }];
if (sql.includes('from statuses')) return [{ status_id: 'status-114' }];
if (sql.includes('from priorities')) return [{ priority_id: 'priority-114' }];
if (sql.includes('from users')) return [{ user_id: 'user-114' }];
if (sql.includes('select status_id from tickets')) return [{ status_id: 'status-114' }];
if (sql.includes('from internal_notifications')) {
return [
{
internal_notification_id: 'notif-114',
title: '[fixture ticket-tags-billing-route] Billing tag added',
message: '[fixture ticket-tags-billing-route] ticketId=ticket-114'
}
];
}
return [];
},
close: async () => {}
})
},
workflow: {
importWorkflowBundleV1: async () => ({ createdWorkflows: [{ key: 'fixture.ticket-tags-billing-route', workflowId: 'wf-114' }] }),
exportWorkflowBundleV1: async () => ({})
},
runs: {
waitForRun: async () => ({ run_id: 'run-114', status: 'SUCCEEDED' }),
getRunSteps: async () => [],
getRunLogs: async () => [],
summarizeSteps: () => ({ counts: {}, failed: [] })
}
});
try {
const { runFixture } = harness.mod;
await runFixture({
testDir: fixtureDir,
bundlePath,
testPath,
baseUrl: 'http://localhost:3010',
tenantId: 'tenant',
cookie: 'cookie',
force: true,
timeoutMs: 1000,
debug: false,
artifactsDir: os.tmpdir(),
pgUrl: 'postgres://unused'
});
} finally {
harness.restore();
if (savedApiKey === undefined) delete process.env.WORKFLOW_HARNESS_API_KEY;
else process.env.WORKFLOW_HARNESS_API_KEY = savedApiKey;
}
assert.equal(requests.length, 3);
assert.equal(requests[0].path, '/api/v1/tickets');
assert.equal(requests[0].opts.method, 'POST');
assert.equal(requests[0].opts.headers['x-api-key'], 'api-key');
assert.equal(requests[1].path, '/api/workflow/events');
assert.equal(requests[1].opts.json.eventName, 'TICKET_TAGS_CHANGED');
assert.equal(requests[1].opts.json.payloadSchemaRef, 'payload.TicketTagsChanged.v1');
assert.equal(requests[1].opts.json.payload.ticketId, 'ticket-114');
assert.equal(requests[2].path, '/api/v1/tickets/ticket-114');
assert.equal(requests[2].opts.method, 'DELETE');
assert.equal(requests[2].opts.headers['x-api-key'], 'api-key');
});