Adds admin API endpoints for product operations (#32)
This commit is contained in:
@@ -0,0 +1,43 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||||
|
|
||||||
|
describe("POST /admin/products/:id/options", () => {
|
||||||
|
describe("successful add option", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/products/${IdMap.getId("productWithOptions")}/options`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
optionTitle: "Test option",
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls service addOption", () => {
|
||||||
|
expect(ProductServiceMock.addOption).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ProductServiceMock.addOption).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("productWithOptions"),
|
||||||
|
"Test option"
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns the updated product decorated", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("productWithOptions"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -23,16 +23,12 @@ describe("POST /admin/products", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("returns 200", () => {
|
it("returns 200", () => {
|
||||||
expect(subject.status).toEqual(201)
|
expect(subject.status).toEqual(200)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("returns created product draft", () => {
|
it("returns created product draft", () => {
|
||||||
expect(subject.body).toEqual({
|
expect(subject.body._id).toEqual(IdMap.getId("product1"))
|
||||||
title: "Test Product",
|
expect(subject.body.decorated).toEqual(true)
|
||||||
description: "Test Description",
|
|
||||||
tags: "hi,med,dig",
|
|
||||||
handle: "test-product",
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls service createDraft", () => {
|
it("calls service createDraft", () => {
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||||
|
|
||||||
|
describe("DELETE /admin/products/:id/options/:optionId", () => {
|
||||||
|
describe("successfully updates an option", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"DELETE",
|
||||||
|
`/admin/products/${IdMap.getId(
|
||||||
|
"productWithOptions"
|
||||||
|
)}/options/${IdMap.getId("option1")}`,
|
||||||
|
{
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200 and correct delete info", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
expect(subject.body).toEqual({
|
||||||
|
optionId: IdMap.getId("option1"),
|
||||||
|
object: "option",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls update", () => {
|
||||||
|
expect(ProductServiceMock.deleteOption).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ProductServiceMock.deleteOption).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("productWithOptions"),
|
||||||
|
IdMap.getId("option1")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||||
|
|
||||||
|
describe("DELETE /admin/products/:id", () => {
|
||||||
|
describe("successfully deletes a product", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"DELETE",
|
||||||
|
`/admin/products/${IdMap.getId("product1")}`,
|
||||||
|
{
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls ProductService delete", () => {
|
||||||
|
expect(ProductServiceMock.delete).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ProductServiceMock.delete).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("product1")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns correct delete data", () => {
|
||||||
|
expect(subject.body).toEqual({
|
||||||
|
id: IdMap.getId("product1"),
|
||||||
|
object: "product",
|
||||||
|
deleted: 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", () => {
|
||||||
|
describe("successfully gets a product", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"GET",
|
||||||
|
`/admin/products/${IdMap.getId("product1")}`,
|
||||||
|
{
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
jest.clearAllMocks()
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls get product from productSerice", () => {
|
||||||
|
expect(ProductServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ProductServiceMock.retrieve).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("product1")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns product decorated", () => {
|
||||||
|
expect(subject.body._id).toEqual(IdMap.getId("product1"))
|
||||||
|
expect(subject.body.decorated).toEqual(true)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import {
|
||||||
|
ProductServiceMock,
|
||||||
|
products,
|
||||||
|
} from "../../../../../services/__mocks__/product"
|
||||||
|
|
||||||
|
describe("GET /admin/products", () => {
|
||||||
|
describe("successfully lists products", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request("GET", `/admin/products`, {
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
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)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls update", () => {
|
||||||
|
expect(ProductServiceMock.list).toHaveBeenCalledTimes(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||||
|
|
||||||
|
describe("POST /admin/products/:id/publish", () => {
|
||||||
|
describe("successful publish", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/products/${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(ProductServiceMock.publish).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ProductServiceMock.publish).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("publish")
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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 removes variant", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"DELETE",
|
||||||
|
`/admin/products/${IdMap.getId(
|
||||||
|
"productWithOptions"
|
||||||
|
)}/variants/${IdMap.getId("variant1")}`,
|
||||||
|
{
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls service removeVariant", () => {
|
||||||
|
expect(ProductServiceMock.removeVariant).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ProductServiceMock.removeVariant).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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||||
|
|
||||||
|
describe("POST /admin/products/:id/options/:optionId", () => {
|
||||||
|
describe("successfully updates an option", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/products/${IdMap.getId(
|
||||||
|
"productWithOptions"
|
||||||
|
)}/options/${IdMap.getId("option1")}`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
title: "Updated option title",
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls update", () => {
|
||||||
|
expect(ProductServiceMock.updateOption).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ProductServiceMock.updateOption).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("productWithOptions"),
|
||||||
|
IdMap.getId("option1"),
|
||||||
|
{
|
||||||
|
title: "Updated option title",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||||
|
|
||||||
|
describe("POST /admin/products/:id", () => {
|
||||||
|
describe("successfully updates a product", () => {
|
||||||
|
let subject
|
||||||
|
|
||||||
|
beforeAll(async () => {
|
||||||
|
subject = await request(
|
||||||
|
"POST",
|
||||||
|
`/admin/products/${IdMap.getId("product1")}`,
|
||||||
|
{
|
||||||
|
payload: {
|
||||||
|
title: "Product 1",
|
||||||
|
description: "Updated test description",
|
||||||
|
handle: "handle",
|
||||||
|
},
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("returns 200", () => {
|
||||||
|
expect(subject.status).toEqual(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it("calls update", () => {
|
||||||
|
expect(ProductServiceMock.update).toHaveBeenCalledTimes(1)
|
||||||
|
expect(ProductServiceMock.update).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("product1"),
|
||||||
|
{
|
||||||
|
title: "Product 1",
|
||||||
|
description: "Updated test description",
|
||||||
|
handle: "handle",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe("handles failed update operation", () => {
|
||||||
|
it("throws if metadata is to be updated", async () => {
|
||||||
|
try {
|
||||||
|
await request("POST", `/admin/products/${IdMap.getId("product1")}`, {
|
||||||
|
payload: {
|
||||||
|
_id: IdMap.getId("product1"),
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it("throws if variants is to be updated", async () => {
|
||||||
|
try {
|
||||||
|
await request("POST", `/admin/products/${IdMap.getId("product1")}`, {
|
||||||
|
payload: {
|
||||||
|
_id: IdMap.getId("product1"),
|
||||||
|
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 addVariant, reorderVariants, removeVariant to update Product Variants"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
optionTitle: Validator.string().required(),
|
||||||
|
})
|
||||||
|
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.retrieve(id)
|
||||||
|
await productService.addOption(product._id, value.optionTitle)
|
||||||
|
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) {
|
||||||
|
console.log(err)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,10 +18,18 @@ export default async (req, res) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const productService = req.scope.resolve("productService")
|
const productService = req.scope.resolve("productService")
|
||||||
const data = await productService.createDraft(value)
|
let newProduct = await productService.createDraft(value)
|
||||||
|
newProduct = await productService.decorate(newProduct, [
|
||||||
// Return the created product draft
|
"title",
|
||||||
res.status(201).json(data)
|
"description",
|
||||||
|
"tags",
|
||||||
|
"handle",
|
||||||
|
"images",
|
||||||
|
"options",
|
||||||
|
"variants",
|
||||||
|
"published",
|
||||||
|
])
|
||||||
|
res.json(newProduct)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
const { id, optionId } = req.params
|
||||||
|
|
||||||
|
try {
|
||||||
|
const productService = req.scope.resolve("productService")
|
||||||
|
await productService.deleteOption(id, optionId)
|
||||||
|
res.json({
|
||||||
|
optionId,
|
||||||
|
object: "option",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
try {
|
||||||
|
const productService = req.scope.resolve("productService")
|
||||||
|
await productService.delete(id)
|
||||||
|
res.json({
|
||||||
|
id,
|
||||||
|
object: "product",
|
||||||
|
deleted: true,
|
||||||
|
})
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
const productService = req.scope.resolve("productService")
|
||||||
|
let product = await productService.retrieve(id)
|
||||||
|
|
||||||
|
product = await productService.decorate(product, [
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"tags",
|
||||||
|
"handle",
|
||||||
|
"images",
|
||||||
|
"options",
|
||||||
|
"variants",
|
||||||
|
"published",
|
||||||
|
])
|
||||||
|
|
||||||
|
res.json(product)
|
||||||
|
}
|
||||||
@@ -7,8 +7,33 @@ export default app => {
|
|||||||
app.use("/products", route)
|
app.use("/products", route)
|
||||||
|
|
||||||
route.post("/", middlewares.wrap(require("./create-product").default))
|
route.post("/", middlewares.wrap(require("./create-product").default))
|
||||||
|
route.post("/:id", middlewares.wrap(require("./update-product").default))
|
||||||
|
route.post(
|
||||||
|
"/:id/publish",
|
||||||
|
middlewares.wrap(require("./publish-product").default)
|
||||||
|
)
|
||||||
|
route.post(
|
||||||
|
"/:id/variants/:variantId",
|
||||||
|
middlewares.wrap(require("./add-variant").default)
|
||||||
|
)
|
||||||
|
route.post(
|
||||||
|
"/:id/options/:optionId",
|
||||||
|
middlewares.wrap(require("./update-option").default)
|
||||||
|
)
|
||||||
|
route.post("/:id/options", middlewares.wrap(require("./add-option").default))
|
||||||
|
|
||||||
// route.get("/:productId", middlewares.wrap(require("./get-product").default))
|
route.delete(
|
||||||
|
"/:id/variants/:variantId",
|
||||||
|
middlewares.wrap(require("./remove-variant").default)
|
||||||
|
)
|
||||||
|
route.delete("/:id", middlewares.wrap(require("./delete-product").default))
|
||||||
|
route.delete(
|
||||||
|
"/:id/options/:optionId",
|
||||||
|
middlewares.wrap(require("./delete-option").default)
|
||||||
|
)
|
||||||
|
|
||||||
|
route.get("/:id", middlewares.wrap(require("./get-product").default))
|
||||||
|
route.get("/", middlewares.wrap(require("./list-products").default))
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
try {
|
||||||
|
const productService = req.scope.resolve("productService")
|
||||||
|
let products = await productService.list({})
|
||||||
|
products = await Promise.all(
|
||||||
|
products.map(
|
||||||
|
async product =>
|
||||||
|
await productService.decorate(product, [
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"tags",
|
||||||
|
"handle",
|
||||||
|
"images",
|
||||||
|
"options",
|
||||||
|
"variants",
|
||||||
|
"published",
|
||||||
|
])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
res.json(products)
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
const { id } = req.params
|
||||||
|
|
||||||
|
try {
|
||||||
|
const productService = req.scope.resolve("productService")
|
||||||
|
const product = await productService.retrieve(id)
|
||||||
|
await productService.publish(product._id)
|
||||||
|
let publishedProduct = await productService.retrieve(product._id)
|
||||||
|
publishedProduct = await productService.decorate(publishedProduct, [
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"tags",
|
||||||
|
"handle",
|
||||||
|
"images",
|
||||||
|
"options",
|
||||||
|
"variants",
|
||||||
|
"published",
|
||||||
|
])
|
||||||
|
res.json(publishedProduct)
|
||||||
|
} catch (error) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
export default async (req, res) => {
|
||||||
|
const { id, variantId } = 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, [
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"tags",
|
||||||
|
"handle",
|
||||||
|
"images",
|
||||||
|
"options",
|
||||||
|
"variants",
|
||||||
|
"published",
|
||||||
|
])
|
||||||
|
res.json(updatedProduct)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { MedusaError, Validator } from "medusa-core-utils"
|
||||||
|
|
||||||
|
export default async (req, res) => {
|
||||||
|
const { id, optionId } = req.params
|
||||||
|
|
||||||
|
const schema = Validator.object().keys({
|
||||||
|
title: Validator.string(),
|
||||||
|
values: Validator.array().items(),
|
||||||
|
})
|
||||||
|
|
||||||
|
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.retrieve(id)
|
||||||
|
|
||||||
|
await productService.updateOption(product._id, optionId, value)
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
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(),
|
||||||
|
description: Validator.string().optional(),
|
||||||
|
tags: Validator.string().optional(),
|
||||||
|
handle: Validator.string().required(),
|
||||||
|
images: Validator.array()
|
||||||
|
.items(Validator.string())
|
||||||
|
.optional(),
|
||||||
|
variants: Validator.array()
|
||||||
|
.items(Validator.string())
|
||||||
|
.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 oldProduct = await productService.retrieve(id)
|
||||||
|
await productService.update(oldProduct._id, value)
|
||||||
|
let newProduct = await productService.retrieve(oldProduct._id)
|
||||||
|
newProduct = await productService.decorate(newProduct, [
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"tags",
|
||||||
|
"handle",
|
||||||
|
"images",
|
||||||
|
"options",
|
||||||
|
"variants",
|
||||||
|
"published",
|
||||||
|
])
|
||||||
|
res.json(newProduct)
|
||||||
|
} catch (err) {
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,40 +1,24 @@
|
|||||||
import mongoose from "mongoose"
|
import mongoose from "mongoose"
|
||||||
import getProduct from "../get-product"
|
import getProduct from "../get-product"
|
||||||
|
import { request } from "../../../../../helpers/test-request"
|
||||||
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { ProductServiceMock } from "../../../../../services/__mocks__/product"
|
||||||
|
|
||||||
describe("Get product by id", () => {
|
describe("Get product by id", () => {
|
||||||
const testId = `${mongoose.Types.ObjectId("56cb91bdc3464f14678934ca")}`
|
|
||||||
const productServiceMock = {
|
|
||||||
retrieve: jest.fn().mockImplementation(id => {
|
|
||||||
if (id === testId) {
|
|
||||||
return Promise.resolve({ _id: id, title: "test" })
|
|
||||||
}
|
|
||||||
return Promise.resolve(undefined)
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
const reqMock = id => {
|
|
||||||
return {
|
|
||||||
params: {
|
|
||||||
productId: id,
|
|
||||||
},
|
|
||||||
scope: {
|
|
||||||
resolve: jest.fn().mockImplementation(name => {
|
|
||||||
if (name === "productService") {
|
|
||||||
return productServiceMock
|
|
||||||
}
|
|
||||||
return undefined
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resMock = {
|
|
||||||
sendStatus: jest.fn().mockReturnValue(),
|
|
||||||
json: jest.fn().mockReturnValue(),
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("get product by id successfull", () => {
|
describe("get product by id successfull", () => {
|
||||||
|
let subject
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await getProduct(reqMock(testId), resMock)
|
subject = await request(
|
||||||
|
"GET",
|
||||||
|
`/admin/products/${IdMap.getId("product1")}`,
|
||||||
|
{
|
||||||
|
adminSession: {
|
||||||
|
jwt: {
|
||||||
|
userId: IdMap.getId("admin_user"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
@@ -42,52 +26,15 @@ describe("Get product by id", () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it("calls get product from productSerice", () => {
|
it("calls get product from productSerice", () => {
|
||||||
expect(productServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
expect(ProductServiceMock.retrieve).toHaveBeenCalledTimes(1)
|
||||||
expect(productServiceMock.retrieve).toHaveBeenCalledWith(testId)
|
expect(ProductServiceMock.retrieve).toHaveBeenCalledWith(
|
||||||
|
IdMap.getId("product1")
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
it("calls res.json", () => {
|
it("returns product decorated", () => {
|
||||||
expect(resMock.json).toHaveBeenCalledTimes(1)
|
expect(subject.body._id).toEqual(IdMap.getId("product1"))
|
||||||
expect(resMock.json).toHaveBeenCalledWith({
|
expect(subject.body.decorated).toEqual(true)
|
||||||
_id: testId,
|
|
||||||
title: "test",
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("returns 404 when product not found", () => {
|
|
||||||
beforeAll(async () => {
|
|
||||||
const id = mongoose.Types.ObjectId()
|
|
||||||
await getProduct(reqMock(`${id}`), resMock)
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("return 404", () => {
|
|
||||||
expect(resMock.sendStatus).toHaveBeenCalledTimes(1)
|
|
||||||
expect(resMock.json).toHaveBeenCalledTimes(0)
|
|
||||||
expect(resMock.sendStatus).toHaveBeenCalledWith(404)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe("fails when validation fails", () => {
|
|
||||||
let res
|
|
||||||
beforeAll(async () => {
|
|
||||||
try {
|
|
||||||
await getProduct(reqMock(`not object id`), resMock)
|
|
||||||
} catch (err) {
|
|
||||||
res = err
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
afterAll(() => {
|
|
||||||
jest.clearAllMocks()
|
|
||||||
})
|
|
||||||
|
|
||||||
it("return 404", () => {
|
|
||||||
expect(res.name).toEqual("ValidationError")
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,12 +11,18 @@ export default async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const productService = req.scope.resolve("productService")
|
const productService = req.scope.resolve("productService")
|
||||||
const product = await productService.retrieve(value)
|
let product = await productService.retrieve(value)
|
||||||
|
|
||||||
if (!product) {
|
product = await productService.decorate(product, [
|
||||||
res.sendStatus(404)
|
"title",
|
||||||
return
|
"description",
|
||||||
}
|
"tags",
|
||||||
|
"handle",
|
||||||
|
"images",
|
||||||
|
"options",
|
||||||
|
"variants",
|
||||||
|
"published",
|
||||||
|
])
|
||||||
|
|
||||||
res.json(product)
|
res.json(product)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,84 @@
|
|||||||
import { IdMap } from "medusa-test-utils"
|
import { IdMap } from "medusa-test-utils"
|
||||||
|
import { MedusaError } from "medusa-core-utils"
|
||||||
|
|
||||||
export const products = {
|
export const products = {
|
||||||
product1: {
|
product1: {
|
||||||
_id: IdMap.getId("product1"),
|
_id: IdMap.getId("product1"),
|
||||||
name: "Product 1",
|
title: "Product 1",
|
||||||
|
},
|
||||||
|
publishProduct: {
|
||||||
|
_id: IdMap.getId("publish"),
|
||||||
|
title: "Product 1",
|
||||||
|
published: true,
|
||||||
},
|
},
|
||||||
product2: {
|
product2: {
|
||||||
_id: IdMap.getId("product2"),
|
_id: IdMap.getId("product2"),
|
||||||
name: "Product 2",
|
title: "Product 2",
|
||||||
|
},
|
||||||
|
productWithOptions: {
|
||||||
|
_id: IdMap.getId("productWithOptions"),
|
||||||
|
title: "Test",
|
||||||
|
variants: [IdMap.getId("variant1")],
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
_id: IdMap.getId("option1"),
|
||||||
|
title: "Test",
|
||||||
|
values: [IdMap.getId("optionValue1")],
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ProductServiceMock = {
|
export const ProductServiceMock = {
|
||||||
createDraft: jest.fn().mockImplementation(data => {
|
createDraft: jest.fn().mockImplementation(data => {
|
||||||
return Promise.resolve(data)
|
return Promise.resolve(products.product1)
|
||||||
}),
|
}),
|
||||||
retrieve: jest.fn().mockImplementation(id => {
|
publish: jest.fn().mockImplementation(_ => {
|
||||||
if (id === IdMap.getId("validId")) {
|
return Promise.resolve({
|
||||||
return Promise.resolve({ _id: IdMap.getId("validId") })
|
_id: IdMap.getId("publish"),
|
||||||
}
|
name: "Product 1",
|
||||||
if (id === IdMap.getId("product1")) {
|
published: true,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
delete: jest.fn().mockImplementation(_ => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
|
addVariant: jest.fn().mockImplementation((productId, variantId) => {
|
||||||
|
return Promise.resolve(products.productWithOptions)
|
||||||
|
}),
|
||||||
|
removeVariant: jest.fn().mockImplementation((productId, variantId) => {
|
||||||
|
return Promise.resolve(products.productWithOptions)
|
||||||
|
}),
|
||||||
|
decorate: jest.fn().mockImplementation((product, fields) => {
|
||||||
|
product.decorated = true
|
||||||
|
return product
|
||||||
|
}),
|
||||||
|
addOption: jest.fn().mockImplementation((productId, optionTitle) => {
|
||||||
|
return Promise.resolve(products.productWithOptions)
|
||||||
|
}),
|
||||||
|
updateOption: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
deleteOption: jest.fn().mockReturnValue(Promise.resolve()),
|
||||||
|
retrieve: jest.fn().mockImplementation(productId => {
|
||||||
|
if (productId === IdMap.getId("product1")) {
|
||||||
return Promise.resolve(products.product1)
|
return Promise.resolve(products.product1)
|
||||||
}
|
}
|
||||||
if (id === IdMap.getId("product2")) {
|
if (productId === IdMap.getId("product2")) {
|
||||||
return Promise.resolve(products.product2)
|
return Promise.resolve(products.product2)
|
||||||
}
|
}
|
||||||
|
if (productId === IdMap.getId("validId")) {
|
||||||
|
return Promise.resolve({ _id: IdMap.getId("validId") })
|
||||||
|
}
|
||||||
|
if (productId === IdMap.getId("publish")) {
|
||||||
|
return Promise.resolve(products.publishProduct)
|
||||||
|
}
|
||||||
|
if (productId === IdMap.getId("productWithOptions")) {
|
||||||
|
return Promise.resolve(products.productWithOptions)
|
||||||
|
}
|
||||||
return Promise.resolve(undefined)
|
return Promise.resolve(undefined)
|
||||||
}),
|
}),
|
||||||
|
update: jest.fn().mockImplementation((userId, data) => {
|
||||||
|
return Promise.resolve()
|
||||||
|
}),
|
||||||
list: jest.fn().mockImplementation(data => {
|
list: jest.fn().mockImplementation(data => {
|
||||||
// Used to retrieve a product based on a variant id see
|
// Used to retrieve a product based on a variant id see
|
||||||
// ProductVariantService.addOptionValue
|
// ProductVariantService.addOptionValue
|
||||||
@@ -56,7 +108,10 @@ export const ProductServiceMock = {
|
|||||||
return Promise.resolve([])
|
return Promise.resolve([])
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve([products.product1, products.product2])
|
return Promise.resolve([
|
||||||
|
{ ...products.product1, decorated: true },
|
||||||
|
{ ...products.product2, decorated: true },
|
||||||
|
])
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -379,7 +379,7 @@ class ProductService extends BaseService {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title } = data
|
const { title, values } = data
|
||||||
const titleExists = product.options.some(
|
const titleExists = product.options.some(
|
||||||
o => o.title.toUpperCase() === title.toUpperCase()
|
o => o.title.toUpperCase() === title.toUpperCase()
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user