feat(e2e): agregar convenciones y pruebas E2E para el módulo de identidad
This commit is contained in:
parent
94b6515a03
commit
99a3f70226
2
.gitignore
vendored
2
.gitignore
vendored
@ -16,3 +16,5 @@ test-results/
|
||||
blob-report/
|
||||
apps/server/.env.e2e
|
||||
apps/web/.env.e2e
|
||||
|
||||
apps/server/storage/e2e/
|
||||
|
||||
@ -27,3 +27,6 @@ SIGNING_SERVICE_METHOD=POST
|
||||
SIGNING_SERVICE_TIMEOUT_MS=5000
|
||||
SIGNING_SERVICE_MAX_RETRIES=0
|
||||
COMPANY_CERTIFICATES_PATH=./storage/e2e/certificates
|
||||
|
||||
E2E_USER_EMAIL=replace-with-e2e-user-email
|
||||
E2E_USER_PASSWORD=replace-with-e2e-user-password
|
||||
|
||||
@ -136,6 +136,7 @@ export const AppTopbar = () => {
|
||||
<Button
|
||||
aria-label="Abrir menú de usuario"
|
||||
className="h-full gap-3 rounded-none px-4"
|
||||
id="account-menu"
|
||||
type="button"
|
||||
variant="ghost"
|
||||
>
|
||||
@ -162,7 +163,7 @@ export const AppTopbar = () => {
|
||||
<DropdownMenuItem onClick={handleOpenAccount}>
|
||||
{t("web.accountMenu.openAccount")}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem onClick={handleLogoutClick}>
|
||||
<DropdownMenuItem id="account-logout" onClick={handleLogoutClick}>
|
||||
{t("web.accountMenu.logout")}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
|
||||
451
docs/frontend/Frontend-ERP-Convencion-tests-E2E.md
Normal file
451
docs/frontend/Frontend-ERP-Convencion-tests-E2E.md
Normal file
@ -0,0 +1,451 @@
|
||||
# Frontend ERP – Convención de organización y naming de tests E2E
|
||||
|
||||
**Estado:** convención adoptada para Playwright y la suite E2E del ERP.
|
||||
|
||||
## 1. Propósito
|
||||
|
||||
Definir una convención única para organizar, nombrar e identificar los tests E2E del ERP, de forma que sean fáciles de localizar, mantener y relacionar con el módulo y la feature funcional que validan.
|
||||
|
||||
## 2. Principio de organización
|
||||
|
||||
El primer nivel de directorios bajo `tests/e2e` debe corresponder exactamente con el nombre del módulo propietario existente bajo `/modules`.
|
||||
|
||||
Regla:
|
||||
|
||||
```txt
|
||||
tests/e2e/<module>/
|
||||
```
|
||||
|
||||
Ejemplos:
|
||||
|
||||
```txt
|
||||
modules/identity
|
||||
→ tests/e2e/identity
|
||||
|
||||
modules/customers
|
||||
→ tests/e2e/customers
|
||||
|
||||
modules/companies
|
||||
→ tests/e2e/companies
|
||||
|
||||
modules/catalogs
|
||||
→ tests/e2e/catalogs
|
||||
|
||||
modules/customer-invoices
|
||||
→ tests/e2e/customer-invoices
|
||||
```
|
||||
|
||||
No deben crearse categorías E2E alternativas como:
|
||||
|
||||
```txt
|
||||
auth/
|
||||
invoices/
|
||||
settings/
|
||||
```
|
||||
|
||||
si no coinciden con el módulo propietario real.
|
||||
|
||||
## 3. Convención de nombres de fichero
|
||||
|
||||
El nombre del fichero debe representar la **feature o el flujo funcional probado**. No debe incluir numeración de casos.
|
||||
|
||||
Ejemplos:
|
||||
|
||||
```txt
|
||||
login.spec.ts
|
||||
protected-routes.spec.ts
|
||||
logout.spec.ts
|
||||
create-customer.spec.ts
|
||||
update-customer.spec.ts
|
||||
```
|
||||
|
||||
Se adopta `*.spec.ts` como sufijo estándar.
|
||||
|
||||
Al vivir dentro de:
|
||||
|
||||
```txt
|
||||
tests/e2e/
|
||||
```
|
||||
|
||||
no es necesario añadir `e2e` al nombre del fichero.
|
||||
|
||||
## 4. Convención de identificadores de casos
|
||||
|
||||
Cada test funcional debe tener un identificador estable con este formato:
|
||||
|
||||
```txt
|
||||
<MODULE>-<FEATURE>-<NNN>
|
||||
```
|
||||
|
||||
Donde:
|
||||
|
||||
- `MODULE`: nombre lógico del módulo propietario, en mayúsculas.
|
||||
- `FEATURE`: feature o flujo funcional que agrupa el caso.
|
||||
- `NNN`: secuencia numérica de tres dígitos comenzando en `001` dentro de la feature.
|
||||
|
||||
Ejemplos:
|
||||
|
||||
```txt
|
||||
IDENTITY-LOGIN-001
|
||||
IDENTITY-LOGIN-002
|
||||
IDENTITY-ROUTES-001
|
||||
IDENTITY-LOGOUT-001
|
||||
```
|
||||
|
||||
## 5. Ejemplo: Identity
|
||||
|
||||
Estructura:
|
||||
|
||||
```txt
|
||||
tests/e2e/identity/
|
||||
├── login.spec.ts
|
||||
├── protected-routes.spec.ts
|
||||
└── logout.spec.ts
|
||||
```
|
||||
|
||||
Casos:
|
||||
|
||||
```txt
|
||||
IDENTITY-LOGIN-001
|
||||
Login correcto y persistencia tras recarga
|
||||
|
||||
IDENTITY-LOGIN-002
|
||||
Rechazo de credenciales incorrectas
|
||||
|
||||
IDENTITY-ROUTES-001
|
||||
Acceso a ruta protegida sin sesión
|
||||
|
||||
IDENTITY-LOGOUT-001
|
||||
Cierre de sesión
|
||||
```
|
||||
|
||||
Ejemplo en Playwright:
|
||||
|
||||
```ts
|
||||
test(
|
||||
"IDENTITY-LOGIN-001: login correcto y persistencia tras recarga",
|
||||
async ({ page }) => {
|
||||
// ...
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
"IDENTITY-LOGIN-002: rechazo de credenciales incorrectas",
|
||||
async ({ page }) => {
|
||||
// ...
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## 6. Ejemplos en otros módulos
|
||||
|
||||
### Customers
|
||||
|
||||
```txt
|
||||
tests/e2e/customers/create-customer.spec.ts
|
||||
→ CUSTOMERS-CREATE-001
|
||||
→ CUSTOMERS-CREATE-002
|
||||
|
||||
tests/e2e/customers/update-customer.spec.ts
|
||||
→ CUSTOMERS-UPDATE-001
|
||||
```
|
||||
|
||||
Ejemplos:
|
||||
|
||||
```txt
|
||||
CUSTOMERS-CREATE-001
|
||||
Alta válida de cliente
|
||||
|
||||
CUSTOMERS-CREATE-002
|
||||
Validaciones de alta
|
||||
|
||||
CUSTOMERS-UPDATE-001
|
||||
Modificación válida de cliente
|
||||
```
|
||||
|
||||
### Customer Invoices
|
||||
|
||||
Para módulos grandes se permite añadir la bounded feature al identificador:
|
||||
|
||||
```txt
|
||||
tests/e2e/customer-invoices/proformas/create-proforma.spec.ts
|
||||
→ CUSTOMER-INVOICES-PROFORMA-CREATE-001
|
||||
```
|
||||
|
||||
No deben introducirse abreviaturas arbitrarias diferentes para el mismo módulo.
|
||||
|
||||
Evitar mezclar, por ejemplo:
|
||||
|
||||
```txt
|
||||
CUSTOMER-INVOICES-...
|
||||
INVOICES-...
|
||||
CI-...
|
||||
```
|
||||
|
||||
salvo que en el futuro se acuerde formalmente una abreviatura estable.
|
||||
|
||||
## 7. Subdirectorios en módulos grandes
|
||||
|
||||
Un módulo grande puede subdividirse por bounded feature, pero el primer nivel bajo `tests/e2e` continúa siendo siempre el módulo propietario.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```txt
|
||||
tests/e2e/customer-invoices/
|
||||
├── proformas/
|
||||
├── issued-invoices/
|
||||
└── recurring-proformas/
|
||||
```
|
||||
|
||||
Correcto:
|
||||
|
||||
```txt
|
||||
tests/e2e/customer-invoices/proformas/
|
||||
tests/e2e/customer-invoices/issued-invoices/
|
||||
tests/e2e/customer-invoices/recurring-proformas/
|
||||
```
|
||||
|
||||
Evitar:
|
||||
|
||||
```txt
|
||||
tests/e2e/proformas/
|
||||
tests/e2e/issued-invoices/
|
||||
tests/e2e/recurring-proformas/
|
||||
```
|
||||
|
||||
porque perdería la trazabilidad directa con:
|
||||
|
||||
```txt
|
||||
modules/customer-invoices
|
||||
```
|
||||
|
||||
## 8. Reglas de numeración
|
||||
|
||||
La numeración:
|
||||
|
||||
- se reinicia por feature;
|
||||
- utiliza siempre tres dígitos: `001`, `002`, `003`...;
|
||||
- identifica el caso, no su prioridad;
|
||||
- no determina el orden de ejecución;
|
||||
- no debe cambiar por reordenar tests dentro del fichero;
|
||||
- no debe reutilizarse para representar otro escenario funcional diferente.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```txt
|
||||
IDENTITY-LOGIN-001
|
||||
IDENTITY-LOGIN-002
|
||||
IDENTITY-LOGIN-003
|
||||
|
||||
IDENTITY-ROUTES-001
|
||||
IDENTITY-ROUTES-002
|
||||
|
||||
IDENTITY-LOGOUT-001
|
||||
```
|
||||
|
||||
No usar una única secuencia global como:
|
||||
|
||||
```txt
|
||||
IDENTITY-001
|
||||
IDENTITY-002
|
||||
IDENTITY-003
|
||||
...
|
||||
```
|
||||
|
||||
porque pierde información sobre la feature.
|
||||
|
||||
## 9. Naming del test
|
||||
|
||||
El identificador debe formar parte del título de `test()`.
|
||||
|
||||
Correcto:
|
||||
|
||||
```ts
|
||||
test("IDENTITY-LOGIN-002: rechazo de credenciales incorrectas", async ({
|
||||
page,
|
||||
}) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
Evitar:
|
||||
|
||||
```ts
|
||||
test("rechaza credenciales incorrectas", async ({ page }) => {
|
||||
// ...
|
||||
});
|
||||
```
|
||||
|
||||
si ese test pertenece al catálogo E2E formal.
|
||||
|
||||
El texto posterior al identificador debe describir el comportamiento desde el punto de vista funcional.
|
||||
|
||||
## 10. Localización de tests
|
||||
|
||||
La inclusión del identificador en el título permite localizar cualquier caso mediante búsqueda textual.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```bash
|
||||
rg -n --glob "*.spec.ts" "IDENTITY-LOGIN-002" tests/e2e
|
||||
```
|
||||
|
||||
Para localizar todos los casos de una feature:
|
||||
|
||||
```bash
|
||||
rg -n --glob "*.spec.ts" "IDENTITY-LOGIN-" tests/e2e
|
||||
```
|
||||
|
||||
Para obtener solo los ficheros:
|
||||
|
||||
```bash
|
||||
rg -l --glob "*.spec.ts" "IDENTITY-LOGIN-002" tests/e2e
|
||||
```
|
||||
|
||||
## 11. Convención de selectores E2E
|
||||
|
||||
Los tests no deben depender de textos traducidos.
|
||||
|
||||
Orden de preferencia:
|
||||
|
||||
```txt
|
||||
1. id técnico estable
|
||||
2. data-testid estable
|
||||
3. atributo técnico estable, por ejemplo name
|
||||
4. selector CSS técnico y estable, solo excepcionalmente
|
||||
```
|
||||
|
||||
Ejemplos:
|
||||
|
||||
```ts
|
||||
page.locator("#login-email");
|
||||
page.getByTestId("login-submit");
|
||||
page.locator('input[name="email"]');
|
||||
```
|
||||
|
||||
Evitar como contrato principal:
|
||||
|
||||
```ts
|
||||
page.getByText("Entrar");
|
||||
page.getByLabel("Email");
|
||||
page.getByRole("button", { name: "Entrar" });
|
||||
```
|
||||
|
||||
porque esos textos pueden variar con el locale.
|
||||
|
||||
También evitar:
|
||||
|
||||
```txt
|
||||
clases Tailwind visuales
|
||||
nth-child
|
||||
selectores dependientes de posición
|
||||
estructura DOM incidental
|
||||
IDs generados mediante React.useId()
|
||||
```
|
||||
|
||||
Las clases CSS solo deben utilizarse excepcionalmente cuando sean clases técnicas deliberadamente estables, no clases de presentación.
|
||||
|
||||
## 12. Accesibilidad
|
||||
|
||||
La estrategia de selectores E2E no sustituye la accesibilidad.
|
||||
|
||||
Los componentes deben mantener correctamente:
|
||||
|
||||
```txt
|
||||
label
|
||||
htmlFor
|
||||
role
|
||||
aria-label
|
||||
aria-describedby
|
||||
aria-live
|
||||
```
|
||||
|
||||
cuando corresponda.
|
||||
|
||||
Los atributos técnicos para testing son un contrato complementario.
|
||||
|
||||
## 13. Independencia de los tests
|
||||
|
||||
Cada caso E2E debe poder ejecutarse:
|
||||
|
||||
```txt
|
||||
de forma aislada
|
||||
en cualquier orden
|
||||
junto con el resto de la suite
|
||||
```
|
||||
|
||||
El identificador:
|
||||
|
||||
```txt
|
||||
IDENTITY-LOGIN-002
|
||||
```
|
||||
|
||||
no significa que el test dependa de:
|
||||
|
||||
```txt
|
||||
IDENTITY-LOGIN-001
|
||||
```
|
||||
|
||||
Los tests no deben depender del estado dejado por casos anteriores.
|
||||
|
||||
## 14. Relación entre directorio, fichero y caso
|
||||
|
||||
Regla general:
|
||||
|
||||
```txt
|
||||
Directorio
|
||||
→ módulo propietario
|
||||
|
||||
Fichero
|
||||
→ feature o flujo funcional
|
||||
|
||||
Caso
|
||||
→ MODULE-FEATURE-NNN
|
||||
```
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```txt
|
||||
tests/e2e/identity/login.spec.ts
|
||||
→ IDENTITY-LOGIN-001
|
||||
→ IDENTITY-LOGIN-002
|
||||
|
||||
tests/e2e/identity/protected-routes.spec.ts
|
||||
→ IDENTITY-ROUTES-001
|
||||
|
||||
tests/e2e/identity/logout.spec.ts
|
||||
→ IDENTITY-LOGOUT-001
|
||||
```
|
||||
|
||||
## 15. Criterio para nuevas features
|
||||
|
||||
Antes de crear un nuevo test:
|
||||
|
||||
1. identificar el módulo propietario real bajo `/modules`;
|
||||
2. localizar o crear el fichero correspondiente al flujo funcional;
|
||||
3. determinar el nombre estable de la feature;
|
||||
4. asignar el siguiente `NNN` disponible dentro de esa feature;
|
||||
5. incluir el identificador completo en el título de `test()`.
|
||||
|
||||
No crear nuevos prefijos para pequeñas variaciones del mismo flujo si no existe una diferencia funcional clara.
|
||||
|
||||
## 16. Regla mental final
|
||||
|
||||
```txt
|
||||
tests/e2e/<module>/<feature>.spec.ts
|
||||
|
||||
<MODULE>-<FEATURE>-<NNN>
|
||||
```
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```txt
|
||||
tests/e2e/identity/login.spec.ts
|
||||
IDENTITY-LOGIN-001
|
||||
```
|
||||
|
||||
La estructura física indica **quién es propietario del comportamiento**.
|
||||
|
||||
El fichero indica **qué flujo se está probando**.
|
||||
|
||||
El identificador indica **qué caso funcional concreto se está ejecutando**.
|
||||
@ -33,6 +33,7 @@ export function LoginCard({ form, onSubmit }: LoginCardProps) {
|
||||
<form className="flex flex-col gap-5" onSubmit={onSubmit}>
|
||||
<TextField<LoginForm>
|
||||
autoComplete="email"
|
||||
id="login-email"
|
||||
label="Email"
|
||||
name="email"
|
||||
placeholder="tu@email.com"
|
||||
@ -42,6 +43,7 @@ export function LoginCard({ form, onSubmit }: LoginCardProps) {
|
||||
|
||||
<TextField<LoginForm>
|
||||
autoComplete="current-password"
|
||||
id="login-password"
|
||||
label="Contraseña"
|
||||
name="password"
|
||||
placeholder="••••••••"
|
||||
@ -50,12 +52,22 @@ export function LoginCard({ form, onSubmit }: LoginCardProps) {
|
||||
/>
|
||||
|
||||
{rootError ? (
|
||||
<p aria-live="polite" className="text-sm text-destructive" role="alert">
|
||||
<p
|
||||
aria-live="polite"
|
||||
className="text-sm text-destructive"
|
||||
id="login-error"
|
||||
role="alert"
|
||||
>
|
||||
{rootError}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
<Button className="mt-1 w-full" disabled={form.formState.isSubmitting} type="submit">
|
||||
<Button
|
||||
className="mt-1 w-full"
|
||||
disabled={form.formState.isSubmitting}
|
||||
id="login-submit"
|
||||
type="submit"
|
||||
>
|
||||
{form.formState.isSubmitting ? (
|
||||
<>
|
||||
<Spinner className="size-4" />
|
||||
|
||||
@ -21,9 +21,9 @@
|
||||
"check": "biome check .",
|
||||
"check:write": "biome check --write --unsafe .",
|
||||
"test:frontend": "node --experimental-test-module-mocks --import tsx --test",
|
||||
"test:e2e": "playwright test",
|
||||
"test:e2e:ui": "playwright test --ui",
|
||||
"test:e2e:headed": "playwright test --headed",
|
||||
"test:e2e": "node --env-file=apps/server/.env.e2e node_modules/@playwright/test/cli.js test",
|
||||
"test:e2e:ui": "node --env-file=apps/server/.env.e2e node_modules/@playwright/test/cli.js test --ui",
|
||||
"test:e2e:headed": "node --env-file=apps/server/.env.e2e node_modules/@playwright/test/cli.js test --headed",
|
||||
"test:unit": "node --import tsx --test",
|
||||
"typecheck": "tsc -p tsconfig.json --noEmit"
|
||||
},
|
||||
|
||||
@ -57,7 +57,8 @@ export const TextField = <TFormValues extends FieldValues>({
|
||||
}: TextFieldProps<TFormValues>) => {
|
||||
const { register, formState, getFieldState } = useFormContext<TFormValues>();
|
||||
|
||||
const inputId = React.useId();
|
||||
const generatedInputId = React.useId();
|
||||
const inputId = inputRest.id ?? generatedInputId;
|
||||
const disabled = formState.isSubmitting || inputRest.disabled;
|
||||
|
||||
const presetProps = getInputPresetProps(typePreset);
|
||||
|
||||
40
tests/e2e/identity/login.spec.ts
Normal file
40
tests/e2e/identity/login.spec.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { loginViaUi } from "./support/login-via-ui";
|
||||
|
||||
test("IDENTITY-LOGIN-001: mantiene la sesión después de iniciar sesión y recargar", async ({
|
||||
page,
|
||||
}) => {
|
||||
await loginViaUi(page);
|
||||
|
||||
const accountMenu = page.locator("#account-menu");
|
||||
|
||||
await page.reload();
|
||||
|
||||
await expect(page).not.toHaveURL(/\/login(?:\?|$)/);
|
||||
await expect(accountMenu).toBeVisible();
|
||||
});
|
||||
|
||||
test("IDENTITY-LOGIN-002: rechaza credenciales incorrectas sin crear una sesión", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/login");
|
||||
|
||||
const loginEmail = page.locator("#login-email");
|
||||
const loginPassword = page.locator("#login-password");
|
||||
const loginSubmit = page.locator("#login-submit");
|
||||
const loginError = page.locator("#login-error");
|
||||
const accountMenu = page.locator("#account-menu");
|
||||
|
||||
await expect(loginEmail).toBeVisible();
|
||||
await expect(loginPassword).toBeVisible();
|
||||
await expect(loginSubmit).toBeVisible();
|
||||
|
||||
await loginEmail.fill("unknown-user@example.invalid");
|
||||
await loginPassword.fill("invalid-password");
|
||||
await loginSubmit.click();
|
||||
|
||||
await expect(loginError).toBeVisible();
|
||||
await expect(page).toHaveURL(/\/login(?:\?.*)?$/);
|
||||
await expect(accountMenu).not.toBeVisible();
|
||||
});
|
||||
21
tests/e2e/identity/logout.spec.ts
Normal file
21
tests/e2e/identity/logout.spec.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { loginViaUi } from "./support/login-via-ui";
|
||||
|
||||
test("IDENTITY-LOGOUT-001: cierra la sesión y bloquea el acceso posterior a rutas privadas", async ({
|
||||
page,
|
||||
}) => {
|
||||
await loginViaUi(page);
|
||||
|
||||
await page.locator("#account-menu").click();
|
||||
await expect(page.locator("#account-logout")).toBeVisible();
|
||||
await page.locator("#account-logout").click();
|
||||
|
||||
await expect(page).toHaveURL(/\/login(?:\?.*)?$/);
|
||||
await expect(page.locator("#login-email")).toBeVisible();
|
||||
|
||||
await page.goto("/account");
|
||||
|
||||
await expect(page).toHaveURL(/\/login\?returnTo=%2Faccount$/);
|
||||
await expect(page.locator("#login-email")).toBeVisible();
|
||||
});
|
||||
12
tests/e2e/identity/protected-routes.spec.ts
Normal file
12
tests/e2e/identity/protected-routes.spec.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { expect, test } from "@playwright/test";
|
||||
|
||||
test("IDENTITY-ROUTES-001: redirige al login al acceder a una ruta protegida sin sesión", async ({
|
||||
page,
|
||||
}) => {
|
||||
await page.goto("/account");
|
||||
|
||||
await expect(page).toHaveURL(/\/login\?returnTo=%2Faccount$/);
|
||||
await expect(page.locator("#login-email")).toBeVisible();
|
||||
await expect(page.locator("#login-password")).toBeVisible();
|
||||
await expect(page.locator("#account-menu")).not.toBeVisible();
|
||||
});
|
||||
19
tests/e2e/identity/support/login-via-ui.ts
Normal file
19
tests/e2e/identity/support/login-via-ui.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import { type Page, expect } from "@playwright/test";
|
||||
|
||||
export async function loginViaUi(page: Page): Promise<void> {
|
||||
const email = process.env.E2E_USER_EMAIL;
|
||||
const password = process.env.E2E_USER_PASSWORD;
|
||||
|
||||
if (!(email && password)) {
|
||||
throw new Error(
|
||||
"Identity E2E requires E2E_USER_EMAIL and E2E_USER_PASSWORD in apps/server/.env.e2e."
|
||||
);
|
||||
}
|
||||
|
||||
await page.goto("/login");
|
||||
await page.locator("#login-email").fill(email);
|
||||
await page.locator("#login-password").fill(password);
|
||||
await page.locator("#login-submit").click();
|
||||
await expect(page).not.toHaveURL(/\/login(?:\?|$)/);
|
||||
await expect(page.locator("#account-menu")).toBeVisible();
|
||||
}
|
||||
@ -1,13 +1,30 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { dirname, resolve } from "node:path";
|
||||
|
||||
const envFile = resolve("apps/server/.env.e2e");
|
||||
const databaseName = readFileSync(envFile, "utf8")
|
||||
const env = Object.fromEntries(
|
||||
readFileSync(envFile, "utf8")
|
||||
.split(/\r?\n/)
|
||||
.find((line) => line.startsWith("DB_NAME="))
|
||||
?.slice("DB_NAME=".length)
|
||||
.trim();
|
||||
.filter((line) => line && !line.startsWith("#"))
|
||||
.map((line) => {
|
||||
const separatorIndex = line.indexOf("=");
|
||||
|
||||
if (databaseName !== "uecko_erp_e2e") {
|
||||
throw new Error("E2E requires DB_NAME=uecko_erp_e2e in apps/server/.env.e2e.");
|
||||
return [line.slice(0, separatorIndex), line.slice(separatorIndex + 1)];
|
||||
})
|
||||
);
|
||||
|
||||
if (env.DB_NAME !== "factuges_erp_e2e") {
|
||||
throw new Error("E2E requires DB_NAME=factuges_erp_e2e in apps/server/.env.e2e.");
|
||||
}
|
||||
|
||||
const certificatesPath = env.COMPANY_CERTIFICATES_PATH;
|
||||
if (!certificatesPath) {
|
||||
throw new Error("E2E requires COMPANY_CERTIFICATES_PATH in apps/server/.env.e2e.");
|
||||
}
|
||||
|
||||
const certificatesFile = resolve("apps/server", certificatesPath);
|
||||
mkdirSync(dirname(certificatesFile), { recursive: true });
|
||||
|
||||
if (!existsSync(certificatesFile)) {
|
||||
writeFileSync(certificatesFile, "{}\n");
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user