Adds ProductVariantService

This commit is contained in:
olivermrbl
2020-01-28 12:16:59 +01:00
parent 4ba63ccc0d
commit 8c0f6107a0
12 changed files with 777 additions and 177 deletions

View File

@@ -0,0 +1,33 @@
import IdMap from "../../helpers/id-map"
export const ProductVariantModelMock = {
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("validId")) {
return Promise.resolve({
_id: IdMap.getId("validId"),
title: "test",
})
}
if (query._id === IdMap.getId("testVariant")) {
return Promise.resolve({
_id: IdMap.getId("testVariant"),
title: "test",
})
}
if (query._id === IdMap.getId("deleteId")) {
return Promise.resolve({
_id: IdMap.getId("deleteId"),
title: "test",
})
}
if (query._id === IdMap.getId("failId")) {
return Promise.reject(new Error("test error"))
}
return Promise.resolve(undefined)
}),
}

View File

@@ -1,4 +1,4 @@
import IdMap from "../../../helpers/id-map"
import IdMap from "../../helpers/id-map"
export const ProductModelMock = {
create: jest.fn().mockReturnValue(Promise.resolve()),

View File

@@ -15,6 +15,8 @@ class ProductVariantModel extends BaseModel {
prices: { type: [MoneyAmountSchema], default: [], required: true },
options: { type: [OptionValueSchema], default: [] },
image: { type: String, default: "" },
metadata: { type: mongoose.Schema.Types.Mixed, default: {} },
published: { type: Boolean, default: false },
}
}

View File

@@ -5,7 +5,6 @@
import mongoose from "mongoose"
export default new mongoose.Schema({
product_id: { type: mongoose.Types.ObjectId, required: true },
title: { type: String, required: true },
values: { type: [String], default: [] },
})

View File

