193 lines
9.1 KiB
JavaScript
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.tier === 'browser', 'tier should be browser');
|
|
T.assert(d.data.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.title === 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;
|
|
});
|
|
}
|
|
|
|
};
|
|
})();
|