From 58522f3ac2fb51e546cc4c027361d974377b67a6 Mon Sep 17 00:00:00 2001 From: Ranjeet Prabhat Date: Wed, 14 Oct 2026 07:25:35 +0545 Subject: [PATCH] feat(courses): reject enrollment in unpublished courses (BA-013) --- BackendAcademy/src/courses/course.types.ts | 9 ++++- .../src/courses/enrollment.service.spec.ts | 38 +++++++++++++++++++ .../src/courses/enrollment.service.ts | 10 ++++- 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/BackendAcademy/src/courses/course.types.ts b/BackendAcademy/src/courses/course.types.ts index d4912a721c..62217d89d1 100644 --- a/BackendAcademy/src/courses/course.types.ts +++ b/BackendAcademy/src/courses/course.types.ts @@ -17,7 +17,7 @@ * before starting Lesson 2"). */ -// ── Course ────────────────────────────────────────────────────────────────── +export type CourseStatus = 'draft' | 'published'; export interface Course { courseId: string; @@ -28,6 +28,8 @@ export interface Course { * can enrol in this course (e.g. ["ownership-101"] before "lifetimes-201"). */ prerequisiteCourseIds: string[]; + /** Publication status: 'draft' | 'published'. Draft courses reject enrollment. */ + status?: CourseStatus | string; createdAt: string; } @@ -77,3 +79,8 @@ export interface PrerequisiteError { /** The specific prerequisite IDs that are not yet satisfied. */ unmetPrerequisiteIds: string[]; } + +export interface CourseNotPublishedError { + code: 'COURSE_NOT_PUBLISHED'; + message: string; +} diff --git a/BackendAcademy/src/courses/enrollment.service.spec.ts b/BackendAcademy/src/courses/enrollment.service.spec.ts index 0b602a96a5..b20fb529a4 100644 --- a/BackendAcademy/src/courses/enrollment.service.spec.ts +++ b/BackendAcademy/src/courses/enrollment.service.spec.ts @@ -360,4 +360,42 @@ describe('EnrollmentService — prerequisite chains (BE-041)', () => { expect(enrollment.courseId).toBe('lifetimes-201'); }); }); + + // ── Course publication status (BA-013) ─────────────────────────────────── + + describe('course publication status (BA-013)', () => { + it('rejects enrollment in a draft course with 409 ConflictException', () => { + const course = makeCourse('rust-advanced-draft'); + course.status = 'draft'; + + let caught: ConflictException | undefined; + try { + enrollmentService.enroll('user-1', 'rust-advanced-draft'); + } catch (err) { + caught = err as ConflictException; + } + + expect(caught).toBeInstanceOf(ConflictException); + expect(caught!.getStatus()).toBe(409); + const body = caught!.getResponse() as Record; + expect(body.code).toBe('COURSE_NOT_PUBLISHED'); + expect(typeof body.message).toBe('string'); + expect(body.message).toContain('rust-advanced-draft'); + }); + + it('allows enrollment in a published course', () => { + const course = makeCourse('rust-intro-published'); + course.status = 'published'; + + const enrollment = enrollmentService.enroll('user-1', 'rust-intro-published'); + expect(enrollment.courseId).toBe('rust-intro-published'); + expect(enrollment.userId).toBe('user-1'); + }); + + it('allows enrollment in a course when status is not explicitly set (defaults to allowed)', () => { + makeCourse('rust-standard-course'); + const enrollment = enrollmentService.enroll('user-1', 'rust-standard-course'); + expect(enrollment.courseId).toBe('rust-standard-course'); + }); + }); }); diff --git a/BackendAcademy/src/courses/enrollment.service.ts b/BackendAcademy/src/courses/enrollment.service.ts index 3bfa3a69de..868685487c 100644 --- a/BackendAcademy/src/courses/enrollment.service.ts +++ b/BackendAcademy/src/courses/enrollment.service.ts @@ -6,7 +6,7 @@ import { } from '@nestjs/common'; import { CourseService } from './course.service'; import { LessonService } from './lesson.service'; -import { Enrollment, LessonProgress, PrerequisiteError } from './course.types'; +import { CourseNotPublishedError, Enrollment, LessonProgress, PrerequisiteError } from './course.types'; /** * Enrollment and lesson-progress service (BE-041). @@ -64,6 +64,14 @@ export class EnrollmentService { enroll(userId: string, courseId: string): Enrollment { const course = this.courseService.findById(courseId); + if (course.status === 'draft') { + const errorPayload: CourseNotPublishedError = { + code: 'COURSE_NOT_PUBLISHED', + message: `Cannot enroll in draft course "${courseId}". Course is not published.`, + }; + throw new ConflictException(errorPayload); + } + const userEnrollments = this.enrollments.get(userId) ?? new Map(); if (userEnrollments.has(courseId)) {