/** * ICD Test Runner — CRUD: Surfaces * Surface install lifecycle (install → read → admin list → user list → * disable → hidden from user → still in admin → enable → visible again → * core disable rejection → not found → disable not found → missing manifest → * core conflict → reseed preserves enabled → delete → delete not found). * Uses T.crud.buildTestZip() from _helpers.js. */ (function () { 'use strict'; var T = window.ICD; if (!T) return; if (!T.crud) T.crud = {}; var buildTestZip = T.crud.buildTestZip; T.crud.surfaces = async function (testTag) { // ── Surfaces CRUD ── var surfaceId = null; await T.test('crud', 'surfaces', 'POST /admin/surfaces/install (happy path)', async function () { surfaceId = 'icd-test-' + Date.now(); var manifest = JSON.stringify({ id: surfaceId, title: 'ICD Test Surface', route: '/s/' + surfaceId }); var zipBlob = buildTestZip({ 'manifest.json': manifest }); await T.apiUpload('/admin/surfaces/install', zipBlob, surfaceId + '.surface'); // Verify it exists var d = await T.apiGet('/admin/surfaces/' + surfaceId); T.assert(d.id === surfaceId, 'installed surface id mismatch'); T.assert(d.title === 'ICD Test Surface', 'title mismatch'); T.registerCleanup(async function () { if (surfaceId) { var token = await T.getAuthToken(); return T.authFetch(token, 'DELETE', '/admin/surfaces/' + surfaceId); } }); }); if (surfaceId) { await T.test('crud', 'surfaces', 'GET /admin/surfaces/:id (read)', async function () { var d = await T.apiGet('/admin/surfaces/' + surfaceId); T.assertShape(d, T.S.surfaceAdmin, 'surface detail'); T.assert(d.id === surfaceId, 'id mismatch'); T.assert(d.source === 'extension', 'source should be extension'); T.assert(d.enabled === true, 'newly installed surface should be enabled'); }); await T.test('crud', 'surfaces', 'GET /admin/surfaces (list includes new)', async function () { var d = await T.apiGet('/admin/surfaces'); T.assertHasKey(d, 'data', '/admin/surfaces'); var found = d.data.some(function (s) { return s.id === surfaceId; }); T.assert(found, 'installed surface should appear in admin list'); }); await T.test('crud', 'surfaces', 'GET /surfaces (user list includes new)', async function () { var d = await T.apiGet('/surfaces'); T.assertHasKey(d, 'data', '/surfaces'); var found = d.data.some(function (s) { return s.id === surfaceId; }); T.assert(found, 'enabled extension surface should appear in user nav list'); // Verify nav shape: id + title + route, NOT source/enabled var entry = d.data.find(function (s) { return s.id === surfaceId; }); T.assert(entry.route !== undefined, 'nav entry should have route'); T.assert(entry.source === undefined, 'nav entry should NOT have source'); T.assert(entry.enabled === undefined, 'nav entry should NOT have enabled'); }); await T.test('crud', 'surfaces', 'PUT /admin/surfaces/:id/disable', async function () { var d = await T.authFetch(await T.getAuthToken(), 'PUT', '/admin/surfaces/' + surfaceId + '/disable'); T.assertStatus(d, 200, 'disable'); T.assert(d.enabled === false, 'should report disabled'); }); await T.test('crud', 'surfaces', 'GET /surfaces (disabled hidden from user)', async function () { var d = await T.apiGet('/surfaces'); var found = d.data.some(function (s) { return s.id === surfaceId; }); T.assert(!found, 'disabled surface should NOT appear in user nav list'); }); await T.test('crud', 'surfaces', 'GET /admin/surfaces (disabled still in admin)', async function () { var d = await T.apiGet('/admin/surfaces'); var entry = d.data.find(function (s) { return s.id === surfaceId; }); T.assert(entry, 'disabled surface should still appear in admin list'); T.assert(entry.enabled === false, 'should show as disabled in admin list'); }); await T.test('crud', 'surfaces', 'PUT /admin/surfaces/:id/enable', async function () { var d = await T.authFetch(await T.getAuthToken(), 'PUT', '/admin/surfaces/' + surfaceId + '/enable'); T.assertStatus(d, 200, 'enable'); T.assert(d.enabled === true, 'should report enabled'); }); await T.test('crud', 'surfaces', 'GET /surfaces (re-enabled visible)', async function () { var d = await T.apiGet('/surfaces'); var found = d.data.some(function (s) { return s.id === surfaceId; }); T.assert(found, 're-enabled surface should appear in user nav list'); }); await T.test('crud', 'surfaces', 'PUT /admin/surfaces/chat/disable (reject → 400)', async function () { var d = await T.authFetch(await T.getAuthToken(), 'PUT', '/admin/surfaces/chat/disable'); T.assert(d._status === 400, 'disabling chat should return 400, got ' + d._status); }); await T.test('crud', 'surfaces', 'PUT /admin/surfaces/admin/disable (reject → 400)', async function () { var d = await T.authFetch(await T.getAuthToken(), 'PUT', '/admin/surfaces/admin/disable'); T.assert(d._status === 400, 'disabling admin should return 400, got ' + d._status); }); await T.test('crud', 'surfaces', 'DELETE /admin/surfaces/chat (core reject → 400)', async function () { var d = await T.authFetch(await T.getAuthToken(), 'DELETE', '/admin/surfaces/chat'); T.assert(d._status === 400, 'deleting core surface should return 400, got ' + d._status); }); await T.test('crud', 'surfaces', 'GET /admin/surfaces/:id (not found → 404)', async function () { var d = await T.authFetch(await T.getAuthToken(), 'GET', '/admin/surfaces/nonexistent-surface-id'); T.assert(d._status === 404, 'nonexistent should return 404, got ' + d._status); }); await T.test('crud', 'surfaces', 'PUT /admin/surfaces/nonexistent/disable (404)', async function () { var d = await T.authFetch(await T.getAuthToken(), 'PUT', '/admin/surfaces/nonexistent-surface-id/disable'); T.assert(d._status === 404, 'disable nonexistent should return 404, got ' + d._status); }); await T.test('crud', 'surfaces', 'POST /admin/surfaces/install (missing manifest → 400)', async function () { var zipBlob = buildTestZip({ 'readme.txt': 'no manifest here' }); var d; try { d = await T.apiUpload('/admin/surfaces/install', zipBlob, 'bad.surface'); T.assert(false, 'should have thrown'); } catch (e) { T.assert(e.message.indexOf('400') !== -1, 'missing manifest should 400: ' + e.message); } }); await T.test('crud', 'surfaces', 'POST /admin/surfaces/install (core conflict → 409)', async function () { var manifest = JSON.stringify({ id: 'chat', title: 'Evil Chat', route: '/s/chat' }); var zipBlob = buildTestZip({ 'manifest.json': manifest }); var d; try { d = await T.apiUpload('/admin/surfaces/install', zipBlob, 'chat.surface'); // If no throw, check status manually T.assert(false, 'overwriting core surface should have thrown'); } catch (e) { T.assert(e.message.indexOf('409') !== -1, 'core conflict should 409: ' + e.message); } }); await T.test('crud', 'surfaces', 'POST /admin/surfaces/install (reseed preserves enabled)', async function () { // Surface was re-enabled above. Disable it, then re-install. await T.authFetch(await T.getAuthToken(), 'PUT', '/admin/surfaces/' + surfaceId + '/disable'); // Re-install same ID var manifest = JSON.stringify({ id: surfaceId, title: 'ICD Test Surface v2', route: '/s/' + surfaceId }); var zipBlob = buildTestZip({ 'manifest.json': manifest }); await T.apiUpload('/admin/surfaces/install', zipBlob, surfaceId + '.surface'); // Verify title updated but enabled preserved as false var d = await T.apiGet('/admin/surfaces/' + surfaceId); T.assert(d.title === 'ICD Test Surface v2', 'title should be updated after reseed'); T.assert(d.enabled === false, 'enabled should be preserved as false after reseed'); // Re-enable for cleanup await T.authFetch(await T.getAuthToken(), 'PUT', '/admin/surfaces/' + surfaceId + '/enable'); }); await T.test('crud', 'surfaces', 'DELETE /admin/surfaces/:id', async function () { var token = await T.getAuthToken(); var d = await T.authFetch(token, 'DELETE', '/admin/surfaces/' + surfaceId); T.assertStatus(d, 200, 'delete'); T.assert(d.deleted === true, 'should report deleted'); // Verify gone var check = await T.authFetch(token, 'GET', '/admin/surfaces/' + surfaceId); T.assert(check._status === 404, 'deleted surface should 404 on get'); surfaceId = null; }); await T.test('crud', 'surfaces', 'DELETE /admin/surfaces/nonexistent (404)', async function () { var d = await T.authFetch(await T.getAuthToken(), 'DELETE', '/admin/surfaces/nonexistent-surface-id'); T.assert(d._status === 404, 'delete nonexistent should return 404, got ' + d._status); }); } }; })();