beeeeg update
Some checks failed
Test and deploy staging / staging (push) Has been cancelled

This commit is contained in:
2026-09-23 04:15:10 +00:00
parent 487eff5bb2
commit d0c7b6548b
135 changed files with 9531 additions and 1777 deletions

246
tests/auth.test.ts Normal file
View File

@@ -0,0 +1,246 @@
import { test, expect, type APIRequestContext } from '@playwright/test';
import postgres from 'postgres';
import { execFileSync } from 'node:child_process';
// Opt-in: these tests create and clean up their own rows in the configured dev DB.
test.skip(
!process.env.AUTH_E2E,
'Run npm run test:auth against a migrated, seeded development database'
);
const origin = 'http://127.0.0.1:4174';
const password = 'A-long-test-password-2026!';
const createdUsers: string[] = [];
const createdEmails: string[] = [];
const createdLists: number[] = [];
const createdArticles: number[] = [];
let sql: ReturnType<typeof postgres>;
test.beforeAll(() => {
if (!process.env.DB_URL) throw new Error('DB_URL is required');
sql = postgres(process.env.DB_URL, { max: 1 });
});
test.afterAll(async () => {
if (!sql) return;
for (const id of createdLists) await sql`delete from lists where id = ${id}`;
for (const id of createdArticles) await sql`delete from blog_articles where id = ${id}`;
for (const id of createdUsers) await sql`delete from "user" where id = ${id}`;
for (const email of createdEmails) await sql`delete from "user" where email = ${email}`;
await sql.end();
});
async function register(request: APIRequestContext, name: string) {
const email = `auth-test-${crypto.randomUUID()}@example.com`;
createdEmails.push(email);
const response = await request.post('/api/auth/sign-up/email', {
headers: { Origin: origin },
data: { name, email, password, role: 'admin' }
});
expect(response.ok(), await response.text()).toBe(true);
const data = await response.json();
createdUsers.push(data.user.id);
expect(data.user.role).toBe('user');
return { id: data.user.id as string, email };
}
async function action(
request: APIRequestContext,
path: string,
values: Record<string, string> = {}
) {
const response = await request.post(path, {
headers: { Origin: origin, Accept: 'application/json', 'x-sveltekit-action': 'true' },
form: values,
maxRedirects: 0
});
const result = await response.json().catch(() => null);
return {
status: () => result?.status ?? response.status(),
headers: () => ({
...response.headers(),
location: result?.location ?? response.headers().location
})
};
}
test('register, persist progress, isolate users, enforce admin, revoke session', async ({
page,
playwright
}) => {
const errors: string[] = [];
page.on('pageerror', (error) => errors.push(error.message));
const email = `auth-test-${crypto.randomUUID()}@example.com`;
createdEmails.push(email);
await page.goto('/register?returnTo=/projects/catch-em-all');
await page.getByLabel('Name', { exact: true }).fill('Browser Trainer');
await page.getByRole('textbox', { name: 'Email', exact: true }).fill(email);
await page.getByLabel('Password', { exact: true }).fill(password);
await page.getByRole('button', { name: 'Create account', exact: true }).click();
await expect(page).toHaveURL(`${origin}/projects/catch-em-all`);
const session = await (await page.request.get('/api/auth/get-session')).json();
createdUsers.push(session.user.id);
expect(session.user.role).toBe('user');
await expect(page.getByRole('link', { name: 'Browser Trainer', exact: true })).toBeVisible();
await page.screenshot({ path: '/tmp/personal-website-catchemall.png' });
const firstCatch = page.getByRole('checkbox', { name: /^Caught / }).first();
await firstCatch.check();
await expect(firstCatch).toBeEnabled();
await page.reload();
await expect(page.getByRole('checkbox', { name: /^Caught / }).first()).toBeChecked();
const [caught] =
await sql`select pokemon_game_entry_id from user_entries where user_id = ${session.user.id}`;
expect(caught).toBeTruthy();
const [{ count: beforeCount }] = await sql`select count(*) from pokemon_game_entries`;
execFileSync('bun', ['scripts/seed-catalog.ts'], { env: process.env });
expect((await sql`select count(*) from pokemon_game_entries`)[0].count).toBe(beforeCount);
expect(
(
await sql`select pokemon_game_entry_id from user_entries where user_id = ${session.user.id}`
)[0]
).toEqual(caught);
const [game] = await sql`select id from games order by id limit 1`;
const selection = await action(page.request, '/projects/catch-em-all?/setGames', {
gameIds: String(game.id)
});
expect(selection.status()).toBe(200);
await page.reload();
await expect(page.locator('thead th')).toHaveCount(2);
// Explicitly selecting none must not revert to all games after a reload.
expect((await action(page.request, '/projects/catch-em-all?/setGames')).status()).toBe(200);
await page.reload();
await expect(page.locator('thead th')).toHaveCount(1);
const bob = await playwright.request.newContext({ baseURL: origin });
try {
const other = await register(bob, 'Other Trainer');
expect(
(
await action(bob, '/projects/catch-em-all?/togglePokemonCaught', {
entryId: String(caught.pokemon_game_entry_id)
})
).status()
).toBe(200);
expect((await sql`select * from user_entries where user_id = ${session.user.id}`).length).toBe(
1
);
expect((await sql`select * from user_entries where user_id = ${other.id}`).length).toBe(0);
expect((await action(bob, '/lists?/newList')).status()).toBe(403);
expect((await action(bob, '/blog', { title: 'Not allowed' })).status()).toBe(403);
expect((await action(bob, '/projects/progress/create', { name: 'Not allowed' })).status()).toBe(
403
);
expect(
(
await bob.post('/projects/animaldex/api/animals/1/moderate', {
headers: { Origin: origin },
data: { action: 'accept' }
})
).status()
).toBe(403);
expect(
(await action(bob, '/projects/catch-em-all?/setGames', { gameIds: 'not-a-game' })).status()
).toBe(400);
expect(
(
await action(bob, '/projects/catch-em-all?/togglePokemonCaught', { entryId: '999999999' })
).status()
).toBe(404);
} finally {
await bob.dispose();
}
await sql`update "user" set role = 'admin' where id = ${session.user.id}`;
await page.goto('/blog');
await expect(page.getByRole('button', { name: 'Write new article' })).toBeVisible();
const listResponse = await action(page.request, '/lists?/newList');
expect(listResponse.status()).toBe(303);
const listId = Number(listResponse.headers().location?.split('/').pop());
createdLists.push(listId);
await page.goto(`/lists/${listId}`);
await page.getByLabel('List name').fill('Auth test list');
const renameResponse = page.waitForResponse(
(response) => response.url().includes('/lists/') && response.request().method() === 'POST'
);
await page.getByRole('button', { name: 'Rename', exact: true }).click();
const renamed = await renameResponse;
expect(renamed.status()).toBe(200);
expect((await sql`select name from lists where id = ${listId}`)[0].name).toBe('Auth test list');
await page.screenshot({ path: '/tmp/personal-website-list.png' });
await expect(page.getByRole('heading', { name: 'Auth test list' })).toBeVisible();
const title = `Auth test article ${crypto.randomUUID()}`;
const articleResponse = await action(page.request, '/blog', { title });
expect(articleResponse.status()).toBe(303);
const [article] = await sql`select id, slug from blog_articles where title = ${title}`;
createdArticles.push(article.id);
await page.goto(`/blog/${article.slug}/edit`);
await expect(page.getByRole('button', { name: 'Save draft', exact: true }).first()).toBeVisible();
await sql`update "user" set role = 'user' where id = ${session.user.id}`;
expect((await action(page.request, '/lists?/newList')).status()).toBe(403);
const oldCookies = await page.context().cookies();
await page.goto('/account');
await page.getByRole('button', { name: 'Log out', exact: true }).last().click();
await expect(page).toHaveURL(/\/login$/);
const replay = await playwright.request.newContext({
baseURL: origin,
extraHTTPHeaders: { Cookie: oldCookies.map((c) => `${c.name}=${c.value}`).join('; ') }
});
try {
expect(await (await replay.get('/api/auth/get-session')).json()).toBeNull();
} finally {
await replay.dispose();
}
await page.getByRole('textbox', { name: 'Email', exact: true }).fill(email);
await page.getByLabel('Password', { exact: true }).fill(password);
await page.getByRole('button', { name: 'Log in', exact: true }).click();
await expect(page).toHaveURL(/\/account$/);
expect(errors).toEqual([]);
});
test('anonymous/forged cookies, reserved identities, unsafe redirects, OAuth start', async ({
page
}) => {
await page.context().addCookies([
{ name: 'admin_token', value: 'anything', url: origin },
{ name: 'token', value: 'anything', url: origin }
]);
expect((await action(page.request, '/lists?/newList')).status()).toBe(401);
expect(
(
await action(page.request, '/projects/catch-em-all?/togglePokemonCaught', {
entryId: '1',
caught: 'on'
})
).status()
).toBe(401);
expect(
(await page.request.post('/admin/login', { data: { password: 'anything' } })).status()
).toBe(410);
const reserved = await page.request.post('/api/auth/sign-up/email', {
headers: { Origin: origin },
data: { name: 'Fake osu', email: '123@osu.invalid', password }
});
expect(reserved.status()).toBe(400);
const crossOrigin = await page.request.post('/api/auth/sign-in/email', {
headers: { Origin: 'https://evil.example' },
data: { name: 'Fake', email: 'cross-origin@example.com', password }
});
expect(crossOrigin.status()).toBe(403);
expect(
(await page.request.post('/logout', { headers: { Origin: 'https://evil.example' } })).status()
).toBe(403);
await page.goto('/login?returnTo=https://evil.example');
await expect(page.getByRole('link', { name: 'Create an account' })).toHaveAttribute(
'href',
'/register?returnTo=%2Faccount'
);
if (process.env.OSU_CLIENT_ID && process.env.OSU_CLIENT_SECRET) {
const response = await page.request.post('/api/auth/sign-in/social', {
headers: { Origin: origin },
data: { provider: 'osu', callbackURL: '/account', disableRedirect: true }
});
expect(response.ok(), await response.text()).toBe(true);
const url = new URL((await response.json()).url);
expect(url.origin).toBe('https://osu.ppy.sh');
expect(url.searchParams.get('scope')).toBe('identify');
expect(url.searchParams.get('redirect_uri')).toBe(`${origin}/api/auth/callback/osu`);
expect(url.searchParams.get('state')).toBeTruthy();
}
});

