Adds cart service skeleton
This commit is contained in:
@@ -0,0 +1,28 @@
|
||||
import IdMap from "../../helpers/id-map"
|
||||
|
||||
export const carts = {
|
||||
emptyCart: {
|
||||
_id: IdMap.getId("emptyCart"),
|
||||
title: "test",
|
||||
region: IdMap.getId("testRegion"),
|
||||
items: [],
|
||||
shippingAddress: {},
|
||||
billingAddress: {},
|
||||
discounts: [],
|
||||
customer_id: "",
|
||||
},
|
||||
}
|
||||
|
||||
export const CartModelMock = {
|
||||
create: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
updateOne: jest.fn().mockImplementation((query, update) => {
|
||||
return Promise.resolve()
|
||||
}),
|
||||
deleteOne: jest.fn().mockReturnValue(Promise.resolve()),
|
||||
findOne: jest.fn().mockImplementation(query => {
|
||||
if (query._id === IdMap.getId("emptyCart")) {
|
||||
return Promise.resolve(carts.emptyCart)
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*******************************************************************************
|
||||
* models/product-variant.js
|
||||
*
|
||||
******************************************************************************/
|
||||
import mongoose from "mongoose"
|
||||
import { BaseModel } from "../interfaces"
|
||||
|
||||
import LineItemSchema from "./schemas/line-item"
|
||||
import AddressSchema from "./schemas/address"
|
||||
|
||||
class CartModel extends BaseModel {
|
||||
static modelName = "Cart"
|
||||
|
||||
static schema = {
|
||||
email: { type: String },
|
||||
billingAddress: { type: AddressSchema },
|
||||
shippingAddress: { type: AddressSchema },
|
||||
items: { type: [LinetItemSchema], default: [] },
|
||||
region: { type: String, required: true },
|
||||
discounts: { type: [String], default: true },
|
||||
customer_id: { type: String },
|
||||
}
|
||||
}
|
||||
|
||||
export default CartModel
|
||||
@@ -0,0 +1,16 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
******************************************************************************/
|
||||
import mongoose from "mongoose"
|
||||
|
||||
export default new mongoose.Schema({
|
||||
first_name: { type: String, required: true },
|
||||
last_name: { type: String, required: true },
|
||||
address1: { type: String, required: true },
|
||||
address2: { type: String },
|
||||
city: { type: String, required: true },
|
||||
country_code: { type: String, required: true },
|
||||
province: { type: String },
|
||||
postal_code: { type: String, required: true },
|
||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||
})
|
||||
@@ -0,0 +1,29 @@
|
||||
/*******************************************************************************
|
||||
*
|
||||
******************************************************************************/
|
||||
import mongoose from "mongoose"
|
||||
|
||||
export default new mongoose.Schema({
|
||||
title: { type: String, required: true },
|
||||
description: { type: String },
|
||||
thumbnail: { type: String },
|
||||
|
||||
// mongoose doesn't allow multi-type validation but this field allows both
|
||||
// an object containing:
|
||||
// {
|
||||
// variant: (ProductVariantSchema),
|
||||
// product: (ProductSchema)
|
||||
// }
|
||||
//
|
||||
// and and array containing:
|
||||
// [
|
||||
// {
|
||||
// variant: (ProductVariantSchema),
|
||||
// product: (ProductSchema)
|
||||
// }
|
||||
// ]
|
||||
// validation is done in the cart service.
|
||||
content: { type: mongoose.Schema.Types.Mixed, required: true },
|
||||
quantity: { type: Number, required: true },
|
||||
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
|
||||
})
|
||||
@@ -0,0 +1,31 @@
|
||||
import IdMap from "../../helpers/id-map"
|
||||
|
||||
export const regions = {
|
||||
testRegion: {
|
||||
_id: IdMap.getId("testRegion"),
|
||||
name: "Test Region",
|
||||
countries: ["DK", "US", "DE"],
|
||||
tax_rate: 0.25,
|
||||
payment_providers: ["default_provider"],
|
||||
shipping_providers: ["test_shipper"],
|
||||
currency_code: "usd",
|
||||
},
|
||||
}
|
||||
|
||||
export const RegionServiceMock = {
|
||||
retrieve: jest.fn().mockImplementation(regionId => {
|
||||
if (regionId === IdMap.getId("testRegion")) {
|
||||
return Promise.resolve(regions.testRegion)
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
}),
|
||||
list: jest.fn().mockImplementation(data => {
|
||||
return Promise.resolve([regions.testRegion])
|
||||
}),
|
||||
}
|
||||
|
||||
const mock = jest.fn().mockImplementation(() => {
|
||||
return RegionServiceMock
|
||||
})
|
||||
|
||||
export default mock
|
||||
@@ -0,0 +1,62 @@
|
||||
import mongoose from "mongoose"
|
||||
import CartService from "../cart"
|
||||
import { RegionServiceMock } from "../__mocks__/region"
|
||||
import { CartModelMock, carts } from "../../models/__mocks__/cart"
|
||||
import IdMap from "../../helpers/id-map"
|
||||
|
||||
describe("CartService", () => {
|
||||
describe("retrieve", () => {
|
||||
let result
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
const cartService = new CartService({
|
||||
cartModel: CartModelMock,
|
||||
})
|
||||
result = await cartService.retrieve(IdMap.getId("emptyCart"))
|
||||
})
|
||||
|
||||
it("calls cart model functions", () => {
|
||||
expect(CartModelMock.findOne).toHaveBeenCalledTimes(1)
|
||||
expect(CartModelMock.findOne).toHaveBeenCalledWith({
|
||||
_id: IdMap.getId("emptyCart"),
|
||||
})
|
||||
})
|
||||
|
||||
it("returns the cart", () => {
|
||||
expect(result).toEqual(carts.emptyCart)
|
||||
})
|
||||
})
|
||||
|
||||
describe("setMetadata", () => {
|
||||
const cartService = new CartService({
|
||||
cartModel: CartModelMock,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
it("calls updateOne with correct params", async () => {
|
||||
const id = mongoose.Types.ObjectId()
|
||||
await cartService.setMetadata(`${id}`, "metadata", "testMetadata")
|
||||
|
||||
expect(CartModelMock.updateOne).toBeCalledTimes(1)
|
||||
expect(CartModelMock.updateOne).toBeCalledWith(
|
||||
{ _id: `${id}` },
|
||||
{ $set: { "metadata.metadata": "testMetadata" } }
|
||||
)
|
||||
})
|
||||
|
||||
it("throw error on invalid key type", async () => {
|
||||
const id = mongoose.Types.ObjectId()
|
||||
|
||||
try {
|
||||
await cartService.setMetadata(`${id}`, 1234, "nono")
|
||||
} catch (err) {
|
||||
expect(err.message).toEqual(
|
||||
"Key type is invalid. Metadata keys must be strings"
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,115 @@
|
||||
import mongoose from "mongoose"
|
||||
import _ from "lodash"
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "../interfaces"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate carts.
|
||||
* @implements BaseService
|
||||
*/
|
||||
class CartService extends BaseService {
|
||||
constructor({
|
||||
cartModel,
|
||||
regionService,
|
||||
productService,
|
||||
productVariantService,
|
||||
eventBusService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
/** @private @const {CartModel} */
|
||||
this.cartModel_ = cartModel
|
||||
|
||||
/** @private @const {EventBus} */
|
||||
this.eventBus_ = eventBusService
|
||||
|
||||
/** @private @const {ProductVariantService} */
|
||||
this.productVariantService_ = productVariantService
|
||||
|
||||
/** @private @const {ProductService} */
|
||||
this.productService_ = productService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to validate cart ids. Throws an error if the cast fails
|
||||
* @param {string} rawId - the raw cart id to validate.
|
||||
* @return {string} the validated id
|
||||
*/
|
||||
validateId_(rawId) {
|
||||
const schema = Validator.objectId()
|
||||
const { value, error } = schema.validate(rawId)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"The cartId could not be casted to an ObjectId"
|
||||
)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} selector - the query object for find
|
||||
* @return {Promise} the result of the find operation
|
||||
*/
|
||||
list(selector) {
|
||||
return this.cartModel_.find(selector)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a cart by id.
|
||||
* @param {string} cartId - the id of the cart to get.
|
||||
* @return {Promise<Cart>} the cart document.
|
||||
*/
|
||||
retrieve(cartId) {
|
||||
const validatedId = this.validateId_(cartId)
|
||||
return this.cartModel_.findOne({ _id: validatedId }).catch(err => {
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorates a cart.
|
||||
* @param {Cart} cart - the cart to decorate.
|
||||
* @param {string[]} fields - the fields to include.
|
||||
* @param {string[]} expandFields - fields to expand.
|
||||
* @return {Cart} return the decorated cart.
|
||||
*/
|
||||
async decorate(cart, fields, expandFields = []) {
|
||||
const requiredFields = ["_id", "metadata"]
|
||||
const decorated = _.pick(product, fields.concat(requiredFields))
|
||||
return decorated
|
||||
}
|
||||
|
||||
/**
|
||||
* Dedicated method to set metadata for a cart.
|
||||
* To ensure that plugins does not overwrite each
|
||||
* others metadata fields, setMetadata is provided.
|
||||
* @param {string} cartId - the cart to apply metadata to.
|
||||
* @param {string} key - key for metadata field
|
||||
* @param {string} value - value for metadata field.
|
||||
* @return {Promise} resolves to the updated result.
|
||||
*/
|
||||
setMetadata(cartId, key, value) {
|
||||
const validatedId = this.validateId_(cartId)
|
||||
|
||||
if (typeof key !== "string") {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_ARGUMENT,
|
||||
"Key type is invalid. Metadata keys must be strings"
|
||||
)
|
||||
}
|
||||
|
||||
const keyPath = `metadata.${key}`
|
||||
return this.cartModel_
|
||||
.updateOne({ _id: validatedId }, { $set: { [keyPath]: value } })
|
||||
.catch(err => {
|
||||
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default CartService
|
||||
Reference in New Issue
Block a user