Changeset 0.28.7 (#193)
This commit is contained in:
198
packages/icd-test-runner/js/crud/workspaces.js
Normal file
198
packages/icd-test-runner/js/crud/workspaces.js
Normal file
@@ -0,0 +1,198 @@
|
||||
/**
|
||||
* ICD Test Runner — CRUD: Workspaces
|
||||
* Workspace lifecycle — create, read, update, file ops, stats, isolation, delete.
|
||||
* Git credentials list envelope.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
var T = window.ICD;
|
||||
if (!T) return;
|
||||
if (!T.crud) T.crud = {};
|
||||
|
||||
T.crud.workspaces = async function (testTag) {
|
||||
|
||||
// ── GET /workspaces — list envelope ──
|
||||
await T.test('crud', 'workspaces', 'GET /workspaces (list envelope)', async function () {
|
||||
var d = await T.apiGet('/workspaces');
|
||||
T.assertHasKey(d, 'data', 'GET /workspaces');
|
||||
T.assert(Array.isArray(d.data), 'data must be array');
|
||||
});
|
||||
|
||||
// ── GET /git-credentials — list envelope ──
|
||||
await T.test('crud', 'workspaces', 'GET /git-credentials (list envelope)', async function () {
|
||||
var d = await T.apiGet('/git-credentials');
|
||||
T.assertHasKey(d, 'data', 'GET /git-credentials');
|
||||
T.assert(Array.isArray(d.data), 'data must be array');
|
||||
});
|
||||
|
||||
// ── POST /git-credentials/generate — server-side keygen (v0.28.6) ──
|
||||
var generatedKeyId = null;
|
||||
await T.test('crud', 'workspaces', 'POST /git-credentials/generate', async function () {
|
||||
var d = await T.apiPost('/git-credentials/generate', { name: 'ICD Test Key' });
|
||||
T.assert(d.id, 'should return id');
|
||||
T.assert(d.auth_type === 'ssh_key', 'auth_type should be ssh_key');
|
||||
T.assert(d.public_key && d.public_key.length > 0, 'should return public_key');
|
||||
T.assert(d.fingerprint && d.fingerprint.startsWith('SHA256:'), 'should return SHA256 fingerprint');
|
||||
T.assert(!d.encrypted_data, 'must NOT expose encrypted_data');
|
||||
T.assert(!d.nonce, 'must NOT expose nonce');
|
||||
generatedKeyId = d.id;
|
||||
T.registerCleanup(function () { if (generatedKeyId) return T.safeDelete('/git-credentials/' + generatedKeyId); });
|
||||
});
|
||||
|
||||
if (generatedKeyId) {
|
||||
await T.test('crud', 'workspaces', 'GET /git-credentials/:id/public-key', async function () {
|
||||
var d = await T.apiGet('/git-credentials/' + generatedKeyId + '/public-key');
|
||||
T.assertHasKey(d, 'public_key', 'public-key response');
|
||||
T.assertHasKey(d, 'fingerprint', 'public-key response');
|
||||
T.assert(d.public_key.length > 0, 'public_key should be non-empty');
|
||||
});
|
||||
|
||||
await T.test('crud', 'workspaces', 'GET /git-credentials (list after generate)', async function () {
|
||||
var d = await T.apiGet('/git-credentials');
|
||||
var found = (d.data || []).find(function (c) { return c.id === generatedKeyId; });
|
||||
T.assert(found, 'generated key should appear in list');
|
||||
T.assert(found.public_key, 'list item should have public_key');
|
||||
T.assert(found.fingerprint, 'list item should have fingerprint');
|
||||
});
|
||||
|
||||
await T.test('crud', 'workspaces', 'DELETE /git-credentials/:id', async function () {
|
||||
var d = await T.apiDelete('/git-credentials/' + generatedKeyId);
|
||||
T.assert(d.deleted === true, 'should return deleted: true');
|
||||
generatedKeyId = null;
|
||||
});
|
||||
}
|
||||
|
||||
await T.test('crud', 'workspaces', 'POST /git-credentials/generate (missing name → 400)', async function () {
|
||||
var token = await T.getAuthToken();
|
||||
var d = await T.authFetch(token, 'POST', '/git-credentials/generate', {});
|
||||
T.assertStatus(d, 400, 'missing name');
|
||||
});
|
||||
|
||||
// ── Workspace CRUD ──
|
||||
var wsId = null;
|
||||
await T.test('crud', 'workspaces', 'POST /workspaces (create)', async function () {
|
||||
var d = await T.apiPost('/workspaces', {
|
||||
name: testTag + '-ws',
|
||||
owner_type: 'user',
|
||||
owner_id: T.user.id
|
||||
});
|
||||
T.assertShape(d, T.S.workspace, 'workspace');
|
||||
T.assert(d.owner_id === T.user.id, 'owner_id mismatch');
|
||||
T.assert(d.status === 'active', 'status should be active');
|
||||
wsId = d.id;
|
||||
T.registerCleanup(function () { if (wsId) return T.safeDelete('/workspaces/' + wsId); });
|
||||
});
|
||||
|
||||
if (wsId) {
|
||||
await T.test('crud', 'workspaces', 'GET /workspaces/:id (read)', async function () {
|
||||
var d = await T.apiGet('/workspaces/' + wsId);
|
||||
T.assertShape(d, T.S.workspace, 'workspace');
|
||||
T.assert(d.id === wsId, 'id mismatch');
|
||||
T.assert(d.name === testTag + '-ws', 'name mismatch');
|
||||
});
|
||||
|
||||
// ── PATCH /workspaces/:id ──
|
||||
await T.test('crud', 'workspaces', 'PATCH /workspaces/:id (update name)', async function () {
|
||||
var d = await T.apiPatch('/workspaces/' + wsId, { name: testTag + '-ws-updated' });
|
||||
T.assertShape(d, T.S.workspace, 'workspace-patched');
|
||||
T.assert(d.name === testTag + '-ws-updated', 'name not updated');
|
||||
});
|
||||
|
||||
// ── root_path must never be exposed ──
|
||||
await T.test('crud', 'workspaces', 'GET /workspaces/:id (no root_path)', async function () {
|
||||
var d = await T.apiGet('/workspaces/' + wsId);
|
||||
T.assert(d.root_path === undefined, 'root_path must not be exposed in API');
|
||||
});
|
||||
|
||||
// ── File operations ──
|
||||
await T.test('crud', 'workspaces', 'GET /workspaces/:id/files (list root)', async function () {
|
||||
var d = await T.apiGet('/workspaces/' + wsId + '/files?path=/');
|
||||
T.assertHasKey(d, 'data', 'ListFiles envelope');
|
||||
T.assert(Array.isArray(d.data), 'files should be array');
|
||||
});
|
||||
|
||||
// mkdir
|
||||
await T.test('crud', 'workspaces', 'POST /workspaces/:id/files/mkdir', async function () {
|
||||
var d = await T.apiPost('/workspaces/' + wsId + '/files/mkdir?path=/testdir', {});
|
||||
T.assert(d.ok === true, 'mkdir should return ok:true');
|
||||
T.assert(d.path === '/testdir', 'path mismatch');
|
||||
});
|
||||
|
||||
// Write a file then read it back
|
||||
await T.test('crud', 'workspaces', 'PUT /workspaces/:id/files/write', async function () {
|
||||
var d = await T.apiPut('/workspaces/' + wsId + '/files/write?path=' + encodeURIComponent('/test.txt'), {
|
||||
content: 'Hello from ICD test runner'
|
||||
});
|
||||
T.assert(typeof d === 'object', 'expected object');
|
||||
});
|
||||
|
||||
await T.test('crud', 'workspaces', 'GET /workspaces/:id/files/read', async function () {
|
||||
var d = await T.apiGet('/workspaces/' + wsId + '/files/read?path=/test.txt');
|
||||
T.assertHasKey(d, 'content', '/ws-file-read');
|
||||
T.assert(d.content === 'Hello from ICD test runner', 'content mismatch');
|
||||
});
|
||||
|
||||
await T.test('crud', 'workspaces', 'GET /workspaces/:id/stats', async function () {
|
||||
var d = await T.apiGet('/workspaces/' + wsId + '/stats');
|
||||
T.assert(typeof d === 'object', 'expected object');
|
||||
T.assertHasKey(d, 'file_count', 'stats.file_count');
|
||||
T.assertHasKey(d, 'total_bytes', 'stats.total_bytes');
|
||||
});
|
||||
|
||||
await T.test('crud', 'workspaces', 'GET /workspaces/:id/index-status', async function () {
|
||||
var d = await T.apiGet('/workspaces/' + wsId + '/index-status');
|
||||
T.assertHasKey(d, 'workspace_id', 'index-status.workspace_id');
|
||||
T.assertHasKey(d, 'status_counts', 'index-status.status_counts');
|
||||
T.assertHasKey(d, 'total_chunks', 'index-status.total_chunks');
|
||||
});
|
||||
|
||||
// ── File cleanup ──
|
||||
await T.test('crud', 'workspaces', 'DELETE /workspaces/:id/files (file)', async function () {
|
||||
try {
|
||||
await T.safeDelete('/workspaces/' + wsId + '/files/delete?path=' + encodeURIComponent('/test.txt'));
|
||||
} catch (e) {
|
||||
if (e.message && e.message.indexOf('404') === -1) throw e;
|
||||
}
|
||||
});
|
||||
|
||||
await T.test('crud', 'workspaces', 'DELETE /workspaces/:id', async function () {
|
||||
await T.safeDelete('/workspaces/' + wsId);
|
||||
wsId = null;
|
||||
});
|
||||
}
|
||||
|
||||
// ── Workspace isolation: other user cannot access ──
|
||||
// Create workspace as running admin user, try to access from fixture regular user
|
||||
if (T.fixtures.ready) {
|
||||
var fixtureUser = T.getFixtureUser('-user');
|
||||
if (fixtureUser && fixtureUser.token) {
|
||||
var isolWsId = null;
|
||||
|
||||
await T.test('crud', 'workspaces', 'isolation: create admin workspace', async function () {
|
||||
var d = await T.apiPost('/workspaces', {
|
||||
name: testTag + '-iso-ws',
|
||||
owner_type: 'user',
|
||||
owner_id: T.user.id
|
||||
});
|
||||
T.assertShape(d, T.S.workspace, 'workspace-iso');
|
||||
isolWsId = d.id;
|
||||
T.registerCleanup(function () { if (isolWsId) return T.safeDelete('/workspaces/' + isolWsId); });
|
||||
});
|
||||
|
||||
if (isolWsId) {
|
||||
await T.test('crud', 'workspaces', 'isolation: user cannot GET other workspace', async function () {
|
||||
var d = await T.authFetch(fixtureUser.token, 'GET', '/workspaces/' + isolWsId);
|
||||
T.assert(d._status === 403, 'expected 403, got ' + d._status);
|
||||
});
|
||||
|
||||
// cleanup
|
||||
await T.test('crud', 'workspaces', 'isolation: cleanup admin workspace', async function () {
|
||||
await T.safeDelete('/workspaces/' + isolWsId);
|
||||
isolWsId = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user