Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion BackendAcademy/src/courses/course.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
* before starting Lesson 2").
*/

// ── Course ──────────────────────────────────────────────────────────────────
export type CourseStatus = 'draft' | 'published';

export interface Course {
courseId: string;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
}
38 changes: 38 additions & 0 deletions BackendAcademy/src/courses/enrollment.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, unknown>;
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');
});
});
});
10 changes: 9 additions & 1 deletion BackendAcademy/src/courses/enrollment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -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<string, Enrollment>();

if (userEnrollments.has(courseId)) {
Expand Down