import { describe, test, beforeAll, afterAll, expect } from 'vitest'; import { getWebsiteRouteContracts, RouteContract } from '../../shared/website/RouteContractSpec'; import { WebsiteServerHarness } from '../harness/WebsiteServerHarness'; import { ApiServerHarness } from '../harness/ApiServerHarness'; import { HttpDiagnostics } from '../../shared/website/HttpDiagnostics'; const WEBSITE_BASE_URL = process.env.WEBSITE_BASE_URL || 'http://localhost:3005'; const API_BASE_URL = process.env.API_BASE_URL || 'http://localhost:3006'; // Ensure WebsiteRouteManager uses the same persistence mode as the API harness process.env.GRIDPILOT_API_PERSISTENCE = 'inmemory'; describe('Website SSR Integration', () => { let websiteHarness: WebsiteServerHarness | null = null; let apiHarness: ApiServerHarness | null = null; const contracts = getWebsiteRouteContracts(); beforeAll(async () => { // 1. Start API console.log(`[WebsiteSSR] Starting API harness on ${API_BASE_URL}...`); apiHarness = new ApiServerHarness({ port: parseInt(new URL(API_BASE_URL).port) || 3006, }); await apiHarness.start(); console.log(`[WebsiteSSR] API Harness started.`); // 2. Start Website console.log(`[WebsiteSSR] Starting website harness on ${WEBSITE_BASE_URL}...`); websiteHarness = new WebsiteServerHarness({ port: parseInt(new URL(WEBSITE_BASE_URL).port) || 3005, env: { PORT: '3005', API_BASE_URL: API_BASE_URL, NEXT_PUBLIC_API_BASE_URL: API_BASE_URL, NODE_ENV: 'test', }, }); await websiteHarness.start(); console.log(`[WebsiteSSR] Website Harness started.`); }, 180000); afterAll(async () => { if (websiteHarness) { await websiteHarness.stop(); } if (apiHarness) { await apiHarness.stop(); } }); test.each(contracts)('SSR for $path ($accessLevel)', async (contract: RouteContract) => { const url = `${WEBSITE_BASE_URL}${contract.path}`; const response = await fetch(url, { method: 'GET', redirect: 'manual', }); const status = response.status; const location = response.headers.get('location'); const html = await response.text(); const failureContext = { url, status, location, html: html.substring(0, 1000), // Limit HTML in logs serverLogs: websiteHarness?.getLogTail(60), }; const formatFailure = (extra: string) => HttpDiagnostics.formatHttpFailure({ ...failureContext, extra }); // 1. Assert Status if (contract.expectedStatus === 'ok') { if (status !== 200) { throw new Error(formatFailure(`Expected status 200 OK, but got ${status}`)); } } else if (contract.expectedStatus === 'redirect') { if (status !== 302 && status !== 307) { throw new Error(formatFailure(`Expected redirect status (302/307), but got ${status}`)); } // 2. Assert Redirect Location if (contract.expectedRedirectTo) { if (!location) { throw new Error(formatFailure(`Expected redirect to ${contract.expectedRedirectTo}, but got no Location header`)); } const locationPathname = new URL(location, WEBSITE_BASE_URL).pathname; if (locationPathname !== contract.expectedRedirectTo) { throw new Error(formatFailure(`Expected redirect to pathname "${contract.expectedRedirectTo}", but got "${locationPathname}" (full: ${location})`)); } } } else if (contract.expectedStatus === 'notFoundAllowed') { if (status !== 404 && status !== 200) { throw new Error(formatFailure(`Expected 404 or 200 (notFoundAllowed), but got ${status}`)); } } else if (contract.expectedStatus === 'errorRoute') { // Error routes themselves should return 200 or their respective error codes (like 500) if (status >= 600) { throw new Error(formatFailure(`Error route returned unexpected status ${status}`)); } } // 3. Assert SSR HTML Markers (only if not a redirect) if (status === 200 || status === 404) { if (contract.ssrMustContain) { for (const marker of contract.ssrMustContain) { if (typeof marker === 'string') { if (!html.includes(marker)) { throw new Error(formatFailure(`SSR HTML missing expected marker: "${marker}"`)); } } else if (marker instanceof RegExp) { if (!marker.test(html)) { throw new Error(formatFailure(`SSR HTML missing expected regex marker: ${marker}`)); } } } } if (contract.ssrMustNotContain) { for (const marker of contract.ssrMustNotContain) { if (typeof marker === 'string') { if (html.includes(marker)) { throw new Error(formatFailure(`SSR HTML contains forbidden marker: "${marker}"`)); } } else if (marker instanceof RegExp) { if (marker.test(html)) { throw new Error(formatFailure(`SSR HTML contains forbidden regex marker: ${marker}`)); } } } } if (contract.minTextLength && html.length < contract.minTextLength) { throw new Error(formatFailure(`SSR HTML length ${html.length} is less than minimum ${contract.minTextLength}`)); } } }, 30000); });