28
tests/finder.test.ts Normal file
View File

@@ -0,0 +1,28 @@
import { expect, test } from '@playwright/test';
const path = 'http://localhost:4173/projects/spotify-beatmap-finder';
test('finder is responsive and validates enhanced submissions', async ({ page }) => {
await page.setViewportSize({ width: 390, height: 844 });
await page.goto(path);
await expect(
page.getByRole('heading', { name: 'Spotify Map Finder', exact: true })
).toBeVisible();
expect(await page.evaluate(() => document.documentElement.scrollWidth <= window.innerWidth)).toBe(
true
);
await page.getByLabel('Song or Spotify URL').fill('spotify:artist:6PUzxtIHkv346yP89NzP9X');
await page.getByRole('button', { name: 'Search' }).click();
await expect(page.getByRole('alert')).toContainText('Unsupported Spotify resource');
await expect(page.getByLabel('Song or Spotify URL')).toHaveValue(
'spotify:artist:6PUzxtIHkv346yP89NzP9X'
);
});
test('finder retains native form behavior', async ({ browser }) => {
const context = await browser.newContext({ javaScriptEnabled: false });
const page = await context.newPage();
await page.goto(path);
await page.getByLabel('Song or Spotify URL').fill('spotify:album:invalid');
await page.getByRole('button', { name: 'Search' }).click();
await expect(page.getByRole('alert')).toContainText('Invalid Spotify ID');
await expect(page.getByLabel('Song or Spotify URL')).toHaveValue('spotify:album:invalid');
await context.close();
});