Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: mergecardservice to usecase #1309

Merged
merged 20 commits into from
Mar 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
6fac1c1
refactor: exception insert_failed to service
GoncaloCanteiro Mar 22, 2023
3954880
refactor: create card service to useCase
GoncaloCanteiro Mar 23, 2023
26e541b
fix: createCardUseCase name, add provider/module
GoncaloCanteiro Mar 23, 2023
ac92c1a
fix: remove create.card.service
GoncaloCanteiro Mar 23, 2023
c41559e
test: create-card useCase base test
GoncaloCanteiro Mar 23, 2023
08adcb8
refactor: create-card useCase into functions
GoncaloCanteiro Mar 24, 2023
96f4de2
refactor: getNewCardPopulated name, add try catch
GoncaloCanteiro Mar 24, 2023
a537ce7
test: completed test coverage
GoncaloCanteiro Mar 24, 2023
e35f3f3
Merge branch 'main' into refactor/create-card-service-to-usecase
GoncaloCanteiro Mar 24, 2023
91ea102
fix: change dto to presenter, delete params folder
GoncaloCanteiro Mar 27, 2023
a6c0135
feat: add merge-card.use-case dto
GoncaloCanteiro Mar 27, 2023
1330293
fix: isString validator
GoncaloCanteiro Mar 27, 2023
60f9cb2
refactor: merge.card.service to useCase
GoncaloCanteiro Mar 27, 2023
0c364d3
fix: remove unused merge.service and merge.app
GoncaloCanteiro Mar 27, 2023
f9709fe
test: add merge-card.use-case
GoncaloCanteiro Mar 27, 2023
4c0475b
fix: missing await in controller
GoncaloCanteiro Mar 27, 2023
9b82a83
refactor: concatenate cards to a funtion
GoncaloCanteiro Mar 27, 2023
269f2e5
refactor: test description enhancement
GoncaloCanteiro Mar 27, 2023
a08bfd5
Merge branch 'main' into refactor/mergecardservice-to-usecase
GoncaloCanteiro Mar 28, 2023
5b27f2e
fix: codacy code standards
GoncaloCanteiro Mar 28, 2023
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
112 changes: 112 additions & 0 deletions backend/src/modules/cards/applications/merge-card.use-case.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { DeepMocked, createMock } from '@golevelup/ts-jest';
import { Test, TestingModule } from '@nestjs/testing';
import { CardRepositoryInterface } from '../repository/card.repository.interface';
import { MergeCardUseCase } from './merge-card.use-case';
import { TYPES } from '../interfaces/types';
import { GetCardServiceInterface } from '../interfaces/services/get.card.service.interface';
import MergeCardUseCaseDto from '../dto/useCase/merge-card.use-case.dto';
import faker from '@faker-js/faker';
import { BadRequestException } from '@nestjs/common';
import { CardFactory } from 'src/libs/test-utils/mocks/factories/card-factory.mock';
import { UpdateResult } from 'mongodb';
import { CardItemFactory } from 'src/libs/test-utils/mocks/factories/cardItem-factory.mock';

const mergeCardDtoMock: MergeCardUseCaseDto = {
boardId: faker.datatype.uuid(),
draggedCardId: faker.datatype.uuid(),
targetCardId: faker.datatype.uuid()
};
const cardMock = CardFactory.createMany(2, [
{ items: CardItemFactory.createMany(1) },
{ items: CardItemFactory.createMany(1) }
]);
const updateResult: UpdateResult = {
acknowledged: true,
matchedCount: 1,
modifiedCount: 1,
upsertedCount: 1,
upsertedId: null
};

describe('MergeCardUseCase', () => {
let useCase: MergeCardUseCase;
let cardRepositoryMock: DeepMocked<CardRepositoryInterface>;
let getCardServiceMock: DeepMocked<GetCardServiceInterface>;
beforeAll(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
MergeCardUseCase,
{
provide: TYPES.services.GetCardService,
useValue: createMock<GetCardServiceInterface>()
},
{
provide: TYPES.repository.CardRepository,
useValue: createMock<CardRepositoryInterface>()
}
]
}).compile();
useCase = module.get<MergeCardUseCase>(MergeCardUseCase);
getCardServiceMock = module.get(TYPES.services.GetCardService);
cardRepositoryMock = module.get(TYPES.repository.CardRepository);

