e2e.auth.test.ts
Raw
1import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
2import { chromium } from 'playwright';
3import type { Browser } from 'playwright';
4import {
5 BASE,
6 ADMIN_PASS,
7 setupTestEnv,
8 spawnServer,
9 killServer,
10 login,
11 logout,
12} from './helpers.ts';
13
14let browser: Browser;
15let server: Awaited<ReturnType<typeof spawnServer>>;
16
17beforeAll(async () => {
18 await setupTestEnv();
19 server = await spawnServer();
20 browser = await chromium.launch();
21});
22
23afterAll(async () => {
24 await browser.close();
25 await killServer(server);
26});
27
28// ─── Auth ─────────────────────────────────────────────────────────────────────
29
30describe('auth', () => {
31 test('homepage loads', async () => {
32 const ctx = await browser.newContext();
33 const page = await ctx.newPage();
34 try {
35 await page.goto(BASE);
36 expect(await page.title()).toContain('Hearthforge');
37 } finally { await ctx.close(); }
38 });
39
40 test('wrong password shows error', async () => {
41 const ctx = await browser.newContext();
42 const page = await ctx.newPage();
43 try {
44 await page.goto(`${BASE}/login`);
45 await page.fill('[name=username]', 'admin');
46 await page.fill('[name=password]', 'wrongpassword');
47 await page.click('button[type=submit]');
48 expect(await page.locator('.form-error').textContent()).toContain('Invalid');
49 } finally { await ctx.close(); }
50 });
51
52 test('correct credentials redirect to homepage', async () => {
53 const ctx = await browser.newContext();
54 const page = await ctx.newPage();
55 try {
56 await login(page);
57 expect(page.url()).toBe(BASE + '/');
58 expect(await page.locator('.nav-user').isVisible()).toBe(true);
59 } finally { await ctx.close(); }
60 });
61
62 test('register new user', async () => {
63 const ctx = await browser.newContext();
64 const page = await ctx.newPage();
65 try {
66 await page.goto(`${BASE}/register`);
67 await page.fill('[name=username]', 'alice');
68 await page.fill('[name=password]', 'password123');
69 await page.fill('[name=password2]', 'password123');
70 await page.click('button[type=submit]');
71 await page.waitForURL(BASE + '/');
72 expect(await page.locator('.nav-user').textContent()).toBe('alice');
73 } finally { await ctx.close(); }
74 });
75
76 test('register with mismatched passwords shows error', async () => {
77 const ctx = await browser.newContext();
78 const page = await ctx.newPage();
79 try {
80 await page.goto(`${BASE}/register`);
81 await page.fill('[name=username]', 'bob');
82 await page.fill('[name=password]', 'password123');
83 await page.fill('[name=password2]', 'different456');
84 await page.click('button[type=submit]');
85 expect(await page.locator('.form-error').textContent()).toContain('match');
86 } finally { await ctx.close(); }
87 });
88
89 test('register with duplicate username shows error', async () => {
90 const ctx = await browser.newContext();
91 const page = await ctx.newPage();
92 try {
93 await page.goto(`${BASE}/register`);
94 await page.fill('[name=username]', 'alice'); // already registered above
95 await page.fill('[name=password]', 'password123');
96 await page.fill('[name=password2]', 'password123');
97 await page.click('button[type=submit]');
98 expect(await page.locator('.form-error').textContent()).toContain('taken');
99 } finally { await ctx.close(); }
100 });
101
102 test('cross-origin POST is rejected (CSRF defense)', async () => {
103 const ctx = await browser.newContext();
104 try {
105 // Forge an Origin from a different host; server should refuse the POST.
106 const resp = await ctx.request.post(`${BASE}/login`, {
107 headers: { Origin: 'http://evil.example' },
108 form: { username: 'admin', password: ADMIN_PASS },
109 maxRedirects: 0,
110 });
111 expect(resp.status()).toBe(403);
112 // Other tests (login, register) cover the same-origin success path.
113 } finally { await ctx.close(); }
114 });
115
116 test('logout clears session', async () => {
117 const ctx = await browser.newContext();
118 const page = await ctx.newPage();
119 try {
120 await login(page);
121 await logout(page);
122 expect(await page.locator('.nav-user').count()).toBe(0);
123 expect(await page.locator('a[href="/login"]').isVisible()).toBe(true);
124 } finally { await ctx.close(); }
125 });
126});
127