Files
medusa-store/packages/medusa-test-utils/src/module-test-runner.ts
Carlos R. L. Rodrigues e413cfefc2 feat(utils): define file config (#13283)
** What
 - Allow auto-loaded Medusa files to export a config object.
 - Currently supports isDisabled to control loading.
 - new instance `FeatureFlag` exported by `@medusajs/framework/utils`
 - `feature-flags` is now a supported folder for medusa projects, modules, providers and plugins. They will be loaded and added to `FeatureFlag`

** Why
 - Enables conditional loading of routes, migrations, jobs, subscribers, workflows, and other files based on feature flags.

```ts
// /src/feature-flags

import { FlagSettings } from "@medusajs/framework/feature-flags"

const CustomFeatureFlag: FlagSettings = {
  key: "custom_feature",
  default_val: false,
  env_key: "FF_MY_CUSTOM_FEATURE",
  description: "Enable xyz",
}

export default CustomFeatureFlag
```

```ts
// /src/modules/my-custom-module/migration/Migration20250822135845.ts

import { FeatureFlag } from "@medusajs/framework/utils"

export class Migration20250822135845 extends Migration {
  override async up(){ }
  override async down(){ }
}

defineFileConfig({
  isDisabled: () => !FeatureFlag.isFeatureEnabled("custom_feature")
})
```
2025-08-26 12:22:30 +00:00

203 lines
4.7 KiB
TypeScript

import {
ContainerRegistrationKeys,
DmlEntity,
isSharedConnectionSymbol,
loadModels,
Modules,
ModulesSdkUtils,
normalizeImportPathWithSource,
toMikroOrmEntities,
} from "@medusajs/framework/utils"
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"
export interface SuiteOptions<TService = unknown> {
MikroOrmWrapper: TestDatabase
medusaApp: any
service: TService
dbConfig: {
schema: string
clientUrl: string
}
}
function createMikroOrmWrapper(options: {
moduleModels?: (Function | DmlEntity<any, any>)[]
resolve?: string
dbConfig: any
cwd?: string
}): {
MikroOrmWrapper: TestDatabase
models: (Function | DmlEntity<any, any>)[]
} {
let moduleModels: (Function | DmlEntity<any, any>)[] =
options.moduleModels ?? []
if (!options.moduleModels) {
const basePath = normalizeImportPathWithSource(
options.resolve ?? options.cwd ?? process.cwd()
)
const modelsPath = fs.existsSync(`${basePath}/dist/models`)
? "/dist/models"
: fs.existsSync(`${basePath}/models`)
? "/models"
: ""
if (modelsPath) {
moduleModels = loadModels(`${basePath}${modelsPath}`)
} else {
moduleModels = []
}
}
moduleModels = toMikroOrmEntities(moduleModels)
const MikroOrmWrapper = getMikroOrmWrapper({
mikroOrmEntities: moduleModels,
clientUrl: options.dbConfig.clientUrl,
schema: options.dbConfig.schema,
})
return { MikroOrmWrapper, models: moduleModels }
}
export function moduleIntegrationTestRunner<TService = any>({
moduleName,
moduleModels,
moduleOptions = {},
moduleDependencies,
joinerConfig = [],
schema = "public",
debug = false,
testSuite,
resolve,
injectedDependencies = {},
cwd,
}: {
moduleName: string
moduleModels?: any[]
moduleOptions?: Record<string, any>
moduleDependencies?: string[]
joinerConfig?: any[]
schema?: string
dbName?: string
injectedDependencies?: Record<string, any>
resolve?: string
debug?: boolean
cwd?: string
testSuite: (options: SuiteOptions<TService>) => void
}) {
const moduleSdkImports = require("@medusajs/framework/modules-sdk")
process.env.LOG_LEVEL = "error"
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
const dbName = `medusa-${moduleName.toLowerCase()}-integration-${tempName}`
const dbConfig = {
clientUrl: getDatabaseURL(dbName),
schema,
debug,
}
// Use a unique connection for all the entire suite
const connection = ModulesSdkUtils.createPgConnection(dbConfig)
const { MikroOrmWrapper, models } = createMikroOrmWrapper({
moduleModels,
resolve,
dbConfig,
cwd,
})
moduleModels = models
const modulesConfig_ = {
[moduleName]: {
definition: moduleSdkImports.ModulesDefinition[moduleName],
resolve,
dependencies: moduleDependencies,
options: {
database: dbConfig,
...moduleOptions,
[isSharedConnectionSymbol]: true,
},
},
}
const moduleOptions_: InitModulesOptions = {
injectedDependencies: {
[ContainerRegistrationKeys.PG_CONNECTION]: connection,
[Modules.EVENT_BUS]: new MockEventBusService(),
[ContainerRegistrationKeys.LOGGER]: console,
...injectedDependencies,
},
modulesConfig: modulesConfig_,
databaseConfig: dbConfig,
joinerConfig,
preventConnectionDestroyWarning: true,
cwd,
}
let shutdown: () => Promise<void>
let moduleService
let medusaApp = {}
const options = {
MikroOrmWrapper,
medusaApp: new Proxy(
{},
{
get: (target, prop) => {
return medusaApp[prop]
},
}
),
service: new Proxy(
{},
{
get: (target, prop) => {
return moduleService[prop]
},
}
),
dbConfig: {
schema,
clientUrl: dbConfig.clientUrl,
},
} as SuiteOptions<TService>
const beforeEach_ = async () => {
if (moduleModels.length) {
await MikroOrmWrapper.setupDatabase()
}
const output = await initModules(moduleOptions_)
shutdown = output.shutdown
medusaApp = output.medusaApp
moduleService = output.medusaApp.modules[moduleName]
}
const afterEach_ = async () => {
if (moduleModels.length) {
await MikroOrmWrapper.clearDatabase()
}
await shutdown()
moduleService = {}
medusaApp = {}
}
return describe("", () => {
beforeEach(beforeEach_)
afterEach(afterEach_)
afterAll(async () => {
await (connection as any).context?.destroy()
await (connection as any).destroy()
})
testSuite(options)
})
}