adds get cart and create cart endpoints; adds in

This commit is contained in:
Sebastian Rindom
2020-02-25 15:56:37 +01:00
parent 379aa8f83f
commit 30785ebb95
11 changed files with 407 additions and 7 deletions

View File

@@ -0,0 +1,151 @@
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", () => {
describe("successfully creates a cart", () => {
let subject
beforeAll(async () => {
subject = await request("POST", `/store/carts`, {
payload: {
region_id: IdMap.getId("testRegion"),
},
})
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls CartService create", () => {
expect(CartServiceMock.create).toHaveBeenCalledTimes(1)
expect(CartServiceMock.create).toHaveBeenCalledWith({
region_id: IdMap.getId("testRegion"),
})
})
it("returns 201", () => {
expect(subject.status).toEqual(201)
})
it("returns the cart", () => {
expect(subject.body._id).toEqual(IdMap.getId("regionCart"))
})
})
describe("handles failed create operation", () => {
let subject
beforeAll(async () => {
subject = await request("POST", `/store/carts`, {
payload: {
region_id: IdMap.getId("fail"),
},
})
})
afterAll(() => {
jest.clearAllMocks()
})
it("returns error", () => {
expect(subject.status).toEqual(400)
expect(subject.body.message).toEqual("Region not found")
})
})
describe("returns invalid data if region_id is not set", () => {
let subject
beforeAll(async () => {
subject = await request("POST", `/store/carts`)
})
afterAll(() => {
jest.clearAllMocks()
})
it("returns error", () => {
expect(subject.status).toEqual(400)
})
})
describe("creates cart with line items", () => {
let subject
beforeAll(async () => {
subject = await request("POST", `/store/carts`, {
payload: {
region_id: IdMap.getId("testRegion"),
items: [
{
variant_id: IdMap.getId("testVariant"),
quantity: 3,
},
{
variant_id: IdMap.getId("testVariant1"),
quantity: 1,
},
],
},
})
})
afterAll(() => {
jest.clearAllMocks()
})
it("returns 201", () => {
expect(subject.status).toEqual(201)
})
it("calls line item generate", () => {
expect(LineItemServiceMock.generate).toHaveBeenCalledTimes(2)
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
IdMap.getId("testVariant"),
3,
IdMap.getId("testRegion")
)
expect(LineItemServiceMock.generate).toHaveBeenCalledWith(
IdMap.getId("testVariant1"),
1,
IdMap.getId("testRegion")
)
})
it("returns cart", () => {
expect(subject.body._id).toEqual(IdMap.getId("regionCart"))
})
})
describe("fails if line items are not formatted correctly", () => {
let subject
beforeAll(async () => {
subject = await request("POST", `/store/carts`, {
payload: {
region_id: IdMap.getId("testRegion"),
items: [
{
quantity: 3,
},
{
variant_id: IdMap.getId("testVariant1"),
quantity: 1,
},
],
},
})
})
afterAll(() => {
jest.clearAllMocks()
})
it("returns 400", () => {
expect(subject.status).toEqual(400)
})
})
})

View File

@@ -0,0 +1,49 @@
import { IdMap } from "medusa-test-utils"
import { request } from "../../../../../helpers/test-request"
import { CartServiceMock } from "../../../../../services/__mocks__/cart"
describe("GET /store/carts", () => {
describe("successfully gets a cart", () => {
let subject
beforeAll(async () => {
subject = await request("GET", `/store/carts/${IdMap.getId("emptyCart")}`)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls get product from productSerice", () => {
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(CartServiceMock.retrieve).toHaveBeenCalledWith(
IdMap.getId("emptyCart")
)
})
it("returns products", () => {
expect(subject.body._id).toEqual(IdMap.getId("emptyCart"))
})
})
describe("returns 404 on undefined cart", () => {
let subject
beforeAll(async () => {
subject = await request("GET", `/store/carts/none`)
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls get product from productSerice", () => {
expect(CartServiceMock.retrieve).toHaveBeenCalledTimes(1)
expect(CartServiceMock.retrieve).toHaveBeenCalledWith("none")
})
it("returns products", () => {
expect(subject.status).toEqual(404)
})
})
})

View File

@@ -0,0 +1,42 @@
import { Validator, MedusaError } from "medusa-core-utils"
export default async (req, res) => {
const schema = Validator.object().keys({
region_id: Validator.string().required(),
items: Validator.array()
.items({
variant_id: Validator.string().required(),
quantity: Validator.number().required(),
})
.optional(),
})
const { value, error } = schema.validate(req.body)
if (error) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, error.details)
}
try {
const lineItemService = req.scope.resolve("lineItemService")
const cartService = req.scope.resolve("cartService")
let cart = await cartService.create({ region_id: value.region_id })
if (value.items) {
await Promise.all(
value.items.map(async i => {
const lineItem = await lineItemService.generate(
i.variant_id,
i.quantity,
value.region_id
)
await cartService.addLineItem(cart._id, lineItem)
})
)
}
cart = await cartService.retrieve(cart._id)
res.status(201).json(cart)
} catch (err) {
throw err
}
}

