diff --git a/packages/medusa/src/api/index.js b/packages/medusa/src/api/index.js index d570a6ffea..ccfa33175d 100644 --- a/packages/medusa/src/api/index.js +++ b/packages/medusa/src/api/index.js @@ -2,6 +2,7 @@ import { Router } from "express" import admin from "./routes/admin" import store from "./routes/store" import users from "./routes/users" +import errorHandler from "./middlewares/error-handler" // guaranteed to get dependencies export default () => { @@ -11,5 +12,7 @@ export default () => { admin(app) store(app) + app.use(errorHandler()) + return app } diff --git a/packages/medusa/src/api/middlewares/error-handler.js b/packages/medusa/src/api/middlewares/error-handler.js new file mode 100644 index 0000000000..4f7c932ecb --- /dev/null +++ b/packages/medusa/src/api/middlewares/error-handler.js @@ -0,0 +1,22 @@ +import { MedusaError } from "medusa-core-utils" + +export default () => { + return (err, req, res, next) => { + const logger = req.scope.resolve("logger") + logger.error(err.message) + + let statusCode = 500 + switch (err.name) { + case MedusaError.Types.INVALID_DATA: + statusCode = 400 + break + case MedusaError.Types.DB_ERROR: + statusCode = 500 + break + default: + break + } + + res.status(statusCode).json(err) + } +} diff --git a/packages/medusa/src/api/routes/admin/products/__tests__/create-product.js b/packages/medusa/src/api/routes/admin/products/__tests__/create-product.js index e241a6846b..2d91b0c994 100644 --- a/packages/medusa/src/api/routes/admin/products/__tests__/create-product.js +++ b/packages/medusa/src/api/routes/admin/products/__tests__/create-product.js @@ -1,17 +1,76 @@ import IdMap from "../../../../../helpers/id-map" import { request } from "../../../../../helpers/test-request" +import { ProductServiceMock } from "../../../../../services/__mocks__/product" describe("POST /admin/products", () => { describe("successful creation", () => { - it("calls mock function", async () => { - const res = await request("POST", "/admin/products", { + let subject + + beforeAll(async () => { + subject = await request("POST", "/admin/products", { + payload: { + title: "Test Product", + description: "Test Description", + tags: "hi,med,dig", + handle: "test-product", + }, adminSession: { jwt: { userId: IdMap.getId("admin_user"), }, }, }) - expect(res.status).toEqual(200) + }) + + it("returns 200", () => { + expect(subject.status).toEqual(201) + }) + + it("returns created product draft", () => { + expect(subject.body).toEqual({ + title: "Test Product", + description: "Test Description", + tags: "hi,med,dig", + handle: "test-product", + }) + }) + + it("calls service createDraft", () => { + expect(ProductServiceMock.createDraft).toHaveBeenCalledTimes(1) + expect(ProductServiceMock.createDraft).toHaveBeenCalledWith({ + title: "Test Product", + description: "Test Description", + tags: "hi,med,dig", + handle: "test-product", + }) + }) + }) + + describe("invalid data returns error details", () => { + let subject + + beforeAll(async () => { + subject = await request("POST", "/admin/products", { + payload: { + description: "Test Description", + tags: "hi,med,dig", + handle: "test-product", + }, + 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`) }) }) }) diff --git a/packages/medusa/src/api/routes/admin/products/create-product.js b/packages/medusa/src/api/routes/admin/products/create-product.js index 64fb0c47ed..901a29b48f 100644 --- a/packages/medusa/src/api/routes/admin/products/create-product.js +++ b/packages/medusa/src/api/routes/admin/products/create-product.js @@ -1,11 +1,28 @@ -import { Validator } from "medusa-core-utils" +import { MedusaError, Validator } from "medusa-core-utils" export default async (req, res) => { - try { - const variantService = req.scope.resolve("productVariantService") - } catch (err) { - console.log(err) + const schema = Validator.object().keys({ + title: Validator.string().required(), + description: Validator.string(), + tags: Validator.string(), + images: Validator.array().items(Validator.string()), + variants: Validator.array().items(Validator.string()), + metadata: Validator.object(), + handle: Validator.string().required(), + }) + + const { value, error } = schema.validate(req.body) + if (error) { + throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details) } - res.sendStatus(200) + try { + const productService = req.scope.resolve("productService") + const data = await productService.createDraft(value) + + // Return the created product draft + res.status(201).json(data) + } catch (err) { + throw err + } } diff --git a/packages/medusa/src/app.js b/packages/medusa/src/app.js index a5a5d38fb3..ccf5efae6b 100644 --- a/packages/medusa/src/app.js +++ b/packages/medusa/src/app.js @@ -1,7 +1,6 @@ import "core-js/stable" import "regenerator-runtime/runtime" import express from "express" -import { MedusaError } from "medusa-core-utils" import loaders from "./loaders" import Logger from "./loaders/logger" @@ -12,28 +11,6 @@ const startServer = async () => { await loaders({ expressApp: app }) - app.use((err, req, res, next) => { - const logger = req.scope.resolve("logger") - logger.error(err.message) - - let statusCode = 500 - switch (err.name) { - case "ValidationError": - statusCode = 400 - break - case MedusaError.Types.INVALID_DATA: - statusCode = 400 - break - case MedusaError.Types.DB_ERROR: - statusCode = 500 - break - default: - break - } - - res.json(err).status(statusCode) - }) - app.listen(PORT, err => { if (err) { console.log(err) diff --git a/packages/medusa/src/helpers/test-request.js b/packages/medusa/src/helpers/test-request.js index dc34ce961f..69f39187dc 100644 --- a/packages/medusa/src/helpers/test-request.js +++ b/packages/medusa/src/helpers/test-request.js @@ -1,4 +1,4 @@ -import { createContainer } from "awilix" +import { createContainer, asValue } from "awilix" import express from "express" import supertest from "supertest" import jwt from "jsonwebtoken" @@ -13,6 +13,11 @@ import config from "../config" const testApp = express() const container = createContainer() +container.register({ + logger: asValue({ + error: () => {}, + }), +}) servicesLoader({ container }) expressLoader({ app: testApp }) @@ -29,13 +34,13 @@ apiLoader({ app: testApp }) const supertestRequest = supertest(testApp) let adminSessionOpts = { - cookieName: "adminSession", + cookieName: "session", secret: "test", } export { adminSessionOpts } let clientSessionOpts = { - cookieName: "clientSession", + cookieName: "session", secret: "test", } export { clientSessionOpts } diff --git a/packages/medusa/src/services/__mocks__/product-variant.js b/packages/medusa/src/services/__mocks__/product-variant.js index 1821e85f10..233bd28026 100644 --- a/packages/medusa/src/services/__mocks__/product-variant.js +++ b/packages/medusa/src/services/__mocks__/product-variant.js @@ -90,39 +90,39 @@ export const variants = { empty_variant: emptyVariant, } +export const ProductVariantServiceMock = { + retrieve: jest.fn().mockImplementation(variantId => { + if (variantId === "1") { + return Promise.resolve(variant1) + } + if (variantId === "2") { + return Promise.resolve(variant2) + } + if (variantId === "3") { + return Promise.resolve(variant3) + } + if (variantId === "4") { + return Promise.resolve(variant4) + } + if (variantId === "invalid_option") { + return Promise.resolve(invalidVariant) + } + if (variantId === "empty_option") { + return Promise.resolve(emptyVariant) + } + return Promise.resolve(undefined) + }), + delete: jest.fn().mockReturnValue(Promise.resolve()), + addOptionValue: jest.fn().mockImplementation((variantId, optionId, value) => { + return Promise.resolve({}) + }), + deleteOptionValue: jest.fn().mockImplementation((variantId, optionId) => { + return Promise.resolve({}) + }), +} + const mock = jest.fn().mockImplementation(() => { - return { - retrieve: jest.fn().mockImplementation(variantId => { - if (variantId === "1") { - return Promise.resolve(variant1) - } - if (variantId === "2") { - return Promise.resolve(variant2) - } - if (variantId === "3") { - return Promise.resolve(variant3) - } - if (variantId === "4") { - return Promise.resolve(variant4) - } - if (variantId === "invalid_option") { - return Promise.resolve(invalidVariant) - } - if (variantId === "empty_option") { - return Promise.resolve(emptyVariant) - } - return Promise.resolve(undefined) - }), - delete: jest.fn().mockReturnValue(Promise.resolve()), - addOptionValue: jest - .fn() - .mockImplementation((variantId, optionId, value) => { - return Promise.resolve({}) - }), - deleteOptionValue: jest.fn().mockImplementation((variantId, optionId) => { - return Promise.resolve({}) - }), - } + return ProductVariantServiceMock }) export default mock diff --git a/packages/medusa/src/services/__mocks__/product.js b/packages/medusa/src/services/__mocks__/product.js new file mode 100644 index 0000000000..74c79606ef --- /dev/null +++ b/packages/medusa/src/services/__mocks__/product.js @@ -0,0 +1,11 @@ +export const ProductServiceMock = { + createDraft: jest.fn().mockImplementation(data => { + return Promise.resolve(data) + }), +} + +const mock = jest.fn().mockImplementation(() => { + return ProductServiceMock +}) + +export default mock