Skip to content

Commit

Permalink
feat(course): adding relations at the enrollment
Browse files Browse the repository at this point in the history
  • Loading branch information
leonardodimarchi committed Oct 13, 2023
1 parent ac6ace4 commit 3731589
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 160 deletions.
44 changes: 30 additions & 14 deletions src/modules/course/domain/entities/enrollment/enrollment.entity.ts
Original file line number Diff line number Diff line change
@@ -1,45 +1,61 @@
import { UserEntity } from '@modules/user/domain/entities/user/user.entity';
import { BaseEntity, BaseEntityProps } from '@shared/domain/base.entity';
import { Replace } from '@shared/helpers/replace';
import { Either, Right } from '@shared/helpers/either';
import { UserEntity } from '@modules/user/domain/entities/user/user.entity';
import { Replace } from '@shared/helpers/replace';
import { UUID } from 'crypto';
import { CourseEntity } from '../course/course.entity';

export interface EnrollmentEntityProps {
student: UserEntity;
course: CourseEntity;
studentId: UUID;
courseId: UUID;
student?: UserEntity;
course?: CourseEntity;
}

export type EnrollmentEntityCreateProps = Replace<EnrollmentEntityProps, {}>;
export type EnrollmentEntityCreateProps = Replace<
EnrollmentEntityProps,
unknown
>;

export class EnrollmentEntity extends BaseEntity<EnrollmentEntityProps> {
private constructor(
props: EnrollmentEntityProps,
baseEntityProps?: BaseEntityProps
baseEntityProps?: BaseEntityProps,
) {
super(props, baseEntityProps);
Object.freeze(this);
}

static create(
{ student, course }: EnrollmentEntityCreateProps,
baseEntityProps?: BaseEntityProps
{ studentId, courseId, student, course }: EnrollmentEntityCreateProps,
baseEntityProps?: BaseEntityProps,
): Either<Error, EnrollmentEntity> {
return new Right(
new EnrollmentEntity(
{
studentId,
courseId,
student,
course,
},
baseEntityProps
)
baseEntityProps,
),
);
}

public get student(): UserEntity {
return this.props.student;
public get studentId(): UUID {
return this.props.studentId;
}

public get courseId(): UUID {
return this.props.courseId;
}

public get student(): UserEntity | null {
return this.props.student || null;
}

public get course(): CourseEntity {
return this.props.course;
public get course(): CourseEntity | null {
return this.props.course || null;
}
}
Original file line number Diff line number Diff line change
@@ -1,117 +1,117 @@
import { faker } from '@faker-js/faker'
import { EnrollStudentInCourseUseCase } from './enroll-student-in-course.usecase'
import { UUID } from 'crypto'
import { InMemoryRepository } from 'test/repositories/in-memory-repository'
import { EnrollmentEntity } from '../entities/enrollment/enrollment.entity'
import { EnrollmentRepository } from '../repositories/enrollment.repository'
import { InMemoryEnrollmentRepository } from 'test/repositories/in-memory-enrollment-repository'
import { StudentNotFoundError } from '../errors/student-not-found.error'
import { UserRepository } from '@modules/user/domain/repositories/user.repository'
import { UserEntity } from '@modules/user/domain/entities/user/user.entity'
import { InMemoryUserRepository } from 'test/repositories/in-memory-user-repository'
import { MockUser } from 'test/factories/mock-user'
import { CourseRepository } from '../repositories/course.repository'
import { CourseEntity } from '../entities/course/course.entity'
import { InMemoryCourseRepository } from 'test/repositories/in-memory-course-repository'
import { MockCourse } from 'test/factories/mock-course'
import { CourseNotFoundError } from '../errors/course-not-found.error'
import { StudentAlreadyEnrolledError } from '../errors/student-already-enrolled.error'
import { faker } from '@faker-js/faker';
import { UserEntity } from '@modules/user/domain/entities/user/user.entity';
import { UserRepository } from '@modules/user/domain/repositories/user.repository';
import { UUID } from 'crypto';
import { MockCourse } from 'test/factories/mock-course';
import { MockUser } from 'test/factories/mock-user';
import { InMemoryCourseRepository } from 'test/repositories/in-memory-course-repository';
import { InMemoryEnrollmentRepository } from 'test/repositories/in-memory-enrollment-repository';
import { InMemoryRepository } from 'test/repositories/in-memory-repository';
import { InMemoryUserRepository } from 'test/repositories/in-memory-user-repository';
import { CourseEntity } from '../entities/course/course.entity';
import { EnrollmentEntity } from '../entities/enrollment/enrollment.entity';
import { CourseNotFoundError } from '../errors/course-not-found.error';
import { StudentAlreadyEnrolledError } from '../errors/student-already-enrolled.error';
import { StudentNotFoundError } from '../errors/student-not-found.error';
import { CourseRepository } from '../repositories/course.repository';
import { EnrollmentRepository } from '../repositories/enrollment.repository';
import { EnrollStudentInCourseUseCase } from './enroll-student-in-course.usecase';

describe('EnrollStudentInCourseUseCase', () => {
let usecase: EnrollStudentInCourseUseCase
let usecase: EnrollStudentInCourseUseCase;
let enrollmentRespository: InMemoryRepository<
EnrollmentRepository,
EnrollmentEntity
>
let userRepository: InMemoryRepository<UserRepository, UserEntity>
let courseRepository: InMemoryRepository<CourseRepository, CourseEntity>
>;
let userRepository: InMemoryRepository<UserRepository, UserEntity>;
let courseRepository: InMemoryRepository<CourseRepository, CourseEntity>;

beforeEach(() => {
enrollmentRespository = new InMemoryEnrollmentRepository()
userRepository = new InMemoryUserRepository()
courseRepository = new InMemoryCourseRepository()
enrollmentRespository = new InMemoryEnrollmentRepository();
userRepository = new InMemoryUserRepository();
courseRepository = new InMemoryCourseRepository();
usecase = new EnrollStudentInCourseUseCase(
enrollmentRespository,
userRepository,
courseRepository,
)
})
);
});

