/** * 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'); }); await T.test('crud', 'teams', 'GET /teams/:teamId/personas', async function () { var d = await T.apiGet('/teams/' + teamId + '/personas'); T.assertHasKey(d, 'data', '/team-personas'); T.assert(Array.isArray(d.data), 'team personas should be array'); }); await T.test('crud', 'teams', 'GET /teams/:teamId/providers', async function () { var d = await T.apiGet('/teams/' + teamId + '/providers'); T.assertHasKey(d, 'data', '/team-providers'); T.assert(Array.isArray(d.data), 'data should be array'); T.assertHasKey(d, 'allow_team_providers', '/team-providers'); }); await T.test('crud', 'teams', 'GET /teams/:teamId/models', async function () { var d = await T.apiGet('/teams/' + teamId + '/models'); T.assertHasKey(d, 'models', '/team-models'); }); // ── 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; }); } } }; })();