@@ -60,6 +60,21 @@ const variant4 = {
],
}
const variant5 = {
_id: "5",
title: "Variant with valid id",
options: [
{
option_id: IdMap.getId("color_id"),
value: "blue",
},
{
option_id: IdMap.getId("size_id"),
value: "50",
},
],
}
const invalidVariant = {
_id: "invalid_option",
title: "variant3",
@@ -104,6 +119,9 @@ export const ProductVariantServiceMock = {
if (variantId === "4") {
return Promise.resolve(variant4)
}
if (variantId === IdMap.getId("validId")) {
return Promise.resolve(variant5)
}
if (variantId === "invalid_option") {
return Promise.resolve(invalidVariant)
}

View File

@@ -1,7 +1,26 @@
import IdMap from "../../helpers/id-map"
export const ProductServiceMock = {
createDraft: jest.fn().mockImplementation(data => {
return Promise.resolve(data)
}),
list: jest.fn().mockImplementation(data => {
if (data.variants === IdMap.getId("testVariant")) {
return Promise.resolve([
{
_id: "1234",
title: "test",
options: [
{
_id: IdMap.getId("testOptionId"),
title: "testOption",
},
],
},
])
}
return Promise.resolve([])
}),
}
const mock = jest.fn().mockImplementation(() => {

View File

@@ -1,122 +0,0 @@
import IdMap from "../../../helpers/id-map"
const variant1 = {
_id: "1",
title: "variant1",
options: [
{
option_id: IdMap.getId("color_id"),
value: "blue",
},
{
option_id: IdMap.getId("size_id"),
value: "160",
},
],
}
const variant2 = {
_id: "2",
title: "variant2",
options: [
{
option_id: IdMap.getId("color_id"),
value: "black",
},
{
option_id: IdMap.getId("size_id"),
value: "160",
},
],
}
const variant3 = {
_id: "3",
title: "variant3",
options: [
{
option_id: IdMap.getId("color_id"),
value: "blue",
},
{
option_id: IdMap.getId("size_id"),
value: "150",
},
],
}
const variant4 = {
_id: "4",
title: "variant4",
options: [
{
option_id: IdMap.getId("color_id"),
value: "blue",
},
{
option_id: IdMap.getId("size_id"),
value: "50",
},
],
}
const invalidVariant = {
_id: "invalid_option",
title: "variant3",
options: [
{
option_id: "invalid_id",
value: "blue",
},
{
option_id: IdMap.getId("size_id"),
value: "150",
},
],
}
const emptyVariant = {
_id: "empty_option",
title: "variant3",
options: [],
}
export const variants = {
one: variant1,
two: variant2,
three: variant3,
four: variant4,
invalid_variant: invalidVariant,
empty_variant: emptyVariant,
}
export const ProductVariantServiceMock = {
retrieve: jest.fn().mockImplementation(variantId => {
if (variantId === "1") {
return Promise.resolve(variant1)
}
if (variantId === "2") {
return Promise.resolve(variant2)
}
if (variantId === "3") {
return Promise.resolve(variant3)
}
if (variantId === "4") {
return Promise.resolve(variant4)
}
if (variantId === "invalid_option") {
return Promise.resolve(invalidVariant)
}
if (variantId === "empty_option") {
return Promise.resolve(emptyVariant)
}
return Promise.resolve(undefined)
}),
delete: jest.fn().mockReturnValue(Promise.resolve()),
addOptionValue: jest.fn().mockImplementation((variantId, optionId, value) => {
return Promise.resolve({})
}),
deleteOptionValue: jest.fn().mockImplementation((variantId, optionId) => {
return Promise.resolve({})
}),
}

View File

@@ -0,0 +1,416 @@
import mongoose from "mongoose"
import ProductVariantService from "../product-variant"
import { ProductVariantModelMock } from "../../models/__mocks__/product-variant"
import IdMap from "../../helpers/id-map"
import { ProductServiceMock } from "../__mocks__/product"
describe("ProductVariantService", () => {
describe("retrieve", () => {
describe("successfully get product variant", () => {
let res
beforeAll(async () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
})
res = await productVariantService.retrieve(IdMap.getId("validId"))
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls model layer findOne", () => {
expect(ProductVariantModelMock.findOne).toHaveBeenCalledTimes(1)
expect(ProductVariantModelMock.findOne).toHaveBeenCalledWith({
_id: IdMap.getId("validId"),
})
})
it("returns correct variant", () => {
expect(res.title).toEqual("test")
})
})
describe("query fail", () => {
let res
beforeAll(async () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
})
await productVariantService
.retrieve(IdMap.getId("failId"))
.catch(err => {
res = err
})
})
afterAll(() => {
jest.clearAllMocks()
})
it("calls model layer findOne", () => {
expect(ProductVariantModelMock.findOne).toHaveBeenCalledTimes(1)
expect(ProductVariantModelMock.findOne).toHaveBeenCalledWith({
_id: IdMap.getId("failId"),
})
})
it("model query throws error", () => {
expect(res.name).toEqual("database_error")
expect(res.message).toEqual("test error")
})
})
})
describe("createDraft", () => {
beforeAll(() => {
jest.clearAllMocks()
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
})
productVariantService.createDraft({
title: "Test Prod",
image: "test-image",
options: [],
prices: [
{
currency_code: "usd",
amount: 100,
},
],
})
})
it("calls model layer create", () => {
expect(ProductVariantModelMock.create).toHaveBeenCalledTimes(1)
expect(ProductVariantModelMock.create).toHaveBeenCalledWith({
title: "Test Prod",
image: "test-image",
options: [],
prices: [
{
currency_code: "usd",
amount: 100,
},
],
published: false,
})
})
})
describe("publishVariant", () => {
beforeAll(() => {
jest.clearAllMocks()
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
})
productVariantService.publish(IdMap.getId("variantId"))
})
it("calls model layer create", () => {
expect(ProductVariantModelMock.create).toHaveBeenCalledTimes(0)
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledTimes(1)
expect(ProductVariantModelMock.updateOne).toHaveBeenCalledWith(
{ _id: IdMap.getId("variantId") },
{ $set: { published: true } }
)
})
})
describe("update", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
})
beforeEach(() => {
jest.clearAllMocks()
})
it("calls updateOne with correct params", async () => {
const id = mongoose.Types.ObjectId()
await productVariantService.update(`${id}`, { title: "new title" })
expect(ProductVariantModelMock.updateOne).toBeCalledTimes(1)
expect(ProductVariantModelMock.updateOne).toBeCalledWith(
{ _id: `${id}` },
{ $set: { title: "new title" } },
{ runValidators: true }
)
})
it("throw error on invalid variant id type", async () => {
try {
await productVariantService.update(19314235, { title: "new title" })
} catch (err) {
expect(err.message).toEqual(
"The variantId could not be casted to an ObjectId"
)
}
})
it("throws error when trying to update metadata", async () => {
const id = mongoose.Types.ObjectId()
try {
await productVariantService.update(`${id}`, {
metadata: { key: "value" },
})
} catch (err) {
expect(err.message).toEqual("Use setMetadata to update metadata fields")
}
})
})
describe("decorate", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
})
const fakeVariant = {
_id: "1234",
title: "test",
image: "test-image",
prices: [
{
currency_code: "usd",
amount: 100,
},
],
metadata: { testKey: "testValue" },
published: true,
}
beforeEach(() => {
jest.clearAllMocks()
})
it("returns decorated product", async () => {
const decorated = await productVariantService.decorate(fakeVariant, [])
expect(decorated).toEqual({
_id: "1234",
metadata: { testKey: "testValue" },
})
})
it("returns decorated product with handle", async () => {
const decorated = await productVariantService.decorate(fakeVariant, [
"prices",
])
expect(decorated).toEqual({
_id: "1234",
metadata: { testKey: "testValue" },
prices: [
{
currency_code: "usd",
amount: 100,
},
],
})
})
})
describe("setMetadata", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
})
beforeEach(() => {
jest.clearAllMocks()
})
it("calls updateOne with correct params", async () => {
const id = mongoose.Types.ObjectId()
await productVariantService.setMetadata(
`${id}`,
"metadata",
"testMetadata"
)
expect(ProductVariantModelMock.updateOne).toBeCalledTimes(1)
expect(ProductVariantModelMock.updateOne).toBeCalledWith(
{ _id: `${id}` },
{ $set: { "metadata.metadata": "testMetadata" } }
)
})
it("throw error on invalid key type", async () => {
const id = mongoose.Types.ObjectId()
try {
await productVariantService.setMetadata(`${id}`, 1234, "nono")
} catch (err) {
expect(err.message).toEqual(
"Key type is invalid. Metadata keys must be strings"
)
}
})
it("throws error on invalid variantId type", async () => {
try {
await productVariantService.setMetadata("fakeVariantId", 1234, "nono")
} catch (err) {
expect(err.message).toEqual(
"The variantId could not be casted to an ObjectId"
)
}
})
})
describe("addOptionValue", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
productService: ProductServiceMock,
})
beforeEach(() => {
jest.clearAllMocks()
})
it("it successfully adds option value", async () => {
await productVariantService.addOptionValue(
IdMap.getId("testVariant"),
IdMap.getId("testOptionId"),
"testValue"
)
expect(ProductVariantModelMock.updateOne).toBeCalledTimes(1)
expect(ProductVariantModelMock.updateOne).toBeCalledWith(
{ _id: IdMap.getId("testVariant") },
{
$push: {
options: {
option_id: IdMap.getId("testOptionId"),
value: "testValue",
},
},
}
)
})
it("it successfully casts numeric option value to string", async () => {
await productVariantService.addOptionValue(
IdMap.getId("testVariant"),
IdMap.getId("testOptionId"),
1234
)
expect(ProductVariantModelMock.updateOne).toBeCalledTimes(1)
expect(ProductVariantModelMock.updateOne).toBeCalledWith(
{ _id: IdMap.getId("testVariant") },
{
$push: {
options: {
option_id: IdMap.getId("testOptionId"),
value: "1234",
},
},
}
)
})
it("throw error if product with variant does not exist", async () => {
try {
await productVariantService.addOptionValue(
IdMap.getId("failId"),
IdMap.getId("testOptionId"),
"testValue"
)
} catch (err) {
expect(err.message).toEqual(
`Products with variant: ${IdMap.getId("failId")} was not found`
)
}
})
it("throw error if product does not have option id", async () => {
try {
await productVariantService.addOptionValue(
IdMap.getId("testVariant"),
IdMap.getId("failOptionId"),
"testValue"
)
} catch (err) {
expect(err.message).toEqual(
`Associated product does not have option: ${IdMap.getId(
"failOptionId"
)}`
)
}
})
it("throw error if option value is not string", async () => {
try {
await productVariantService.addOptionValue(
IdMap.getId("testVariant"),
IdMap.getId("testOptionId"),
{}
)
} catch (err) {
expect(err.message).toEqual(
`Option value is not of type string or number`
)
}
})
})
describe("deleteOptionValue", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
productService: ProductServiceMock,
})
beforeEach(() => {
jest.clearAllMocks()
})
it("successfully deletes option value from variant", async () => {
await productVariantService.deleteOptionValue(
IdMap.getId("testVariant"),
IdMap.getId("testing")
)
expect(ProductVariantModelMock.updateOne).toBeCalledTimes(1)
expect(ProductVariantModelMock.updateOne).toBeCalledWith(
{ _id: IdMap.getId("testVariant") },
{ $pull: { options: { option_id: IdMap.getId("testing") } } }
)
})
it("throw error if product still has the option id of the option value we are trying to delete", async () => {
try {
await productVariantService.deleteOptionValue(
IdMap.getId("testVariant"),
IdMap.getId("testOptionId")
)
} catch (err) {
expect(err.message).toEqual(
`Associated product has option with id: ${IdMap.getId(
"testOptionId"
)}`
)
}
})
})
describe("delete", () => {
const productVariantService = new ProductVariantService({
productVariantModel: ProductVariantModelMock,
})
beforeEach(() => {
jest.clearAllMocks()
})
it("deletes all variants and product successfully", async () => {
await productVariantService.delete(IdMap.getId("deleteId"))
expect(ProductVariantModelMock.deleteOne).toBeCalledTimes(1)
expect(ProductVariantModelMock.deleteOne).toBeCalledWith({
_id: IdMap.getId("deleteId"),
})
})
})
})

