- 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.
263 lines
10 KiB
TypeScript
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 } });
|
|
}
|