All checks were successful
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Successful in 6s
CI/CD / test-go-pg (pull_request) Successful in 2m43s
CI/CD / test-sqlite (pull_request) Successful in 2m51s
CI/CD / build-and-deploy (pull_request) Successful in 1m13s
Lift block rendering to kernel SDK primitives (sw.renderers + sw.markdown) so all surfaces share one markdown pipeline. Rewrite docs for external audience — remove all fork history references. Add Mermaid architecture diagrams, CONTRIBUTING guide, and extension tutorial. - sw.renderers SDK module: kernel-level renderer registry - sw.markdown SDK module: unified marked v16 + DOMPurify pipeline - Browser extension script loader for renderer injection - Notes + Docs surfaces migrated to sw.markdown.renderSync() - 4 renderer extensions rewritten to IIFE + sw.renderers.register() - 6 Mermaid diagrams in ARCHITECTURE.md - CONTRIBUTING.md + TUTORIAL-FIRST-EXTENSION.md - DESIGN-WORKFLOWS.md replaces fork-era design doc - Surface alias routes removed from main.go - ICD/SDK runners migrated to /admin/packages/ endpoints - 13 new renderer tests - Docs, CHANGELOG, ROADMAP cleaned of fork references Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
205 lines
9.4 KiB
JavaScript
205 lines
9.4 KiB
JavaScript
/**
|
|
* 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/packages/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/packages/install', zipBlob, surfaceId + '.surface');
|
|
|
|
// Verify it exists
|
|
var d = await T.apiGet('/admin/packages/' + 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/packages/' + surfaceId);
|
|
}
|
|
});
|
|
});
|
|
|
|
if (surfaceId) {
|
|
await T.test('crud', 'surfaces', 'GET /admin/packages/:id (read)', async function () {
|
|
var d = await T.apiGet('/admin/packages/' + 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/packages (list includes new)', async function () {
|
|
var d = await T.apiGet('/admin/packages');
|
|
T.assertHasKey(d, 'data', '/admin/packages');
|
|
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/packages/:id/disable', async function () {
|
|
var d = await T.authFetch(await T.getAuthToken(), 'PUT',
|
|
'/admin/packages/' + 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/packages (disabled still in admin)', async function () {
|
|
var d = await T.apiGet('/admin/packages');
|
|
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/packages/:id/enable', async function () {
|
|
var d = await T.authFetch(await T.getAuthToken(), 'PUT',
|
|
'/admin/packages/' + 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/packages/chat/disable (reject → 400)', async function () {
|
|
var d = await T.authFetch(await T.getAuthToken(), 'PUT',
|
|
'/admin/packages/chat/disable');
|
|
T.assert(d._status === 400, 'disabling chat should return 400, got ' + d._status);
|
|
});
|
|
|
|
await T.test('crud', 'surfaces', 'PUT /admin/packages/admin/disable (reject → 400)', async function () {
|
|
var d = await T.authFetch(await T.getAuthToken(), 'PUT',
|
|
'/admin/packages/admin/disable');
|
|
T.assert(d._status === 400, 'disabling admin should return 400, got ' + d._status);
|
|
});
|
|
|
|
await T.test('crud', 'surfaces', 'DELETE /admin/packages/chat (core reject → 400)', async function () {
|
|
var d = await T.authFetch(await T.getAuthToken(), 'DELETE',
|
|
'/admin/packages/chat');
|
|
T.assert(d._status === 400, 'deleting core surface should return 400, got ' + d._status);
|
|
});
|
|
|
|
await T.test('crud', 'surfaces', 'GET /admin/packages/:id (not found → 404)', async function () {
|
|
var d = await T.authFetch(await T.getAuthToken(), 'GET',
|
|
'/admin/packages/nonexistent-surface-id');
|
|
T.assert(d._status === 404, 'nonexistent should return 404, got ' + d._status);
|
|
});
|
|
|
|
await T.test('crud', 'surfaces', 'PUT /admin/packages/nonexistent/disable (404)', async function () {
|
|
var d = await T.authFetch(await T.getAuthToken(), 'PUT',
|
|
'/admin/packages/nonexistent-surface-id/disable');
|
|
T.assert(d._status === 404, 'disable nonexistent should return 404, got ' + d._status);
|
|
});
|
|
|
|
await T.test('crud', 'surfaces', 'POST /admin/packages/install (missing manifest → 400)', async function () {
|
|
var zipBlob = buildTestZip({ 'readme.txt': 'no manifest here' });
|
|
var d;
|
|
try {
|
|
d = await T.apiUpload('/admin/packages/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/packages/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/packages/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/packages/install (reseed preserves enabled)', async function () {
|
|
// Surface was re-enabled above. Disable it, then re-install.
|
|
await T.authFetch(await T.getAuthToken(), 'PUT',
|
|
'/admin/packages/' + 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/packages/install', zipBlob, surfaceId + '.surface');
|
|
|
|
// Verify title updated but enabled preserved as false
|
|
var d = await T.apiGet('/admin/packages/' + 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/packages/' + surfaceId + '/enable');
|
|
});
|
|
|
|
await T.test('crud', 'surfaces', 'DELETE /admin/packages/:id', async function () {
|
|
var token = await T.getAuthToken();
|
|
var d = await T.authFetch(token, 'DELETE', '/admin/packages/' + surfaceId);
|
|
T.assertStatus(d, 200, 'delete');
|
|
T.assert(d.deleted === true, 'should report deleted');
|
|
|
|
// Verify gone
|
|
var check = await T.authFetch(token, 'GET', '/admin/packages/' + surfaceId);
|
|
T.assert(check._status === 404, 'deleted surface should 404 on get');
|
|
surfaceId = null;
|
|
});
|
|
|
|
await T.test('crud', 'surfaces', 'DELETE /admin/packages/nonexistent (404)', async function () {
|
|
var d = await T.authFetch(await T.getAuthToken(), 'DELETE',
|
|
'/admin/packages/nonexistent-surface-id');
|
|
T.assert(d._status === 404, 'delete nonexistent should return 404, got ' + d._status);
|
|
});
|
|
}
|
|
|
|
};
|
|
})();
|