Makes ProductVariant operations go through ProductService (#68)
Ensures that we can reliably manage product variants and their relationship to products
This commit is contained in:
@@ -1,15 +1,21 @@
|
||||
import mongoose from "mongoose"
|
||||
|
||||
String.prototype.equals = function(that) {
|
||||
return this === that
|
||||
}
|
||||
|
||||
class IdMap {
|
||||
ids = {}
|
||||
|
||||
getId(key) {
|
||||
getId(key, backend=false) {
|
||||
if (this.ids[key]) {
|
||||
return this.ids[key]
|
||||
}
|
||||
const id = `${mongoose.Types.ObjectId()}`
|
||||
this.ids[key] = id
|
||||
return id
|
||||
|
||||
const mongooseId = `${mongoose.Types.ObjectId()}`
|
||||
this.ids[key] = mongooseId
|
||||
|
||||
return mongooseId
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ export default () => {
|
||||
break
|
||||
case MedusaError.Types.DB_ERROR:
|
||||
statusCode = 500
|
||||
logger.error(err)
|
||||
break
|
||||
default:
|
||||
break
|
||||
|
||||
@@ -3,7 +3,6 @@ import middlewares from "../../middlewares"
|
||||
import authRoutes from "./auth"
|
||||
import productRoutes from "./products"
|
||||
import userRoutes from "./users"
|
||||
import productVariantRoutes from "./product-variants"
|
||||
import regionRoutes from "./regions"
|
||||
import shippingOptionRoutes from "./shipping-options"
|
||||
import shippingProfileRoutes from "./shipping-profiles"
|
||||
@@ -36,7 +35,6 @@ export default (app, container) => {
|
||||
shippingProfileRoutes(route)
|
||||
discountRoutes(route)
|
||||
orderRoutes(route)
|
||||
productVariantRoutes(route)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("POST /admin/product-variants/:id/options", () => {
|
||||
describe("successful add option value", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/product-variants/${IdMap.getId("testVariant")}/options`,
|
||||
{
|
||||
payload: {
|
||||
optionId: IdMap.getId("testOption"),
|
||||
optionValue: "test",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addOption", () => {
|
||||
expect(ProductVariantServiceMock.addOptionValue).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.addOptionValue).toHaveBeenCalledWith(
|
||||
IdMap.getId("testVariant"),
|
||||
IdMap.getId("testOption"),
|
||||
"test"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
-65
@@ -1,65 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("POST /admin/product-variants", () => {
|
||||
describe("successful creation", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", "/admin/product-variants", {
|
||||
payload: {
|
||||
title: "Test Product Variant",
|
||||
prices: [{}],
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns created product draft", () => {
|
||||
expect(subject.body._id).toEqual(IdMap.getId("testVariant"))
|
||||
})
|
||||
|
||||
it("calls service createDraft", () => {
|
||||
expect(ProductVariantServiceMock.createDraft).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.createDraft).toHaveBeenCalledWith({
|
||||
title: "Test Product Variant",
|
||||
prices: [{}],
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("invalid data returns error details", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("POST", "/admin/products", {
|
||||
payload: {
|
||||
image: "image",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("returns 400", () => {
|
||||
expect(subject.status).toEqual(400)
|
||||
})
|
||||
|
||||
it("returns error details", () => {
|
||||
expect(subject.body.name).toEqual("invalid_data")
|
||||
expect(subject.body.message[0].message).toEqual(`"title" is required`)
|
||||
})
|
||||
})
|
||||
})
|
||||
-39
@@ -1,39 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("DELETE /admin/product-variants/:id/options", () => {
|
||||
describe("successfully deletes option value", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"DELETE",
|
||||
`/admin/product-variants/${IdMap.getId("testVariant")}/options`,
|
||||
{
|
||||
payload: {
|
||||
optionId: IdMap.getId("testOption"),
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
it("calls service deleteOptionValue", () => {
|
||||
expect(ProductVariantServiceMock.deleteOptionValue).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(ProductVariantServiceMock.deleteOptionValue).toHaveBeenCalledWith(
|
||||
IdMap.getId("testVariant"),
|
||||
IdMap.getId("testOption")
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("DELETE /admin/product-variants/:id", () => {
|
||||
describe("successfully deletes a product variant", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"DELETE",
|
||||
`/admin/product-variants/${IdMap.getId("testVariant")}`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls ProductVariantService delete", () => {
|
||||
expect(ProductVariantServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.delete).toHaveBeenCalledWith(
|
||||
IdMap.getId("testVariant")
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns correct delete data", () => {
|
||||
expect(subject.body).toEqual({
|
||||
id: IdMap.getId("testVariant"),
|
||||
object: "productVariant",
|
||||
deleted: true,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("GET /admin/product-variants/:id", () => {
|
||||
describe("successfully gets a product variant", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"GET",
|
||||
`/admin/product-variants/${IdMap.getId("testVariant")}`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls productVariantService retrieve", () => {
|
||||
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.retrieve).toHaveBeenCalledWith(
|
||||
IdMap.getId("testVariant")
|
||||
)
|
||||
})
|
||||
|
||||
it("returns product", () => {
|
||||
expect(subject.body._id).toEqual(IdMap.getId("testVariant"))
|
||||
})
|
||||
})
|
||||
})
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("GET /admin/product-variants", () => {
|
||||
describe("successfully lists product variants", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request("GET", `/admin/product-variants`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it("calls ProductVariantService list", () => {
|
||||
expect(ProductVariantServiceMock.list).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
it("returns 200 and product variants", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
expect(subject.body[0]._id).toEqual(IdMap.getId("testVariant"))
|
||||
})
|
||||
})
|
||||
})
|
||||
-38
@@ -1,38 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("POST /admin/product-variants/:id/publish", () => {
|
||||
describe("successful publish", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/product-variants/${IdMap.getId("publish")}/publish`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("returns product with published flag true", () => {
|
||||
expect(subject.body.published).toEqual(true)
|
||||
})
|
||||
|
||||
it("calls service publish", () => {
|
||||
expect(ProductVariantServiceMock.publish).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.publish).toHaveBeenCalledWith(
|
||||
IdMap.getId("publish")
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,42 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("POST /admin/product-variants/:id/currency-price", () => {
|
||||
describe("successful sets currency price", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/product-variants/${IdMap.getId("testVariant")}/currency-price`,
|
||||
{
|
||||
payload: {
|
||||
currencyCode: "DKK",
|
||||
amount: 100,
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service setCurrencyPrice", () => {
|
||||
expect(ProductVariantServiceMock.setCurrencyPrice).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(ProductVariantServiceMock.setCurrencyPrice).toHaveBeenCalledWith(
|
||||
IdMap.getId("testVariant"),
|
||||
"DKK",
|
||||
100
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,40 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("POST /admin/product-variants/:id/region-price", () => {
|
||||
describe("successfully sets region price", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/product-variants/${IdMap.getId("testVariant")}/region-price`,
|
||||
{
|
||||
payload: {
|
||||
regionId: IdMap.getId("region-fr"),
|
||||
amount: 100,
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service setCurrencyPrice", () => {
|
||||
expect(ProductVariantServiceMock.setRegionPrice).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.setRegionPrice).toHaveBeenCalledWith(
|
||||
IdMap.getId("testVariant"),
|
||||
IdMap.getId("region-fr"),
|
||||
100
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
-70
@@ -1,70 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("POST /admin/product-variants/:id", () => {
|
||||
describe("successful update", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/product-variants/${IdMap.getId("testVariant")}`,
|
||||
{
|
||||
payload: {
|
||||
title: "Test Product Variant Updated",
|
||||
prices: [{}],
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service update", () => {
|
||||
expect(ProductVariantServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.update).toHaveBeenCalledWith(
|
||||
IdMap.getId("testVariant"),
|
||||
{
|
||||
title: "Test Product Variant Updated",
|
||||
prices: [{}],
|
||||
}
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe("handles failed update operation", () => {
|
||||
it("throws if metadata is to be updated", async () => {
|
||||
try {
|
||||
await request(
|
||||
"POST",
|
||||
`/admin/product-variants/${IdMap.getId("testVariant")}`,
|
||||
{
|
||||
payload: {
|
||||
_id: IdMap.getId("testVariant"),
|
||||
title: "Product 1",
|
||||
metadata: "Test Description",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
} catch (error) {
|
||||
expect(error.status).toEqual(400)
|
||||
expect(error.message).toEqual(
|
||||
"Use setMetadata to update metadata fields"
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,28 +0,0 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
optionId: Validator.objectId().required(),
|
||||
optionValue: Validator.string().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
const productVariant = await productVariantService.addOptionValue(
|
||||
id,
|
||||
value.optionId,
|
||||
value.optionValue
|
||||
)
|
||||
|
||||
res.status(200).json(productVariant)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const schema = Validator.object().keys({
|
||||
title: Validator.string().required(),
|
||||
prices: Validator.array()
|
||||
.items({})
|
||||
.required(),
|
||||
options: Validator.array().items({}),
|
||||
image: Validator.string().optional(),
|
||||
inventory_quantity: Validator.number().optional(),
|
||||
allow_backorder: Validator.boolean().optional(),
|
||||
manage_inventory: Validator.boolean().optional(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
const productVariant = await productVariantService.createDraft(value)
|
||||
|
||||
res.status(200).json(productVariant)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
optionId: Validator.objectId().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
const productVariant = await productVariantService.deleteOptionValue(
|
||||
id,
|
||||
value.optionId
|
||||
)
|
||||
|
||||
res.status(200).json(productVariant)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
await productVariantService.delete(id)
|
||||
|
||||
res.json({
|
||||
id: id,
|
||||
object: "productVariant",
|
||||
deleted: true,
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
const productVariant = await productVariantService.retrieve(id)
|
||||
|
||||
res.json(productVariant)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
import { Router } from "express"
|
||||
import middlewares from "../../../middlewares"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default app => {
|
||||
app.use("/product-variants", route)
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-product-variant").default))
|
||||
route.post(
|
||||
"/:id",
|
||||
middlewares.wrap(require("./update-product-variant").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/publish",
|
||||
middlewares.wrap(require("./publish-product-variant").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/currency-price",
|
||||
middlewares.wrap(require("./set-currency-price").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/region-price",
|
||||
middlewares.wrap(require("./set-region-price").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/options",
|
||||
middlewares.wrap(require("./add-option-value").default)
|
||||
)
|
||||
|
||||
route.delete(
|
||||
"/:id/options",
|
||||
middlewares.wrap(require("./delete-option-value").default)
|
||||
)
|
||||
|
||||
route.delete(
|
||||
"/:id",
|
||||
middlewares.wrap(require("./delete-product-variant").default)
|
||||
)
|
||||
|
||||
route.get("/:id", middlewares.wrap(require("./get-product-variant").default))
|
||||
route.get("/", middlewares.wrap(require("./list-product-variants").default))
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
export default async (req, res) => {
|
||||
try {
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
const productVariants = await productVariantService.list({})
|
||||
|
||||
res.json(productVariants)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
try {
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
const productVariant = await productVariantService.publish(id)
|
||||
|
||||
res.json(productVariant)
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
currencyCode: Validator.string().required(),
|
||||
amount: Validator.number().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
const productVariant = await productVariantService.setCurrencyPrice(
|
||||
id,
|
||||
value.currencyCode,
|
||||
value.amount
|
||||
)
|
||||
|
||||
res.status(200).json(productVariant)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
regionId: Validator.objectId().required(),
|
||||
amount: Validator.number().required(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
const productVariant = await productVariantService.setRegionPrice(
|
||||
id,
|
||||
value.regionId,
|
||||
value.amount
|
||||
)
|
||||
|
||||
res.status(200).json(productVariant)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
title: Validator.string().optional(),
|
||||
prices: Validator.array()
|
||||
.items({})
|
||||
.optional(),
|
||||
options: Validator.array()
|
||||
.items({})
|
||||
.optional(),
|
||||
image: Validator.string().optional(),
|
||||
inventory_quantity: Validator.number().optional(),
|
||||
allow_backorder: Validator.boolean().optional(),
|
||||
manage_inventory: Validator.boolean().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
const productVariant = await productVariantService.update(id, value)
|
||||
|
||||
res.status(200).json(productVariant)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ describe("POST /admin/products/:id/options", () => {
|
||||
`/admin/products/${IdMap.getId("productWithOptions")}/options`,
|
||||
{
|
||||
payload: {
|
||||
optionTitle: "Test option",
|
||||
option_title: "Test option",
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||
|
||||
describe("POST /admin/products/:id/variants/:variantId", () => {
|
||||
describe("successful add variant", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/products/${IdMap.getId(
|
||||
"productWithOptions"
|
||||
)}/variants/${IdMap.getId("variant2")}`,
|
||||
{
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addVariant", () => {
|
||||
expect(ProductServiceMock.addVariant).toHaveBeenCalledTimes(1)
|
||||
expect(ProductServiceMock.addVariant).toHaveBeenCalledWith(
|
||||
IdMap.getId("productWithOptions"),
|
||||
IdMap.getId("variant2")
|
||||
)
|
||||
})
|
||||
|
||||
it("returns the updated product decorated", () => {
|
||||
expect(subject.body._id).toEqual(IdMap.getId("productWithOptions"))
|
||||
expect(subject.body.decorated).toEqual(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||
|
||||
describe("POST /admin/products/:id/variants", () => {
|
||||
describe("successful add variant", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/products/${IdMap.getId("productWithOptions")}/variants`,
|
||||
{
|
||||
payload: {
|
||||
title: "Test Product Variant",
|
||||
prices: [
|
||||
{
|
||||
currency_code: "DKK",
|
||||
amount: 1234,
|
||||
},
|
||||
],
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service addVariant", () => {
|
||||
expect(ProductServiceMock.createVariant).toHaveBeenCalledTimes(1)
|
||||
expect(ProductServiceMock.createVariant).toHaveBeenCalledWith(
|
||||
IdMap.getId("productWithOptions"),
|
||||
{
|
||||
title: "Test Product Variant",
|
||||
options: [],
|
||||
prices: [
|
||||
{
|
||||
currency_code: "DKK",
|
||||
amount: 1234,
|
||||
},
|
||||
],
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns the updated product decorated", () => {
|
||||
expect(subject.body.product._id).toEqual(
|
||||
IdMap.getId("productWithOptions")
|
||||
)
|
||||
expect(subject.body.product.decorated).toEqual(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
+8
-5
@@ -27,16 +27,19 @@ describe("POST /admin/products/:id/variants/:variantId", () => {
|
||||
})
|
||||
|
||||
it("calls service removeVariant", () => {
|
||||
expect(ProductServiceMock.removeVariant).toHaveBeenCalledTimes(1)
|
||||
expect(ProductServiceMock.removeVariant).toHaveBeenCalledWith(
|
||||
expect(ProductServiceMock.deleteVariant).toHaveBeenCalledTimes(1)
|
||||
expect(ProductServiceMock.deleteVariant).toHaveBeenCalledWith(
|
||||
IdMap.getId("productWithOptions"),
|
||||
IdMap.getId("variant1")
|
||||
)
|
||||
})
|
||||
|
||||
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,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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"))
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||
import { ProductVariantServiceMock } from "../../../../../services/__mocks__/product-variant"
|
||||
|
||||
describe("POST /admin/products/:id/variants/:variantId", () => {
|
||||
describe("successful updates variant prices", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/products/${IdMap.getId(
|
||||
"productWithOptions"
|
||||
)}/variants/${IdMap.getId("variant1")}`,
|
||||
{
|
||||
payload: {
|
||||
title: "hi",
|
||||
prices: [
|
||||
{
|
||||
region_id: IdMap.getId("region-fr"),
|
||||
amount: 100,
|
||||
},
|
||||
{
|
||||
currency_code: "DKK",
|
||||
amount: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service removeVariant", () => {
|
||||
expect(ProductVariantServiceMock.setCurrencyPrice).toHaveBeenCalledTimes(
|
||||
1
|
||||
)
|
||||
expect(ProductVariantServiceMock.setCurrencyPrice).toHaveBeenCalledWith(
|
||||
IdMap.getId("variant1"),
|
||||
"DKK",
|
||||
100
|
||||
)
|
||||
|
||||
expect(ProductVariantServiceMock.setRegionPrice).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.setRegionPrice).toHaveBeenCalledWith(
|
||||
IdMap.getId("variant1"),
|
||||
IdMap.getId("region-fr"),
|
||||
100
|
||||
)
|
||||
})
|
||||
|
||||
it("filters prices", () => {
|
||||
expect(ProductVariantServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.update).toHaveBeenCalledWith(
|
||||
IdMap.getId("variant1"),
|
||||
{
|
||||
title: "hi",
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns decorated product with variant removed", () => {
|
||||
expect(subject.body.product._id).toEqual(
|
||||
IdMap.getId("productWithOptions")
|
||||
)
|
||||
expect(subject.body.product.decorated).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("successful updates options", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/products/${IdMap.getId(
|
||||
"productWithOptions"
|
||||
)}/variants/${IdMap.getId("variant1")}`,
|
||||
{
|
||||
payload: {
|
||||
options: [
|
||||
{
|
||||
option_id: IdMap.getId("option_id"),
|
||||
value: 100,
|
||||
},
|
||||
],
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls service removeVariant", () => {
|
||||
expect(ProductServiceMock.updateOptionValue).toHaveBeenCalledTimes(1)
|
||||
expect(ProductServiceMock.updateOptionValue).toHaveBeenCalledWith(
|
||||
IdMap.getId("productWithOptions"),
|
||||
IdMap.getId("variant1"),
|
||||
IdMap.getId("option_id"),
|
||||
100
|
||||
)
|
||||
})
|
||||
|
||||
it("returns decorated product with variant removed", () => {
|
||||
expect(subject.body.product._id).toEqual(
|
||||
IdMap.getId("productWithOptions")
|
||||
)
|
||||
expect(subject.body.product.decorated).toEqual(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("successful updates variant", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/products/${IdMap.getId(
|
||||
"productWithOptions"
|
||||
)}/variants/${IdMap.getId("variant1")}`,
|
||||
{
|
||||
payload: {
|
||||
title: "hi",
|
||||
inventory_quantity: 123,
|
||||
allow_backorder: true,
|
||||
},
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns 200", () => {
|
||||
expect(subject.status).toEqual(200)
|
||||
})
|
||||
|
||||
it("calls variant update", () => {
|
||||
expect(ProductVariantServiceMock.update).toHaveBeenCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.update).toHaveBeenCalledWith(
|
||||
IdMap.getId("variant1"),
|
||||
{
|
||||
title: "hi",
|
||||
inventory_quantity: 123,
|
||||
allow_backorder: true,
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("returns decorated product with variant removed", () => {
|
||||
expect(subject.body.product._id).toEqual(
|
||||
IdMap.getId("productWithOptions")
|
||||
)
|
||||
expect(subject.body.product.decorated).toEqual(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -4,7 +4,7 @@ export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const schema = Validator.object().keys({
|
||||
optionTitle: Validator.string().required(),
|
||||
option_title: Validator.string().required(),
|
||||
})
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
@@ -13,10 +13,9 @@ export default async (req, res) => {
|
||||
|
||||
try {
|
||||
const productService = req.scope.resolve("productService")
|
||||
const product = await productService.retrieve(id)
|
||||
await productService.addOption(product._id, value.optionTitle)
|
||||
let newProduct = await productService.retrieve(product._id)
|
||||
newProduct = await productService.decorate(newProduct, [
|
||||
const newProduct = await productService.addOption(id, value.option_title)
|
||||
|
||||
const data = await productService.decorate(newProduct, [
|
||||
"title",
|
||||
"description",
|
||||
"tags",
|
||||
@@ -26,9 +25,8 @@ export default async (req, res) => {
|
||||
"variants",
|
||||
"published",
|
||||
])
|
||||
res.json(newProduct)
|
||||
res.json({ product: data })
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
export default async (req, res) => {
|
||||
const { id, variantId } = req.params
|
||||
|
||||
try {
|
||||
const productService = req.scope.resolve("productService")
|
||||
const product = await productService.retrieve(id)
|
||||
await productService.addVariant(product._id, variantId)
|
||||
let newProduct = await productService.retrieve(product._id)
|
||||
newProduct = await productService.decorate(newProduct, [
|
||||
"title",
|
||||
"description",
|
||||
"tags",
|
||||
"handle",
|
||||
"images",
|
||||
"options",
|
||||
"variants",
|
||||
"published",
|
||||
])
|
||||
res.json(newProduct)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ export default async (req, res) => {
|
||||
"variants",
|
||||
"published",
|
||||
])
|
||||
res.json(newProduct)
|
||||
res.json({ product: newProduct })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
const schema = Validator.object().keys({
|
||||
title: Validator.string().required(),
|
||||
prices: Validator.array()
|
||||
.items({
|
||||
currency_code: Validator.string().required(),
|
||||
amount: Validator.number().required(),
|
||||
})
|
||||
.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(),
|
||||
manage_inventory: Validator.boolean().optional(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const productService = req.scope.resolve("productService")
|
||||
const product = await productService.createVariant(id, value)
|
||||
const data = await productService.decorate(product, [
|
||||
"title",
|
||||
"description",
|
||||
"tags",
|
||||
"handle",
|
||||
"images",
|
||||
"options",
|
||||
"variants",
|
||||
"published",
|
||||
])
|
||||
res.json({ product: data })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
+9
-5
@@ -1,11 +1,10 @@
|
||||
export default async (req, res) => {
|
||||
const { id, variantId } = req.params
|
||||
const { id, variant_id } = req.params
|
||||
|
||||
try {
|
||||
const productService = req.scope.resolve("productService")
|
||||
await productService.removeVariant(id, variantId)
|
||||
let updatedProduct = await productService.retrieve(id)
|
||||
updatedProduct = await productService.decorate(updatedProduct, [
|
||||
const product = await productService.deleteVariant(id, variant_id)
|
||||
const data = await productService.decorate(product, [
|
||||
"title",
|
||||
"description",
|
||||
"tags",
|
||||
@@ -15,7 +14,12 @@ export default async (req, res) => {
|
||||
"variants",
|
||||
"published",
|
||||
])
|
||||
res.json(updatedProduct)
|
||||
|
||||
res.json({
|
||||
variant_id,
|
||||
object: "product-variant",
|
||||
deleted: true,
|
||||
})
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
@@ -15,5 +15,5 @@ export default async (req, res) => {
|
||||
"published",
|
||||
])
|
||||
|
||||
res.json(product)
|
||||
res.json({ product })
|
||||
}
|
||||
|
||||
@@ -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 })
|
||||
}
|
||||
@@ -12,23 +12,35 @@ export default app => {
|
||||
"/:id/publish",
|
||||
middlewares.wrap(require("./publish-product").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/variants/:variantId",
|
||||
middlewares.wrap(require("./add-variant").default)
|
||||
"/:id/variants",
|
||||
middlewares.wrap(require("./create-variant").default)
|
||||
)
|
||||
|
||||
route.get(
|
||||
"/:id/variants",
|
||||
middlewares.wrap(require("./get-variants").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/:id/options/:optionId",
|
||||
"/:id/variants/:variant_id",
|
||||
middlewares.wrap(require("./update-variant").default)
|
||||
)
|
||||
|
||||
route.post(
|
||||
"/: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",
|
||||
middlewares.wrap(require("./remove-variant").default)
|
||||
"/: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)
|
||||
)
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ export default async (req, res) => {
|
||||
])
|
||||
)
|
||||
)
|
||||
res.json(products)
|
||||
res.json({ products })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export default async (req, res) => {
|
||||
"variants",
|
||||
"published",
|
||||
])
|
||||
res.json(publishedProduct)
|
||||
res.json({ product: publishedProduct })
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default async (req, res) => {
|
||||
"variants",
|
||||
"published",
|
||||
])
|
||||
res.json(newProduct)
|
||||
res.json({ product: newProduct })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
import _ from "lodash"
|
||||
import { MedusaError, Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { id, variant_id } = req.params
|
||||
const schema = Validator.object().keys({
|
||||
title: Validator.string().optional(),
|
||||
prices: Validator.array().items(
|
||||
Validator.object()
|
||||
.keys({
|
||||
region_id: Validator.string(),
|
||||
currency_code: Validator.string(),
|
||||
amount: Validator.number().required(),
|
||||
})
|
||||
.xor("region_id", "currency_code")
|
||||
),
|
||||
options: Validator.array().items({
|
||||
option_id: Validator.objectId().required(),
|
||||
value: Validator.alternatives(
|
||||
Validator.string(),
|
||||
Validator.number()
|
||||
).required(),
|
||||
}),
|
||||
image: Validator.string().optional(),
|
||||
inventory_quantity: Validator.number().optional(),
|
||||
allow_backorder: Validator.boolean().optional(),
|
||||
manage_inventory: Validator.boolean().optional(),
|
||||
metadata: Validator.object().optional(),
|
||||
})
|
||||
|
||||
const { value, error } = schema.validate(req.body)
|
||||
if (error) {
|
||||
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
|
||||
}
|
||||
|
||||
try {
|
||||
const productService = req.scope.resolve("productService")
|
||||
const productVariantService = req.scope.resolve("productVariantService")
|
||||
|
||||
if (value.prices && value.prices.length) {
|
||||
for (const price of value.prices) {
|
||||
if (price.region_id) {
|
||||
await productVariantService.setRegionPrice(
|
||||
variant_id,
|
||||
price.region_id,
|
||||
price.amount
|
||||
)
|
||||
} else {
|
||||
await productVariantService.setCurrencyPrice(
|
||||
variant_id,
|
||||
price.currency_code,
|
||||
price.amount
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (value.options && value.options.length) {
|
||||
for (const option of value.options) {
|
||||
await productService.updateOptionValue(
|
||||
id,
|
||||
variant_id,
|
||||
option.option_id,
|
||||
option.value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
delete value.prices
|
||||
delete value.options
|
||||
|
||||
if (!_.isEmpty(value)) {
|
||||
await productVariantService.update(variant_id, value)
|
||||
}
|
||||
|
||||
const product = await productService.retrieve(id)
|
||||
const data = await productService.decorate(product, [
|
||||
"title",
|
||||
"description",
|
||||
"tags",
|
||||
"handle",
|
||||
"images",
|
||||
"options",
|
||||
"variants",
|
||||
"published",
|
||||
])
|
||||
res.json({ product: data })
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@ describe("POST /admin/regions/:region_id/countries", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
const id = IdMap.getId("testRegion")
|
||||
subject = await request("POST", `/admin/regions/${id}/countries`, {
|
||||
payload: {
|
||||
country_code: "se",
|
||||
@@ -27,7 +27,7 @@ describe("POST /admin/regions/:region_id/countries", () => {
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.addCountry).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.addCountry).toHaveBeenCalledWith(
|
||||
IdMap.getId("region"),
|
||||
IdMap.getId("testRegion"),
|
||||
"se"
|
||||
)
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ describe("POST /admin/regions/:region_id/fulfillment-providers", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
const id = IdMap.getId("testRegion")
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/regions/${id}/fulfillment-providers`,
|
||||
@@ -31,7 +31,7 @@ describe("POST /admin/regions/:region_id/fulfillment-providers", () => {
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.addFulfillmentProvider).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.addFulfillmentProvider).toHaveBeenCalledWith(
|
||||
IdMap.getId("region"),
|
||||
IdMap.getId("testRegion"),
|
||||
"default_provider"
|
||||
)
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ describe("POST /admin/regions/:region_id/payment-providers", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
const id = IdMap.getId("testRegion")
|
||||
subject = await request(
|
||||
"POST",
|
||||
`/admin/regions/${id}/payment-providers`,
|
||||
@@ -31,7 +31,7 @@ describe("POST /admin/regions/:region_id/payment-providers", () => {
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.addPaymentProvider).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.addPaymentProvider).toHaveBeenCalledWith(
|
||||
IdMap.getId("region"),
|
||||
IdMap.getId("testRegion"),
|
||||
"default_provider"
|
||||
)
|
||||
})
|
||||
|
||||
@@ -7,7 +7,7 @@ describe("GET /admin/regions/:region_id", () => {
|
||||
let subject
|
||||
|
||||
beforeAll(async () => {
|
||||
const id = IdMap.getId("region")
|
||||
const id = IdMap.getId("testRegion")
|
||||
subject = await request("GET", `/admin/regions/${id}`, {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
@@ -24,7 +24,7 @@ describe("GET /admin/regions/:region_id", () => {
|
||||
it("calls service addCountry", () => {
|
||||
expect(RegionServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||
expect(RegionServiceMock.retrieve).toHaveBeenCalledWith(
|
||||
IdMap.getId("region")
|
||||
IdMap.getId("testRegion")
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -32,8 +32,8 @@ describe("POST /store/carts/:id", () => {
|
||||
expect(LineItemServiceMock.generate).toHaveBeenCalledTimes(1)
|
||||
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
|
||||
IdMap.getId("testVariant"),
|
||||
3,
|
||||
IdMap.getId("testRegion")
|
||||
IdMap.getId("testRegion"),
|
||||
3
|
||||
)
|
||||
})
|
||||
|
||||
@@ -71,8 +71,8 @@ describe("POST /store/carts/:id", () => {
|
||||
expect(LineItemServiceMock.generate).toHaveBeenCalledTimes(1)
|
||||
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
|
||||
IdMap.getId("fail"),
|
||||
3,
|
||||
IdMap.getId("testRegion")
|
||||
IdMap.getId("testRegion"),
|
||||
3
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ describe("GET /store/carts", () => {
|
||||
expect(CartServiceMock.retrieve).toHaveBeenCalledWith("none")
|
||||
})
|
||||
|
||||
it("returns products", () => {
|
||||
it("returns 404 status", () => {
|
||||
expect(subject.status).toEqual(404)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -20,8 +20,8 @@ export default async (req, res) => {
|
||||
|
||||
const lineItem = await lineItemService.generate(
|
||||
value.variant_id,
|
||||
value.quantity,
|
||||
cart.region_id
|
||||
cart.region_id,
|
||||
value.quantity
|
||||
)
|
||||
await cartService.addLineItem(cart._id, lineItem)
|
||||
|
||||
|
||||
@@ -1,15 +1,13 @@
|
||||
export default async (req, res) => {
|
||||
const { id } = req.params
|
||||
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
let cart = await cartService.retrieve(id)
|
||||
try {
|
||||
const cartService = req.scope.resolve("cartService")
|
||||
let cart = await cartService.retrieve(id)
|
||||
cart = await cartService.decorate(cart)
|
||||
|
||||
if (!cart) {
|
||||
res.sendStatus(404)
|
||||
return
|
||||
res.json(cart)
|
||||
} catch (err) {
|
||||
throw err
|
||||
}
|
||||
|
||||
cart = await cartService.decorate(cart)
|
||||
|
||||
res.json(cart)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ export const orders = {
|
||||
quantity: 10,
|
||||
},
|
||||
],
|
||||
region: IdMap.getId("region-france"),
|
||||
region_id: IdMap.getId("region-france"),
|
||||
customer_id: IdMap.getId("test-customer"),
|
||||
payment_method: {
|
||||
provider_id: "default_provider",
|
||||
@@ -102,7 +102,7 @@ export const orders = {
|
||||
quantity: 10,
|
||||
},
|
||||
],
|
||||
region: IdMap.getId("region-france"),
|
||||
region_id: IdMap.getId("region-france"),
|
||||
customer_id: IdMap.getId("test-customer"),
|
||||
payment_method: {
|
||||
provider_id: "default_provider",
|
||||
@@ -182,7 +182,7 @@ export const orders = {
|
||||
quantity: 10,
|
||||
},
|
||||
],
|
||||
region: IdMap.getId("region-france"),
|
||||
region_id: IdMap.getId("region-france"),
|
||||
customer_id: IdMap.getId("test-customer"),
|
||||
payment_method: {
|
||||
provider_id: "default_provider",
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -8,7 +8,7 @@ import LineItemSchema from "./schemas/line-item"
|
||||
import PaymentMethodSchema from "./schemas/payment-method"
|
||||
import ShippingMethodSchema from "./schemas/shipping-method"
|
||||
import AddressSchema from "./schemas/address"
|
||||
import DiscountModel from "./discount"
|
||||
import DiscountSchema from "./schemas/discount"
|
||||
|
||||
class CartModel extends BaseModel {
|
||||
static modelName = "Cart"
|
||||
@@ -19,7 +19,7 @@ class CartModel extends BaseModel {
|
||||
shipping_address: { type: AddressSchema },
|
||||
items: { type: [LineItemSchema], default: [] },
|
||||
region_id: { type: String, required: true },
|
||||
discounts: { type: [DiscountModel.schema], default: [] },
|
||||
discounts: { type: [DiscountSchema], default: [] },
|
||||
customer_id: { type: String, default: "" },
|
||||
payment_sessions: { type: [PaymentMethodSchema], default: [] },
|
||||
shipping_options: { type: [ShippingMethodSchema], default: [] },
|
||||
|
||||
@@ -21,7 +21,7 @@ class OrderModel extends BaseModel {
|
||||
billing_address: { type: AddressSchema, required: true },
|
||||
shipping_address: { type: AddressSchema, required: true },
|
||||
items: { type: [LineItemSchema], required: true },
|
||||
region: { type: String, required: true },
|
||||
region_id: { type: String, required: true },
|
||||
discounts: { type: [DiscountModel.schema], default: [] },
|
||||
customer_id: { type: String, required: true },
|
||||
payment_method: { type: PaymentMethodSchema, required: true },
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import mongoose from "mongoose"
|
||||
|
||||
import DiscountRule from "./discount-rule"
|
||||
|
||||
export default new mongoose.Schema({
|
||||
code: { type: String },
|
||||
discount_rule: { type: DiscountRule },
|
||||
usage_count: { type: Number },
|
||||
disabled: { type: Boolean },
|
||||
starts_at: { type: Date },
|
||||
ends_at: { type: Date },
|
||||
regions: { type: [String], default: [] },
|
||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||
})
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
|
||||
const adminUser = {
|
||||
_id: IdMap.getId("admin_user"),
|
||||
_id: IdMap.getId("admin_user", true),
|
||||
password: "1235",
|
||||
name: "hi",
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ export const CartServiceMock = {
|
||||
if (cartId === IdMap.getId("cartWithPaySessions")) {
|
||||
return Promise.resolve(carts.cartWithPaySessions)
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
throw new MedusaError(MedusaError.Types.NOT_FOUND, "cart not found")
|
||||
}),
|
||||
addLineItem: jest.fn().mockImplementation((cartId, lineItem) => {
|
||||
return Promise.resolve()
|
||||
|
||||
@@ -41,7 +41,7 @@ export const orders = {
|
||||
quantity: 10,
|
||||
},
|
||||
],
|
||||
region: IdMap.getId("testRegion"),
|
||||
region_id: IdMap.getId("testRegion"),
|
||||
customer_id: IdMap.getId("testCustomer"),
|
||||
payment_method: {
|
||||
provider_id: "default_provider",
|
||||
@@ -94,7 +94,7 @@ export const orders = {
|
||||
quantity: 10,
|
||||
},
|
||||
],
|
||||
region: IdMap.getId("region-france"),
|
||||
region_id: IdMap.getId("region-france"),
|
||||
customer_id: IdMap.getId("test-customer"),
|
||||
payment_method: {
|
||||
provider_id: "default_provider",
|
||||
|
||||
@@ -103,6 +103,7 @@ const emptyVariant = {
|
||||
|
||||
const eur10us12 = {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
title: "EUR10US-12",
|
||||
}
|
||||
|
||||
export const variants = {
|
||||
@@ -193,10 +194,30 @@ export const ProductVariantServiceMock = {
|
||||
update: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
setCurrencyPrice: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
setRegionPrice: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
updateOptionValue: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
addOptionValue: jest.fn().mockImplementation((variantId, optionId, value) => {
|
||||
return Promise.resolve({})
|
||||
}),
|
||||
list: jest.fn().mockImplementation(data => {
|
||||
if (data._id && data._id.$in) {
|
||||
return Promise.resolve(
|
||||
data._id.$in.map(id => {
|
||||
if (id === "1") {
|
||||
return variant1
|
||||
}
|
||||
if (id === "2") {
|
||||
return variant2
|
||||
}
|
||||
if (id === "3") {
|
||||
return variant3
|
||||
}
|
||||
if (id === "4") {
|
||||
return variant4
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
return Promise.resolve([testVariant])
|
||||
}),
|
||||
deleteOptionValue: jest.fn().mockImplementation((variantId, optionId) => {
|
||||
|
||||
@@ -43,10 +43,10 @@ export const ProductServiceMock = {
|
||||
delete: jest.fn().mockImplementation(_ => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
addVariant: jest.fn().mockImplementation((productId, variantId) => {
|
||||
createVariant: jest.fn().mockImplementation((productId, value) => {
|
||||
return Promise.resolve(products.productWithOptions)
|
||||
}),
|
||||
removeVariant: jest.fn().mockImplementation((productId, variantId) => {
|
||||
deleteVariant: jest.fn().mockImplementation((productId, variantId) => {
|
||||
return Promise.resolve(products.productWithOptions)
|
||||
}),
|
||||
decorate: jest.fn().mockImplementation((product, fields) => {
|
||||
@@ -56,8 +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)
|
||||
@@ -101,6 +109,7 @@ export const ProductServiceMock = {
|
||||
{
|
||||
_id: "1234",
|
||||
title: "test",
|
||||
thumbnail: "test.1234",
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
@@ -21,18 +21,21 @@ export const regions = {
|
||||
},
|
||||
regionUs: {
|
||||
_id: IdMap.getId("region-us"),
|
||||
tax_rate: 0.25,
|
||||
name: "USA",
|
||||
countries: ["US"],
|
||||
currency_code: "usd",
|
||||
},
|
||||
regionGermany: {
|
||||
_id: IdMap.getId("region-de"),
|
||||
tax_rate: 0.25,
|
||||
name: "Germany",
|
||||
countries: ["DE"],
|
||||
currency_code: "eur",
|
||||
},
|
||||
regionSweden: {
|
||||
_id: IdMap.getId("region-se"),
|
||||
tax_rate: 0.25,
|
||||
name: "Sweden",
|
||||
countries: ["SE"],
|
||||
currency_code: "sek",
|
||||
@@ -56,7 +59,7 @@ export const RegionServiceMock = {
|
||||
if (regionId === IdMap.getId("region-se")) {
|
||||
return Promise.resolve(regions.regionSweden)
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
throw Error(regionId + "not found")
|
||||
}),
|
||||
delete: jest.fn().mockImplementation(data => Promise.resolve()),
|
||||
create: jest.fn().mockImplementation(data => Promise.resolve()),
|
||||
|
||||
@@ -24,24 +24,22 @@ describe("LineItemService", () => {
|
||||
|
||||
it("generates line item and successfully defaults quantity of content to 1", () => {
|
||||
expect(result).toEqual({
|
||||
title: "test",
|
||||
description: "EUR10US-12",
|
||||
thumbnail: "test.1234",
|
||||
content: {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
title: "EUR10US-12",
|
||||
},
|
||||
product: {
|
||||
_id: "1234",
|
||||
title: "test",
|
||||
thumbnail: "test.1234",
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
product: {
|
||||
_id: "1234",
|
||||
title: "test",
|
||||
},
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
},
|
||||
quantity: 2,
|
||||
})
|
||||
})
|
||||
|
||||
@@ -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" },
|
||||
})
|
||||
})
|
||||
@@ -310,79 +310,168 @@ describe("ProductService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("addVariant", () => {
|
||||
describe("createVariant", () => {
|
||||
const productService = new ProductService({
|
||||
productModel: ProductModelMock,
|
||||
productVariantService: ProductVariantServiceMock,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("add variant to product successfilly", async () => {
|
||||
await productService.addVariant(IdMap.getId("variantProductId"), "1")
|
||||
await productService.createVariant(IdMap.getId("variantProductId"), {
|
||||
title: "variant1",
|
||||
options: [
|
||||
{
|
||||
option_id: IdMap.getId("color_id"),
|
||||
value: "blue",
|
||||
},
|
||||
{
|
||||
option_id: IdMap.getId("size_id"),
|
||||
value: "160",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(ProductVariantServiceMock.retrieve).toBeCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.retrieve).toBeCalledWith("1")
|
||||
expect(ProductModelMock.findOne).toBeCalledTimes(1)
|
||||
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: "160",
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
expect(ProductModelMock.findOne).toBeCalledTimes(2)
|
||||
expect(ProductModelMock.findOne).toBeCalledWith({
|
||||
_id: IdMap.getId("variantProductId"),
|
||||
})
|
||||
|
||||
expect(ProductModelMock.updateOne).toBeCalledTimes(1)
|
||||
expect(ProductModelMock.updateOne).toBeCalledWith(
|
||||
{ _id: IdMap.getId("variantProductId") },
|
||||
{ $push: { variants: "1" } }
|
||||
{ $push: { variants: expect.stringMatching(/.*/) } }
|
||||
)
|
||||
})
|
||||
|
||||
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 () => {
|
||||
try {
|
||||
await productService.addVariant(
|
||||
IdMap.getId("variantProductId"),
|
||||
"invalid_option"
|
||||
)
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"Variant options do not contain value for Color"
|
||||
)
|
||||
}
|
||||
await expect(
|
||||
productService.createVariant(IdMap.getId("variantProductId"), {
|
||||
title: "variant3",
|
||||
options: [
|
||||
{
|
||||
option_id: "invalid_id",
|
||||
value: "blue",
|
||||
},
|
||||
{
|
||||
option_id: IdMap.getId("size_id"),
|
||||
value: "150",
|
||||
},
|
||||
],
|
||||
})
|
||||
).rejects.toThrow("Variant options do not contain value for Color")
|
||||
})
|
||||
|
||||
it("throws error if product variant options is empty", async () => {
|
||||
try {
|
||||
await productService.addVariant(
|
||||
IdMap.getId("variantProductId"),
|
||||
"empty_option"
|
||||
)
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"Product options length does not match variant options length. Product has 2 and variant has 0."
|
||||
)
|
||||
}
|
||||
await expect(
|
||||
productService.createVariant(IdMap.getId("variantProductId"), {
|
||||
title: "variant3",
|
||||
options: [],
|
||||
})
|
||||
).rejects.toThrow(
|
||||
"Product options length does not match variant options length. Product has 2 and variant has 0."
|
||||
)
|
||||
})
|
||||
|
||||
it("throws error if product options is empty and product variant contains options", async () => {
|
||||
try {
|
||||
await productService.addVariant(
|
||||
IdMap.getId("emptyVariantProductId"),
|
||||
"1"
|
||||
)
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"Product options length does not match variant options length. Product has 0 and variant has 2."
|
||||
)
|
||||
}
|
||||
await expect(
|
||||
productService.createVariant(IdMap.getId("emptyVariantProductId"), {
|
||||
title: "variant1",
|
||||
options: [
|
||||
{
|
||||
option_id: IdMap.getId("color_id"),
|
||||
value: "blue",
|
||||
},
|
||||
{
|
||||
option_id: IdMap.getId("size_id"),
|
||||
value: "160",
|
||||
},
|
||||
],
|
||||
})
|
||||
).rejects.toThrow(
|
||||
"Product options length does not match variant options length. Product has 0 and variant has 2."
|
||||
)
|
||||
})
|
||||
|
||||
it("throws error if option values of added variant already exists", async () => {
|
||||
try {
|
||||
await productService.addVariant(IdMap.getId("productWithVariants"), "3")
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"Variant with provided options already exists"
|
||||
)
|
||||
}
|
||||
await expect(
|
||||
productService.createVariant(IdMap.getId("productWithVariants"), {
|
||||
title: "variant3",
|
||||
options: [
|
||||
{
|
||||
option_id: IdMap.getId("color_id"),
|
||||
value: "blue",
|
||||
},
|
||||
{
|
||||
option_id: IdMap.getId("size_id"),
|
||||
value: "150",
|
||||
},
|
||||
],
|
||||
})
|
||||
).rejects.toThrow("Variant with provided options already exists")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -539,7 +628,7 @@ describe("ProductService", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("removeVariant", () => {
|
||||
describe("deleteVariant", () => {
|
||||
const productService = new ProductService({
|
||||
productModel: ProductModelMock,
|
||||
productVariantService: ProductVariantServiceMock,
|
||||
@@ -550,11 +639,14 @@ describe("ProductService", () => {
|
||||
})
|
||||
|
||||
it("removes variant from product", async () => {
|
||||
await productService.removeVariant(
|
||||
await productService.deleteVariant(
|
||||
IdMap.getId("productWithVariants"),
|
||||
"1"
|
||||
)
|
||||
|
||||
expect(ProductVariantServiceMock.delete).toBeCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.delete).toBeCalledWith("1")
|
||||
|
||||
expect(ProductModelMock.updateOne).toBeCalledTimes(1)
|
||||
expect(ProductModelMock.updateOne).toBeCalledWith(
|
||||
{ _id: IdMap.getId("productWithVariants") },
|
||||
@@ -746,4 +838,55 @@ describe("ProductService", () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateOptionValue", () => {
|
||||
const productService = new ProductService({
|
||||
productModel: ProductModelMock,
|
||||
productVariantService: ProductVariantServiceMock,
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("successfully updates an option value", async () => {
|
||||
await productService.updateOptionValue(
|
||||
IdMap.getId("productWithVariants"),
|
||||
"1",
|
||||
IdMap.getId("color_id"),
|
||||
"Blue"
|
||||
)
|
||||
|
||||
expect(ProductVariantServiceMock.updateOptionValue).toBeCalledTimes(1)
|
||||
expect(ProductVariantServiceMock.updateOptionValue).toBeCalledWith(
|
||||
"1",
|
||||
IdMap.getId("color_id"),
|
||||
"Blue"
|
||||
)
|
||||
})
|
||||
|
||||
it("throws product-variant relationship isn't valid", async () => {
|
||||
await expect(
|
||||
productService.updateOptionValue(
|
||||
IdMap.getId("productWithFourVariants"),
|
||||
"invalid_variant",
|
||||
IdMap.getId("color_id"),
|
||||
"Blue"
|
||||
)
|
||||
).rejects.toThrow("The variant could not be found in the product")
|
||||
})
|
||||
|
||||
it("throws if combination exists", async () => {
|
||||
await expect(
|
||||
productService.updateOptionValue(
|
||||
IdMap.getId("productWithFourVariants"),
|
||||
"1",
|
||||
IdMap.getId("color_id"),
|
||||
"black"
|
||||
)
|
||||
).rejects.toThrow(
|
||||
"A variant with the given option value combination already exist"
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,6 +23,7 @@ class CartService extends BaseService {
|
||||
shippingOptionService,
|
||||
shippingProfileService,
|
||||
discountService,
|
||||
totalsService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
@@ -55,6 +56,9 @@ class CartService extends BaseService {
|
||||
|
||||
/** @private @const {DiscountService} */
|
||||
this.discountService_ = discountService
|
||||
|
||||
/** @private @const {DiscountService} */
|
||||
this.totalsService_ = totalsService
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -64,7 +68,7 @@ class CartService extends BaseService {
|
||||
*/
|
||||
validateId_(rawId) {
|
||||
const schema = Validator.objectId()
|
||||
const { value, error } = schema.validate(rawId)
|
||||
const { value, error } = schema.validate(rawId.toString())
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
@@ -221,7 +225,9 @@ class CartService extends BaseService {
|
||||
* @return {Cart} return the decorated cart.
|
||||
*/
|
||||
async decorate(cart, fields, expandFields = []) {
|
||||
return cart
|
||||
const c = cart.toObject()
|
||||
c.total = await this.totalsService_.getTotal(cart)
|
||||
return c
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -101,9 +101,10 @@ class LineItemService extends BaseService {
|
||||
)
|
||||
|
||||
return {
|
||||
variant,
|
||||
product,
|
||||
title: product.title,
|
||||
description: variant.title,
|
||||
quantity,
|
||||
thumbnail: product.thumbnail,
|
||||
content: {
|
||||
unit_price,
|
||||
variant,
|
||||
|
||||
@@ -28,7 +28,7 @@ class ProductVariantService extends BaseService {
|
||||
*/
|
||||
validateId_(rawId) {
|
||||
const schema = Validator.objectId()
|
||||
const { value, error } = schema.validate(rawId)
|
||||
const { value, error } = schema.validate(rawId.toString())
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
@@ -61,6 +61,7 @@ class ProductVariantService extends BaseService {
|
||||
return variant
|
||||
}
|
||||
|
||||
// TODO: Validate productVariant
|
||||
/**
|
||||
* Creates an unpublished product variant.
|
||||
* @param {object} variant - the variant to create
|
||||
@@ -296,6 +297,28 @@ class ProductVariantService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates variant's option value.
|
||||
* Option value must be of type string or number.
|
||||
* @param {string} variantId - the variant to decorate.
|
||||
* @param {string} optionId - the option from product.
|
||||
* @param {string | number} optionValue - option value to add.
|
||||
* @return {Promise} the result of the update operation.
|
||||
*/
|
||||
async updateOptionValue(variantId, optionId, optionValue) {
|
||||
if (typeof optionValue !== "string" && typeof optionValue !== "number") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Option value is not of type string or number`
|
||||
)
|
||||
}
|
||||
|
||||
return this.productVariantModel_.updateOne(
|
||||
{ _id: variantId, "options.option_id": optionId },
|
||||
{ $set: { "options.$.value": `${optionValue}` } }
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds option value to a varaint.
|
||||
* Fails when product with variant does not exists or
|
||||
|
||||
@@ -29,7 +29,7 @@ class ProductService extends BaseService {
|
||||
*/
|
||||
validateId_(rawId) {
|
||||
const schema = Validator.objectId()
|
||||
const { value, error } = schema.validate(rawId)
|
||||
const { value, error } = schema.validate(rawId.toString())
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
@@ -71,6 +71,16 @@ class ProductService extends BaseService {
|
||||
return product
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all variants belonging to a product.
|
||||
* @param {string} productId - the id of the product to get variants from.
|
||||
* @return {Promise} an array of variants
|
||||
*/
|
||||
async retrieveVariants(productId) {
|
||||
const product = await this.retrieve(productId)
|
||||
return this.productVariantService_.list({ _id: { $in: product.variants } })
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an unpublished product.
|
||||
* @param {object} product - the product to create
|
||||
@@ -171,11 +181,9 @@ class ProductService extends BaseService {
|
||||
* @param {string} variantId - the variant to add to the product
|
||||
* @return {Promise} the result of update
|
||||
*/
|
||||
async addVariant(productId, variantId) {
|
||||
async createVariant(productId, variant) {
|
||||
const product = await this.retrieve(productId)
|
||||
|
||||
const variant = await this.productVariantService_.retrieve(variantId)
|
||||
|
||||
if (product.options.length !== variant.options.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
@@ -184,7 +192,7 @@ class ProductService extends BaseService {
|
||||
}
|
||||
|
||||
product.options.forEach(option => {
|
||||
if (!variant.options.find(vo => vo.option_id === option._id)) {
|
||||
if (!variant.options.find(vo => option._id.equals(vo.option_id))) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Variant options do not contain value for ${option.title}`
|
||||
@@ -193,15 +201,18 @@ class ProductService extends BaseService {
|
||||
})
|
||||
|
||||
let combinationExists = false
|
||||
if (product.variants) {
|
||||
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
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -212,9 +223,11 @@ class ProductService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
const newVariant = await this.productVariantService_.createDraft(variant)
|
||||
|
||||
return this.productModel_.updateOne(
|
||||
{ _id: product._id },
|
||||
{ $push: { variants: variantId } }
|
||||
{ $push: { variants: newVariant._id } }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -414,11 +427,11 @@ class ProductService extends BaseService {
|
||||
async deleteOption(productId, optionId) {
|
||||
const product = await this.retrieve(productId)
|
||||
|
||||
if (!product.options.find(o => o._id === optionId)) {
|
||||
if (!product.options.find(o => o._id.equals(optionId))) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
if (product.variants) {
|
||||
if (product.variants.length) {
|
||||
// For the option we want to delete, make sure that all variants have the
|
||||
// same option values. The reason for doing is, that we want to avoid
|
||||
// duplicate variants. For example, if we have a product with size and
|
||||
@@ -430,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
|
||||
})
|
||||
)
|
||||
@@ -479,9 +492,11 @@ class ProductService extends BaseService {
|
||||
* @param {string} variantId - the variant to remove from product
|
||||
* @return {Promise} the result of update
|
||||
*/
|
||||
async removeVariant(productId, variantId) {
|
||||
async deleteVariant(productId, variantId) {
|
||||
const product = await this.retrieve(productId)
|
||||
|
||||
await this.productVariantService_.delete(variantId)
|
||||
|
||||
return this.productModel_.updateOne(
|
||||
{ _id: product._id },
|
||||
{
|
||||
@@ -492,6 +507,58 @@ class ProductService extends BaseService {
|
||||
)
|
||||
}
|
||||
|
||||
async updateOptionValue(productId, variantId, optionId, value) {
|
||||
const product = await this.retrieve(productId)
|
||||
|
||||
// Check if the product-to-variant relationship holds
|
||||
if (!product.variants.includes(variantId)) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"The variant could not be found in the product"
|
||||
)
|
||||
}
|
||||
|
||||
// Retrieve all variants
|
||||
const variants = await this.retrieveVariants(productId)
|
||||
const toUpdate = variants.find(v => v._id.equals(variantId))
|
||||
|
||||
// Check if an update would create duplicate variants
|
||||
const canUpdate = variants.every(v => {
|
||||
// The variant we update is irrelevant
|
||||
if (v._id.equals(variantId)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if the variant's options are identical to the variant we
|
||||
// are updating
|
||||
const hasMatchingOptions = v.options.every(option => {
|
||||
if (option.option_id.equals(optionId)) {
|
||||
return option.value === value
|
||||
}
|
||||
|
||||
const toUpdateOption = toUpdate.options.find(o =>
|
||||
option.option_id.equals(o.option_id)
|
||||
)
|
||||
return toUpdateOption.value === option.value
|
||||
})
|
||||
|
||||
return !hasMatchingOptions
|
||||
})
|
||||
|
||||
if (!canUpdate) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"A variant with the given option value combination already exist"
|
||||
)
|
||||
}
|
||||
|
||||
return this.productVariantService_.updateOptionValue(
|
||||
variantId,
|
||||
optionId,
|
||||
value
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a product with product variants.
|
||||
* @param {Product} product - the product to decorate.
|
||||
@@ -503,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
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ class RegionService extends BaseService {
|
||||
*/
|
||||
validateId_(rawId) {
|
||||
const schema = Validator.objectId()
|
||||
const { value, error } = schema.validate(rawId)
|
||||
const { value, error } = schema.validate(rawId.toString())
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
@@ -195,6 +195,16 @@ class RegionService extends BaseService {
|
||||
return region
|
||||
}
|
||||
|
||||
/**
|
||||
* Lists all regions based on a query
|
||||
* @param {string} regionId - the id of the region to retrieve
|
||||
* @return {Region} the region
|
||||
*/
|
||||
async list(query) {
|
||||
const regions = await this.regionModel_.find(query)
|
||||
return regions
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a region.
|
||||
* @param {string} regionId - the region to delete
|
||||
|
||||
@@ -74,7 +74,7 @@ class TotalsService extends BaseService {
|
||||
async getTaxTotal(object) {
|
||||
const subtotal = this.getSubtotal(object)
|
||||
const shippingTotal = this.getShippingTotal(object)
|
||||
const region = await this.regionService_.retrieve(object.region)
|
||||
const region = await this.regionService_.retrieve(object.region_id)
|
||||
const { tax_rate } = region
|
||||
return (subtotal + shippingTotal) * tax_rate
|
||||
}
|
||||
@@ -101,7 +101,7 @@ class TotalsService extends BaseService {
|
||||
|
||||
const subtotal = this.getSubtotal({ items: lineItems })
|
||||
|
||||
const region = await this.regionService_.retrieve(order.region)
|
||||
const region = await this.regionService_.retrieve(order.region_id)
|
||||
|
||||
// if nothing is discounted, return the subtotal of line items
|
||||
if (!discount) {
|
||||
|
||||
@@ -26,7 +26,7 @@ class UserService extends BaseService {
|
||||
*/
|
||||
validateId_(rawId) {
|
||||
const schema = Validator.objectId()
|
||||
const { value, error } = schema.validate(rawId)
|
||||
const { value, error } = schema.validate(rawId.toString())
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
|
||||
@@ -4522,14 +4522,6 @@ media-typer@0.3.0:
|
||||
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
|
||||
integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=
|
||||
|
||||
medusa-core-utils@^0.3.0:
|
||||
version "0.1.39"
|
||||
resolved "https://registry.yarnpkg.com/medusa-core-utils/-/medusa-core-utils-0.1.39.tgz#d57816c9bd43f9a92883650c1e66add1665291df"
|
||||
integrity sha512-R8+U1ile7if+nR6Cjh5exunx0ETV0OfkWUUBUpz1KmHSDv0V0CcvQqU9lcZesPFDEbu3Y2iEjsCqidVA4nG2nQ==
|
||||
dependencies:
|
||||
"@hapi/joi" "^16.1.8"
|
||||
joi-objectid "^3.0.1"
|
||||
|
||||
medusa-interfaces@^0.1.27:
|
||||
version "0.1.27"
|
||||
resolved "https://registry.yarnpkg.com/medusa-interfaces/-/medusa-interfaces-0.1.27.tgz#e77f9a9f82a7118eac8b35c1498ef8a5cec78898"
|
||||
@@ -4537,13 +4529,6 @@ medusa-interfaces@^0.1.27:
|
||||
dependencies:
|
||||
mongoose "^5.8.0"
|
||||
|
||||
medusa-test-utils@^0.3.0:
|
||||
version "0.1.39"
|
||||
resolved "https://registry.yarnpkg.com/medusa-test-utils/-/medusa-test-utils-0.1.39.tgz#b7c166006a2fa4f02e52ab3bfafc19a3ae787f3e"
|
||||
integrity sha512-M/Br8/HYvl7x2oLnme4NxdQwoyV0XUyOWiCyvPp7q1HUTB684lhJf1MikZVrcSjsh2L1rpyi3GRbKdf4cpJWvw==
|
||||
dependencies:
|
||||
mongoose "^5.8.0"
|
||||
|
||||
memory-pager@^1.0.2:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/memory-pager/-/memory-pager-1.5.0.tgz#d8751655d22d384682741c972f2c3d6dfa3e66b5"
|
||||
|
||||
Reference in New Issue
Block a user