getCardServiceMock.getCardFromBoard
.mockResolvedValue(cardMock[0])
.mockResolvedValue(cardMock[1]);
cardRepositoryMock.pullCard.mockResolvedValue(updateResult);
});

beforeEach(() => {
jest.clearAllMocks();
jest.restoreAllMocks();
});

it('should be defined', () => {
expect(useCase).toBeDefined();
});
it('should ', async () => {
await useCase.execute(mergeCardDtoMock);
await expect(cardRepositoryMock.updateCardOnMerge).toHaveBeenNthCalledWith(
1,
mergeCardDtoMock.boardId,
mergeCardDtoMock.targetCardId,
expect.anything(),
expect.anything(),
expect.anything(),
true
);
});

it('should throw badRequest if getCardFromBoard not found', async () => {
getCardServiceMock.getCardFromBoard.mockResolvedValueOnce(null);
await expect(useCase.execute(mergeCardDtoMock)).rejects.toThrow(BadRequestException);
});

it('should throw badRequest if repository pullCard not found', async () => {
cardRepositoryMock.pullCard.mockResolvedValueOnce(null);
await expect(useCase.execute(mergeCardDtoMock)).rejects.toThrow(BadRequestException);
});

it('should throw badRequest if getCardFromBoard not found', async () => {
getCardServiceMock.getCardFromBoard
.mockResolvedValueOnce(cardMock[0])
.mockResolvedValueOnce(null);
await expect(useCase.execute(mergeCardDtoMock)).rejects.toThrow(BadRequestException);
});

it('should throw badRequest if pullResult.modifiedCount different then 1', async () => {
updateResult.modifiedCount = 2;
await expect(useCase.execute(mergeCardDtoMock)).rejects.toThrow(BadRequestException);
updateResult.modifiedCount = 1;
});

it('should throw badRequest if updateCardMerge fails', async () => {
cardRepositoryMock.updateCardOnMerge.mockResolvedValue(null);
await expect(useCase.execute(mergeCardDtoMock)).rejects.toThrow(BadRequestException);
});

it('should throw badRequest with default message when a non expected error occurs', async () => {
cardRepositoryMock.updateCardOnMerge.mockRejectedValueOnce(Error);
await expect(useCase.execute(mergeCardDtoMock)).rejects.toThrow(BadRequestException);
});
});
Original file line number Diff line number Diff line change
@@ -1,47 +1,42 @@
import { Inject } from '@nestjs/common';
import { CARD_NOT_FOUND, CARD_NOT_REMOVED, UPDATE_FAILED } from 'src/libs/exceptions/messages';
import { Inject, Injectable } from '@nestjs/common';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import { GetCardServiceInterface } from '../interfaces/services/get.card.service.interface';
import { MergeCardServiceInterface } from '../interfaces/services/merge.card.service.interface';
import { TYPES } from '../interfaces/types';
import { CardRepositoryInterface } from '../repository/card.repository.interface';
import { TYPES } from '../interfaces/types';
import MergeCardUseCaseDto from '../dto/useCase/merge-card.use-case.dto';
import { CARD_NOT_FOUND, CARD_NOT_REMOVED, UPDATE_FAILED } from 'src/libs/exceptions/messages';
import { UpdateFailedException } from 'src/libs/exceptions/updateFailedBadRequestException';

