/** * ICD Test Runner — CRUD: Personas * Admin persona lifecycle (create → list → update → tool grants → * KB bindings → delete), personal persona (policy-gated), team persona * (fixture team), persona groups CRUD. */ (function () { 'use strict'; var T = window.ICD; if (!T) return; if (!T.crud) T.crud = {}; T.crud.personas = async function (testTag) { // ── Personas CRUD (admin + personal + team + tool grants + groups) ── var personaId = ''; var personalPersonaId = ''; var teamPersonaId = ''; // Admin persona CRUD await T.test('crud', 'personas', 'POST /admin/personas (create global)', async function () { var d = await T.apiPost('/admin/personas', { name: 'ICD Test Persona', base_model_id: 'test-model', system_prompt: 'You are a test persona.', description: 'Created by ICD test runner' }); T.assert(d.id, 'persona should have id'); T.assert(d.scope === 'global', 'admin persona scope should be global'); T.assert(d.handle, 'handle should be auto-generated'); personaId = d.id; }); if (personaId) { await T.test('crud', 'personas', 'GET /admin/personas (list contains new)', async function () { var d = await T.apiGet('/admin/personas'); T.assertHasKey(d, 'data', '/admin/personas'); var found = d.data.some(function (p) { return p.id === personaId; }); T.assert(found, 'created persona should appear in admin list'); }); await T.test('crud', 'personas', 'PUT /admin/personas/:id (update)', async function () { var d = await T.apiPut('/admin/personas/' + personaId, { name: 'ICD Updated Persona' }); T.assert(d.message === 'persona updated', 'expected update confirmation'); }); // Tool grants await T.test('crud', 'personas', 'GET /admin/personas/:id/tool-grants (empty)', async function () { var d = await T.apiGet('/admin/personas/' + personaId + '/tool-grants'); T.assertHasKey(d, 'data', 'tool-grants'); T.assert(Array.isArray(d.data), 'grants should be array'); T.assert(d.data.length === 0, 'initial grants should be empty'); }); await T.test('crud', 'personas', 'PUT /admin/personas/:id/tool-grants (set)', async function () { var d = await T.apiPut('/admin/personas/' + personaId + '/tool-grants', { tool_names: ['web_search', 'calculator'] }); T.assert(d.status === 'ok', 'expected ok'); }); await T.test('crud', 'personas', 'GET /admin/personas/:id/tool-grants (verify)', async function () { var d = await T.apiGet('/admin/personas/' + personaId + '/tool-grants'); T.assert(d.data.length === 2, 'should have 2 grants, got ' + d.data.length); }); await T.test('crud', 'personas', 'PUT /admin/personas/:id/tool-grants (clear)', async function () { await T.apiPut('/admin/personas/' + personaId + '/tool-grants', { tool_names: [] }); var d = await T.apiGet('/admin/personas/' + personaId + '/tool-grants'); T.assert(d.data.length === 0, 'grants should be empty after clear'); }); // KB bindings await T.test('crud', 'personas', 'GET /admin/personas/:id/knowledge-bases (empty)', async function () { var d = await T.apiGet('/admin/personas/' + personaId + '/knowledge-bases'); T.assertHasKey(d, 'data', 'persona-kbs'); T.assert(Array.isArray(d.data), 'kbs should be array'); }); await T.test('crud', 'personas', 'DELETE /admin/personas/:id', async function () { await T.apiDelete('/admin/personas/' + personaId); }); await T.test('crud', 'personas', 'GET /admin/personas (deleted gone)', async function () { var d = await T.apiGet('/admin/personas'); var found = d.data.some(function (p) { return p.id === personaId; }); T.assert(!found, 'deleted persona should not appear in list'); }); } // Personal persona (policy-gated) await T.test('crud', 'personas', 'POST /personas (create personal)', async function () { // Ensure policy allows personal personas try { await T.apiPut('/admin/settings/allow_user_personas', { value: 'true' }); } catch (e) { /* may already be set */ } var d = await T.apiPost('/personas', { name: 'ICD Personal Bot', base_model_id: 'test-model', system_prompt: 'Personal test.' }); T.assert(d.id, 'personal persona should have id'); T.assert(d.scope === 'personal', 'scope should be personal'); personalPersonaId = d.id; }); if (personalPersonaId) { await T.test('crud', 'personas', 'PUT /personas/:id (update personal)', async function () { var d = await T.apiPut('/personas/' + personalPersonaId, { description: 'updated desc' }); T.assert(d.message === 'persona updated', 'expected update confirmation'); }); await T.test('crud', 'personas', 'GET /personas (list contains personal)', async function () { var d = await T.apiGet('/personas'); T.assertHasKey(d, 'data', '/personas'); var found = d.data.some(function (p) { return p.id === personalPersonaId; }); T.assert(found, 'personal persona should appear in user list'); }); await T.test('crud', 'personas', 'DELETE /personas/:id (delete personal)', async function () { await T.apiDelete('/personas/' + personalPersonaId); }); } // Team persona (uses fixture team) if (T.fixtures.team) { var tId = T.fixtures.team.id; await T.test('crud', 'personas', 'POST /teams/:teamId/personas (create)', async function () { var d = await T.apiPost('/teams/' + tId + '/personas', { name: 'ICD Team Bot', base_model_id: 'test-model', system_prompt: 'Team test.' }); T.assert(d.id, 'team persona should have id'); T.assert(d.scope === 'team', 'scope should be team'); teamPersonaId = d.id; }); if (teamPersonaId) { await T.test('crud', 'personas', 'PUT /teams/:teamId/personas/:id (update)', async function () { var d = await T.apiPut('/teams/' + tId + '/personas/' + teamPersonaId, { name: 'ICD Team Bot v2' }); T.assert(d.message === 'persona updated', 'expected update confirmation'); }); await T.test('crud', 'personas', 'GET /teams/:teamId/personas/:id/tool-grants', async function () { var d = await T.apiGet('/teams/' + tId + '/personas/' + teamPersonaId + '/tool-grants'); T.assertHasKey(d, 'data', 'team-tool-grants'); T.assert(Array.isArray(d.data), 'grants should be array'); }); await T.test('crud', 'personas', 'PUT /teams/:teamId/personas/:id/tool-grants (set)', async function () { await T.apiPut('/teams/' + tId + '/personas/' + teamPersonaId + '/tool-grants', { tool_names: ['kb_search'] }); var d = await T.apiGet('/teams/' + tId + '/personas/' + teamPersonaId + '/tool-grants'); T.assert(d.data.length === 1, 'team persona should have 1 grant'); }); await T.test('crud', 'personas', 'GET /teams/:teamId/personas/:id/knowledge-bases', async function () { var d = await T.apiGet('/teams/' + tId + '/personas/' + teamPersonaId + '/knowledge-bases'); T.assertHasKey(d, 'data', 'team-persona-kbs'); }); await T.test('crud', 'personas', 'DELETE /teams/:teamId/personas/:id', async function () { await T.apiDelete('/teams/' + tId + '/personas/' + teamPersonaId); }); await T.test('crud', 'personas', 'GET /teams/:teamId/personas (deleted gone)', async function () { var d = await T.apiGet('/teams/' + tId + '/personas'); var found = d.data.some(function (p) { return p.id === teamPersonaId; }); T.assert(!found, 'deleted team persona should not appear in list'); }); } } // Persona Groups CRUD var pgId = ''; var pgMemberId = ''; await T.test('crud', 'personas', 'POST /persona-groups (create)', async function () { var d = await T.apiPost('/persona-groups', { name: 'ICD Test Group', description: 'Test' }); T.assert(d.id, 'group should have id'); T.assert(d.scope === 'personal', 'group scope should be personal'); pgId = d.id; }); if (pgId) { await T.test('crud', 'personas', 'GET /persona-groups/:id (read)', async function () { var d = await T.apiGet('/persona-groups/' + pgId); T.assert(d.id === pgId, 'group id should match'); T.assert(d.name === 'ICD Test Group', 'group name should match'); T.assert(Array.isArray(d.members), 'members should be array'); }); await T.test('crud', 'personas', 'PUT /persona-groups/:id (update)', async function () { var d = await T.apiPut('/persona-groups/' + pgId, { name: 'ICD Updated Group' }); T.assert(d.ok === true, 'expected ok'); }); await T.test('crud', 'personas', 'GET /persona-groups (list)', async function () { var d = await T.apiGet('/persona-groups'); T.assertHasKey(d, 'data', '/persona-groups'); var found = d.data.some(function (g) { return g.id === pgId; }); T.assert(found, 'created group should appear in list'); }); // Add a member (need a persona — create a temporary one) var tmpPersonaId = ''; await T.test('crud', 'personas', 'POST /admin/personas (for group member)', async function () { var d = await T.apiPost('/admin/personas', { name: 'Group Member Bot', base_model_id: 'test-model' }); tmpPersonaId = d.id; }); if (tmpPersonaId) { await T.test('crud', 'personas', 'POST /persona-groups/:id/members (add)', async function () { var d = await T.apiPost('/persona-groups/' + pgId + '/members', { persona_id: tmpPersonaId, is_leader: true }); T.assert(d.ok === true, 'expected ok'); }); await T.test('crud', 'personas', 'GET /persona-groups/:id (with member)', async function () { var d = await T.apiGet('/persona-groups/' + pgId); T.assert(d.members.length === 1, 'should have 1 member'); T.assert(d.members[0].is_leader === true, 'member should be leader'); pgMemberId = d.members[0].id; }); if (pgMemberId) { await T.test('crud', 'personas', 'DELETE /persona-groups/:id/members/:mid', async function () { await T.apiDelete('/persona-groups/' + pgId + '/members/' + pgMemberId); }); await T.test('crud', 'personas', 'GET /persona-groups/:id (member removed)', async function () { var d = await T.apiGet('/persona-groups/' + pgId); T.assert(d.members.length === 0, 'should have 0 members after removal'); }); } // Cleanup temp persona try { await T.apiDelete('/admin/personas/' + tmpPersonaId); } catch (e) { /* ok */ } } await T.test('crud', 'personas', 'DELETE /persona-groups/:id', async function () { await T.apiDelete('/persona-groups/' + pgId); }); } }; })();