fix(): Index integration tests flackyness (#13953)
* fix(): Index integration tests flackyness * fix * Create twenty-eels-remain.md * fix * fix * fix * fix * finalize * finalize * finalize * finalize * finalize * finalize * chore: empty commit * finalize * finalize * chore: empty commit * finalize * finalize
This commit is contained in:
committed by
GitHub
parent
c6556d1256
commit
9d9d0397a8
6
.changeset/twenty-eels-remain.md
Normal file
6
.changeset/twenty-eels-remain.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
"@medusajs/utils": patch
|
||||
"@medusajs/medusa-utils": patch
|
||||
---
|
||||
|
||||
fix(): Index integration tests flackyness
|
||||
@@ -6,6 +6,7 @@
|
||||
export interface WaitForIndexOptions {
|
||||
timeout?: number
|
||||
pollInterval?: number
|
||||
isLink?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -18,19 +19,22 @@ export async function waitForIndexedEntities(
|
||||
entityIds: string[],
|
||||
options: WaitForIndexOptions = {}
|
||||
): Promise<void> {
|
||||
const { timeout = 120000, pollInterval = 100 } = options
|
||||
const { timeout = 30000, pollInterval = 250 } = options
|
||||
const startTime = Date.now()
|
||||
|
||||
// Normalize the entity name to match partition table naming convention
|
||||
const normalizedName = entityName.toLowerCase().replace(/[^a-z0-9_]/g, "_")
|
||||
const normalizedName = !options.isLink
|
||||
? entityName.toLowerCase().replace(/[^a-z0-9_]/g, "_")
|
||||
: `link${entityName.toLowerCase()}`
|
||||
const partitionTableName = `cat_${normalizedName}`
|
||||
const normalizedEntityName = options.isLink ? `Link${entityName}` : entityName
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
try {
|
||||
// Query the index_data table to check if all entities are indexed
|
||||
const result = await dbConnection.raw(
|
||||
`SELECT id FROM index_data WHERE name = ? AND id = ANY(?) AND staled_at IS NULL`,
|
||||
[entityName, entityIds]
|
||||
`SELECT id FROM index_data WHERE id = ANY(?) AND staled_at IS NULL`,
|
||||
[entityIds]
|
||||
)
|
||||
|
||||
const indexedIds = result.rows
|
||||
@@ -62,15 +66,15 @@ export async function waitForIndexedEntities(
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
// Continue polling on database errors
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, pollInterval))
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
console.error(
|
||||
`Entities [${entityIds.join(
|
||||
", "
|
||||
)}] of type '${entityName}' were not fully replicated to partition table within ${timeout}ms`
|
||||
)}] of type '${normalizedEntityName}' were not fully replicated to partition table within ${timeout}ms`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -872,6 +872,8 @@ medusaIntegrationTestRunner({
|
||||
}),
|
||||
])
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
const { result: fullOrder } = await getOrderDetailWorkflow(
|
||||
appContainer
|
||||
).run({
|
||||
|
||||
@@ -112,15 +112,15 @@ medusaIntegrationTestRunner({
|
||||
testSuite: ({ getContainer, dbConnection, api, dbConfig }) => {
|
||||
let appContainer
|
||||
|
||||
beforeAll(() => {
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
process.env.ENABLE_INDEX_MODULE = "false"
|
||||
})
|
||||
|
||||
describe("Index engine - Query.index", () => {
|
||||
beforeAll(() => {
|
||||
appContainer = getContainer()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
process.env.ENABLE_INDEX_MODULE = "false"
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
await createAdminUser(dbConnection, adminHeaders, appContainer)
|
||||
})
|
||||
@@ -134,7 +134,7 @@ medusaIntegrationTestRunner({
|
||||
name: "Medusa Brand",
|
||||
})
|
||||
|
||||
await link.create({
|
||||
const [createdLink] = await link.create({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: products.find((p) => p.title === "Extra product").id,
|
||||
},
|
||||
@@ -166,6 +166,14 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
),
|
||||
waitForIndexedEntities(dbConnection, "Brand", [brand.id]),
|
||||
waitForIndexedEntities(
|
||||
dbConnection,
|
||||
"ProductProductBrandBrand",
|
||||
[createdLink.id],
|
||||
{
|
||||
isLink: true,
|
||||
}
|
||||
),
|
||||
])
|
||||
|
||||
const resultset = await fetchAndRetry(
|
||||
@@ -584,7 +592,7 @@ medusaIntegrationTestRunner({
|
||||
name: "Medusa Brand",
|
||||
})
|
||||
|
||||
await link.create({
|
||||
const [createdLink] = await link.create({
|
||||
[Modules.PRODUCT]: {
|
||||
product_id: products.find((p) => p.title === "Extra product").id,
|
||||
},
|
||||
@@ -616,6 +624,14 @@ medusaIntegrationTestRunner({
|
||||
)
|
||||
),
|
||||
waitForIndexedEntities(dbConnection, "Brand", [brand.id]),
|
||||
waitForIndexedEntities(
|
||||
dbConnection,
|
||||
"ProductProductBrandBrand",
|
||||
[createdLink.id],
|
||||
{
|
||||
isLink: true,
|
||||
}
|
||||
),
|
||||
])
|
||||
|
||||
const resultset = await fetchAndRetry(
|
||||
@@ -636,6 +652,7 @@ medusaIntegrationTestRunner({
|
||||
waitSeconds: 1.5,
|
||||
}
|
||||
)
|
||||
|
||||
expect(resultset.data.length).toEqual(1)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -150,7 +150,7 @@
|
||||
"test:chunk": "./scripts/run-workspace-unit-tests-in-chunks.sh",
|
||||
"test:integration:packages:fast": "turbo run test:integration --concurrency=1 --no-daemon --no-cache --force --filter='./packages/core/*' --filter='./packages/medusa' --filter='./packages/modules/*' --filter='./packages/modules/providers/*' --filter='!./packages/modules/{workflow-engine-redis,index,product,order,cart}'",
|
||||
"test:integration:packages:slow": "turbo run test:integration --concurrency=1 --no-daemon --no-cache --force --filter='./packages/modules/{workflow-engine-redis,index,product,order,cart}'",
|
||||
"test:integration:packages": "turbo run test:integration --concurrency=2 --no-daemon --no-cache --force --filter='./packages/core/*' --filter='./packages/medusa' --filter='./packages/modules/*' --filter='./packages/modules/providers/*'",
|
||||
"test:integration:packages": "turbo run test:integration --concurrency=1 --no-daemon --no-cache --force --filter='./packages/core/*' --filter='./packages/medusa' --filter='./packages/modules/*' --filter='./packages/modules/providers/*'",
|
||||
"test:integration:api": "turbo run test:integration:chunk --no-daemon --no-cache --force --filter=integration-tests-api",
|
||||
"test:integration:http": "turbo run test:integration:chunk --no-daemon --no-cache --force --filter=integration-tests-http",
|
||||
"test:integration:modules": "turbo run test:integration:chunk --no-daemon --no-cache --force --filter=integration-tests-modules",
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { promiseAll } from "./promise-all"
|
||||
|
||||
/**
|
||||
* Execute functions with a concurrency limit
|
||||
* @param functions Array of functions to execute in parallel
|
||||
@@ -7,13 +9,12 @@ export async function executeWithConcurrency<T>(
|
||||
functions: (() => Promise<T>)[],
|
||||
concurrency: number
|
||||
): Promise<PromiseSettledResult<Awaited<T>>[]> {
|
||||
const results: PromiseSettledResult<Awaited<T>>[] = new Array(
|
||||
functions.length
|
||||
)
|
||||
const functionsLength = functions.length
|
||||
const results: PromiseSettledResult<Awaited<T>>[] = new Array(functionsLength)
|
||||
let currentIndex = 0
|
||||
|
||||
const executeNext = async (): Promise<void> => {
|
||||
while (currentIndex < functions.length) {
|
||||
while (currentIndex < functionsLength) {
|
||||
const index = currentIndex++
|
||||
const result = await Promise.allSettled([functions[index]()])
|
||||
results[index] = result[0]
|
||||
@@ -21,11 +22,11 @@ export async function executeWithConcurrency<T>(
|
||||
}
|
||||
|
||||
const workers: Promise<void>[] = []
|
||||
for (let i = 0; i < concurrency; i++) {
|
||||
for (let i = 0; i < Math.min(concurrency, functionsLength); i++) {
|
||||
workers.push(executeNext())
|
||||
}
|
||||
|
||||
await Promise.all(workers)
|
||||
await promiseAll(workers)
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"axios": "^1.13.1",
|
||||
"express": "^4.21.0",
|
||||
"get-port": "^5.1.1",
|
||||
"randomatic": "^3.1.1"
|
||||
"ulid": "^2.3.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@medusajs/framework": "2.11.2",
|
||||
|
||||
@@ -237,6 +237,8 @@ export const dbTestUtilFactory = (): any => ({
|
||||
let hasIndexTables = false
|
||||
|
||||
const tablesToTruncate: string[] = []
|
||||
const allTablesToVerify: string[] = []
|
||||
|
||||
for (const { table_name } of tableNames) {
|
||||
if (mainPartitionTables.includes(table_name)) {
|
||||
hasIndexTables = true
|
||||
@@ -246,20 +248,61 @@ export const dbTestUtilFactory = (): any => ({
|
||||
table_name.startsWith(skipIndexPartitionPrefix) ||
|
||||
mainPartitionTables.includes(table_name)
|
||||
) {
|
||||
allTablesToVerify.push(table_name)
|
||||
continue
|
||||
}
|
||||
|
||||
tablesToTruncate.push(`${schema}."${table_name}"`)
|
||||
}
|
||||
if (tablesToTruncate.length > 0) {
|
||||
await runRawQuery(`TRUNCATE ${tablesToTruncate.join(", ")};`)
|
||||
allTablesToVerify.push(table_name)
|
||||
}
|
||||
|
||||
if (hasIndexTables) {
|
||||
await runRawQuery(
|
||||
`TRUNCATE ${schema}.index_data, ${schema}.index_relation;`
|
||||
)
|
||||
const allTablesToTruncase = [
|
||||
...tablesToTruncate,
|
||||
...(hasIndexTables ? mainPartitionTables : []),
|
||||
].join(", ")
|
||||
|
||||
if (allTablesToTruncase) {
|
||||
await runRawQuery(`TRUNCATE ${allTablesToTruncase};`)
|
||||
}
|
||||
|
||||
const verifyEmpty = async (maxRetries = 5) => {
|
||||
for (let retry = 0; retry < maxRetries; retry++) {
|
||||
const countQueries = allTablesToVerify.map(
|
||||
(tableName) =>
|
||||
`SELECT '${tableName}' as table_name, COUNT(*) as count FROM ${schema}."${tableName}"`
|
||||
)
|
||||
|
||||
const { rows: counts } = await runRawQuery(
|
||||
countQueries.join(" UNION ALL ")
|
||||
)
|
||||
|
||||
const nonEmptyTables = counts.filter(
|
||||
(row: { table_name: string; count: string }) =>
|
||||
parseInt(row.count) > 0
|
||||
)
|
||||
|
||||
if (nonEmptyTables.length === 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (retry < maxRetries - 1) {
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
} else {
|
||||
const tableList = nonEmptyTables
|
||||
.map(
|
||||
(t: { table_name: string; count: string }) =>
|
||||
`${t.table_name}(${t.count})`
|
||||
)
|
||||
.join(", ")
|
||||
logger.warn(
|
||||
`Some tables still contain data after truncate: ${tableList}`
|
||||
)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
await verifyEmpty()
|
||||
} catch (error) {
|
||||
logger.error("Error during database teardown:", error)
|
||||
throw error
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
import randomize from "randomatic"
|
||||
|
||||
class IdMap {
|
||||
ids = {}
|
||||
|
||||
getId(key, prefix = "", length = 10) {
|
||||
if (this.ids[key]) {
|
||||
return this.ids[key]
|
||||
}
|
||||
|
||||
const id = `${prefix && prefix + "_"}${randomize("Aa0", length)}`
|
||||
this.ids[key] = id
|
||||
|
||||
return id
|
||||
}
|
||||
}
|
||||
|
||||
const instance = new IdMap()
|
||||
export default instance
|
||||
@@ -1,6 +1,5 @@
|
||||
export * as TestDatabaseUtils from "./database"
|
||||
export * as TestEventUtils from "./events"
|
||||
export { default as IdMap } from "./id-map"
|
||||
export * from "./init-modules"
|
||||
export * as JestUtils from "./jest"
|
||||
export * from "./medusa-test-runner"
|
||||
|
||||
@@ -20,6 +20,7 @@ import {
|
||||
syncLinks,
|
||||
} from "./medusa-test-runner-utils"
|
||||
import { waitWorkflowExecutions } from "./medusa-test-runner-utils/wait-workflow-executions"
|
||||
import { ulid } from "ulid"
|
||||
|
||||
export interface MedusaSuiteOptions {
|
||||
dbConnection: any // knex instance
|
||||
@@ -84,8 +85,7 @@ class MedusaTestRunner {
|
||||
|
||||
constructor(config: TestRunnerConfig) {
|
||||
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
|
||||
const moduleName =
|
||||
config.moduleName ?? Math.random().toString(36).substring(7)
|
||||
const moduleName = config.moduleName ?? ulid()
|
||||
this.dbName =
|
||||
config.dbName ??
|
||||
`medusa-${moduleName.toLowerCase()}-integration-${tempName}`
|
||||
|
||||
@@ -13,6 +13,7 @@ import * as fs from "fs"
|
||||
import { getDatabaseURL, getMikroOrmWrapper, TestDatabase } from "./database"
|
||||
import { initModules, InitModulesOptions } from "./init-modules"
|
||||
import { default as MockEventBusService } from "./mock-event-bus-service"
|
||||
import { ulid } from "ulid"
|
||||
|
||||
export interface SuiteOptions<TService = unknown> {
|
||||
MikroOrmWrapper: TestDatabase
|
||||
@@ -114,9 +115,10 @@ class ModuleTestRunner<TService = any> {
|
||||
constructor(config: ModuleTestRunnerConfig<TService>) {
|
||||
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
|
||||
this.moduleName = config.moduleName
|
||||
const moduleName = this.moduleName ?? ulid()
|
||||
this.dbName =
|
||||
config.dbName ??
|
||||
`medusa-${config.moduleName.toLowerCase()}-integration-${tempName}`
|
||||
`medusa-${moduleName.toLowerCase()}-integration-${tempName}`
|
||||
this.schema = config.schema ?? "public"
|
||||
this.debug = config.debug ?? false
|
||||
this.resolve = config.resolve
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
"resolve:aliases": "yarn run -T tsc --showConfig -p tsconfig.json > tsconfig.resolved.json && yarn run -T tsc-alias -p tsconfig.resolved.json && yarn run -T rimraf tsconfig.resolved.json",
|
||||
"build": "yarn run -T rimraf dist && yarn run -T tsc --build && npm run resolve:aliases",
|
||||
"test": "../../../node_modules/.bin/jest --passWithNoTests --bail --forceExit --testPathPattern=src",
|
||||
"test:integration": "../../../node_modules/.bin/jest --passWithNoTests --forceExit --testPathPattern=\"integration-tests/__tests__/.*\\.spec\\.ts\"",
|
||||
"test:integration": "../../../node_modules/.bin/jest --passWithNoTests --forceExit --testPathPattern=\"integration-tests/__tests__/index\\.spec\\.ts\" && ../../../node_modules/.bin/jest --passWithNoTests --forceExit --testPathPattern=\"integration-tests/__tests__/race\\.spec\\.ts\" && ../../../node_modules/.bin/jest --passWithNoTests --forceExit --testPathPattern=\"integration-tests/__tests__/subscribe\\.spec\\.ts\"",
|
||||
"migration:initial": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts MIKRO_ORM_ALLOW_GLOBAL_CLI=true medusa-mikro-orm migration:create --initial",
|
||||
"migration:create": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts MIKRO_ORM_ALLOW_GLOBAL_CLI=true medusa-mikro-orm migration:create",
|
||||
"migration:up": "MIKRO_ORM_CLI_CONFIG=./mikro-orm.config.dev.ts MIKRO_ORM_ALLOW_GLOBAL_CLI=true medusa-mikro-orm migration:up",
|
||||
|
||||
34
yarn.lock
34
yarn.lock
@@ -4058,7 +4058,7 @@ __metadata:
|
||||
axios: ^1.13.1
|
||||
express: ^4.21.0
|
||||
get-port: ^5.1.1
|
||||
randomatic: ^3.1.1
|
||||
ulid: ^2.3.0
|
||||
peerDependencies:
|
||||
"@medusajs/framework": 2.11.2
|
||||
"@medusajs/medusa": 2.11.2
|
||||
@@ -18800,13 +18800,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-number@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "is-number@npm:4.0.0"
|
||||
checksum: bb17a331f357eb59a7f8db848086c41886715b2ea1db03f284a99d14001cda094083a5b6a7b343b5bcf410ccef668a70bc626d07bc2032cc4ab46dd264cea244
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-number@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "is-number@npm:7.0.0"
|
||||
@@ -20057,13 +20050,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"kind-of@npm:^6.0.0":
|
||||
version: 6.0.3
|
||||
resolution: "kind-of@npm:6.0.3"
|
||||
checksum: 61cdff9623dabf3568b6445e93e31376bee1cdb93f8ba7033d86022c2a9b1791a1d9510e026e6465ebd701a6dd2f7b0808483ad8838341ac52f003f512e0b4c4
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"kleur@npm:4.1.5":
|
||||
version: 4.1.5
|
||||
resolution: "kleur@npm:4.1.5"
|
||||
@@ -20729,13 +20715,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"math-random@npm:^1.0.1":
|
||||
version: 1.0.4
|
||||
resolution: "math-random@npm:1.0.4"
|
||||
checksum: 7b0ddc17f5dfe3b426c1e92505122e6a32f884dd50f5e0bb3898e5ce2da60b4ffb47c9b607809cf0beb5b8bf253b9dcc3b6f7331b20ce59b8bd7e8dbbbb1e347
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mdn-data@npm:2.0.28":
|
||||
version: 2.0.28
|
||||
resolution: "mdn-data@npm:2.0.28"
|
||||
@@ -23546,17 +23525,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"randomatic@npm:^3.1.1":
|
||||
version: 3.1.1
|
||||
resolution: "randomatic@npm:3.1.1"
|
||||
dependencies:
|
||||
is-number: ^4.0.0
|
||||
kind-of: ^6.0.0
|
||||
math-random: ^1.0.1
|
||||
checksum: 4b1da4b8e234d3d0bd2294a42541dfa03edbde85ee06fa0722e2b004e845da197d72fa7995723d32ea7d7402823ea62550034118cf22e94638560a509cec5bfc
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"randombytes@npm:^2.1.0":
|
||||
version: 2.1.0
|
||||
resolution: "randombytes@npm:2.1.0"
|
||||
|
||||
Reference in New Issue
Block a user