feat(product, types, modules-sdk): added event bus events for products (#4654)
what: - adds an eventbus dependency to product module. - emits events on product, category and collection CUD RESOLVES CORE-1450
This commit is contained in:
@@ -5,8 +5,6 @@ import { ProductRepository } from "../__fixtures__/module"
|
||||
import { createProductAndTags } from "../__fixtures__/product"
|
||||
import { productsData } from "../__fixtures__/product/data"
|
||||
import { DB_URL, TestDatabase } from "../utils"
|
||||
import { buildProductAndRelationsData } from "../__fixtures__/product/data/create-product"
|
||||
import { kebabCase } from "@medusajs/utils"
|
||||
import { IProductModuleService } from "@medusajs/types"
|
||||
|
||||
const beforeEach_ = async () => {
|
||||
|
||||
+50
@@ -6,6 +6,7 @@ import { initialize } from "../../../../src"
|
||||
import { DB_URL, TestDatabase } from "../../../utils"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { productCategoriesRankData } from "../../../__fixtures__/product-category/data"
|
||||
import { EventBusService } from "../../../__fixtures__/event-bus"
|
||||
|
||||
describe("ProductModuleService product categories", () => {
|
||||
let service: IProductModuleService
|
||||
@@ -16,16 +17,20 @@ describe("ProductModuleService product categories", () => {
|
||||
let productCategoryOne: ProductCategory
|
||||
let productCategoryTwo: ProductCategory
|
||||
let productCategories: ProductCategory[]
|
||||
let eventBus
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
eventBus = new EventBusService()
|
||||
|
||||
service = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
}, {
|
||||
eventBusModuleService: eventBus
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
@@ -68,6 +73,7 @@ describe("ProductModuleService product categories", () => {
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("listCategories", () => {
|
||||
@@ -279,6 +285,22 @@ describe("ProductModuleService product categories", () => {
|
||||
)
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(EventBusService.prototype, 'emit')
|
||||
const category = await service.createCategory({
|
||||
name: "New Category",
|
||||
parent_category_id: productCategoryOne.id,
|
||||
})
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
"product-category.created",
|
||||
{
|
||||
id: category.id
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should append rank from an existing category depending on parent", async () => {
|
||||
await service.createCategory({
|
||||
name: "New Category",
|
||||
@@ -356,6 +378,21 @@ describe("ProductModuleService product categories", () => {
|
||||
productCategoryZeroTwo = categories[5]
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(EventBusService.prototype, 'emit')
|
||||
await service.updateCategory(productCategoryZero.id, {
|
||||
name: "New Category",
|
||||
})
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
"product-category.updated",
|
||||
{
|
||||
id: productCategoryZero.id
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should update the name of the category successfully", async () => {
|
||||
await service.updateCategory(productCategoryZero.id, {
|
||||
name: "New Category",
|
||||
@@ -513,6 +550,19 @@ describe("ProductModuleService product categories", () => {
|
||||
productCategoryTwo = categories[2]
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(EventBusService.prototype, 'emit')
|
||||
await service.deleteCategory(productCategoryOne.id)
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith(
|
||||
"product-category.deleted",
|
||||
{
|
||||
id: productCategoryOne.id
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
it("should throw an error when an id does not exist", async () => {
|
||||
let error
|
||||
|
||||
|
||||
+52
@@ -6,6 +6,7 @@ import { ProductTypes } from "@medusajs/types"
|
||||
import { initialize } from "../../../../src"
|
||||
import { DB_URL, TestDatabase } from "../../../utils"
|
||||
import { createCollections } from "../../../__fixtures__/product"
|
||||
import { EventBusService } from "../../../__fixtures__/event-bus"
|
||||
|
||||
describe("ProductModuleService product collections", () => {
|
||||
let service: IProductModuleService
|
||||
@@ -16,16 +17,20 @@ describe("ProductModuleService product collections", () => {
|
||||
let productCollectionOne: ProductCollection
|
||||
let productCollectionTwo: ProductCollection
|
||||
let productCollections: ProductCollection[]
|
||||
let eventBus
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
repositoryManager = await TestDatabase.forkManager()
|
||||
eventBus = new EventBusService()
|
||||
|
||||
service = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
}, {
|
||||
eventBusModuleService: eventBus
|
||||
})
|
||||
|
||||
testManager = await TestDatabase.forkManager()
|
||||
@@ -65,6 +70,7 @@ describe("ProductModuleService product collections", () => {
|
||||
|
||||
afterEach(async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("listCollections", () => {
|
||||
@@ -261,11 +267,41 @@ describe("ProductModuleService product collections", () => {
|
||||
|
||||
expect(collections).toHaveLength(0)
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(EventBusService.prototype, 'emit')
|
||||
await service.deleteCollections(
|
||||
[collectionId],
|
||||
)
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith([{
|
||||
eventName: "product-collection.deleted",
|
||||
data: { id: collectionId }
|
||||
}])
|
||||
})
|
||||
})
|
||||
|
||||
describe("updateCollections", () => {
|
||||
const collectionId = "test-1"
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(EventBusService.prototype, 'emit')
|
||||
|
||||
await service.updateCollections(
|
||||
[{
|
||||
id: collectionId,
|
||||
title: "New Collection"
|
||||
}]
|
||||
)
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith([{
|
||||
eventName: "product-collection.updated",
|
||||
data: { id: collectionId }
|
||||
}])
|
||||
})
|
||||
|
||||
it("should update the value of the collection successfully", async () => {
|
||||
await service.updateCollections(
|
||||
[{
|
||||
@@ -311,6 +347,22 @@ describe("ProductModuleService product collections", () => {
|
||||
|
||||
expect(productCollection.title).toEqual("New Collection")
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(EventBusService.prototype, 'emit')
|
||||
|
||||
const collections = await service.createCollections(
|
||||
[{
|
||||
title: "New Collection"
|
||||
}]
|
||||
)
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith([{
|
||||
eventName: "product-collection.created",
|
||||
data: { id: collections[0].id }
|
||||
}])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
+309
@@ -1,12 +1,14 @@
|
||||
import { MedusaModule } from "@medusajs/modules-sdk"
|
||||
import { Product, ProductCategory, ProductCollection, ProductType, ProductVariant } from "@models"
|
||||
import { IProductModuleService, ProductTypes } from "@medusajs/types"
|
||||
import { kebabCase } from "@medusajs/utils"
|
||||
|
||||
import { initialize } from "../../../../src"
|
||||
import { DB_URL, TestDatabase } from "../../../utils"
|
||||
import { buildProductAndRelationsData } from "../../../__fixtures__/product/data/create-product"
|
||||
import { createProductCategories } from "../../../__fixtures__/product-category"
|
||||
import { createCollections, createTypes } from "../../../__fixtures__/product"
|
||||
import { EventBusService } from "../../../__fixtures__/event-bus"
|
||||
|
||||
const beforeEach_ = async () => {
|
||||
await TestDatabase.setupDatabase()
|
||||
@@ -15,6 +17,7 @@ const beforeEach_ = async () => {
|
||||
|
||||
const afterEach_ = async () => {
|
||||
await TestDatabase.clearDatabase()
|
||||
jest.clearAllMocks()
|
||||
}
|
||||
|
||||
describe("ProductModuleService products", function () {
|
||||
@@ -32,6 +35,7 @@ describe("ProductModuleService products", function () {
|
||||
let productTypeOne: ProductType
|
||||
let productTypeTwo: ProductType
|
||||
let images = ["image-1"]
|
||||
let eventBus
|
||||
|
||||
const productCategoriesData = [{
|
||||
id: "test-1",
|
||||
@@ -135,11 +139,14 @@ describe("ProductModuleService products", function () {
|
||||
|
||||
MedusaModule.clearInstances()
|
||||
|
||||
eventBus = new EventBusService()
|
||||
module = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
}, {
|
||||
eventBusModuleService: eventBus
|
||||
})
|
||||
})
|
||||
|
||||
@@ -228,6 +235,28 @@ describe("ProductModuleService products", function () {
|
||||
)
|
||||
})
|
||||
|
||||
it("should emit events through event bus", async () => {
|
||||
const eventBusSpy = jest.spyOn(EventBusService.prototype, 'emit')
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const updateData = {
|
||||
...data,
|
||||
id: productOne.id,
|
||||
title: "updated title"
|
||||
}
|
||||
|
||||
await module.update([updateData])
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith([{
|
||||
eventName: "product.updated",
|
||||
data: { id: productOne.id }
|
||||
}])
|
||||
})
|
||||
|
||||
it("should add relationships to a product", async () => {
|
||||
const updateData = {
|
||||
id: productOne.id,
|
||||
@@ -461,4 +490,284 @@ describe("ProductModuleService products", function () {
|
||||
expect(error.message).toEqual(`ProductVariant with id "does-not-exist" not found`)
|
||||
})
|
||||
})
|
||||
|
||||
describe("create", function () {
|
||||
let module: IProductModuleService
|
||||
let images = ["image-1"]
|
||||
let eventBus
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEach_()
|
||||
|
||||
MedusaModule.clearInstances()
|
||||
|
||||
eventBus = new EventBusService()
|
||||
module = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
}, {
|
||||
eventBusModuleService: eventBus
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(afterEach_)
|
||||
|
||||
it("should create a product", async () => {
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const products = await module.create([data])
|
||||
|
||||
expect(products).toHaveLength(1)
|
||||
expect(products[0].images).toHaveLength(1)
|
||||
expect(products[0].options).toHaveLength(1)
|
||||
expect(products[0].tags).toHaveLength(1)
|
||||
expect(products[0].categories).toHaveLength(0)
|
||||
expect(products[0].variants).toHaveLength(1)
|
||||
|
||||
expect(products[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
title: data.title,
|
||||
handle: kebabCase(data.title),
|
||||
description: data.description,
|
||||
subtitle: data.subtitle,
|
||||
is_giftcard: data.is_giftcard,
|
||||
discountable: data.discountable,
|
||||
thumbnail: images[0],
|
||||
status: data.status,
|
||||
images: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
url: images[0],
|
||||
}),
|
||||
]),
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
title: data.options[0].title,
|
||||
values: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
value: data.variants[0].options?.[0].value,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
tags: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
value: data.tags[0].value,
|
||||
}),
|
||||
]),
|
||||
type: expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
value: data.type.value,
|
||||
}),
|
||||
variants: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
title: data.variants[0].title,
|
||||
sku: data.variants[0].sku,
|
||||
allow_backorder: false,
|
||||
manage_inventory: true,
|
||||
inventory_quantity: 100,
|
||||
variant_rank: 0,
|
||||
options: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
value: data.variants[0].options?.[0].value,
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
it("should emit events through eventBus", async () => {
|
||||
const eventBusSpy = jest.spyOn(EventBusService.prototype, 'emit')
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const products = await module.create([data])
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledTimes(1)
|
||||
expect(eventBusSpy).toHaveBeenCalledWith([{
|
||||
eventName: "product.created",
|
||||
data: { id: products[0].id }
|
||||
}])
|
||||
})
|
||||
})
|
||||
|
||||
describe("softDelete", function () {
|
||||
let module: IProductModuleService
|
||||
let images = ["image-1"]
|
||||
let eventBus
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEach_()
|
||||
|
||||
MedusaModule.clearInstances()
|
||||
|
||||
eventBus = new EventBusService()
|
||||
module = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
}, {
|
||||
eventBusModuleService: eventBus
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(afterEach_)
|
||||
|
||||
it("should soft delete a product and its cascaded relations", async () => {
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const products = await module.create([data])
|
||||
|
||||
await module.softDelete([products[0].id])
|
||||
|
||||
const deletedProducts = await module.list(
|
||||
{ id: products[0].id },
|
||||
{
|
||||
relations: [
|
||||
"variants",
|
||||
"variants.options",
|
||||
"options",
|
||||
"options.values",
|
||||
],
|
||||
withDeleted: true,
|
||||
}
|
||||
)
|
||||
|
||||
expect(deletedProducts).toHaveLength(1)
|
||||
expect(deletedProducts[0].deleted_at).not.toBeNull()
|
||||
|
||||
for (const option of deletedProducts[0].options) {
|
||||
expect(option.deleted_at).not.toBeNull()
|
||||
}
|
||||
|
||||
const productOptionsValues = deletedProducts[0].options
|
||||
.map((o) => o.values)
|
||||
.flat()
|
||||
|
||||
for (const optionValue of productOptionsValues) {
|
||||
expect(optionValue.deleted_at).not.toBeNull()
|
||||
}
|
||||
|
||||
for (const variant of deletedProducts[0].variants) {
|
||||
expect(variant.deleted_at).not.toBeNull()
|
||||
}
|
||||
|
||||
const variantsOptions = deletedProducts[0].options
|
||||
.map((o) => o.values)
|
||||
.flat()
|
||||
|
||||
for (const option of variantsOptions) {
|
||||
expect(option.deleted_at).not.toBeNull()
|
||||
}
|
||||
})
|
||||
|
||||
it("should emit events through eventBus", async () => {
|
||||
const eventBusSpy = jest.spyOn(EventBusService.prototype, 'emit')
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const products = await module.create([data])
|
||||
|
||||
await module.softDelete([products[0].id])
|
||||
|
||||
expect(eventBusSpy).toHaveBeenCalledWith([{
|
||||
eventName: "product.created",
|
||||
data: { id: products[0].id }
|
||||
}])
|
||||
})
|
||||
})
|
||||
|
||||
describe("restore", function () {
|
||||
let module: IProductModuleService
|
||||
let images = ["image-1"]
|
||||
|
||||
beforeEach(async () => {
|
||||
await beforeEach_()
|
||||
|
||||
MedusaModule.clearInstances()
|
||||
|
||||
module = await initialize({
|
||||
database: {
|
||||
clientUrl: DB_URL,
|
||||
schema: process.env.MEDUSA_PRODUCT_DB_SCHEMA,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(afterEach_)
|
||||
|
||||
it("should restore a soft deleted product and its cascaded relations", async () => {
|
||||
const data = buildProductAndRelationsData({
|
||||
images,
|
||||
thumbnail: images[0],
|
||||
})
|
||||
|
||||
const products = await module.create([data])
|
||||
|
||||
await module.softDelete([products[0].id])
|
||||
await module.restore([products[0].id])
|
||||
|
||||
const deletedProducts = await module.list(
|
||||
{ id: products[0].id },
|
||||
{
|
||||
relations: [
|
||||
"variants",
|
||||
"variants.options",
|
||||
"variants.options",
|
||||
"options",
|
||||
"options.values",
|
||||
],
|
||||
withDeleted: true,
|
||||
}
|
||||
)
|
||||
|
||||
expect(deletedProducts).toHaveLength(1)
|
||||
expect(deletedProducts[0].deleted_at).toBeNull()
|
||||
|
||||
for (const option of deletedProducts[0].options) {
|
||||
expect(option.deleted_at).toBeNull()
|
||||
}
|
||||
|
||||
const productOptionsValues = deletedProducts[0].options
|
||||
.map((o) => o.values)
|
||||
.flat()
|
||||
|
||||
for (const optionValue of productOptionsValues) {
|
||||
expect(optionValue.deleted_at).toBeNull()
|
||||
}
|
||||
|
||||
for (const variant of deletedProducts[0].variants) {
|
||||
expect(variant.deleted_at).toBeNull()
|
||||
}
|
||||
|
||||
const variantsOptions = deletedProducts[0].options
|
||||
.map((o) => o.values)
|
||||
.flat()
|
||||
|
||||
for (const option of variantsOptions) {
|
||||
expect(option.deleted_at).toBeNull()
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user