View File

@@ -0,0 +1,13 @@
export default async (req, res) => {
const { cartId } = req.params
const cartService = req.scope.resolve("cartService")
const cart = await cartService.retrieve(cartId)
if (!cart) {
res.sendStatus(404)
return
}
res.json(cart)
}

View File

@@ -0,0 +1,13 @@
import { Router } from "express"
import middlewares from "../../../middlewares"
const route = Router()
export default app => {
app.use("/carts", route)
route.get("/:cartId", middlewares.wrap(require("./get-cart").default))
route.post("/", middlewares.wrap(require("./create-cart").default))
return app
}

View File

@@ -1,5 +1,6 @@
import { Router } from "express"
import productRoutes from './products'
import productRoutes from "./products"
import cartRoutes from "./carts"
import middlewares from "../../middlewares"
const route = Router()
@@ -8,6 +9,7 @@ export default app => {
app.use("/store", route)
productRoutes(route)
cartRoutes(route)
return app
}

View File

@@ -14,14 +14,14 @@ class CartModel extends BaseModel {
static schema = {
email: { type: String },
billing_address: { type: AddressSchema },
shipping_address: { type: AddressSchema },
billing_address: { type: AddressSchema, default: {} },
shipping_address: { type: AddressSchema, default: {} },
items: { type: [LineItemSchema], default: [] },
region_id: { type: String },
region_id: { type: String, required: true },
discounts: { type: [String], default: [] },
customer_id: { type: String },
payment_method: { type: PaymentMethodSchema },
shipping_methods: { type: [ShippingMethodSchema] },
customer_id: { type: String, default: "" },
payment_method: { type: PaymentMethodSchema, default: {} },
shipping_methods: { type: [ShippingMethodSchema], default: [] },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
}
}

View File

@@ -0,0 +1,43 @@
import { MedusaError } from "medusa-core-utils"
import { IdMap } from "medusa-test-utils"
export const carts = {
emptyCart: {
_id: IdMap.getId("emptyCart"),
items: [],
},
regionCart: {
_id: IdMap.getId("regionCart"),
name: "Product 1",
region_id: IdMap.getId("testRegion"),
},
}
export const CartServiceMock = {
create: jest.fn().mockImplementation(data => {
if (data.region_id === IdMap.getId("testRegion")) {
return Promise.resolve(carts.regionCart)
}
if (data.region_id === IdMap.getId("fail")) {
throw new MedusaError(MedusaError.Types.INVALID_DATA, "Region not found")
}
}),
retrieve: jest.fn().mockImplementation(cartId => {
if (cartId === IdMap.getId("regionCart")) {
return Promise.resolve(carts.regionCart)
}
if (cartId === IdMap.getId("emptyCart")) {
return Promise.resolve(carts.emptyCart)
}
return Promise.resolve(undefined)
}),
addLineItem: jest.fn().mockImplementation((cartId, lineItem) => {
return Promise.resolve()
}),
}
const mock = jest.fn().mockImplementation(() => {
return CartServiceMock
})
export default mock

View File

@@ -0,0 +1,34 @@
import { IdMap } from "medusa-test-utils"
export const LineItemServiceMock = {
generate: jest.fn().mockImplementation((variantId, quantity, regionId) => {
return Promise.resolve({
content: {
variant: {
_id: variantId,
},
product: {
_id: `p_${variantId}`,
},
quantity: 1,
unit_price: 100,
},
quantity,
})
}),
validate: jest.fn().mockImplementation(cartId => {
if (cartId === IdMap.getId("regionCart")) {
return Promise.resolve(carts.regionCart)
}
if (cartId === IdMap.getId("emptyCart")) {
return Promise.resolve(carts.emptyCart)
}
return Promise.resolve(undefined)
}),
}
const mock = jest.fn().mockImplementation(() => {
return LineItemServiceMock
})
export default mock

View File

@@ -65,6 +65,28 @@ describe("CartService", () => {
})
})
describe("create", () => {
const cartService = new CartService({
cartModel: CartModelMock,
regionService: RegionServiceMock,
})
beforeEach(() => {
jest.clearAllMocks()
})
it("successfully creates a cart", async () => {
await cartService.create({
region_id: IdMap.getId("testRegion"),
})
expect(CartModelMock.create).toHaveBeenCalledTimes(1)
expect(CartModelMock.create).toHaveBeenCalledWith({
region_id: IdMap.getId("testRegion"),
})
})
})
describe("addLineItem", () => {
const cartService = new CartService({
cartModel: CartModelMock,

View File

@@ -194,6 +194,37 @@ class CartService extends BaseService {
})
}
/**
* Creates a cart.
* @param {Object} data - the data to create the cart with
* @return {Promise} the result of the create operation
*/
async create(data) {
const { region_id } = data
if (!region_id) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`A region_id must be provided when creating a cart`
)
}
const region = await this.regionService_.retrieve(region_id)
if (!region) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`A region with id: ${region_id} does not exist`
)
}
return this.cartModel_
.create({
region_id,
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
* Decorates a cart.
* @param {Cart} cart - the cart to decorate.