Skip to content

Commit ed08dc6

Browse files
Merge pull request #107 from 4lessandrodev/feat/delete-budget-box
test(application): added tests to delete budget box use case
2 parents 2f28d92 + 53a22a8 commit ed08dc6

File tree

14 files changed

+253
-6
lines changed

14 files changed

+253
-6
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export interface DeleteBudgetBoxDto {
2+
userId: string;
3+
budgetBoxId: string;
4+
}
5+
6+
export default DeleteBudgetBoxDto;
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
2+
import { IBudgetBoxRepository } from '@modules/budget-box/domain/interfaces/budget-box.repository.interface';
3+
import budgetBoxMockRepo from '@modules/budget-box/application/mocks/budget-box-repo.mock';
4+
import DeleteBudgetBoxUseCase from './delete-budget-box.use-case';
5+
import { BudgetBoxMock } from '@modules/budget-box/domain/tests/mock/budget-box.mock';
6+
import { CURRENCY } from '@config/env';
7+
8+
describe('delete-budget-box.use-case', () => {
9+
10+
let repo: IBudgetBoxRepository;
11+
const budgetBoxMock = new BudgetBoxMock();
12+
13+
beforeEach(() => {
14+
repo = budgetBoxMockRepo;
15+
jest.spyOn(repo, 'findOne').mockClear();
16+
});
17+
18+
it('should execute delete-budget-box use case with success', async () => {
19+
20+
const aggregate = budgetBoxMock.domain({
21+
balanceAvailable: {
22+
currency: CURRENCY,
23+
value: 0
24+
}
25+
}).getResult();
26+
27+
jest.spyOn(repo, 'findOne').mockResolvedValueOnce(aggregate);
28+
29+
const useCase = new DeleteBudgetBoxUseCase(repo);
30+
const useCaseSpy = jest.spyOn(useCase, 'execute');
31+
32+
const dto = { userId: 'valid_id', budgetBoxId: 'valid_id' };
33+
34+
const result = await useCase.execute(dto);
35+
36+
expect(aggregate.domainEvents).toHaveLength(1);
37+
38+
expect(result.isSuccess).toBeTruthy();
39+
expect(useCaseSpy).toHaveBeenCalledWith(dto);
40+
});
41+
42+
it('should fails if budget box does not exists', async () => {
43+
44+
jest.spyOn(repo, 'findOne').mockResolvedValueOnce(null);
45+
46+
const useCase = new DeleteBudgetBoxUseCase(repo);
47+
const saveSpy = jest.spyOn(repo, 'save');
48+
49+
const dto = { userId: 'valid_id', budgetBoxId: 'valid_id' };
50+
51+
const result = await useCase.execute(dto);
52+
53+
expect(result.isFailure).toBeTruthy();
54+
expect(result.error).toBe('Budget Box Does Not Exists');
55+
expect(saveSpy).not.toHaveBeenCalled();
56+
});
57+
58+
it('should fails if budget box has balance', async () => {
59+
60+
const aggregate = budgetBoxMock.domain({
61+
balanceAvailable: {
62+
currency: CURRENCY,
63+
value: 1
64+
}
65+
}).getResult();
66+
67+
jest.spyOn(repo, 'findOne').mockResolvedValueOnce(aggregate);
68+
69+
const useCase = new DeleteBudgetBoxUseCase(repo);
70+
const saveSpy = jest.spyOn(repo, 'save');
71+
72+
const dto = { userId: 'valid_id', budgetBoxId: 'valid_id' };
73+
74+
const result = await useCase.execute(dto);
75+
76+
expect(result.isFailure).toBeTruthy();
77+
expect(result.error).toBe('The budget box must have a zero balance');
78+
expect(saveSpy).not.toHaveBeenCalledWith(dto);
79+
});
80+
81+
it('should fails if repository throws', async () => {
82+
83+
jest.spyOn(repo, 'findOne').mockImplementationOnce(async () => {
84+
throw new Error("error");
85+
});
86+
87+
const useCase = new DeleteBudgetBoxUseCase(repo);
88+
const saveSpy = jest.spyOn(repo, 'save');
89+
90+
const dto = { userId: 'valid_id', budgetBoxId: 'valid_id' };
91+
92+
const result = await useCase.execute(dto);
93+
94+
expect(result.isFailure).toBeTruthy();
95+
expect(result.error).toBe('Internal Server Error on Delete Budget BoxUse Case');
96+
expect(saveSpy).not.toHaveBeenCalled();
97+
});
98+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { IBudgetBoxRepository } from '@modules/budget-box/domain/interfaces/budget-box.repository.interface';
2+
import { Inject } from '@nestjs/common';
3+
import { IUseCase, Result } from 'types-ddd';
4+
import DeleteBudgetBoxDto from './delete-budget-box.dto';
5+
6+
export class DeleteBudgetBoxUseCase implements IUseCase<DeleteBudgetBoxDto, Result<void, string>>{
7+
constructor (
8+
@Inject('BudgetBoxRepository')
9+
private readonly budgetBoxRepo: IBudgetBoxRepository
10+
) { }
11+
12+
async execute ({ budgetBoxId: id, userId: ownerId }: DeleteBudgetBoxDto): Promise<Result<void, string>> {
13+
try {
14+
15+
const budgetBoxOrNull = await this.budgetBoxRepo.findOne({ id, ownerId });
16+
17+
if (!budgetBoxOrNull) {
18+
return Result.fail('Budget Box Does Not Exists', 'NOT_FOUND');
19+
}
20+
21+
const budgetBox = budgetBoxOrNull;
22+
23+
const hasNotBalance = budgetBox.balanceAvailable.isEqualTo(0);
24+
25+
if (!hasNotBalance) {
26+
return Result.fail('The budget box must have a zero balance', 'CONFLICT');
27+
}
28+
29+
budgetBox.delete();
30+
31+
await this.budgetBoxRepo.delete({ id });
32+
33+
return Result.success();
34+
35+
} catch (error) {
36+
return Result.fail('Internal Server Error on Delete Budget BoxUse Case', 'INTERNAL_SERVER_ERROR');
37+
}
38+
}
39+
}
40+
41+
export default DeleteBudgetBoxUseCase;

src/modules/budget-box/domain/budget-box.aggregate.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
PercentageValueObject
88
} from './percentage.value-object';
99
import ReasonDescriptionValueObject from './reason-description.value-object';
10-
10+
import BudgetBoxDeletedEvent from './events/budget-box-deleted.event';
1111

1212
export interface BudgetAggregateProps extends BaseDomainEntity {
1313
ownerId: DomainId;
@@ -115,6 +115,12 @@ export class BudgetBoxAggregate extends AggregateRoot<BudgetAggregateProps> {
115115
return exists;
116116
}
117117

118+
delete (): void {
119+
this.props.deletedAt = new Date();
120+
this.props.isDeleted = true;
121+
this.addDomainEvent(new BudgetBoxDeletedEvent(this));
122+
}
123+
118124
public static create (
119125
props: BudgetAggregateProps
120126
): Result<BudgetBoxAggregate> {
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { IDomainEvent, UniqueEntityID } from "types-ddd";
2+
import BudgetBoxAggregate from "../budget-box.aggregate";
3+
4+
export class BudgetBoxDeletedEvent implements IDomainEvent {
5+
public dateTimeOccurred: Date;
6+
public budgetBox: BudgetBoxAggregate;
7+
8+
constructor (budgetBox: BudgetBoxAggregate) {
9+
this.budgetBox = budgetBox;
10+
this.dateTimeOccurred = new Date();
11+
}
12+
13+
getAggregateId (): UniqueEntityID {
14+
return this.budgetBox.id.value;
15+
}
16+
17+
}
18+
19+
export default BudgetBoxDeletedEvent;
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { DomainEvents, IHandle, Logger } from "types-ddd";
2+
import BudgetBoxDeletedEvent from "../events/budget-box-deleted.event";
3+
4+
export class AfterBudgetBoxDeleted implements IHandle<BudgetBoxDeletedEvent>{
5+
constructor () {
6+
this.setupSubscriptions();
7+
}
8+
9+
setupSubscriptions (): void {
10+
DomainEvents.register(
11+
(event) => this.dispatch(Object.assign(event)),
12+
BudgetBoxDeletedEvent.name
13+
);
14+
}
15+
16+
async dispatch (event: BudgetBoxDeletedEvent): Promise<void> {
17+
Logger.info(`budget box deleted: ${event.budgetBox.id.uid}`);
18+
}
19+
20+
}
21+
22+
export default AfterBudgetBoxDeleted;

src/modules/budget-box/infra/budget-box.module.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import ChangeBudgetBoxPercentageUseCase from "@modules/budget-box/application/us
1919
import ChangeBudgetBoxNameUseCase from "@modules/budget-box/application/use-cases/change-budget-box-name/change-budget-box-name.use-case";
2020
import CanAllocatePercentageToBudgetBoxDomainService from "@modules/budget-box/domain/services/can-allocate-percentage-to-budget-box.domain-service";
2121
import CanChangeBudgetBoxPercentageDomainService from "@modules/budget-box/domain/services/can-change-budget-box-percentage.domain-service";
22+
import AfterBudgetBoxDeleted from "@modules/budget-box/domain/subscription/after-budget-box-deleted.subscription";
23+
import DeleteBudgetBoxUseCase from "@modules/budget-box/application/use-cases/delete-budget-box/delete-budget-box.use-case";
2224

2325
@Module({
2426
imports: [
@@ -51,7 +53,9 @@ import CanChangeBudgetBoxPercentageDomainService from "@modules/budget-box/domai
5153
useClass: BudgetBoxQueryService
5254
},
5355
CanAllocatePercentageToBudgetBoxDomainService,
54-
CanChangeBudgetBoxPercentageDomainService
56+
CanChangeBudgetBoxPercentageDomainService,
57+
DeleteBudgetBoxUseCase,
58+
AfterBudgetBoxDeleted
5559
],
5660
exports: []
5761
})

src/modules/budget-box/infra/budget-box.service.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ import ChangeBudgetBoxNameUseCase from "@modules/budget-box/application/use-case
1919
import ChangeBudgetBoxNameDto from "@modules/budget-box/application/use-cases/change-budget-box-name/change-budget-box-name.dto";
2020
import CanAllocatePercentageToBudgetBoxDomainService from "@modules/budget-box/domain/services/can-allocate-percentage-to-budget-box.domain-service";
2121
import CanChangeBudgetBoxPercentageDomainService from "@modules/budget-box/domain/services/can-change-budget-box-percentage.domain-service";
22+
import DeleteBudgetBoxUseCase from "../application/use-cases/delete-budget-box/delete-budget-box.use-case";
23+
import DeleteBudgetBoxDto from "../application/use-cases/delete-budget-box/delete-budget-box.dto";
2224

2325
@Injectable()
2426
export class BudgetBoxService {
@@ -51,7 +53,10 @@ export class BudgetBoxService {
5153
private readonly canChangeBudgetBoxPercentageDomainService: CanChangeBudgetBoxPercentageDomainService,
5254

5355
@Inject(ChangeBudgetBoxNameUseCase)
54-
private readonly changeBudgetBoxNameUseCase: ChangeBudgetBoxNameUseCase
56+
private readonly changeBudgetBoxNameUseCase: ChangeBudgetBoxNameUseCase,
57+
58+
@Inject(DeleteBudgetBoxUseCase)
59+
private readonly deleteBudgetBoxUseCase: DeleteBudgetBoxUseCase
5560
) { }
5661
async createBudgetBox (dto: CreateBudgetBoxDto): Promise<void>{
5762

@@ -102,6 +107,11 @@ export class BudgetBoxService {
102107
const result = await this.changeBudgetBoxNameUseCase.execute(dto);
103108
CheckResultInterceptor(result);
104109
}
110+
111+
async deleteBudgetBox (dto: DeleteBudgetBoxDto): Promise<void> {
112+
const result = await this.deleteBudgetBoxUseCase.execute(dto);
113+
CheckResultInterceptor(result);
114+
}
105115
}
106116

107117
export default BudgetBoxService;

src/modules/budget-box/infra/entities/budget-box.schema.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { IBudgetBox, ICurrency, IReason } from "@shared/index";
22
import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose";
33
import { Document } from "mongoose";
4+
import { DomainEvents, DomainId } from "types-ddd";
45

56
export type BudgetBoxDocument = BudgetBox & Document;
67

@@ -36,3 +37,9 @@ export class BudgetBox implements IBudgetBox {
3637
}
3738

3839
export const BudgetBoxSchema = SchemaFactory.createForClass(BudgetBox);
40+
41+
// calls domain events
42+
BudgetBoxSchema.post('remove', function (doc: IBudgetBox) {
43+
const id = DomainId.create(doc.id);
44+
DomainEvents.dispatchEventsForAggregate(id.value);
45+
});
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { Field, InputType } from "@nestjs/graphql";
2+
3+
@InputType()
4+
export class DeleteBudgetBoxInput {
5+
@Field(() => String)
6+
budgetBoxId!: string;
7+
}
8+
9+
export default DeleteBudgetBoxInput;

0 commit comments

Comments
 (0)