1a0d6de04d
GET/POST/PUT/PATCH/DELETE /api/admin/workflows with graph read/write (PUT writes a fresh node/edge set under version+1 and bumps workflows.version so in-flight runs keep their pinned version). Run-history (/:id/runs, /runs/:runId/steps) and the pending-approval inbox (GET /approvals, POST /approvals/:id/:action → actById) round it out. Gated by the workflows flag + RBAC (view for reads, manage for writes); built-in flows refuse delete; graph validated (exactly one trigger, unique keys, edges reference known nodes). Route tests cover CRUD, validation, version bump, toggle, inbox, and the 403 permission gate.
103 lines
3.5 KiB
JavaScript
103 lines
3.5 KiB
JavaScript
/**
|
|
* Admin workflow API — route tests (CRUD, versioning, RBAC gate, approvals).
|
|
*/
|
|
const request = require('supertest');
|
|
const {
|
|
bootCrmDb, seedMinimal, assignAdminRole, mintAdminToken, buildRouteApp,
|
|
} = require('./helpers/crmDb');
|
|
|
|
let db;
|
|
let cleanup;
|
|
let app;
|
|
let token;
|
|
let noPermToken;
|
|
|
|
const sampleGraph = {
|
|
name: 'Test flow',
|
|
trigger_type: 'invoice.sent',
|
|
enabled: false,
|
|
nodes: [
|
|
{ node_key: 'n1', type: 'trigger' },
|
|
{ node_key: 'n2', type: 'action', config: { action: 'noop' } },
|
|
],
|
|
edges: [{ from_node: 'n1', to_node: 'n2' }],
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
({ db, cleanup } = await bootCrmDb());
|
|
const { adminId } = await seedMinimal(db);
|
|
await assignAdminRole(db, adminId, 'super_admin');
|
|
token = mintAdminToken(adminId);
|
|
|
|
const ins = await db('admin_users').insert({
|
|
username: 'norole', email: 'nr@example.com', password_hash: 'x',
|
|
must_change_password: false, created_at: new Date(),
|
|
}).returning('id');
|
|
noPermToken = mintAdminToken(ins[0]?.id ?? ins[0]);
|
|
|
|
await db('feature_flags').insert({ key: 'workflows', value: true });
|
|
app = buildRouteApp('/api/admin/workflows', require('../../src/routes/adminWorkflows'));
|
|
});
|
|
|
|
afterAll(async () => { await cleanup(); });
|
|
|
|
const auth = (t) => ({ Authorization: `Bearer ${t}` });
|
|
|
|
describe('admin workflows API', () => {
|
|
let createdId;
|
|
|
|
test('create → 201 with id', async () => {
|
|
const res = await request(app).post('/api/admin/workflows').set(auth(token)).send(sampleGraph);
|
|
expect(res.status).toBe(201);
|
|
expect(res.body.id).toBeGreaterThan(0);
|
|
createdId = res.body.id;
|
|
});
|
|
|
|
test('rejects a graph without exactly one trigger', async () => {
|
|
const res = await request(app).post('/api/admin/workflows').set(auth(token))
|
|
.send({ ...sampleGraph, nodes: [{ node_key: 'x', type: 'action' }], edges: [] });
|
|
expect(res.status).toBe(400);
|
|
});
|
|
|
|
test('get one returns the graph', async () => {
|
|
const res = await request(app).get(`/api/admin/workflows/${createdId}`).set(auth(token));
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.nodes).toHaveLength(2);
|
|
expect(res.body.edges).toHaveLength(1);
|
|
expect(res.body.version).toBe(1);
|
|
});
|
|
|
|
test('list includes it', async () => {
|
|
const res = await request(app).get('/api/admin/workflows').set(auth(token));
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.some((w) => w.id === createdId)).toBe(true);
|
|
});
|
|
|
|
test('update bumps the version', async () => {
|
|
const res = await request(app).put(`/api/admin/workflows/${createdId}`).set(auth(token))
|
|
.send({ ...sampleGraph, name: 'Renamed' });
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.version).toBe(2);
|
|
const get = await request(app).get(`/api/admin/workflows/${createdId}`).set(auth(token));
|
|
expect(get.body.name).toBe('Renamed');
|
|
expect(get.body.version).toBe(2);
|
|
});
|
|
|
|
test('enable toggle', async () => {
|
|
const res = await request(app).patch(`/api/admin/workflows/${createdId}/enabled`).set(auth(token)).send({ enabled: true });
|
|
expect(res.status).toBe(200);
|
|
expect(res.body.enabled).toBe(true);
|
|
});
|
|
|
|
test('approvals inbox returns an array', async () => {
|
|
const res = await request(app).get('/api/admin/workflows/approvals').set(auth(token));
|
|
expect(res.status).toBe(200);
|
|
expect(Array.isArray(res.body)).toBe(true);
|
|
});
|
|
|
|
test('a role without workflows.manage is forbidden from writing', async () => {
|
|
const res = await request(app).post('/api/admin/workflows').set(auth(noPermToken)).send(sampleGraph);
|
|
expect(res.status).toBe(403);
|
|
});
|
|
});
|