Changeset 0.30.2 cs2 (#202)

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit is contained in:
2026-03-18 18:05:58 +00:00
committed by xcaliber
parent 7b0b6eb061
commit 5883cb50e2
17 changed files with 1231 additions and 31 deletions

View File

@@ -599,5 +599,140 @@
})();
}
// ── Workflow Package Export/Import (v0.30.2) ──
if (T.user.role === 'admin') {
await (async function () {
var wpWfId = null;
var wpStage1Id = null;
var wpStage2Id = null;
var wpPkgId = null;
var wpSlug = testTag.toLowerCase().replace(/[^a-z0-9-]/g, '-') + '-wp';
await T.test('crud', 'workflows', 'wfpkg: create workflow for export', async function () {
var d = await T.apiPost('/workflows', {
name: testTag + '-wfpkg',
slug: wpSlug,
description: 'Workflow package export/import test',
entry_mode: 'public_link'
});
T.assertShape(d, T.S.workflow, 'workflow');
wpWfId = d.id;
T.registerCleanup(function () {
if (wpWfId) return T.safeDelete('/workflows/' + wpWfId);
});
});
if (wpWfId) {
await T.test('crud', 'workflows', 'wfpkg: add stages with form_template', async function () {
var s1 = await T.apiPost('/workflows/' + wpWfId + '/stages', {
name: 'Intake',
ordinal: 0,
stage_mode: 'form_only',
history_mode: 'full',
form_template: { fields: [{ key: 'name', type: 'text', label: 'Full Name', required: true }] }
});
T.assertShape(s1, T.S.workflowStage, 'stage1');
wpStage1Id = s1.id;
var s2 = await T.apiPost('/workflows/' + wpWfId + '/stages', {
name: 'Review',
ordinal: 1,
stage_mode: 'review',
history_mode: 'summary'
});
T.assertShape(s2, T.S.workflowStage, 'stage2');
wpStage2Id = s2.id;
});
// Test surface_pkg_id round-trip
await T.test('crud', 'workflows', 'wfpkg: surface_pkg_id persists on update', async function () {
// Stage update is PUT (full replace), so send all required fields.
// Use the ICD test runner's own package ID (always installed when tests run).
var realPkgId = 'icd-test-runner';
var stageBase = {
name: 'Intake', ordinal: 0, stage_mode: 'form_only', history_mode: 'full',
form_template: { fields: [{ key: 'name', type: 'text', label: 'Full Name', required: true }] }
};
// Set surface_pkg_id
var withPkg = Object.assign({}, stageBase, { surface_pkg_id: realPkgId });
var updated = await T.apiPut('/workflows/' + wpWfId + '/stages/' + wpStage1Id, withPkg);
T.assert(updated.surface_pkg_id === realPkgId,
'surface_pkg_id should persist, got: ' + updated.surface_pkg_id);
// Clear it
var cleared = await T.apiPut('/workflows/' + wpWfId + '/stages/' + wpStage1Id, stageBase);
T.assert(!cleared.surface_pkg_id,
'surface_pkg_id should be cleared, got: ' + cleared.surface_pkg_id);
});
// Export workflow as .pkg
var exportBlob = null;
await T.test('crud', 'workflows', 'wfpkg: GET export .pkg', async function () {
var token = await T.getAuthToken();
var resp = await fetch(T.base + '/api/v1/admin/workflows/' + wpWfId + '/export', {
headers: { 'Authorization': 'Bearer ' + token }
});
T.assert(resp.ok, 'export should return 200, got ' + resp.status);
var ct = resp.headers.get('content-type') || '';
T.assert(ct.indexOf('zip') !== -1 || ct.indexOf('octet') !== -1,
'content-type should be zip/octet, got ' + ct);
exportBlob = await resp.blob();
T.assert(exportBlob.size > 0, 'export blob should not be empty');
});
// Delete original workflow before import to test clean install
await T.test('crud', 'workflows', 'wfpkg: delete original workflow', async function () {
await T.apiDelete('/workflows/' + wpWfId);
wpWfId = null;
});
// Import the .pkg
if (exportBlob) {
await T.test('crud', 'workflows', 'wfpkg: POST install .pkg (re-import)', async function () {
var token = await T.getAuthToken();
var formData = new FormData();
formData.append('file', exportBlob, 'test-workflow.pkg');
var resp = await fetch(T.base + '/api/v1/admin/packages/install', {
method: 'POST',
headers: { 'Authorization': 'Bearer ' + token },
body: formData
});
var result = await resp.json();
T.assert(resp.ok, 'install should return 200, got ' + resp.status + ': ' + (result.error || ''));
T.assert(result.id, 'install should return package id');
wpPkgId = result.id;
T.registerCleanup(function () {
if (wpPkgId) return T.safeDelete('/admin/packages/' + wpPkgId);
});
});
// Verify the workflow was recreated
await T.test('crud', 'workflows', 'wfpkg: verify imported workflow has stages', async function () {
var wfs = await T.apiGet('/workflows');
var list = wfs.data || wfs || [];
var found = list.find(function (w) { return w.slug === wpSlug; });
T.assert(found, 'imported workflow with slug ' + wpSlug + ' should exist');
wpWfId = found.id;
var stages = await T.apiGet('/workflows/' + wpWfId + '/stages');
var stList = stages.data || stages || [];
T.assert(stList.length === 2, 'should have 2 stages, got ' + stList.length);
T.assert(stList[0].name === 'Intake', 'stage 0 name should be Intake');
T.assert(stList[1].name === 'Review', 'stage 1 name should be Review');
T.assert(stList[0].stage_mode === 'form_only', 'stage 0 mode should be form_only');
T.assert(stList[1].stage_mode === 'review', 'stage 1 mode should be review');
});
}
// Cleanup
await T.test('crud', 'workflows', 'wfpkg: cleanup', async function () {
if (wpPkgId) { await T.safeDelete('/admin/packages/' + wpPkgId); wpPkgId = null; }
if (wpWfId) { await T.safeDelete('/workflows/' + wpWfId); wpWfId = null; }
});
}
})();
}
};
})();