From 67351b0b1dc57ff6ebefa4a45f5a8e9f5cef2a00 Mon Sep 17 00:00:00 2001 From: Jamal Soueidan Date: Sat, 25 May 2024 12:43:23 +0200 Subject: [PATCH] Refactor product add and update specs to use location helper functions - Replace manual location object creation with helper functions in product add and update tests - Add city tag generation based on location in product add and update tests - Include scheduleMetafieldId in product get service response - Update product update service to fetch locations from LocationModel and handle potential errors --- .../customer/controllers/product/add.spec.ts | 18 ++-- .../controllers/product/update.spec.ts | 17 +++- .../customer/services/product/add.spec.ts | 13 ++- .../customer/services/product/get.ts | 1 + .../customer/services/product/update.spec.ts | 9 ++ .../customer/services/product/update.ts | 89 +++++++++---------- src/library/jest/helpers/location.ts | 10 +-- 7 files changed, 95 insertions(+), 62 deletions(-) diff --git a/src/functions/customer/controllers/product/add.spec.ts b/src/functions/customer/controllers/product/add.spec.ts index 8231945f..099aaeea 100644 --- a/src/functions/customer/controllers/product/add.spec.ts +++ b/src/functions/customer/controllers/product/add.spec.ts @@ -6,9 +6,11 @@ import { createHttpRequest, } from "~/library/jest/azure"; -import mongoose from "mongoose"; -import { LocationOriginTypes, LocationTypes } from "~/functions/location"; import { createUser } from "~/library/jest/helpers"; +import { + createLocation, + getDumbLocationObject, +} from "~/library/jest/helpers/location"; import { createSchedule } from "~/library/jest/helpers/schedule"; import { shopifyAdmin } from "~/library/shopify"; import { GidFormat } from "~/library/zod"; @@ -126,14 +128,13 @@ describe("CustomerProductControllerAdd", () => { customerId, metafieldId: "gid://shopify/Metafield/533232", }); + const location = await createLocation({ customerId: user.customerId }); const locations = [ - { - metafieldId: "1", - location: new mongoose.Types.ObjectId(), - locationType: LocationTypes.DESTINATION, - originType: LocationOriginTypes.COMMERCIAL, - }, + getDumbLocationObject({ + ...location, + location: location._id, + }), ]; const body: CustomerProductControllerAddRequest["body"] = { @@ -167,6 +168,7 @@ describe("CustomerProductControllerAdd", () => { `product-${mockProduct.productDuplicate?.newProduct?.handle}`, `scheduleid-${newSchedule._id}`, `locationid-${body.locations[0].location}`, + `city-${location.city.replace(/ /g, "-").toLowerCase()}`, ]; const mockProductUpdate: ProductUpdateMutation = { diff --git a/src/functions/customer/controllers/product/update.spec.ts b/src/functions/customer/controllers/product/update.spec.ts index f692dcca..959847df 100644 --- a/src/functions/customer/controllers/product/update.spec.ts +++ b/src/functions/customer/controllers/product/update.spec.ts @@ -8,6 +8,10 @@ import { import { TimeUnit } from "~/functions/schedule"; import { createUser } from "~/library/jest/helpers"; +import { + createLocation, + getDumbLocationObject, +} from "~/library/jest/helpers/location"; import { getProductObject } from "~/library/jest/helpers/product"; import { createScheduleWithProducts } from "~/library/jest/helpers/schedule"; import { shopifyAdmin } from "~/library/shopify"; @@ -44,8 +48,18 @@ describe("CustomerProductControllerUpdate", () => { it("should be able to update product inside schedule", async () => { const user = await createUser({ customerId }); + const location = await createLocation({ customerId: user.customerId }); + + const product = getProductObject({ + productId, + locations: [ + getDumbLocationObject({ + ...location, + location: location._id, + }), + ], + }); - const product = getProductObject({ productId }); const newSchedule = await createScheduleWithProducts({ name: "adsasd", customerId, @@ -76,6 +90,7 @@ describe("CustomerProductControllerUpdate", () => { `product-${product.productHandle}`, `scheduleid-${newSchedule._id}`, `locationid-${product.locations[0].location}`, + `city-${location.city.replace(/ /g, "-").toLowerCase()}`, ], parentId: { id: "gid://shopify/Metafield/44429081510215", diff --git a/src/functions/customer/services/product/add.spec.ts b/src/functions/customer/services/product/add.spec.ts index 4fc9c514..d2caaac0 100644 --- a/src/functions/customer/services/product/add.spec.ts +++ b/src/functions/customer/services/product/add.spec.ts @@ -2,7 +2,10 @@ import mongoose from "mongoose"; import { LocationOriginTypes, LocationTypes } from "~/functions/location"; import { ScheduleProduct } from "~/functions/schedule"; import { createUser } from "~/library/jest/helpers"; -import { getDumbLocationObject } from "~/library/jest/helpers/location"; +import { + createLocation, + getDumbLocationObject, +} from "~/library/jest/helpers/location"; import { createSchedule } from "~/library/jest/helpers/schedule"; import { shopifyAdmin } from "~/library/shopify"; import { @@ -141,6 +144,13 @@ describe("CustomerProductServiceAdd", () => { it("should add a new product to the schedule", async () => { const customerId = 123; const user = await createUser({ customerId }); + const location = await createLocation({ customerId: user.customerId }); + productBody = { + ...productBody, + locations: [ + getDumbLocationObject({ ...location, location: location._id }), + ], + }; const newSchedule = await createSchedule({ name: "Test Schedule", @@ -160,6 +170,7 @@ describe("CustomerProductServiceAdd", () => { `product-${mockProduct.productDuplicate?.newProduct?.handle}`, `scheduleid-${newSchedule._id}`, `locationid-${productBody.locations[0].location}`, + `city-${location.city.replace(/ /g, "-").toLowerCase()}`, ]; const mockProductUpdate: ProductUpdateMutation = { diff --git a/src/functions/customer/services/product/get.ts b/src/functions/customer/services/product/get.ts index 6b09d48c..140f7124 100644 --- a/src/functions/customer/services/product/get.ts +++ b/src/functions/customer/services/product/get.ts @@ -46,5 +46,6 @@ export const CustomerProductServiceGet = async ( ...product, scheduleId: schedule._id, scheduleName: schedule.name, + scheduleMetafieldId: schedule.metafieldId, }; }; diff --git a/src/functions/customer/services/product/update.spec.ts b/src/functions/customer/services/product/update.spec.ts index 77661049..9a6da65b 100644 --- a/src/functions/customer/services/product/update.spec.ts +++ b/src/functions/customer/services/product/update.spec.ts @@ -1,5 +1,9 @@ import { TimeUnit } from "~/functions/schedule"; import { createUser } from "~/library/jest/helpers"; +import { + createLocation, + getDumbLocationObject, +} from "~/library/jest/helpers/location"; import { getProductObject } from "~/library/jest/helpers/product"; import { createScheduleWithProducts } from "~/library/jest/helpers/schedule"; import { shopifyAdmin } from "~/library/shopify"; @@ -132,6 +136,7 @@ describe("CustomerProductServiceUpdate", () => { it("should update an existing product in the schedule", async () => { const user = await createUser({ customerId }); + const location = await createLocation({ customerId: user.customerId }); const newSchedule = await createScheduleWithProducts({ name, @@ -139,6 +144,9 @@ describe("CustomerProductServiceUpdate", () => { metafieldId: "gid://shopify/Metafield/533232", products: [ getProductObject({ + locations: [ + getDumbLocationObject({ ...location, location: location._id }), + ], parentId: GidFormat.parse( mockProductUpdate.productUpdate?.product?.parentId?.value ), @@ -225,6 +233,7 @@ describe("CustomerProductServiceUpdate", () => { `product-${product.productHandle}`, `scheduleid-${newSchedule._id}`, `locationid-${product.locations[0].location}`, + `city-${location.city.replace(/ /g, "-").toLowerCase()}`, ]; expect(shopifyAdmin.request).toHaveBeenCalledTimes(2); diff --git a/src/functions/customer/services/product/update.ts b/src/functions/customer/services/product/update.ts index 03b5c72e..1d040778 100644 --- a/src/functions/customer/services/product/update.ts +++ b/src/functions/customer/services/product/update.ts @@ -1,9 +1,11 @@ import { Schedule, ScheduleModel, ScheduleProduct } from "~/functions/schedule"; -import { UserModel } from "~/functions/user"; import { NotFoundError } from "~/library/handler"; import { shopifyAdmin } from "~/library/shopify"; import { MetafieldInput } from "~/types/admin.types"; +import { CustomerServiceGet } from "../customer/get"; +import { LocationModel } from "./../../../location/location.model"; import { PRODUCT_FRAGMENT } from "./add"; +import { CustomerProductServiceGet } from "./get"; export type CustomerProductServiceUpdate = { customerId: Schedule["customerId"]; @@ -18,46 +20,15 @@ export const CustomerProductServiceUpdate = async ( { customerId, productId }: CustomerProductServiceUpdate, body: CustomerProductServiceUpdateBody ) => { - const user = await UserModel.findOne({ + const user = await CustomerServiceGet({ customerId, - }) - .orFail( - new NotFoundError([ - { - path: ["customerId"], - message: "NOT_FOUND", - code: "custom", - }, - ]) - ) - .lean(); - - const schedule = await ScheduleModel.findOne({ - customerId, - "products.productId": productId, - }) - .orFail( - new NotFoundError([ - { - code: "custom", - message: "PRODUCT_NOT_FOUND", - path: ["productId"], - }, - ]) - ) - .lean(); - - const oldProduct = schedule.products.find((p) => p.productId === productId); + }); - if (!oldProduct) { - throw new NotFoundError([ - { - code: "custom", - message: "PRODUCT_NOT_FOUND", - path: ["productId"], - }, - ]); - } + const { scheduleId, scheduleMetafieldId, scheduleName, ...oldProduct } = + await CustomerProductServiceGet({ + customerId, + productId, + }); const metafields: MetafieldInput[] = [ ...(body.hasOwnProperty("hideFromProfile") @@ -141,15 +112,29 @@ export const CustomerProductServiceUpdate = async ( }); } - const locations = oldProduct.locations.concat( + const bodyLocations = oldProduct.locations.concat( (body.locations || [])?.filter( (item2) => !oldProduct.locations.some( - (item1) => item1.location.toString() === item2.location.toString() // must use toString since location is type of objectId + (item1) => item1.location.toString() === item2.location.toString() // must use toString since location could be type of objectId ) ) ); + const locations = await LocationModel.find({ + _id: { $in: bodyLocations.map((l) => l.location) }, + }); + + if (locations.length !== bodyLocations.length) { + throw new NotFoundError([ + { + path: ["customerId", "locations"], + message: "LOCATIONS_ERROR", + code: "custom", + }, + ]); + } + const newProduct = { ...oldProduct, ...body, @@ -174,7 +159,12 @@ export const CustomerProductServiceUpdate = async ( ...oldProduct.price, ...body.price, }, - locations: locations, + locations: locations.map((l) => ({ + metafieldId: l.metafieldId, + locationType: l.locationType, + originType: l.originType, + location: l._id, + })), options: mergeArraysUnique( oldProduct?.options || [], body?.options || [], @@ -196,7 +186,7 @@ export const CustomerProductServiceUpdate = async ( ...metafields, { id: oldProduct?.scheduleIdMetafieldId, - value: schedule.metafieldId, + value: scheduleMetafieldId, }, ], tags: [ @@ -207,9 +197,14 @@ export const CustomerProductServiceUpdate = async ( `parentid-${oldProduct.parentId}`, `productid-${oldProduct.productId}`, `product-${oldProduct.productHandle}`, - `scheduleid-${schedule._id}`, + `scheduleid-${scheduleId}`, ] - .concat(newProduct.locations.map((l) => `locationid-${l.location}`)) + .concat(locations.map((l) => `locationid-${l._id}`)) + .concat( + locations.map( + (l) => `city-${l.city.replace(/ /g, "-").toLowerCase()}` + ) + ) .join(", "), }, }); @@ -244,8 +239,8 @@ export const CustomerProductServiceUpdate = async ( return { ...newProduct, - scheduleId: schedule._id.toString(), - scheduleName: schedule.name, + scheduleId: scheduleId.toString(), + scheduleName, }; }; diff --git a/src/library/jest/helpers/location.ts b/src/library/jest/helpers/location.ts index d1e0b02e..35a20dfc 100644 --- a/src/library/jest/helpers/location.ts +++ b/src/library/jest/helpers/location.ts @@ -17,11 +17,11 @@ export const getDumbLocationObject = ( } > = {} ) => ({ - metafieldId: `gid://${faker.number.int({ min: 1, max: 5 })}`, - location: new mongoose.Types.ObjectId(), - locationType: LocationTypes.DESTINATION, - originType: LocationOriginTypes.COMMERCIAL, - ...props, + metafieldId: + props.metafieldId || `gid://${faker.number.int({ min: 1, max: 5 })}`, + location: props.location || new mongoose.Types.ObjectId(), + locationType: props.locationType || LocationTypes.DESTINATION, + originType: props.originType || LocationOriginTypes.COMMERCIAL, }); export const getLocationObject = (props: Partial = {}): Location => ({