Adds OrderService and /orders endpoints

Adds OrderService

Adds endpoints for orders - both store and admin
This commit is contained in:
Oliver Windall Juhl
2020-05-11 13:54:09 +02:00
committed by GitHub
parent 7ca03579db
commit 8255a839f6
39 changed files with 2648 additions and 47 deletions
@@ -8,6 +8,7 @@ import regionRoutes from "./regions"
import shippingOptionRoutes from "./shipping-options"
import shippingProfileRoutes from "./shipping-profiles"
import discountRoutes from "./discounts"
import orderRoutes from "./orders"
const route = Router()
@@ -34,6 +35,7 @@ export default (app, container) => {
shippingOptionRoutes(route)
shippingProfileRoutes(route)
discountRoutes(route)
orderRoutes(route)
// productVariantRoutes(route)
return app
@@ -0,0 +1,40 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
describe("POST /admin/orders/:id/archive", () => {
describe("successfully archives an order", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/orders/${IdMap.getId("processed-order")}/archive`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls OrderService archive", () => {
expect(OrderServiceMock.archive).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.archive).toHaveBeenCalledWith(
IdMap.getId("processed-order")
)
})
it("returns order with status = archived", () => {
expect(subject.status).toEqual(200)
expect(subject.body._id).toEqual(IdMap.getId("processed-order"))
expect(subject.body.status).toEqual("archived")
})
})
})
@@ -0,0 +1,40 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
describe("POST /admin/orders/:id/cancel", () => {
describe("successfully cancels an order", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/orders/${IdMap.getId("test-order")}/cancel`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls OrderService cancel", () => {
expect(OrderServiceMock.cancel).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.cancel).toHaveBeenCalledWith(
IdMap.getId("test-order")
)
})
it("returns order with status = cancelled", () => {
expect(subject.status).toEqual(200)
expect(subject.body._id).toEqual(IdMap.getId("test-order"))
expect(subject.body.status).toEqual("cancelled")
})
})
})
@@ -0,0 +1,40 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
describe("POST /admin/orders/:id/capture", () => {
describe("successfully captures payment for an order", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/orders/${IdMap.getId("test-order")}/capture`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls OrderService capturePayment", () => {
expect(OrderServiceMock.capturePayment).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.capturePayment).toHaveBeenCalledWith(
IdMap.getId("test-order")
)
})
it("returns order with payment_status = captured", () => {
expect(subject.status).toEqual(200)
expect(subject.body._id).toEqual(IdMap.getId("test-order"))
expect(subject.body.payment_status).toEqual("captured")
})
})
})
@@ -0,0 +1,40 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
describe("POST /admin/orders/:id/fulfillment", () => {
describe("successfully fulfills an order", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/orders/${IdMap.getId("test-order")}/fulfillment`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls OrderService createFulfillment", () => {
expect(OrderServiceMock.createFulfillment).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.createFulfillment).toHaveBeenCalledWith(
IdMap.getId("test-order")
)
})
it("returns order with fulfillment_status = fulfilled", () => {
expect(subject.status).toEqual(200)
expect(subject.body._id).toEqual(IdMap.getId("test-order"))
expect(subject.body.fulfillment_status).toEqual("fulfilled")
})
})
})
@@ -0,0 +1,140 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import {
orders,
OrderServiceMock,
} from "../../../../../services/__mocks__/order"
describe("POST /admin/orders", () => {
describe("successful creation", () => {
let subject
beforeAll(async () => {
subject = await request("POST", "/admin/orders", {
payload: {
email: "virgil@vandijk.dk",
billing_address: {
first_name: "Virgil",
last_name: "Van Dijk",
address_1: "24 Dunks Drive",
city: "Los Angeles",
country_code: "US",
province: "CA",
postal_code: "93011",
},
shipping_address: {
first_name: "Virgil",
last_name: "Van Dijk",
address_1: "24 Dunks Drive",
city: "Los Angeles",
country_code: "US",
province: "CA",
postal_code: "93011",
},
items: [
{
_id: IdMap.getId("existingLine"),
title: "merge line",
description: "This is a new line",
thumbnail: "test-img-yeah.com/thumb",
content: {
unit_price: 123,
variant: {
_id: IdMap.getId("can-cover"),
},
product: {
_id: IdMap.getId("validId"),
},
quantity: 1,
},
quantity: 10,
},
],
region: IdMap.getId("testRegion"),
customer_id: IdMap.getId("testCustomer"),
payment_method: {
provider_id: "default_provider",
data: {},
},
shipping_method: [
{
provider_id: "default_provider",
profile_id: IdMap.getId("validId"),
price: 123,
data: {},
items: [],
},
],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls OrderService create", () => {
expect(OrderServiceMock.create).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.create).toHaveBeenCalledWith({
email: "virgil@vandijk.dk",
billing_address: {
first_name: "Virgil",
last_name: "Van Dijk",
address_1: "24 Dunks Drive",
city: "Los Angeles",
country_code: "US",
province: "CA",
postal_code: "93011",
},
shipping_address: {
first_name: "Virgil",
last_name: "Van Dijk",
address_1: "24 Dunks Drive",
city: "Los Angeles",
country_code: "US",
province: "CA",
postal_code: "93011",
},
items: [
{
_id: IdMap.getId("existingLine"),
title: "merge line",
description: "This is a new line",
thumbnail: "test-img-yeah.com/thumb",
content: {
unit_price: 123,
variant: {
_id: IdMap.getId("can-cover"),
},
product: {
_id: IdMap.getId("validId"),
},
quantity: 1,
},
quantity: 10,
},
],
region: IdMap.getId("testRegion"),
customer_id: IdMap.getId("testCustomer"),
payment_method: {
provider_id: "default_provider",
data: {},
},
shipping_method: [
{
provider_id: "default_provider",
profile_id: IdMap.getId("validId"),
price: 123,
data: {},
items: [],
},
],
})
})
})
})
@@ -0,0 +1,39 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
describe("GET /admin/orders", () => {
describe("successfully gets an order", () => {
let subject
beforeAll(async () => {
subject = await request(
"GET",
`/admin/orders/${IdMap.getId("test-order")}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls orderService retrieve", () => {
expect(OrderServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("test-order")
)
})
it("returns order", () => {
expect(subject.status).toEqual(200)
expect(subject.body._id).toEqual(IdMap.getId("test-order"))
})
})
})
@@ -0,0 +1,74 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
describe("POST /admin/orders/:id/return", () => {
describe("successfully returns full order", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/orders/${IdMap.getId("test-order")}/return`,
{
payload: {
items: [
{
_id: IdMap.getId("existingLine"),
title: "merge line",
description: "This is a new line",
thumbnail: "test-img-yeah.com/thumb",
content: {
unit_price: 123,
variant: {
_id: IdMap.getId("can-cover"),
},
product: {
_id: IdMap.getId("validId"),
},
quantity: 1,
},
quantity: 10,
},
],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls OrderService return", () => {
expect(OrderServiceMock.return).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.return).toHaveBeenCalledWith(
IdMap.getId("test-order"),
[
{
_id: IdMap.getId("existingLine"),
title: "merge line",
description: "This is a new line",
thumbnail: "test-img-yeah.com/thumb",
content: {
unit_price: 123,
variant: {
_id: IdMap.getId("can-cover"),
},
product: {
_id: IdMap.getId("validId"),
},
quantity: 1,
},
quantity: 10,
},
]
)
})
})
})
@@ -0,0 +1,63 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
describe("POST /admin/orders/:id", () => {
describe("successfully updates an order", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/orders/${IdMap.getId("test-order")}`,
{
payload: {
email: "oliver@test.dk",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls OrderService update", () => {
expect(OrderServiceMock.update).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.update).toHaveBeenCalledWith(
IdMap.getId("test-order"),
{
email: "oliver@test.dk",
}
)
})
})
describe("handles failed update operation", () => {
it("throws if metadata is to be updated", async () => {
try {
await request("POST", `/admin/orders/${IdMap.getId("test-order")}`, {
payload: {
_id: IdMap.getId("test-order"),
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"
)
}
})
})
})
@@ -0,0 +1,11 @@
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
const order = await orderService.archive(id)
res.json(order)
} catch (error) {
throw error
}
}
@@ -0,0 +1,11 @@
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
const order = await orderService.cancel(id)
res.json(order)
} catch (error) {
throw error
}
}
@@ -0,0 +1,11 @@
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
const order = await orderService.capturePayment(id)
res.json(order)
} catch (error) {
throw error
}
}
@@ -0,0 +1,11 @@
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
const order = await orderService.createFulfillment(id)
res.json(order)
} catch (error) {
throw error
}
}
@@ -0,0 +1,46 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object().keys({
status: Validator.string().optional(),
email: Validator.string()
.email()
.required(),
billing_address: Validator.address().required(),
shipping_address: Validator.address().required(),
items: Validator.array().required(),
region: Validator.string().required(),
discounts: Validator.array().optional(),
customer_id: Validator.string().required(),
payment_method: Validator.object()
.keys({
provider_id: Validator.string().required(),
data: Validator.object().optional(),
})
.required(),
shipping_method: Validator.array()
.items({
provider_id: Validator.string().required(),
profile_id: Validator.string().required(),
price: Validator.number().required(),
data: Validator.object().optional(),
items: Validator.array().optional(),
})
.required(),
metadata: Validator.object().optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const orderService = req.scope.resolve("orderService")
const order = await orderService.create(value)
res.status(200).json(order)
} catch (err) {
throw err
}
}
@@ -0,0 +1,12 @@
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
const order = await orderService.retrieve(id)
res.json(order)
} catch (error) {
throw error
}
}
@@ -0,0 +1,30 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/orders", route)
route.get("/:id", middlewares.wrap(require("./get-order").default))
route.post("/", middlewares.wrap(require("./create-order").default))
route.post("/:id", middlewares.wrap(require("./update-order").default))
route.post(
"/:id/capture",
middlewares.wrap(require("./capture-payment").default)
)
route.post(
"/:id/fulfillment",
middlewares.wrap(require("./create-fulfillment").default)
)
route.post("/:id/return", middlewares.wrap(require("./return-order").default))
route.post("/:id/cancel", middlewares.wrap(require("./cancel-order").default))
route.post(
"/:id/archive",
middlewares.wrap(require("./archive-order").default)
)
return app
}
@@ -0,0 +1,23 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
items: Validator.array().required(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const orderService = req.scope.resolve("orderService")
const order = await orderService.return(id, value.items)
res.status(200).json(order)
} catch (err) {
throw err
}
}
@@ -0,0 +1,40 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { id } = req.params
const schema = Validator.object().keys({
email: Validator.string().email(),
billing_address: Validator.address(),
shipping_address: Validator.address(),
items: Validator.array(),
region: Validator.string(),
discounts: Validator.array(),
customer_id: Validator.string(),
payment_method: Validator.object().keys({
provider_id: Validator.string(),
data: Validator.object(),
}),
shipping_method: Validator.array().items({
provider_id: Validator.string(),
profile_id: Validator.string(),
price: Validator.number(),
data: Validator.object(),
items: Validator.array(),
}),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const orderService = req.scope.resolve("orderService")
const order = await orderService.update(id, value)
res.status(200).json(order)
} catch (err) {
throw err
}
}
@@ -2,6 +2,7 @@ import { Router } from "express"
import productRoutes from "./products"
import cartRoutes from "./carts"
import orderRoutes from "./orders"
import customerRoutes from "./customers"
import shippingOptionRoutes from "./shipping-options"
@@ -12,6 +13,7 @@ export default app => {
customerRoutes(route)
productRoutes(route)
orderRoutes(route)
cartRoutes(route)
shippingOptionRoutes(route)
@@ -0,0 +1,32 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
import { carts } from "../../../../../services/__mocks__/cart"
describe("POST /store/orders", () => {
describe("successful creation", () => {
let subject
beforeAll(async () => {
subject = await request("POST", "/store/orders", {
payload: {
cartId: IdMap.getId("fr-cart"),
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service create", () => {
expect(OrderServiceMock.create).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.create).toHaveBeenCalledWith(carts.frCart)
})
})
})
@@ -0,0 +1,31 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { OrderServiceMock } from "../../../../../services/__mocks__/order"
describe("GET /store/orders", () => {
describe("successfully gets an order", () => {
let subject
beforeAll(async () => {
subject = await request(
"GET",
`/store/orders/${IdMap.getId("test-order")}`
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls orderService retrieve", () => {
expect(OrderServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(OrderServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("test-order")
)
})
it("returns order", () => {
expect(subject.body._id).toEqual(IdMap.getId("test-order"))
})
})
})
@@ -0,0 +1,39 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object().keys({
cartId: Validator.string().required(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const cartService = req.scope.resolve("cartService")
const orderService = req.scope.resolve("orderService")
const cart = await cartService.retrieve(value.cartId)
let order = await orderService.create(cart)
order = await orderService.decorate(order, [
"status",
"fulfillment_status",
"payment_status",
"email",
"billing_address",
"shipping_address",
"items",
"region",
"discounts",
"customer_id",
"payment_method",
"shipping_methods",
"metadata",
])
res.status(200).json(order)
} catch (err) {
throw err
}
}
@@ -0,0 +1,27 @@
export default async (req, res) => {
const { id } = req.params
try {
const orderService = req.scope.resolve("orderService")
let order = await orderService.retrieve(id)
order = await orderService.decorate(order, [
"status",
"fulfillment_status",
"payment_status",
"email",
"billing_address",
"shipping_address",
"items",
"region",
"discounts",
"customer_id",
"payment_method",
"shipping_methods",
"metadata",
])
res.json(order)
} catch (error) {
throw error
}
}
@@ -0,0 +1,14 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/orders", route)
route.get("/:id", middlewares.wrap(require("./get-order").default))
route.post("/", middlewares.wrap(require("./create-order").default))
return app
}