chore(infra): update dev compose, add playwright e2e suite & assets
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import { test, expect } from "@playwright/test";
|
||||
import { BASE, requireAuth, injectAuth } from "./helpers";
|
||||
|
||||
test.describe("Chat", () => {
|
||||
test("conversations endpoint returns list", async ({ page }) => {
|
||||
const auth = requireAuth();
|
||||
const res = await page.request.get(`${BASE}/api/chats/conversations`, {
|
||||
headers: { Authorization: `Bearer ${auth.accessToken}` },
|
||||
});
|
||||
expect(res.status()).toBe(200);
|
||||
const data = await res.json();
|
||||
expect(data).toHaveProperty("conversations");
|
||||
expect(Array.isArray(data.conversations)).toBe(true);
|
||||
});
|
||||
|
||||
test("student→student chat is rejected (IDOR guard)", async ({ page }) => {
|
||||
const auth = requireAuth();
|
||||
const adminId = auth.user!.id;
|
||||
|
||||
// Create a student
|
||||
const u1 = await page.request.post(`${BASE}/api/admin/users`, {
|
||||
headers: { Authorization: `Bearer ${auth.accessToken}` },
|
||||
data: {
|
||||
name: "ChatTest", email: "chat-idor@e2e.test", username: "chat_idor_e2e",
|
||||
password: "TestPass123!", role: "student",
|
||||
securityQuestion: "Q", securityAnswer: "A",
|
||||
},
|
||||
});
|
||||
// 201 = created, 409 = already exists from prior run — both are fine
|
||||
if (u1.status() !== 201 && u1.status() !== 409) {
|
||||
test.skip(true, `Unexpected status creating test user: ${u1.status()}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const studentId = u1.status() === 201
|
||||
? (await u1.json()).user.id
|
||||
: null;
|
||||
|
||||
if (!studentId) { test.skip(true, "Could not get student ID"); return; }
|
||||
|
||||
// Log in as the student
|
||||
const captchaRes = await page.request.get(`${BASE}/api/auth/captcha`);
|
||||
const { challengeId, answer } = await captchaRes.json();
|
||||
if (!answer) { test.skip(true, "CAPTCHA not solvable"); return; }
|
||||
|
||||
const loginRes = await page.request.post(`${BASE}/api/auth/login`, {
|
||||
data: { username: "chat_idor_e2e", password: "TestPass123!", challengeId, captchaAnswer: answer },
|
||||
});
|
||||
const studentSession = await loginRes.json();
|
||||
if (!studentSession.accessToken) { test.skip(true, "Student login failed"); return; }
|
||||
|
||||
// Student tries to read another student's conversations — must fail
|
||||
const forbidden = await page.request.get(`${BASE}/api/chats/${studentId}/messages`, {
|
||||
headers: { Authorization: `Bearer ${studentSession.accessToken}` },
|
||||
});
|
||||
expect([403, 404]).toContain(forbidden.status());
|
||||
});
|
||||
|
||||
test("chat page renders without redirect to /login", async ({ page }) => {
|
||||
await injectAuth(page);
|
||||
await page.goto("/unwrap?tab=chats");
|
||||
await page.waitForLoadState("networkidle", { timeout: 15_000 });
|
||||
expect(page.url()).not.toContain("/login");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user