Files
medusa-store/packages/modules/index/integration-tests/__tests__/index-engine-module.spec.ts
Carlos R. L. Rodrigues cc73802ab3 chore(order): dml (#10292)
* ../../core/types/src/dml/index.ts

* ../../core/types/src/dml/index.ts

* fix: relationships mapping

* handle nullable foreign keys types

* handle nullable foreign keys types

* handle nullable foreign keys types

* continue to update product category repository

* fix all product category repositories issues

* fix product category service types

* fix product module service types

* fix product module service types

* fix repository template type

* refactor: use a singleton DMLToMikroORM factory instance

Since the MikroORM MetadataStorage is global, we will also have to turn DML
to MikroORM entities conversion use a global bucket as well

* refactor: update product module to use DML in tests

* wip: tests

* WIP product linkable fixes

* continue type fixing and start test fixing

* test: fix more tests

* fix repository

* fix pivot table computaion + fix mikro orm repository

* fix many to many management and configuration

* fix many to many management and configuration

* fix many to many management and configuration

* update product tag relation configuration

* Introduce experimental dml hooks to fix some issues with categories

* more fixes

* fix product tests

* add missing id prefixes

* fix product category handle management

* test: fix more failing tests

* test: make it all green

* test: fix breaking tests

* fix: build issues

* fix: build issues

* fix: more breaking tests

* refactor: fix issues after merge

* refactor: fix issues after merge

* refactor: surpress types error

* test: fix DML failing tests

* improve many to many inference + tests

* Wip fix columns from product entity

* remove product model before create hook and manage handle validation and transformation at the service level

* test: fix breaking unit tests

* fix: product module service to not update handle on product update

* fix define link and joiner config

* test: fix joiner config test

* test: fix joiner config test

* fix joiner config primary keys

* Fix joiner config builder

* Fix joiner config builder

* test: remove only modifier from test

* refactor: remove hooks usage from product collection

* refactor: remove hooks usage from product-option

* refactor: remove hooks usage for computing category handle

* refactor: remove hooks usage from productCategory model

* refactor: remove hooks from DML

* refactor: remove cruft

* order dml

* cleanup

* re add foerign key indexes

* wip

* chore: remove unused types

* wip

* changes

* rm raw

* autoincrement

* wip

* rel

* refactor: cleanup

* migration and models configuration adjustments

* cleanup

* number searchable

* fix random ordering

* fix

* test: fix product-category tests

* test: update breaking DML tests

* test: array assertion to not care about ordering

* fix: temporarily apply id ordering for products

* types

* wip

* WIP type improvements

* update order models

* partially fix types temporarely

* rel

* fix: recursive type issue

* improve type inference breaks

* improve type inference breaks

* update models

* rm nullable

* default value

* repository

* update default value handling

* fix unit tests

* WIP

* toMikroORM

* fix relations

* cascades

* fix

* experimental dml hooks

* rm migration

* serial

* nullable autoincrement

* fix model

* model changes

* fix one to one DML

* order test

* fix addresses

* fix unit tests

* Re align dml entity name inference

* update model table name config

* update model table name config

* revert

* update return relation

* WIP

* hasOne

* models

* fix

* model

* initial commit

* cart service

* order module

* utils unit test

* index engine

* changeset

* merge

* fix hasOne with fk

* update

* free text filter per entity

* tests

* prod category

* property string many to many

* fix big number

* link modules migration set names

* merge

* shipping option rules

* serializer

* unit test

* fix test mikro orm init

* fix test mikro orm init

* Maintain merge object properties

* fix test mikro orm init

* prevent unit test from connecting to db

* wip

* fix test

* fix test

* link test

* schema

* models

* auto increment

* hook

* model hooks

* order

* wip

* orm version

* request return field

* fix return configuration on order model

* workflows

* core flows

* unit test

* test

* base repo

* test

* base repo

* test fix

* inventory move

* locking inventory

* test

* free text fix

* rm timeout mock

* migrate fulfillment values

* v6.4.3

* cleanup

* link-modules update sql

* revert test

* remove fake timers

---------

Co-authored-by: adrien2p <adrien.deperetti@gmail.com>
Co-authored-by: Harminder Virk <virk.officials@gmail.com>
Co-authored-by: Oli Juhl <59018053+olivermrbl@users.noreply.github.com>
2025-01-21 08:04:47 -05:00

733 lines
18 KiB
TypeScript

import {
configLoader,
container,
logger,
MedusaAppLoader,
} from "@medusajs/framework"
import { MedusaAppOutput, MedusaModule } from "@medusajs/framework/modules-sdk"
import { EventBusTypes, IndexTypes } from "@medusajs/framework/types"
import {
ContainerRegistrationKeys,
ModuleRegistrationName,
Modules,
toMikroORMEntity,
} from "@medusajs/framework/utils"
import { initDb, TestDatabaseUtils } from "@medusajs/test-utils"
import { EntityManager } from "@mikro-orm/postgresql"
import { IndexData, IndexRelation } from "@models"
import { asValue } from "awilix"
import * as path from "path"
import { EventBusServiceMock } from "../__fixtures__"
import { dbName } from "../__fixtures__/medusa-config"
const eventBusMock = new EventBusServiceMock()
const queryMock = {
graph: jest.fn(),
}
const dbUtils = TestDatabaseUtils.dbTestUtilFactory()
jest.setTimeout(300000)
const productId = "prod_1"
const variantId = "var_1"
const priceSetId = "price_set_1"
const priceId = "money_amount_1"
const linkId = "link_id_1"
const sendEvents = async (eventDataToEmit) => {
let a = 0
queryMock.graph = jest.fn().mockImplementation((query) => {
const entity = query.entity
if (entity === "product") {
return {
data: {
id: a++ > 0 ? "aaaa" : productId,
},
}
} else if (entity === "product_variant") {
return {
data: {
id: variantId,
sku: "aaa test aaa",
product: {
id: productId,
},
},
}
} else if (entity === "price_set") {
return {
data: {
id: priceSetId,
},
}
} else if (entity === "price") {
return {
data: {
id: priceId,
amount: 100,
price_set: [
{
id: priceSetId,
},
],
},
}
} else if (entity === "product_variant_price_set") {
return {
data: {
id: linkId,
variant_id: variantId,
price_set_id: priceSetId,
variant: [
{
id: variantId,
},
],
},
}
}
return {}
})
await eventBusMock.emit(eventDataToEmit)
}
let isFirstTime = true
let medusaAppLoader!: MedusaAppLoader
let index!: IndexTypes.IIndexService
const beforeAll_ = async () => {
try {
await configLoader(
path.join(__dirname, "./../__fixtures__"),
"medusa-config"
)
console.log(`Creating database ${dbName}`)
await dbUtils.create(dbName)
dbUtils.pgConnection_ = await initDb()
container.register({
[ContainerRegistrationKeys.LOGGER]: asValue(logger),
[ContainerRegistrationKeys.QUERY]: asValue(null),
[ContainerRegistrationKeys.PG_CONNECTION]: asValue(dbUtils.pgConnection_),
})
medusaAppLoader = new MedusaAppLoader(container as any)
// Migrations
await medusaAppLoader.runModulesMigrations()
const linkPlanner = await medusaAppLoader.getLinksExecutionPlanner()
const plan = await linkPlanner.createPlan()
await linkPlanner.executePlan(plan)
// Clear partially loaded instances
MedusaModule.clearInstances()
// Bootstrap modules
const globalApp = await medusaAppLoader.load()
index = container.resolve(Modules.INDEX)
// Mock event bus the index module
;(index as any).eventBusModuleService_ = eventBusMock
await globalApp.onApplicationStart()
;(index as any).storageProvider_.query_ = queryMock
return globalApp
} catch (error) {
console.error("Error initializing", error?.message)
throw error
}
}
const beforeEach_ = async (eventDataToEmit) => {
jest.clearAllMocks()
if (isFirstTime) {
isFirstTime = false
await sendEvents(eventDataToEmit)
return
}
try {
await medusaAppLoader.runModulesLoader()
await sendEvents(eventDataToEmit)
} catch (error) {
console.error("Error runner modules loaders", error?.message)
throw error
}
}
const afterEach_ = async () => {
try {
await dbUtils.teardown({ schema: "public" })
} catch (error) {
console.error("Error tearing down database:", error?.message)
throw error
}
}
describe("IndexModuleService", function () {
let medusaApp: MedusaAppOutput
let onApplicationPrepareShutdown!: () => Promise<void>
let onApplicationShutdown!: () => Promise<void>
beforeAll(async () => {
medusaApp = await beforeAll_()
onApplicationPrepareShutdown = medusaApp.onApplicationPrepareShutdown
onApplicationShutdown = medusaApp.onApplicationShutdown
})
afterAll(async () => {
await onApplicationPrepareShutdown()
await onApplicationShutdown()
await dbUtils.shutdown(dbName)
})
describe("on created or attached events", function () {
let manager
const eventDataToEmit: EventBusTypes.Event[] = [
{
name: "product.created",
data: {
id: productId,
},
},
{
name: "product.created",
data: {
id: "PRODUCTASDASDAS",
},
},
{
name: "variant.created",
data: {
id: variantId,
product: {
id: productId,
},
},
},
{
name: "PriceSet.created",
data: {
id: priceSetId,
},
},
{
name: "price.created",
data: {
id: priceId,
price_set: {
id: priceSetId,
},
},
},
{
name: "LinkProductVariantPriceSet.attached",
data: {
id: linkId,
variant_id: variantId,
price_set_id: priceSetId,
},
},
]
beforeEach(async () => {
await beforeEach_(eventDataToEmit)
manager = (
medusaApp.sharedContainer!.resolve(ModuleRegistrationName.INDEX) as any
).container_.manager as EntityManager
})
afterEach(afterEach_)
it("should create the corresponding index entries and index relation entries", async function () {
/**
* Validate all index entries and index relation entries
*/
const indexEntries: IndexData[] = await manager.find(
toMikroORMEntity(IndexData),
{}
)
const productIndexEntries = indexEntries.filter((entry) => {
return entry.name === "Product"
})
expect(productIndexEntries).toHaveLength(2)
const variantIndexEntries = indexEntries.filter((entry) => {
return entry.name === "ProductVariant"
})
expect(variantIndexEntries).toHaveLength(1)
const priceSetIndexEntries = indexEntries.filter((entry) => {
return entry.name === "PriceSet"
})
expect(priceSetIndexEntries).toHaveLength(1)
const priceIndexEntries = indexEntries.filter((entry) => {
return entry.name === "Price"
})
expect(priceIndexEntries).toHaveLength(1)
const linkIndexEntries = indexEntries.filter((entry) => {
return entry.name === "LinkProductVariantPriceSet"
})
expect(linkIndexEntries).toHaveLength(1)
const indexRelationEntries: IndexRelation[] = await manager.find(
IndexRelation,
{}
)
expect(indexRelationEntries).toHaveLength(4)
const productVariantIndexRelationEntries = indexRelationEntries.filter(
(entry) => {
return (
entry.parent_id === productId &&
entry.parent_name === "Product" &&
entry.child_id === variantId &&
entry.child_name === "ProductVariant"
)
}
)
expect(productVariantIndexRelationEntries).toHaveLength(1)
const variantLinkIndexRelationEntries = indexRelationEntries.filter(
(entry) => {
return (
entry.parent_id === variantId &&
entry.parent_name === "ProductVariant" &&
entry.child_id === linkId &&
entry.child_name === "LinkProductVariantPriceSet"
)
}
)
expect(variantLinkIndexRelationEntries).toHaveLength(1)
const linkPriceSetIndexRelationEntries = indexRelationEntries.filter(
(entry) => {
return (
entry.parent_id === linkId &&
entry.parent_name === "LinkProductVariantPriceSet" &&
entry.child_id === priceSetId &&
entry.child_name === "PriceSet"
)
}
)
expect(linkPriceSetIndexRelationEntries).toHaveLength(1)
const priceSetPriceIndexRelationEntries = indexRelationEntries.filter(
(entry) => {
return (
entry.parent_id === priceSetId &&
entry.parent_name === "PriceSet" &&
entry.child_id === priceId &&
entry.child_name === "Price"
)
}
)
expect(priceSetPriceIndexRelationEntries).toHaveLength(1)
})
})
describe("on unordered created or attached events", function () {
let manager
const eventDataToEmit: EventBusTypes.Event[] = [
{
name: "variant.created",
data: {
id: variantId,
product: {
id: productId,
},
},
},
{
name: "product.created",
data: {
id: productId,
},
},
{
name: "product.created",
data: {
id: "PRODUCTASDASDAS",
},
},
{
name: "PriceSet.created",
data: {
id: priceSetId,
},
},
{
name: "price.created",
data: {
id: priceId,
price_set: {
id: priceSetId,
},
},
},
{
name: "LinkProductVariantPriceSet.attached",
data: {
id: linkId,
variant_id: variantId,
price_set_id: priceSetId,
},
},
]
beforeEach(async () => {
await beforeEach_(eventDataToEmit)
manager = (
medusaApp.sharedContainer!.resolve(ModuleRegistrationName.INDEX) as any
).container_.manager as EntityManager
})
afterEach(afterEach_)
it("should create the corresponding index entries and index relation entries", async function () {
/**
* Validate all index entries and index relation entries
*/
const indexEntries: IndexData[] = await manager.find(
toMikroORMEntity(IndexData),
{}
)
const productIndexEntries = indexEntries.filter((entry) => {
return entry.name === "Product"
})
expect(productIndexEntries).toHaveLength(2)
expect(productIndexEntries[0].id).toEqual(productId)
const variantIndexEntries = indexEntries.filter((entry) => {
return entry.name === "ProductVariant"
})
expect(variantIndexEntries).toHaveLength(1)
expect(variantIndexEntries[0].id).toEqual(variantId)
const priceSetIndexEntries = indexEntries.filter((entry) => {
return entry.name === "PriceSet"
})
expect(priceSetIndexEntries).toHaveLength(1)
expect(priceSetIndexEntries[0].id).toEqual(priceSetId)
const priceIndexEntries = indexEntries.filter((entry) => {
return entry.name === "Price"
})
expect(priceIndexEntries).toHaveLength(1)
expect(priceIndexEntries[0].id).toEqual(priceId)
const linkIndexEntries = indexEntries.filter((entry) => {
return entry.name === "LinkProductVariantPriceSet"
})
expect(linkIndexEntries).toHaveLength(1)
expect(linkIndexEntries[0].id).toEqual(linkId)
const indexRelationEntries: IndexRelation[] = await manager.find(
IndexRelation,
{}
)
expect(indexRelationEntries).toHaveLength(4)
const productVariantIndexRelationEntries = indexRelationEntries.filter(
(entry) => {
return (
entry.parent_id === productId &&
entry.parent_name === "Product" &&
entry.child_id === variantId &&
entry.child_name === "ProductVariant"
)
}
)
expect(productVariantIndexRelationEntries).toHaveLength(1)
const variantLinkIndexRelationEntries = indexRelationEntries.filter(
(entry) => {
return (
entry.parent_id === variantId &&
entry.parent_name === "ProductVariant" &&
entry.child_id === linkId &&
entry.child_name === "LinkProductVariantPriceSet"
)
}
)
expect(variantLinkIndexRelationEntries).toHaveLength(1)
const linkPriceSetIndexRelationEntries = indexRelationEntries.filter(
(entry) => {
return (
entry.parent_id === linkId &&
entry.parent_name === "LinkProductVariantPriceSet" &&
entry.child_id === priceSetId &&
entry.child_name === "PriceSet"
)
}
)
expect(linkPriceSetIndexRelationEntries).toHaveLength(1)
const priceSetPriceIndexRelationEntries = indexRelationEntries.filter(
(entry) => {
return (
entry.parent_id === priceSetId &&
entry.parent_name === "PriceSet" &&
entry.child_id === priceId &&
entry.child_name === "Price"
)
}
)
expect(priceSetPriceIndexRelationEntries).toHaveLength(1)
})
})
describe("on updated events", function () {
let manager
const updateData = async (manager) => {
const indexRepository = manager.getRepository(IndexData)
await indexRepository.upsertMany([
{
id: productId,
name: "Product",
data: {
id: productId,
},
},
{
id: variantId,
name: "ProductVariant",
data: {
id: variantId,
sku: "aaa test aaa",
product: {
id: productId,
},
},
},
])
}
const eventDataToEmit: EventBusTypes.Event[] = [
{
name: "product.updated",
data: {
id: productId,
},
},
{
name: "variant.updated",
data: {
id: variantId,
product: {
id: productId,
},
},
},
]
beforeEach(async () => {
await beforeEach_(eventDataToEmit)
manager = (
medusaApp.sharedContainer!.resolve(ModuleRegistrationName.INDEX) as any
).container_.manager as EntityManager
await updateData(manager)
queryMock.graph = jest.fn().mockImplementation((query) => {
const entity = query.entity
if (entity === "product") {
return {
data: {
id: productId,
title: "updated Title",
},
}
} else if (entity === "product_variant") {
return {
data: {
id: variantId,
sku: "updated sku",
product: [
{
id: productId,
},
],
},
}
}
return {}
})
await eventBusMock.emit(eventDataToEmit)
})
afterEach(afterEach_)
it("should update the corresponding index entries", async () => {
const updatedIndexEntries = await manager.find(
toMikroORMEntity(IndexData),
{}
)
expect(updatedIndexEntries).toHaveLength(2)
const productEntry = updatedIndexEntries.find((entry) => {
return entry.name === "Product" && entry.id === productId
})
expect(productEntry?.data?.title).toEqual("updated Title")
const variantEntry = updatedIndexEntries.find((entry) => {
return entry.name === "ProductVariant" && entry.id === variantId
})
expect(variantEntry?.data?.sku).toEqual("updated sku")
})
})
describe("on deleted events", function () {
let manager
const eventDataToEmit: EventBusTypes.Event[] = [
{
name: "product.created",
data: {
id: productId,
},
},
{
name: "variant.created",
data: {
id: variantId,
product: {
id: productId,
},
},
},
{
name: "PriceSet.created",
data: {
id: priceSetId,
},
},
{
name: "price.created",
data: {
id: priceId,
price_set: {
id: priceSetId,
},
},
},
{
name: "LinkProductVariantPriceSet.attached",
data: {
id: linkId,
variant_id: variantId,
price_set_id: priceSetId,
},
},
]
const deleteEventDataToEmit: EventBusTypes.Event[] = [
{
name: "product.deleted",
data: {
id: productId,
},
},
{
name: "variant.deleted",
data: {
id: variantId,
},
},
]
beforeEach(async () => {
await beforeEach_(eventDataToEmit)
manager = (
medusaApp.sharedContainer!.resolve(ModuleRegistrationName.INDEX) as any
).container_.manager as EntityManager
queryMock.graph = jest.fn().mockImplementation((query) => {
const entity = query.entity
if (entity === "product") {
return {
data: {
id: productId,
},
}
} else if (entity === "product_variant") {
return {
data: {
id: variantId,
product: [
{
id: productId,
},
],
},
}
}
return {}
})
await eventBusMock.emit(deleteEventDataToEmit)
})
afterEach(afterEach_)
it("should consume all deleted events and delete the index entries", async () => {
const indexEntries = await manager.find(toMikroORMEntity(IndexData), {})
const indexRelationEntries = await manager.find(
toMikroORMEntity(IndexRelation)
)
expect(indexEntries).toHaveLength(3)
expect(indexRelationEntries).toHaveLength(2)
})
})
})