Skip to content

Commit

Permalink
Merge pull request #107 from 4lessandrodev/feat/delete-budget-box
Browse files Browse the repository at this point in the history
test(application): added tests to delete budget box use case
  • Loading branch information
4lessandrodev authored Apr 30, 2022
2 parents 2f28d92 + 53a22a8 commit ed08dc6
Show file tree
Hide file tree
Showing 14 changed files with 253 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface DeleteBudgetBoxDto {
userId: string;
budgetBoxId: string;
}

export default DeleteBudgetBoxDto;
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@

import { IBudgetBoxRepository } from '@modules/budget-box/domain/interfaces/budget-box.repository.interface';
import budgetBoxMockRepo from '@modules/budget-box/application/mocks/budget-box-repo.mock';
import DeleteBudgetBoxUseCase from './delete-budget-box.use-case';
import { BudgetBoxMock } from '@modules/budget-box/domain/tests/mock/budget-box.mock';
import { CURRENCY } from '@config/env';

describe('delete-budget-box.use-case', () => {

let repo: IBudgetBoxRepository;
const budgetBoxMock = new BudgetBoxMock();

beforeEach(() => {
repo = budgetBoxMockRepo;
jest.spyOn(repo, 'findOne').mockClear();
});

it('should execute delete-budget-box use case with success', async () => {

const aggregate = budgetBoxMock.domain({
balanceAvailable: {
currency: CURRENCY,
value: 0
}
}).getResult();

jest.spyOn(repo, 'findOne').mockResolvedValueOnce(aggregate);

const useCase = new DeleteBudgetBoxUseCase(repo);
const useCaseSpy = jest.spyOn(useCase, 'execute');

const dto = { userId: 'valid_id', budgetBoxId: 'valid_id' };

const result = await useCase.execute(dto);

expect(aggregate.domainEvents).toHaveLength(1);

expect(result.isSuccess).toBeTruthy();
expect(useCaseSpy).toHaveBeenCalledWith(dto);
});

it('should fails if budget box does not exists', async () => {

jest.spyOn(repo, 'findOne').mockResolvedValueOnce(null);

const useCase = new DeleteBudgetBoxUseCase(repo);
const saveSpy = jest.spyOn(repo, 'save');

const dto = { userId: 'valid_id', budgetBoxId: 'valid_id' };

const result = await useCase.execute(dto);

expect(result.isFailure).toBeTruthy();
expect(result.error).toBe('Budget Box Does Not Exists');
expect(saveSpy).not.toHaveBeenCalled();
});

it('should fails if budget box has balance', async () => {

const aggregate = budgetBoxMock.domain({
balanceAvailable: {
currency: CURRENCY,
value: 1
}
}).getResult();

jest.spyOn(repo, 'findOne').mockResolvedValueOnce(aggregate);

const useCase = new DeleteBudgetBoxUseCase(repo);
const saveSpy = jest.spyOn(repo, 'save');

const dto = { userId: 'valid_id', budgetBoxId: 'valid_id' };

const result = await useCase.execute(dto);

expect(result.isFailure).toBeTruthy();
expect(result.error).toBe('The budget box must have a zero balance');
expect(saveSpy).not.toHaveBeenCalledWith(dto);
});

it('should fails if repository throws', async () => {

jest.spyOn(repo, 'findOne').mockImplementationOnce(async () => {
throw new Error("error");
});

const useCase = new DeleteBudgetBoxUseCase(repo);
const saveSpy = jest.spyOn(repo, 'save');

const dto = { userId: 'valid_id', budgetBoxId: 'valid_id' };

const result = await useCase.execute(dto);

expect(result.isFailure).toBeTruthy();
expect(result.error).toBe('Internal Server Error on Delete Budget BoxUse Case');
expect(saveSpy).not.toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { IBudgetBoxRepository } from '@modules/budget-box/domain/interfaces/budget-box.repository.interface';
import { Inject } from '@nestjs/common';
import { IUseCase, Result } from 'types-ddd';
import DeleteBudgetBoxDto from './delete-budget-box.dto';

export class DeleteBudgetBoxUseCase implements IUseCase<DeleteBudgetBoxDto, Result<void, string>>{
constructor (
@Inject('BudgetBoxRepository')
private readonly budgetBoxRepo: IBudgetBoxRepository
) { }

async execute ({ budgetBoxId: id, userId: ownerId }: DeleteBudgetBoxDto): Promise<Result<void, string>> {
try {

const budgetBoxOrNull = await this.budgetBoxRepo.findOne({ id, ownerId });

if (!budgetBoxOrNull) {
return Result.fail('Budget Box Does Not Exists', 'NOT_FOUND');
}

const budgetBox = budgetBoxOrNull;

const hasNotBalance = budgetBox.balanceAvailable.isEqualTo(0);

if (!hasNotBalance) {
return Result.fail('The budget box must have a zero balance', 'CONFLICT');
}

budgetBox.delete();

await this.budgetBoxRepo.delete({ id });

return Result.success();

} catch (error) {
return Result.fail('Internal Server Error on Delete Budget BoxUse Case', 'INTERNAL_SERVER_ERROR');
}
}
}

export default DeleteBudgetBoxUseCase;
8 changes: 7 additions & 1 deletion src/modules/budget-box/domain/budget-box.aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
PercentageValueObject
} from './percentage.value-object';
import ReasonDescriptionValueObject from './reason-description.value-object';

import BudgetBoxDeletedEvent from './events/budget-box-deleted.event';

export interface BudgetAggregateProps extends BaseDomainEntity {
ownerId: DomainId;
Expand Down Expand Up @@ -115,6 +115,12 @@ export class BudgetBoxAggregate extends AggregateRoot<BudgetAggregateProps> {
return exists;
}

delete (): void {
this.props.deletedAt = new Date();
this.props.isDeleted = true;
this.addDomainEvent(new BudgetBoxDeletedEvent(this));
}

public static create (
props: BudgetAggregateProps
): Result<BudgetBoxAggregate> {
Expand Down
19 changes: 19 additions & 0 deletions src/modules/budget-box/domain/events/budget-box-deleted.event.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { IDomainEvent, UniqueEntityID } from "types-ddd";
import BudgetBoxAggregate from "../budget-box.aggregate";

export class BudgetBoxDeletedEvent implements IDomainEvent {
public dateTimeOccurred: Date;
public budgetBox: BudgetBoxAggregate;

constructor (budgetBox: BudgetBoxAggregate) {
this.budgetBox = budgetBox;
this.dateTimeOccurred = new Date();
}

getAggregateId (): UniqueEntityID {
return this.budgetBox.id.value;
}

}

export default BudgetBoxDeletedEvent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { DomainEvents, IHandle, Logger } from "types-ddd";
import BudgetBoxDeletedEvent from "../events/budget-box-deleted.event";

export class AfterBudgetBoxDeleted implements IHandle<BudgetBoxDeletedEvent>{
constructor () {
this.setupSubscriptions();
}

setupSubscriptions (): void {
DomainEvents.register(
(event) => this.dispatch(Object.assign(event)),
BudgetBoxDeletedEvent.name
);
}

async dispatch (event: BudgetBoxDeletedEvent): Promise<void> {
Logger.info(`budget box deleted: ${event.budgetBox.id.uid}`);
}

}

export default AfterBudgetBoxDeleted;
6 changes: 5 additions & 1 deletion src/modules/budget-box/infra/budget-box.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import ChangeBudgetBoxPercentageUseCase from "@modules/budget-box/application/us
import ChangeBudgetBoxNameUseCase from "@modules/budget-box/application/use-cases/change-budget-box-name/change-budget-box-name.use-case";
import CanAllocatePercentageToBudgetBoxDomainService from "@modules/budget-box/domain/services/can-allocate-percentage-to-budget-box.domain-service";
import CanChangeBudgetBoxPercentageDomainService from "@modules/budget-box/domain/services/can-change-budget-box-percentage.domain-service";
import AfterBudgetBoxDeleted from "@modules/budget-box/domain/subscription/after-budget-box-deleted.subscription";
import DeleteBudgetBoxUseCase from "@modules/budget-box/application/use-cases/delete-budget-box/delete-budget-box.use-case";

@Module({
imports: [
Expand Down Expand Up @@ -51,7 +53,9 @@ import CanChangeBudgetBoxPercentageDomainService from "@modules/budget-box/domai
useClass: BudgetBoxQueryService
},
CanAllocatePercentageToBudgetBoxDomainService,
CanChangeBudgetBoxPercentageDomainService
CanChangeBudgetBoxPercentageDomainService,
DeleteBudgetBoxUseCase,
AfterBudgetBoxDeleted
],
exports: []
})
Expand Down
12 changes: 11 additions & 1 deletion src/modules/budget-box/infra/budget-box.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import ChangeBudgetBoxNameUseCase from "@modules/budget-box/application/use-case
import ChangeBudgetBoxNameDto from "@modules/budget-box/application/use-cases/change-budget-box-name/change-budget-box-name.dto";
import CanAllocatePercentageToBudgetBoxDomainService from "@modules/budget-box/domain/services/can-allocate-percentage-to-budget-box.domain-service";
import CanChangeBudgetBoxPercentageDomainService from "@modules/budget-box/domain/services/can-change-budget-box-percentage.domain-service";
import DeleteBudgetBoxUseCase from "../application/use-cases/delete-budget-box/delete-budget-box.use-case";
import DeleteBudgetBoxDto from "../application/use-cases/delete-budget-box/delete-budget-box.dto";

@Injectable()
export class BudgetBoxService {
Expand Down Expand Up @@ -51,7 +53,10 @@ export class BudgetBoxService {
private readonly canChangeBudgetBoxPercentageDomainService: CanChangeBudgetBoxPercentageDomainService,

@Inject(ChangeBudgetBoxNameUseCase)
private readonly changeBudgetBoxNameUseCase: ChangeBudgetBoxNameUseCase
private readonly changeBudgetBoxNameUseCase: ChangeBudgetBoxNameUseCase,

@Inject(DeleteBudgetBoxUseCase)
private readonly deleteBudgetBoxUseCase: DeleteBudgetBoxUseCase
) { }
async createBudgetBox (dto: CreateBudgetBoxDto): Promise<void>{

Expand Down Expand Up @@ -102,6 +107,11 @@ export class BudgetBoxService {
const result = await this.changeBudgetBoxNameUseCase.execute(dto);
CheckResultInterceptor(result);
}

async deleteBudgetBox (dto: DeleteBudgetBoxDto): Promise<void> {
const result = await this.deleteBudgetBoxUseCase.execute(dto);
CheckResultInterceptor(result);
}
}

export default BudgetBoxService;
7 changes: 7 additions & 0 deletions src/modules/budget-box/infra/entities/budget-box.schema.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { IBudgetBox, ICurrency, IReason } from "@shared/index";
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
import { Document } from "mongoose";
import { DomainEvents, DomainId } from "types-ddd";

export type BudgetBoxDocument = BudgetBox & Document;

Expand Down Expand Up @@ -36,3 +37,9 @@ export class BudgetBox implements IBudgetBox {
}

export const BudgetBoxSchema = SchemaFactory.createForClass(BudgetBox);

// calls domain events
BudgetBoxSchema.post('remove', function (doc: IBudgetBox) {
const id = DomainId.create(doc.id);
DomainEvents.dispatchEventsForAggregate(id.value);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Field, InputType } from "@nestjs/graphql";

@InputType()
export class DeleteBudgetBoxInput {
@Field(() => String)
budgetBoxId!: string;
}

export default DeleteBudgetBoxInput;
10 changes: 8 additions & 2 deletions src/modules/budget-box/infra/repo/budget-box.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,14 @@ export class BudgetBoxRepository implements IBudgetBoxRepository {
return aggregate.getResult();
};

delete (filter: Filter<Partial<IBudgetBox>>) : Promise<void> {
throw new Error(`Method not implemented, ${filter}`);
async delete (filter: Filter<Partial<IBudgetBox>>) : Promise<void> {
const document = await this.conn.findOne({ ...filter });

if (!document) {
return;
}

await document.remove();
};

async exists (filter: Filter<Partial<IBudgetBox>>) : Promise<boolean> {
Expand Down
14 changes: 14 additions & 0 deletions src/modules/budget-box/infra/resolver/budget-box.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import RemoveReasonFromBudgetBoxInput from "@modules/budget-box/infra/inputs/rem
import ChangeReasonDescriptionBoxInput from "@modules/budget-box/infra/inputs/change-reason-description.input";
import ChangeBudgetBoxPercentageInput from "@modules/budget-box/infra/inputs/change-budget-percentage.input";
import ChangeBudgetBoxNameInput from "@modules/budget-box/infra/inputs/change-budget-box-name.input";
import DeleteBudgetBoxInput from "@modules/budget-box/infra/inputs/delete-budget-box.input";

@Resolver(() => BudgetBoxType)
export class BudgetBoxResolver {
Expand Down Expand Up @@ -115,6 +116,19 @@ export class BudgetBoxResolver {

return isSuccess;
}

@Mutation(() => Boolean)
@UseGuards(JwtAuthGuard)
async deleteBudgetBox (
@Args(DeleteBudgetBoxInput.name) args: DeleteBudgetBoxInput,
@GetUserId() userId: string
): Promise<boolean> {
const isSuccess = true;

await this.budgetBoxService.deleteBudgetBox({ ...args, userId });

return isSuccess;
}
}

export default BudgetBoxResolver;
5 changes: 5 additions & 0 deletions src/types/schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ type Mutation {
changeReasonDescription(ChangeReasonDescriptionBoxInput: ChangeReasonDescriptionBoxInput!): Boolean!
changeBudgetPercentage(ChangeBudgetBoxPercentageInput: ChangeBudgetBoxPercentageInput!): Boolean!
changeBudgetName(ChangeBudgetBoxNameInput: ChangeBudgetBoxNameInput!): Boolean!
deleteBudgetBox(DeleteBudgetBoxInput: DeleteBudgetBoxInput!): Boolean!
percentageCapitalInflowPosting(PercentageCapitalInflowPostingInput: PercentageCapitalInflowPostingInput!): Boolean!
postingToBenefit(PostingToBenefitInput: PostingToBenefitInput!): Boolean!
createExpense(CreateExpenseInput: CreateExpenseInput!): Boolean!
Expand Down Expand Up @@ -185,6 +186,10 @@ input ChangeBudgetBoxNameInput {
description: String!
}

input DeleteBudgetBoxInput {
budgetBoxId: String!
}

input PercentageCapitalInflowPostingInput {
total: Float!
reason: String!
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.build.tsbuildinfo

Large diffs are not rendered by default.

0 comments on commit ed08dc6

Please sign in to comment.