Adds LineItemService (#11)
This commit is contained in:
committed by
GitHub
parent
245557ac2e
commit
8d51a3f716
@@ -1,6 +1,12 @@
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
|
||||
export const LineItemServiceMock = {
|
||||
validate: jest.fn().mockImplementation(data => {
|
||||
if (data.title === "invalid lineitem") {
|
||||
throw new Error(`"content" is required`)
|
||||
}
|
||||
return data
|
||||
}),
|
||||
generate: jest.fn().mockImplementation((variantId, quantity, regionId) => {
|
||||
return Promise.resolve({
|
||||
content: {
|
||||
@@ -16,15 +22,6 @@ export const LineItemServiceMock = {
|
||||
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(() => {
|
||||
|
||||
@@ -96,6 +96,10 @@ const emptyVariant = {
|
||||
options: [],
|
||||
}
|
||||
|
||||
const eur10us12 = {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
}
|
||||
|
||||
export const variants = {
|
||||
one: variant1,
|
||||
two: variant2,
|
||||
@@ -103,6 +107,7 @@ export const variants = {
|
||||
four: variant4,
|
||||
invalid_variant: invalidVariant,
|
||||
empty_variant: emptyVariant,
|
||||
eur10us12: eur10us12,
|
||||
}
|
||||
|
||||
export const ProductVariantServiceMock = {
|
||||
@@ -128,7 +133,8 @@ export const ProductVariantServiceMock = {
|
||||
if (variantId === "empty_option") {
|
||||
return Promise.resolve(emptyVariant)
|
||||
}
|
||||
return Promise.resolve(undefined)
|
||||
if (variantId === IdMap.getId("eur-10-us-12"))
|
||||
return Promise.resolve(eur10us12)
|
||||
}),
|
||||
canCoverQuantity: jest.fn().mockImplementation((variantId, quantity) => {
|
||||
if (variantId === IdMap.getId("can-cover")) {
|
||||
|
||||
@@ -32,7 +32,14 @@ export const ProductServiceMock = {
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
if (data.variants === IdMap.getId("eur-10-us-12")) {
|
||||
return Promise.resolve([
|
||||
{
|
||||
_id: "1234",
|
||||
title: "test",
|
||||
},
|
||||
])
|
||||
}
|
||||
if (data.variants === IdMap.getId("failId")) {
|
||||
return Promise.resolve([])
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
|
||||
import { RegionServiceMock } from "../__mocks__/region"
|
||||
import { CartModelMock, carts } from "../../models/__mocks__/cart"
|
||||
import { LineItemServiceMock } from "../__mocks__/line-item"
|
||||
|
||||
describe("CartService", () => {
|
||||
describe("retrieve", () => {
|
||||
@@ -91,6 +92,7 @@ describe("CartService", () => {
|
||||
const cartService = new CartService({
|
||||
cartModel: CartModelMock,
|
||||
productVariantService: ProductVariantServiceMock,
|
||||
lineItemService: LineItemServiceMock,
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -128,53 +130,6 @@ describe("CartService", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully defaults quantity of content to 1", async () => {
|
||||
const lineItem = {
|
||||
title: "New 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("product"),
|
||||
},
|
||||
},
|
||||
quantity: 10,
|
||||
}
|
||||
|
||||
await cartService.addLineItem(IdMap.getId("emptyCart"), lineItem)
|
||||
|
||||
expect(CartModelMock.updateOne).toHaveBeenCalledTimes(1)
|
||||
expect(CartModelMock.updateOne).toHaveBeenCalledWith(
|
||||
{
|
||||
_id: IdMap.getId("emptyCart"),
|
||||
},
|
||||
{
|
||||
$push: {
|
||||
items: {
|
||||
title: "New 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("product"),
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
quantity: 10,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("successfully merges existing line item", async () => {
|
||||
const lineItem = {
|
||||
title: "merge line",
|
||||
@@ -251,7 +206,7 @@ describe("CartService", () => {
|
||||
|
||||
it("throws if line item not validated", async () => {
|
||||
const lineItem = {
|
||||
title: "merge line",
|
||||
title: "invalid lineitem",
|
||||
description: "This is a new line",
|
||||
thumbnail: "test-img-yeah.com/thumb",
|
||||
}
|
||||
|
||||
49
packages/medusa/src/services/__tests__/line-item.js
Normal file
49
packages/medusa/src/services/__tests__/line-item.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import mongoose from "mongoose"
|
||||
import { IdMap } from "medusa-test-utils"
|
||||
import LineItemService from "../line-item"
|
||||
import { ProductVariantServiceMock } from "../__mocks__/product-variant"
|
||||
import { ProductServiceMock } from "../__mocks__/product"
|
||||
import { RegionServiceMock } from "../__mocks__/region"
|
||||
|
||||
describe("LineItemService", () => {
|
||||
describe("generate", () => {
|
||||
let result
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks()
|
||||
const lineItemService = new LineItemService({
|
||||
productVariantService: ProductVariantServiceMock,
|
||||
productService: ProductServiceMock,
|
||||
regionService: RegionServiceMock,
|
||||
})
|
||||
result = await lineItemService.generate(
|
||||
IdMap.getId("eur-10-us-12"),
|
||||
IdMap.getId("region-france"),
|
||||
2
|
||||
)
|
||||
})
|
||||
|
||||
it("generates line item and successfully defaults quantity of content to 1", () => {
|
||||
expect(result).toEqual({
|
||||
content: {
|
||||
unit_price: 10,
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
},
|
||||
product: {
|
||||
_id: "1234",
|
||||
title: "test",
|
||||
},
|
||||
quantity: 1,
|
||||
},
|
||||
product: {
|
||||
_id: "1234",
|
||||
title: "test",
|
||||
},
|
||||
variant: {
|
||||
_id: IdMap.getId("eur-10-us-12"),
|
||||
},
|
||||
quantity: 2,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -14,6 +14,7 @@ class CartService extends BaseService {
|
||||
productService,
|
||||
productVariantService,
|
||||
regionService,
|
||||
lineItemService,
|
||||
}) {
|
||||
super()
|
||||
|
||||
@@ -32,6 +33,9 @@ class CartService extends BaseService {
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
|
||||
/** @private @const {LineItemService} */
|
||||
this.lineItemService_ = lineItemService
|
||||
|
||||
/** @private @const {PaymentProviderService} */
|
||||
this.paymentProviderService_ = paymentProviderService
|
||||
}
|
||||
@@ -54,47 +58,6 @@ class CartService extends BaseService {
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to validate line items.
|
||||
* @param {object} rawLineItem - the raw cart id to validate.
|
||||
* @return {object} the validated id
|
||||
*/
|
||||
validateLineItem_(rawLineItem) {
|
||||
const content = Validator.object({
|
||||
unit_price: Validator.number().required(),
|
||||
variant: Validator.object().required(),
|
||||
product: Validator.object().required(),
|
||||
quantity: Validator.number()
|
||||
.integer()
|
||||
.min(1)
|
||||
.default(1),
|
||||
})
|
||||
|
||||
const lineItemSchema = Validator.object({
|
||||
title: Validator.string().required(),
|
||||
description: Validator.string(),
|
||||
thumbnail: Validator.string(),
|
||||
content: Validator.alternatives()
|
||||
.try(content, Validator.array().items(content))
|
||||
.required(),
|
||||
quantity: Validator.number()
|
||||
.integer()
|
||||
.min(1)
|
||||
.required(),
|
||||
metadata: Validator.object(),
|
||||
})
|
||||
|
||||
const { value, error } = lineItemSchema.validate(rawLineItem)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
error.details[0].message
|
||||
)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Contents of a line item
|
||||
* @typedef {(object | array)} LineItemContent
|
||||
@@ -245,7 +208,7 @@ class CartService extends BaseService {
|
||||
* @retur {Promise} the result of the update operation
|
||||
*/
|
||||
async addLineItem(cartId, lineItem) {
|
||||
const validatedLineItem = this.validateLineItem_(lineItem)
|
||||
const validatedLineItem = this.lineItemService_.validate(lineItem)
|
||||
|
||||
const cart = await this.retrieve(cartId)
|
||||
if (!cart) {
|
||||
|
||||
130
packages/medusa/src/services/line-item.js
Normal file
130
packages/medusa/src/services/line-item.js
Normal file
@@ -0,0 +1,130 @@
|
||||
import { Validator, MedusaError } from "medusa-core-utils"
|
||||
import { BaseService } from "medusa-interfaces"
|
||||
|
||||
/**
|
||||
* Provides layer to manipulate line items.
|
||||
* @implements BaseService
|
||||
*/
|
||||
class LineItemService extends BaseService {
|
||||
constructor({ productVariantService, productService, regionService }) {
|
||||
super()
|
||||
|
||||
/** @private @const {ProductVariantService} */
|
||||
this.productVariantService_ = productVariantService
|
||||
|
||||
/** @private @const {ProductService} */
|
||||
this.productService_ = productService
|
||||
|
||||
/** @private @const {RegionService} */
|
||||
this.regionService_ = regionService
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to validate line items.
|
||||
* @param {object} rawLineItem - the raw line item to validate.
|
||||
* @return {object} the validated id
|
||||
*/
|
||||
validate(rawLineItem) {
|
||||
const content = Validator.object({
|
||||
unit_price: Validator.number().required(),
|
||||
variant: Validator.object().required(),
|
||||
product: Validator.object().required(),
|
||||
quantity: Validator.number()
|
||||
.integer()
|
||||
.min(1)
|
||||
.default(1),
|
||||
})
|
||||
|
||||
const lineItemSchema = Validator.object({
|
||||
title: Validator.string().required(),
|
||||
description: Validator.string(),
|
||||
thumbnail: Validator.string(),
|
||||
content: Validator.alternatives()
|
||||
.try(content, Validator.array().items(content))
|
||||
.required(),
|
||||
quantity: Validator.number()
|
||||
.integer()
|
||||
.min(1)
|
||||
.required(),
|
||||
metadata: Validator.object(),
|
||||
})
|
||||
|
||||
const { value, error } = lineItemSchema.validate(rawLineItem)
|
||||
if (error) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
error.details[0].message
|
||||
)
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
/**
|
||||
* Contents of a line item
|
||||
* @typedef {(object | array)} LineItemContent
|
||||
* @property {number} unit_price - the price of the content
|
||||
* @property {object} variant - the product variant of the content
|
||||
* @property {object} product - the product of the content
|
||||
* @property {number} quantity - the quantity of the content
|
||||
*/
|
||||
|
||||
/**
|
||||
* A collection of contents grouped in the same line item
|
||||
* @typedef {LineItemContent[]} LineItemContentArray
|
||||
*/
|
||||
|
||||
/**
|
||||
* Generates a line item.
|
||||
* @param {string} variantId - id of the line item variant
|
||||
* @param {*} regionId - id of the cart region
|
||||
* @param {*} quantity - number of items
|
||||
*/
|
||||
async generate(variantId, regionId, quantity) {
|
||||
const variant = await this.productVariantService_.retrieve(variantId)
|
||||
if (!variant) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Variant: ${variantId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
const region = await await this.regionService_.retrieve(regionId)
|
||||
if (!region) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.NOT_FOUND,
|
||||
`Region: ${regionId} was not found`
|
||||
)
|
||||
}
|
||||
|
||||
const products = await this.productService_.list({ variants: variantId })
|
||||
// this should never fail, since a variant must have a product associated
|
||||
// with it to exists, but better safe than sorry
|
||||
if (!products.length) {
|
||||
throw new MedusaError(
|
||||
MedusaError.Types.INVALID_DATA,
|
||||
`Could not find product for variant with id: ${variantId}`
|
||||
)
|
||||
}
|
||||
|
||||
const product = products[0]
|
||||
const unit_price = await this.productVariantService_.getRegionPrice(
|
||||
variantId,
|
||||
regionId
|
||||
)
|
||||
|
||||
return {
|
||||
variant,
|
||||
product,
|
||||
quantity,
|
||||
content: {
|
||||
unit_price,
|
||||
variant,
|
||||
product,
|
||||
quantity: 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LineItemService
|
||||
Reference in New Issue
Block a user