import { test, expect } from '@playwright/test'; const BASE = process.env.BASE_URL || 'https://marketing-stage-r4tfklscwq-uc.a.run.app'; const get = (path: string, headers: Record = {}) => fetch(`${BASE}${path}`, { headers }); // ── /api/health ─────────────────────────────────────────────────────────────── test('/api/health — returns 200 with status:ok', async () => { const r = await get('/api/health'); expect(r.status).toBe(200); const body = await r.json() as Record; expect(body.status).toBe('ok'); expect(body.service).toBe('neuron-web'); }); test('/api/health — content-type is application/json', async () => { const r = await get('/api/health'); expect(r.headers.get('content-type')).toContain('application/json'); }); // ── /api/founding-count ─────────────────────────────────────────────────────── test('/api/founding-count — returns numeric fields', async () => { const r = await get('/api/founding-count'); expect(r.status).toBe(200); const body = await r.json() as Record; expect(typeof body.sold).toBe('number'); expect(typeof body.total).toBe('number'); expect(typeof body.remaining).toBe('number'); // Invariants expect(body.total).toBe(1000); expect(body.sold).toBeGreaterThanOrEqual(0); expect(body.remaining).toBe(body.total - body.sold); }); // ── /api/supabase-config ────────────────────────────────────────────────────── // Requires a permitted Origin. See security.test.ts for CORS tests. test('/api/supabase-config — returns url and anon_key for allowed origin', async () => { const r = await get('/api/supabase-config', { Origin: 'https://neurontechnologies.ai' }); expect(r.status).toBe(200); const body = await r.json() as Record; expect(body.url).toMatch(/supabase\.co/); expect(typeof body.anon_key).toBe('string'); expect(body.anon_key.length).toBeGreaterThan(20); }); test('/api/supabase-config — anon_key is a valid JWT shape', async () => { const r = await get('/api/supabase-config', { Origin: 'https://neurontechnologies.ai' }); const body = await r.json() as Record; // Supabase anon key is a JWT: three base64 segments separated by dots const parts = body.anon_key.split('.'); expect(parts).toHaveLength(3); }); // ── /sitemap.xml ────────────────────────────────────────────────────────────── test('/sitemap.xml — returns valid XML with production URLs', async () => { const r = await get('/sitemap.xml'); expect(r.status).toBe(200); expect(r.headers.get('content-type')).toContain('xml'); const text = await r.text(); expect(text).toContain(' { const r = await get('/sitemap.xml'); const text = await r.text(); expect(text).toContain('neurontechnologies.ai/'); expect(text).toContain('neurontechnologies.ai/about'); expect(text).toContain('neurontechnologies.ai/legal/terms'); expect(text).toContain('neurontechnologies.ai/legal/enterprise-terms'); }); // ── /robots.txt ─────────────────────────────────────────────────────────────── test('/robots.txt — accessible with correct directives', async () => { const r = await get('/robots.txt'); expect(r.status).toBe(200); const text = await r.text(); expect(text).toContain('User-agent'); // Private paths are disallowed expect(text).toContain('Disallow: /checkout'); expect(text).toContain('Disallow: /account'); expect(text).toContain('Disallow: /api/'); // Sitemap link points to production expect(text).toContain('Sitemap: https://neurontechnologies.ai/sitemap.xml'); }); // ── /llms.txt ───────────────────────────────────────────────────────────────── test('/llms.txt — accessible', async () => { const r = await get('/llms.txt'); expect(r.status).toBe(200); const text = await r.text(); expect(text.length).toBeGreaterThan(0); }); // ── 404 handling ───────────────────────────────────────────────────────────── test('Unknown route returns 404', async () => { const r = await get('/this-route-xyz-does-not-exist-abc123'); expect(r.status).toBe(404); }); // ── /api/webhooks/stripe — POST-only, requires valid signature ──────────────── test('/api/webhooks/stripe — rejects missing Stripe-Signature with 400', async () => { const r = await fetch(`${BASE}/api/webhooks/stripe`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ type: 'payment_intent.succeeded' }), }); expect(r.status).toBe(400); }); // ── /api/demo — POST only, auth-gated ──────────────────────────────────────── test('/api/demo — missing access_token returns auth_required', async () => { const r = await fetch(`${BASE}/api/demo`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ message: 'hello' }), }); const body = await r.json() as Record; expect(body.auth_required).toBe(true); }); // ── /api/soul-health — internal gate ───────────────────────────────────────── // The probe responses embedded in the JSON body may contain literal newlines // (control characters), so we test via text matching, not JSON.parse. test('/api/soul-health — 404 without X-Internal header', async () => { const r = await get('/api/soul-health'); expect(r.status).toBe(404); }); test('/api/soul-health — 200 with X-Internal: true, body contains soul_url', async () => { const r = await get('/api/soul-health', { 'X-Internal': 'true' }); expect(r.status).toBe(200); const text = await r.text(); expect(text).toContain('"soul_url"'); expect(text).toMatch(/soul_url.*https?:\/\//); });