66 lines
2.6 KiB
TypeScript
66 lines
2.6 KiB
TypeScript
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");
|
||
|
|
});
|
||
|
|
});
|