FactuGES_ERP/apps/server/src/testing/e2e/bootstrap-e2e-data.ts
David da362fef64 feat(e2e): implement end-to-end testing framework with database preparation and lifecycle management
- Add E2ESchemaDatabaseConfig interface for database configuration validation.
- Implement assertE2ESchemaDatabaseConfig function to enforce schema requirements.
- Create prepareE2ESchema function to handle schema preparation and initialization.
- Develop E2E lifecycle management functions to provision, prepare, and run tests.
- Introduce Docker Compose setup for E2E database environment.
- Add Playwright configuration for E2E testing.
- Implement various test cases to validate E2E schema preparation and lifecycle.
- Create utility functions for environment variable management and database assertions.
- Establish smoke tests to verify initial data setup and integrity.
2026-09-22 16:49:08 +02:00

134 lines
4.5 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { createSequelize } from "@erp/core/api";
import { BcryptPasswordHasher } from "@erp/identity/api";
import { IdentityPermissionCodes } from "@erp/identity/common";
import type { ILogger } from "@repo/rdx-logger";
import type { Sequelize } from "sequelize";
import type { E2ESchemaDatabaseConfig } from "./e2e-schema-database-config";
import { assertE2ESchemaDatabaseConfig } from "./e2e-schema-database-config";
export interface E2EBootstrapConfig {
readonly executionId: string;
readonly email: string;
readonly password: string;
readonly companyName: string;
readonly companySlug: string;
}
export interface E2EBootstrapResult {
readonly accountId: string;
readonly companyId: string;
readonly companySlug: string;
readonly email: string;
}
/** Crea el grafo mínimo autenticable sobre un schema E2E ya preparado. */
export async function bootstrapE2EData(
databaseConfig: E2ESchemaDatabaseConfig,
config: E2EBootstrapConfig,
dependencies: {
readonly logger: ILogger;
readonly createDatabase?: (config: E2ESchemaDatabaseConfig) => Promise<Sequelize>;
}
): Promise<E2EBootstrapResult> {
assertE2ESchemaDatabaseConfig(databaseConfig);
assertBootstrapConfig(config, databaseConfig.executionId);
const database = await (dependencies.createDatabase ?? createDatabase)(
databaseConfig,
dependencies.logger
);
try {
return await createBootstrapData(database, config);
} finally {
await database.close();
}
}
async function createBootstrapData(
database: Sequelize,
config: E2EBootstrapConfig
): Promise<E2EBootstrapResult> {
const passwordHashResult = await new BcryptPasswordHasher({ rounds: 10 }).hash(config.password);
if (passwordHashResult.isFailure) {
throw passwordHashResult.error;
}
const accountId = randomUUID();
const companyId = randomUUID();
const membershipId = randomUUID();
const roleId = randomUUID();
const onboardingId = randomUUID();
await database.transaction(async (transaction) => {
const query = (sql: string, replacements: Record<string, unknown>) =>
database.query(sql, { replacements, transaction });
await query(
"INSERT INTO identity_accounts (id, email, password_hash, name, avatar_url, language_code, status, created_at, updated_at) VALUES (:id, :email, :passwordHash, :name, NULL, 'es', 'active', NOW(), NOW())",
{
id: accountId,
email: config.email,
passwordHash: passwordHashResult.data.toPrimitive(),
name: "E2E User",
}
);
await query(
"INSERT INTO companies (id, legal_name, slug, status, created_at, updated_at) VALUES (:id, :name, :slug, 'active', NOW(), NOW())",
{ id: companyId, name: config.companyName, slug: config.companySlug }
);
await query(
"INSERT INTO identity_company_memberships (id, account_id, company_id, status, created_at, updated_at) VALUES (:id, :accountId, :companyId, 'active', NOW(), NOW())",
{ id: membershipId, accountId, companyId }
);
await query(
"INSERT INTO identity_roles (id, company_id, code, name, is_system, permission_codes, created_at, updated_at) VALUES (:id, :companyId, 'admin', 'Administrator', true, :permissions, NOW(), NOW())",
{ id: roleId, companyId, permissions: JSON.stringify(Object.values(IdentityPermissionCodes)) }
);
await query(
"INSERT INTO identity_company_membership_roles (membership_id, role_id) VALUES (:membershipId, :roleId)",
{ membershipId, roleId }
);
await query(
"INSERT INTO initial_onboardings (id, account_id, status, company_id, created_at, updated_at, completed_at) VALUES (:id, :accountId, 'completed', :companyId, NOW(), NOW(), NOW())",
{ id: onboardingId, accountId, companyId }
);
});
return { accountId, companyId, companySlug: config.companySlug, email: config.email };
}
async function createDatabase(
config: E2ESchemaDatabaseConfig,
logger: ILogger
): Promise<Sequelize> {
return await createSequelize(
{
dialect: "mysql",
host: config.host,
port: config.port,
name: config.database,
user: config.username,
password: config.password,
logging: false,
ssl: false,
timezone: "Europe/Madrid",
},
logger
);
}
function assertBootstrapConfig(config: E2EBootstrapConfig, executionId: string): void {
if (
config.executionId !== executionId ||
!config.email ||
!config.password ||
!config.companyName ||
!config.companySlug
) {
throw new Error("Invalid E2E bootstrap configuration.");
}
}