Skip to content

Commit 0e9de0f

Browse files
authored
Merge pull request #4 from Axel77g/improve-test-coverage-domain
Improve test coverage domain
2 parents c9ae9fe + 9b53544 commit 0e9de0f

File tree

26 files changed

+752
-80
lines changed

26 files changed

+752
-80
lines changed

backend/jest.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ module.exports = {
1111
"^@application/(.*)$": "<rootDir>/src/application/$1",
1212
"^@shared/(.*)$": "<rootDir>/src/shared/$1",
1313
"^@expressApp/(.*)$": "<rootDir>/src/infrastructure/frameworks/express/$1",
14+
"^@tests/(.*)$": "<rootDir>/src/tests/$1",
1415
},
1516
testPathIgnorePatterns: ["/node_modules/", "/dist/"],
1617
};

backend/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
"@infrastructure": "dist/infrastructure",
4141
"@application": "dist/application",
4242
"@shared": "dist/shared",
43-
"@infra_express": "dist/infrastructure/frameworks/express"
43+
"@infra_express": "dist/infrastructure/frameworks/express",
44+
"@tests": "dist/tests"
4445
}
4546
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {Dealer} from "@domain/inventoryManagement/entities/Dealer";
2+
import {ApplicationException} from "@shared/ApplicationException";
3+
import {assertError, generateAddressDTO, generateDealerDTO} from "@tests/Utils";
4+
import {Siret} from "@domain/shared/value-object/Siret";
5+
import {Address} from "@domain/shared/value-object/Address";
6+
import {RegisterDealerEvent} from "@domain/inventoryManagement/events/RegisterDealerEvent";
7+
import {UnregisterDealerEvent} from "@domain/inventoryManagement/events/UnregisterDealerEvent";
8+
9+
describe('Dealer.fromObject', () => {
10+
it('should return a Dealer when provided with a valid DealerDTO', () => {
11+
const dto = generateDealerDTO();
12+
const dealer = Dealer.fromObject(dto);
13+
expect(dealer).toBeInstanceOf(Dealer);
14+
});
15+
16+
it('should return an ApplicationException when SIRET is invalid', () => {
17+
const dto = generateDealerDTO({ siret: "INVALID_SIRET" });
18+
const dealer = Dealer.fromObject(dto) as ApplicationException;
19+
expect(dealer).toBeInstanceOf(ApplicationException);
20+
assertError(dealer, Siret.errors.SIRET_NOT_VALID);
21+
});
22+
23+
it('should return an ApplicationException when Address is invalid', () => {
24+
const dto = generateDealerDTO({
25+
address: generateAddressDTO({ country :"France", postalCode: "INVALID_POSTAL_CODE" })
26+
});
27+
const dealer = Dealer.fromObject(dto) as ApplicationException;
28+
expect(dealer).toBeInstanceOf(ApplicationException);
29+
assertError(dealer, Address.errors.COUNTRY_CODE_NOT_VALID);
30+
});
31+
});
32+
33+
describe('Dealer.create', () => {
34+
it('should create a Dealer when provided with valid inputs', () => {
35+
const siret = Siret.create("12345678901234") as Siret;
36+
const address = Address.create(generateAddressDTO()) as Address;
37+
const dealer = Dealer.create({
38+
siret,
39+
name: "Valid Dealer",
40+
address,
41+
phoneNumber: "0123456789"
42+
});
43+
44+
expect(dealer).toBeInstanceOf(Dealer);
45+
expect(dealer.name).toBe("Valid Dealer");
46+
expect(dealer.phoneNumber).toBe("0123456789");
47+
});
48+
49+
});
50+
51+
describe('Dealer.registerEvent', () => {
52+
it('should return a RegisterDealerEvent with the correct properties', () => {
53+
const siret = Siret.create("12345678901234") as Siret;
54+
const address = Address.create(generateAddressDTO()) as Address;
55+
const dealer = Dealer.create({
56+
siret,
57+
name: "Dealer for Event",
58+
address,
59+
phoneNumber: "0123456789"
60+
});
61+
62+
const event = dealer.registerEvent();
63+
expect(event).toBeInstanceOf(RegisterDealerEvent);
64+
expect(event.payload.siret).toBe(siret.getValue());
65+
expect(event.payload.name).toBe("Dealer for Event");
66+
expect(event.payload.address).toEqual(address);
67+
expect(event.payload.phoneNumber).toBe("0123456789");
68+
});
69+
});
70+
71+
describe('Dealer.unregisterEvent', () => {
72+
it('should return an UnregisterDealerEvent with the correct SIRET', () => {
73+
const siret = Siret.create("12345678901234") as Siret;
74+
const address = Address.create(generateAddressDTO()) as Address;
75+
const dealer = Dealer.create({
76+
siret,
77+
name: "Dealer for Event",
78+
address,
79+
phoneNumber: "0123456789"
80+
});
81+
82+
const event = dealer.unregisterEvent();
83+
expect(event).toBeInstanceOf(UnregisterDealerEvent);
84+
expect(event.payload.siret).toBe(siret.getValue());
85+
});
86+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { InventorySparePart } from "./InventorySparePart";
2+
import { ApplicationException } from "@shared/ApplicationException";
3+
import {assertError, generateInventorySparePartDTO} from "@tests/Utils";
4+
5+
describe("InventorySparePart", () => {
6+
describe("create", () => {
7+
it("should create an InventorySparePart when provided with valid data", () => {
8+
const dto = generateInventorySparePartDTO();
9+
const sparePart = InventorySparePart.create(dto) as InventorySparePart;
10+
expect(sparePart).toBeInstanceOf(InventorySparePart);
11+
expect(sparePart.reference).toBe(dto.reference);
12+
expect(sparePart.name).toBe(dto.name);
13+
});
14+
15+
it("should return an ApplicationException when name is too short", () => {
16+
const dto = generateInventorySparePartDTO({ name: "A" }); // Nom trop court
17+
const sparePart = InventorySparePart.create(dto) as ApplicationException;
18+
expect(sparePart).toBeInstanceOf(ApplicationException);
19+
assertError(sparePart, InventorySparePart.errors.NAME_TOO_SHORT)
20+
});
21+
22+
it("should return an ApplicationException when reference is too short", () => {
23+
const dto = generateInventorySparePartDTO({ reference: "ABC" }); // Référence trop courte
24+
const sparePart = InventorySparePart.create(dto) as ApplicationException;
25+
expect(sparePart).toBeInstanceOf(ApplicationException);
26+
assertError(sparePart, InventorySparePart.errors.REFERENCE_TOO_SHORT)
27+
});
28+
});
29+
30+
describe("fromObject", () => {
31+
it("should create an InventorySparePart from a valid DTO", () => {
32+
const dto = generateInventorySparePartDTO();
33+
const sparePart = InventorySparePart.fromObject(dto);
34+
expect(sparePart).toBeInstanceOf(InventorySparePart);
35+
});
36+
37+
it("should return an ApplicationException for an invalid DTO", () => {
38+
const dto = generateInventorySparePartDTO({ reference: "123", name: "" }); // Référence et nom invalides
39+
const sparePart = InventorySparePart.fromObject(dto);
40+
expect(sparePart).toBeInstanceOf(ApplicationException);
41+
});
42+
});
43+
44+
describe("setName", () => {
45+
it("should create a new InventorySparePart with an updated name", () => {
46+
const dto = generateInventorySparePartDTO();
47+
const sparePart = InventorySparePart.create(dto) as InventorySparePart;
48+
const updatedSparePart = sparePart.setName("Updated Name");
49+
expect(updatedSparePart).not.toBe(sparePart); // Nouvelle instance
50+
expect(updatedSparePart.name).toBe("Updated Name");
51+
});
52+
});
53+
54+
describe("upsertEvent", () => {
55+
it("should create an UpsertInventorySparePartEvent with the correct data", () => {
56+
const dto = generateInventorySparePartDTO();
57+
const sparePart = InventorySparePart.create(dto) as InventorySparePart;
58+
const event = sparePart.upsertEvent();
59+
expect(event).toBeDefined();
60+
expect(event.payload.reference).toBe(sparePart.reference);
61+
expect(event.payload.name).toBe(sparePart.name);
62+
});
63+
});
64+
});

backend/src/domain/inventoryManagement/entities/InventorySparePart.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ export interface InventorySparePartDTO {
99
export class InventorySparePart{
1010
public static readonly REFERENCE_MIN_LENGTH = 5;
1111
public static readonly NAME_MIN_LENGTH = 2;
12+
static errors = {
13+
REFERENCE_TOO_SHORT: new ApplicationException("InventorySparePart.ReferenceTooShort",`Reference must be at least ${InventorySparePart.REFERENCE_MIN_LENGTH} characters long`),
14+
NAME_TOO_SHORT: new ApplicationException("InventorySparePart.NameTooShort",`Name must be at least ${InventorySparePart.NAME_MIN_LENGTH} characters long`),
15+
}
16+
1217

1318
private constructor(
1419
public readonly reference: string,
@@ -25,8 +30,8 @@ export class InventorySparePart{
2530
}
2631

2732
static create(sparePart: InventorySparePartDTO): InventorySparePart | ApplicationException {
28-
if(sparePart.name.length < this.NAME_MIN_LENGTH) return new ApplicationException("InventorySparePart.NameTooShort",`Name must be at least ${InventorySparePart.NAME_MIN_LENGTH} characters long`);
29-
if(sparePart.reference.length < this.REFERENCE_MIN_LENGTH) return new ApplicationException("InventorySparePart.ReferenceTooShort",`Reference must be at least ${InventorySparePart.REFERENCE_MIN_LENGTH} characters long`);
33+
if(sparePart.name.length < this.NAME_MIN_LENGTH) return InventorySparePart.errors.NAME_TOO_SHORT
34+
if(sparePart.reference.length < this.REFERENCE_MIN_LENGTH) return InventorySparePart.errors.REFERENCE_TOO_SHORT
3035
return new InventorySparePart(sparePart.reference, sparePart.name);
3136
}
3237

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import {assertError, generateOrderDTO, generateOrderLineDTOs, generateSiret} from "@tests/Utils";
2+
import {ApplicationException} from "@shared/ApplicationException";
3+
import {Order} from "@domain/inventoryManagement/entities/Order";
4+
import {OrderStatusEnum} from "@domain/inventoryManagement/enums/OrderStatusEnum";
5+
import {OrderLine} from "@domain/inventoryManagement/value-object/OrderLine";
6+
import {InventorySparePart} from "@domain/inventoryManagement/entities/InventorySparePart";
7+
8+
describe("Order", () => {
9+
describe("create", () => {
10+
it("should create an Order with valid data", () => {
11+
const dto = generateOrderDTO();
12+
const order = Order.create({
13+
...dto,
14+
siret: generateSiret(),
15+
lines : [OrderLine.create({reference: "123", quantity: 1, unitPrice: 100}) as OrderLine]
16+
});
17+
expect(order).toBeInstanceOf(Order);
18+
});
19+
20+
it("should return an ApplicationException when orderedAt is after deliveredAt", () => {
21+
const dto = generateOrderDTO({
22+
orderedAt: new Date("2023-01-03"),
23+
deliveredAt: new Date("2023-01-01"),
24+
});
25+
const order = Order.create({
26+
...dto,
27+
siret: generateSiret()
28+
});
29+
expect(order).toBeInstanceOf(ApplicationException);
30+
assertError(order as ApplicationException, Order.errors.INVALID_DATE);
31+
});
32+
33+
it("should return an ApplicationException when no order lines are provided", () => {
34+
const dto = generateOrderDTO({ lines: [] });
35+
const order = Order.create({
36+
...dto,
37+
siret: generateSiret(),
38+
});
39+
expect(order).toBeInstanceOf(ApplicationException);
40+
assertError(order as ApplicationException, Order.errors.NO_LINES);
41+
});
42+
});
43+
44+
describe("fromObject", () => {
45+
it("should create an Order from a valid DTO", () => {
46+
const dto = generateOrderDTO();
47+
const order = Order.fromObject(dto);
48+
expect(order).toBeInstanceOf(Order);
49+
});
50+
51+
it("should return an ApplicationException for an invalid SIRET", () => {
52+
const dto = generateOrderDTO({ siret: "INVALID_SIRET" });
53+
const order = Order.fromObject(dto);
54+
expect(order).toBeInstanceOf(ApplicationException);
55+
});
56+
57+
it("should return an ApplicationException for an invalid OrderLine", () => {
58+
const dto = generateOrderDTO({
59+
lines: generateOrderLineDTOs(1, { reference: "" }), // Ligne invalide
60+
});
61+
const order = Order.fromObject(dto);
62+
expect(order).toBeInstanceOf(ApplicationException);
63+
assertError(order as ApplicationException, InventorySparePart.errors.REFERENCE_TOO_SHORT);
64+
});
65+
});
66+
67+
describe("applyStatus", () => {
68+
it("should change the status to COMPLETED if applicable", () => {
69+
const dto = generateOrderDTO();
70+
const order = Order.fromObject(dto) as Order;
71+
const updatedOrder = order.applyStatus(OrderStatusEnum.COMPLETED);
72+
expect(updatedOrder).toBeInstanceOf(Order);
73+
expect((updatedOrder as Order).status).toBe(OrderStatusEnum.COMPLETED);
74+
});
75+
76+
it("should return an ApplicationException if trying to COMPLETE a CANCELED order", () => {
77+
const dto = generateOrderDTO({ status: OrderStatusEnum.CANCELED });
78+
const order = Order.fromObject(dto) as Order;
79+
const result = order.applyStatus(OrderStatusEnum.COMPLETED);
80+
expect(result).toBeInstanceOf(ApplicationException);
81+
assertError(
82+
result as ApplicationException,
83+
Order.errors.CANNOT_COMPLETE_CANCELED_ORDER
84+
);
85+
});
86+
87+
it("should return an ApplicationException if trying to CANCEL a COMPLETED order", () => {
88+
const dto = generateOrderDTO({ status: OrderStatusEnum.COMPLETED });
89+
const order = Order.fromObject(dto) as Order;
90+
const result = order.applyStatus(OrderStatusEnum.CANCELED);
91+
expect(result).toBeInstanceOf(ApplicationException);
92+
assertError(
93+
result as ApplicationException,
94+
Order.errors.CANNOT_CANCEL_COMPLETED_ORDER
95+
);
96+
});
97+
});
98+
99+
describe("registerEvent", () => {
100+
it("should create a RegisterOrderEvent with the correct data", () => {
101+
const dto = generateOrderDTO();
102+
const order = Order.fromObject(dto) as Order;
103+
const event = order.registerEvent();
104+
expect(event).toBeDefined();
105+
expect(event.payload.orderId).toBe(order.orderId);
106+
expect(event.payload.lines).toBe(order.lines);
107+
});
108+
});
109+
110+
describe("updateStatusEvent", () => {
111+
it("should create an UpdateOrderStatusEvent with the correct data", () => {
112+
const dto = generateOrderDTO();
113+
const order = Order.fromObject(dto) as Order;
114+
const event = order.updateStatusEvent();
115+
expect(event).toBeDefined();
116+
expect(event.payload.orderId).toBe(order.orderId);
117+
expect(event.payload.status).toBe(order.status);
118+
});
119+
});
120+
});

backend/src/domain/inventoryManagement/entities/Order.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ export class Order {
2727
static errors = {
2828
CANNOT_COMPLETE_CANCELED_ORDER: new ApplicationException("Order","Cannot complete a cancelled order"),
2929
CANNOT_CANCEL_COMPLETED_ORDER: new ApplicationException("Order","Cannot cancel a completed order"),
30-
CANNOT_PROCESS_ORDER: new ApplicationException("Order","Cannot process a cancelled or a completed order")
30+
CANNOT_PROCESS_ORDER: new ApplicationException("Order","Cannot process a cancelled or a completed order"),
31+
INVALID_DATE : new ApplicationException("Order.InvalidDates","Ordered date must be before delivered date"),
32+
NO_LINES : new ApplicationException("Order.NoLines","Order must have at least one line")
3133
}
3234

3335
private constructor(
@@ -129,9 +131,9 @@ export class Order {
129131
lines: OrderLine[],
130132
status ?: OrderStatusEnum,
131133
statusHistory ?: OrderStatusHistory[]
132-
}){
133-
if(order.orderedAt > order.deliveredAt) return new ApplicationException("Order.InvalidDates","Ordered date must be before delivered date");
134-
if(order.lines.length === 0) return new ApplicationException("Order.NoLines","Order must have at least one line");
134+
}) : Order | ApplicationException {
135+
if(order.orderedAt > order.deliveredAt) return this.errors.INVALID_DATE;
136+
if(order.lines.length === 0) return this.errors.NO_LINES;
135137
return new Order(
136138
order.orderId || Order.generateID(),
137139
order.orderedAt,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import {OrderLine, OrderLineDTO} from "@domain/inventoryManagement/value-object/OrderLine";
2+
import {assertError, generateOrderLineDTO} from "@tests/Utils";
3+
import {ApplicationException} from "@shared/ApplicationException";
4+
import {InventorySparePart} from "@domain/inventoryManagement/entities/InventorySparePart";
5+
6+
describe("OrderLine", () => {
7+
describe("create", () => {
8+
it("should create an OrderLine with valid data", () => {
9+
const dto: OrderLineDTO = generateOrderLineDTO();
10+
const orderLine = OrderLine.create(dto);
11+
expect(orderLine).toBeInstanceOf(OrderLine);
12+
expect(orderLine).toEqual(
13+
expect.objectContaining({
14+
reference: dto.reference,
15+
quantity: dto.quantity,
16+
unitPrice: dto.unitPrice,
17+
})
18+
);
19+
});
20+
21+
it("should return an ApplicationException when the reference is invalid", () => {
22+
const dto: OrderLineDTO = generateOrderLineDTO({ reference: "X" }); // Référence invalide
23+
const orderLine = OrderLine.create(dto);
24+
expect(orderLine).toBeInstanceOf(ApplicationException);
25+
assertError(orderLine as ApplicationException, InventorySparePart.errors.REFERENCE_TOO_SHORT);
26+
});
27+
28+
it("should return an ApplicationException when quantity is <= 0", () => {
29+
const dto: OrderLineDTO = generateOrderLineDTO({ quantity: 0 }); // Quantité invalide
30+
const orderLine = OrderLine.create(dto);
31+
expect(orderLine).toBeInstanceOf(ApplicationException);
32+
assertError(orderLine as ApplicationException, OrderLine.errors.INVALID_QUANTITY);
33+
});
34+
});
35+
});

0 commit comments

Comments
 (0)