From 0bcff435a3cf7f2f6941d33275d792a3b117e695 Mon Sep 17 00:00:00 2001 From: Sebastian Rindom Date: Tue, 26 May 2020 22:02:40 +0200 Subject: [PATCH] Updates API endpoints to match new product-to-variant structure --- .../src/api/middlewares/error-handler.js | 1 + .../admin/products/__tests__/add-option.js | 6 +- .../products/__tests__/create-product.js | 4 +- .../products/__tests__/create-variant.js | 7 ++- .../admin/products/__tests__/delete-option.js | 2 +- .../products/__tests__/delete-variant.js | 9 ++- .../admin/products/__tests__/get-product.js | 4 +- .../admin/products/__tests__/get-variants.js | 39 ++++++++++++ .../admin/products/__tests__/list-products.js | 8 +-- .../products/__tests__/publish-product.js | 2 +- .../products/__tests__/update-variant.js | 18 ++++-- .../api/routes/admin/products/add-option.js | 2 +- .../routes/admin/products/create-product.js | 2 +- .../routes/admin/products/create-variant.js | 12 ++-- .../routes/admin/products/delete-option.js | 6 +- .../routes/admin/products/delete-variant.js | 11 +++- .../api/routes/admin/products/get-product.js | 2 +- .../api/routes/admin/products/get-variants.js | 8 +++ .../src/api/routes/admin/products/index.js | 11 +++- .../routes/admin/products/list-products.js | 2 +- .../routes/admin/products/publish-product.js | 2 +- .../routes/admin/products/update-option.js | 33 ++++++----- .../routes/admin/products/update-product.js | 2 +- .../routes/admin/products/update-variant.js | 2 +- .../store/products/__tests__/get-product.js | 4 +- .../api/routes/store/products/get-product.js | 24 ++++---- .../routes/store/products/list-products.js | 21 ++++++- .../medusa/src/models/__mocks__/product.js | 7 +++ .../medusa/src/services/__mocks__/product.js | 9 ++- .../medusa/src/services/__tests__/product.js | 59 ++++++++++++++++--- .../medusa/src/services/product-variant.js | 4 +- packages/medusa/src/services/product.js | 31 +++++----- 32 files changed, 255 insertions(+), 99 deletions(-) create mode 100644 packages/medusa/src/api/routes/admin/products/__tests__/get-variants.js create mode 100644 packages/medusa/src/api/routes/admin/products/get-variants.js diff --git a/packages/medusa/src/api/middlewares/error-handler.js b/packages/medusa/src/api/middlewares/error-handler.js index 661131ad87..0aafd74533 100644 --- a/packages/medusa/src/api/middlewares/error-handler.js +++ b/packages/medusa/src/api/middlewares/error-handler.js @@ -16,6 +16,7 @@ export default () => { break case MedusaError.Types.DB_ERROR: statusCode = 500 + logger.error(err) break default: break diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/add-option.js b/packages/medusa/src/api/routes/admin/products/__tests__/add-option.js index 8330c6b49a..a2d188279d 100644 --- a/packages/medusa/src/api/routes/admin/products/__tests__/add-option.js +++ b/packages/medusa/src/api/routes/admin/products/__tests__/add-option.js @@ -36,8 +36,10 @@ describe("POST /admin/products/:id/options", () => { }) it("returns the updated product decorated", () => { - expect(subject.body._id).toEqual(IdMap.getId("productWithOptions")) - expect(subject.body.decorated).toEqual(true) + expect(subject.body.product._id).toEqual( + IdMap.getId("productWithOptions") + ) + expect(subject.body.product.decorated).toEqual(true) }) }) }) diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/create-product.js b/packages/medusa/src/api/routes/admin/products/__tests__/create-product.js index b4c4695308..eac06fe22e 100644 --- a/packages/medusa/src/api/routes/admin/products/__tests__/create-product.js +++ b/packages/medusa/src/api/routes/admin/products/__tests__/create-product.js @@ -27,8 +27,8 @@ describe("POST /admin/products", () => { }) it("returns created product draft", () => { - expect(subject.body._id).toEqual(IdMap.getId("product1")) - expect(subject.body.decorated).toEqual(true) + expect(subject.body.product._id).toEqual(IdMap.getId("product1")) + expect(subject.body.product.decorated).toEqual(true) }) it("calls service createDraft", () => { diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/create-variant.js b/packages/medusa/src/api/routes/admin/products/__tests__/create-variant.js index 3b2aa306f1..5e5494b4cc 100644 --- a/packages/medusa/src/api/routes/admin/products/__tests__/create-variant.js +++ b/packages/medusa/src/api/routes/admin/products/__tests__/create-variant.js @@ -39,6 +39,7 @@ describe("POST /admin/products/:id/variants", () => { IdMap.getId("productWithOptions"), { title: "Test Product Variant", + options: [], prices: [ { currency_code: "DKK", @@ -50,8 +51,10 @@ describe("POST /admin/products/:id/variants", () => { }) it("returns the updated product decorated", () => { - expect(subject.body._id).toEqual(IdMap.getId("productWithOptions")) - expect(subject.body.decorated).toEqual(true) + expect(subject.body.product._id).toEqual( + IdMap.getId("productWithOptions") + ) + expect(subject.body.product.decorated).toEqual(true) }) }) }) diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/delete-option.js b/packages/medusa/src/api/routes/admin/products/__tests__/delete-option.js index 757eb17c1f..e77240935c 100644 --- a/packages/medusa/src/api/routes/admin/products/__tests__/delete-option.js +++ b/packages/medusa/src/api/routes/admin/products/__tests__/delete-option.js @@ -25,7 +25,7 @@ describe("DELETE /admin/products/:id/options/:optionId", () => { it("returns 200 and correct delete info", () => { expect(subject.status).toEqual(200) expect(subject.body).toEqual({ - optionId: IdMap.getId("option1"), + option_id: IdMap.getId("option1"), object: "option", deleted: true, }) diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/delete-variant.js b/packages/medusa/src/api/routes/admin/products/__tests__/delete-variant.js index 813f13f118..408b92dd08 100644 --- a/packages/medusa/src/api/routes/admin/products/__tests__/delete-variant.js +++ b/packages/medusa/src/api/routes/admin/products/__tests__/delete-variant.js @@ -34,9 +34,12 @@ describe("POST /admin/products/:id/variants/:variantId", () => { ) }) - it("returns decorated product with variant removed", () => { - expect(subject.body._id).toEqual(IdMap.getId("productWithOptions")) - expect(subject.body.decorated).toEqual(true) + it("returns delete result", () => { + expect(subject.body).toEqual({ + variant_id: IdMap.getId("variant1"), + object: "product-variant", + deleted: true, + }) }) }) }) diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/get-product.js b/packages/medusa/src/api/routes/admin/products/__tests__/get-product.js index e2f6b7fab5..e6ae586169 100644 --- a/packages/medusa/src/api/routes/admin/products/__tests__/get-product.js +++ b/packages/medusa/src/api/routes/admin/products/__tests__/get-product.js @@ -32,8 +32,8 @@ describe("GET /admin/products/:id", () => { }) it("returns product decorated", () => { - expect(subject.body._id).toEqual(IdMap.getId("product1")) - expect(subject.body.decorated).toEqual(true) + expect(subject.body.product._id).toEqual(IdMap.getId("product1")) + expect(subject.body.product.decorated).toEqual(true) }) }) }) diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/get-variants.js b/packages/medusa/src/api/routes/admin/products/__tests__/get-variants.js new file mode 100644 index 0000000000..c727cf1dd8 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/products/__tests__/get-variants.js @@ -0,0 +1,39 @@ +import { IdMap } from "medusa-test-utils" +import { request } from "../../../../../helpers/test-request" +import { ProductServiceMock } from "../../../../../services/__mocks__/product" + +describe("GET /admin/products/:id/variants", () => { + describe("successfully gets a product", () => { + let subject + + beforeAll(async () => { + subject = await request( + "GET", + `/admin/products/${IdMap.getId("product1")}/variants`, + { + adminSession: { + jwt: { + userId: IdMap.getId("admin_user"), + }, + }, + } + ) + }) + + afterAll(() => { + jest.clearAllMocks() + }) + + it("calls get product from productSerice", () => { + expect(ProductServiceMock.retrieveVariants).toHaveBeenCalledTimes(1) + expect(ProductServiceMock.retrieveVariants).toHaveBeenCalledWith( + IdMap.getId("product1") + ) + }) + + it("returns variants", () => { + expect(subject.body.variants[0]._id).toEqual(IdMap.getId("1")) + expect(subject.body.variants[1]._id).toEqual(IdMap.getId("2")) + }) + }) +}) diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/list-products.js b/packages/medusa/src/api/routes/admin/products/__tests__/list-products.js index 23ffb60262..c4b8bc82b0 100644 --- a/packages/medusa/src/api/routes/admin/products/__tests__/list-products.js +++ b/packages/medusa/src/api/routes/admin/products/__tests__/list-products.js @@ -21,10 +21,10 @@ describe("GET /admin/products", () => { it("returns 200 and decorated products", () => { expect(subject.status).toEqual(200) - expect(subject.body[0]._id).toEqual(products.product1._id) - expect(subject.body[0].decorated).toEqual(true) - expect(subject.body[1]._id).toEqual(products.product2._id) - expect(subject.body[1].decorated).toEqual(true) + expect(subject.body.products[0]._id).toEqual(products.product1._id) + expect(subject.body.products[0].decorated).toEqual(true) + expect(subject.body.products[1]._id).toEqual(products.product2._id) + expect(subject.body.products[1].decorated).toEqual(true) }) it("calls update", () => { diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/publish-product.js b/packages/medusa/src/api/routes/admin/products/__tests__/publish-product.js index 6e22c7e4b3..b3e5ed31e5 100644 --- a/packages/medusa/src/api/routes/admin/products/__tests__/publish-product.js +++ b/packages/medusa/src/api/routes/admin/products/__tests__/publish-product.js @@ -25,7 +25,7 @@ describe("POST /admin/products/:id/publish", () => { }) it("returns product with published flag true", () => { - expect(subject.body.published).toEqual(true) + expect(subject.body.product.published).toEqual(true) }) it("calls service publish", () => { diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/update-variant.js b/packages/medusa/src/api/routes/admin/products/__tests__/update-variant.js index 918808e637..92d97b4548 100644 --- a/packages/medusa/src/api/routes/admin/products/__tests__/update-variant.js +++ b/packages/medusa/src/api/routes/admin/products/__tests__/update-variant.js @@ -70,8 +70,10 @@ describe("POST /admin/products/:id/variants/:variantId", () => { }) it("returns decorated product with variant removed", () => { - expect(subject.body._id).toEqual(IdMap.getId("productWithOptions")) - expect(subject.body.decorated).toEqual(true) + expect(subject.body.product._id).toEqual( + IdMap.getId("productWithOptions") + ) + expect(subject.body.product.decorated).toEqual(true) }) }) @@ -118,8 +120,10 @@ describe("POST /admin/products/:id/variants/:variantId", () => { }) it("returns decorated product with variant removed", () => { - expect(subject.body._id).toEqual(IdMap.getId("productWithOptions")) - expect(subject.body.decorated).toEqual(true) + expect(subject.body.product._id).toEqual( + IdMap.getId("productWithOptions") + ) + expect(subject.body.product.decorated).toEqual(true) }) }) @@ -165,8 +169,10 @@ describe("POST /admin/products/:id/variants/:variantId", () => { }) it("returns decorated product with variant removed", () => { - expect(subject.body._id).toEqual(IdMap.getId("productWithOptions")) - expect(subject.body.decorated).toEqual(true) + expect(subject.body.product._id).toEqual( + IdMap.getId("productWithOptions") + ) + expect(subject.body.product.decorated).toEqual(true) }) }) }) diff --git a/packages/medusa/src/api/routes/admin/products/add-option.js b/packages/medusa/src/api/routes/admin/products/add-option.js index 365380d41d..550e8e6061 100644 --- a/packages/medusa/src/api/routes/admin/products/add-option.js +++ b/packages/medusa/src/api/routes/admin/products/add-option.js @@ -25,7 +25,7 @@ export default async (req, res) => { "variants", "published", ]) - res.json(data) + res.json({ product: data }) } catch (err) { throw err } diff --git a/packages/medusa/src/api/routes/admin/products/create-product.js b/packages/medusa/src/api/routes/admin/products/create-product.js index 84cc36a596..2afbe02077 100644 --- a/packages/medusa/src/api/routes/admin/products/create-product.js +++ b/packages/medusa/src/api/routes/admin/products/create-product.js @@ -29,7 +29,7 @@ export default async (req, res) => { "variants", "published", ]) - res.json(newProduct) + res.json({ product: newProduct }) } catch (err) { throw err } diff --git a/packages/medusa/src/api/routes/admin/products/create-variant.js b/packages/medusa/src/api/routes/admin/products/create-variant.js index 03da3d7734..7cfed5f766 100644 --- a/packages/medusa/src/api/routes/admin/products/create-variant.js +++ b/packages/medusa/src/api/routes/admin/products/create-variant.js @@ -10,10 +10,12 @@ export default async (req, res) => { amount: Validator.number().required(), }) .required(), - options: Validator.array().items({ - option_id: Validator.objectId().required(), - value: Validator.string().required(), - }), + options: Validator.array() + .items({ + option_id: Validator.objectId().required(), + value: Validator.string().required(), + }) + .default([]), image: Validator.string().optional(), inventory_quantity: Validator.number().optional(), allow_backorder: Validator.boolean().optional(), @@ -39,7 +41,7 @@ export default async (req, res) => { "variants", "published", ]) - res.json(data) + res.json({ product: data }) } catch (err) { throw err } diff --git a/packages/medusa/src/api/routes/admin/products/delete-option.js b/packages/medusa/src/api/routes/admin/products/delete-option.js index 33911faa91..5ee09ee263 100644 --- a/packages/medusa/src/api/routes/admin/products/delete-option.js +++ b/packages/medusa/src/api/routes/admin/products/delete-option.js @@ -1,11 +1,11 @@ export default async (req, res) => { - const { id, optionId } = req.params + const { id, option_id } = req.params try { const productService = req.scope.resolve("productService") - await productService.deleteOption(id, optionId) + await productService.deleteOption(id, option_id) res.json({ - optionId, + option_id, object: "option", deleted: true, }) diff --git a/packages/medusa/src/api/routes/admin/products/delete-variant.js b/packages/medusa/src/api/routes/admin/products/delete-variant.js index 1de4f4ee57..99cbcf2ed2 100644 --- a/packages/medusa/src/api/routes/admin/products/delete-variant.js +++ b/packages/medusa/src/api/routes/admin/products/delete-variant.js @@ -1,9 +1,9 @@ export default async (req, res) => { - const { id, variantId } = req.params + const { id, variant_id } = req.params try { const productService = req.scope.resolve("productService") - const product = await productService.deleteVariant(id, variantId) + const product = await productService.deleteVariant(id, variant_id) const data = await productService.decorate(product, [ "title", "description", @@ -14,7 +14,12 @@ export default async (req, res) => { "variants", "published", ]) - res.json(product) + + res.json({ + variant_id, + object: "product-variant", + deleted: true, + }) } catch (err) { throw err } diff --git a/packages/medusa/src/api/routes/admin/products/get-product.js b/packages/medusa/src/api/routes/admin/products/get-product.js index 2a5a7ad8db..b7e01755e2 100644 --- a/packages/medusa/src/api/routes/admin/products/get-product.js +++ b/packages/medusa/src/api/routes/admin/products/get-product.js @@ -15,5 +15,5 @@ export default async (req, res) => { "published", ]) - res.json(product) + res.json({ product }) } diff --git a/packages/medusa/src/api/routes/admin/products/get-variants.js b/packages/medusa/src/api/routes/admin/products/get-variants.js new file mode 100644 index 0000000000..f37032cba5 --- /dev/null +++ b/packages/medusa/src/api/routes/admin/products/get-variants.js @@ -0,0 +1,8 @@ +export default async (req, res) => { + const { id } = req.params + + const productService = req.scope.resolve("productService") + const variants = await productService.retrieveVariants(id) + + res.json({ variants }) +} diff --git a/packages/medusa/src/api/routes/admin/products/index.js b/packages/medusa/src/api/routes/admin/products/index.js index 3ffcdf6a9c..3004091a57 100644 --- a/packages/medusa/src/api/routes/admin/products/index.js +++ b/packages/medusa/src/api/routes/admin/products/index.js @@ -18,24 +18,29 @@ export default app => { middlewares.wrap(require("./create-variant").default) ) + route.get( + "/:id/variants", + middlewares.wrap(require("./get-variants").default) + ) + route.post( "/:id/variants/:variant_id", middlewares.wrap(require("./update-variant").default) ) route.post( - "/:id/options/:optionId", + "/:id/options/:option_id", middlewares.wrap(require("./update-option").default) ) route.post("/:id/options", middlewares.wrap(require("./add-option").default)) route.delete( - "/:id/variants/:variantId", + "/:id/variants/:variant_id", middlewares.wrap(require("./delete-variant").default) ) route.delete("/:id", middlewares.wrap(require("./delete-product").default)) route.delete( - "/:id/options/:optionId", + "/:id/options/:option_id", middlewares.wrap(require("./delete-option").default) ) diff --git a/packages/medusa/src/api/routes/admin/products/list-products.js b/packages/medusa/src/api/routes/admin/products/list-products.js index 26d6be1d25..427591e58f 100644 --- a/packages/medusa/src/api/routes/admin/products/list-products.js +++ b/packages/medusa/src/api/routes/admin/products/list-products.js @@ -17,7 +17,7 @@ export default async (req, res) => { ]) ) ) - res.json(products) + res.json({ products }) } catch (error) { throw error } diff --git a/packages/medusa/src/api/routes/admin/products/publish-product.js b/packages/medusa/src/api/routes/admin/products/publish-product.js index 7f622e0c4c..4f7cc97dbc 100644 --- a/packages/medusa/src/api/routes/admin/products/publish-product.js +++ b/packages/medusa/src/api/routes/admin/products/publish-product.js @@ -16,7 +16,7 @@ export default async (req, res) => { "variants", "published", ]) - res.json(publishedProduct) + res.json({ product: publishedProduct }) } catch (error) { throw error } diff --git a/packages/medusa/src/api/routes/admin/products/update-option.js b/packages/medusa/src/api/routes/admin/products/update-option.js index 2d214ab811..1a07876fae 100644 --- a/packages/medusa/src/api/routes/admin/products/update-option.js +++ b/packages/medusa/src/api/routes/admin/products/update-option.js @@ -1,11 +1,10 @@ import { MedusaError, Validator } from "medusa-core-utils" export default async (req, res) => { - const { id, optionId } = req.params + const { id, option_id } = req.params const schema = Validator.object().keys({ title: Validator.string(), - values: Validator.array().items(), }) const { value, error } = schema.validate(req.body) @@ -15,23 +14,25 @@ export default async (req, res) => { try { const productService = req.scope.resolve("productService") - const product = await productService.retrieve(id) - await productService.updateOption(product._id, optionId, value) + const product = await productService.updateOption(id, option_id, value) - let newProduct = await productService.retrieve(product._id) - newProduct = await productService.decorate(newProduct, [ - "title", - "description", - "tags", - "handle", - "images", - "options", - "variants", - "published", - ]) + const data = await productService.decorate( + product, + [ + "title", + "description", + "tags", + "handle", + "images", + "options", + "variants", + "published", + ], + ["variants"] + ) - res.json(newProduct) + res.json({ product: data }) } catch (err) { throw err } diff --git a/packages/medusa/src/api/routes/admin/products/update-product.js b/packages/medusa/src/api/routes/admin/products/update-product.js index 91972b240e..5ebd93b3d2 100644 --- a/packages/medusa/src/api/routes/admin/products/update-product.js +++ b/packages/medusa/src/api/routes/admin/products/update-product.js @@ -37,7 +37,7 @@ export default async (req, res) => { "variants", "published", ]) - res.json(newProduct) + res.json({ product: newProduct }) } catch (err) { throw err } diff --git a/packages/medusa/src/api/routes/admin/products/update-variant.js b/packages/medusa/src/api/routes/admin/products/update-variant.js index 0d1e8d009b..b25f4d5db2 100644 --- a/packages/medusa/src/api/routes/admin/products/update-variant.js +++ b/packages/medusa/src/api/routes/admin/products/update-variant.js @@ -84,7 +84,7 @@ export default async (req, res) => { "variants", "published", ]) - res.json(data) + res.json({ product: data }) } catch (err) { throw err } diff --git a/packages/medusa/src/api/routes/store/products/__tests__/get-product.js b/packages/medusa/src/api/routes/store/products/__tests__/get-product.js index 64014cf697..e45284eee5 100644 --- a/packages/medusa/src/api/routes/store/products/__tests__/get-product.js +++ b/packages/medusa/src/api/routes/store/products/__tests__/get-product.js @@ -33,8 +33,8 @@ describe("Get product by id", () => { }) it("returns product decorated", () => { - expect(subject.body._id).toEqual(IdMap.getId("product1")) - expect(subject.body.decorated).toEqual(true) + expect(subject.body.product._id).toEqual(IdMap.getId("product1")) + expect(subject.body.product.decorated).toEqual(true) }) }) }) diff --git a/packages/medusa/src/api/routes/store/products/get-product.js b/packages/medusa/src/api/routes/store/products/get-product.js index 94de2897a7..82b235010a 100644 --- a/packages/medusa/src/api/routes/store/products/get-product.js +++ b/packages/medusa/src/api/routes/store/products/get-product.js @@ -13,16 +13,20 @@ export default async (req, res) => { const productService = req.scope.resolve("productService") let product = await productService.retrieve(value) - product = await productService.decorate(product, [ - "title", - "description", - "tags", - "handle", - "images", - "options", - "variants", - "published", - ]) + product = await productService.decorate( + product, + [ + "title", + "description", + "tags", + "handle", + "images", + "options", + "variants", + "published", + ], + ["variants"] + ) res.json(product) } diff --git a/packages/medusa/src/api/routes/store/products/list-products.js b/packages/medusa/src/api/routes/store/products/list-products.js index 6881f48cd3..f44805c72b 100644 --- a/packages/medusa/src/api/routes/store/products/list-products.js +++ b/packages/medusa/src/api/routes/store/products/list-products.js @@ -6,5 +6,24 @@ export default async (req, res) => { const productService = req.scope.resolve("productService") const products = await productService.list(selector) - res.json(products) + const data = await Promise.all( + products.map(p => + productService.decorate( + p, + [ + "title", + "description", + "tags", + "handle", + "images", + "options", + "variants", + "published", + ], + ["variants"] + ) + ) + ) + + res.json(data) } diff --git a/packages/medusa/src/models/__mocks__/product.js b/packages/medusa/src/models/__mocks__/product.js index 0e1f1b0550..5b55d53994 100644 --- a/packages/medusa/src/models/__mocks__/product.js +++ b/packages/medusa/src/models/__mocks__/product.js @@ -10,6 +10,13 @@ export const ProductModelMock = { }), deleteOne: jest.fn().mockReturnValue(Promise.resolve()), findOne: jest.fn().mockImplementation(query => { + if (query._id === IdMap.getId("fakeId")) { + return Promise.resolve({ + _id: IdMap.getId("fakeId"), + title: "Product With Variants", + variants: ["1", "2", "3"], + }) + } if (query._id === IdMap.getId("productWithFourVariants")) { return Promise.resolve({ _id: IdMap.getId("productWithFourVariants"), diff --git a/packages/medusa/src/services/__mocks__/product.js b/packages/medusa/src/services/__mocks__/product.js index 884c3d436d..29cfbc6ea2 100644 --- a/packages/medusa/src/services/__mocks__/product.js +++ b/packages/medusa/src/services/__mocks__/product.js @@ -56,9 +56,16 @@ export const ProductServiceMock = { addOption: jest.fn().mockImplementation((productId, optionTitle) => { return Promise.resolve(products.productWithOptions) }), - updateOption: jest.fn().mockReturnValue(Promise.resolve()), + updateOption: jest + .fn() + .mockReturnValue(Promise.resolve(products.productWithOptions)), updateOptionValue: jest.fn().mockReturnValue(Promise.resolve()), deleteOption: jest.fn().mockReturnValue(Promise.resolve()), + retrieveVariants: jest + .fn() + .mockReturnValue( + Promise.resolve([{ _id: IdMap.getId("1") }, { _id: IdMap.getId("2") }]) + ), retrieve: jest.fn().mockImplementation(productId => { if (productId === IdMap.getId("product1")) { return Promise.resolve(products.product1) diff --git a/packages/medusa/src/services/__tests__/product.js b/packages/medusa/src/services/__tests__/product.js index e06a69e625..01455b166f 100644 --- a/packages/medusa/src/services/__tests__/product.js +++ b/packages/medusa/src/services/__tests__/product.js @@ -130,7 +130,7 @@ describe("ProductService", () => { }) const fakeProduct = { - _id: "1234", + _id: IdMap.getId("fakeId"), variants: ["1", "2", "3"], tags: "testtag1, testtag2", handle: "test-product", @@ -148,7 +148,7 @@ describe("ProductService", () => { ["variants"] ) expect(decorated).toEqual({ - _id: "1234", + _id: IdMap.getId("fakeId"), metadata: { testKey: "testValue" }, variants: [variants.one, variants.two, variants.three], }) @@ -161,7 +161,7 @@ describe("ProductService", () => { ["variants"] ) expect(decorated).toEqual({ - _id: "1234", + _id: IdMap.getId("fakeId"), metadata: { testKey: "testValue" }, handle: "test-product", variants: [variants.one, variants.two, variants.three], @@ -174,7 +174,7 @@ describe("ProductService", () => { "tags", ]) expect(decorated).toEqual({ - _id: "1234", + _id: IdMap.getId("fakeId"), metadata: { testKey: "testValue" }, tags: "testtag1, testtag2", handle: "test-product", @@ -184,7 +184,7 @@ describe("ProductService", () => { it("returns decorated product with metadata", async () => { const decorated = await productService.decorate(fakeProduct, []) expect(decorated).toEqual({ - _id: "1234", + _id: IdMap.getId("fakeId"), metadata: { testKey: "testValue" }, }) }) @@ -316,7 +316,7 @@ describe("ProductService", () => { productVariantService: ProductVariantServiceMock, }) - beforeEach(() => { + afterEach(() => { jest.clearAllMocks() }) @@ -350,7 +350,7 @@ describe("ProductService", () => { ], }) - expect(ProductModelMock.findOne).toBeCalledTimes(1) + expect(ProductModelMock.findOne).toBeCalledTimes(2) expect(ProductModelMock.findOne).toBeCalledWith({ _id: IdMap.getId("variantProductId"), }) @@ -362,6 +362,51 @@ describe("ProductService", () => { ) }) + it("add variant to product successfully", async () => { + await productService.createVariant( + IdMap.getId("productWithFourVariants"), + { + title: "variant1", + options: [ + { + option_id: IdMap.getId("color_id"), + value: "blue", + }, + { + option_id: IdMap.getId("size_id"), + value: "1600", + }, + ], + } + ) + + expect(ProductVariantServiceMock.createDraft).toBeCalledTimes(1) + expect(ProductVariantServiceMock.createDraft).toBeCalledWith({ + title: "variant1", + options: [ + { + option_id: IdMap.getId("color_id"), + value: "blue", + }, + { + option_id: IdMap.getId("size_id"), + value: "1600", + }, + ], + }) + + expect(ProductModelMock.findOne).toBeCalledTimes(2) + expect(ProductModelMock.findOne).toBeCalledWith({ + _id: IdMap.getId("productWithFourVariants"), + }) + + expect(ProductModelMock.updateOne).toBeCalledTimes(1) + expect(ProductModelMock.updateOne).toBeCalledWith( + { _id: IdMap.getId("productWithFourVariants") }, + { $push: { variants: expect.stringMatching(/.*/) } } + ) + }) + it("throws error if option id is not present in product", async () => { await expect( productService.createVariant(IdMap.getId("variantProductId"), { diff --git a/packages/medusa/src/services/product-variant.js b/packages/medusa/src/services/product-variant.js index d359e715ae..a4315a1945 100644 --- a/packages/medusa/src/services/product-variant.js +++ b/packages/medusa/src/services/product-variant.js @@ -314,8 +314,8 @@ class ProductVariantService extends BaseService { } return this.productVariantModel_.updateOne( - { _id: variant._id, "options.option_id": optionId }, - { $set: { "options.$.option_id": `${optionValue}` } } + { _id: variantId, "options.option_id": optionId }, + { $set: { "options.$.value": `${optionValue}` } } ) } diff --git a/packages/medusa/src/services/product.js b/packages/medusa/src/services/product.js index 9557a0ea53..bf999adbcc 100644 --- a/packages/medusa/src/services/product.js +++ b/packages/medusa/src/services/product.js @@ -202,14 +202,17 @@ class ProductService extends BaseService { let combinationExists = false if (product.variants && product.variants.length) { + const variants = await this.retrieveVariants(productId) // Check if option value of the variant to add already exists. Go through // each existing variant. Check if this variants option values are // identical to the option values of the variant being added. - combinationExists = product.variants.some(async vId => { - const v = await this.productVariantService_.retrieve(vId) - return v.options.reduce((acc, option, index) => { - return acc && option.value === variant.options[index].value - }, true) + combinationExists = variants.some(v => { + return v.options.every(option => { + const variantOption = variant.options.find(o => + option.option_id.equals(o.option_id) + ) + return option.value === variantOption.value + }) }) } @@ -440,14 +443,14 @@ class ProductService extends BaseService { const firstVariant = await this.productVariantService_.retrieve( product.variants[0] ) - const valueToMatch = firstVariant.options.find( - o => o.option_id === optionId + const valueToMatch = firstVariant.options.find(o => + o.option_id.equals(optionId) ).value const equalsFirst = await Promise.all( product.variants.map(async vId => { const v = await this.productVariantService_.retrieve(vId) - const option = v.options.find(o => o.option_id === optionId) + const option = v.options.find(o => o.option_id.equals(optionId)) return option.value === valueToMatch }) ) @@ -529,12 +532,12 @@ class ProductService extends BaseService { // Check if the variant's options are identical to the variant we // are updating const hasMatchingOptions = v.options.every(option => { - if (option.option_id === optionId) { + if (option.option_id.equals(optionId)) { return option.value === value } - const toUpdateOption = toUpdate.options.find( - o => o.option_id === option.option_id + const toUpdateOption = toUpdate.options.find(o => + option.option_id.equals(o.option_id) ) return toUpdateOption.value === option.value }) @@ -567,11 +570,7 @@ class ProductService extends BaseService { const requiredFields = ["_id", "metadata"] const decorated = _.pick(product, fields.concat(requiredFields)) if (expandFields.includes("variants")) { - decorated.variants = await Promise.all( - product.variants.map(variantId => - this.productVariantService_.retrieve(variantId) - ) - ) + decorated.variants = await this.retrieveVariants(product._id) } return decorated }