const createStudentWithId = (id: UUID) => {
userRepository.save(MockUser.createEntity({ basePropsOverride: { id } }))
}
userRepository.save(MockUser.createEntity({ basePropsOverride: { id } }));
};

const createCourseWithId = (id: UUID) => {
courseRepository.save(
MockCourse.createEntity({ basePropsOverride: { id } }),
)
}
);
};

it('should persist the enrollment with the correct student and course', async () => {
const studentId = faker.string.uuid() as UUID
const courseId = faker.string.uuid() as UUID
const studentId = faker.string.uuid() as UUID;
const courseId = faker.string.uuid() as UUID;

createStudentWithId(studentId)
createCourseWithId(courseId)
createStudentWithId(studentId);
createCourseWithId(courseId);

await usecase.exec({
studentId,
courseId,
})
});

expect(enrollmentRespository.items).toHaveLength(1)
expect(enrollmentRespository.items[0].student.id).toEqual(studentId)
expect(enrollmentRespository.items[0].course.id).toEqual(courseId)
})
expect(enrollmentRespository.items).toHaveLength(1);
expect(enrollmentRespository.items[0].studentId).toEqual(studentId);
expect(enrollmentRespository.items[0].courseId).toEqual(courseId);
});

it('should return an error if the student does not exists', async () => {
const studentId = faker.string.uuid() as UUID
const courseId = faker.string.uuid() as UUID
const studentId = faker.string.uuid() as UUID;
const courseId = faker.string.uuid() as UUID;

createCourseWithId(courseId)
createCourseWithId(courseId);

const result = await usecase.exec({
studentId,
courseId,
})
});

expect(result.isLeft()).toBeTruthy()
expect(result.value).toBeInstanceOf(StudentNotFoundError)
})
expect(result.isLeft()).toBeTruthy();
expect(result.value).toBeInstanceOf(StudentNotFoundError);
});

it('should return an error if the course does not exists', async () => {
const studentId = faker.string.uuid() as UUID
const courseId = faker.string.uuid() as UUID
const studentId = faker.string.uuid() as UUID;
const courseId = faker.string.uuid() as UUID;

createStudentWithId(studentId)
createStudentWithId(studentId);

const result = await usecase.exec({
studentId,
courseId,
})
});

expect(result.isLeft()).toBeTruthy()
expect(result.value).toBeInstanceOf(CourseNotFoundError)
})
expect(result.isLeft()).toBeTruthy();
expect(result.value).toBeInstanceOf(CourseNotFoundError);
});

it('should return an error if the the user is already enrolled at the course', async () => {
const studentId = faker.string.uuid() as UUID
const courseId = faker.string.uuid() as UUID
const studentId = faker.string.uuid() as UUID;
const courseId = faker.string.uuid() as UUID;

createStudentWithId(studentId)
createCourseWithId(courseId)
createStudentWithId(studentId);
createCourseWithId(courseId);

await usecase.exec({
studentId,
courseId,
})
});

const result = await usecase.exec({
studentId,
courseId,
})
});

