From e3a02913e2c3fe21e43e267024a178a2a8f20d6c Mon Sep 17 00:00:00 2001 From: edevlopa Date: Wed, 21 Oct 2026 22:53:12 +0100 Subject: [PATCH] test(e2e): add tests for course stats, prerequisites, and recommended courses Closes #645 Closes #644 Closes #642 --- tests/e2e/course-prerequisites-check.test.ts | 66 ++++++++++++++++++ tests/e2e/course-recommended.test.ts | 70 ++++++++++++++++++++ tests/e2e/course-stats.test.ts | 50 ++++++++++++++ 3 files changed, 186 insertions(+) create mode 100644 tests/e2e/course-prerequisites-check.test.ts create mode 100644 tests/e2e/course-recommended.test.ts create mode 100644 tests/e2e/course-stats.test.ts diff --git a/tests/e2e/course-prerequisites-check.test.ts b/tests/e2e/course-prerequisites-check.test.ts new file mode 100644 index 0000000..6e01079 --- /dev/null +++ b/tests/e2e/course-prerequisites-check.test.ts @@ -0,0 +1,66 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import type { FastifyInstance } from "fastify"; +import { buildApp } from "../../src/server.js"; + +/** + * E2E tests for course prerequisites endpoint (#644). + * + * Covers: + * - GET /api/v1/courses/:id/prerequisites (prerequisites listing and completion status) + */ +describe("Course Prerequisites Check API (#644)", () => { + let app: FastifyInstance; + + beforeAll(async () => { + app = await buildApp(); + await app.ready(); + }); + + afterAll(async () => { + await app.close(); + }); + + const tokenFor = (sub: string) => + app.jwt.sign({ + sub, + stellarAddress: + "GALICE0000000000000000000000000000000000000000000000000000000", + }); + + it("rejects invalid course UUID parameter", async () => { + const response = await app.inject({ + method: "GET", + url: "/api/v1/courses/not-a-valid-uuid/prerequisites", + }); + + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.payload); + expect(body.error).toBe("VALIDATION_ERROR"); + }); + + it("handles unauthenticated request for prerequisites", async () => { + const response = await app.inject({ + method: "GET", + url: "/api/v1/courses/c1111111-1111-4111-8111-111111111111/prerequisites", + }); + + expect([200, 404, 500]).toContain(response.statusCode); + }); + + it("handles authenticated request returning prerequisites with completion status", async () => { + const token = tokenFor("a1111111-1111-4111-8111-111111111111"); + const response = await app.inject({ + method: "GET", + url: "/api/v1/courses/c1111111-1111-4111-8111-111111111111/prerequisites", + headers: { + authorization: `Bearer ${token}`, + }, + }); + + expect([200, 404, 500]).toContain(response.statusCode); + if (response.statusCode === 200) { + const body = JSON.parse(response.payload); + expect(body).toHaveProperty("success"); + } + }); +}); diff --git a/tests/e2e/course-recommended.test.ts b/tests/e2e/course-recommended.test.ts new file mode 100644 index 0000000..5b5a139 --- /dev/null +++ b/tests/e2e/course-recommended.test.ts @@ -0,0 +1,70 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import type { FastifyInstance } from "fastify"; +import { buildApp } from "../../src/server.js"; + +/** + * E2E tests for course recommended endpoint (#642). + * + * Covers: + * - GET /api/v1/courses/recommended (personalized recommended courses, query validation) + */ +describe("Course Recommended API (#642)", () => { + let app: FastifyInstance; + + beforeAll(async () => { + app = await buildApp(); + await app.ready(); + }); + + afterAll(async () => { + await app.close(); + }); + + const tokenFor = (sub: string) => + app.jwt.sign({ + sub, + stellarAddress: + "GALICE0000000000000000000000000000000000000000000000000000000", + }); + + it("rejects unauthenticated requests", async () => { + const response = await app.inject({ + method: "GET", + url: "/api/v1/courses/recommended", + }); + + expect(response.statusCode).toBe(401); + }); + + it("handles authenticated recommendations request", async () => { + const token = tokenFor("a1111111-1111-4111-8111-111111111111"); + const response = await app.inject({ + method: "GET", + url: "/api/v1/courses/recommended", + headers: { + authorization: `Bearer ${token}`, + }, + }); + + expect([200, 404, 500]).toContain(response.statusCode); + if (response.statusCode === 200) { + const body = JSON.parse(response.payload); + expect(body).toHaveProperty("success"); + } + }); + + it("rejects invalid limit query parameter", async () => { + const token = tokenFor("a1111111-1111-4111-8111-111111111111"); + const response = await app.inject({ + method: "GET", + url: "/api/v1/courses/recommended?limit=0", + headers: { + authorization: `Bearer ${token}`, + }, + }); + + expect(response.statusCode).toBe(400); + const body = JSON.parse(response.payload); + expect(body.error).toBe("VALIDATION_ERROR"); + }); +}); diff --git a/tests/e2e/course-stats.test.ts b/tests/e2e/course-stats.test.ts new file mode 100644 index 0000000..df377a9 --- /dev/null +++ b/tests/e2e/course-stats.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect, beforeAll, afterAll } from "vitest"; +import type { FastifyInstance } from "fastify"; +import { buildApp } from "../../src/server.js"; + +/** + * E2E tests for course stats endpoint (#645). + * + * Covers: + * - GET /api/v1/courses/stats (course statistics overview and caching behavior) + */ +describe("Course Stats API (#645)", () => { + let app: FastifyInstance; + + beforeAll(async () => { + app = await buildApp(); + await app.ready(); + }); + + afterAll(async () => { + await app.close(); + }); + + it("returns course statistics without requiring auth", async () => { + const response = await app.inject({ + method: "GET", + url: "/api/v1/courses/stats", + }); + + expect([200, 500]).toContain(response.statusCode); + if (response.statusCode === 200) { + const body = JSON.parse(response.payload); + expect(body).toHaveProperty("success"); + expect(body).toHaveProperty("data"); + } + }); + + it("serves consistent responses across multiple calls (cached)", async () => { + const res1 = await app.inject({ + method: "GET", + url: "/api/v1/courses/stats", + }); + + const res2 = await app.inject({ + method: "GET", + url: "/api/v1/courses/stats", + }); + + expect(res1.statusCode).toBe(res2.statusCode); + }); +});