From d00ba72c84603c4b1b4ec5ce0228ed6238f9f3c7 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 15 Jul 2025 05:33:42 +0000 Subject: [PATCH 1/2] Implement addParcelToDelivery order update action with comprehensive tests Co-authored-by: mvantellingen <245297+mvantellingen@users.noreply.github.com> --- .changeset/shiny-moons-hear.md | 5 + src/repositories/order/actions.ts | 63 +++++++- src/services/order.test.ts | 245 ++++++++++++++++++++++++++++++ 3 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 .changeset/shiny-moons-hear.md diff --git a/.changeset/shiny-moons-hear.md b/.changeset/shiny-moons-hear.md new file mode 100644 index 00000000..db80561d --- /dev/null +++ b/.changeset/shiny-moons-hear.md @@ -0,0 +1,5 @@ +--- +"@labdigital/commercetools-mock": patch +--- + +Add missing addParcelToDelivery order update action diff --git a/src/repositories/order/actions.ts b/src/repositories/order/actions.ts index f2b0b7ef..5ba999f5 100644 --- a/src/repositories/order/actions.ts +++ b/src/repositories/order/actions.ts @@ -2,6 +2,7 @@ import type { CustomLineItemReturnItem, LineItemReturnItem, Order, + OrderAddParcelToDeliveryAction, OrderAddPaymentAction, OrderAddReturnInfoAction, OrderChangeOrderStateAction, @@ -22,6 +23,7 @@ import type { OrderTransitionStateAction, OrderUpdateAction, OrderUpdateSyncInfoAction, + Parcel, ReturnInfo, State, Store, @@ -31,7 +33,7 @@ import { getBaseResourceProperties } from "~src/helpers"; import type { Writable } from "~src/types"; import type { RepositoryContext, UpdateHandlerInterface } from "../abstract"; import { AbstractUpdateHandler } from "../abstract"; -import { createAddress } from "../helpers"; +import { createAddress, createCustomFields } from "../helpers"; export class OrderUpdateHandler extends AbstractUpdateHandler @@ -62,6 +64,65 @@ export class OrderUpdateHandler }); } + addParcelToDelivery( + context: RepositoryContext, + resource: Writable, + { + deliveryId, + deliveryKey, + parcelKey, + measurements, + trackingData, + items, + custom, + }: OrderAddParcelToDeliveryAction, + ) { + if (!resource.shippingInfo) { + throw new Error("Order has no shipping info"); + } + + if (!deliveryId && !deliveryKey) { + throw new Error("Either deliveryId or deliveryKey must be provided"); + } + + // Find the delivery by id or key + let targetDelivery = null; + for (const delivery of resource.shippingInfo.deliveries || []) { + if ( + (deliveryId && delivery.id === deliveryId) || + (deliveryKey && delivery.key === deliveryKey) + ) { + targetDelivery = delivery; + break; + } + } + + if (!targetDelivery) { + const identifier = deliveryId || deliveryKey; + throw new Error( + `Delivery with ${deliveryId ? "id" : "key"} '${identifier}' not found`, + ); + } + + // Create the new parcel + const newParcel: Parcel = { + ...getBaseResourceProperties(), + ...(parcelKey && { key: parcelKey }), + ...(measurements && { measurements }), + ...(trackingData && { trackingData }), + items: items || [], + ...(custom && { + custom: createCustomFields(custom, context.projectKey, this._storage), + }), + }; + + // Add the parcel to the delivery + if (!targetDelivery.parcels) { + targetDelivery.parcels = []; + } + targetDelivery.parcels.push(newParcel); + } + addReturnInfo( context: RepositoryContext, resource: Writable, diff --git a/src/services/order.test.ts b/src/services/order.test.ts index 009ba296..c06adf94 100644 --- a/src/services/order.test.ts +++ b/src/services/order.test.ts @@ -794,6 +794,251 @@ describe("Order Update Actions", () => { }, ]); }); + + test("addParcelToDelivery", async () => { + const order: Order = { + ...getBaseResourceProperties(), + customLineItems: [], + lastMessageSequenceNumber: 0, + lineItems: [], + orderNumber: "1391", + orderState: "Open", + origin: "Customer", + refusedGifts: [], + shippingInfo: { + shippingMethodName: "Standard delivery", + price: { + type: "centPrecision", + currencyCode: "EUR", + centAmount: 500, + fractionDigits: 2, + }, + shippingRate: { + price: { + type: "centPrecision", + currencyCode: "EUR", + centAmount: 500, + fractionDigits: 2, + }, + tiers: [], + }, + deliveries: [ + { + id: "delivery-1", + key: "DELIVERY-001", + createdAt: "2024-01-01T10:00:00.000Z", + items: [ + { + id: "line-item-1", + quantity: 2, + }, + ], + parcels: [], + }, + ], + shippingMethodState: "MatchesCart", + }, + shipping: [], + shippingMode: "Single", + syncInfo: [], + totalPrice: { + type: "centPrecision", + fractionDigits: 2, + centAmount: 1500, + currencyCode: "EUR", + }, + }; + ctMock.project("dummy").add("order", order); + + const response = await supertest(ctMock.app).get( + `/dummy/orders/order-number=${order.orderNumber}`, + ); + expect(response.status).toBe(200); + + // Test adding parcel by deliveryId + const updateResponse = await supertest(ctMock.app) + .post(`/dummy/orders/${response.body.id}`) + .send({ + version: 0, + actions: [ + { + action: "addParcelToDelivery", + deliveryId: "delivery-1", + parcelKey: "parcel-001", + measurements: { + heightInMillimeter: 100, + lengthInMillimeter: 200, + widthInMillimeter: 150, + weightInGram: 500, + }, + trackingData: { + trackingId: "TRACK123", + carrier: "DHL", + }, + items: [ + { + id: "line-item-1", + quantity: 1, + }, + ], + }, + ], + }); + expect(updateResponse.status).toBe(200); + expect(updateResponse.body.version).toBe(1); + + const delivery = updateResponse.body.shippingInfo.deliveries[0]; + expect(delivery.parcels).toHaveLength(1); + + const parcel = delivery.parcels[0]; + expect(parcel.key).toBe("parcel-001"); + expect(parcel.measurements).toMatchObject({ + heightInMillimeter: 100, + lengthInMillimeter: 200, + widthInMillimeter: 150, + weightInGram: 500, + }); + expect(parcel.trackingData).toMatchObject({ + trackingId: "TRACK123", + carrier: "DHL", + }); + expect(parcel.items).toMatchObject([ + { + id: "line-item-1", + quantity: 1, + }, + ]); + + // Test adding parcel by deliveryKey + const updateResponse2 = await supertest(ctMock.app) + .post(`/dummy/orders/${response.body.id}`) + .send({ + version: 1, + actions: [ + { + action: "addParcelToDelivery", + deliveryKey: "DELIVERY-001", + parcelKey: "parcel-002", + items: [ + { + id: "line-item-1", + quantity: 1, + }, + ], + }, + ], + }); + expect(updateResponse2.status).toBe(200); + expect(updateResponse2.body.version).toBe(2); + + const delivery2 = updateResponse2.body.shippingInfo.deliveries[0]; + expect(delivery2.parcels).toHaveLength(2); + expect(delivery2.parcels[1].key).toBe("parcel-002"); + }); + + test("addParcelToDelivery - error cases", async () => { + const order: Order = { + ...getBaseResourceProperties(), + customLineItems: [], + lastMessageSequenceNumber: 0, + lineItems: [], + orderNumber: "1392", + orderState: "Open", + origin: "Customer", + refusedGifts: [], + shippingInfo: { + shippingMethodName: "Standard delivery", + price: { + type: "centPrecision", + currencyCode: "EUR", + centAmount: 500, + fractionDigits: 2, + }, + shippingRate: { + price: { + type: "centPrecision", + currencyCode: "EUR", + centAmount: 500, + fractionDigits: 2, + }, + tiers: [], + }, + deliveries: [ + { + id: "delivery-1", + key: "DELIVERY-001", + createdAt: "2024-01-01T10:00:00.000Z", + items: [], + parcels: [], + }, + ], + shippingMethodState: "MatchesCart", + }, + shipping: [], + shippingMode: "Single", + syncInfo: [], + totalPrice: { + type: "centPrecision", + fractionDigits: 2, + centAmount: 1500, + currencyCode: "EUR", + }, + }; + ctMock.project("dummy").add("order", order); + + const response = await supertest(ctMock.app).get( + `/dummy/orders/order-number=${order.orderNumber}`, + ); + expect(response.status).toBe(200); + + // Test error: no deliveryId or deliveryKey provided + const errorResponse1 = await supertest(ctMock.app) + .post(`/dummy/orders/${response.body.id}`) + .send({ + version: 0, + actions: [ + { + action: "addParcelToDelivery", + parcelKey: "parcel-001", + }, + ], + }); + expect(errorResponse1.status).toBe(500); + + // Test error: delivery not found + const errorResponse2 = await supertest(ctMock.app) + .post(`/dummy/orders/${response.body.id}`) + .send({ + version: 0, + actions: [ + { + action: "addParcelToDelivery", + deliveryId: "nonexistent-delivery", + parcelKey: "parcel-001", + }, + ], + }); + expect(errorResponse2.status).toBe(500); + }); + + test("addParcelToDelivery - order without shipping info", async () => { + assert(order, "order not created"); + + // Test error: order has no shipping info + const errorResponse = await supertest(ctMock.app) + .post(`/dummy/orders/${order.id}`) + .send({ + version: 1, + actions: [ + { + action: "addParcelToDelivery", + deliveryId: "delivery-1", + parcelKey: "parcel-001", + }, + ], + }); + expect(errorResponse.status).toBe(500); + }); }); describe("Order Import", () => { From 08ad0e7f7b224d2371f78ebb1e382c35193cc8cb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 15 Jul 2025 05:57:07 +0000 Subject: [PATCH 2/2] Fix error handling in addParcelToDelivery to use CommercetoolsError instead of regular Error Co-authored-by: mvantellingen <245297+mvantellingen@users.noreply.github.com> --- src/repositories/order/actions.ts | 38 +++++++++++++++++++++++++++---- src/services/order.test.ts | 6 ++--- 2 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/repositories/order/actions.ts b/src/repositories/order/actions.ts index 5ba999f5..952653fc 100644 --- a/src/repositories/order/actions.ts +++ b/src/repositories/order/actions.ts @@ -1,5 +1,6 @@ import type { CustomLineItemReturnItem, + InvalidInputError, LineItemReturnItem, Order, OrderAddParcelToDeliveryAction, @@ -29,6 +30,7 @@ import type { Store, SyncInfo, } from "@commercetools/platform-sdk"; +import { CommercetoolsError } from "~src/exceptions"; import { getBaseResourceProperties } from "~src/helpers"; import type { Writable } from "~src/types"; import type { RepositoryContext, UpdateHandlerInterface } from "../abstract"; @@ -78,11 +80,29 @@ export class OrderUpdateHandler }: OrderAddParcelToDeliveryAction, ) { if (!resource.shippingInfo) { - throw new Error("Order has no shipping info"); + throw new CommercetoolsError({ + code: "InvalidInput", + message: "Order has no shipping info", + errors: [ + { + code: "InvalidInput", + message: "Order has no shipping info", + }, + ], + }); } if (!deliveryId && !deliveryKey) { - throw new Error("Either deliveryId or deliveryKey must be provided"); + throw new CommercetoolsError({ + code: "InvalidInput", + message: "Either deliveryId or deliveryKey must be provided", + errors: [ + { + code: "InvalidInput", + message: "Either deliveryId or deliveryKey must be provided", + }, + ], + }); } // Find the delivery by id or key @@ -99,9 +119,17 @@ export class OrderUpdateHandler if (!targetDelivery) { const identifier = deliveryId || deliveryKey; - throw new Error( - `Delivery with ${deliveryId ? "id" : "key"} '${identifier}' not found`, - ); + const message = `Delivery with ${deliveryId ? "id" : "key"} '${identifier}' not found`; + throw new CommercetoolsError({ + code: "InvalidInput", + message, + errors: [ + { + code: "InvalidInput", + message, + }, + ], + }); } // Create the new parcel diff --git a/src/services/order.test.ts b/src/services/order.test.ts index c06adf94..6ecd8f11 100644 --- a/src/services/order.test.ts +++ b/src/services/order.test.ts @@ -1003,7 +1003,7 @@ describe("Order Update Actions", () => { }, ], }); - expect(errorResponse1.status).toBe(500); + expect(errorResponse1.status).toBe(400); // Test error: delivery not found const errorResponse2 = await supertest(ctMock.app) @@ -1018,7 +1018,7 @@ describe("Order Update Actions", () => { }, ], }); - expect(errorResponse2.status).toBe(500); + expect(errorResponse2.status).toBe(400); }); test("addParcelToDelivery - order without shipping info", async () => { @@ -1037,7 +1037,7 @@ describe("Order Update Actions", () => { }, ], }); - expect(errorResponse.status).toBe(500); + expect(errorResponse.status).toBe(400); }); });