metis-desk/apps/server/src/asset-directory-memberships.fixture.ts
David fe6ec361b7 feat(companies): vincular identidades locales a activos y añadir cuenta de acceso a FolderMapping
- Asociar entornos locales con activos y conservar su trazabilidad.
- Registrar la cuenta de acceso del mapeo con historial de cambios.
- Calcular cobertura documental específica sin alterar la cobertura general.
- Añadir migraciones, validaciones, endpoints protegidos, pruebas y documentación.
2026-10-21 11:25:23 +02:00

263 lines
10 KiB
TypeScript

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 } });
}