-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat(Class, Enrollment, Course): Mark class as completed use case
- Loading branch information
1 parent
3bcbd3d
commit 3ec0858
Showing
7 changed files
with
231 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
144 changes: 144 additions & 0 deletions
144
src/domain/course-management/application/use-cases/mark-class-as-completed.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
import { NotAllowedError } from '@/core/errors/errors/not-allowed-error' | ||
import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' | ||
import { makeClass } from '../../../../../test/factories/make-class' | ||
import { makeCourse } from '../../../../../test/factories/make-course' | ||
import { makeEnrollment } from '../../../../../test/factories/make-enrollment' | ||
import { makeInstructor } from '../../../../../test/factories/make-instructor' | ||
import { makeModule } from '../../../../../test/factories/make-module' | ||
import { makeStudent } from '../../../../../test/factories/make-student' | ||
import { InMemoryClassesRepository } from '../../../../../test/repositories/in-memory-classes-repository' | ||
import { InMemoryCoursesRepository } from '../../../../../test/repositories/in-memory-courses-repository' | ||
import { InMemoryEnrollmentsRepository } from '../../../../../test/repositories/in-memory-enrollments-repository' | ||
import { InMemoryStudentsRepository } from '../../../../../test/repositories/in-memory-students-repository' | ||
import { InMemoryInstructorRepository } from './../../../../../test/repositories/in-memory-instructors-repository' | ||
import { InMemoryModulesRepository } from './../../../../../test/repositories/in-memory-modules-repository' | ||
import { MarkClassAsCompletedUseCase } from './mark-class-as-completed' | ||
|
||
let inMemoryEnrollmentsRepository: InMemoryEnrollmentsRepository | ||
let inMemoryStudentsRepository: InMemoryStudentsRepository | ||
let inMemoryClassesRepository: InMemoryClassesRepository | ||
let inMemoryInstructorsRepository: InMemoryInstructorRepository | ||
let inMemoryModulesRepository: InMemoryModulesRepository | ||
let inMemoryCoursesRepository: InMemoryCoursesRepository | ||
let sut: MarkClassAsCompletedUseCase | ||
|
||
describe('Mark class as completed use case', () => { | ||
beforeEach(() => { | ||
inMemoryStudentsRepository = new InMemoryStudentsRepository() | ||
inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository() | ||
inMemoryClassesRepository = new InMemoryClassesRepository() | ||
inMemoryInstructorsRepository = new InMemoryInstructorRepository() | ||
|
||
inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository) | ||
|
||
inMemoryCoursesRepository = new InMemoryCoursesRepository( | ||
inMemoryModulesRepository, inMemoryInstructorsRepository, inMemoryEnrollmentsRepository, inMemoryStudentsRepository | ||
) | ||
|
||
sut = new MarkClassAsCompletedUseCase( | ||
inMemoryEnrollmentsRepository, inMemoryCoursesRepository, inMemoryModulesRepository, inMemoryClassesRepository, inMemoryStudentsRepository | ||
) | ||
}) | ||
|
||
it('should be able to mark a class of a enrollment as completed', async () => { | ||
const instructor = makeInstructor() | ||
await inMemoryInstructorsRepository.create(instructor) | ||
|
||
const course = makeCourse({ instructorId: instructor.id }) | ||
await inMemoryCoursesRepository.create(course) | ||
|
||
const module = makeModule({ | ||
courseId: course.id, | ||
moduleNumber: 1 | ||
}) | ||
await inMemoryModulesRepository.create(module) | ||
|
||
const classToMarkAsCompleted = makeClass({ name: 'John Doe Class', moduleId: module.id, classNumber: 1 }) | ||
await inMemoryClassesRepository.create(classToMarkAsCompleted) | ||
|
||
const student = makeStudent() | ||
await inMemoryStudentsRepository.create(student) | ||
|
||
const enrollment = makeEnrollment({ studentId: student.id, courseId: course.id }) | ||
await inMemoryEnrollmentsRepository.create(enrollment) | ||
|
||
const result = await sut.exec({ | ||
enrollmentId: enrollment.id.toString(), | ||
classId: classToMarkAsCompleted.id.toString(), | ||
studentId: student.id.toString() | ||
}) | ||
|
||
expect(result.isRight()).toBe(true) | ||
expect(result.value).toMatchObject({ | ||
class: expect.objectContaining({ | ||
name: 'John Doe Class' | ||
}) | ||
}) | ||
}) | ||
|
||
it('should not be able to mark a inexistent class as completed', async () => { | ||
const instructor = makeInstructor() | ||
await inMemoryInstructorsRepository.create(instructor) | ||
|
||
const course = makeCourse({ instructorId: instructor.id }) | ||
await inMemoryCoursesRepository.create(course) | ||
|
||
const module = makeModule({ | ||
courseId: course.id, | ||
moduleNumber: 1 | ||
}) | ||
await inMemoryModulesRepository.create(module) | ||
|
||
const student = makeStudent() | ||
await inMemoryStudentsRepository.create(student) | ||
|
||
const enrollment = makeEnrollment({ studentId: student.id, courseId: course.id }) | ||
await inMemoryEnrollmentsRepository.create(enrollment) | ||
|
||
const result = await sut.exec({ | ||
enrollmentId: enrollment.id.toString(), | ||
classId: 'inexistentClassId', | ||
studentId: student.id.toString() | ||
}) | ||
|
||
expect(result.isLeft()).toBe(true) | ||
expect(result.value).toBeInstanceOf(ResourceNotFoundError) | ||
}) | ||
|
||
it('should not be able to mark a class of a enrollment as completed if the student not is the owner of the enrollment', async () => { | ||
const instructor = makeInstructor() | ||
await inMemoryInstructorsRepository.create(instructor) | ||
|
||
const course = makeCourse({ instructorId: instructor.id }) | ||
await inMemoryCoursesRepository.create(course) | ||
|
||
const module = makeModule({ | ||
courseId: course.id, | ||
moduleNumber: 1 | ||
}) | ||
await inMemoryModulesRepository.create(module) | ||
|
||
const classToMarkAsCompleted = makeClass({ name: 'John Doe Class', moduleId: module.id, classNumber: 1 }) | ||
await inMemoryClassesRepository.create(classToMarkAsCompleted) | ||
|
||
const correctStudent = makeStudent() | ||
const wrongStudent = makeStudent() | ||
|
||
await Promise.all([ | ||
inMemoryStudentsRepository.create(correctStudent), | ||
inMemoryStudentsRepository.create(wrongStudent) | ||
]) | ||
|
||
const enrollment = makeEnrollment({ studentId: correctStudent.id, courseId: course.id }) | ||
await inMemoryEnrollmentsRepository.create(enrollment) | ||
|
||
const result = await sut.exec({ | ||
enrollmentId: enrollment.id.toString(), | ||
classId: classToMarkAsCompleted.id.toString(), | ||
studentId: wrongStudent.id.toString() | ||
}) | ||
|
||
expect(result.isLeft()).toBe(true) | ||
expect(result.value).toBeInstanceOf(NotAllowedError) | ||
}) | ||
}) |
77 changes: 77 additions & 0 deletions
77
src/domain/course-management/application/use-cases/mark-class-as-completed.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { left, right, type Either } from '@/core/either' | ||
import { NotAllowedError } from '@/core/errors/errors/not-allowed-error' | ||
import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error' | ||
import { type UseCase } from '@/core/use-cases/use-case' | ||
import { type Class } from '../../enterprise/entities/class' | ||
import { type CoursesRepository } from '../repositories/courses-repository' | ||
import { type EnrollmentsRepository } from '../repositories/enrollments-repository' | ||
import { type StudentsRepository } from '../repositories/students-repository' | ||
import { type ClassesRepository } from './../repositories/classes-repository' | ||
import { type ModulesRepository } from './../repositories/modules-repository' | ||
|
||
interface MarkClassAsCompletedUseCaseRequest { | ||
enrollmentId: string | ||
studentId: string | ||
classId: string | ||
} | ||
|
||
type MarkClassAsCompletedUseCaseResponse = Either< | ||
ResourceNotFoundError | NotAllowedError, | ||
{ | ||
class: Class | ||
} | ||
> | ||
|
||
export class MarkClassAsCompletedUseCase implements UseCase<MarkClassAsCompletedUseCaseRequest, MarkClassAsCompletedUseCaseResponse> { | ||
constructor( | ||
private readonly enrollmentsRepository: EnrollmentsRepository, | ||
private readonly coursesRepository: CoursesRepository, | ||
private readonly modulesRepository: ModulesRepository, | ||
private readonly classesRepository: ClassesRepository, | ||
private readonly studentsRepository: StudentsRepository | ||
) { } | ||
|
||
async exec({ | ||
enrollmentId, | ||
studentId, | ||
classId | ||
}: MarkClassAsCompletedUseCaseRequest): Promise<MarkClassAsCompletedUseCaseResponse> { | ||
const [enrollment, student, classToMarkAsCompleted] = await Promise.all([ | ||
this.enrollmentsRepository.findById(enrollmentId), | ||
this.studentsRepository.findById(studentId), | ||
this.classesRepository.findById(classId) | ||
]) | ||
|
||
if (!enrollment || !student || !classToMarkAsCompleted) { | ||
return left(new ResourceNotFoundError()) | ||
} | ||
|
||
const studentIsTheEnrollmentOwner = enrollment.studentId.toString() === studentId | ||
|
||
if (!studentIsTheEnrollmentOwner) { | ||
return left(new NotAllowedError()) | ||
} | ||
|
||
const completeCourse = await this.coursesRepository.findCompleteCourseEntityById(enrollment.courseId.toString()) | ||
|
||
if (!completeCourse) { | ||
return left(new ResourceNotFoundError()) | ||
} | ||
|
||
const courseClasses = await this.modulesRepository.findManyClassesByCourseId(completeCourse.course.id.toString()) | ||
const courseClassesIds = courseClasses.map(classToMap => classToMap.id.toString()) | ||
|
||
const classExistInThisCourse = courseClassesIds.includes(classId) | ||
|
||
if (!classExistInThisCourse) { | ||
return left(new ResourceNotFoundError()) | ||
} | ||
|
||
enrollment.completedClasses.push(classToMarkAsCompleted.id) | ||
await this.enrollmentsRepository.save(enrollment) | ||
|
||
return right({ | ||
class: classToMarkAsCompleted | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters