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:
@@ -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">');
|
||||
});
|
||||
});
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user