Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { CourseFinder as DomainCourseFinder } from '../../domain/CourseFinder';
import { CourseId } from '../../../Shared/domain/Courses/CourseId';
import { GetCourseResponse } from './GetCourseResponse';
import { CourseResponse } from '../../../Shared/domain/Courses/application/CourseResponse';

export type Params = {
courseId: CourseId;
Expand All @@ -13,8 +13,8 @@ export class CourseFinder {
this.courseFinder = courseFinder;
}

async run({ courseId }: Params): Promise<GetCourseResponse> {
async run({ courseId }: Params): Promise<CourseResponse> {
const course = await this.courseFinder.run(courseId);
return new GetCourseResponse(course);
return new CourseResponse(course);
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { GetCourseQuery } from './GetCourseQuery';
import { CourseId } from '../../../Shared/domain/Courses/CourseId';
import { CourseFinder } from './CourseFinder';
import { GetCourseResponse } from './GetCourseResponse';
import { CourseResponse } from '../../../Shared/domain/Courses/application/CourseResponse';
import { QueryHandler } from '../../../../Shared/domain/QueryHandler';
import { Query } from '../../../../Shared/domain/Query';

export class GetCourseQueryHandler implements QueryHandler<GetCourseQuery, GetCourseResponse> {
export class GetCourseQueryHandler implements QueryHandler<GetCourseQuery, CourseResponse> {
constructor(private courseFinder: CourseFinder) {}

subscribedTo(): Query {
return GetCourseQuery;
}

async handle(query: GetCourseQuery): Promise<GetCourseResponse> {
async handle(query: GetCourseQuery): Promise<CourseResponse> {
const courseId = new CourseId(query.id);
return this.courseFinder.run({ courseId });
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Course } from '../../domain/Course';
import { CourseResponse } from '../../../Shared/domain/Courses/application/CourseResponse';
import { Nullable } from '../../../../Shared/domain/Nullable';
export class CoursesResponse {
readonly data: CourseResponse[];

constructor(courses: Nullable<Course[]>) {
this.data = [];
if (courses !== null) {
courses.forEach(course => {
this.data.push(new CourseResponse(course))
});
}
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { CourseFinder as DomainCourseFinder } from '../../domain/CourseFinder';
import { CourseId } from '../../../Shared/domain/Courses/CourseId';
import { CourseResponse } from '../../../Shared/domain/Courses/application/CourseResponse';
import { CourseRepository } from '../../domain/CourseRepository';
import { Course } from '../../domain/Course';
import { CoursesResponse } from './CoursesResponse';
import { Nullable } from '../../../../Shared/domain/Nullable';

export class CoursesSearcher {
private repository: CourseRepository;

constructor(repository: CourseRepository) {
this.repository = repository;
}

async run(): Promise<CoursesResponse> {
const courses : Nullable<Course[]> = await this.repository.getAll();
return new CoursesResponse(courses);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Query } from '../../../../Shared/domain/Query';

export class GetCoursesQuery extends Query {

constructor() {
super();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { QueryHandler } from '../../../../Shared/domain/QueryHandler';
import { Query } from '../../../../Shared/domain/Query';
import { CoursesResponse } from './CoursesResponse';
import { GetCoursesQuery } from './GetCoursesQuery';
import { CoursesSearcher } from './CoursesSearcher';

export class GetCoursesQueryHandler implements QueryHandler<GetCoursesQuery, CoursesResponse> {
constructor(private coursesSearcher: CoursesSearcher) {}

subscribedTo(): Query {
return GetCoursesQuery;
}

async handle(query: GetCoursesQuery): Promise<CoursesResponse> {
return this.coursesSearcher.run();
}
}
2 changes: 2 additions & 0 deletions src/Contexts/Mooc/Courses/domain/CourseRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ export interface CourseRepository {
save(course: Course): Promise<void>;

search(id: CourseId): Promise<Nullable<Course>>;

getAll(): Promise<Course[]>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ export class MongoCourseRepository extends MongoRepository<Course> implements Co
return document ? Course.fromPrimitives({ ...document, id: id.value }) : null;
}

public async getAll(): Promise<Course[]> {
const collection = await this.collection();

const documents = await collection.find().toArray();

const courses : Course[] = [];
documents.forEach((document: any) => document ? courses.push(Course.fromPrimitives({...document, id: document._id})) : undefined);

return courses;
}

protected moduleName(): string {
return 'courses';
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Course } from '../../domain/Course';
export class GetCourseResponse {
import { Course } from '../../../../Courses/domain/Course';
export class CourseResponse {
readonly id: string;
readonly name: string;
readonly duration: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,3 +38,13 @@ services:
tags:
- { name: 'commandHandler' }

Mooc.courses.CoursesSearcher:
class: ../../../../../Contexts/Mooc/Courses/application/GetCourses/CoursesSearcher
arguments: ["@Mooc.courses.CourseRepository"]

Mooc.courses.GetCoursesQueryHandler:
class: ../../../../../Contexts/Mooc/Courses/application/GetCourses/GetCoursesQueryHandler
arguments: ["@Mooc.courses.CoursesSearcher"]
tags:
- { name: 'queryHandler' }

Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,8 @@ services:

Apps.mooc.controllers.CourseGetController:
class: ../../../controllers/CourseGetController
arguments: ["@Shared.QueryBus"]

Apps.mooc.controllers.CoursesGetController:
class: ../../../controllers/CoursesGetController
arguments: ["@Shared.QueryBus"]
4 changes: 2 additions & 2 deletions src/apps/mooc_backend/controllers/CourseGetController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import httpStatus = require('http-status');

import { QueryBus } from '../../../Contexts/Shared/domain/QueryBus';
import { GetCourseQuery } from '../../../Contexts/Mooc/Courses/application/GetCourse/GetCourseQuery';
import { GetCourseResponse } from '../../../Contexts/Mooc/Courses/application/GetCourse/GetCourseResponse';
import { CourseResponse } from '../../../Contexts/Mooc/Shared/domain/Courses/application/CourseResponse';
import { CourseNotFound } from '../../../Contexts/Mooc/Courses/domain/CourseNotFound';

export class CourseGetController implements Controller {
Expand All @@ -13,7 +13,7 @@ export class CourseGetController implements Controller {
try {
const id: string = req.params.id;
const query = new GetCourseQuery({id});
const course = await this.queryBus.ask<GetCourseResponse>(query);
const course = await this.queryBus.ask<CourseResponse>(query);
res.status(httpStatus.OK).send(course);
} catch (e) {
if (e instanceof CourseNotFound) {
Expand Down
19 changes: 19 additions & 0 deletions src/apps/mooc_backend/controllers/CoursesGetController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { CoursesResponse } from './../../../Contexts/Mooc/Courses/application/GetCourses/CoursesResponse';
import { Controller } from './Controller';
import { Request, Response } from 'express';
import httpStatus = require('http-status');
import { QueryBus } from '../../../Contexts/Shared/domain/QueryBus';
import { GetCoursesQuery } from '../../../Contexts/Mooc/Courses/application/GetCourses/GetCoursesQuery';

export class CoursesGetController implements Controller {
constructor(private queryBus: QueryBus) {}
async run(req: Request, res: Response): Promise<void> {
try {
const query = new GetCoursesQuery();
const course = await this.queryBus.ask<CoursesResponse>(query);
res.status(httpStatus.OK).send(course);
} catch (e) {
res.status(httpStatus.INTERNAL_SERVER_ERROR).send();
}
}
}
4 changes: 4 additions & 0 deletions src/apps/mooc_backend/routes/courses.route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Router, Request, Response } from 'express';
import container from '../config/dependency-injection';

export const register = (router: Router) => {

const coursePutController = container.get('Apps.mooc.controllers.CoursePutController');
router.put('/courses/:id', (req: Request, res: Response) => coursePutController.run(req, res));

Expand All @@ -11,6 +12,9 @@ export const register = (router: Router) => {
const courseGetController = container.get('Apps.mooc.controllers.CourseGetController');
router.get('/courses/:id', (req: Request, res: Response) => courseGetController.run(req, res));

const coursesGetController = container.get('Apps.mooc.controllers.CoursesGetController');
router.get('/courses', (req: Request, res: Response) => coursesGetController.run(req, res));

const coursesCounterGetController = container.get('Apps.mooc.controllers.CoursesCounterGetController');
router.get('/courses-counter', (req: Request, res: Response) => coursesCounterGetController.run(req, res));
};
16 changes: 16 additions & 0 deletions tests/Contexts/Mooc/Courses/__mocks__/CourseRepositoryMock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import { CourseId } from '../../../../../src/Contexts/Mooc/Shared/domain/Courses
import { Nullable } from '../../../../../src/Contexts/Shared/domain/Nullable';

export class CourseRepositoryMock implements CourseRepository {

private mockSave = jest.fn();
private mockSearch = jest.fn();
private mockGetAll = jest.fn();
private course: Nullable<Course> = null;
private courses: Course[] = [];

async save(course: Course): Promise<void> {
this.mockSave(course);
Expand Down Expand Up @@ -40,4 +43,17 @@ export class CourseRepositoryMock implements CourseRepository {
returnOnSearch(course: Course) {
this.course = course;
}

async getAll(): Promise<Course[]> {
this.mockGetAll();
return this.courses;
}

returnOnGetAll(courses: Course[]) {
this.courses = courses;
}

assertGetAll() {
expect(this.mockGetAll).toHaveBeenCalled();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { CourseFinder } from '../../../../../../src/Contexts/Mooc/Courses/applic
import { CourseFinder as DomainCourseFinder } from '../../../../../../src/Contexts/Mooc/Courses/domain/CourseFinder';
import { CourseRepositoryMock } from '../../__mocks__/CourseRepositoryMock';
import { CourseMother } from '../../domain/CourseMother';
import { GetCourseResponse } from '../../../../../../src/Contexts/Mooc/Courses/application/GetCourse/GetCourseResponse';
import { ParamsMother } from './ParamsMother';
import { CourseIdMother } from '../../../Shared/domain/Courses/CourseIdMother';
import { CourseNotFound } from '../../../../../../src/Contexts/Mooc/Courses/domain/CourseNotFound';
import { CourseResponse } from '../../../../../../src/Contexts/Mooc/Shared/domain/Courses/application/CourseResponse';

let repository: CourseRepositoryMock;
let finder: CourseFinder;
Expand All @@ -25,7 +25,7 @@ it('should get a course', async () => {
const response = await finder.run(params);

repository.assertSearch(id);
const expected = new GetCourseResponse(course);
const expected = new CourseResponse(course);
expect(expected).toEqual(response);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { GetCourseQuery } from '../../../../../../src/Contexts/Mooc/Courses/appl
import { CourseIdMother } from '../../../Shared/domain/Courses/CourseIdMother';
import { GetCourseQueryHandler } from '../../../../../../src/Contexts/Mooc/Courses/application/GetCourse/GetCourseQueryHandler';
import { CourseNotFound } from '../../../../../../src/Contexts/Mooc/Courses/domain/CourseNotFound';
import { GetCourseResponse } from '../../../../../../src/Contexts/Mooc/Courses/application/GetCourse/GetCourseResponse';
import { CourseResponse } from '../../../../../../src/Contexts/Mooc/Shared/domain/Courses/application/CourseResponse';

describe('GetCourse QueryHandler', () => {
let repository: CourseRepositoryMock;
Expand All @@ -30,7 +30,7 @@ describe('GetCourse QueryHandler', () => {

repository.assertSearch(id);

const expected = new GetCourseResponse(course);
const expected = new CourseResponse(course);
expect(expected).toEqual(response);
});

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { CourseRepositoryMock } from '../../__mocks__/CourseRepositoryMock';
import { CourseMother } from '../../domain/CourseMother';
import { CoursesSearcher } from '../../../../../../src/Contexts/Mooc/Courses/application/GetCourses/CoursesSearcher';
import { GetCoursesQueryHandler } from '../../../../../../src/Contexts/Mooc/Courses/application/GetCourses/GetCoursesQueryHandler';
import { GetCoursesQuery } from '../../../../../../src/Contexts/Mooc/Courses/application/GetCourses/GetCoursesQuery';
import { CoursesResponse } from '../../../../../../src/Contexts/Mooc/Courses/application/GetCourses/CoursesResponse';

describe('GetCourse QueryHandler', () => {
let repository: CourseRepositoryMock;
let coursesSerarcher: CoursesSearcher;

beforeEach(() => {
repository = new CourseRepositoryMock();
coursesSerarcher = new CoursesSearcher(repository);
});


it('should search all courses', async () => {
const firstCourse = CourseMother.random();
const secondCourse = CourseMother.random();
const courses = [firstCourse, secondCourse];
repository.returnOnGetAll(courses);

const handler = new GetCoursesQueryHandler(new CoursesSearcher(repository));

const query = new GetCoursesQuery();
const response = await handler.handle(query);

repository.assertGetAll();

const expected = new CoursesResponse(courses);
expect(expected).toEqual(response);
});

it('should get no one course', async () => {
const handler = new GetCoursesQueryHandler(new CoursesSearcher(repository));

const query = new GetCoursesQuery();
const response = await handler.handle(query);

repository.assertGetAll();

const expected = new CoursesResponse([]);
expect(expected).toEqual(response);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import container from '../../../../../../src/apps/mooc_backend/config/dependency-injection';
import { Course } from '../../../../../../src/Contexts/Mooc/Courses/domain/Course';
import { CourseRepository } from '../../../../../../src/Contexts/Mooc/Courses/domain/CourseRepository';
import { EnvironmentArranger } from '../../../../Shared/infrastructure/arranger/EnvironmentArranger';
import { CourseMother } from '../../domain/CourseMother';
Expand Down Expand Up @@ -27,11 +28,33 @@ describe('Search Course', () => {
const course = CourseMother.random();

await repository.save(course);
const response = await repository.search(course.id);

expect(course).toEqual(await repository.search(course.id));
expect(course).toEqual(response);
});

it('should not return a non existing course', async () => {
expect(await repository.search(CourseMother.random().id)).toBeFalsy();
});
});

describe('Get all Courses', () => {
it('should return a list of existing courses', async () => {
const firstCourse = CourseMother.random();
const secondCourse = CourseMother.random();
await repository.save(firstCourse);
await repository.save(secondCourse);

const response = await repository.getAll();

const expected = [firstCourse, secondCourse];
expect(expected).toEqual(response);
});

it('should return an empty list', async () => {
const response = await repository.getAll();

const expected : Course[] = [];
expect(expected).toEqual(response);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ export abstract class EnvironmentArranger {
public abstract arrange(): Promise<void>;

public abstract close(): Promise<void>;

public abstract addCourseWithId(id: string): Promise<void>;
}
Loading