expect(result.isLeft()).toBeTruthy()
expect(result.value).toBeInstanceOf(StudentAlreadyEnrolledError)
})
})
expect(result.isLeft()).toBeTruthy();
expect(result.value).toBeInstanceOf(StudentAlreadyEnrolledError);
});
});
Original file line number Diff line number Diff line change
@@ -1,28 +1,28 @@
import { UseCase } from '@shared/domain/usecase'
import { Either, Left, Right } from '@shared/helpers/either'
import { UUID } from 'crypto'
import { EnrollmentRepository } from '../repositories/enrollment.repository'
import { EnrollmentEntity } from '../entities/enrollment/enrollment.entity'
import { UserRepository } from '@modules/user/domain/repositories/user.repository'
import { StudentNotFoundError } from '../errors/student-not-found.error'
import { CourseRepository } from '../repositories/course.repository'
import { CourseNotFoundError } from '../errors/course-not-found.error'
import { StudentAlreadyEnrolledError } from '../errors/student-already-enrolled.error'
import { UserRepository } from '@modules/user/domain/repositories/user.repository';
import { UseCase } from '@shared/domain/usecase';
import { Either, Left, Right } from '@shared/helpers/either';
import { UUID } from 'crypto';
import { EnrollmentEntity } from '../entities/enrollment/enrollment.entity';
import { CourseNotFoundError } from '../errors/course-not-found.error';
import { StudentAlreadyEnrolledError } from '../errors/student-already-enrolled.error';
import { StudentNotFoundError } from '../errors/student-not-found.error';
import { CourseRepository } from '../repositories/course.repository';
import { EnrollmentRepository } from '../repositories/enrollment.repository';

export interface EnrollStudentInCourseUseCaseInput {
studentId: UUID
courseId: UUID
studentId: UUID;
courseId: UUID;
}

export interface EnrollStudentInCourseUseCaseOutput {
createdEnrollment: EnrollmentEntity
createdEnrollment: EnrollmentEntity;
}

export type EnrollStudentInCourseUseCaseErrors =
| CourseNotFoundError
| StudentNotFoundError
| StudentAlreadyEnrolledError
| Error
| Error;

export class EnrollStudentInCourseUseCase
implements
Expand All @@ -47,37 +47,37 @@ export class EnrollStudentInCourseUseCase
EnrollStudentInCourseUseCaseOutput
>
> {
const course = await this.courseRepository.getById(courseId)
const course = await this.courseRepository.getById(courseId);

if (!course) {
return new Left(new CourseNotFoundError(courseId))
return new Left(new CourseNotFoundError(courseId));
}

const student = await this.userRepository.getById(studentId)
const student = await this.userRepository.getById(studentId);

if (!student) {
return new Left(new StudentNotFoundError(studentId))
return new Left(new StudentNotFoundError(studentId));
}

const enrollmentAlreadyExists = await this.enrollmentRepository
.getByStudentAndCourse(studentId, courseId)
.then((r) => !!r)
.then((r) => !!r);

if (enrollmentAlreadyExists) {
return new Left(new StudentAlreadyEnrolledError(studentId, courseId))
return new Left(new StudentAlreadyEnrolledError(studentId, courseId));
}

const enrollment = EnrollmentEntity.create({
course,
student,
})
courseId,
studentId,
});

if (enrollment.isLeft()) {
return new Left(enrollment.value)
return new Left(enrollment.value);
}

this.enrollmentRepository.save(enrollment.value)
this.enrollmentRepository.save(enrollment.value);

return new Right({ createdEnrollment: enrollment.value })
return new Right({ createdEnrollment: enrollment.value });
}
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
import { TypeOrmUserMapper } from '@modules/user/infra/database/typeorm/mappers/typeorm-user.mapper';
import { EnrollmentEntity } from '@modules/course/domain/entities/enrollment/enrollment.entity';
import { TypeOrmUserMapper } from '@modules/user/infra/database/typeorm/mappers/typeorm-user.mapper';
import { EnrollmentSchema } from '../schemas/enrollment.schema';
import { TypeOrmCourseMapper } from './typeorm-course.mapper';

export class TypeOrmEnrollmentMapper {
static toEntity(schema: EnrollmentSchema): EnrollmentEntity {
const enrollment = EnrollmentEntity.create(
{
course: TypeOrmCourseMapper.toEntity(schema.course),
student: TypeOrmUserMapper.toEntity(schema.student),
courseId: schema.courseId,
studentId: schema.studentId,
...(schema.student && {
student: TypeOrmUserMapper.toEntity(schema.student),
}),
...(schema.course && {
course: TypeOrmCourseMapper.toEntity(schema.course),
}),
},
{
id: schema.id,
createdAt: schema.createdAt,
updatedAt: schema.updatedAt,
}
},
);

if (enrollment.isLeft()) {
throw new Error(`Could not map enrollment schema to entity: ${ enrollment.value }`);
throw new Error(
`Could not map enrollment schema to entity: ${enrollment.value}`,
);
}

return enrollment.value;
Expand All @@ -27,8 +35,8 @@ export class TypeOrmEnrollmentMapper {
static toSchema(entity: EnrollmentEntity): EnrollmentSchema {
return EnrollmentSchema.create({
id: entity.id,
student: TypeOrmUserMapper.toSchema(entity.student),
course: TypeOrmCourseMapper.toSchema(entity.course),
studentId: entity.studentId,
courseId: entity.courseId,
});
}
}
Loading

0 comments on commit 3731589

Please sign in to comment.