All checks were successful
CI/CD / detect-changes (pull_request) Successful in 21s
CI/CD / test-frontend (pull_request) Successful in 5s
CI/CD / test-go-pg (pull_request) Successful in 2m9s
CI/CD / test-sqlite (pull_request) Successful in 2m50s
CI/CD / build-and-deploy (pull_request) Successful in 1m50s
Team admin settings audit (pass 1): remove dead BYOK/provider/persona vestiges from team store, models, policy defaults, workflow stage UI, and ICD tests. Fix stale chat_only stage mode in frontend to match backend CHECK constraint. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
264 lines
12 KiB
JavaScript
264 lines
12 KiB
JavaScript
/**
|
|
* ICD Test Runner — CRUD: Teams
|
|
* Admin team lifecycle (create → read → update → members → team-scoped
|
|
* endpoints → team tasks → groups → grants → permissions → audit →
|
|
* usage → assignments → member remove → delete).
|
|
*/
|
|
(function () {
|
|
'use strict';
|
|
var T = window.ICD;
|
|
if (!T) return;
|
|
if (!T.crud) T.crud = {};
|
|
|
|
T.crud.teams = async function (testTag) {
|
|
|
|
// ── Teams CRUD (admin — create team, exercise team-scoped endpoints) ──
|
|
if (T.user.role === 'admin') {
|
|
var teamId = null;
|
|
|
|
await T.test('crud', 'teams', 'POST /admin/teams (create)', async function () {
|
|
var d = await T.apiPost('/admin/teams', { name: testTag + '-team', description: 'ICD test team' });
|
|
T.assertShape(d, T.S.team, 'team');
|
|
teamId = d.id;
|
|
T.registerCleanup(function () { if (teamId) return T.safeDelete('/admin/teams/' + teamId); });
|
|
});
|
|
|
|
if (teamId) {
|
|
await T.test('crud', 'teams', 'GET /admin/teams/:id (read)', async function () {
|
|
var d = await T.apiGet('/admin/teams/' + teamId);
|
|
T.assertShape(d, T.S.team, 'team');
|
|
T.assert(d.id === teamId, 'id mismatch');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'PUT /admin/teams/:id (update)', async function () {
|
|
var d = await T.apiPut('/admin/teams/' + teamId, { name: testTag + '-team-updated' });
|
|
T.assert(typeof d === 'object', 'expected object');
|
|
});
|
|
|
|
// Add current user as team admin
|
|
await T.test('crud', 'teams', 'POST /admin/teams/:id/members (add self)', async function () {
|
|
var d = await T.apiPost('/admin/teams/' + teamId + '/members', { user_id: T.user.id, role: 'admin' });
|
|
T.assert(typeof d === 'object', 'expected object');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'GET /admin/teams/:id/members', async function () {
|
|
var d = await T.apiGet('/admin/teams/' + teamId + '/members');
|
|
T.assertHasKey(d, 'data', '/team-members');
|
|
T.assert(Array.isArray(d.data), 'data should be array');
|
|
T.assert(d.data.length >= 1, 'expected at least 1 member');
|
|
});
|
|
|
|
// Team-scoped endpoints (via team admin routes)
|
|
await T.test('crud', 'teams', 'GET /teams/:teamId/members (team route)', async function () {
|
|
var d = await T.apiGet('/teams/' + teamId + '/members');
|
|
T.assertHasKey(d, 'data', '/team-members');
|
|
});
|
|
|
|
// Member update + remove (need a second user — use admin self-membership)
|
|
await T.test('crud', 'teams', 'PUT /admin/teams/:id/members/:mid (update role)', async function () {
|
|
// Get own membership ID
|
|
var d = await T.apiGet('/admin/teams/' + teamId + '/members');
|
|
var me = d.data.find(function (m) { return m.user_id === T.user.id; });
|
|
T.assert(me, 'should find self in members');
|
|
// Update role to member then back to admin
|
|
var r = await T.apiPut('/admin/teams/' + teamId + '/members/' + me.id, { role: 'member' });
|
|
T.assert(typeof r === 'object', 'expected object');
|
|
// Restore admin role
|
|
await T.apiPut('/admin/teams/' + teamId + '/members/' + me.id, { role: 'admin' });
|
|
});
|
|
|
|
// Team groups listing
|
|
await T.test('crud', 'teams', 'GET /teams/:teamId/groups', async function () {
|
|
var d = await T.apiGet('/teams/' + teamId + '/groups');
|
|
T.assertHasKey(d, 'data', '/team-groups');
|
|
T.assert(Array.isArray(d.data), 'data should be array');
|
|
});
|
|
|
|
// ── Team Tasks CRUD ──
|
|
var teamTaskId = null;
|
|
|
|
await T.test('crud', 'teams', 'POST /teams/:teamId/tasks (create team task)', async function () {
|
|
var d = await T.apiPost('/teams/' + teamId + '/tasks', {
|
|
name: testTag + '-team-task',
|
|
task_type: 'prompt',
|
|
schedule: '@daily',
|
|
user_prompt: 'Team task test',
|
|
model_id: 'test-model'
|
|
});
|
|
T.assertShape(d, T.S.taskFull, 'team task');
|
|
T.assert(d.scope === 'team', 'team task scope should be team');
|
|
T.assert(d.team_id === teamId, 'team_id should match');
|
|
teamTaskId = d.id;
|
|
T.registerCleanup(function () { if (teamTaskId) return T.safeDelete('/teams/' + teamId + '/tasks/' + teamTaskId); });
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'GET /teams/:teamId/tasks (list team tasks)', async function () {
|
|
var d = await T.apiGet('/teams/' + teamId + '/tasks');
|
|
T.assertHasKey(d, 'data', '/team-tasks');
|
|
T.assert(Array.isArray(d.data), 'data should be array');
|
|
if (teamTaskId) {
|
|
T.assert(d.data.length >= 1, 'should have at least 1 team task');
|
|
var found = d.data.some(function (t) { return t.id === teamTaskId; });
|
|
T.assert(found, 'created team task should be in list');
|
|
}
|
|
});
|
|
|
|
if (teamTaskId) {
|
|
await T.test('crud', 'teams', 'PUT /teams/:teamId/tasks/:id (update team task)', async function () {
|
|
var d = await T.apiPut('/teams/' + teamId + '/tasks/' + teamTaskId, {
|
|
name: testTag + '-team-task-updated'
|
|
});
|
|
T.assert(d.name === testTag + '-team-task-updated', 'name not updated');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'GET /teams/:teamId/tasks/:id/runs (team task runs)', async function () {
|
|
var d = await T.apiGet('/teams/' + teamId + '/tasks/' + teamTaskId + '/runs');
|
|
T.assertHasKey(d, 'data', '/team-task-runs');
|
|
T.assert(Array.isArray(d.data), 'data should be array');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'POST /teams/:teamId/tasks/:id/run (team task run)', async function () {
|
|
var d = await T.apiPost('/teams/' + teamId + '/tasks/' + teamTaskId + '/run', {});
|
|
T.assertHasKey(d, 'scheduled', '/team-run');
|
|
T.assert(d.scheduled === true, 'should be scheduled');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'POST /teams/:teamId/tasks/:id/kill (team task kill)', async function () {
|
|
var token = await T.getAuthToken();
|
|
var d = await T.authFetch(token, 'POST', '/teams/' + teamId + '/tasks/' + teamTaskId + '/kill');
|
|
T.assert(d._status === 200 || d._status === 404,
|
|
'expected 200 or 404, got ' + d._status);
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'DELETE /teams/:teamId/tasks/:id (delete team task)', async function () {
|
|
await T.safeDelete('/teams/' + teamId + '/tasks/' + teamTaskId);
|
|
teamTaskId = null;
|
|
});
|
|
}
|
|
|
|
await T.test('crud', 'teams', 'GET /teams/:teamId/roles', async function () {
|
|
var d = await T.apiGet('/teams/' + teamId + '/roles');
|
|
T.assertHasKey(d, 'data', '/team-roles');
|
|
T.assert(typeof d.data === 'object', 'data should be object (role overrides map)');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'GET /teams/:teamId/audit', async function () {
|
|
var d = await T.apiGet('/teams/' + teamId + '/audit');
|
|
T.assertHasKey(d, 'data', '/team-audit');
|
|
T.assert(Array.isArray(d.data), 'data should be array');
|
|
T.assertHasKey(d, 'total', '/team-audit pagination');
|
|
T.assertHasKey(d, 'page', '/team-audit pagination');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'GET /teams/:teamId/audit/actions', async function () {
|
|
var d = await T.apiGet('/teams/' + teamId + '/audit/actions');
|
|
T.assertHasKey(d, 'actions', '/team-audit-actions');
|
|
T.assert(Array.isArray(d.actions), 'actions should be array');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'GET /teams/:teamId/usage', async function () {
|
|
var d = await T.apiGet('/teams/' + teamId + '/usage');
|
|
T.assertHasKey(d, 'totals', '/team-usage');
|
|
T.assertHasKey(d, 'results', '/team-usage');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'GET /teams/:teamId/assignments', async function () {
|
|
var d = await T.apiGet('/teams/' + teamId + '/assignments');
|
|
T.assert(typeof d === 'object', 'expected object');
|
|
});
|
|
|
|
// Admin groups CRUD within team context
|
|
var groupId = null;
|
|
|
|
await T.test('crud', 'teams', 'POST /admin/groups (create team group)', async function () {
|
|
var d = await T.apiPost('/admin/groups', { name: testTag + '-group', scope: 'team', team_id: teamId });
|
|
T.assertShape(d, T.S.group, 'group');
|
|
groupId = d.id;
|
|
T.registerCleanup(function () { if (groupId) return T.safeDelete('/admin/groups/' + groupId); });
|
|
});
|
|
|
|
if (groupId) {
|
|
await T.test('crud', 'teams', 'GET /admin/groups/:id', async function () {
|
|
var d = await T.apiGet('/admin/groups/' + groupId);
|
|
T.assertShape(d, T.S.group, 'group');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'PUT /admin/groups/:id (set permissions)', async function () {
|
|
var d = await T.apiPut('/admin/groups/' + groupId, {
|
|
permissions: ['model.use', 'kb.read', 'channel.create']
|
|
});
|
|
T.assert(typeof d === 'object', 'expected object');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'POST /admin/groups/:id/members (add self)', async function () {
|
|
var d = await T.apiPost('/admin/groups/' + groupId + '/members', { user_id: T.user.id });
|
|
T.assert(typeof d === 'object', 'expected object');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'GET /admin/groups/:id/members', async function () {
|
|
var d = await T.apiGet('/admin/groups/' + groupId + '/members');
|
|
T.assertHasKey(d, 'data', '/group-members');
|
|
T.assert(Array.isArray(d.data), 'data should be array');
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'DELETE /admin/groups/:id/members/:uid', async function () {
|
|
await T.safeDelete('/admin/groups/' + groupId + '/members/' + T.user.id);
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'DELETE /admin/groups/:id', async function () {
|
|
await T.safeDelete('/admin/groups/' + groupId);
|
|
groupId = null;
|
|
});
|
|
}
|
|
|
|
// Grants
|
|
await T.test('crud', 'teams', 'GET /admin/grants/persona/:id (404 = no grant)', async function () {
|
|
// No persona to test, just verify the endpoint responds
|
|
try {
|
|
var d = await T.apiGet('/admin/grants/persona/00000000-0000-0000-0000-000000000000');
|
|
T.assert(typeof d === 'object', 'expected object');
|
|
} catch (e) {
|
|
if (e.message && e.message.indexOf('404') !== -1) return; // expected
|
|
throw e;
|
|
}
|
|
});
|
|
|
|
// User permissions
|
|
await T.test('crud', 'teams', 'GET /admin/users/:id/permissions', async function () {
|
|
var d = await T.apiGet('/admin/users/' + T.user.id + '/permissions');
|
|
T.assertHasKey(d, 'permissions', '/user-permissions');
|
|
T.assert(Array.isArray(d.permissions), 'permissions should be array');
|
|
T.assertHasKey(d, 'user_id', '/user-permissions');
|
|
T.assertHasKey(d, 'groups', '/user-permissions contributing groups');
|
|
T.assert(Array.isArray(d.groups), 'groups should be array');
|
|
});
|
|
|
|
// List all permissions
|
|
await T.test('crud', 'teams', 'GET /admin/permissions', async function () {
|
|
var d = await T.apiGet('/admin/permissions');
|
|
T.assertHasKey(d, 'permissions', '/admin/permissions');
|
|
T.assert(Array.isArray(d.permissions), 'permissions should be array');
|
|
T.assert(d.permissions.length > 0, 'should have at least 1 permission');
|
|
});
|
|
|
|
// Member remove (remove self then re-add to keep admin for cleanup)
|
|
await T.test('crud', 'teams', 'DELETE /admin/teams/:id/members/:mid (remove)', async function () {
|
|
var d = await T.apiGet('/admin/teams/' + teamId + '/members');
|
|
var me = d.data.find(function (m) { return m.user_id === T.user.id; });
|
|
T.assert(me, 'should find self in members');
|
|
var r = await T.safeDelete('/admin/teams/' + teamId + '/members/' + me.id);
|
|
T.assert(typeof r === 'object' || r === undefined, 'expected object or void');
|
|
// Re-add self as admin for cleanup
|
|
await T.apiPost('/admin/teams/' + teamId + '/members', { user_id: T.user.id, role: 'admin' });
|
|
});
|
|
|
|
await T.test('crud', 'teams', 'DELETE /admin/teams/:id', async function () {
|
|
await T.safeDelete('/admin/teams/' + teamId);
|
|
teamId = null;
|
|
});
|
|
}
|
|
}
|
|
|
|
};
|
|
})();
|