Adds cart service skeleton

This commit is contained in:
Sebastian Rindom
2020-01-28 16:25:28 +01:00
parent b16f32f63f
commit 751adec7f7
7 changed files with 306 additions and 0 deletions
@@ -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)
}),
}
+25
View File
@@ -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"
)
}
})
})
})
+115
View File
@@ -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