View File

@@ -1,10 +1,10 @@
import mongoose from "mongoose"
import ProductService from "../product"
import { ProductModelMock } from "./mocks/product-model"
import { ProductModelMock } from "../../models/__mocks__/product"
import {
ProductVariantServiceMock,
variants,
} from "./mocks/product-variant-service"
} from "../__mocks__/product-variant"
import IdMap from "../../helpers/id-map"
describe("ProductService", () => {
@@ -107,6 +107,7 @@ describe("ProductService", () => {
jest.clearAllMocks()
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
})
productService.publish(IdMap.getId("productId"))
@@ -189,7 +190,7 @@ describe("ProductService", () => {
})
})
describe("add metadata to product model", () => {
describe("setMetadata", () => {
const productService = new ProductService({
productModel: ProductModelMock,
})
@@ -232,7 +233,7 @@ describe("ProductService", () => {
})
})
describe("update product", () => {
describe("update", () => {
const productService = new ProductService({
productModel: ProductModelMock,
})
@@ -285,7 +286,7 @@ describe("ProductService", () => {
})
})
describe("delete product", () => {
describe("delete", () => {
const productService = new ProductService({
productModel: ProductModelMock,
productVariantService: ProductVariantServiceMock,
@@ -307,16 +308,6 @@ describe("ProductService", () => {
_id: IdMap.getId("deleteId"),
})
})
it("throw error on invalid product id type", async () => {
try {
await productService.update(19314235, { title: "new title" })
} catch (err) {
expect(err.message).toEqual(
"The productId could not be casted to an ObjectId"
)
}
})
})
describe("addVariant", () => {

View File

@@ -1,12 +1,14 @@
import _ from "lodash"
import { BaseService } from "../interfaces"
import { Validator, MedusaError } from "medusa-core-utils"
/**
* Provides layer to manipulate products.
* Provides layer to manipulate product variants.
* @implements BaseService
*/
class ProductVariantService extends BaseService {
/** @param { productModel: (ProductModel) } */
constructor({ productVariantModel, eventBusService }) {
/** @param { productVariantModel: (ProductVariantModel) } */
constructor({ productVariantModel, eventBusService, productService }) {
super()
/** @private @const {ProductVariantModel} */
@@ -14,36 +16,250 @@ class ProductVariantService extends BaseService {
/** @private @const {EventBus} */
this.eventBus_ = eventBusService
/** @private @const {ProductService} */
this.productService_ = productService
}
/**
* Creates an unpublished product.
* @param {object} product - the product to create
* Used to validate product ids. Throws an error if the cast fails
* @param {string} rawId - the raw product 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 variantId could not be casted to an ObjectId"
)
}
return value
}
/**
* Gets a product variant by id.
* @param {string} variantId - the id of the product to get.
* @return {Promise<Product>} the product document.
*/
retrieve(variantId) {
const validatedId = this.validateId_(variantId)
return this.productVariantModel_
.findOne({ _id: validatedId })
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
* Creates an unpublished product variant.
* @param {object} variant - the variant to create
* @return {Promise} resolves to the creation result.
*/
createDraft(productVariant) {
return this.productVariantModel_.create({
...productVariant,
published: false,
})
return this.productVariantModel_
.create({
...productVariant,
published: false,
})
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
* Creates an publishes product.
* @param {string} productId - ID of the product to publish.
* Creates an publishes variant.
* @param {string} variantId - ID of the variant to publish.
* @return {Promise} resolves to the creation result.
*/
publish(variantId) {
return this.productVariantModel_
.updateOne({ _id: variantId }, { $set: { published: true } })
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
* Updates a variant. Metadata updates and price updates should
* use dedicated methods, e.g. `setMetadata`, etc. The function
* will throw errors if metadata updates and price updates are attempted.
* @param {string} variantId - the id of the variant. Must be a string that
* can be casted to an ObjectId
* @param {object} update - an object with the update values.
* @return {Promise} resolves to the update result.
*/
update(variantId, update) {
const validatedId = this.validateId_(variantId)
if (update.metadata) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
"Use setMetadata to update metadata fields"
)
}
return this.productVariantModel_
.updateOne(
{ _id: validatedId },
{ $set: update },
{ runValidators: true }
)
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
* Adds option value to a varaint.
* Fails when product with variant does not exists or
* if that product does not have an option with the given
* option id. Fails if given variant is not found.
* Option value must be of type string or number.
* @param {string} variantId - the variant to decorate.
* @param {string} optionId - the option from product.
* @param {string | number} optionValue - option value to add.
* @return {Promise} the result of the update operation.
*/
async addOptionValue(variantId, optionId, optionValue) {
const products = await this.productService_.list({ variants: variantId })
if (!products.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Products with variant: ${variantId} was not found`
)
}
const product = products[0]
if (!product.options.find(o => o._id === optionId)) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Associated product does not have option: ${optionId}`
)
}
const variant = await this.retrieve(variantId)
if (!variant) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Variant with ${variantId} was not found`
)
}
if (typeof optionValue !== "string" && typeof optionValue !== "number") {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Option value is not of type string or number`
)
}
return this.productVariantModel_.updateOne(
{ _id: variantId },
{ $set: { published: true } }
{ $push: { options: { option_id: optionId, value: `${optionValue}` } } }
)
}
/**
*
* Deletes option value from given variant.
* Fails when product with variant does not exists or
* if that product has an option with the given
* option id.
* This method should only be used from the product service.
* @param {string} variantId - the variant to decorate.
* @param {string} optionId - the option from product.
* @return {Promise} the result of the update operation.
*/
addOptionValue(variantId, optionId, optionValue) {
async deleteOptionValue(variantId, optionId) {
const products = await this.productService_.list({ variants: variantId })
if (!products.length) {
throw new MedusaError(
MedusaError.Types.NOT_FOUND,
`Products with variant: ${variantId} was not found`
)
}
const product = products[0]
if (product.options.find(o => o._id === optionId)) {
throw new MedusaError(
MedusaError.Types.INVALID_DATA,
`Associated product has option with id: ${optionId}`
)
}
return this.productVariantModel_.updateOne(
{ _id: variantId },
{ $pull: { options: { option_id: optionId } } }
)
}
/**
* @param {Object} selector - the query object for find
* @return {Promise} the result of the find operation
*/
list(selector) {
return this.productVariantModel_.find(selector)
}
/**
* Deletes a variant from given variant id.
* @param {string} variantId - the id of the variant to delete. Must be
* castable as an ObjectId
* @return {Promise} the result of the delete operation.
*/
async delete(variantId) {
const variant = await this.retrieve(variantId)
// Delete is idempotent, but we return a promise to allow then-chaining
if (!variant) {
return Promise.resolve()
}
return this.productVariantModel_
.deleteOne({ _id: variantId })
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
/**
* Decorates a variant with variant variants.
* @param {ProductVariant} variant - the variant to decorate.
* @param {string[]} fields - the fields to include.
* @param {string[]} expandFields - fields to expand.
* @return {ProductVariant} return the decorated variant.
*/
async decorate(variant, fields, expandFields = []) {
const requiredFields = ["_id", "metadata"]
const decorated = _.pick(variant, fields.concat(requiredFields))
return decorated
}
/**
* Dedicated method to set metadata for a variant.
* To ensure that plugins does not overwrite each
* others metadata fields, setMetadata is provided.
* @param {string} variantId - the variant to decorate.
* @param {string} key - key for metadata field
* @param {string} value - value for metadata field.
* @return {Promise} resolves to the updated result.
*/
setMetadata(variantId, key, value) {
const validatedId = this.validateId_(variantId)
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.productVariantModel_
.updateOne({ _id: validatedId }, { $set: { [keyPath]: value } })
.catch(err => {
throw new MedusaError(MedusaError.Types.DB_ERROR, err.message)
})
}
}

View File

@@ -41,10 +41,10 @@ class ProductService extends BaseService {
}
/**
*
* @param {Object} selector - the query object for find
* @return {Promise} the result of the find operation
*/
list(query) {
const selector = {}
list(selector) {
return this.productModel_.find(selector)
}
@@ -477,15 +477,9 @@ class ProductService extends BaseService {
`To delete an option, first delete all variants, such that when option is deleted, no duplicate variants will exist. For more info check MEDUSA.com`
)
}
await Promise.all(
product.variants.map(async variantId =>
this.productVariantService_.deleteOptionValue(variantId, optionId)
)
)
}
return this.productModel_.updateOne(
const result = await this.productModel_.updateOne(
{ _id: productId },
{
$pull: {
@@ -495,6 +489,17 @@ class ProductService extends BaseService {
},
}
)
// If we reached this point, we can delete option value from variants
if (product.variants) {
await Promise.all(
product.variants.map(async variantId =>
this.productVariantService_.deleteOptionValue(variantId, optionId)
)
)
}
return result
}
/**
@@ -543,7 +548,9 @@ class ProductService extends BaseService {
}
/**
* Sets metadata for a product
* Dedicated method to set metadata for a product.
* To ensure that plugins does not overwrite each
* others metadata fields, setMetadata is provided.
* @param {string} productId - the product to decorate.
* @param {string} key - key for metadata field
* @param {string} value - value for metadata field.

View File

@@ -1,29 +1,29 @@
Arguments:
/Users/srindom/.nvm/versions/node/v10.15.3/bin/node /usr/local/Cellar/yarn/1.19.0/libexec/bin/yarn.js add -D client-sessions
/usr/local/bin/node /usr/local/Cellar/yarn/1.17.3/libexec/bin/yarn.js
PATH:
/Users/srindom/.rvm/gems/ruby-2.6.3/bin:/Users/srindom/.rvm/gems/ruby-2.6.3@global/bin:/Users/srindom/.rvm/rubies/ruby-2.6.3/bin:/Users/srindom/.nvm/versions/node/v10.15.3/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/TeX/texbin:/Users/srindom/.rvm/bin
/Users/oliverjuhl/Desktop/development/google-cloud-sdk/bin:/Library/Frameworks/Python.framework/Versions/3.8/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/share/dotnet:~/.dotnet/tools:/Library/Frameworks/Mono.framework/Versions/Current/Commands:/Users/oliverjuhl/miniconda3/bin:/Users/oliverjuhl/development/flutter/bin
Yarn version:
1.19.0
1.17.3
Node version:
10.15.3
10.15.0
Platform:
darwin x64
Trace:
Error: https://registry.yarnpkg.com/medusa-core-utils: Not found
at Request.params.callback [as _callback] (/usr/local/Cellar/yarn/1.19.0/libexec/lib/cli.js:66918:18)
at Request.self.callback (/usr/local/Cellar/yarn/1.19.0/libexec/lib/cli.js:140539:22)
at Request.emit (events.js:189:13)
at Request.<anonymous> (/usr/local/Cellar/yarn/1.19.0/libexec/lib/cli.js:141511:10)
at Request.emit (events.js:189:13)
at IncomingMessage.<anonymous> (/usr/local/Cellar/yarn/1.19.0/libexec/lib/cli.js:141433:12)
at Object.onceWrapper (events.js:277:13)
at IncomingMessage.emit (events.js:194:15)
at endReadableNT (_stream_readable.js:1125:12)
at Request.params.callback [as _callback] (/usr/local/Cellar/yarn/1.17.3/libexec/lib/cli.js:66830:18)
at Request.self.callback (/usr/local/Cellar/yarn/1.17.3/libexec/lib/cli.js:140464:22)
at Request.emit (events.js:182:13)
at Request.<anonymous> (/usr/local/Cellar/yarn/1.17.3/libexec/lib/cli.js:141436:10)
at Request.emit (events.js:182:13)
at IncomingMessage.<anonymous> (/usr/local/Cellar/yarn/1.17.3/libexec/lib/cli.js:141358:12)
at Object.onceWrapper (events.js:273:13)
at IncomingMessage.emit (events.js:187:15)
at endReadableNT (_stream_readable.js:1094:12)
at process._tickCallback (internal/process/next_tick.js:63:19)
npm manifest:
@@ -48,6 +48,7 @@ npm manifest:
"@babel/preset-env": "^7.7.5",
"@babel/register": "^7.7.4",
"@babel/runtime": "^7.7.6",
"client-sessions": "^0.8.0",
"eslint": "^6.7.2",
"jest": "^24.9.0",
"nodemon": "^2.0.1",
@@ -1699,6 +1700,13 @@ Lockfile:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
client-sessions@^0.8.0:
version "0.8.0"
resolved "https://registry.yarnpkg.com/client-sessions/-/client-sessions-0.8.0.tgz#a7d8c5558ad5d56f2a199f3533eb654b5df893fd"
integrity sha1-p9jFVYrV1W8qGZ81M+tlS134k/0=
dependencies:
cookies "^0.7.0"
cliui@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
@@ -1878,6 +1886,14 @@ Lockfile:
resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c"
integrity sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA==
cookies@^0.7.0:
version "0.7.3"
resolved "https://registry.yarnpkg.com/cookies/-/cookies-0.7.3.tgz#7912ce21fbf2e8c2da70cf1c3f351aecf59dadfa"
integrity sha512-+gixgxYSgQLTaTIilDHAdlNPZDENDQernEMiIcZpYYP14zgHsCt4Ce1FEjFtcp6GefhozebB6orvhAAWx/IS0A==
dependencies:
depd "~1.1.2"
keygrip "~1.0.3"
copy-descriptor@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d"
@@ -3947,6 +3963,11 @@ Lockfile:
resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.1.tgz#def12d9c941017fabfb00f873af95e9c99e1be87"
integrity sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==
keygrip@~1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.0.3.tgz#399d709f0aed2bab0a059e0cdd3a5023a053e1dc"
integrity sha512-/PpesirAIfaklxUzp4Yb7xBper9MwP6hNRA6BGGUFCgbJ+BM5CKBtsoxinNXkLHAr+GXS1/lSlF2rP7cv5Fl+g==
kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0:
version "3.2.2"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64"