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:
Sebastian Rindom
2020-01-21 15:13:47 +01:00
parent 3d494bc652
commit ed472e9fba
33 changed files with 14053 additions and 76 deletions

View File

@@ -1,10 +1,3 @@
module.exports = {
plugins: [
{
resolve: `mrbl-payment-stripe`,
options: {
stripeApiKey: "12345",
},
},
],
plugins: [],
}

View File

@@ -19,10 +19,12 @@
"@babel/preset-env": "^7.7.5",
"@babel/register": "^7.7.4",
"@babel/runtime": "^7.7.6",
"client-sessions": "^0.8.0",
"eslint": "^6.7.2",
"jest": "^24.9.0",
"nodemon": "^2.0.1",
"prettier": "^1.19.1"
"prettier": "^1.19.1",
"supertest": "^4.0.2"
},
"scripts": {
"start": "nodemon --watch plugins/ --watch src/ --exec babel-node src/app.js",
@@ -42,13 +44,15 @@
"dotenv": "^8.2.0",
"express": "^4.17.1",
"express-session": "^1.17.0",
"joi-objectid": "^3.0.1",
"fs-exists-cached": "^1.0.0",
"glob": "^7.1.6",
"joi-objectid": "^3.0.1",
"jsonwebtoken": "^8.5.1",
"medusa-core-utils": "^1.0.0",
"mongoose": "^5.8.0",
"morgan": "^1.9.1",
"passport": "^0.4.0",
"passport-http-bearer": "^1.0.1",
"passport-jwt": "^4.0.0",
"passport-local": "^1.0.0",
"regenerator-runtime": "^0.13.3",

View File

@@ -1,5 +1,5 @@
import passport from "passport"
export default () => {
return passport.authenticate("jwt", { session: false })
return passport.authenticate(["jwt", "bearer"], { session: false })
}

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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
}

View File

@@ -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)
})
})
})

View File

@@ -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)
}

View File

@@ -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
}

View File

@@ -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) {

View File

@@ -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
}

View File

@@ -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:

View File

@@ -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

View File

@@ -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
}

View File

@@ -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,
},
})
)

View File

@@ -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())
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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.

View File

@@ -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)
})
}
}

View File

@@ -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

View File

@@ -1,4 +0,0 @@
import Joi from "@hapi/joi"
Joi.objectId = require("joi-objectid")(Joi)
export default Joi

File diff suppressed because it is too large Load Diff

View File

@@ -1605,6 +1605,13 @@ cli-width@^2.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
client-sessions@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/client-sessions/-/client-sessions-0.8.0.tgz#a7d8c5558ad5d56f2a199f3533eb654b5df893fd"
integrity sha1-p9jFVYrV1W8qGZ81M+tlS134k/0=
dependencies:
cookies "^0.7.0"
cliui@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
@@ -1710,7 +1717,7 @@ commondir@^1.0.1:
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
component-emitter@^1.2.1:
component-emitter@^1.2.0, component-emitter@^1.2.1:
version "1.3.0"
resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0"
integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==
@@ -1779,6 +1786,19 @@ cookie@0.4.0:
resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba"
integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==
cookiejar@^2.1.0:
version "2.1.2"
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==
cookies@^0.7.0:
version "0.7.3"
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.3.tgz#7912ce21fbf2e8c2da70cf1c3f351aecf59dadfa"
integrity sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==
dependencies:
depd "~1.1.2"
keygrip "~1.0.3"
copy-descriptor@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
@@ -1892,7 +1912,7 @@ debug@3.1.0:
dependencies:
ms "2.0.0"
debug@^3.2.6:
debug@^3.1.0, debug@^3.2.6:
version "3.2.6"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b"
integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==
@@ -2412,7 +2432,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@~3.0.2:
extend@^3.0.0, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@@ -2566,6 +2586,15 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@^2.3.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
@@ -2575,6 +2604,11 @@ form-data@~2.3.2:
combined-stream "^1.0.6"
mime-types "^2.1.12"
formidable@^1.2.0:
version "1.2.1"
resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.2.1.tgz#70fb7ca0290ee6ff961090415f4b3df3d2082659"
integrity sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg==
forwarded@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84"
@@ -3834,6 +3868,11 @@ kareem@2.3.1:
resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.1.tgz#def12d9c941017fabfb00f873af95e9c99e1be87"
integrity sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==
keygrip@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.3.tgz#399d709f0aed2bab0a059e0cdd3a5023a053e1dc"
integrity sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"
@@ -4058,7 +4097,7 @@ merge-stream@^2.0.0:
resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
methods@~1.1.2:
methods@^1.1.1, methods@^1.1.2, methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
@@ -4094,7 +4133,7 @@ mime-types@^2.1.12, mime-types@~2.1.19, mime-types@~2.1.24:
dependencies:
mime-db "1.42.0"
mime@1.6.0:
mime@1.6.0, mime@^1.4.1:
version "1.6.0"
resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1"
integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==
@@ -4685,6 +4724,13 @@ pascalcase@^0.1.1:
resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14"
integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=
passport-http-bearer@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/passport-http-bearer/-/passport-http-bearer-1.0.1.tgz#147469ea3669e2a84c6167ef99dbb77e1f0098a8"
integrity sha1-FHRp6jZp4qhMYWfvmdu3fh8AmKg=
dependencies:
passport-strategy "1.x.x"
passport-jwt@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-4.0.0.tgz#7f0be7ba942e28b9f5d22c2ebbb8ce96ef7cf065"
@@ -4907,6 +4953,11 @@ qs@6.7.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@^6.5.1:
version "6.9.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.9.1.tgz#20082c65cb78223635ab1a9eaca8875a29bf8ec9"
integrity sha512-Cxm7/SS/y/Z3MHWSxXb8lIFqgqBowP5JMlTUFyJN88y0SGQhVmZnqFK/PeuMX9LzUyWsqqhNxIyg0jlzq946yA==
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@@ -4977,6 +5028,19 @@ readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.3.6:
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^2.3.5:
version "2.3.7"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
dependencies:
core-util-is "~1.0.0"
inherits "~2.0.3"
isarray "~1.0.0"
process-nextick-args "~2.0.0"
safe-buffer "~5.1.1"
string_decoder "~1.1.1"
util-deprecate "~1.0.1"
readable-stream@^3.1.1:
version "3.4.0"
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.4.0.tgz#a51c26754658e0a3c21dbf59163bd45ba6f447fc"
@@ -5718,6 +5782,30 @@ strip-json-comments@~2.0.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
superagent@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.3.tgz#460ea0dbdb7d5b11bc4f78deba565f86a178e128"
integrity sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==
dependencies:
component-emitter "^1.2.0"
cookiejar "^2.1.0"
debug "^3.1.0"
extend "^3.0.0"
form-data "^2.3.1"
formidable "^1.2.0"
methods "^1.1.1"
mime "^1.4.1"
qs "^6.5.1"
readable-stream "^2.3.5"
supertest@^4.0.2:
version "4.0.2"
resolved "https://registry.yarnpkg.com/supertest/-/supertest-4.0.2.tgz#c2234dbdd6dc79b6f15b99c8d6577b90e4ce3f36"
integrity sha512-1BAbvrOZsGA3YTCWqbmh14L0YEq0EGICX/nBnfkfVJn7SrxQV1I3pMYjSzG9y/7ZU2V9dWqyqk2POwxlb09duQ==
dependencies:
methods "^1.1.2"
superagent "^3.8.3"
supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"