import assert from "node:assert/strict"; import { createAssetModule } from "@metis/assets/module"; import { createCompanyModule } from "@metis/companies/module"; import { createIdentityModule, migrateIdentity } from "@metis/identity/module"; import { createResourceModule } from "@metis/resources/module"; import express from "express"; import session from "express-session"; import type { Sequelize } from "sequelize"; import { AssetUseCases } from "../../../modules/assets/src/api/application/asset.use-cases"; import { DirectoryMembershipUseCases } from "../../../modules/assets/src/api/application/directory-membership.use-cases"; import { SequelizeAssetRepository } from "../../../modules/assets/src/api/infrastructure/persistence/sequelize-asset.repository"; import { SequelizeDirectoryMembershipRepository } from "../../../modules/assets/src/api/infrastructure/persistence/sequelize-directory-membership.repository"; import { ResourceUseCases } from "../../../modules/resources/src/api/application/resource.use-cases"; import { SequelizeResourceRepository } from "../../../modules/resources/src/api/infrastructure/persistence/sequelize-resource.repository"; import { UniqueID } from "../../../packages/rdx-ddd/src/index"; import { registerDirectoryRelationshipRoutes } from "./infrastructure/http/directory-relationship.routes"; import { createRelationshipRuntime } from "./infrastructure/http/relationship-runtime"; export async function prepareMembershipFixture( database: Sequelize, origin: string, actorId: string, companyIds: readonly string[] ) { await migrateIdentity(database); const identity = await createIdentityModule(database, origin); const companies = await createCompanyModule(database, identity.authenticate); const resources = await createResourceModule( database, identity.authenticate, companies.directoryContexts, companies.resourceAccess ); const assets = await createAssetModule( database, identity.authenticate, resources.services.directoryDomains, resources.services.networkShares ); const localIdentities = await companies.initializeLocalIdentities( assets.services.localIdentities ); await database.query( "INSERT INTO users (id,name,email,passwordHash,active,isInitialAdmin,authVersion) VALUES (:actorId,'Membership fixture',:email,'fixture-not-authenticatable',1,0,1)", { replacements: { actorId, email: `${actorId}@example.invalid` } } ); for (const id of companyIds) { await database.query("INSERT INTO companies (id,name,active) VALUES (:id,:name,1)", { replacements: { id, name: `Membership fixture ${id}` }, }); } const app = express(); app.use(express.json()); app.use( session({ secret: "fixture-only-secret-for-membership-tests", resave: false, saveUninitialized: false, }) ); // Solo esta aplicación de prueba emite la sesión fixture; las rutas /api usan las protecciones reales. app.get("/fixture-session", (request, response) => { request.session.userId = actorId; request.session.authVersion = 1; request.session.csrfToken = "a".repeat(64); response.json({ csrfToken: request.session.csrfToken }); }); const relationships = createRelationshipRuntime(identity, companies, assets, resources); identity.register(app); registerDirectoryRelationshipRoutes(app, relationships, identity.authenticate); assets.register(app); localIdentities.register(app); resources.register(app); const resourceUseCases = new ResourceUseCases( new SequelizeResourceRepository(database, companies.directoryContexts, companies.resourceAccess) ); const assetUseCases = new AssetUseCases(new SequelizeAssetRepository(database)); const membershipUseCases = new DirectoryMembershipUseCases( new SequelizeDirectoryMembershipRepository(database, resources.services.directoryDomains) ); async function domain(companyId: string, dnsName: string) { const loaded = await resourceUseCases.get(companyId); assert.ok(loaded.isSuccess); const template = loaded.data.catalog.definitions.find( (item) => item.stockKey.getOrUndefined() === "template:windows_domain" ); const dns = loaded.data.catalog.definitions.find( (item) => item.stockKey.getOrUndefined() === "field:domain_dns" ); assert.ok(template && dns); const saved = await resourceUseCases.save( companyId, undefined, { name: dnsName, templateId: template.id.toPrimitive(), active: true, notes: "", characteristics: { [dns.id.toPrimitive()]: dnsName }, }, actorId ); assert.ok(saved.isSuccess && saved.data.directory); return saved.data.resource; } async function asset(companyId: string, key = "server") { const loaded = await assetUseCases.get(companyId); assert.ok(loaded.isSuccess); const template = loaded.data.catalog.definitions.find( (item) => item.stockKey.getOrUndefined() === `template:${key}` ); assert.ok(template); const saved = await assetUseCases.save( companyId, undefined, { name: `Fixture ${UniqueID.generateNewID().toPrimitive()}`, templateId: template.id.toPrimitive(), status: "operational", siteId: null, notes: "", characteristics: {}, }, actorId ); assert.ok(saved.isSuccess); return saved.data.asset; } return { app, localIdentities: localIdentities.useCases, inventory: assets.services.inventory, authenticate: identity.authenticate, resourceAccess: companies.resourceAccess, networkShareReader: resources.services.networkShares, domain, asset, resourceUseCases, assetUseCases, membershipUseCases, domainReader: resources.services.directoryDomains, relationships, }; } /** Limpieza por UUID de fixture, sin desactivar FK ni tocar inventarios ajenos. */ export async function cleanupMembershipFixture( database: Sequelize, companyIds: readonly string[], actorId: string ) { for (const id of companyIds) { await database.query("DELETE FROM access_local_identity_environments WHERE companyId=:id", { replacements: { id }, }); await database.query( "UPDATE asset_network_share_publications SET predecessorId=NULL WHERE companyId=:id", { replacements: { id } } ); await database.query("DELETE FROM asset_network_share_publications WHERE companyId=:id", { replacements: { id }, }); await database.query("DELETE FROM resource_network_shares WHERE companyId=:id", { replacements: { id }, }); for (const kind of ["resource", "resource_type"]) { await database.query( `DELETE v FROM access_${kind}_revision_values v JOIN access_revisions r ON r.id=v.ownerId JOIN access_items i ON i.id=r.itemId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:id`, { replacements: { id } } ); await database.query( `DELETE v FROM access_${kind}_values v JOIN access_items i ON i.id=v.ownerId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:id`, { replacements: { id } } ); } await database.query( "DELETE v FROM asset_directory_membership_revisions v JOIN asset_directory_memberships m ON m.id=v.membershipId WHERE m.companyId=:id", { replacements: { id } } ); await database.query( "UPDATE asset_directory_memberships SET predecessorId=NULL WHERE companyId=:id", { replacements: { id } } ); await database.query("DELETE FROM asset_directory_memberships WHERE companyId=:id", { replacements: { id }, }); for (const entity of ["asset", "resource"]) { const root = entity === "asset" ? "assets" : "resources"; const tables = [ `${entity}_revision_characteristics`, `${entity}_revision_specification_choices`, `${entity}_revision_specification_fields`, `${entity}_revision_specification_sections`, `${entity}_revision_specifications`, `${entity}_characteristics`, `${entity}_specification_field_choices`, `${entity}_specification_fields`, `${entity}_specification_sections`, `${entity}_revisions`, ]; for (const table of tables) { await database.query( `DELETE FROM ${table} WHERE ${entity}Id IN (SELECT id FROM ${root} WHERE companyId=:id)`, { replacements: { id } } ); } } await database.query("DELETE FROM assets WHERE companyId=:id", { replacements: { id } }); await database.query("DELETE FROM resource_directory_domains WHERE companyId=:id", { replacements: { id }, }); await database.query("DELETE FROM resources WHERE companyId=:id", { replacements: { id } }); await database.query("DELETE FROM access_directory_environments WHERE companyId=:id", { replacements: { id }, }); await database.query("DELETE FROM access_directory_systems WHERE companyId=:id", { replacements: { id }, }); await database.query( "DELETE FROM access_references WHERE itemId IN (SELECT i.id FROM access_items i JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:id)", { replacements: { id } } ); await database.query( "DELETE r FROM access_revisions r JOIN access_items i ON i.id=r.itemId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:id", { replacements: { id } } ); for (const table of ["access_environment_values", "access_system_values"]) { await database.query( `DELETE v FROM ${table} v JOIN access_items i ON i.id=v.ownerId JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:id`, { replacements: { id } } ); } await database.query( "DELETE i FROM access_items i JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:id", { replacements: { id } } ); await database.query("DELETE FROM access_workspaces WHERE companyId=:id", { replacements: { id }, }); await database.query("DELETE FROM companies WHERE id=:id", { replacements: { id } }); } await database.query("DELETE FROM users WHERE id=:actorId", { replacements: { actorId } }); }