Creates ShippingProfileService & ShippingOptionService (#30)

Adds ShippingProfiles:

With Shipping Profiles store operators can group products together and select which shipping options can fulfill the products. The shipping profiles are region agnostic, but for products to be shippable to a given region the shipping profile must have a shipping option associated that ships to this region.

Adds Shipping Options:

Shipping Options represents a way that the customer can have their order shipped. The shipping option has a fulfillment provider associated to determine who fulfills orders with the given shipping method. If a fulfillment provider has multiple ways that they can ship a shipping option for each of the fulfillment provider's shipping options can be created.
This commit is contained in:
Sebastian Rindom authored and GitHub committed 2020-04-07 16:35:27 +02:00
1 parent aff1ec3390
commit ea4185eddd
72 files changed
+3658 -370

No files matched your search

@@ -3,6 +3,8 @@ import middlewares from "../../middlewares"
import authRoutes from "./auth"
import productRoutes from "./products"
import productVariantRoutes from "./product-variants"
import shippingOptionRoutes from "./shipping-options"
import shippingProfileRoutes from "./shipping-profiles"
const route = Router()
@@ -16,6 +18,8 @@ export default app => {
route.use(middlewares.authenticate())
productRoutes(route)
shippingOptionRoutes(route)
shippingProfileRoutes(route)
// productVariantRoutes(route)
return app
@@ -4,11 +4,6 @@ import middlewares from "../../../middlewares"
const route = Router()
export default app => {
// Inject <rootDir>/api/routes/admin/products/router.js
// Inject <rootDir>/plugins/*/api/routes/admin/products/router.js
// Inject <rootDir>/node_modules/*/api/routes/admin/products/router.js
app.use("/products", route)
route.post("/", middlewares.wrap(require("./create-product").default))
@@ -0,0 +1,91 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingOptionServiceMock } from "../../../../../services/__mocks__/shipping-option"
describe("POST /admin/shipping-options", () => {
describe("successful creation", () => {
let subject
beforeAll(async () => {
subject = await request("POST", "/admin/shipping-options", {
payload: {
name: "Test option",
region_id: "testregion",
provider_id: "test_provider",
price: {
type: "flat_rate",
amount: 100,
},
requirements: [
{
type: "min_subtotal",
value: 1,
},
],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service create", () => {
expect(ShippingOptionServiceMock.create).toHaveBeenCalledTimes(1)
expect(ShippingOptionServiceMock.create).toHaveBeenCalledWith({
name: "Test option",
region_id: "testregion",
provider_id: "test_provider",
price: {
type: "flat_rate",
amount: 100,
},
requirements: [
{
type: "min_subtotal",
value: 1,
},
],
})
})
})
describe("fails on invalid data", () => {
let subject
beforeAll(async () => {
subject = await request("POST", "/admin/shipping-options", {
payload: {
price: {
type: "flat_rate",
amount: 100,
},
requirements: [
{
type: "min_subtotal",
value: 1,
},
],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
it("returns error", () => {
expect(subject.body.message[0].message).toEqual(`"name" is required`)
})
})
})
@@ -0,0 +1,34 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingOptionServiceMock } from "../../../../../services/__mocks__/shipping-option"
describe("POST /admin/shipping-options", () => {
describe("successful creation", () => {
let subject
beforeAll(async () => {
subject = await request(
"DELETE",
`/admin/shipping-options/${IdMap.getId("validId")}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service delete", () => {
expect(ShippingOptionServiceMock.delete).toHaveBeenCalledTimes(1)
expect(ShippingOptionServiceMock.delete).toHaveBeenCalledWith(
IdMap.getId("validId")
)
})
})
})
@@ -0,0 +1,34 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingOptionServiceMock } from "../../../../../services/__mocks__/shipping-option"
describe("GET /admin/shipping-options/:optionId", () => {
describe("successful retrieval", () => {
let subject
beforeAll(async () => {
subject = await request(
"GET",
`/admin/shipping-options/${IdMap.getId("validId")}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service retrieve", () => {
expect(ShippingOptionServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ShippingOptionServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("validId")
)
})
})
})
@@ -0,0 +1,28 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingOptionServiceMock } from "../../../../../services/__mocks__/shipping-option"
describe("GET /admin/shipping-options", () => {
describe("successful retrieval", () => {
let subject
beforeAll(async () => {
subject = await request("GET", `/admin/shipping-options`, {
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service retrieve", () => {
expect(ShippingOptionServiceMock.list).toHaveBeenCalledTimes(1)
expect(ShippingOptionServiceMock.list).toHaveBeenCalledWith()
})
})
})
@@ -0,0 +1,60 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingOptionServiceMock } from "../../../../../services/__mocks__/shipping-option"
describe("POST /admin/shipping-options", () => {
describe("successful creation", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/shipping-options/${IdMap.getId("validId")}`,
{
payload: {
name: "Test option",
price: {
type: "flat_rate",
amount: 100,
},
requirements: [
{
type: "min_subtotal",
value: 1,
},
],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service method", () => {
expect(ShippingOptionServiceMock.update).toHaveBeenCalledTimes(1)
expect(ShippingOptionServiceMock.update).toHaveBeenCalledWith(
IdMap.getId("validId"),
{
name: "Test option",
price: {
type: "flat_rate",
amount: 100,
},
requirements: [
{
type: "min_subtotal",
value: 1,
},
],
}
)
})
})
})
@@ -0,0 +1,36 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object().keys({
name: Validator.string().required(),
region_id: Validator.string().required(),
provider_id: Validator.string().required(),
data: Validator.object(),
price: Validator.object().keys({
type: Validator.string().required(),
amount: Validator.number().optional(),
}),
requirements: Validator.array()
.items(
Validator.object({
type: Validator.string().required(),
value: Validator.number().required(),
})
)
.optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const optionService = req.scope.resolve("shippingOptionService")
const data = await optionService.create(value)
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,14 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { option_id} = req.params
try {
const optionService = req.scope.resolve("shippingOptionService")
await optionService.delete(option_id)
res.sendStatus(200)
} catch (err) {
throw err
}
}
@@ -0,0 +1,11 @@
export default async (req, res) => {
const { option_id } = req.params
try {
const optionService = req.scope.resolve("shippingOptionService")
const data = await optionService.retrieve(option_id)
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,26 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/shipping-options", route)
route.get("/", middlewares.wrap(require("./list-shipping-options").default))
route.post("/", middlewares.wrap(require("./create-shipping-option").default))
route.get(
"/:option_id",
middlewares.wrap(require("./get-shipping-option").default)
)
route.post(
"/:option_id",
middlewares.wrap(require("./update-shipping-option").default)
)
route.delete(
"/:option_id",
middlewares.wrap(require("./delete-shipping-option").default)
)
return app
}
@@ -0,0 +1,11 @@
export default async (req, res) => {
const { optionId } = req.params
try {
const optionService = req.scope.resolve("shippingOptionService")
const data = await optionService.list()
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,39 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { option_id } = req.params
const schema = Validator.object().keys({
name: Validator.string().optional(),
price: Validator.object()
.keys({
type: Validator.string().required(),
amount: Validator.number().optional(),
})
.optional(),
requirements: Validator.array()
.items(
Validator.object({
type: Validator.string().required(),
value: Validator.number().required(),
})
)
.optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const optionService = req.scope.resolve("shippingOptionService")
await optionService.update(option_id, value)
const data = await optionService.retrieve(option_id)
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,43 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
describe("POST /admin/shipping-profiles/:profile_id/products", () => {
describe("successful addition", () => {
let subject
beforeAll(async () => {
const profileId = IdMap.getId("validId")
subject = await request(
"POST",
`/admin/shipping-profiles/${profileId}/products`,
{
payload: {
product_id: IdMap.getId("validId"),
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service retrieve", () => {
expect(ShippingProfileServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ShippingProfileServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("validId")
)
expect(ShippingProfileServiceMock.addProduct).toHaveBeenCalledTimes(1)
expect(ShippingProfileServiceMock.addProduct).toHaveBeenCalledWith(
IdMap.getId("validId"),
IdMap.getId("validId")
)
})
})
})
@@ -0,0 +1,45 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
describe("POST /admin/shipping-profiles/:profile_id/products", () => {
describe("successful addition", () => {
let subject
beforeAll(async () => {
const profileId = IdMap.getId("validId")
subject = await request(
"POST",
`/admin/shipping-profiles/${profileId}/shipping-options`,
{
payload: {
option_id: IdMap.getId("validId"),
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service retrieve", () => {
expect(ShippingProfileServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ShippingProfileServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("validId")
)
expect(
ShippingProfileServiceMock.addShippingOption
).toHaveBeenCalledTimes(1)
expect(ShippingProfileServiceMock.addShippingOption).toHaveBeenCalledWith(
IdMap.getId("validId"),
IdMap.getId("validId")
)
})
})
})
@@ -0,0 +1,33 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
describe("POST /admin/shipping-profiles", () => {
describe("successful creation", () => {
let subject
beforeAll(async () => {
subject = await request("POST", "/admin/shipping-profiles", {
payload: {
name: "Test Profile",
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service create", () => {
expect(ShippingProfileServiceMock.create).toHaveBeenCalledTimes(1)
expect(ShippingProfileServiceMock.create).toHaveBeenCalledWith({
name: "Test Profile",
})
})
})
})
@@ -0,0 +1,34 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingOptionServiceMock } from "../../../../../services/__mocks__/shipping-option"
describe("POST /admin/shipping-options", () => {
describe("successful creation", () => {
let subject
beforeAll(async () => {
subject = await request(
"DELETE",
`/admin/shipping-options/${IdMap.getId("validId")}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service delete", () => {
expect(ShippingOptionServiceMock.delete).toHaveBeenCalledTimes(1)
expect(ShippingOptionServiceMock.delete).toHaveBeenCalledWith(
IdMap.getId("validId")
)
})
})
})
@@ -0,0 +1,34 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
describe("GET /admin/shipping-profiles/:profile_id", () => {
describe("successful retrieval", () => {
let subject
beforeAll(async () => {
subject = await request(
"GET",
`/admin/shipping-profiles/${IdMap.getId("validId")}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service retrieve", () => {
expect(ShippingProfileServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(ShippingProfileServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("validId")
)
})
})
})
@@ -0,0 +1,28 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
describe("GET /admin/shipping-profiles", () => {
describe("successful retrieval", () => {
let subject
beforeAll(async () => {
subject = await request("GET", `/admin/shipping-profiles`, {
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
})
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service retrieve", () => {
expect(ShippingProfileServiceMock.list).toHaveBeenCalledTimes(1)
expect(ShippingProfileServiceMock.list).toHaveBeenCalledWith()
})
})
})
@@ -0,0 +1,37 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
describe("DELETE /admin/shipping-profiles/:profile_id/products/:product_id", () => {
describe("successful addition", () => {
let subject
beforeAll(async () => {
const profileId = IdMap.getId("validId")
const productId = IdMap.getId("validId")
subject = await request(
"DELETE",
`/admin/shipping-profiles/${profileId}/products/${productId}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service retrieve", () => {
expect(ShippingProfileServiceMock.removeProduct).toHaveBeenCalledTimes(1)
expect(ShippingProfileServiceMock.removeProduct).toHaveBeenCalledWith(
IdMap.getId("validId"),
IdMap.getId("validId")
)
})
})
})
@@ -0,0 +1,38 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
describe("DELETE /admin/shipping-profiles/:profile_id/shipping-options/:option_id", () => {
describe("successful addition", () => {
let subject
beforeAll(async () => {
const profileId = IdMap.getId("validId")
const optionId = IdMap.getId("validId")
subject = await request(
"DELETE",
`/admin/shipping-profiles/${profileId}/shipping-options/${optionId}`,
{
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service retrieve", () => {
expect(
ShippingProfileServiceMock.removeShippingOption
).toHaveBeenCalledTimes(1)
expect(
ShippingProfileServiceMock.removeShippingOption
).toHaveBeenCalledWith(IdMap.getId("validId"), IdMap.getId("validId"))
})
})
})
@@ -0,0 +1,44 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
describe("POST /admin/shipping-profile", () => {
describe("successful update", () => {
let subject
beforeAll(async () => {
subject = await request(
"POST",
`/admin/shipping-profiles/${IdMap.getId("validId")}`,
{
payload: {
name: "Test option",
products: [IdMap.getId("product1")],
shipping_options: [IdMap.getId("shipping1")],
},
adminSession: {
jwt: {
userId: IdMap.getId("admin_user"),
},
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("calls service method", () => {
expect(ShippingProfileServiceMock.update).toHaveBeenCalledTimes(1)
expect(ShippingProfileServiceMock.update).toHaveBeenCalledWith(
IdMap.getId("validId"),
{
name: "Test option",
products: [IdMap.getId("product1")],
shipping_options: [IdMap.getId("shipping1")],
}
)
})
})
})
@@ -0,0 +1,24 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { profile_id } = req.params
const schema = Validator.object().keys({
product_id: Validator.objectId().required(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const profileService = req.scope.resolve("shippingProfileService")
await profileService.addProduct(profile_id, value.product_id)
const data = profileService.retrieve(profile_id)
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,24 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { profile_id } = req.params
const schema = Validator.object().keys({
option_id: Validator.objectId().required(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const profileService = req.scope.resolve("shippingProfileService")
await profileService.addShippingOption(profile_id, value.option_id)
const data = profileService.retrieve(profile_id)
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,21 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object().keys({
name: Validator.string().required(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const profileService = req.scope.resolve("shippingProfileService")
const data = await profileService.create(value)
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,16 @@
export default async (req, res) => {
const { profile_id } = req.params
try {
const profileService = req.scope.resolve("shippingProfileService")
await profileService.delete(profile_id)
res.status(200).json({
id: profile_id,
object: "shipping_profile",
deleted: true,
})
} catch (err) {
throw err
}
}
@@ -0,0 +1,12 @@
export default async (req, res) => {
const { profile_id } = req.params
try {
const profileService = req.scope.resolve("shippingProfileService")
const data = await profileService.retrieve(profile_id)
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,49 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/shipping-profiles", route)
route.get("/", middlewares.wrap(require("./list-shipping-profiles").default))
route.post(
"/",
middlewares.wrap(require("./create-shipping-profile").default)
)
route.get(
"/:profile_id",
middlewares.wrap(require("./get-shipping-profile").default)
)
route.post(
"/:profile_id",
middlewares.wrap(require("./update-shipping-profile").default)
)
route.delete(
"/:profile_id",
middlewares.wrap(require("./delete-shipping-profile").default)
)
// Product management
route.post(
"/:profile_id/products",
middlewares.wrap(require("./add-product").default)
)
route.delete(
"/:profile_id/products/:product_id",
middlewares.wrap(require("./remove-product").default)
)
// Shipping Option management
route.post(
"/:profile_id/shipping-options",
middlewares.wrap(require("./add-shipping-option").default)
)
route.delete(
"/:profile_id/shipping-options/:option_id",
middlewares.wrap(require("./remove-shipping-option").default)
)
return app
}
@@ -0,0 +1,11 @@
export default async (req, res) => {
try {
const profileService = req.scope.resolve("shippingProfileService")
const data = await profileService.list()
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,14 @@
export default async (req, res) => {
const { profile_id, product_id } = req.params
try {
const profileService = req.scope.resolve("shippingProfileService")
await profileService.removeProduct(profile_id, product_id)
const data = profileService.retrieve(profile_id)
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,14 @@
export default async (req, res) => {
const { profile_id, option_id } = req.params
try {
const profileService = req.scope.resolve("shippingProfileService")
await profileService.removeShippingOption(profile_id, option_id)
const data = profileService.retrieve(profile_id)
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -0,0 +1,27 @@
import { MedusaError, Validator } from "medusa-core-utils"
export default async (req, res) => {
const { profile_id } = req.params
const schema = Validator.object().keys({
name: Validator.string(),
products: Validator.array().items(Validator.objectId()),
shipping_options: Validator.array().items(Validator.objectId()),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const profileService = req.scope.resolve("shippingProfileService")
await profileService.update(profile_id, value)
const data = await profileService.retrieve(profile_id)
res.status(200).json(data)
} catch (err) {
throw err
}
}
@@ -1,7 +1,6 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
import { LineItemServiceMock } from "../../../../../services/__mocks__/line-item"
describe("POST /store/carts/:id/shipping-methods", () => {
describe("successfully adds a shipping method", () => {
@@ -24,22 +23,12 @@ describe("POST /store/carts/:id/shipping-methods", () => {
jest.clearAllMocks()
})
it("calls CartService retrieveShippingOption", () => {
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledTimes(1)
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledWith(
IdMap.getId("fr-cart"),
IdMap.getId("freeShipping")
)
})
it("calls CartService addShipping", () => {
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
IdMap.getId("fr-cart"),
{
_id: IdMap.getId("freeShipping"),
profile_id: "default_profile",
}
IdMap.getId("freeShipping"),
{}
)
})
@@ -76,24 +65,13 @@ describe("POST /store/carts/:id/shipping-methods", () => {
jest.clearAllMocks()
})
it("calls CartService retrieveShippingOption", () => {
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledTimes(1)
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledWith(
IdMap.getId("fr-cart"),
IdMap.getId("freeShipping")
)
})
it("calls CartService addShipping", () => {
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
IdMap.getId("fr-cart"),
IdMap.getId("freeShipping"),
{
_id: IdMap.getId("freeShipping"),
profile_id: "default_profile",
data: {
extra_id: "id",
},
extra_id: "id",
}
)
})
@@ -107,60 +85,4 @@ describe("POST /store/carts/:id/shipping-methods", () => {
expect(subject.body.decorated).toEqual(true)
})
})
describe("additional data without overwriting", () => {
let subject
beforeAll(async () => {
const cartId = IdMap.getId("emptyCart")
subject = await request(
"POST",
`/store/carts/${cartId}/shipping-methods`,
{
payload: {
option_id: IdMap.getId("withData"),
data: {
extra_id: "id",
},
},
}
)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls CartService retrieveShippingOption", () => {
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledTimes(1)
expect(CartServiceMock.retrieveShippingOption).toHaveBeenCalledWith(
IdMap.getId("emptyCart"),
IdMap.getId("withData")
)
})
it("calls CartService addShipping", () => {
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledTimes(1)
expect(CartServiceMock.addShippingMethod).toHaveBeenCalledWith(
IdMap.getId("emptyCart"),
{
_id: IdMap.getId("withData"),
profile_id: "default_profile",
data: {
extra_id: "id",
some_data: "yes",
},
}
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns the cart", () => {
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
expect(subject.body.decorated).toEqual(true)
})
})
})
@@ -6,7 +6,9 @@ export default async (req, res) => {
const schema = Validator.object().keys({
option_id: Validator.string().required(),
data: Validator.object().optional(),
data: Validator.object()
.optional()
.default({}),
})
const { value, error } = schema.validate(req.body)
@@ -17,17 +19,7 @@ export default async (req, res) => {
try {
const cartService = req.scope.resolve("cartService")
const method = await cartService.retrieveShippingOption(id, value.option_id)
// If the option accepts additional data this will be added
if (!_.isEmpty(value.data)) {
method.data = {
...method.data,
...value.data,
}
}
await cartService.addShippingMethod(id, method)
await cartService.addShippingMethod(id, value.option_id, value.data)
let cart = await cartService.retrieve(id)
cart = await cartService.decorate(cart)
@@ -1,6 +1,7 @@
import { Router } from "express"
import productRoutes from "./products"
import cartRoutes from "./carts"
import shippingOptionRoutes from "./shipping-options"
import middlewares from "../../middlewares"
const route = Router()
@@ -10,6 +11,7 @@ export default app => {
productRoutes(route)
cartRoutes(route)
shippingOptionRoutes(route)
return app
}
@@ -0,0 +1,46 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { carts, CartServiceMock } from "../../../../../services/__mocks__/cart"
import { ShippingProfileServiceMock } from "../../../../../services/__mocks__/shipping-profile"
describe("GET /store/shipping-options", () => {
describe("retrieves shipping options", () => {
let subject
beforeAll(async () => {
subject = await request("GET", `/store/shipping-options`, {
payload: {
cart_id: IdMap.getId("emptyCart"),
},
})
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls CartService retrieve", () => {
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(CartServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("emptyCart")
)
})
it("calls ShippingProfileService fetchCartOptions", () => {
expect(ShippingProfileServiceMock.fetchCartOptions).toHaveBeenCalledTimes(
1
)
expect(ShippingProfileServiceMock.fetchCartOptions).toHaveBeenCalledWith(
carts.emptyCart
)
})
it("returns 200", () => {
expect(subject.status).toEqual(200)
})
it("returns the cart", () => {
expect(subject.body[0]._id).toEqual(IdMap.getId("cartShippingOption"))
})
})
})
@@ -0,0 +1,24 @@
import { Validator, MedusaError } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object().keys({
cart_id: Validator.string(),
})
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 shippingProfileService = req.scope.resolve("shippingProfileService")
const cart = await cartService.retrieve(value.cart_id)
const options = await shippingProfileService.fetchCartOptions(cart)
res.status(200).json(options)
} catch (err) {
throw err
}
}
@@ -0,0 +1,12 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/shipping-options", route)
route.get("/", middlewares.wrap(require("./get-shipping-options").default))
return app
}