Añadido Dockerfil docker-compose.yml

This commit is contained in:
2026-05-30 21:47:44 -05:00
parent 40986b0359
commit 5a563b214f
101 changed files with 10207 additions and 950 deletions
+151
View File
@@ -0,0 +1,151 @@
import { test, expect } from "@playwright/test";
test.describe("Patients Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/dashboard/patients");
});
test("patients page renders with title or redirects", async ({ page }) => {
if (page.url().includes("/login")) {
expect(page.url()).toContain("/login");
return;
}
await expect(page.getByRole("heading", { name: /pacientes/i })).toBeVisible();
});
test("search input exists when authenticated", async ({ page }) => {
if (page.url().includes("/login")) return;
const searchInput = page.locator('input[type="search"], input[placeholder*="Buscar" i]').first();
await expect(searchInput).toBeVisible();
});
test("search debounce prevents excessive requests", async ({ page }) => {
if (page.url().includes("/login")) return;
const searchInput = page.locator('input[type="search"], input[placeholder*="Buscar" i]').first();
if (await searchInput.isVisible().catch(() => false)) {
await searchInput.fill("maria");
await searchInput.fill("maria garcia");
await searchInput.fill("maria garcia test");
// Wait for debounce (300ms + buffer)
await page.waitForTimeout(500);
const bodyText = await page.locator("body").textContent();
expect(bodyText).toMatch(/paciente|no se encontraron|no hay|cargando/i);
}
});
test("patient cards navigate to detail when authenticated", async ({ page }) => {
if (page.url().includes("/login")) return;
// Wait for patient cards
const cards = page.locator("button[class*='cursor-pointer'], a[href*='/patients/']").first();
if (await cards.isVisible().catch(() => false)) {
await cards.click();
await page.waitForURL(/\/dashboard\/patients\/.+/, { timeout: 5000 });
expect(page.url()).toMatch(/\/dashboard\/patients\/[^/]+$/);
}
});
test("invalid patient ID is handled", async ({ page }) => {
await page.goto("/dashboard/patients/invalid-id");
await page.waitForLoadState("networkidle");
// Should show 404, not found, or redirect to patients list
const url = page.url();
const bodyText = await page.locator("body").textContent();
const isHandled =
url.includes("404") ||
bodyText?.match(/404|not found|no encontrado|error/i) !== null ||
url === "http://localhost:3000/dashboard/patients";
expect(isHandled).toBe(true);
});
test("malformed UUID in URL is handled", async ({ page }) => {
await page.goto("/dashboard/patients/'; DROP TABLE patients; --");
await page.waitForLoadState("networkidle");
const url = page.url();
const bodyText = await page.locator("body").textContent();
const isHandled =
url.includes("404") ||
bodyText?.match(/404|not found|no encontrado|error/i) !== null ||
url === "http://localhost:3000/dashboard/patients";
expect(isHandled).toBe(true);
});
});
test.describe("Patient Detail Page", () => {
test("tabs are accessible when authenticated", async ({ page }) => {
await page.goto("/dashboard/patients");
if (page.url().includes("/login")) return;
// Try to find and click a patient card
const card = page.locator("button[class*='cursor-pointer']").first();
if (await card.isVisible().catch(() => false)) {
await card.click();
await page.waitForURL(/\/dashboard\/patients\/.+/, { timeout: 5000 });
// Check for tabs
const tabs = page.locator('[role="tab"]');
if (await tabs.count() > 0) {
const firstTab = tabs.first();
await firstTab.click();
await expect(page.locator('[role="tabpanel"]')).toBeVisible();
}
}
});
test("back button navigates to patients list", async ({ page }) => {
await page.goto("/dashboard/patients/a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11");
await page.waitForLoadState("networkidle");
if (page.url().includes("/login")) return;
const backButton = page.locator('a:has-text("Volver"), button:has-text("Volver")').first();
if (await backButton.isVisible().catch(() => false)) {
await backButton.click();
await page.waitForURL("/dashboard/patients", { timeout: 5000 });
expect(page.url()).toContain("/dashboard/patients");
}
});
});
test.describe("Patients Security", () => {
test("unauthenticated access redirects to login", async ({ page }) => {
await page.goto("/dashboard/patients");
if (!page.url().includes("/login")) {
await page.waitForURL("/login", { timeout: 5000 });
}
expect(page.url()).toContain("/login");
});
test("XSS in patient name is not executed", async ({ page }) => {
await page.goto("/dashboard/patients");
// If redirected to login, skip this specific check
if (page.url().includes("/login")) return;
// Check patient card text contents (not HTML) don't contain raw scripts
const cardTexts = await page.locator("[class*='cursor-pointer']").allTextContents();
for (const text of cardTexts) {
// Patient names rendered as text should not contain HTML tags
expect(text).not.toContain("<script>");
expect(text).not.toContain("javascript:");
}
// Verify no event handlers in rendered HTML of patient list area
const patientListHTML = await page.locator("body").innerHTML();
// Next.js includes its own <script> tags for hydration — that's expected
// We verify user data doesn't inject inline event handlers
expect(patientListHTML).not.toContain("onerror=");
expect(patientListHTML).not.toContain("onload=");
expect(patientListHTML).not.toContain("onclick=");
});
});