diff --git a/.changeset/few-masks-draw.md b/.changeset/few-masks-draw.md new file mode 100644 index 0000000000..4455745302 --- /dev/null +++ b/.changeset/few-masks-draw.md @@ -0,0 +1,5 @@ +--- +"@medusajs/medusa": patch +--- + +fix(medusa): Handle variant error during line item generation diff --git a/packages/medusa/src/services/__tests__/line-item.js b/packages/medusa/src/services/__tests__/line-item.js index 45e171af2e..57efa430a1 100644 --- a/packages/medusa/src/services/__tests__/line-item.js +++ b/packages/medusa/src/services/__tests__/line-item.js @@ -4,6 +4,9 @@ import LineItemService from "../line-item" import { PricingServiceMock } from "../__mocks__/pricing" import { ProductVariantServiceMock } from "../__mocks__/product-variant" import { RegionServiceMock } from "../__mocks__/region" + +const unknownVariantId = "unknown-variant" + ;[true, false].forEach((isTaxInclusiveEnabled) => { describe(`tax inclusive flag set to: ${isTaxInclusiveEnabled}`, () => { describe("LineItemService", () => { @@ -480,16 +483,21 @@ describe("LineItemService", () => { }) }) }) + describe("generate", () => { const lineItemRepository = MockRepository({ create: (data) => data, }) const cartRepository = MockRepository({ - findOne: () => - Promise.resolve({ - region_id: IdMap.getId("test-region"), - }), + findOne: ({ id }) => + Promise.resolve( + !id + ? null + : { + region_id: IdMap.getId("test-region"), + } + ), }) const regionService = { @@ -521,16 +529,22 @@ describe("LineItemService", () => { }, getRegionPrice: () => 100, list: jest.fn().mockImplementation(async (selector) => { - return (selector.id || []).map((id) => { - return { - id, - title: "Test variant", - product: { - title: "Test product", - thumbnail: "", - }, - } - }) + return (selector.id || []) + .map((id) => { + if (id === unknownVariantId) { + return null + } + + return { + id, + title: "Test variant", + product: { + title: "Test product", + thumbnail: "", + }, + } + }) + .filter(Boolean) }), } @@ -570,6 +584,48 @@ describe("LineItemService", () => { jest.clearAllMocks() }) + it("should not succeed to generate a line item if a variant id is not provided", async () => { + const err = await lineItemService + .generate( + [ + { + variantId: "", + quantity: 1, + }, + ], + { + region_id: IdMap.getId("test-region"), + } + ) + .catch((e) => e) + + expect(err).toBeDefined() + expect(err.message).toEqual( + "Unable to generate the line item because one or more required argument(s) are missing. Ensure a variant id is passed for each variant" + ) + }) + + it("should not succeed to generate a line item if a variant id is not found", async () => { + const err = await lineItemService + .generate( + [ + { + variantId: unknownVariantId, + quantity: 1, + }, + ], + { + region_id: IdMap.getId("test-region"), + } + ) + .catch((e) => e) + + expect(err).toBeDefined() + expect(err.message).toEqual( + "Unable to generate the line items, some variant has not been found: unknown-variant" + ) + }) + it("successfully create a line item with tax inclusive set to false", async () => { await lineItemService.generate( IdMap.getId("test-variant"), diff --git a/packages/medusa/src/services/line-item.ts b/packages/medusa/src/services/line-item.ts index 664c8a6a89..98b42b9494 100644 --- a/packages/medusa/src/services/line-item.ts +++ b/packages/medusa/src/services/line-item.ts @@ -210,6 +210,7 @@ class LineItemService extends TransactionBaseService { quantity ) + // Resolve data const data = isString(variantIdOrData) ? { variantId: variantIdOrData, @@ -235,6 +236,7 @@ class LineItemService extends TransactionBaseService { resolvedData.map((d) => [d.variantId, d]) ) + // Retrieve variants const variants = await this.productVariantService_.list( { id: resolvedData.map((d) => d.variantId), @@ -244,6 +246,23 @@ class LineItemService extends TransactionBaseService { } ) + // Validate that all variants has been found + const inputDataVariantId = new Set(resolvedData.map((d) => d.variantId)) + const foundVariants = new Set(variants.map((v) => v.id)) + const notFoundVariants = new Set( + [...inputDataVariantId].filter((x) => !foundVariants.has(x)) + ) + + if (notFoundVariants.size) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `Unable to generate the line items, some variant has not been found: ${[ + ...notFoundVariants, + ].join(", ")}` + ) + } + + // Prepare data to retrieve variant pricing const variantsMap = new Map() const variantsToCalculatePricingFor: { variantId: string @@ -277,6 +296,7 @@ class LineItemService extends TransactionBaseService { }) } + // Generate line items const generatedItems: LineItem[] = [] for (const variantData of resolvedData) { @@ -601,22 +621,46 @@ class LineItemService extends TransactionBaseService { regionIdOrContext: T extends string ? string : GenerateLineItemContext, quantity?: number ): void | never { + const errorMessage = + "Unable to generate the line item because one or more required argument(s) are missing" + if (isString(variantIdOrData)) { if (!quantity || !regionIdOrContext || !isString(regionIdOrContext)) { throw new MedusaError( - MedusaError.Types.UNEXPECTED_STATE, - "The generate method has been called with a variant id but one of the argument quantity or regionId is missing. Please, provide the variantId, quantity and regionId." + MedusaError.Types.INVALID_DATA, + `${errorMessage}. Ensure quantity, regionId, and variantId are passed` ) } - } else { - const resolvedContext = regionIdOrContext as GenerateLineItemContext - if (!resolvedContext.region_id) { + if (!variantIdOrData) { throw new MedusaError( - MedusaError.Types.UNEXPECTED_STATE, - "The generate method has been called with the data but the context is missing either region_id or region. Please provide at least one of region or region_id." + MedusaError.Types.INVALID_DATA, + `${errorMessage}. Ensure variant id is passed` ) } + return + } + + const resolvedContext = regionIdOrContext as GenerateLineItemContext + + if (!resolvedContext?.region_id) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `${errorMessage}. Ensure region or region_id are passed` + ) + } + + const variantsData = Array.isArray(variantIdOrData) + ? variantIdOrData + : [variantIdOrData] + + const hasMissingVariantId = variantsData.some((d) => !d?.variantId) + + if (hasMissingVariantId) { + throw new MedusaError( + MedusaError.Types.INVALID_DATA, + `${errorMessage}. Ensure a variant id is passed for each variant` + ) } } }