import assert from "node:assert/strict"; import test from "node:test"; import { AsyncLocalStorageOperationContextStore, type IModuleServer, type ModuleParams, SequelizeTransactionContext, } from "@erp/core/api"; import { Audit } from "@repo/rdx-audit"; import { Result } from "@repo/rdx-utils"; import { resetModelsRegistry } from "./model-loader"; import { initModules, registerModule, resetModuleRegistry, startModules } from "./module-loader"; import { listServices, resetServiceRegistry } from "./service-registry"; type FakeDatabase = { sync: () => Promise; models: Record; }; function buildModuleParams(): ModuleParams & { config: { database: { syncMode: string } }; database: FakeDatabase; logger: Console; } { return { getService: () => { throw new Error("Base params getService should not be called directly in tests."); }, config: { database: { syncMode: "none", }, }, warmup: { timeoutMs: 10_000, strict: false }, databaseSyncMode: "none", database: { sync: async () => undefined, models: {}, }, logger: console, operationContext: new AsyncLocalStorageOperationContextStore(), transactionContext: new SequelizeTransactionContext(), audit: new Audit({ source: { type: "test", id: "module-loader" }, contextProvider: { get: () => null }, store: { append: async () => Result.ok() }, }), }; } function resetModuleTestState(): void { resetServiceRegistry(); resetModuleRegistry(); resetModelsRegistry(); } test("module-loader permite dependencias externas declaradas", async (t) => { resetModuleTestState(); t.after(resetModuleTestState); let receivedService = ""; const catalogsModule: IModuleServer = { name: "catalogs", version: "1.0.0", setup: () => ({ services: { general: { name: "catalogs-service", }, }, }), }; const companiesModule: IModuleServer = { name: "companies", version: "1.0.0", dependencies: ["catalogs"], start: ({ getService }) => { const service = getService<{ name: string }>("catalogs:general"); receivedService = service.name; }, }; registerModule(catalogsModule); registerModule(companiesModule); await initModules(buildModuleParams()); await startModules(buildModuleParams()); assert.equal(receivedService, "catalogs-service"); assert.deepEqual(listServices().sort(), ["catalogs:general"]); }); test("module-loader falla cuando una dependencia usada no está declarada", async (t) => { resetModuleTestState(); t.after(resetModuleTestState); registerModule({ name: "catalogs", version: "1.0.0", setup: () => ({ services: { general: { name: "catalogs-service", }, }, }), }); registerModule({ name: "companies", version: "1.0.0", start: ({ getService }) => { getService("catalogs:general"); }, }); await assert.rejects( async () => { await initModules(buildModuleParams()); await startModules(buildModuleParams()); }, /without declaring dependency on "catalogs"/i ); }); test("module-loader sigue detectando ciclos reales entre módulos", async (t) => { resetModuleTestState(); t.after(resetModuleTestState); registerModule({ name: "companies", version: "1.0.0", dependencies: ["catalogs"], }); registerModule({ name: "catalogs", version: "1.0.0", dependencies: ["companies"], }); await assert.rejects(() => initModules(buildModuleParams()), /cyclic dependency detected/i); }); test("module-loader rechaza la autoreferencia explícita por nombre de módulo", async (t) => { resetModuleTestState(); t.after(resetModuleTestState); registerModule({ name: "companies", version: "1.0.0", setup: () => ({ services: { general: { name: "companies-service", }, }, }), start: ({ getService }) => { getService("companies:general"); }, }); await assert.rejects( async () => { await initModules(buildModuleParams()); await startModules(buildModuleParams()); }, /without declaring dependency on "companies"/i ); });