Changeset 0.28.7 (#193)
This commit is contained in:
192
packages/icd-test-runner/js/crud/extensions.js
Normal file
192
packages/icd-test-runner/js/crud/extensions.js
Normal file
@@ -0,0 +1,192 @@
|
||||
/**
|
||||
* 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;
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user