Creates test request helper for API endpoints
Separates common utils into the medusa-core-utils package. Sets up a testing environment where mocked models/services/etc. can be placed in __mocks__ folder within its corresponding directory. The mocks will automatically be registered in a awilix container only used for testing.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import passport from "passport"
|
||||
|
||||
export default () => {
|
||||
return passport.authenticate("jwt", { session: false })
|
||||
return passport.authenticate(["jwt", "bearer"], { session: false })
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import jwt from "jsonwebtoken"
|
||||
import { Validator } from "medusa-core-utils"
|
||||
import config from "../../../../config"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { body } = req
|
||||
const schema = Validator.object().keys({
|
||||
email: Validator.string().required(),
|
||||
password: Validator.string().required(),
|
||||
})
|
||||
const { value, error } = schema.validate(body)
|
||||
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
|
||||
const authService = req.scope.resolve("authService")
|
||||
const result = await authService.authenticate(value.email, value.password)
|
||||
if (!result.success) {
|
||||
res.sendStatus(401)
|
||||
return
|
||||
}
|
||||
|
||||
// Add JWT to cookie
|
||||
req.session.jwt = jwt.sign({ userId: result.user._id }, config.jwtSecret, {
|
||||
expiresIn: "24h",
|
||||
})
|
||||
|
||||
res.json(result.user)
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Router } from "express"
|
||||
import middlewares from "../../../middlewares"
|
||||
|
||||
const route = Router()
|
||||
|
||||
export default app => {
|
||||
app.use("/auth", route)
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-session").default))
|
||||
|
||||
return app
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import { Router } from "express"
|
||||
import middlewares from "../../middlewares"
|
||||
import authRoutes from "./auth"
|
||||
import productRoutes from "./products"
|
||||
import productVariantRoutes from "./product-variants"
|
||||
|
||||
const route = Router()
|
||||
|
||||
@@ -7,12 +10,13 @@ export default app => {
|
||||
app.use("/admin", route)
|
||||
|
||||
// Unauthenticated routes
|
||||
// route.use("/auth", require("./auth").default)
|
||||
authRoutes(route)
|
||||
|
||||
// Authenticated routes
|
||||
route.use(middlewares.authenticate())
|
||||
route.use("/products", require("./products").default)
|
||||
route.use("/product-variants", require("./product-variants").default)
|
||||
|
||||
productRoutes(route)
|
||||
// productVariantRoutes(route)
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
import IdMap from "../../../../../helpers/id-map"
|
||||
import { request } from "../../../../../helpers/test-request"
|
||||
|
||||
describe("POST /admin/products", () => {
|
||||
describe("successful creation", () => {
|
||||
it("calls mock function", async () => {
|
||||
const res = await request("POST", "/admin/products", {
|
||||
adminSession: {
|
||||
jwt: {
|
||||
userId: IdMap.getId("admin_user"),
|
||||
},
|
||||
},
|
||||
})
|
||||
expect(res.status).toEqual(200)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,11 @@
|
||||
import { Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
try {
|
||||
const variantService = req.scope.resolve("productVariantService")
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
res.sendStatus(200)
|
||||
}
|
||||
@@ -4,5 +4,11 @@ import middlewares from "../../../middlewares"
|
||||
const route = Router()
|
||||
|
||||
export default app => {
|
||||
app.use("/products", route)
|
||||
|
||||
route.post("/", middlewares.wrap(require("./create-product").default))
|
||||
|
||||
// route.get("/:productId", middlewares.wrap(require("./get-product").default))
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import validator from "../../../../utils/validator"
|
||||
import { Validator } from "medusa-core-utils"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { productId } = req.params
|
||||
|
||||
const schema = validator.objectId()
|
||||
const schema = Validator.objectId()
|
||||
const { value, error } = schema.validate(productId)
|
||||
|
||||
if (error) {
|
||||
|
||||
@@ -5,6 +5,8 @@ const route = Router()
|
||||
|
||||
export default app => {
|
||||
app.use("/products", route)
|
||||
|
||||
route.get("/:productId", middlewares.wrap(require("./get-product").default))
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
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"
|
||||
import { MedusaErrorTypes } from "./utils/errors"
|
||||
|
||||
const PORT = process.env.PORT || 80
|
||||
|
||||
@@ -21,10 +21,10 @@ const startServer = async () => {
|
||||
case "ValidationError":
|
||||
statusCode = 400
|
||||
break
|
||||
case MedusaErrorTypes.INVALID_DATA:
|
||||
case MedusaError.Types.INVALID_DATA:
|
||||
statusCode = 400
|
||||
break
|
||||
case MedusaErrorTypes.DB_ERROR:
|
||||
case MedusaError.Types.DB_ERROR:
|
||||
statusCode = 500
|
||||
break
|
||||
default:
|
||||
|
||||
@@ -9,7 +9,7 @@ if (!envFound) {
|
||||
throw new Error("⚠️ Couldn't find .env file ⚠️")
|
||||
}
|
||||
|
||||
export default {
|
||||
const config = {
|
||||
/**
|
||||
* Your favorite port
|
||||
*/
|
||||
@@ -21,9 +21,10 @@ export default {
|
||||
/**
|
||||
* Your secret sauce
|
||||
*/
|
||||
jwtSecret: process.env.JWT_SECRET,
|
||||
jwtSecret: process.env.NODE_ENV === "test" ? "test" : process.env.JWT_SECRET,
|
||||
|
||||
cookieSecret: process.env.COOKIE_SECRET,
|
||||
cookieSecret:
|
||||
process.env.NODE_ENV === "test" ? "test" : process.env.COOKIE_SECRET,
|
||||
|
||||
/**
|
||||
* Used by winston logger
|
||||
@@ -39,3 +40,5 @@ export default {
|
||||
prefix: "/api",
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { createContainer } from "awilix"
|
||||
import express from "express"
|
||||
import supertest from "supertest"
|
||||
import jwt from "jsonwebtoken"
|
||||
import sessions from "client-sessions"
|
||||
import cookie from "cookie"
|
||||
import servicesLoader from "../loaders/services"
|
||||
import expressLoader from "../loaders/express"
|
||||
import apiLoader from "../loaders/api"
|
||||
import passportLoader from "../loaders/passport"
|
||||
import config from "../config"
|
||||
|
||||
const testApp = express()
|
||||
|
||||
const container = createContainer()
|
||||
|
||||
servicesLoader({ container })
|
||||
expressLoader({ app: testApp })
|
||||
passportLoader({ app: testApp, container })
|
||||
|
||||
// Add the registered services to the request scope
|
||||
testApp.use((req, res, next) => {
|
||||
req.scope = container.createScope()
|
||||
next()
|
||||
})
|
||||
|
||||
apiLoader({ app: testApp })
|
||||
|
||||
const supertestRequest = supertest(testApp)
|
||||
|
||||
let adminSessionOpts = {
|
||||
cookieName: "adminSession",
|
||||
secret: "test",
|
||||
}
|
||||
export { adminSessionOpts }
|
||||
|
||||
let clientSessionOpts = {
|
||||
cookieName: "clientSession",
|
||||
secret: "test",
|
||||
}
|
||||
export { clientSessionOpts }
|
||||
|
||||
export async function request(method, url, opts = {}) {
|
||||
let { payload, headers } = opts
|
||||
|
||||
headers = headers || {}
|
||||
headers.Cookie = headers.Cookie || ""
|
||||
if (opts.adminSession) {
|
||||
if (opts.adminSession.jwt) {
|
||||
opts.adminSession.jwt = jwt.sign(
|
||||
opts.adminSession.jwt,
|
||||
config.jwtSecret,
|
||||
{
|
||||
expiresIn: "30m",
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
headers.Cookie +=
|
||||
adminSessionOpts.cookieName +
|
||||
"=" +
|
||||
sessions.util.encode(adminSessionOpts, opts.adminSession) +
|
||||
"; "
|
||||
// console.log(sessions.util.decode(adminSessionOpts, opts.headers.Cookie))
|
||||
}
|
||||
if (opts.clientSession) {
|
||||
headers.Cookie +=
|
||||
clientSessionOpts.cookieName +
|
||||
"=" +
|
||||
sessions.util.encode(clientSessionOpts, opts.clientSession) +
|
||||
"; "
|
||||
// console.log(sessions.util.decode(adminSessionOpts, opts.headers.Cookie))
|
||||
}
|
||||
|
||||
let req = supertestRequest[method.toLowerCase()](url)
|
||||
|
||||
for (let name in headers) {
|
||||
req.set(name, headers[name])
|
||||
}
|
||||
|
||||
if (payload && !req.get("content-type")) {
|
||||
req.set("Content-Type", "application/json")
|
||||
}
|
||||
|
||||
if (!req.get("accept")) {
|
||||
req.set("Accept", "application/json")
|
||||
}
|
||||
|
||||
req.set("Host", "localhost")
|
||||
|
||||
let res
|
||||
try {
|
||||
res = await req.send(JSON.stringify(payload))
|
||||
} catch (e) {
|
||||
if (e.response) {
|
||||
res = e.response
|
||||
} else {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
//let c =
|
||||
// res.headers["set-cookie"] && cookie.parse(res.headers["set-cookie"][0])
|
||||
//res.adminSession =
|
||||
// c &&
|
||||
// c[adminSessionOpts.cookieName] &&
|
||||
// sessions.util.decode(adminSessionOpts, c[adminSessionOpts.cookieName])
|
||||
// .content
|
||||
//res.clientSession =
|
||||
// c &&
|
||||
// c[clientSessionOpts.cookieName] &&
|
||||
// sessions.util.decode(clientSessionOpts, c[clientSessionOpts.cookieName])
|
||||
// .content
|
||||
|
||||
return res
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import express from "express"
|
||||
import bodyParser from "body-parser"
|
||||
import session from "express-session"
|
||||
import session from "client-sessions"
|
||||
import cookieParser from "cookie-parser"
|
||||
import cors from "cors"
|
||||
import morgan from "morgan"
|
||||
@@ -11,15 +11,23 @@ export default async ({ app }) => {
|
||||
app.enable("trust proxy")
|
||||
|
||||
app.use(cors())
|
||||
app.use(morgan("combined"))
|
||||
app.use(
|
||||
morgan("combined", {
|
||||
skip: () => process.env.NODE_ENV === "test",
|
||||
})
|
||||
)
|
||||
app.use(cookieParser())
|
||||
app.use(bodyParser.json())
|
||||
app.use(
|
||||
session({
|
||||
cookieName: "session",
|
||||
secret: config.cookieSecret,
|
||||
resave: false,
|
||||
saveUninitialized: true,
|
||||
cookie: { secure: true },
|
||||
duration: 24 * 60 * 60 * 1000,
|
||||
activeDuration: 1000 * 60 * 5,
|
||||
cookie: {
|
||||
httpOnly: true,
|
||||
secure: false,
|
||||
},
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import passport from "passport"
|
||||
import { Strategy as LocalStrategy } from "passport-local"
|
||||
import { Strategy as BearerStrategy } from "passport-http-bearer"
|
||||
import { Strategy as JWTStrategy } from "passport-jwt"
|
||||
import config from "../config"
|
||||
|
||||
export default async ({ app, container }) => {
|
||||
const authService = container.cradle.authService
|
||||
const authService = container.resolve("authService")
|
||||
|
||||
// For good old email password authentication
|
||||
passport.use(
|
||||
new LocalStrategy(
|
||||
{
|
||||
@@ -27,19 +29,32 @@ export default async ({ app, container }) => {
|
||||
)
|
||||
)
|
||||
|
||||
// After a user has authenticated a JWT will be placed on a cookie, all
|
||||
// calls will be authenticated based on the JWT
|
||||
passport.use(
|
||||
new JWTStrategy(
|
||||
{
|
||||
jwtFromRequest: req => req.cookies.jwt,
|
||||
jwtFromRequest: req => req.session.jwt,
|
||||
secretOrKey: config.jwtSecret,
|
||||
},
|
||||
(jwtPayload, done) => {
|
||||
if (Date.now() > jwtPayload.expires) {
|
||||
return done("jwt expired")
|
||||
}
|
||||
|
||||
return done(null, jwtPayload)
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
// Alternatively use bearer token to authenticate to the admin api
|
||||
passport.use(
|
||||
new BearerStrategy((token, done) => {
|
||||
const auth = authService.authenticateAPIToken(token)
|
||||
if (auth.success) {
|
||||
done(null, auth.user)
|
||||
} else {
|
||||
done(auth.error)
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
}
|
||||
|
||||
@@ -4,8 +4,14 @@ import { Lifetime } from "awilix"
|
||||
* Registers all services in the services directory
|
||||
*/
|
||||
export default ({ container }) => {
|
||||
let loadPath = "src/services/*.js"
|
||||
|
||||
if (process.env.NODE_ENV === "test") {
|
||||
loadPath = "src/services/__mocks__/*.js"
|
||||
}
|
||||
|
||||
// service/auth.js -> authService
|
||||
container.loadModules(["src/services/*.js"], {
|
||||
container.loadModules([loadPath], {
|
||||
resolverOptions: {
|
||||
lifetime: Lifetime.SINGLETON,
|
||||
},
|
||||
@@ -19,7 +25,13 @@ export default ({ container }) => {
|
||||
const name = parts.join("")
|
||||
|
||||
const splat = descriptor.path.split("/")
|
||||
const namespace = splat[splat.length - 2]
|
||||
|
||||
let offset = 2
|
||||
if (process.env.NODE_ENV === "test") {
|
||||
offset = 3
|
||||
}
|
||||
|
||||
const namespace = splat[splat.length - offset]
|
||||
const upperNamespace =
|
||||
namespace.charAt(0).toUpperCase() + namespace.slice(1, -1)
|
||||
return name + upperNamespace
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import IdMap from "../../helpers/id-map"
|
||||
|
||||
const adminUser = {
|
||||
_id: IdMap.getId("admin_user"),
|
||||
password: "1235",
|
||||
name: "hi",
|
||||
}
|
||||
|
||||
const mock = jest.fn().mockImplementation(() => {
|
||||
return {
|
||||
authenticate: jest.fn().mockImplementation((email, password) => {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
user: adminUser,
|
||||
})
|
||||
}),
|
||||
authenticateAPIToken: jest.fn().mockImplementation(token => {
|
||||
return Promise.resolve({
|
||||
success: true,
|
||||
user: adminUser,
|
||||
})
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
export default mock
|
||||
@@ -0,0 +1,128 @@
|
||||
import IdMap from "../../helpers/id-map"
|
||||
|
||||
const variant1 = {
|
||||
_id: "1",
|
||||
title: "variant1",
|
||||
options: [
|
||||
{
|
||||
option_id: IdMap.getId("color_id"),
|
||||
value: "blue",
|
||||
},
|
||||
{
|
||||
option_id: IdMap.getId("size_id"),
|
||||
value: "160",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const variant2 = {
|
||||
_id: "2",
|
||||
title: "variant2",
|
||||
options: [
|
||||
{
|
||||
option_id: IdMap.getId("color_id"),
|
||||
value: "black",
|
||||
},
|
||||
{
|
||||
option_id: IdMap.getId("size_id"),
|
||||
value: "160",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const variant3 = {
|
||||
_id: "3",
|
||||
title: "variant3",
|
||||
options: [
|
||||
{
|
||||
option_id: IdMap.getId("color_id"),
|
||||
value: "blue",
|
||||
},
|
||||
{
|
||||
option_id: IdMap.getId("size_id"),
|
||||
value: "150",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const variant4 = {
|
||||
_id: "4",
|
||||
title: "variant4",
|
||||
options: [
|
||||
{
|
||||
option_id: IdMap.getId("color_id"),
|
||||
value: "blue",
|
||||
},
|
||||
{
|
||||
option_id: IdMap.getId("size_id"),
|
||||
value: "50",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const invalidVariant = {
|
||||
_id: "invalid_option",
|
||||
title: "variant3",
|
||||
options: [
|
||||
{
|
||||
option_id: "invalid_id",
|
||||
value: "blue",
|
||||
},
|
||||
{
|
||||
option_id: IdMap.getId("size_id"),
|
||||
value: "150",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const emptyVariant = {
|
||||
_id: "empty_option",
|
||||
title: "variant3",
|
||||
options: [],
|
||||
}
|
||||
|
||||
export const variants = {
|
||||
one: variant1,
|
||||
two: variant2,
|
||||
three: variant3,
|
||||
four: variant4,
|
||||
invalid_variant: invalidVariant,
|
||||
empty_variant: emptyVariant,
|
||||
}
|
||||
|
||||
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({})
|
||||
}),
|
||||
}
|
||||
})
|
||||
|
||||
export default mock
|
||||
@@ -13,6 +13,33 @@ class AuthService extends BaseService {
|
||||
this.userModel_ = userModel
|
||||
}
|
||||
|
||||
/**
|
||||
* Authenticates a given user with an API token
|
||||
* @param {string} token - the api_token of the user to authenticate
|
||||
* @return {{
|
||||
* success: (bool),
|
||||
* user: (object | undefined),
|
||||
* error: (string | undefined)
|
||||
* }}
|
||||
* success: whether authentication succeeded
|
||||
* user: the user document if authentication succeded
|
||||
* error: a string with the error message
|
||||
*/
|
||||
async authenticateAPIToken(token) {
|
||||
const user = await this.userModel_.findOne({ api_token: token })
|
||||
|
||||
if (user) {
|
||||
return {
|
||||
success: true,
|
||||
user,
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
success: false,
|
||||
error: "Invalid API Token",
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Authenticates a given user based on an email, password combination. Uses
|
||||
* bcrypt to match password with hashed value.
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import mongoose from "mongoose"
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "../interfaces"
|
||||
import MedusaError, { MedusaErrorTypes } from "../utils/errors"
|
||||
import validator from "../utils/validator"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate products.
|
||||
@@ -29,11 +28,11 @@ class ProductService extends BaseService {
|
||||
* @return {string} the validated id
|
||||
*/
|
||||
validateId_(rawId) {
|
||||
const schema = validator.objectId()
|
||||
const schema = Validator.objectId()
|
||||
const { value, error } = schema.validate(rawId)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_ARGUMENT,
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"The productId could not be casted to an ObjectId"
|
||||
)
|
||||
}
|
||||
@@ -57,7 +56,7 @@ class ProductService extends BaseService {
|
||||
retrieve(productId) {
|
||||
const validatedId = this.validateId_(productId)
|
||||
return this.productModel_.findOne({ _id: validatedId }).catch(err => {
|
||||
throw new MedusaError(MedusaErrorTypes.DB_ERROR, err.message)
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -73,7 +72,7 @@ class ProductService extends BaseService {
|
||||
published: false,
|
||||
})
|
||||
.catch(err => {
|
||||
throw new MedusaError(MedusaErrorTypes.DB_ERROR, err.message)
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -86,7 +85,7 @@ class ProductService extends BaseService {
|
||||
return this.productModel_
|
||||
.updateOne({ _id: productId }, { $set: { published: true } })
|
||||
.catch(err => {
|
||||
throw new MedusaError(MedusaErrorTypes.DB_ERROR, err.message)
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -104,14 +103,14 @@ class ProductService extends BaseService {
|
||||
|
||||
if (update.metadata) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Use setMetadata to update metadata fields"
|
||||
)
|
||||
}
|
||||
|
||||
if (update.variants) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
"Use addVariant, reorderVariants, removeVariant to update Product Variants"
|
||||
)
|
||||
}
|
||||
@@ -123,7 +122,7 @@ class ProductService extends BaseService {
|
||||
{ runValidators: true }
|
||||
)
|
||||
.catch(err => {
|
||||
throw new MedusaError(MedusaErrorTypes.DB_ERROR, err.message)
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -148,7 +147,7 @@ class ProductService extends BaseService {
|
||||
})
|
||||
|
||||
return this.productModel_.deleteOne({ _id: product._id }).catch(err => {
|
||||
throw new MedusaError(MedusaErrorTypes.DB_ERROR, err.message)
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -163,7 +162,7 @@ class ProductService extends BaseService {
|
||||
const product = await this.retrieve(productId)
|
||||
if (!product) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.NOT_FOUND,
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Product with ${product._id} was not found`
|
||||
)
|
||||
}
|
||||
@@ -171,14 +170,14 @@ class ProductService extends BaseService {
|
||||
const variant = await this.productVariantService_.retrieve(variantId)
|
||||
if (!variant) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.NOT_FOUND,
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Variant with ${variantId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
if (product.options.length !== variant.options.length) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Product options length does not match variant options length. Product has ${product.options.length} and variant has ${variant.options.length}.`
|
||||
)
|
||||
}
|
||||
@@ -186,7 +185,7 @@ class ProductService extends BaseService {
|
||||
product.options.forEach(option => {
|
||||
if (!variant.options.find(vo => vo.option_id === option._id)) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Variant options do not contain value for ${option.title}`
|
||||
)
|
||||
}
|
||||
@@ -207,7 +206,7 @@ class ProductService extends BaseService {
|
||||
|
||||
if (combinationExists) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Variant with provided options already exists`
|
||||
)
|
||||
}
|
||||
@@ -230,7 +229,7 @@ class ProductService extends BaseService {
|
||||
const product = await this.retrieve(productId)
|
||||
if (!product) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.NOT_FOUND,
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Product with ${product._id} was not found`
|
||||
)
|
||||
}
|
||||
@@ -238,7 +237,7 @@ class ProductService extends BaseService {
|
||||
// Make sure that option doesn't already exist
|
||||
if (product.options.find(o => o.title === optionTitle)) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`An option with the title: ${optionTitle} already exists`
|
||||
)
|
||||
}
|
||||
@@ -297,14 +296,14 @@ class ProductService extends BaseService {
|
||||
const product = await this.retrieve(productId)
|
||||
if (!product) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.NOT_FOUND,
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Product with ${product._id} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
if (product.variants.length !== variantOrder.length) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Product variants and new variant order differ in length. To delete or add variants use removeVariant or addVariant`
|
||||
)
|
||||
}
|
||||
@@ -313,7 +312,7 @@ class ProductService extends BaseService {
|
||||
const variant = product.variants.find(id => id === vId)
|
||||
if (!variant) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Product has no variant with id: ${vId}`
|
||||
)
|
||||
}
|
||||
@@ -344,14 +343,14 @@ class ProductService extends BaseService {
|
||||
const product = await this.retrieve(productId)
|
||||
if (!product) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.NOT_FOUND,
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Product with ${product._id} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
if (product.options.length !== optionOrder.length) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Product options and new options order differ in length. To delete or add options use removeOption or addOption`
|
||||
)
|
||||
}
|
||||
@@ -360,7 +359,7 @@ class ProductService extends BaseService {
|
||||
const option = product.options.find(o => o._id === oId)
|
||||
if (!option) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Product has no option with id: ${oId}`
|
||||
)
|
||||
}
|
||||
@@ -390,7 +389,7 @@ class ProductService extends BaseService {
|
||||
const product = await this.retrieve(productId)
|
||||
if (!product) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.NOT_FOUND,
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Product with ${product._id} was not found`
|
||||
)
|
||||
}
|
||||
@@ -398,7 +397,7 @@ class ProductService extends BaseService {
|
||||
const option = product.options.find(o => o._id === optionId)
|
||||
if (!option) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.NOT_FOUND,
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Product has no option with id: ${optionId}`
|
||||
)
|
||||
}
|
||||
@@ -410,7 +409,7 @@ class ProductService extends BaseService {
|
||||
|
||||
if (titleExists) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.NOT_FOUND,
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`An option with title ${title} already exists`
|
||||
)
|
||||
}
|
||||
@@ -439,7 +438,7 @@ class ProductService extends BaseService {
|
||||
const product = await this.retrieve(productId)
|
||||
if (!product) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.NOT_FOUND,
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Product with ${product._id} was not found`
|
||||
)
|
||||
}
|
||||
@@ -474,7 +473,7 @@ class ProductService extends BaseService {
|
||||
|
||||
if (!equalsFirst.every(v => v)) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_DATA,
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`To delete an option, first delete all variants, such that when option is deleted, no duplicate variants will exist. For more info check MEDUSA.com`
|
||||
)
|
||||
}
|
||||
@@ -508,7 +507,7 @@ class ProductService extends BaseService {
|
||||
const product = await this.retrieve(productId)
|
||||
if (!product) {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.NOT_FOUND,
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Product with ${product._id} was not found`
|
||||
)
|
||||
}
|
||||
@@ -555,7 +554,7 @@ class ProductService extends BaseService {
|
||||
|
||||
if (typeof key !== "string") {
|
||||
throw new MedusaError(
|
||||
MedusaErrorTypes.INVALID_ARGUMENT,
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Key type is invalid. Metadata keys must be strings"
|
||||
)
|
||||
}
|
||||
@@ -564,7 +563,7 @@ class ProductService extends BaseService {
|
||||
return this.productModel_
|
||||
.updateOne({ _id: validatedId }, { $set: { [keyPath]: value } })
|
||||
.catch(err => {
|
||||
throw new MedusaError(MedusaErrorTypes.DB_ERROR, err.message)
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
/**
|
||||
* @typedef MedusaErrorType
|
||||
*
|
||||
*/
|
||||
export const MedusaErrorTypes = {
|
||||
/** Errors stemming from the database */
|
||||
DB_ERROR: "database_error",
|
||||
INVALID_ARGUMENT: "invalid_argument",
|
||||
INVALID_DATA: "invalid_data",
|
||||
NOT_FOUND: "not_found"
|
||||
}
|
||||
|
||||
/**
|
||||
* Standardized error to be used across Medusa project.
|
||||
* @extends Error
|
||||
*/
|
||||
class MedusaError extends Error {
|
||||
/**
|
||||
* Creates a standardized error to be used across Medusa project.
|
||||
* @param type {MedusaErrorType} - the type of error.
|
||||
* @param params {Array} - Error params.
|
||||
*/
|
||||
constructor(name, message, ...params) {
|
||||
super(...params)
|
||||
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, MedusaError)
|
||||
}
|
||||
|
||||
this.name = name
|
||||
this.message = message
|
||||
this.date = new Date()
|
||||
}
|
||||
}
|
||||
|
||||
export default MedusaError
|
||||
@@ -1,4 +0,0 @@
|
||||
import Joi from "@hapi/joi"
|
||||
Joi.objectId = require("joi-objectid")(Joi)
|
||||
|
||||
export default Joi
|
||||
Reference in New Issue
Block a user