Some checks failed
Tests / Go Tests (push) Has been cancelled
Tests / Python Tests (push) Has been cancelled
Tests / Integration Tests (push) Has been cancelled
Tests / Go Lint (push) Has been cancelled
Tests / Python Lint (push) Has been cancelled
Tests / Security Scan (push) Has been cancelled
Tests / All Checks Passed (push) Has been cancelled
Security Scanning / Secret Scanning (push) Has been cancelled
Security Scanning / Dependency Vulnerability Scan (push) Has been cancelled
Security Scanning / Go Security Scan (push) Has been cancelled
Security Scanning / Python Security Scan (push) Has been cancelled
Security Scanning / Node.js Security Scan (push) Has been cancelled
Security Scanning / Docker Image Security (push) Has been cancelled
Security Scanning / Security Summary (push) Has been cancelled
CI/CD Pipeline / Go Tests (push) Has been cancelled
CI/CD Pipeline / Python Tests (push) Has been cancelled
CI/CD Pipeline / Website Tests (push) Has been cancelled
CI/CD Pipeline / Linting (push) Has been cancelled
CI/CD Pipeline / Security Scan (push) Has been cancelled
CI/CD Pipeline / Docker Build & Push (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Deploy to Staging (push) Has been cancelled
CI/CD Pipeline / Deploy to Production (push) Has been cancelled
CI/CD Pipeline / CI Summary (push) Has been cancelled
ci/woodpecker/manual/build-ci-image Pipeline was successful
ci/woodpecker/manual/main Pipeline failed
All services: admin-v2, studio-v2, website, ai-compliance-sdk, consent-service, klausur-service, voice-service, and infrastructure. Large PDFs and compiled binaries excluded via .gitignore.
318 lines
9.0 KiB
TypeScript
318 lines
9.0 KiB
TypeScript
/**
|
|
* Website Structure Tests
|
|
*
|
|
* Testet, dass alle wichtigen Seiten und Komponenten existieren.
|
|
* Diese Tests sind statische Struktur-Tests, keine Runtime-Tests.
|
|
*/
|
|
|
|
import { existsSync, readFileSync } from 'fs'
|
|
import { join } from 'path'
|
|
|
|
const ROOT_DIR = join(__dirname, '..')
|
|
const APP_DIR = join(ROOT_DIR, 'app')
|
|
const COMPONENTS_DIR = join(ROOT_DIR, 'components')
|
|
const LIB_DIR = join(ROOT_DIR, 'lib')
|
|
|
|
describe('Website Structure', () => {
|
|
describe('App Pages', () => {
|
|
it('has landing page (page.tsx)', () => {
|
|
expect(existsSync(join(APP_DIR, 'page.tsx'))).toBe(true)
|
|
})
|
|
|
|
it('has layout (layout.tsx)', () => {
|
|
expect(existsSync(join(APP_DIR, 'layout.tsx'))).toBe(true)
|
|
})
|
|
|
|
it('has FAQ page', () => {
|
|
expect(existsSync(join(APP_DIR, 'faq', 'page.tsx'))).toBe(true)
|
|
})
|
|
|
|
it('has success page', () => {
|
|
expect(existsSync(join(APP_DIR, 'success', 'page.tsx'))).toBe(true)
|
|
})
|
|
|
|
it('has cancel page', () => {
|
|
expect(existsSync(join(APP_DIR, 'cancel', 'page.tsx'))).toBe(true)
|
|
})
|
|
|
|
it('has admin page', () => {
|
|
expect(existsSync(join(APP_DIR, 'admin', 'page.tsx'))).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('Components', () => {
|
|
it('has Header component', () => {
|
|
expect(existsSync(join(COMPONENTS_DIR, 'Header.tsx'))).toBe(true)
|
|
})
|
|
|
|
it('has Footer component', () => {
|
|
expect(existsSync(join(COMPONENTS_DIR, 'Footer.tsx'))).toBe(true)
|
|
})
|
|
|
|
it('has PricingSection component', () => {
|
|
expect(existsSync(join(COMPONENTS_DIR, 'PricingSection.tsx'))).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('Configuration', () => {
|
|
it('has package.json', () => {
|
|
expect(existsSync(join(ROOT_DIR, 'package.json'))).toBe(true)
|
|
})
|
|
|
|
it('has Dockerfile', () => {
|
|
expect(existsSync(join(ROOT_DIR, 'Dockerfile'))).toBe(true)
|
|
})
|
|
|
|
it('has tailwind.config.ts', () => {
|
|
expect(existsSync(join(ROOT_DIR, 'tailwind.config.ts'))).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('Lib', () => {
|
|
it('has content.ts', () => {
|
|
expect(existsSync(join(LIB_DIR, 'content.ts'))).toBe(true)
|
|
})
|
|
})
|
|
|
|
describe('API Routes', () => {
|
|
it('has content API route', () => {
|
|
expect(existsSync(join(APP_DIR, 'api', 'content', 'route.ts'))).toBe(true)
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('FAQ Page Content', () => {
|
|
const faqPagePath = join(APP_DIR, 'faq', 'page.tsx')
|
|
|
|
it('contains FAQ items', () => {
|
|
const content = readFileSync(faqPagePath, 'utf-8')
|
|
expect(content).toContain('faqItems')
|
|
})
|
|
|
|
it('has question about "Aufgabe"', () => {
|
|
const content = readFileSync(faqPagePath, 'utf-8')
|
|
expect(content).toContain('Was ist bei Breakpilot eine')
|
|
})
|
|
|
|
it('has question about trial', () => {
|
|
const content = readFileSync(faqPagePath, 'utf-8')
|
|
expect(content).toContain('kostenlos testen')
|
|
})
|
|
|
|
it('has question about DSGVO/data privacy', () => {
|
|
const content = readFileSync(faqPagePath, 'utf-8')
|
|
expect(content).toContain('DSGVO')
|
|
})
|
|
|
|
it('has question about cancellation', () => {
|
|
const content = readFileSync(faqPagePath, 'utf-8')
|
|
expect(content).toContain('kuendigen')
|
|
})
|
|
|
|
it('has at least 10 FAQ items', () => {
|
|
const content = readFileSync(faqPagePath, 'utf-8')
|
|
// Zaehle die Fragen (question: Eintraege)
|
|
const matches = content.match(/question:/g)
|
|
expect(matches).not.toBeNull()
|
|
expect(matches!.length).toBeGreaterThanOrEqual(10)
|
|
})
|
|
})
|
|
|
|
describe('Header Navigation', () => {
|
|
const headerPath = join(COMPONENTS_DIR, 'Header.tsx')
|
|
|
|
it('has link to FAQ', () => {
|
|
const content = readFileSync(headerPath, 'utf-8')
|
|
expect(content).toContain('/faq')
|
|
})
|
|
|
|
it('has link to pricing', () => {
|
|
const content = readFileSync(headerPath, 'utf-8')
|
|
expect(content).toContain('#pricing')
|
|
})
|
|
|
|
it('has link to features', () => {
|
|
const content = readFileSync(headerPath, 'utf-8')
|
|
expect(content).toContain('#features')
|
|
})
|
|
|
|
it('has mobile menu', () => {
|
|
const content = readFileSync(headerPath, 'utf-8')
|
|
expect(content).toContain('mobileMenuOpen')
|
|
})
|
|
})
|
|
|
|
describe('Landing Page Content', () => {
|
|
const landingPath = join(APP_DIR, 'page.tsx')
|
|
|
|
it('uses LandingContent component', () => {
|
|
const content = readFileSync(landingPath, 'utf-8')
|
|
expect(content).toContain('LandingContent')
|
|
})
|
|
|
|
it('imports Header component', () => {
|
|
const content = readFileSync(landingPath, 'utf-8')
|
|
expect(content).toContain('Header')
|
|
})
|
|
|
|
it('imports Footer component', () => {
|
|
const content = readFileSync(landingPath, 'utf-8')
|
|
expect(content).toContain('Footer')
|
|
})
|
|
|
|
it('loads content from getContent', () => {
|
|
const content = readFileSync(landingPath, 'utf-8')
|
|
expect(content).toContain('getContent')
|
|
})
|
|
|
|
it('passes content to LandingContent', () => {
|
|
const content = readFileSync(landingPath, 'utf-8')
|
|
expect(content).toContain('content={content}')
|
|
})
|
|
})
|
|
|
|
describe('PricingSection Content', () => {
|
|
const pricingPath = join(COMPONENTS_DIR, 'PricingSection.tsx')
|
|
|
|
it('has three plans', () => {
|
|
const content = readFileSync(pricingPath, 'utf-8')
|
|
expect(content).toContain('basic')
|
|
expect(content).toContain('standard')
|
|
expect(content).toContain('premium')
|
|
})
|
|
|
|
it('has trial button', () => {
|
|
const content = readFileSync(pricingPath, 'utf-8')
|
|
expect(content).toContain('7 Tage kostenlos')
|
|
})
|
|
|
|
it('has email form modal', () => {
|
|
const content = readFileSync(pricingPath, 'utf-8')
|
|
expect(content).toContain('showEmailForm')
|
|
})
|
|
|
|
it('calls billing service API', () => {
|
|
const content = readFileSync(pricingPath, 'utf-8')
|
|
expect(content).toContain('BILLING_API_URL')
|
|
expect(content).toContain('trial/start')
|
|
})
|
|
})
|
|
|
|
describe('Admin Page', () => {
|
|
const adminPath = join(APP_DIR, 'admin', 'page.tsx')
|
|
|
|
it('is a client component', () => {
|
|
const content = readFileSync(adminPath, 'utf-8')
|
|
expect(content).toContain("'use client'")
|
|
})
|
|
|
|
it('uses AdminLayout', () => {
|
|
const content = readFileSync(adminPath, 'utf-8')
|
|
expect(content).toContain('AdminLayout')
|
|
})
|
|
|
|
it('has quick actions section', () => {
|
|
const content = readFileSync(adminPath, 'utf-8')
|
|
expect(content).toContain('quickActions')
|
|
})
|
|
|
|
it('has stats display', () => {
|
|
const content = readFileSync(adminPath, 'utf-8')
|
|
expect(content).toContain('Stats')
|
|
})
|
|
|
|
it('loads stats from API', () => {
|
|
const content = readFileSync(adminPath, 'utf-8')
|
|
expect(content).toContain('loadStats')
|
|
})
|
|
})
|
|
|
|
describe('Content API Route', () => {
|
|
const apiPath = join(APP_DIR, 'api', 'content', 'route.ts')
|
|
|
|
it('has GET handler', () => {
|
|
const content = readFileSync(apiPath, 'utf-8')
|
|
expect(content).toContain('export async function GET')
|
|
})
|
|
|
|
it('has POST handler', () => {
|
|
const content = readFileSync(apiPath, 'utf-8')
|
|
expect(content).toContain('export async function POST')
|
|
})
|
|
|
|
it('validates admin key', () => {
|
|
const content = readFileSync(apiPath, 'utf-8')
|
|
expect(content).toContain('x-admin-key')
|
|
expect(content).toContain('Unauthorized')
|
|
})
|
|
|
|
it('uses content lib', () => {
|
|
const content = readFileSync(apiPath, 'utf-8')
|
|
expect(content).toContain('getContent')
|
|
expect(content).toContain('saveContent')
|
|
})
|
|
})
|
|
|
|
describe('Content Library', () => {
|
|
const contentPath = join(LIB_DIR, 'content.ts')
|
|
|
|
it('exports getContent function', () => {
|
|
const content = readFileSync(contentPath, 'utf-8')
|
|
expect(content).toContain('export function getContent')
|
|
})
|
|
|
|
it('exports saveContent function', () => {
|
|
const content = readFileSync(contentPath, 'utf-8')
|
|
expect(content).toContain('export function saveContent')
|
|
})
|
|
|
|
it('exports WebsiteContent type', () => {
|
|
const content = readFileSync(contentPath, 'utf-8')
|
|
expect(content).toContain('WebsiteContent')
|
|
})
|
|
|
|
it('exports HeroContent type', () => {
|
|
const content = readFileSync(contentPath, 'utf-8')
|
|
expect(content).toContain('HeroContent')
|
|
})
|
|
|
|
it('has default content', () => {
|
|
const content = readFileSync(contentPath, 'utf-8')
|
|
expect(content).toContain('defaultContent')
|
|
})
|
|
|
|
it('stores content as JSON', () => {
|
|
const content = readFileSync(contentPath, 'utf-8')
|
|
expect(content).toContain('website.json')
|
|
})
|
|
})
|
|
|
|
describe('Landing Page Uses Dynamic Content', () => {
|
|
const landingPath = join(APP_DIR, 'page.tsx')
|
|
const landingContentPath = join(COMPONENTS_DIR, 'LandingContent.tsx')
|
|
|
|
it('imports getContent', () => {
|
|
const content = readFileSync(landingPath, 'utf-8')
|
|
expect(content).toContain('import { getContent }')
|
|
})
|
|
|
|
it('calls getContent', () => {
|
|
const content = readFileSync(landingPath, 'utf-8')
|
|
expect(content).toContain('getContent()')
|
|
})
|
|
|
|
it('LandingContent component exists', () => {
|
|
expect(existsSync(landingContentPath)).toBe(true)
|
|
})
|
|
|
|
it('LandingContent has hero section', () => {
|
|
const content = readFileSync(landingContentPath, 'utf-8')
|
|
expect(content).toContain('Hero')
|
|
})
|
|
|
|
it('LandingContent has pricing section', () => {
|
|
const content = readFileSync(landingContentPath, 'utf-8')
|
|
expect(content).toContain('PricingSection')
|
|
})
|
|
})
|