upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/tests/e2e/captive-portal.spec.mjs
diff options
context:
space:
mode:
authorYour Name <you@example.com>2026-05-17 17:18:43 +0530
committerYour Name <you@example.com>2026-05-17 17:18:43 +0530
commit8071741815f0b0938701e80a63e80b0ec94b2778 (patch)
tree2a1511480e0b58f4efb144aa9d10c9fba5eed034 /tests/e2e/captive-portal.spec.mjs
parent0c2c67b463d6a90aaa0bb69bf3c91dba1d9ec3ec (diff)
refactor: reorganize test suite, add integration tests for NAT filter
- Move integration tests (api, network, phase2, smoke) to tests/integration/ - Move Playwright specs (captive-portal, interop-happy-path) to tests/e2e/ - Move playwright.config.mjs to tests/e2e/ - Fix hardcoded IP fallbacks: 192.168.4.1 → 10.192.45.1 - Add test-reset-auth.mjs: reset→pay→allow→revoke→block cycle - Add test-session-expiry.mjs: pay→wait 65s→verify blocked (slow test) - Add test-dns-firewall.mjs: DNS hijack/forward + per-client NAT filter - Update Makefile with test-unit, test-integration, test-e2e, test-all targets - Update package.json scripts for new paths - Fix Playwright video: retain-on-failure instead of always-on - Update AGENTS.md: per-client NAT filter description - Update CHECKLIST.md: mark completed items, add Board B identity - Board B nsec: 9af47906... → SSID TollGate-b96d80, AP IP 10.185.47.1 - 186 unit tests passing
Diffstat (limited to 'tests/e2e/captive-portal.spec.mjs')
-rw-r--r--tests/e2e/captive-portal.spec.mjs118
1 files changed, 118 insertions, 0 deletions
diff --git a/tests/e2e/captive-portal.spec.mjs b/tests/e2e/captive-portal.spec.mjs
new file mode 100644
index 0000000..ab9d4f1
--- /dev/null
+++ b/tests/e2e/captive-portal.spec.mjs
@@ -0,0 +1,118 @@
1import { test, expect } from '@playwright/test';
2
3const PORTAL_IP = process.env.TOLLGATE_IP || '10.192.45.1';
4const PORTAL_URL = `http://${PORTAL_IP}`;
5const API_URL = `http://${PORTAL_IP}:2121`;
6
7test.describe('Captive Portal - Phase 2', () => {
8
9 test('portal page loads with TollGate branding', async ({ page }) => {
10 await page.goto(PORTAL_URL);
11 await expect(page.locator('h1')).toHaveText('TollGate');
12 await expect(page.locator('.subtitle')).toContainText('internet access');
13 });
14
15 test('portal shows price from API', async ({ page }) => {
16 await page.goto(PORTAL_URL);
17 const priceEl = page.locator('.price-amount');
18 await expect(priceEl).toHaveText(/\d+/, { timeout: 5000 });
19 });
20
21 test('portal embeds mint URL without JavaScript fetch', async ({ request }) => {
22 const resp = await request.fetch(PORTAL_URL);
23 const body = await resp.text();
24 expect(body).not.toContain('Loading...');
25 expect(body).not.toContain('Error loading mint URL');
26 expect(body).toMatch(/testnut\.cashu\.space/);
27 });
28
29 test('portal embeds price without JavaScript fetch', async ({ request }) => {
30 const resp = await request.fetch(PORTAL_URL);
31 const body = await resp.text();
32 expect(body).not.toContain('__PRICE__');
33 expect(body).toMatch(/price-amount['"]>\d+</);
34 });
35
36 test('portal HTML has no unresolved template placeholders', async ({ request }) => {
37 const resp = await request.fetch(PORTAL_URL);
38 const body = await resp.text();
39 expect(body).not.toContain('__AP_IP__');
40 expect(body).not.toContain('__MINT_URL__');
41 expect(body).not.toContain('__PRICE__');
42 });
43
44 test('mints section appears after token input in DOM order', async ({ page }) => {
45 await page.goto(PORTAL_URL);
46 const textarea = page.locator('#tokenInput');
47 const mintUrl = page.locator('#mintUrl');
48 await expect(textarea).toBeVisible();
49 await expect(mintUrl).toBeVisible();
50 const inputBox = await textarea.boundingBox();
51 const mintBox = await mintUrl.boundingBox();
52 expect(mintBox.y).toBeGreaterThan(inputBox.y);
53 });
54
55 test('portal has Cashu token input', async ({ page }) => {
56 await page.goto(PORTAL_URL);
57 const textarea = page.locator('#tokenInput');
58 await expect(textarea).toBeVisible();
59 await expect(textarea).toHaveAttribute('placeholder', /cashuA/);
60 });
61
62 test('portal has Pay & Connect button', async ({ page }) => {
63 await page.goto(PORTAL_URL);
64 const btn = page.locator('#payBtn');
65 await expect(btn).toBeVisible();
66 await expect(btn).toHaveText(/Pay/);
67 });
68
69 test('captive detection URIs return portal HTML (200)', async ({ request }) => {
70 const uris = ['/generate_204', '/hotspot-detect.html', '/canonical.html', '/success.txt'];
71 for (const uri of uris) {
72 const resp = await request.fetch(`${PORTAL_URL}${uri}`);
73 expect(resp.status()).toBe(200);
74 const body = await resp.text();
75 expect(body).toContain('TollGate');
76 }
77 });
78
79 test('catch-all URIs redirect to portal page', async ({ page }) => {
80 await page.goto(`${PORTAL_URL}/some-random-page`);
81 await expect(page.locator('h1')).toHaveText('TollGate');
82 });
83
84 test('/whoami returns ip and mac', async ({ page }) => {
85 const resp = await page.goto(`${API_URL}/whoami`);
86 expect(resp.status()).toBe(200);
87 const text = await resp.text();
88 expect(text).toMatch(/ip=\d+\.\d+\.\d+\.\d+/);
89 expect(text).toMatch(/mac=(unknown|[0-9a-f]{2}:)/);
90 });
91
92 test('/usage returns -1/-1 before payment', async ({ page }) => {
93 const resp = await page.goto(`${API_URL}/usage`);
94 expect(resp.status()).toBe(200);
95 const text = await resp.text();
96 expect(text).toBe('-1/-1');
97 });
98
99 test('API advertisement has correct structure', async ({ page }) => {
100 const resp = await page.goto(API_URL);
101 expect(resp.status()).toBe(200);
102 const data = await resp.json();
103 expect(data.kind).toBe(10021);
104 expect(data.tags).toBeDefined();
105 expect(data.tags.some(t => t[0] === 'price_per_step')).toBe(true);
106 expect(data.tags.some(t => t[0] === 'step_size')).toBe(true);
107 });
108
109 test('invalid token returns error', async ({ request }) => {
110 const resp = await request.fetch(API_URL, {
111 method: 'POST',
112 data: 'garbage_not_a_token'
113 });
114 expect(resp.status()).toBe(400);
115 const data = await resp.json();
116 expect(data.kind).toBe(21023);
117 });
118});