- 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.
134 lines
4.5 KiB
TypeScript
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.");
|
|
}
|
|
}
|