export class MergeCardService implements MergeCardServiceInterface {
@Injectable()
export class MergeCardUseCase implements UseCase<MergeCardUseCaseDto, boolean> {
constructor(
@Inject(TYPES.services.GetCardService)
private readonly getCardService: GetCardServiceInterface,
@Inject(TYPES.repository.CardRepository)
private readonly cardRepository: CardRepositoryInterface
) {}

// eslint-disable-next-line @typescript-eslint/no-unused-vars
async mergeCards(boardId: string, draggedCardId: string, cardId: string) {
async execute(mergeCardUseCaseDto: MergeCardUseCaseDto) {
const { boardId, draggedCardId, targetCardId } = mergeCardUseCaseDto;
await this.cardRepository.startTransaction();

try {
const cardToMove = await this.getCardService.getCardFromBoard(boardId, draggedCardId);

if (!cardToMove) return null;
if (!cardToMove) throw Error(CARD_NOT_FOUND);

const pullResult = await this.cardRepository.pullCard(boardId, draggedCardId, true);

if (pullResult.modifiedCount !== 1) throw Error(CARD_NOT_REMOVED);

const cardGroup = await this.getCardService.getCardFromBoard(boardId, cardId);
const cardGroup = await this.getCardService.getCardFromBoard(boardId, targetCardId);

if (!cardGroup) throw Error(CARD_NOT_FOUND);

const { items, comments, votes } = cardToMove;
const newItems = cardGroup.items.concat(items);

const newVotes = (cardGroup.votes as unknown as string[]).concat(
votes as unknown as string[]
);

const newComments = cardGroup.comments.concat(comments);
const { newItems, newVotes, newComments } = this.concatCards(cardToMove, cardGroup);

const updateCard = await this.cardRepository.updateCardOnMerge(
boardId,
cardId,
targetCardId,
newItems,
newVotes,
newComments,
Expand All @@ -54,10 +49,20 @@ export class MergeCardService implements MergeCardServiceInterface {
return true;
} catch (e) {
await this.cardRepository.abortTransaction();
throw new UpdateFailedException(e.message ? e.message : UPDATE_FAILED);
} finally {
await this.cardRepository.endSession();
}
}

private concatCards(cardToMove, cardGroup) {
const { items, comments, votes } = cardToMove;
const newItems = cardGroup.items.concat(items);

const newVotes = (cardGroup.votes as unknown as string[]).concat(votes as unknown as string[]);

const newComments = cardGroup.comments.concat(comments);

return false;
return { newItems, newVotes, newComments };
}
}
16 changes: 0 additions & 16 deletions backend/src/modules/cards/applications/merge.card.application.ts

This file was deleted.

8 changes: 3 additions & 5 deletions backend/src/modules/cards/cards.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import {
deleteCardApplication,
deleteCardService,
getCardService,
mergeCardApplication,
mergeCardService,
mergeCardUseCase,
unmergeCardApplication,
unmergeCardService,
updateCardApplication,
Expand All @@ -25,14 +24,13 @@ import CardsController from './controller/cards.controller';
getCardService,
deleteCardService,
updateCardService,
mergeCardService,
unmergeCardService,
updateCardApplication,
deleteCardApplication,
mergeCardApplication,
unmergeCardApplication,
cardRepository,
creacteCardUseCase
creacteCardUseCase,
mergeCardUseCase
],
exports: [getCardService, deleteCardService]
})
Expand Down
18 changes: 6 additions & 12 deletions backend/src/modules/cards/cards.providers.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { CreateCardUseCase } from './applications/create-card.use-case';
import { DeleteCardApplication } from './applications/delete.card.application';
import { MergeCardApplication } from './applications/merge.card.application';
import { MergeCardUseCase } from './applications/merge-card.use-case';
import { UnmergeCardApplication } from './applications/unmerge.card.application';
import { UpdateCardApplication } from './applications/update.card.application';
import { TYPES } from './interfaces/types';
import { CardRepository } from './repository/card.repository';
import DeleteCardService from './services/delete.card.service';
import GetCardService from './services/get.card.service';
import { MergeCardService } from './services/merge.card.service';
import { UnmergeCardService } from './services/unmerge.card.service';
import UpdateCardService from './services/update.card.service';

Expand All @@ -26,11 +25,6 @@ export const deleteCardService = {
useClass: DeleteCardService
};

export const mergeCardService = {
provide: TYPES.services.MergeCardService,
useClass: MergeCardService
};

export const unmergeCardService = {
provide: TYPES.services.UnmergeCardService,
useClass: UnmergeCardService
Expand All @@ -46,11 +40,6 @@ export const deleteCardApplication = {
useClass: DeleteCardApplication
};

export const mergeCardApplication = {
provide: TYPES.applications.MergeCardApplication,
useClass: MergeCardApplication
};

export const unmergeCardApplication = {
provide: TYPES.applications.UnmergeCardApplication,
useClass: UnmergeCardApplication
Expand All @@ -65,3 +54,8 @@ export const creacteCardUseCase = {
provide: TYPES.applications.CreateCardUseCase,
useClass: CreateCardUseCase
};

export const mergeCardUseCase = {
provide: TYPES.applications.MergeCardUseCase,
useClass: MergeCardUseCase
};
12 changes: 6 additions & 6 deletions backend/src/modules/cards/controller/cards.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,12 @@ import { TYPES } from '../interfaces/types';
import { MergeCardDto } from '../dto/group/merge.card.dto';
import { UpdateCardApplicationInterface } from '../interfaces/applications/update.card.application.interface';
import { DeleteCardApplicationInterface } from '../interfaces/applications/delete.card.application.interface';
import { MergeCardApplicationInterface } from '../interfaces/applications/merge.card.application.interface';
import { UnmergeCardApplicationInterface } from '../interfaces/applications/unmerge.card.application.interface';
import CreateCardUseCaseDto from '../dto/useCase/create-card.use-case.dto';
import { UseCase } from 'src/libs/interfaces/use-case.interface';
import CardCreationPresenter from '../dto/useCase/presenters/create-card-res.use-case.dto';
import MergeCardUseCaseDto from '../dto/useCase/merge-card.use-case.dto';


@ApiBearerAuth('access-token')
@ApiTags('Cards')
Expand All @@ -63,8 +64,8 @@ export default class CardsController {
private updateCardApp: UpdateCardApplicationInterface,
@Inject(TYPES.applications.DeleteCardApplication)
private deleteCardApp: DeleteCardApplicationInterface,
@Inject(TYPES.applications.MergeCardApplication)
private mergeCardApp: MergeCardApplicationInterface,
@Inject(TYPES.applications.MergeCardUseCase)
private mergeCardUseCase: UseCase<MergeCardUseCaseDto, boolean>,
@Inject(TYPES.applications.UnmergeCardApplication)
private unmergeCardApp: UnmergeCardApplicationInterface,
private socketService: SocketGateway
Expand Down Expand Up @@ -330,10 +331,9 @@ export default class CardsController {
const { boardId, cardId: draggedCardId, targetCardId } = params;
const { socketId } = mergeCardsDto;

const board = await this.mergeCardApp.mergeCards(boardId, draggedCardId, targetCardId);
const board = await this.mergeCardUseCase.execute({ boardId, draggedCardId, targetCardId });

if (!board) throw new BadRequestException(UPDATE_FAILED);
this.socketService.sendMergeCards(socketId, mergeCardsDto);
if (board) this.socketService.sendMergeCards(socketId, mergeCardsDto);

return HttpStatus.OK;
}
Expand Down
19 changes: 19 additions & 0 deletions backend/src/modules/cards/dto/useCase/merge-card.use-case.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty, IsString } from 'class-validator';

export default class MergeCardUseCaseDto {
@ApiProperty()
@IsNotEmpty()
@IsString()
boardId: string;

@ApiProperty()
@IsNotEmpty()
@IsString()
draggedCardId: string;

@ApiProperty()
@IsNotEmpty()
@IsString()
targetCardId: string;
}

This file was deleted.

This file was deleted.

3 changes: 2 additions & 1 deletion backend/src/modules/cards/interfaces/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export const TYPES = {
UpdateCardApplication: 'UpdateCardApplication',
MergeCardApplication: 'MergeCardApplication',
UnmergeCardApplication: 'UnmergeCardApplication',
CreateCardUseCase: 'CreateCardUseCase'
CreateCardUseCase: 'CreateCardUseCase',
MergeCardUseCase: 'MergeCardUseCase'
},
repository: {
CardRepository: 'CardRepository'
Expand Down