/** * Smoke test del rol "auditor" (solo lectura total) y no-regresion del rol * "editor". * * El rol efectivo se lee de ACAI_ROLE_OVERRIDE en CADA llamada a * getEffectiveRole(), pero los `tools//index.js` lo consultan en el * momento del REGISTRO. Por eso basta con fijar la env var antes de invocar * las funciones de registro (no hace falta recargar modulos). */ import assert from "node:assert/strict"; import test from "node:test"; import { registerRecordTools } from "../tools/records/index.js"; import { registerTableTools } from "../tools/tables/index.js"; import { registerMediaTools } from "../tools/media/index.js"; import { registerLanguageTools } from "../tools/languages/index.js"; import { registerFileTools } from "../tools/files/index.js"; import { registerModuleTools } from "../tools/modules/index.js"; import { registerLayoutTools } from "../tools/layout/index.js"; import { registerHookTools } from "../tools/hooks/index.js"; import { registerLibrariesTools } from "../tools/libraries/index.js"; import { registerProjectTools } from "../tools/project/index.js"; const GROUPS = { records: registerRecordTools, tables: registerTableTools, media: registerMediaTools, languages: registerLanguageTools, files: registerFileTools, modules: registerModuleTools, layout: registerLayoutTools, hooks: registerHookTools, libraries: registerLibrariesTools, project: registerProjectTools, }; /** Server MCP falso: solo acumula los nombres de tool registrados. */ function createFakeServer() { const names = []; return { names, tool(name) { names.push(name); }, }; } function registerWithRole(role, groupName) { const previous = process.env.ACAI_ROLE_OVERRIDE; process.env.ACAI_ROLE_OVERRIDE = role; try { const server = createFakeServer(); GROUPS[groupName](server); return server.names; } finally { if (previous === undefined) delete process.env.ACAI_ROLE_OVERRIDE; else process.env.ACAI_ROLE_OVERRIDE = previous; } } // --------------------------------------------------------------------------- // Rol "auditor": SOLO lectura. Listas exactas (nombre y orden de registro). // --------------------------------------------------------------------------- const AUDITOR_EXPECTED = { records: [ "list_table_records", "get_record", "list_page_modules", "get_module_config_vars", ], tables: [ "list_tables", "get_table_schema", ], media: [ "analyze_image", ], languages: [ "list_web_languages", "get_record_translations", ], files: [ "acai-view", "acai-glob", "acai-grep", ], modules: [ "check_module", "check_module_usage", ], layout: [ "get_layout_field", ], hooks: [ "get_hook_middleware", "get_hook_entryparams", ], libraries: [ "list_global_libraries", ], project: [ "get_web_url", ], }; // --------------------------------------------------------------------------- // Rol "editor": exactamente lo mismo que registraba ANTES de introducir el rol // auditor (derivado del codigo previo al cambio). Si este bloque se rompe, la // regresion es real. // --------------------------------------------------------------------------- const EDITOR_EXPECTED = { records: [ "list_table_records", "get_record", "create_or_update_record", "delete_table_records", "add_module_to_record", "remove_module_from_record", "list_page_modules", "reorder_module", "toggle_module_visibility", "set_module_config_vars", "get_module_config_vars", ], tables: [ "list_tables", "get_table_schema", "create_table", "update_table_metadata", "delete_table", "reorder_tables", "create_field", "update_field", "delete_field", "reorder_fields", "regenerate_enlaces", ], media: [ "upload_record_image", "list_record_uploads", "replace_record_image", "delete_record_upload", "reorder_record_uploads", "upload_image_to_assets", "generate_image", "analyze_image", "set_upload_info", ], languages: [ "list_web_languages", "get_record_translations", "set_record_translations", ], files: [ "acai-view", "acai-glob", "acai-grep", ], modules: [ "check_module", "check_module_usage", ], layout: [ "get_layout_field", ], hooks: [ "get_hook_middleware", "get_hook_entryparams", ], libraries: [ "list_global_libraries", ], project: [ "get_web_url", ], }; for (const [group, expected] of Object.entries(AUDITOR_EXPECTED)) { test(`auditor: ${group} registra solo tools de lectura`, () => { assert.deepEqual(registerWithRole("auditor", group), expected); }); } for (const [group, expected] of Object.entries(EDITOR_EXPECTED)) { test(`editor: ${group} sigue registrando lo mismo que antes`, () => { assert.deepEqual(registerWithRole("editor", group), expected); }); } test("auditor: ninguna tool de escritura conocida queda expuesta", () => { const registered = new Set( Object.keys(GROUPS).flatMap((group) => registerWithRole("auditor", group)) ); const writeTools = [ "create_or_update_record", "delete_table_records", "add_module_to_record", "remove_module_from_record", "reorder_module", "toggle_module_visibility", "set_module_config_vars", "create_table", "update_table_metadata", "delete_table", "reorder_tables", "create_field", "update_field", "delete_field", "reorder_fields", "regenerate_enlaces", "upload_record_image", "replace_record_image", "delete_record_upload", "reorder_record_uploads", "upload_image_to_assets", "generate_image", "set_upload_info", "set_record_translations", "acai-write", "acai-line-replace", "acai-delete", "compile_module", "delete_module", "update_module_metadata", "set_layout_field", "set_hook_middleware", "set_hook_entryparams", "add_global_library", "remove_global_library", "set_global_libraries", "save_project_styles", ]; for (const name of writeTools) { assert.equal(registered.has(name), false, `tool de escritura expuesta al auditor: ${name}`); } }); test("developer: conserva las tools de escritura de codigo", () => { const files = registerWithRole("developer", "files"); assert.deepEqual(files, [ "acai-view", "acai-glob", "acai-grep", "acai-write", "acai-line-replace", "acai-delete", ]); });