This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/surfaces/icd-test-runner/js/crud/extensions.js
2026-03-14 19:36:33 +00:00

193 lines
9.1 KiB
JavaScript

/**
* ICD Test Runner — CRUD: Extensions
* Extension install lifecycle (install → list → update → user list →
* tier filter → manifest → tools → assets → settings → disable →
* duplicate 409 → invalid tier 400 → not found 404 → disabled asset 404 →
* delete), system extension disable rejection.
*/
(function () {
'use strict';
var T = window.ICD;
if (!T) return;
if (!T.crud) T.crud = {};
T.crud.extensions = async function (testTag) {
// ── Extensions CRUD ──
var extId = null; // UUID (DB id)
var extExtId = null; // ext_id (manifest id)
await T.test('crud', 'extensions', 'POST /admin/extensions (install)', async function () {
extExtId = testTag + '-ext';
var d = await T.apiPost('/admin/extensions', {
ext_id: extExtId,
name: testTag + ' Test Extension',
version: '1.0.0',
tier: 'browser',
description: 'ICD CRUD test extension',
author: 'icd-runner',
manifest: { _script: 'console.log("icd-test");', tools: [{ name: 'icd_tool', description: 'Test tool' }] },
is_enabled: true,
is_system: false
});
T.assertHasKey(d, 'data', 'install response');
T.assertShape(d.data, T.S.extension, 'installed extension');
T.assert(d.data.ext_id === extExtId, 'ext_id mismatch');
T.assert(d.data.tier === 'browser', 'tier should be browser');
T.assert(d.data.is_enabled === true, 'should be enabled');
T.assert(d.data.is_system === false, 'should not be system');
extId = d.data.id;
T.registerCleanup(function () { if (extId) return T.safeDelete('/admin/extensions/' + extId); });
});
if (extId) {
await T.test('crud', 'extensions', 'GET /admin/extensions (list)', async function () {
var d = await T.apiGet('/admin/extensions');
T.assertHasKey(d, 'data', '/admin/extensions');
T.assert(Array.isArray(d.data), 'admin list should be array');
var found = d.data.some(function (e) { return e.id === extId; });
T.assert(found, 'installed extension should be in admin list');
});
await T.test('crud', 'extensions', 'PUT /admin/extensions/:id (update)', async function () {
var d = await T.apiPut('/admin/extensions/' + extId, {
name: testTag + ' Updated Extension',
version: '2.0.0',
description: 'Updated description'
});
T.assertHasKey(d, 'data', 'update response');
T.assert(d.data.name === testTag + ' Updated Extension', 'name not updated');
T.assert(d.data.version === '2.0.0', 'version not updated');
});
await T.test('crud', 'extensions', 'GET /extensions (user list)', async function () {
var d = await T.apiGet('/extensions');
T.assertHasKey(d, 'data', '/extensions');
T.assert(Array.isArray(d.data), 'user list should be array');
var found = d.data.some(function (e) { return e.id === extId; });
T.assert(found, 'enabled extension should appear in user list');
});
await T.test('crud', 'extensions', 'GET /extensions?tier=browser (filter)', async function () {
var d = await T.apiGet('/extensions?tier=browser');
T.assertHasKey(d, 'data', '/extensions?tier=browser');
var found = d.data.some(function (e) { return e.id === extId; });
T.assert(found, 'browser extension should appear with tier filter');
});
await T.test('crud', 'extensions', 'GET /extensions?tier=starlark (filter excludes)', async function () {
var d = await T.apiGet('/extensions?tier=starlark');
T.assertHasKey(d, 'data', '/extensions?tier=starlark');
var found = d.data.some(function (e) { return e.id === extId; });
T.assert(!found, 'browser extension should NOT appear with starlark filter');
});
await T.test('crud', 'extensions', 'GET /extensions/:id/manifest (by ext_id)', async function () {
var d = await T.apiGet('/extensions/' + extExtId + '/manifest');
T.assertHasKey(d, 'data', 'manifest response');
T.assert(typeof d.data === 'object', 'manifest should be object');
});
await T.test('crud', 'extensions', 'GET /extensions/tools (tool schemas)', async function () {
var d = await T.apiGet('/extensions/tools');
T.assertHasKey(d, 'data', '/extensions/tools');
T.assert(Array.isArray(d.data), 'tools should be array');
var found = d.data.some(function (t) { return t.name === 'icd_tool'; });
T.assert(found, 'icd_tool should appear in tool schemas');
});
await T.test('crud', 'extensions', 'GET /extensions/:id/assets (public, no auth)', async function () {
var resp = await fetch(T.base + '/api/v1/extensions/' + extExtId + '/assets/main.js');
T.assert(resp.ok, 'asset fetch should succeed: ' + resp.status);
var text = await resp.text();
T.assert(text.indexOf('icd-test') !== -1, 'script body should contain icd-test');
var ct = resp.headers.get('Content-Type') || '';
T.assert(ct.indexOf('javascript') !== -1, 'content-type should be javascript, got ' + ct);
});
await T.test('crud', 'extensions', 'POST /extensions/:id/settings (save)', async function () {
var d = await T.apiPost('/extensions/' + extId + '/settings', {
settings: { theme: 'dark' },
is_enabled: true
});
T.assertHasKey(d, 'ok', 'settings response');
T.assert(d.ok === true, 'ok should be true');
});
await T.test('crud', 'extensions', 'POST /extensions/:id/settings (disable non-system)', async function () {
var d = await T.apiPost('/extensions/' + extId + '/settings', { is_enabled: false });
T.assert(d.ok === true, 'should succeed for non-system extension');
// Re-enable for subsequent tests
await T.apiPost('/extensions/' + extId + '/settings', { is_enabled: true });
});
await T.test('crud', 'extensions', 'POST /admin/extensions (duplicate → 409)', async function () {
var d = await T.authFetch(await T.getAuthToken(), 'POST', '/admin/extensions', {
ext_id: extExtId, name: 'Duplicate'
});
T.assert(d._status === 409, 'duplicate ext_id should return 409, got ' + d._status);
});
await T.test('crud', 'extensions', 'POST /admin/extensions (invalid tier → 400)', async function () {
var d = await T.authFetch(await T.getAuthToken(), 'POST', '/admin/extensions', {
ext_id: 'bad-tier-ext', name: 'Bad Tier', tier: 'invalid'
});
T.assert(d._status === 400, 'invalid tier should return 400, got ' + d._status);
});
await T.test('crud', 'extensions', 'PUT /admin/extensions/:id (not found → 404)', async function () {
var d = await T.authFetch(await T.getAuthToken(), 'PUT',
'/admin/extensions/00000000-0000-0000-0000-000000000000', { name: 'ghost' });
T.assert(d._status === 404, 'nonexistent should return 404, got ' + d._status);
});
await T.test('crud', 'extensions', 'GET /extensions/:id/assets (disabled → 404)', async function () {
// Disable via admin
await T.apiPut('/admin/extensions/' + extId, { is_enabled: false });
// Cache-bust: the earlier asset fetch is cached (max-age=3600)
var resp = await fetch(T.base + '/api/v1/extensions/' + extExtId + '/assets/main.js?_cb=' + Date.now());
T.assert(resp.status === 404, 'disabled asset should 404, got ' + resp.status);
// Re-enable
await T.apiPut('/admin/extensions/' + extId, { is_enabled: true });
});
await T.test('crud', 'extensions', 'DELETE /admin/extensions/:id', async function () {
await T.safeDelete('/admin/extensions/' + extId);
// Verify gone from admin list
var d = await T.apiGet('/admin/extensions');
var found = d.data.some(function (e) { return e.id === extId; });
T.assert(!found, 'deleted extension should not appear in list');
extId = null;
});
}
// ── System extension disable rejection ──
var sysExtId = null;
await T.test('crud', 'extensions', 'POST /admin/extensions (system ext)', async function () {
var d = await T.apiPost('/admin/extensions', {
ext_id: testTag + '-sys-ext', name: 'System Test', is_system: true, is_enabled: true
});
T.assertHasKey(d, 'data', 'system install');
T.assert(d.data.is_system === true, 'should be system');
sysExtId = d.data.id;
T.registerCleanup(function () { if (sysExtId) return T.safeDelete('/admin/extensions/' + sysExtId); });
});
if (sysExtId) {
await T.test('crud', 'extensions', 'POST /extensions/:id/settings (system disable → 403)', async function () {
var d = await T.authFetch(await T.getAuthToken(), 'POST',
'/extensions/' + sysExtId + '/settings', { is_enabled: false });
T.assert(d._status === 403, 'disabling system ext should return 403, got ' + d._status);
});
await T.test('crud', 'extensions', 'DELETE /admin/extensions/:id (system cleanup)', async function () {
await T.safeDelete('/admin/extensions/' + sysExtId);
sysExtId = null;
});
}
};
})();