Skip to content

Commit

Permalink
Feat(Course, Certificate): Delete course certificate use case
Browse files Browse the repository at this point in the history
  • Loading branch information
Artur-Poffo committed Feb 18, 2024
1 parent 1169abb commit d041967
Show file tree
Hide file tree
Showing 7 changed files with 196 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@

### Certificate
- [ ] POST /courses/:courseId/certificates - Add certificate to course
- [ ] DELETE /courses/:courseId/certificates - Remove certificate from course - make use case
- [ ] DELETE /courses/:courseId/certificates - Remove certificate from course

### StudentCertificate
- [ ] GET /enrollments/:enrollmentId/certificate - Issue student certificate
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ export interface CertificatesRepository {
findById: (id: string) => Promise<Certificate | null>
findByCourseId: (courseId: string) => Promise<Certificate | null>
create: (certificate: Certificate) => Promise<Certificate | null>
delete: (certificate: Certificate) => Promise<void>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { NotAllowedError } from '@/core/errors/errors/not-allowed-error'
import { ResourceNotFoundError } from '@/core/errors/errors/resource-not-found-error'
import { makeCertificate } from '../../../../../test/factories/make-certificate'
import { makeCourse } from '../../../../../test/factories/make-course'
import { makeInstructor } from '../../../../../test/factories/make-instructor'
import { InMemoryClassesRepository } from '../../../../../test/repositories/in-memory-classes-repository'
import { InMemoryCourseTagsRepository } from '../../../../../test/repositories/in-memory-course-tags-repository'
import { InMemoryCoursesRepository } from '../../../../../test/repositories/in-memory-courses-repository'
import { InMemoryEnrollmentCompletedItemsRepository } from '../../../../../test/repositories/in-memory-enrollment-completed-items-repository'
import { InMemoryEnrollmentsRepository } from '../../../../../test/repositories/in-memory-enrollments-repository'
import { InMemoryInstructorRepository } from '../../../../../test/repositories/in-memory-instructors-repository'
import { InMemoryModulesRepository } from '../../../../../test/repositories/in-memory-modules-repository'
import { InMemoryStudentsRepository } from '../../../../../test/repositories/in-memory-students-repository'
import { InMemoryCertificatesRepository } from './../../../../../test/repositories/in-memory-certificates-repository'
import { DeleteCourseCertificateUseCase } from './delete-course-certificate'

let inMemoryEnrollmentCompletedItemsRepository: InMemoryEnrollmentCompletedItemsRepository
let inMemoryCertificatesRepository: InMemoryCertificatesRepository
let inMemoryClassesRepository: InMemoryClassesRepository
let inMemoryCourseTagsRepository: InMemoryCourseTagsRepository
let inMemoryEnrollmentsRepository: InMemoryEnrollmentsRepository
let inMemoryStudentsRepository: InMemoryStudentsRepository
let inMemoryInstructorsRepository: InMemoryInstructorRepository
let inMemoryModulesRepository: InMemoryModulesRepository
let inMemoryCoursesRepository: InMemoryCoursesRepository
let sut: DeleteCourseCertificateUseCase

describe('Delete course certificate use case', () => {
beforeEach(() => {
inMemoryEnrollmentCompletedItemsRepository = new InMemoryEnrollmentCompletedItemsRepository()
inMemoryCertificatesRepository = new InMemoryCertificatesRepository()
inMemoryClassesRepository = new InMemoryClassesRepository()
inMemoryCourseTagsRepository = new InMemoryCourseTagsRepository()
inMemoryStudentsRepository = new InMemoryStudentsRepository()
inMemoryInstructorsRepository = new InMemoryInstructorRepository()

inMemoryModulesRepository = new InMemoryModulesRepository(inMemoryClassesRepository)

inMemoryEnrollmentsRepository = new InMemoryEnrollmentsRepository(
inMemoryStudentsRepository, inMemoryEnrollmentCompletedItemsRepository
)
inMemoryCoursesRepository = new InMemoryCoursesRepository(inMemoryModulesRepository, inMemoryInstructorsRepository, inMemoryEnrollmentsRepository, inMemoryStudentsRepository, inMemoryCourseTagsRepository)

sut = new DeleteCourseCertificateUseCase(
inMemoryCertificatesRepository,
inMemoryCoursesRepository,
inMemoryInstructorsRepository
)
})

it('should be able to delete a course certificate', async () => {
const instructor = makeInstructor()
await inMemoryInstructorsRepository.create(instructor)

const course = makeCourse({ name: 'John Doe Course', instructorId: instructor.id })
await inMemoryCoursesRepository.create(course)

const certificate = makeCertificate({ courseId: course.id })
await inMemoryCertificatesRepository.create(certificate)

const result = await sut.exec({
courseId: course.id.toString(),
certificateId: certificate.id.toString(),
instructorId: instructor.id.toString()
})

expect(result.isRight()).toBe(true)
expect(inMemoryCertificatesRepository.items).toHaveLength(0)
})

it('should not be able to delete a course certificate from a inexistent course', async () => {
const result = await sut.exec({
courseId: 'inexistentCourseId',
certificateId: 'inexistentCertificateId',
instructorId: 'inexistentInstructorId'
})

expect(result.isLeft()).toBe(true)
expect(result.value).toBeInstanceOf(ResourceNotFoundError)
})

it('should not be able to delete a course if the instructor not is the owner', async () => {
const owner = makeInstructor()
const wrongInstructor = makeInstructor()

await Promise.all([
inMemoryInstructorsRepository.create(owner),
inMemoryInstructorsRepository.create(wrongInstructor)
])

const course = makeCourse({ name: 'John Doe Course', instructorId: owner.id })
await inMemoryCoursesRepository.create(course)

const certificate = makeCertificate({ courseId: course.id })
await inMemoryCertificatesRepository.create(certificate)

const result = await sut.exec({
courseId: course.id.toString(),
certificateId: certificate.id.toString(),
instructorId: wrongInstructor.id.toString()
})

expect(result.isLeft()).toBe(true)
expect(result.value).toBeInstanceOf(NotAllowedError)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
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 Certificate } from '../../enterprise/entities/certificate'
import { type CertificatesRepository } from '../repositories/certificates-repository'
import { type CoursesRepository } from '../repositories/courses-repository'
import { type InstructorsRepository } from '../repositories/instructors-repository'

interface DeleteCourseCertificateUseCaseRequest {
certificateId: string
courseId: string
instructorId: string
}

type DeleteCourseCertificateUseCaseResponse = Either<
ResourceNotFoundError | NotAllowedError,
{
certificate: Certificate
}
>

export class DeleteCourseCertificateUseCase implements UseCase<DeleteCourseCertificateUseCaseRequest, DeleteCourseCertificateUseCaseResponse> {
constructor(
private readonly certificatesRepository: CertificatesRepository,
private readonly coursesRepository: CoursesRepository,
private readonly instructorsRepository: InstructorsRepository
) { }

async exec({
certificateId,
courseId,
instructorId
}: DeleteCourseCertificateUseCaseRequest): Promise<DeleteCourseCertificateUseCaseResponse> {
const [certificate, course, instructor] = await Promise.all([
this.certificatesRepository.findById(certificateId),
this.coursesRepository.findById(courseId),
this.instructorsRepository.findById(instructorId)
])

if (!certificate || !course || !instructor) {
return left(new ResourceNotFoundError())
}

const instructorIsTheOwner = course.instructorId.toString() === instructorId

if (!instructorIsTheOwner) {
return left(new NotAllowedError())
}

await this.certificatesRepository.delete(certificate)

return right({
certificate
})
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,12 @@ export class PrismaCertificatesRepository implements CertificatesRepository {

return certificate
}

async delete(certificate: Certificate): Promise<void> {
await prisma.certificate.delete({
where: {
id: certificate.id.toString()
}
})
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { DeleteCourseCertificateUseCase } from '@/domain/course-management/application/use-cases/delete-course-certificate'
import { makePrismaCertificatesRepository } from '@/infra/database/prisma/repositories/factories/make-prisma-certificates-repository'
import { makePrismaCoursesRepository } from '@/infra/database/prisma/repositories/factories/make-prisma-courses-repository'
import { makePrismaInstructorsRepository } from '@/infra/database/prisma/repositories/factories/make-prisma-instructors-repository'

export function makeDeleteCourseCertificateUseCase() {
const prismaCertificatesRepository = makePrismaCertificatesRepository()
const prismaCoursesRepository = makePrismaCoursesRepository()
const prismaInstructorsRepository = makePrismaInstructorsRepository()

const deleteCourseCertificateUseCase = new DeleteCourseCertificateUseCase(
prismaCertificatesRepository,
prismaCoursesRepository,
prismaInstructorsRepository
)

return deleteCourseCertificateUseCase
}
5 changes: 5 additions & 0 deletions test/repositories/in-memory-certificates-repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,9 @@ export class InMemoryCertificatesRepository implements CertificatesRepository {
this.items.push(certificate)
return certificate
}

async delete(certificate: Certificate): Promise<void> {
const certificateIndex = this.items.indexOf(certificate)
this.items.splice(certificateIndex, 1)
}
}

0 comments on commit d041967

Please sign in to comment.