189 lines
6.9 KiB
TypeScript
189 lines
6.9 KiB
TypeScript
import { expect, test, type APIRequestContext, type Page } from "@playwright/test";
|
|
|
|
const apiBase = process.env.LDD_API_URL ?? "http://localhost:8080";
|
|
|
|
type Seeded = {
|
|
courseId: string;
|
|
courseNo: number;
|
|
courseTitle: string;
|
|
groupId: string;
|
|
groupName: string;
|
|
studentFullName: string;
|
|
studentLogin: string;
|
|
studentPassword: string;
|
|
teacherLogin: string;
|
|
teacherPassword: string;
|
|
adminLogin: string;
|
|
adminPassword: string;
|
|
};
|
|
|
|
let seeded: Seeded;
|
|
let api: APIRequestContext;
|
|
|
|
async function expectOk(response: Awaited<ReturnType<APIRequestContext["post"]>>): Promise<unknown> {
|
|
if (!response.ok()) {
|
|
throw new Error(`${response.url()} -> ${response.status()}: ${await response.text()}`);
|
|
}
|
|
return response.json();
|
|
}
|
|
|
|
async function setupAdmin(context: APIRequestContext): Promise<string> {
|
|
const health = await context.get("/api/v1/auth/health");
|
|
const healthBody = (await health.json()) as { adminExists: boolean };
|
|
if (healthBody.adminExists) {
|
|
const login = await context.post("/api/v1/auth/login", {
|
|
data: { login: "e2e-admin", password: "e2e-admin-123" },
|
|
});
|
|
return ((await expectOk(login)) as { token: string }).token;
|
|
}
|
|
const setup = await context.post("/api/v1/auth/setup", {
|
|
data: { login: "e2e-admin", password: "e2e-admin-123", password2: "e2e-admin-123" },
|
|
});
|
|
return ((await expectOk(setup)) as { token: string }).token;
|
|
}
|
|
|
|
async function createWithToken(
|
|
context: APIRequestContext,
|
|
token: string,
|
|
method: "post",
|
|
path: string,
|
|
data: unknown,
|
|
): Promise<Record<string, unknown>> {
|
|
const response = await context[method](path, {
|
|
data,
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
return (await expectOk(response)) as Record<string, unknown>;
|
|
}
|
|
|
|
async function changePassword(context: APIRequestContext, login: string, oldPassword: string, newPassword: string) {
|
|
const loginResponse = await context.post("/api/v1/auth/login", {
|
|
data: { login, password: oldPassword },
|
|
});
|
|
const token = ((await expectOk(loginResponse)) as { token: string }).token;
|
|
await expectOk(
|
|
await context.post("/api/v1/auth/change-password", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
data: { old: oldPassword, new: newPassword, new2: newPassword },
|
|
}),
|
|
);
|
|
}
|
|
|
|
async function login(page: Page, login: string, password: string, expectedPath: RegExp | string) {
|
|
await page.goto("/login");
|
|
await page.fill("#login", login);
|
|
await page.fill("#password", password);
|
|
await Promise.all([page.waitForURL(expectedPath), page.click('button[type="submit"]')]);
|
|
}
|
|
|
|
test.beforeAll(async ({ playwright }) => {
|
|
api = await playwright.request.newContext({ baseURL: apiBase });
|
|
const suffix = Math.random().toString(36).slice(2, 8);
|
|
const adminToken = await setupAdmin(api);
|
|
|
|
const group = await createWithToken(api, adminToken, "post", "/api/v1/groups", {
|
|
stream: "E2E" + suffix,
|
|
course: 1,
|
|
number: 1,
|
|
degree: "БО",
|
|
enrollmentYear: 2026,
|
|
});
|
|
const course = await createWithToken(api, adminToken, "post", "/api/v1/courses", {
|
|
title: "E2E Course " + suffix,
|
|
});
|
|
const teacher = await createWithToken(api, adminToken, "post", "/api/v1/teachers", {
|
|
login: "e2e-teacher-" + suffix,
|
|
password: "e2e-teacher-123",
|
|
});
|
|
const teacherLogin = String(teacher.login);
|
|
await createWithToken(api, adminToken, "post", `/api/v1/courses/${course.id}/teachers`, {
|
|
accountId: teacher.id,
|
|
});
|
|
await createWithToken(api, adminToken, "post", `/api/v1/courses/${course.id}/groups`, {
|
|
groupId: group.id,
|
|
});
|
|
await createWithToken(api, adminToken, "post", "/api/v1/labs", {
|
|
number: 1,
|
|
title: "E2E Lab",
|
|
status: "published",
|
|
courseId: course.id,
|
|
levels: [{ code: "basic", title: "Базовый", position: 1 }],
|
|
});
|
|
const studentNo = "E2E-S-" + suffix;
|
|
const student = await createWithToken(api, adminToken, "post", `/api/v1/groups/${group.id}/students`, {
|
|
studentNo,
|
|
lastName: "Тестов",
|
|
firstName: "Студент",
|
|
basis: "бюджет",
|
|
});
|
|
const studentPassword = String(student.password);
|
|
await changePassword(api, teacherLogin, "e2e-teacher-123", "e2e-teacher-456");
|
|
await changePassword(api, studentNo, studentPassword, "e2e-student-456");
|
|
|
|
seeded = {
|
|
courseId: String(course.id),
|
|
courseNo: Number(course.courseNo),
|
|
courseTitle: String(course.title),
|
|
groupId: String(group.id),
|
|
groupName: String(group.name),
|
|
studentFullName: String(student.fullName),
|
|
studentLogin: studentNo,
|
|
studentPassword: "e2e-student-456",
|
|
teacherLogin,
|
|
teacherPassword: "e2e-teacher-456",
|
|
adminLogin: "e2e-admin",
|
|
adminPassword: "e2e-admin-123",
|
|
};
|
|
});
|
|
|
|
test.afterAll(async () => {
|
|
await api.dispose();
|
|
});
|
|
|
|
test("admin opens courses and audit", async ({ page }) => {
|
|
await login(page, seeded.adminLogin, seeded.adminPassword, "**/admin");
|
|
await page.goto("/admin/courses");
|
|
await expect(page.locator("body")).toContainText(seeded.courseTitle);
|
|
|
|
await page.goto("/admin/audit");
|
|
await expect(page.locator("body")).toContainText("Создан курс");
|
|
});
|
|
|
|
test("teacher opens course, gradebook, student profile and authors a chapter", async ({ page }) => {
|
|
await login(page, seeded.teacherLogin, seeded.teacherPassword, "**/teacher");
|
|
await expect(page.locator("body")).toContainText(seeded.courseTitle);
|
|
|
|
await page.goto(`/teacher/courses/${seeded.courseId}`);
|
|
await expect(page.locator("h1")).toContainText(seeded.courseTitle);
|
|
await expect(page.locator("body")).toContainText(seeded.groupName);
|
|
await expect(page.locator("body")).toContainText("E2E Lab");
|
|
|
|
await page.getByRole("link", { name: "Ведомость" }).click();
|
|
await expect(page).toHaveURL(new RegExp(`/admin/groups/${seeded.groupId}\\?courseId=`));
|
|
await expect(page.locator("body")).toContainText(seeded.groupName);
|
|
await expect(page.locator("body")).toContainText(seeded.studentFullName);
|
|
await expect(page.getByRole("button", { name: "Отчислить" })).toHaveCount(0);
|
|
await expect(page.getByRole("button", { name: "Сбросить" })).toHaveCount(0);
|
|
|
|
await page.getByRole("link", { name: seeded.studentFullName }).first().click();
|
|
await expect(page).toHaveURL(new RegExp(`/teacher/courses/${seeded.courseId}/students/`));
|
|
await expect(page.locator("body")).toContainText(seeded.studentFullName);
|
|
|
|
await page.goto(`/teacher/courses/${seeded.courseId}/content`);
|
|
await page.fill("#chapterTitle", "E2E Chapter");
|
|
await page.getByRole("button", { name: "Создать главу" }).click();
|
|
await expect(page.locator("body")).toContainText("E2E Chapter");
|
|
});
|
|
|
|
test("anonymous and student cannot reach admin pages", async ({ page }) => {
|
|
await page.goto("/admin/audit");
|
|
await expect(page).toHaveURL(/\/login/);
|
|
|
|
await login(page, seeded.studentLogin, seeded.studentPassword, "**/student");
|
|
await page.goto("/admin/audit");
|
|
await expect(page).toHaveURL(/\/student/);
|
|
|
|
await page.goto(`/learn/course/${seeded.courseNo}`);
|
|
await expect(page.locator("body")).toContainText(seeded.courseTitle);
|
|
});
|