Files
medusa-store/packages/modules/index/integration-tests/__tests__/config-sync.spec.ts
Adrien de Peretti 448dbcb596 feat(medusa): Rollout index engine behind feature flag (#11431)
**What**
- Add index engine feature flag
- apply it to the `store/products` end point as well as `admin/products`
- Query builder various fixes
- search capabilities on full data of every entities. The `q` search will be applied to all involved joined table for selection/where clauses

Co-authored-by: Carlos R. L. Rodrigues <37986729+carlos-r-l-rodrigues@users.noreply.github.com>
2025-02-18 13:49:57 +00:00

318 lines
9.0 KiB
TypeScript

import {
configLoader,
container,
logger,
MedusaAppLoader,
} from "@medusajs/framework"
import { MedusaAppOutput, MedusaModule } from "@medusajs/framework/modules-sdk"
import { ContainerRegistrationKeys, Modules } from "@medusajs/framework/utils"
import { initDb, TestDatabaseUtils } from "@medusajs/test-utils"
import { IndexTypes, ModulesSdkTypes } from "@medusajs/types"
import { Configuration } from "@utils"
import { asValue } from "awilix"
import path from "path"
import { setTimeout } from "timers/promises"
import { EventBusServiceMock } from "../__fixtures__"
import { dbName } from "../__fixtures__/medusa-config"
import { updateRemovedSchema } from "../__fixtures__/update-removed-schema"
import { updatedSchema } from "../__fixtures__/updated-schema"
const eventBusMock = new EventBusServiceMock()
const queryMock = {
graph: jest.fn().mockImplementation(async () => ({ data: [] })),
}
const dbUtils = TestDatabaseUtils.dbTestUtilFactory()
jest.setTimeout(300000)
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.PG_CONNECTION]: asValue(dbUtils.pgConnection_),
})
medusaAppLoader = new MedusaAppLoader()
// 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()
container.register({
[ContainerRegistrationKeys.QUERY]: asValue(queryMock),
[ContainerRegistrationKeys.REMOTE_QUERY]: asValue(queryMock),
[Modules.EVENT_BUS]: asValue(eventBusMock),
})
index = container.resolve(Modules.INDEX)
await globalApp.onApplicationStart()
await setTimeout(1000)
return globalApp
} catch (error) {
console.error("Error initializing", error?.message)
throw error
}
}
const beforeEach_ = async () => {
jest.clearAllMocks()
if (isFirstTime) {
isFirstTime = false
return
}
try {
await medusaAppLoader.runModulesLoader()
} 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 syncIndexConfig", function () {
let medusaApp: MedusaAppOutput
let indexMetadataService: ModulesSdkTypes.IMedusaInternalService<any>
let indexSyncService: ModulesSdkTypes.IMedusaInternalService<any>
let dataSynchronizer: ModulesSdkTypes.IMedusaInternalService<any>
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)
})
beforeEach(async () => {
await beforeEach_()
index = container.resolve(Modules.INDEX)
indexMetadataService = (index as any).indexMetadataService_
indexSyncService = (index as any).indexSyncService_
dataSynchronizer = (index as any).dataSynchronizer_
})
afterEach(afterEach_)
it.only("should full sync all entities when the config has changed", async () => {
await setTimeout(1000)
const currentMetadata = await indexMetadataService.list()
expect(currentMetadata).toHaveLength(7)
expect(currentMetadata).toEqual(
expect.arrayContaining([
expect.objectContaining({
entity: "InternalObject",
fields: "b",
status: "done",
}),
expect.objectContaining({
entity: "Product",
fields: "created_at,id,title",
status: "done",
}),
expect.objectContaining({
entity: "InternalNested",
fields: "a",
status: "done",
}),
expect.objectContaining({
entity: "PriceSet",
fields: "id",
status: "done",
}),
expect.objectContaining({
entity: "Price",
fields: "amount,price_set.id",
status: "done",
}),
expect.objectContaining({
entity: "ProductVariant",
fields: "id,product.id,product_id,sku",
status: "done",
}),
expect.objectContaining({
entity: "LinkProductVariantPriceSet",
fields: "id,price_set_id,variant_id",
status: "done",
}),
])
)
let indexSync = await indexSyncService.list({
last_key: null,
})
expect(indexSync).toHaveLength(7)
// update config schema
;(index as any).schemaObjectRepresentation_ = null
;(index as any).moduleOptions_ ??= {}
;(index as any).moduleOptions_.schema = updatedSchema
;(index as any).buildSchemaObjectRepresentation_()
let configurationChecker = new Configuration({
schemaObjectRepresentation: (index as any).schemaObjectRepresentation_,
indexMetadataService,
indexSyncService,
dataSynchronizer,
})
const syncRequired = await configurationChecker.checkChanges()
expect(syncRequired).toHaveLength(2)
expect(syncRequired).toEqual(
expect.arrayContaining([
expect.objectContaining({
entity: "Product",
fields: "handle,id,title",
status: "pending",
}),
expect.objectContaining({
entity: "Price",
fields: "amount,currency_code,price_set.id",
status: "pending",
}),
])
)
indexSync = await indexSyncService.list({
last_key: null,
})
expect(indexSync).toHaveLength(7)
const updatedMetadata = await indexMetadataService.list()
expect(updatedMetadata).toHaveLength(7)
expect(updatedMetadata).toEqual(
expect.arrayContaining([
expect.objectContaining({
entity: "InternalObject",
fields: "b",
status: "done",
}),
expect.objectContaining({
entity: "Product",
fields: "handle,id,title",
status: "pending",
}),
expect.objectContaining({
entity: "InternalNested",
fields: "a",
status: "done",
}),
expect.objectContaining({
entity: "PriceSet",
fields: "id",
status: "done",
}),
expect.objectContaining({
entity: "Price",
fields: "amount,currency_code,price_set.id",
status: "pending",
}),
expect.objectContaining({
entity: "ProductVariant",
fields: "id,product.id,product_id,sku",
status: "done",
}),
expect.objectContaining({
entity: "LinkProductVariantPriceSet",
fields: "id,price_set_id,variant_id",
status: "done",
}),
])
)
await (index as any).dataSynchronizer_.syncEntities(syncRequired)
// Sync again removing entities not linked
;(index as any).schemaObjectRepresentation_ = null
;(index as any).moduleOptions_ ??= {}
;(index as any).moduleOptions_.schema = updateRemovedSchema
;(index as any).buildSchemaObjectRepresentation_()
const spyDataSynchronizer_ = jest.spyOn(
(index as any).dataSynchronizer_,
"removeEntities"
)
configurationChecker = new Configuration({
schemaObjectRepresentation: (index as any).schemaObjectRepresentation_,
indexMetadataService,
indexSyncService,
dataSynchronizer,
})
const syncRequired2 = await configurationChecker.checkChanges()
expect(syncRequired2).toHaveLength(1)
expect(syncRequired2).toEqual(
expect.arrayContaining([
expect.objectContaining({
entity: "ProductVariant",
fields: "description,id,product.id,product_id,sku",
status: "pending",
}),
])
)
const updatedMetadata2 = await indexMetadataService.list()
expect(updatedMetadata2).toHaveLength(2)
expect(updatedMetadata2).toEqual(
expect.arrayContaining([
expect.objectContaining({
entity: "Product",
fields: "handle,id,title",
status: "done",
}),
expect.objectContaining({
entity: "ProductVariant",
fields: "description,id,product.id,product_id,sku",
status: "pending",
}),
])
)
expect(spyDataSynchronizer_).toHaveBeenCalledTimes(1)
})
})