feat: add MCP server with resources, tools, auth, and Traefik routing

- Add backend/app/mcp_server.py with FastMCP server (hms-cms)
- Resources: design-rules, page-structure, sync-status
- Tools: trigger_sync, get_sync_log, set_rentman_token, read_file,
  write_file, create_page, deploy, git_commit, git_status
- Bearer token auth via MCP_AUTH_TOKEN env var
- Mount MCP at /mcp with streamable HTTP transport
- Integrate session manager in FastAPI lifespan
- Add Traefik labels for external /mcp route on hms.media-on.de
- Add git, docker.io, curl to backend Dockerfile
- Mount repo root + docker socket in backend container
- Add mcp>=1.0.0 to requirements.txt
This commit is contained in:
Agent Zero
2026-07-12 15:58:22 +02:00
parent 00c23bc066
commit cd465e0564
124 changed files with 3309 additions and 225 deletions
+68
View File
@@ -0,0 +1,68 @@
import { test, expect } from "@playwright/test";
test.describe("Cart Flow", () => {
test("should render warenkorb page with heading", async ({ page }) => {
await page.goto("/warenkorb");
await expect(page.locator("h1")).toContainText("Warenkorb");
});
test("should show empty state when cart is empty", async ({ page }) => {
await page.goto("/warenkorb");
await expect(page.getByTestId("warenkorb-empty")).toBeVisible();
await expect(page.getByTestId("warenkorb-empty-cta")).toBeVisible();
});
test("should have link to mietkatalog from empty state", async ({ page }) => {
await page.goto("/warenkorb");
const cta = page.getByTestId("warenkorb-empty-cta");
await expect(cta).toHaveAttribute("href", "/mietkatalog");
});
test("should show header cart icon", async ({ page }) => {
await page.goto("/");
await expect(page.getByTestId("header-cart-button")).toBeVisible();
});
test("should open cart drawer when header cart icon clicked", async ({ page }) => {
await page.goto("/");
await page.getByTestId("header-cart-button").click();
await expect(page.getByTestId("cart-drawer")).toBeVisible();
});
test("should show empty state in cart drawer", async ({ page }) => {
await page.goto("/");
await page.getByTestId("header-cart-button").click();
await expect(page.getByTestId("cart-drawer")).toBeVisible();
await expect(page.locator("text=Ihr Warenkorb ist leer")).toBeVisible();
});
test("should close cart drawer on backdrop click", async ({ page }) => {
await page.goto("/");
await page.getByTestId("header-cart-button").click();
await expect(page.getByTestId("cart-drawer")).toBeVisible();
await page.getByTestId("cart-drawer-backdrop").click();
await expect(page.getByTestId("cart-drawer")).not.toBeVisible();
});
test("should have checkout link in cart drawer", async ({ page }) => {
await page.goto("/");
await page.getByTestId("header-cart-button").click();
await expect(page.getByTestId("cart-drawer")).toBeVisible();
const checkout = page.getByTestId("cart-drawer-checkout");
await expect(checkout).toHaveAttribute("href", "/mietanfrage");
});
test("should have view cart link in cart drawer", async ({ page }) => {
await page.goto("/");
await page.getByTestId("header-cart-button").click();
await expect(page.getByTestId("cart-drawer")).toBeVisible();
const viewCart = page.getByTestId("cart-drawer-view-cart");
await expect(viewCart).toHaveAttribute("href", "/warenkorb");
});
test("should have mobile cart button", async ({ page }) => {
await page.goto("/");
await page.setViewportSize({ width: 375, height: 667 });
await expect(page.getByTestId("header-cart-button-mobile")).toBeVisible();
});
});
@@ -0,0 +1,54 @@
import { test, expect } from "@playwright/test";
test.describe("Equipment Detail Page", () => {
test("should show error for non-existent ID", async ({ page }) => {
await page.goto("/mietkatalog/999999");
await expect(page.getByText("nicht gefunden")).toBeVisible();
});
test("should have link back to mietkatalog on error", async ({ page }) => {
await page.goto("/mietkatalog/999999");
const link = page.getByText("Zurück zum Mietkatalog");
await expect(link).toBeVisible();
});
test("should display breadcrumb navigation", async ({ page }) => {
await page.goto("/mietkatalog/1");
await expect(page.getByText("Home")).toBeVisible();
await expect(page.getByText("Mietkatalog")).toBeVisible();
});
test("should show equipment name as heading", async ({ page }) => {
await page.goto("/mietkatalog/1");
const name = page.getByTestId("equipment-name");
const errorState = page.getByText("nicht gefunden");
const hasContent = await name.isVisible().catch(() => false) || await errorState.isVisible().catch(() => false);
expect(hasContent).toBeTruthy();
});
test("should show specs section if equipment found", async ({ page }) => {
await page.goto("/mietkatalog/1");
const specs = page.getByTestId("equipment-specs");
const errorState = page.getByText("nicht gefunden");
const hasContent = await specs.isVisible().catch(() => false) || await errorState.isVisible().catch(() => false);
expect(hasContent).toBeTruthy();
});
test("should have Mietanfrage button", async ({ page }) => {
await page.goto("/mietkatalog/1");
const btn = page.getByTestId("add-to-cart-detail");
const errorState = page.getByText("nicht gefunden");
const hasContent = await btn.isVisible().catch(() => false) || await errorState.isVisible().catch(() => false);
expect(hasContent).toBeTruthy();
});
test("should navigate back to mietkatalog via breadcrumb", async ({ page }) => {
await page.goto("/mietkatalog/1");
const breadcrumbLink = page.locator('a[href="/mietkatalog"]').first();
if (await breadcrumbLink.isVisible()) {
await breadcrumbLink.click();
await page.waitForURL("/mietkatalog");
await expect(page).toHaveURL(/\/mietkatalog$/);
}
});
});
@@ -0,0 +1,10 @@
import { test, expect } from "@playwright/test";
test.describe("Error Pages", () => {
test("404 page should show 'Seite nicht gefunden'", async ({ page }) => {
const response = await page.goto("/nicht-existent");
expect(response?.status()).toBe(404);
await expect(page.locator("body")).toContainText("Seite nicht gefunden");
await expect(page.getByRole("link", { name: /Startseite/ })).toBeVisible();
});
});
@@ -0,0 +1,74 @@
import { test, expect } from "@playwright/test";
test.describe("Header", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
});
test("should display sticky header with logo", async ({ page }) => {
const header = page.locator("header");
await expect(header).toBeVisible();
await expect(header).toHaveClass(/sticky/);
});
test("should have navigation items", async ({ page }) => {
await expect(page.getByRole("navigation").filter({ hasText: "Home" })).toBeVisible();
await expect(page.getByRole("link", { name: "Referenzen" })).toBeVisible();
await expect(page.getByRole("link", { name: "Mietkatalog" })).toBeVisible();
await expect(page.getByRole("link", { name: "Kontakt" })).toBeVisible();
});
test("should have clickable phone link", async ({ page }) => {
const phoneLink = page.locator('a[href="tel:+491726264796"]');
await expect(phoneLink).toBeVisible();
});
test("should have social icons", async ({ page }) => {
await expect(page.locator('a[aria-label="Facebook"]')).toBeVisible();
await expect(page.locator('a[aria-label="Instagram"]')).toBeVisible();
});
test("should open mobile burger menu on click", async ({ page }) => {
const burger = page.locator('button[aria-label="Menü öffnen/schließen"]');
await expect(burger).toBeVisible();
await burger.click();
await expect(page.locator("#mobile-nav")).toBeVisible();
});
});
test.describe("Footer", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
});
test("should display footer with address", async ({ page }) => {
const footer = page.locator("footer");
await expect(footer).toBeVisible();
await expect(footer).toContainText("Leipheim");
await expect(footer).toContainText("89340");
});
test("should have phone number", async ({ page }) => {
await expect(page.locator('a[href="tel:+498221204433"]')).toBeVisible();
});
test("should have email link", async ({ page }) => {
await expect(page.locator('a[href="mailto:info@hms-licht-ton.de"]')).toBeVisible();
});
test("should have legal links", async ({ page }) => {
await expect(page.getByRole("link", { name: "Impressum" })).toBeVisible();
await expect(page.getByRole("link", { name: "DSGVO" })).toBeVisible();
await expect(page.getByRole("link", { name: "AGB Vermietung" })).toBeVisible();
});
test("should NOT contain 'Neu in der Vermietung'", async ({ page }) => {
const footer = page.locator("footer");
await expect(footer).not.toContainText("Neu in der Vermietung");
});
test("should NOT contain 'AGB Shop'", async ({ page }) => {
const footer = page.locator("footer");
await expect(footer).not.toContainText("AGB Shop");
});
});
+68
View File
@@ -0,0 +1,68 @@
import { test, expect } from "@playwright/test";
test.describe("Home Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/");
});
test("should display hero with headline 'Veranstaltungstechnik'", async ({ page }) => {
await expect(page.locator('[data-testid="hero-headline"]')).toContainText("Veranstaltungstechnik");
});
test("should have CTA button linking to /mietkatalog", async ({ page }) => {
const cta = page.locator('[data-testid="hero-cta-mietkatalog"]');
await expect(cta).toBeVisible();
await expect(cta).toHaveAttribute("to", "/mietkatalog");
});
test("should have CTA button linking to /kontakt", async ({ page }) => {
const cta = page.locator('[data-testid="hero-cta-kontakt"]');
await expect(cta).toBeVisible();
await expect(cta).toHaveAttribute("to", "/kontakt");
});
test("should display all 8 services", async ({ page }) => {
const servicesSection = page.locator('[data-testid="services-section"]');
await expect(servicesSection).toBeVisible();
await expect(servicesSection).toContainText("Vermietung");
await expect(servicesSection).toContainText("Verkauf");
await expect(servicesSection).toContainText("Personal");
await expect(servicesSection).toContainText("Transport");
await expect(servicesSection).toContainText("Lagerung");
await expect(servicesSection).toContainText("Werkstatt");
await expect(servicesSection).toContainText("Installation");
await expect(servicesSection).toContainText("Booking");
});
test("should display speaker grid with 6 equipment types", async ({ page }) => {
const grid = page.locator('[data-testid="speaker-grid"]');
await expect(grid).toBeVisible();
await expect(grid).toContainText("Lautsprecher");
await expect(grid).toContainText("Mischpulte");
await expect(grid).toContainText("Lichtanlagen");
await expect(grid).toContainText("Rigging");
await expect(grid).toContainText("Traversen");
await expect(grid).toContainText("Komplett-Setups");
});
test("should display about section with stats", async ({ page }) => {
const about = page.locator('[data-testid="about-section"]');
await expect(about).toBeVisible();
await expect(about).toContainText("20+");
await expect(about).toContainText("1000+");
await expect(about).toContainText("500+");
});
test("should have mietkatalog CTA section", async ({ page }) => {
const cta = page.locator('[data-testid="mietkatalog-cta"]');
await expect(cta).toBeVisible();
const link = page.locator('[data-testid="cta-mietkatalog-link"]');
await expect(link).toBeVisible();
});
test("should be mobile-responsive at 375px width", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await expect(page.locator('[data-testid="hero-headline"]')).toBeVisible();
await expect(page.locator('[data-testid="services-section"]')).toBeVisible();
});
});
@@ -0,0 +1,74 @@
import { test, expect } from "@playwright/test";
test.describe("Kontakt Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/kontakt");
});
test("should display contact page heading", async ({ page }) => {
await expect(page.locator('[data-testid="kontakt-headline"]')).toContainText("Kontakt");
});
test("should show office address card", async ({ page }) => {
const card = page.locator('[data-testid="office-card"]');
await expect(card).toBeVisible();
await expect(card).toContainText("Grockelhofen 10");
await expect(card).toContainText("89340 Leipheim");
});
test("should show warehouse address card", async ({ page }) => {
const card = page.locator('[data-testid="warehouse-card"]');
await expect(card).toBeVisible();
await expect(card).toContainText("Ellzee");
});
test("should show opening hours card", async ({ page }) => {
const card = page.locator('[data-testid="hours-card"]');
await expect(card).toBeVisible();
await expect(card).toContainText("Öffnungszeiten");
});
test("should show 2 contact persons", async ({ page }) => {
await expect(page.locator('[data-testid="contact-person-markus"]')).toBeVisible();
await expect(page.locator('[data-testid="contact-person-thomas"]')).toBeVisible();
});
test("should have form with all required fields", async ({ page }) => {
await expect(page.locator('[data-testid="form-name"]')).toBeVisible();
await expect(page.locator('[data-testid="form-email"]')).toBeVisible();
await expect(page.locator('[data-testid="form-phone"]')).toBeVisible();
await expect(page.locator('[data-testid="form-message"]')).toBeVisible();
await expect(page.locator('[data-testid="form-privacy"]')).toBeVisible();
await expect(page.locator('[data-testid="form-submit"]')).toBeVisible();
});
test("should block submit when email is empty", async ({ page }) => {
await page.locator('[data-testid="form-name"]').fill("Test User");
await page.locator('[data-testid="form-message"]').fill("Test message");
await page.locator('[data-testid="form-privacy"]').check();
await page.locator('[data-testid="form-submit"]').click();
await expect(page.locator('[data-testid="error-email"]')).toBeVisible();
});
test("should block submit when privacy consent unchecked", async ({ page }) => {
await page.locator('[data-testid="form-name"]').fill("Test User");
await page.locator('[data-testid="form-email"]').fill("test@example.com");
await page.locator('[data-testid="form-message"]').fill("Test message");
await page.locator('[data-testid="form-submit"]').click();
await expect(page.locator('[data-testid="error-privacy"]')).toBeVisible();
});
test("should block submit when name is empty", async ({ page }) => {
await page.locator('[data-testid="form-email"]').fill("test@example.com");
await page.locator('[data-testid="form-message"]').fill("Test message");
await page.locator('[data-testid="form-privacy"]').check();
await page.locator('[data-testid="form-submit"]').click();
await expect(page.locator('[data-testid="error-name"]')).toBeVisible();
});
test("should be mobile-responsive at 375px width", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await expect(page.locator('[data-testid="contact-form"]')).toBeVisible();
await expect(page.locator('[data-testid="office-card"]')).toBeVisible();
});
});
@@ -0,0 +1,96 @@
import { test, expect } from "@playwright/test";
test.describe("Impressum Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/impressum");
});
test("should display Impressum heading", async ({ page }) => {
await expect(page.locator("h1")).toContainText("Impressum");
});
test("should contain Hammerschmidt u. Mössle GbR", async ({ page }) => {
await expect(page.locator("body")).toContainText("Hammerschmidt u. Mössle GbR");
});
test("should contain address Grockelhofen 10, 89340 Leipheim", async ({ page }) => {
await expect(page.locator("body")).toContainText("Grockelhofen 10");
await expect(page.locator("body")).toContainText("89340 Leipheim");
});
test("should contain phone number", async ({ page }) => {
await expect(page.locator("body")).toContainText("204433");
});
test("should contain email info@hms-licht-ton.de", async ({ page }) => {
await expect(page.locator('a[href="mailto:info@hms-licht-ton.de"]')).toBeVisible();
});
test("should be mobile-responsive at 375px width", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await expect(page.locator("h1")).toBeVisible();
});
});
test.describe("Datenschutz Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/datenschutz");
});
test("should display Datenschutz heading", async ({ page }) => {
await expect(page.locator("h1")).toContainText("Datenschutz");
});
test("should contain DSGVO-relevant sections", async ({ page }) => {
const body = page.locator("body");
await expect(body).toContainText("Datenerhebung");
await expect(body).toContainText("Cookies");
await expect(body).toContainText("Kontaktformular");
await expect(body).toContainText("Server-Log");
await expect(body).toContainText("Drittanbieter");
});
test("should mention responsible party", async ({ page }) => {
await expect(page.locator("body")).toContainText("Hammerschmidt u. Mössle GbR");
});
test("should be mobile-responsive at 375px width", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await expect(page.locator("h1")).toBeVisible();
});
});
test.describe("AGB Vermietung Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/agb-vermietung");
});
test("should display AGB heading", async ({ page }) => {
await expect(page.locator("h1")).toContainText("AGB");
});
test("should contain Geltungsbereich section", async ({ page }) => {
await expect(page.locator("body")).toContainText("Geltungsbereich");
});
test("should contain Preise section", async ({ page }) => {
await expect(page.locator("body")).toContainText("Preise");
});
test("should contain Zahlungsbedingungen section", async ({ page }) => {
await expect(page.locator("body")).toContainText("Zahlungsbedingungen");
});
test("should contain Haftung section", async ({ page }) => {
await expect(page.locator("body")).toContainText("Haftung");
});
test("should contain Rückgabe section", async ({ page }) => {
await expect(page.locator("body")).toContainText("Rückgabe");
});
test("should be mobile-responsive at 375px width", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await expect(page.locator("h1")).toBeVisible();
});
});
@@ -0,0 +1,27 @@
import { test, expect } from "@playwright/test";
test.describe("Meta and Robots", () => {
test("robots.txt should contain Disallow: /", async ({ request }) => {
const response = await request.get("/robots.txt");
const text = await response.text();
expect(text).toContain("User-agent: *");
expect(text).toContain("Disallow: /");
});
test("home page should have noindex meta tag", async ({ page }) => {
await page.goto("/");
const robotsMeta = page.locator('meta[name="robots"]');
await expect(robotsMeta).toHaveAttribute("content", /noindex/);
await expect(robotsMeta).toHaveAttribute("content", /nofollow/);
await expect(robotsMeta).toHaveAttribute("content", /noarchive/);
await expect(robotsMeta).toHaveAttribute("content", /nosnippet/);
});
test("home page should contain JSON-LD LocalBusiness", async ({ page }) => {
await page.goto("/");
const jsonld = page.locator('script[type="application/ld+json"]');
const content = await jsonld.textContent();
expect(content).toContain("LocalBusiness");
expect(content).toContain("Hammerschmidt");
});
});
@@ -0,0 +1,27 @@
import { test, expect } from "@playwright/test";
test.describe("Mietanfrage Page", () => {
test("should render mietanfrage page with heading", async ({ page }) => {
await page.goto("/mietanfrage");
await expect(page.locator("h1")).toContainText("Mietanfrage");
});
test("should show empty cart state when no items in cart", async ({ page }) => {
await page.goto("/mietanfrage");
await expect(page.getByTestId("mietanfrage-empty-cart")).toBeVisible();
});
test("should have link to mietkatalog from empty cart state", async ({ page }) => {
await page.goto("/mietanfrage");
const cta = page.getByTestId("mietanfrage-empty-cta");
await expect(cta).toBeVisible();
await expect(cta).toHaveAttribute("href", "/mietkatalog");
});
test("should have breadcrumb with warenkorb link", async ({ page }) => {
await page.goto("/mietanfrage");
const breadcrumb = page.locator('nav[aria-label="Breadcrumb"]');
await expect(breadcrumb).toBeVisible();
await expect(breadcrumb.locator('a[href="/warenkorb"]')).toBeVisible();
});
});
@@ -0,0 +1,76 @@
import { test, expect } from "@playwright/test";
test.describe("Mietkatalog Page", () => {
test("should render SSR HTML with Mietkatalog heading", async ({ page }) => {
await page.goto("/mietkatalog");
await expect(page.locator("h1")).toContainText("Mietkatalog");
});
test("should have search input visible", async ({ page }) => {
await page.goto("/mietkatalog");
const searchInput = page.getByTestId("equipment-search");
await expect(searchInput).toBeVisible();
});
test("should have sort dropdown with name options", async ({ page }) => {
await page.goto("/mietkatalog");
const sortSelect = page.getByTestId("sort-select");
await expect(sortSelect).toBeVisible();
await expect(sortSelect.locator("option")).toHaveCount(2);
});
test("should have category filter chips", async ({ page }) => {
await page.goto("/mietkatalog");
const chips = page.getByTestId("category-chips");
await expect(chips).toBeVisible();
});
test("should have reset filters button", async ({ page }) => {
await page.goto("/mietkatalog");
const resetBtn = page.getByTestId("reset-filters");
await expect(resetBtn).toBeVisible();
});
test("should filter equipment by search input", async ({ page }) => {
await page.goto("/mietkatalog");
const searchInput = page.getByTestId("equipment-search");
await searchInput.fill("Lautsprecher");
await page.waitForTimeout(500);
const grid = page.getByTestId("equipment-grid");
await expect(grid).toBeVisible();
});
test("should reset all filters when reset button clicked", async ({ page }) => {
await page.goto("/mietkatalog");
const searchInput = page.getByTestId("equipment-search");
await searchInput.fill("Test");
const resetBtn = page.getByTestId("reset-filters");
await resetBtn.click();
await expect(searchInput).toHaveValue("");
});
test("should show result count", async ({ page }) => {
await page.goto("/mietkatalog");
const resultCount = page.getByTestId("result-count");
await expect(resultCount).toBeVisible();
});
test("should display equipment cards or empty state", async ({ page }) => {
await page.goto("/mietkatalog");
const grid = page.getByTestId("equipment-grid");
const emptyState = page.getByText("Keine Artikel gefunden");
const isVisible = await grid.isVisible().catch(() => false) || await emptyState.isVisible().catch(() => false);
expect(isVisible).toBeTruthy();
});
test("should navigate to detail page on card click", async ({ page }) => {
await page.goto("/mietkatalog");
const card = page.getByTestId("equipment-card").first();
if (await card.isVisible()) {
const link = card.locator("a[href*='/mietkatalog/']").first();
await link.click();
await page.waitForURL(/\/mietkatalog\/\d+/);
await expect(page).toHaveURL(/\/mietkatalog\/\d+/);
}
});
});
@@ -0,0 +1,80 @@
import { test, expect } from "@playwright/test";
test.describe("Referenzen Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/referenzen");
});
test("should display page heading", async ({ page }) => {
await expect(page).toHaveTitle(/Referenzen/);
});
test("should show 9 gallery images in grid", async ({ page }) => {
const grid = page.locator('[data-testid="gallery-grid"]');
await expect(grid).toBeVisible();
const items = grid.locator('[data-testid^="gallery-item-"]');
await expect(items).toHaveCount(9);
});
test("should have 4 category filter chips", async ({ page }) => {
const chips = page.locator('[data-testid="filter-chips"]');
await expect(chips).toBeVisible();
await expect(chips).toContainText("Alle");
await expect(chips).toContainText("Open-Air");
await expect(chips).toContainText("Indoor");
await expect(chips).toContainText("Rigging");
});
test("should filter images by category", async ({ page }) => {
// Click Open-Air filter
await page.locator('[data-testid="filter-open-air"]').click();
const grid = page.locator('[data-testid="gallery-grid"]');
const items = grid.locator('[data-testid^="gallery-item-"]');
const count = await items.count();
expect(count).toBeLessThan(9);
expect(count).toBeGreaterThan(0);
// Click Alle to reset
await page.locator('[data-testid="filter-alle"]').click();
await expect(items).toHaveCount(9);
});
test("should open lightbox on image click", async ({ page }) => {
const firstItem = page.locator('[data-testid="gallery-item-1"]');
await firstItem.click();
const lightbox = page.locator('[data-testid="lightbox"]');
await expect(lightbox).toBeVisible();
});
test("should close lightbox with close button", async ({ page }) => {
await page.locator('[data-testid="gallery-item-1"]').click();
const lightbox = page.locator('[data-testid="lightbox"]');
await expect(lightbox).toBeVisible();
await page.locator('[data-testid="lightbox-close"]').click();
await expect(lightbox).not.toBeVisible();
});
test("should navigate to next image in lightbox", async ({ page }) => {
await page.locator('[data-testid="gallery-item-1"]').click();
const lightbox = page.locator('[data-testid="lightbox"]');
await expect(lightbox).toBeVisible();
const firstCaption = await lightbox.locator("p").textContent();
await page.locator('[data-testid="lightbox-next"]').click();
const secondCaption = await lightbox.locator("p").textContent();
expect(firstCaption).not.toBe(secondCaption);
});
test("should navigate to prev image in lightbox", async ({ page }) => {
await page.locator('[data-testid="gallery-item-1"]').click();
const lightbox = page.locator('[data-testid="lightbox"]');
await expect(lightbox).toBeVisible();
await page.locator('[data-testid="lightbox-prev"]').click();
await expect(lightbox).toBeVisible();
});
test("should be mobile-responsive at 375px width", async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await expect(page.locator('[data-testid="gallery-grid"]')).toBeVisible();
await expect(page.locator('[data-testid="filter-chips"]')).toBeVisible();
});
});
@@ -0,0 +1,101 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Cart Store", () => {
const storePath = resolve(__dirname, "../../stores/cart.ts");
const content = readFileSync(storePath, "utf-8");
it("should define useCartStore with defineStore", () => {
expect(content).toContain("defineStore");
expect(content).toContain("useCartStore");
});
it("should have CartItem interface with required fields", () => {
expect(content).toContain("equipment_id");
expect(content).toContain("name");
expect(content).toContain("quantity");
expect(content).toContain("rental_price");
expect(content).toContain("image_url");
});
it("should have totalCount getter", () => {
expect(content).toContain("totalCount");
expect(content).toContain("reduce");
});
it("should have isEmpty getter", () => {
expect(content).toContain("isEmpty");
});
it("should have hasItems getter", () => {
expect(content).toContain("hasItems");
});
it("should have apiItems getter for rental request payload", () => {
expect(content).toContain("apiItems");
expect(content).toContain("equipment_id");
expect(content).toContain("quantity");
});
it("should have addItem action", () => {
expect(content).toContain("addItem");
expect(content).toContain("existing");
expect(content).toContain("quantity += 1");
});
it("should have addItemByFields action", () => {
expect(content).toContain("addItemByFields");
});
it("should have removeItem action", () => {
expect(content).toContain("removeItem");
expect(content).toContain("splice");
});
it("should have updateQuantity action", () => {
expect(content).toContain("updateQuantity");
expect(content).toContain("quantity <= 0");
});
it("should have incrementQuantity action", () => {
expect(content).toContain("incrementQuantity");
});
it("should have decrementQuantity action", () => {
expect(content).toContain("decrementQuantity");
});
it("should have clearCart action", () => {
expect(content).toContain("clearCart");
expect(content).toContain("items = []");
});
it("should have RentalRequestPayload interface", () => {
expect(content).toContain("RentalRequestPayload");
expect(content).toContain("event_name");
expect(content).toContain("date_start");
expect(content).toContain("date_end");
expect(content).toContain("location");
expect(content).toContain("person_count");
expect(content).toContain("contact_name");
expect(content).toContain("contact_email");
expect(content).toContain("contact_phone");
expect(content).toContain("contact_street");
expect(content).toContain("contact_postalcode");
expect(content).toContain("contact_city");
expect(content).toContain("message");
expect(content).toContain("items");
});
it("should have RentalRequestResponse interface", () => {
expect(content).toContain("RentalRequestResponse");
expect(content).toContain("reference_number");
expect(content).toContain("status");
});
it("should use TypeScript", () => {
expect(content).toContain("import type");
expect(content).toContain("export interface");
});
});
@@ -0,0 +1,106 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Design Tokens (prototype-style.css)", () => {
const cssPath = resolve(__dirname, "../../assets/css/main.css");
const cssContent = readFileSync(cssPath, "utf-8");
it("should define --bg variable in :root", () => {
expect(cssContent).toContain("--bg: #131313");
});
it("should define --panel variable in :root", () => {
expect(cssContent).toContain("--panel: #1a1a1a");
});
it("should define --color-accent variable in :root", () => {
expect(cssContent).toContain("--color-accent: #EC6925");
});
it("should define --surface variable in :root", () => {
expect(cssContent).toContain("--surface: #212121");
});
it("should define --text variable in :root", () => {
expect(cssContent).toContain("--text: #ffffff");
});
it("should define --text-muted variable in :root", () => {
expect(cssContent).toContain("--text-muted: #d4d4d4");
});
it("should define radius tokens", () => {
expect(cssContent).toContain("--radius-sm: 2px");
expect(cssContent).toContain("--radius-md: 3px");
expect(cssContent).toContain("--radius-lg: 4px");
});
it("should define transition tokens", () => {
expect(cssContent).toContain("--transition-fast: 150ms ease");
});
it("should define hms-* CSS classes", () => {
expect(cssContent).toContain(".hms-header");
expect(cssContent).toContain(".hms-hero");
expect(cssContent).toContain(".hms-card");
expect(cssContent).toContain(".hms-btn");
expect(cssContent).toContain(".hms-btn-primary");
expect(cssContent).toContain(".hms-btn-secondary");
expect(cssContent).toContain(".hms-nav-btn");
expect(cssContent).toContain(".hms-badge");
expect(cssContent).toContain(".hms-input");
expect(cssContent).toContain(".hms-skeleton");
expect(cssContent).toContain(".hms-spinner");
expect(cssContent).toContain(".hms-speaker-grid");
expect(cssContent).toContain(".hms-footer");
expect(cssContent).toContain(".hms-gallery");
expect(cssContent).toContain(".hms-eq-card");
expect(cssContent).toContain(".hms-chip");
});
it("should include Tailwind directives", () => {
expect(cssContent).toContain("@tailwind base");
expect(cssContent).toContain("@tailwind components");
expect(cssContent).toContain("@tailwind utilities");
});
});
describe("Tailwind Config", () => {
const configPath = resolve(__dirname, "../../tailwind.config.ts");
const configContent = readFileSync(configPath, "utf-8");
it("should extend colors with accent", () => {
expect(configContent).toContain("accent");
expect(configContent).toContain("#EC6925");
});
it("should configure Inter font family", () => {
expect(configContent).toContain("Inter");
});
});
describe("Nuxt Config", () => {
const configPath = resolve(__dirname, "../../nuxt.config.ts");
const configContent = readFileSync(configPath, "utf-8");
it("should include noindex robots meta tag", () => {
expect(configContent).toContain("noindex");
expect(configContent).toContain("nofollow");
});
it("should include JSON-LD LocalBusiness script", () => {
expect(configContent).toContain("LocalBusiness");
expect(configContent).toContain("schema.org");
});
it("should include routeRules for SSR/CSR", () => {
expect(configContent).toContain("routeRules");
expect(configContent).toContain("/mietkatalog");
expect(configContent).toContain("/admin");
});
it("should include Tailwind CSS module", () => {
expect(configContent).toContain("@nuxtjs/tailwindcss");
});
});
@@ -0,0 +1,83 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Equipment Detail Page (prototype port)", () => {
const pagePath = resolve(__dirname, "../../pages/mietkatalog/[id].vue");
const content = readFileSync(pagePath, "utf-8");
it("should have breadcrumb navigation back to mietkatalog", () => {
expect(content).toContain("Zurück zum Katalog");
expect(content).toContain("/mietkatalog");
});
it("should display equipment name", () => {
expect(content).toContain("item.name");
});
it("should display item code", () => {
expect(content).toContain("item.code");
expect(content).toContain("Artikelnummer");
});
it("should display brand", () => {
expect(content).toContain("item.brand");
expect(content).toContain("Marke");
});
it("should display description", () => {
expect(content).toContain("item.description");
});
it("should display specs table", () => {
expect(content).toContain("item.specs");
expect(content).toContain("Technische Daten");
});
it("should display image with placeholder fallback", () => {
expect(content).toContain("item.image");
expect(content).toContain("📦");
expect(content).toContain("Kein Bild verfügbar");
});
it("should have Mietanfrage section", () => {
expect(content).toContain("Mietanfrage");
expect(content).toContain("Mietbeginn");
expect(content).toContain("Mietende");
});
it("should have quantity selector", () => {
expect(content).toContain("quantity");
expect(content).toContain("Anzahl");
});
it("should have add to cart button", () => {
expect(content).toContain("Zur Mietanfrage hinzufügen");
expect(content).toContain("hms-btn-primary");
});
it("should show ErrorState for non-existent ID", () => {
expect(content).toContain("ErrorState");
expect(content).toContain("nicht gefunden");
});
it("should have related items section", () => {
expect(content).toContain("relatedItems");
expect(content).toContain("Ähnliche Geräte");
expect(content).toContain("EquipmentCard");
});
it("should use loading skeleton", () => {
expect(content).toContain("hms-skeleton");
expect(content).toContain("loading");
});
it("should use TypeScript with lang=ts", () => {
expect(content).toContain('<script setup lang="ts">');
});
it("should have 18 equipment items in data", () => {
expect(content).toContain("L-Acoustics K2");
expect(content).toContain("Sennheiser EW-DX 835");
});
});
@@ -0,0 +1,74 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Home Page (prototype port)", () => {
const pagePath = resolve(__dirname, "../../pages/index.vue");
const content = readFileSync(pagePath, "utf-8");
it("should have hms-hero section", () => {
expect(content).toContain("hms-hero");
expect(content).toContain("hms-hero-bg");
expect(content).toContain("hms-hero-overlay");
});
it("should have hero title with Veranstaltungstechnik text", () => {
expect(content).toContain("Veranstaltungstechnik");
expect(content).toContain("Professionelle Technik");
});
it("should have Mietkatalog button", () => {
expect(content).toContain("Mietkatalog");
expect(content).toContain("hms-btn-primary");
});
it("should have Beratung anfragen button", () => {
expect(content).toContain("Beratung anfragen");
expect(content).toContain("hms-btn-secondary");
});
it("should display all 8 services", () => {
expect(content).toContain("Vermietung");
expect(content).toContain("Verkauf");
expect(content).toContain("Personal");
expect(content).toContain("Transport");
expect(content).toContain("Lagerung");
expect(content).toContain("Werkstatt");
expect(content).toContain("Installation");
expect(content).toContain("Booking");
});
it("should use ServiceCard component", () => {
expect(content).toContain("ServiceCard");
});
it("should have hms-speaker-grid with 6 speakers", () => {
expect(content).toContain("hms-speaker-grid");
expect(content).toContain("hms-speaker-item");
expect(content).toContain("SpeakerIcon");
expect(content).toContain("L-Acoustics K2");
expect(content).toContain("L-Acoustics KS28");
expect(content).toContain("Robe Pointe");
});
it("should have about section with stats", () => {
expect(content).toContain("20+");
expect(content).toContain("1.000+");
expect(content).toContain("Jahre Erfahrung");
expect(content).toContain("Geräte im Lager");
});
it("should have CTA section with Katalog durchsuchen", () => {
expect(content).toContain("Katalog durchsuchen");
expect(content).toContain("Online-Mietkatalog");
});
it("should use TypeScript with lang=ts", () => {
expect(content).toContain('<script setup lang="ts">');
});
it("should use hms-badge hms-badge-primary", () => {
expect(content).toContain("hms-badge");
expect(content).toContain("hms-badge-primary");
});
});
@@ -0,0 +1,84 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Kontakt Page (prototype port)", () => {
const pagePath = resolve(__dirname, "../../pages/kontakt.vue");
const content = readFileSync(pagePath, "utf-8");
it("should have page title Kontakt", () => {
expect(content).toContain("Kontakt");
});
it("should have office address card with Leipheim", () => {
expect(content).toContain("Grockelhofen 10");
expect(content).toContain("89340 Leipheim");
});
it("should have warehouse address with Ellzee", () => {
expect(content).toContain("Zur Schönhalde 8");
expect(content).toContain("89352 Ellzee");
});
it("should have opening hours", () => {
expect(content).toContain("Öffnungszeiten");
expect(content).toContain("10:00 18:00");
});
it("should have 2 contact persons", () => {
expect(content).toContain("Leopold Hammerschmidt");
expect(content).toContain("Andreas Mössle");
});
it("should have form with name field (required)", () => {
expect(content).toContain('id="name"');
expect(content).toContain("Name");
});
it("should have form with email field (required)", () => {
expect(content).toContain('id="email"');
expect(content).toContain("E-Mail");
});
it("should have form with phone field", () => {
expect(content).toContain('id="phone"');
expect(content).toContain("Telefon");
});
it("should have form with subject select", () => {
expect(content).toContain('id="subject"');
expect(content).toContain("Anliegen");
});
it("should have form with message textarea (required)", () => {
expect(content).toContain('id="message"');
expect(content).toContain("Nachricht");
});
it("should have privacy consent checkbox", () => {
expect(content).toContain('type="checkbox"');
expect(content).toContain("Datenschutzerklärung");
});
it("should have submit button", () => {
expect(content).toContain("Nachricht senden");
expect(content).toContain("hms-btn-primary");
});
it("should have email validation", () => {
expect(content).toContain("validate");
});
it("should have success state", () => {
expect(content).toContain("Nachricht übermittelt");
expect(content).toContain("Vielen Dank");
});
it("should have hms-input class", () => {
expect(content).toContain("hms-input");
});
it("should use TypeScript with lang=ts", () => {
expect(content).toContain('<script setup lang="ts">');
});
});
+167
View File
@@ -0,0 +1,167 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("AppHeader Component (prototype port)", () => {
const componentPath = resolve(__dirname, "../../components/AppHeader.vue");
const content = readFileSync(componentPath, "utf-8");
it("should have hms-header class", () => {
expect(content).toContain("hms-header");
});
it("should have nav items: Home, Referenzen, Mietkatalog, Kontakt", () => {
expect(content).toContain("Home");
expect(content).toContain("Referenzen");
expect(content).toContain("Mietkatalog");
expect(content).toContain("Kontakt");
});
it("should have Warenkorb button with hms-btn-primary", () => {
expect(content).toContain("Warenkorb");
expect(content).toContain("hms-btn-primary");
});
it("should have hms-nav-btn class for nav items", () => {
expect(content).toContain("hms-nav-btn");
});
it("should have mobile burger menu with aria-expanded", () => {
expect(content).toContain("aria-expanded");
expect(content).toContain("mobileMenuOpen");
});
it("should use HmsLogo component", () => {
expect(content).toContain("HmsLogo");
});
it("should have NuxtLink for navigation", () => {
expect(content).toContain("NuxtLink");
});
it("should have aria-current for active route", () => {
expect(content).toContain("aria-current");
});
});
describe("AppFooter Component (prototype port)", () => {
const componentPath = resolve(__dirname, "../../components/AppFooter.vue");
const content = readFileSync(componentPath, "utf-8");
it("should have hms-footer class", () => {
expect(content).toContain("hms-footer");
});
it("should contain address with Leipheim", () => {
expect(content).toContain("89340");
expect(content).toContain("Leipheim");
});
it("should contain phone number", () => {
expect(content).toContain("tel:+498221204433");
expect(content).toContain("204433");
});
it("should contain email link info@hms-licht-ton.de", () => {
expect(content).toContain("mailto:info@hms-licht-ton.de");
});
it("should contain Impressum link", () => {
expect(content).toContain("Impressum");
expect(content).toContain("/impressum");
});
it("should contain DSGVO link", () => {
expect(content).toContain("DSGVO");
expect(content).toContain("/datenschutz");
});
it("should contain AGB Vermietung link", () => {
expect(content).toContain("AGB Vermietung");
expect(content).toContain("/agb-vermietung");
});
it("should contain Admin-Login link", () => {
expect(content).toContain("Admin-Login");
expect(content).toContain("/admin");
});
it("should have 4-column grid layout", () => {
expect(content).toContain("lg:grid-cols-4");
});
it("should include Facebook social icon link", () => {
expect(content).toContain("facebook.com");
});
it("should include Instagram social icon link", () => {
expect(content).toContain("instagram.com");
});
it("should use HmsLogo component", () => {
expect(content).toContain("HmsLogo");
});
});
describe("Error Page (404, prototype port)", () => {
const errorPath = resolve(__dirname, "../../error.vue");
const content = readFileSync(errorPath, "utf-8");
it("should contain 'Seite nicht gefunden' for 404", () => {
expect(content).toContain("Seite nicht gefunden");
});
it("should contain 404 text", () => {
expect(content).toContain("404");
});
it("should contain link to home", () => {
expect(content).toContain('to="/"');
expect(content).toContain("Zur Startseite");
});
it("should include noindex meta tag", () => {
expect(content).toContain("noindex");
expect(content).toContain("nofollow");
});
it("should have hms-btn hms-btn-primary", () => {
expect(content).toContain("hms-btn");
expect(content).toContain("hms-btn-primary");
});
});
describe("HmsLogo Component (prototype port)", () => {
const logoPath = resolve(__dirname, "../../components/HmsLogo.vue");
const content = readFileSync(logoPath, "utf-8");
it("should render SVG element", () => {
expect(content).toContain("<svg");
});
it("should use orange accent color #EC6925", () => {
expect(content).toContain("#EC6925");
});
it("should have hms-logo-svg class", () => {
expect(content).toContain("hms-logo-svg");
});
it("should accept size prop with default 40", () => {
expect(content).toContain("size");
expect(content).toContain("40");
});
});
describe("robots.txt route", () => {
const robotsPath = resolve(__dirname, "../../server/routes/robots.txt.ts");
const content = readFileSync(robotsPath, "utf-8");
it("should contain User-agent: *", () => {
expect(content).toContain("User-agent: *");
});
it("should contain Disallow: /", () => {
expect(content).toContain("Disallow: /");
});
});
@@ -0,0 +1,124 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Mietkatalog Page (prototype port)", () => {
const pagePath = resolve(__dirname, "../../pages/mietkatalog.vue");
const content = readFileSync(pagePath, "utf-8");
it("should have page title Mietkatalog", () => {
expect(content).toContain("Mietkatalog");
});
it("should have search input with hms-input", () => {
expect(content).toContain('type="search"');
expect(content).toContain("hms-input");
expect(content).toContain("searchQuery");
});
it("should have sort dropdown", () => {
expect(content).toContain("sortBy");
expect(content).toContain("name");
expect(content).toContain("brand");
expect(content).toContain("code");
});
it("should have category filter chips with hms-chip", () => {
expect(content).toContain("hms-chip");
expect(content).toContain("activeCategory");
expect(content).toContain("Tontechnik");
expect(content).toContain("Lichttechnik");
expect(content).toContain("Rigging");
});
it("should show result count", () => {
expect(content).toContain("filteredEquipment.length");
expect(content).toContain("Gerät");
expect(content).toContain("gefunden");
});
it("should use LoadingSkeleton component", () => {
expect(content).toContain("LoadingSkeleton");
});
it("should use ErrorState component", () => {
expect(content).toContain("ErrorState");
});
it("should use EmptyState component", () => {
expect(content).toContain("EmptyState");
});
it("should render EquipmentCard for each item", () => {
expect(content).toContain("EquipmentCard");
expect(content).toContain("filteredEquipment");
});
it("should have 18 equipment items", () => {
expect(content).toContain("L-Acoustics K2");
expect(content).toContain("Shure SM58");
expect(content).toContain("Sennheiser EW-DX 835");
});
it("should have Warenkorb button", () => {
expect(content).toContain("Warenkorb ansehen");
});
it("should use TypeScript with lang=ts", () => {
expect(content).toContain('<script setup lang="ts">');
});
it("should use hms-card for filter panel", () => {
expect(content).toContain("hms-card");
});
});
describe("EquipmentCard Component (prototype port)", () => {
const cardPath = resolve(__dirname, "../../components/EquipmentCard.vue");
const content = readFileSync(cardPath, "utf-8");
it("should have hms-eq-card class", () => {
expect(content).toContain("hms-eq-card");
});
it("should have image with fallback placeholder", () => {
expect(content).toContain("item.image");
expect(content).toContain("📦");
});
it("should show category badge", () => {
expect(content).toContain("hms-badge");
expect(content).toContain("hms-badge-primary");
expect(content).toContain("item.category");
});
it("should display equipment name", () => {
expect(content).toContain("item.name");
});
it("should show truncated description", () => {
expect(content).toContain("item.description");
expect(content).toContain("line-clamp");
});
it("should display item code", () => {
expect(content).toContain("item.code");
});
it("should emit add-to-cart", () => {
expect(content).toContain("add-to-cart");
});
it("should have Hinzufügen button", () => {
expect(content).toContain("Hinzufügen");
expect(content).toContain("hms-btn-ghost");
});
it("should emit click", () => {
expect(content).toContain("click");
});
it("should use TypeScript with lang=ts", () => {
expect(content).toContain('<script setup lang="ts">');
});
});
@@ -0,0 +1,53 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Referenzen Page (prototype port)", () => {
const pagePath = resolve(__dirname, "../../pages/referenzen.vue");
const content = readFileSync(pagePath, "utf-8");
it("should have page title Referenzen", () => {
expect(content).toContain("Referenzen");
});
it("should have hms-gallery grid", () => {
expect(content).toContain("hms-gallery");
});
it("should have 9 reference images", () => {
expect(content).toContain("ref1.jpg");
expect(content).toContain("ref9.jpg");
});
it("should have category filter chips with hms-chip", () => {
expect(content).toContain("hms-chip");
expect(content).toContain("Alle");
expect(content).toContain("Open-Air");
expect(content).toContain("Indoor");
expect(content).toContain("Rigging");
expect(content).toContain("Event");
});
it("should have filter logic with computed filteredImages", () => {
expect(content).toContain("filteredImages");
expect(content).toContain("filter");
expect(content).toContain("computed");
});
it("should have loading skeleton state", () => {
expect(content).toContain("hms-skeleton");
expect(content).toContain("loading");
});
it("should use ErrorState component", () => {
expect(content).toContain("ErrorState");
});
it("should use EmptyState component", () => {
expect(content).toContain("EmptyState");
});
it("should use TypeScript with lang=ts", () => {
expect(content).toContain('<script setup lang="ts">');
});
});
@@ -0,0 +1,86 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("Warenkorb Page (prototype port)", () => {
const pagePath = resolve(__dirname, "../../pages/warenkorb.vue");
const content = readFileSync(pagePath, "utf-8");
it("should have page title Mietanfrage", () => {
expect(content).toContain("Mietanfrage");
});
it("should use useCart composable", () => {
expect(content).toContain("useCart");
});
it("should have empty state with link to mietkatalog", () => {
expect(content).toContain("EmptyState");
expect(content).toContain("Warenkorb ist leer");
expect(content).toContain("/mietkatalog");
});
it("should have cart item list", () => {
expect(content).toContain("cartItems");
expect(content).toContain("v-for");
});
it("should have quantity stepper with increment and decrement", () => {
expect(content).toContain("incrementQuantity");
expect(content).toContain("decrementQuantity");
});
it("should display item quantity", () => {
expect(content).toContain("item.quantity");
});
it("should have remove button", () => {
expect(content).toContain("removeItem");
});
it("should have clear cart button", () => {
expect(content).toContain("clearCart");
expect(content).toContain("Warenkorb leeren");
});
it("should display total count", () => {
expect(content).toContain("totalCount");
});
it("should have request form with name field", () => {
expect(content).toContain('id="req-name"');
expect(content).toContain("Name");
});
it("should have request form with email field", () => {
expect(content).toContain('id="req-email"');
expect(content).toContain("E-Mail");
});
it("should have request form with event date field", () => {
expect(content).toContain('id="req-date"');
expect(content).toContain("Veranstaltungsdatum");
});
it("should have request form with location field", () => {
expect(content).toContain('id="req-location"');
expect(content).toContain("Veranstaltungsort");
});
it("should have submit button", () => {
expect(content).toContain("Anfrage absenden");
});
it("should have success state", () => {
expect(content).toContain("Mietanfrage übermittelt");
expect(content).toContain("Vielen Dank");
});
it("should have hms-card for items", () => {
expect(content).toContain("hms-card");
});
it("should use TypeScript with lang=ts", () => {
expect(content).toContain('<script setup lang="ts">');
});
});
@@ -0,0 +1,84 @@
import { describe, it, expect } from "vitest";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
describe("useApi composable", () => {
const path = resolve(__dirname, "../../composables/useApi.ts");
const content = readFileSync(path, "utf-8");
it("should export useApi function", () => {
expect(content).toContain("export function useApi");
});
it("should use useRuntimeConfig for apiBase", () => {
expect(content).toContain("useRuntimeConfig");
expect(content).toContain("apiBase");
});
it("should create $fetch client with baseURL", () => {
expect(content).toContain("$fetch.create");
expect(content).toContain("baseURL");
});
it("should export get, post, put, del methods", () => {
expect(content).toContain("async function get");
expect(content).toContain("async function post");
expect(content).toContain("async function put");
expect(content).toContain("async function del");
});
});
describe("useEquipment composable", () => {
const path = resolve(__dirname, "../../composables/useEquipment.ts");
const content = readFileSync(path, "utf-8");
it("should export EquipmentItem interface", () => {
expect(content).toContain("export interface EquipmentItem");
});
it("should export EquipmentDetail interface", () => {
expect(content).toContain("export interface EquipmentDetail");
});
it("should export PaginatedEquipment interface", () => {
expect(content).toContain("export interface PaginatedEquipment");
});
it("should export SortOption type", () => {
expect(content).toContain("export type SortOption");
});
it("should use useApi internally", () => {
expect(content).toContain("useApi");
});
it("should have list method with search, category, sort, page params", () => {
expect(content).toContain("async function list");
expect(content).toContain("search");
expect(content).toContain("category");
expect(content).toContain("sort");
expect(content).toContain("page");
});
it("should call /api/equipment for list", () => {
expect(content).toContain("/api/equipment");
expect(content).toContain("/api/equipment/categories");
expect(content).toContain("/api/equipment/${id}");
});
it("should have detail method", () => {
expect(content).toContain("async function detail");
});
it("should have categories method", () => {
expect(content).toContain("async function categories");
expect(content).toContain("/api/equipment/categories");
});
it("should return list, detail, categories from composable", () => {
expect(content).toContain("return");
expect(content).toContain("list");
expect(content).toContain("detail");
expect(content).toContain("categories");
});
});