chore(): start moving some packages to the core directory (#7215)
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
import { MikroORM, Options, SqlEntityManager } from "@mikro-orm/postgresql"
|
||||
|
||||
export function getDatabaseURL(dbName?: string): string {
|
||||
const DB_HOST = process.env.DB_HOST ?? "localhost"
|
||||
const DB_USERNAME = process.env.DB_USERNAME ?? ""
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD
|
||||
const DB_NAME = dbName ?? process.env.DB_TEMP_NAME
|
||||
|
||||
return `postgres://${DB_USERNAME}${
|
||||
DB_PASSWORD ? `:${DB_PASSWORD}` : ""
|
||||
}@${DB_HOST}/${DB_NAME}`
|
||||
}
|
||||
|
||||
export function getMikroOrmConfig({
|
||||
mikroOrmEntities,
|
||||
pathToMigrations,
|
||||
clientUrl,
|
||||
schema,
|
||||
}: {
|
||||
mikroOrmEntities: any[]
|
||||
pathToMigrations?: string
|
||||
clientUrl?: string
|
||||
schema?: string
|
||||
}): Options {
|
||||
const DB_URL = clientUrl ?? getDatabaseURL()
|
||||
|
||||
return {
|
||||
type: "postgresql",
|
||||
clientUrl: DB_URL,
|
||||
entities: Object.values(mikroOrmEntities),
|
||||
schema: schema ?? process.env.MEDUSA_DB_SCHEMA,
|
||||
debug: false,
|
||||
migrations: {
|
||||
pathTs: pathToMigrations,
|
||||
silent: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export interface TestDatabase {
|
||||
mikroOrmEntities: any[]
|
||||
pathToMigrations?: string
|
||||
schema?: string
|
||||
clientUrl?: string
|
||||
|
||||
orm: MikroORM | null
|
||||
manager: SqlEntityManager | null
|
||||
|
||||
setupDatabase(): Promise<void>
|
||||
clearDatabase(): Promise<void>
|
||||
getManager(): SqlEntityManager
|
||||
forkManager(): SqlEntityManager
|
||||
getOrm(): MikroORM
|
||||
}
|
||||
|
||||
export function getMikroOrmWrapper({
|
||||
mikroOrmEntities,
|
||||
pathToMigrations,
|
||||
clientUrl,
|
||||
schema,
|
||||
}: {
|
||||
mikroOrmEntities: any[]
|
||||
pathToMigrations?: string
|
||||
clientUrl?: string
|
||||
schema?: string
|
||||
}): TestDatabase {
|
||||
return {
|
||||
mikroOrmEntities,
|
||||
pathToMigrations,
|
||||
clientUrl: clientUrl ?? getDatabaseURL(),
|
||||
schema: schema ?? process.env.MEDUSA_DB_SCHEMA,
|
||||
|
||||
orm: null,
|
||||
manager: null,
|
||||
|
||||
getManager() {
|
||||
if (this.manager === null) {
|
||||
throw new Error("manager entity not available")
|
||||
}
|
||||
|
||||
return this.manager
|
||||
},
|
||||
|
||||
forkManager() {
|
||||
if (this.manager === null) {
|
||||
throw new Error("manager entity not available")
|
||||
}
|
||||
|
||||
return this.manager.fork()
|
||||
},
|
||||
|
||||
getOrm() {
|
||||
if (this.orm === null) {
|
||||
throw new Error("orm entity not available")
|
||||
}
|
||||
|
||||
return this.orm
|
||||
},
|
||||
|
||||
async setupDatabase() {
|
||||
const OrmConfig = getMikroOrmConfig({
|
||||
mikroOrmEntities: this.mikroOrmEntities,
|
||||
pathToMigrations: this.pathToMigrations,
|
||||
clientUrl: this.clientUrl,
|
||||
schema: this.schema,
|
||||
})
|
||||
|
||||
// Initializing the ORM
|
||||
this.orm = await MikroORM.init(OrmConfig)
|
||||
|
||||
this.manager = this.orm.em
|
||||
|
||||
try {
|
||||
await this.orm.getSchemaGenerator().ensureDatabase()
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
|
||||
await this.manager?.execute(
|
||||
`CREATE SCHEMA IF NOT EXISTS "${this.schema ?? "public"}";`
|
||||
)
|
||||
|
||||
const pendingMigrations = await this.orm
|
||||
.getMigrator()
|
||||
.getPendingMigrations()
|
||||
|
||||
if (pendingMigrations && pendingMigrations.length > 0) {
|
||||
await this.orm
|
||||
.getMigrator()
|
||||
.up({ migrations: pendingMigrations.map((m) => m.name!) })
|
||||
} else {
|
||||
await this.orm.schema.refreshDatabase() // ensure db exists and is fresh
|
||||
}
|
||||
},
|
||||
|
||||
async clearDatabase() {
|
||||
if (this.orm === null) {
|
||||
throw new Error("ORM not configured")
|
||||
}
|
||||
|
||||
await this.manager?.execute(
|
||||
`DROP SCHEMA IF EXISTS "${this.schema ?? "public"}" CASCADE;`
|
||||
)
|
||||
|
||||
await this.manager?.execute(
|
||||
`CREATE SCHEMA IF NOT EXISTS "${this.schema ?? "public"}";`
|
||||
)
|
||||
|
||||
try {
|
||||
await this.orm.close()
|
||||
} catch {}
|
||||
|
||||
this.orm = null
|
||||
this.manager = null
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
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
|
||||
@@ -0,0 +1,9 @@
|
||||
export * as TestDatabaseUtils from "./database"
|
||||
export { default as IdMap } from "./id-map"
|
||||
export * as JestUtils from "./jest"
|
||||
export { default as MockManager } from "./mock-manager"
|
||||
export { default as MockRepository } from "./mock-repository"
|
||||
export * from "./init-modules"
|
||||
export { default as MockEventBusService } from "./mock-event-bus-service"
|
||||
export * from "./module-test-runner"
|
||||
export * from "./medusa-test-runner"
|
||||
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
ExternalModuleDeclaration,
|
||||
InternalModuleDeclaration,
|
||||
ModuleJoinerConfig,
|
||||
} from "@medusajs/types"
|
||||
import {
|
||||
ContainerRegistrationKeys,
|
||||
ModulesSdkUtils,
|
||||
promiseAll,
|
||||
} from "@medusajs/utils"
|
||||
|
||||
export interface InitModulesOptions {
|
||||
injectedDependencies?: Record<string, unknown>
|
||||
databaseConfig: {
|
||||
clientUrl: string
|
||||
schema?: string
|
||||
}
|
||||
modulesConfig: {
|
||||
[key: string]:
|
||||
| string
|
||||
| boolean
|
||||
| Partial<InternalModuleDeclaration | ExternalModuleDeclaration>
|
||||
}
|
||||
joinerConfig?: ModuleJoinerConfig[]
|
||||
preventConnectionDestroyWarning?: boolean
|
||||
}
|
||||
|
||||
export async function initModules({
|
||||
injectedDependencies,
|
||||
databaseConfig,
|
||||
modulesConfig,
|
||||
joinerConfig,
|
||||
preventConnectionDestroyWarning = false,
|
||||
}: InitModulesOptions) {
|
||||
const moduleSdkImports = require("@medusajs/modules-sdk")
|
||||
|
||||
injectedDependencies ??= {}
|
||||
|
||||
let sharedPgConnection =
|
||||
injectedDependencies?.[ContainerRegistrationKeys.PG_CONNECTION]
|
||||
|
||||
let shouldDestroyConnectionAutomatically = !sharedPgConnection
|
||||
if (!sharedPgConnection) {
|
||||
sharedPgConnection = ModulesSdkUtils.createPgConnection({
|
||||
clientUrl: databaseConfig.clientUrl,
|
||||
schema: databaseConfig.schema,
|
||||
})
|
||||
|
||||
injectedDependencies[ContainerRegistrationKeys.PG_CONNECTION] =
|
||||
sharedPgConnection
|
||||
}
|
||||
|
||||
const medusaApp = await moduleSdkImports.MedusaApp({
|
||||
modulesConfig,
|
||||
servicesConfig: joinerConfig,
|
||||
injectedDependencies,
|
||||
})
|
||||
|
||||
async function shutdown() {
|
||||
if (shouldDestroyConnectionAutomatically) {
|
||||
await promiseAll([
|
||||
(sharedPgConnection as any).context?.destroy(),
|
||||
(sharedPgConnection as any).destroy(),
|
||||
medusaApp.onApplicationShutdown(),
|
||||
])
|
||||
} else {
|
||||
if (!preventConnectionDestroyWarning) {
|
||||
console.info(
|
||||
`You are using a custom shared connection. The connection won't be destroyed automatically.`
|
||||
)
|
||||
}
|
||||
}
|
||||
moduleSdkImports.MedusaModule.clearInstances()
|
||||
}
|
||||
|
||||
return {
|
||||
medusaApp,
|
||||
shutdown,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { dropDatabase } from "pg-god"
|
||||
|
||||
export function afterAllHookDropDatabase() {
|
||||
const DB_HOST = process.env.DB_HOST ?? "localhost"
|
||||
const DB_USERNAME = process.env.DB_USERNAME ?? "postgres"
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD ?? ""
|
||||
const DB_NAME = process.env.DB_TEMP_NAME || ""
|
||||
|
||||
const pgGodCredentials = {
|
||||
user: DB_USERNAME,
|
||||
password: DB_PASSWORD,
|
||||
host: DB_HOST,
|
||||
}
|
||||
|
||||
afterAll(async () => {
|
||||
try {
|
||||
await dropDatabase({ databaseName: DB_NAME }, pgGodCredentials)
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`This might fail if it is run during the unit tests since there is no database to drop. Otherwise, please check what is the issue. ${e.message}`
|
||||
)
|
||||
}
|
||||
})
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
const path = require("path")
|
||||
const express = require("express")
|
||||
const getPort = require("get-port")
|
||||
const { isObject, promiseAll } = require("@medusajs/utils")
|
||||
const { GracefulShutdownServer } = require("medusa-core-utils")
|
||||
|
||||
async function bootstrapApp({ cwd, env = {} } = {}) {
|
||||
const app = express()
|
||||
|
||||
if (isObject(env)) {
|
||||
Object.entries(env).forEach(([k, v]) => (process.env[k] = v))
|
||||
}
|
||||
|
||||
const loaders = require("@medusajs/medusa/dist/loaders").default
|
||||
|
||||
const { container, shutdown } = await loaders({
|
||||
directory: path.resolve(cwd || process.cwd()),
|
||||
expressApp: app,
|
||||
isTest: false,
|
||||
})
|
||||
|
||||
const PORT = await getPort()
|
||||
|
||||
return {
|
||||
shutdown,
|
||||
container,
|
||||
app,
|
||||
port: PORT,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
startBootstrapApp: async ({
|
||||
cwd,
|
||||
env = {},
|
||||
skipExpressListen = false,
|
||||
} = {}) => {
|
||||
const {
|
||||
app,
|
||||
port,
|
||||
container,
|
||||
shutdown: medusaShutdown,
|
||||
} = await bootstrapApp({
|
||||
cwd,
|
||||
env,
|
||||
})
|
||||
|
||||
let expressServer
|
||||
|
||||
if (skipExpressListen) {
|
||||
return
|
||||
}
|
||||
|
||||
const shutdown = async () => {
|
||||
await promiseAll([expressServer.shutdown(), medusaShutdown()])
|
||||
|
||||
if (typeof global !== "undefined" && global?.gc) {
|
||||
global.gc()
|
||||
}
|
||||
}
|
||||
|
||||
return await new Promise((resolve, reject) => {
|
||||
const server = app.listen(port, async (err) => {
|
||||
if (err) {
|
||||
await shutdown()
|
||||
return reject(err)
|
||||
}
|
||||
process.send(port)
|
||||
resolve({
|
||||
shutdown,
|
||||
container,
|
||||
port,
|
||||
})
|
||||
})
|
||||
|
||||
expressServer = GracefulShutdownServer.create(server)
|
||||
})
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
const path = require("path")
|
||||
|
||||
const { getConfigFile } = require("medusa-core-utils")
|
||||
const { asValue } = require("awilix")
|
||||
const {
|
||||
isObject,
|
||||
createMedusaContainer,
|
||||
MedusaV2Flag,
|
||||
} = require("@medusajs/utils")
|
||||
const { DataSource } = require("typeorm")
|
||||
const { ContainerRegistrationKeys } = require("@medusajs/utils")
|
||||
const { logger } = require("@medusajs/medusa-cli/dist/reporter")
|
||||
|
||||
module.exports = {
|
||||
initDb: async function ({
|
||||
cwd,
|
||||
// use for v1 datasource only
|
||||
database_extra,
|
||||
env,
|
||||
force_modules_migration,
|
||||
dbUrl = "",
|
||||
dbSchema = "public",
|
||||
}) {
|
||||
if (isObject(env)) {
|
||||
Object.entries(env).forEach(([k, v]) => (process.env[k] = v))
|
||||
}
|
||||
|
||||
const configModuleLoader =
|
||||
require("@medusajs/medusa/dist/loaders/config").default
|
||||
const configModule = configModuleLoader(cwd)
|
||||
|
||||
const featureFlagsLoader =
|
||||
require("@medusajs/medusa/dist/loaders/feature-flags").default
|
||||
|
||||
const featureFlagRouter = featureFlagsLoader(configModule)
|
||||
const modelsLoader = require("@medusajs/medusa/dist/loaders/models").default
|
||||
const entities = modelsLoader({}, { register: false })
|
||||
|
||||
// get migrations with enabled featureflags
|
||||
const migrationDir = path.resolve(
|
||||
path.join(
|
||||
cwd,
|
||||
`../../`,
|
||||
`node_modules`,
|
||||
`@medusajs`,
|
||||
`medusa`,
|
||||
`dist`,
|
||||
`migrations`,
|
||||
`*.js`
|
||||
)
|
||||
)
|
||||
|
||||
const {
|
||||
getEnabledMigrations,
|
||||
getModuleSharedResources,
|
||||
} = require("@medusajs/medusa/dist/commands/utils/get-migrations")
|
||||
|
||||
const { migrations: moduleMigrations, models: moduleModels } =
|
||||
getModuleSharedResources(configModule, featureFlagRouter)
|
||||
|
||||
const enabledMigrations = getEnabledMigrations([migrationDir], (flag) =>
|
||||
featureFlagRouter.isFeatureEnabled(flag)
|
||||
)
|
||||
|
||||
const enabledEntities = entities.filter(
|
||||
(e) => typeof e.isFeatureEnabled === "undefined" || e.isFeatureEnabled()
|
||||
)
|
||||
|
||||
const dbDataSource = new DataSource({
|
||||
type: "postgres",
|
||||
url: dbUrl || configModule.projectConfig.database_url,
|
||||
entities: enabledEntities.concat(moduleModels),
|
||||
migrations: enabledMigrations.concat(moduleMigrations),
|
||||
extra: database_extra ?? {},
|
||||
//name: "integration-tests",
|
||||
schema: dbSchema,
|
||||
})
|
||||
|
||||
await dbDataSource.initialize()
|
||||
|
||||
await dbDataSource.runMigrations()
|
||||
|
||||
if (
|
||||
force_modules_migration ||
|
||||
featureFlagRouter.isFeatureEnabled(MedusaV2Flag.key)
|
||||
) {
|
||||
const pgConnectionLoader =
|
||||
require("@medusajs/medusa/dist/loaders/pg-connection").default
|
||||
|
||||
const featureFlagLoader =
|
||||
require("@medusajs/medusa/dist/loaders/feature-flags").default
|
||||
|
||||
const container = createMedusaContainer()
|
||||
|
||||
const featureFlagRouter = await featureFlagLoader(configModule)
|
||||
|
||||
const pgConnection = await pgConnectionLoader({
|
||||
configModule: {
|
||||
...configModule,
|
||||
projectConfig: {
|
||||
...configModule.projectConfig,
|
||||
database_url: dbUrl || configModule.projectConfig.database_url,
|
||||
},
|
||||
},
|
||||
container,
|
||||
})
|
||||
|
||||
container.register({
|
||||
[ContainerRegistrationKeys.CONFIG_MODULE]: asValue(configModule),
|
||||
[ContainerRegistrationKeys.LOGGER]: asValue(logger),
|
||||
[ContainerRegistrationKeys.MANAGER]: asValue(dbDataSource.manager),
|
||||
[ContainerRegistrationKeys.PG_CONNECTION]: asValue(pgConnection),
|
||||
featureFlagRouter: asValue(featureFlagRouter),
|
||||
})
|
||||
|
||||
const {
|
||||
migrateMedusaApp,
|
||||
} = require("@medusajs/medusa/dist/loaders/medusa-app")
|
||||
await migrateMedusaApp(
|
||||
{ configModule, container },
|
||||
{ registerInContainer: false }
|
||||
)
|
||||
}
|
||||
|
||||
return { dbDataSource, pgConnection }
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
import { getDatabaseURL } from "./database"
|
||||
import { initDb } from "./medusa-test-runner-utils/use-db"
|
||||
import { startBootstrapApp } from "./medusa-test-runner-utils/bootstrap-app"
|
||||
import { createDatabase, dropDatabase } from "pg-god"
|
||||
import {ContainerLike, MedusaContainer} from "@medusajs/types"
|
||||
import { createMedusaContainer } from "@medusajs/utils"
|
||||
|
||||
const axios = require("axios").default
|
||||
|
||||
const DB_HOST = process.env.DB_HOST
|
||||
const DB_USERNAME = process.env.DB_USERNAME
|
||||
const DB_PASSWORD = process.env.DB_PASSWORD
|
||||
|
||||
const pgGodCredentials = {
|
||||
user: DB_USERNAME,
|
||||
password: DB_PASSWORD,
|
||||
host: DB_HOST,
|
||||
}
|
||||
|
||||
const dbTestUtilFactory = (): any => ({
|
||||
db_: null,
|
||||
pgConnection_: null,
|
||||
|
||||
clear: async function () {
|
||||
this.db_?.synchronize(true)
|
||||
},
|
||||
|
||||
create: async function (dbName: string) {
|
||||
await createDatabase({ databaseName: dbName }, pgGodCredentials)
|
||||
},
|
||||
|
||||
teardown: async function ({
|
||||
forceDelete,
|
||||
schema,
|
||||
}: { forceDelete?: string[]; schema?: string } = {}) {
|
||||
forceDelete ??= []
|
||||
if (!this.db_) {
|
||||
return
|
||||
}
|
||||
|
||||
const manager = this.db_.manager
|
||||
|
||||
schema ??= "public"
|
||||
|
||||
await manager.query(`SET session_replication_role = 'replica';`)
|
||||
const tableNames = await manager.query(`SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = '${schema}';`)
|
||||
|
||||
for (const { table_name } of tableNames) {
|
||||
await manager.query(`DELETE
|
||||
FROM ${schema}."${table_name}";`)
|
||||
}
|
||||
|
||||
await manager.query(`SET session_replication_role = 'origin';`)
|
||||
},
|
||||
|
||||
shutdown: async function (dbName: string) {
|
||||
await this.db_?.destroy()
|
||||
await this.pgConnection_?.context?.destroy()
|
||||
await this.pgConnection_?.destroy()
|
||||
|
||||
return await dropDatabase(
|
||||
{ databaseName: dbName, errorIfNonExist: false },
|
||||
pgGodCredentials
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
export interface MedusaSuiteOptions<TService = unknown> {
|
||||
dbUtils: any
|
||||
dbConnection: any // Legacy typeorm connection
|
||||
getContainer: () => MedusaContainer
|
||||
api: any
|
||||
dbConfig: {
|
||||
dbName: string
|
||||
schema: string
|
||||
clientUrl: string
|
||||
}
|
||||
}
|
||||
|
||||
export function medusaIntegrationTestRunner({
|
||||
moduleName,
|
||||
dbName,
|
||||
schema = "public",
|
||||
env = {},
|
||||
force_modules_migration = false,
|
||||
debug = false,
|
||||
testSuite,
|
||||
}: {
|
||||
moduleName?: string
|
||||
env?: Record<string, any>
|
||||
dbName?: string
|
||||
schema?: string
|
||||
debug?: boolean
|
||||
force_modules_migration?: boolean
|
||||
testSuite: <TService = unknown>(options: MedusaSuiteOptions<TService>) => void
|
||||
}) {
|
||||
const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
|
||||
moduleName = moduleName ?? Math.random().toString(36).substring(7)
|
||||
dbName ??= `medusa-${moduleName.toLowerCase()}-integration-${tempName}`
|
||||
|
||||
let dbConfig = {
|
||||
dbName,
|
||||
clientUrl: getDatabaseURL(dbName),
|
||||
schema,
|
||||
debug,
|
||||
}
|
||||
|
||||
const originalConfigLoader =
|
||||
require("@medusajs/medusa/dist/loaders/config").default
|
||||
require("@medusajs/medusa/dist/loaders/config").default = (
|
||||
rootDirectory: string
|
||||
) => {
|
||||
const config = originalConfigLoader(rootDirectory)
|
||||
config.projectConfig.database_url = dbConfig.clientUrl
|
||||
config.projectConfig.database_driver_options = dbConfig.clientUrl.includes("localhost") ? {} : {
|
||||
connection: {
|
||||
ssl: { rejectUnauthorized: false },
|
||||
},
|
||||
idle_in_transaction_session_timeout: 20000,
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
const cwd = process.cwd()
|
||||
|
||||
let shutdown = async () => void 0
|
||||
let dbUtils = dbTestUtilFactory()
|
||||
let container: ContainerLike
|
||||
let apiUtils: any
|
||||
|
||||
let options = {
|
||||
dbUtils,
|
||||
api: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, prop) => {
|
||||
return apiUtils[prop]
|
||||
},
|
||||
}
|
||||
),
|
||||
dbConnection: new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (target, prop) => {
|
||||
return dbUtils.db_[prop]
|
||||
},
|
||||
}
|
||||
),
|
||||
getContainer: () => container,
|
||||
} as MedusaSuiteOptions
|
||||
|
||||
let isFirstTime = true
|
||||
|
||||
const beforeAll_ = async () => {
|
||||
await dbUtils.create(dbName)
|
||||
|
||||
let dataSourceRes
|
||||
let pgConnectionRes
|
||||
|
||||
try {
|
||||
const { dbDataSource, pgConnection } = await initDb({
|
||||
cwd,
|
||||
env,
|
||||
force_modules_migration,
|
||||
database_extra: {},
|
||||
dbUrl: dbConfig.clientUrl,
|
||||
dbSchema: dbConfig.schema,
|
||||
})
|
||||
|
||||
dataSourceRes = dbDataSource
|
||||
pgConnectionRes = pgConnection
|
||||
} catch (error) {
|
||||
console.error("Error initializing database", error?.message)
|
||||
throw error
|
||||
}
|
||||
|
||||
dbUtils.db_ = dataSourceRes
|
||||
dbUtils.pgConnection_ = pgConnectionRes
|
||||
|
||||
let containerRes
|
||||
let serverShutdownRes
|
||||
let portRes
|
||||
try {
|
||||
const {
|
||||
shutdown = () => void 0,
|
||||
container,
|
||||
port,
|
||||
} = await startBootstrapApp({
|
||||
cwd,
|
||||
env,
|
||||
})
|
||||
|
||||
containerRes = container
|
||||
serverShutdownRes = shutdown
|
||||
portRes = port
|
||||
} catch (error) {
|
||||
console.error("Error starting the app", error?.message)
|
||||
throw error
|
||||
}
|
||||
|
||||
const cancelTokenSource = axios.CancelToken.source()
|
||||
|
||||
container = containerRes
|
||||
shutdown = async () => {
|
||||
await serverShutdownRes()
|
||||
cancelTokenSource.cancel("Request canceled by shutdown")
|
||||
}
|
||||
|
||||
apiUtils = axios.create({
|
||||
baseURL: `http://localhost:${portRes}`,
|
||||
cancelToken: cancelTokenSource.token,
|
||||
})
|
||||
}
|
||||
|
||||
const beforeEach_ = async () => {
|
||||
// The beforeAll already run everything, so lets not re run the loaders for the first iteration
|
||||
if (isFirstTime) {
|
||||
isFirstTime = false
|
||||
return
|
||||
}
|
||||
|
||||
const container = options.getContainer()
|
||||
const copiedContainer = createMedusaContainer({}, container)
|
||||
|
||||
if (process.env.MEDUSA_FF_MEDUSA_V2 != "true") {
|
||||
try {
|
||||
const defaultLoader =
|
||||
require("@medusajs/medusa/dist/loaders/defaults").default
|
||||
await defaultLoader({
|
||||
container: copiedContainer,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error runner medusa loaders", error?.message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const medusaAppLoaderRunner =
|
||||
require("@medusajs/medusa/dist/loaders/medusa-app").runModulesLoader
|
||||
await medusaAppLoaderRunner({
|
||||
container: copiedContainer,
|
||||
configModule: container.resolve("configModule"),
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("Error runner modules loaders", error?.message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const afterEach_ = async () => {
|
||||
try {
|
||||
await dbUtils.teardown({ schema })
|
||||
} catch (error) {
|
||||
console.error("Error tearing down database:", error?.message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
return describe("", () => {
|
||||
beforeAll(beforeAll_)
|
||||
beforeEach(beforeEach_)
|
||||
afterEach(afterEach_)
|
||||
afterAll(async () => {
|
||||
await dbUtils.shutdown(dbName)
|
||||
await shutdown()
|
||||
})
|
||||
|
||||
testSuite(options!)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
import {
|
||||
EmitData,
|
||||
EventBusTypes,
|
||||
IEventBusModuleService,
|
||||
Message,
|
||||
Subscriber,
|
||||
} from "@medusajs/types"
|
||||
|
||||
export default class EventBusService implements IEventBusModuleService {
|
||||
emit<T>(
|
||||
eventName: string,
|
||||
data: T,
|
||||
options?: Record<string, unknown>
|
||||
): Promise<void>
|
||||
emit<T>(data: EmitData<T>[]): Promise<void>
|
||||
emit<T>(data: Message<T>[]): Promise<void>
|
||||
|
||||
async emit<
|
||||
T,
|
||||
TInput extends
|
||||
| string
|
||||
| EventBusTypes.EmitData<T>[]
|
||||
| EventBusTypes.Message<T>[] = string
|
||||
>(
|
||||
eventOrData: TInput,
|
||||
data?: T,
|
||||
options: Record<string, unknown> = {}
|
||||
): Promise<void> {}
|
||||
|
||||
subscribe(event: string | symbol, subscriber: Subscriber): this {
|
||||
return this
|
||||
}
|
||||
|
||||
unsubscribe(
|
||||
event: string | symbol,
|
||||
subscriber: Subscriber,
|
||||
context?: EventBusTypes.SubscriberContext
|
||||
): this {
|
||||
return this
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
export default {
|
||||
connection: {
|
||||
getMetadata: (target) => {
|
||||
return (
|
||||
target["metadata"] ?? {
|
||||
columns: [],
|
||||
}
|
||||
)
|
||||
},
|
||||
},
|
||||
|
||||
getRepository: function (repo) {
|
||||
return repo
|
||||
},
|
||||
|
||||
withRepository: function (repo) {
|
||||
if (repo) {
|
||||
return Object.assign(repo, { manager: this })
|
||||
}
|
||||
|
||||
return repo
|
||||
},
|
||||
|
||||
transaction: function (isolationOrCb, cb) {
|
||||
if (typeof isolationOrCb === "string") {
|
||||
return cb(this)
|
||||
} else {
|
||||
return isolationOrCb(this)
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
class MockRepo {
|
||||
constructor({
|
||||
create,
|
||||
update,
|
||||
remove,
|
||||
softRemove,
|
||||
find,
|
||||
findDescendantsTree,
|
||||
findOne,
|
||||
findOneWithRelations,
|
||||
findOneOrFail,
|
||||
save,
|
||||
findAndCount,
|
||||
del,
|
||||
count,
|
||||
insertBulk,
|
||||
metadata,
|
||||
}) {
|
||||
this.create_ = create
|
||||
this.update_ = update
|
||||
this.remove_ = remove
|
||||
this.delete_ = del
|
||||
this.softRemove_ = softRemove
|
||||
this.find_ = find
|
||||
this.findDescendantsTree_ = findDescendantsTree
|
||||
this.findOne_ = findOne
|
||||
this.findOneOrFail_ = findOneOrFail
|
||||
this.save_ = save
|
||||
this.findAndCount_ = findAndCount
|
||||
this.findOneWithRelations_ = findOneWithRelations
|
||||
this.insertBulk_ = insertBulk
|
||||
this.count_ = count
|
||||
|
||||
this.metadata = metadata ?? {
|
||||
columns: [],
|
||||
}
|
||||
}
|
||||
|
||||
setFindOne(fn) {
|
||||
this.findOne_ = fn
|
||||
}
|
||||
|
||||
insertBulk = jest.fn().mockImplementation((...args) => {
|
||||
if (this.insertBulk_) {
|
||||
return this.insertBulk_(...args)
|
||||
}
|
||||
return {}
|
||||
})
|
||||
create = jest.fn().mockImplementation((...args) => {
|
||||
if (this.create_) {
|
||||
return this.create_(...args)
|
||||
}
|
||||
return {}
|
||||
})
|
||||
softRemove = jest.fn().mockImplementation((...args) => {
|
||||
if (this.softRemove_) {
|
||||
return this.softRemove_(...args)
|
||||
}
|
||||
return {}
|
||||
})
|
||||
remove = jest.fn().mockImplementation((...args) => {
|
||||
if (this.remove_) {
|
||||
return this.remove_(...args)
|
||||
}
|
||||
return {}
|
||||
})
|
||||
update = jest.fn().mockImplementation((...args) => {
|
||||
if (this.update_) {
|
||||
return this.update_(...args)
|
||||
}
|
||||
})
|
||||
findOneOrFail = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOneOrFail_) {
|
||||
return this.findOneOrFail_(...args)
|
||||
}
|
||||
})
|
||||
findOneWithRelations = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOneWithRelations_) {
|
||||
return this.findOneWithRelations_(...args)
|
||||
}
|
||||
})
|
||||
findOne = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findOne_) {
|
||||
return this.findOne_(...args)
|
||||
}
|
||||
})
|
||||
findDescendantsTree = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findDescendantsTree_) {
|
||||
return this.findDescendantsTree_(...args)
|
||||
}
|
||||
})
|
||||
find = jest.fn().mockImplementation((...args) => {
|
||||
if (this.find_) {
|
||||
return this.find_(...args)
|
||||
}
|
||||
})
|
||||
save = jest.fn().mockImplementation((...args) => {
|
||||
if (this.save_) {
|
||||
return this.save_(...args)
|
||||
}
|
||||
return Promise.resolve(...args)
|
||||
})
|
||||
|
||||
findAndCount = jest.fn().mockImplementation((...args) => {
|
||||
if (this.findAndCount_) {
|
||||
return this.findAndCount_(...args)
|
||||
}
|
||||
return {}
|
||||
})
|
||||
count = jest.fn().mockImplementation((...args) => {
|
||||
if (this.count_) {
|
||||
return this.count_(...args)
|
||||
}
|
||||
return {}
|
||||
})
|
||||
|
||||
delete = jest.fn().mockImplementation((...args) => {
|
||||
if (this.delete_) {
|
||||
return this.delete_(...args)
|
||||
}
|
||||
return {}
|
||||
})
|
||||
}
|
||||
|
||||
export default (methods = {}) => {
|
||||
return new MockRepo(methods)
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import { initModules, InitModulesOptions } from "./init-modules"
|
||||
import { getDatabaseURL, getMikroOrmWrapper, TestDatabase } from "./database"
|
||||
|
||||
import { MockEventBusService } from "."
|
||||
import { ContainerRegistrationKeys, ModulesSdkUtils } from "@medusajs/utils"
|
||||
|
||||
export interface SuiteOptions<TService = unknown> {
|
||||
MikroOrmWrapper: TestDatabase
|
||||
medusaApp: any
|
||||
service: TService
|
||||
dbConfig: {
|
||||
schema: string
|
||||
clientUrl: string
|
||||
}
|
||||
}
|
||||
|
||||
export function moduleIntegrationTestRunner({
|
||||
moduleName,
|
||||
moduleModels,
|
||||
moduleOptions = {},
|
||||
joinerConfig = [],
|
||||
schema = "public",
|
||||
debug = false,
|
||||
testSuite,
|
||||
resolve,
|
||||
injectedDependencies = {},
|
||||
}: {
|
||||
moduleName: string
|
||||
moduleModels?: any[]
|
||||
moduleOptions?: Record<string, any>
|
||||
joinerConfig?: any[]
|
||||
schema?: string
|
||||
dbName?: string
|
||||
injectedDependencies?: Record<string, any>
|
||||
resolve?: string
|
||||
debug?: boolean
|
||||
testSuite: <TService = unknown>(options: SuiteOptions<TService>) => void
|
||||
}) {
|
||||
const moduleSdkImports = require("@medusajs/modules-sdk")
|
||||
|
||||
process.env.LOG_LEVEL = "error"
|
||||
|
||||
moduleModels ??= Object.values(require(`${process.cwd()}/src/models`))
|
||||
// migrationPath ??= process.cwd() + "/src/migrations/!(*.d).{js,ts,cjs}"
|
||||
|
||||
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 = getMikroOrmWrapper({
|
||||
mikroOrmEntities: moduleModels,
|
||||
clientUrl: dbConfig.clientUrl,
|
||||
schema: dbConfig.schema,
|
||||
})
|
||||
|
||||
const modulesConfig_ = {
|
||||
[moduleName]: {
|
||||
definition: moduleSdkImports.ModulesDefinition[moduleName],
|
||||
resolve,
|
||||
options: {
|
||||
defaultAdapterOptions: {
|
||||
database: dbConfig,
|
||||
},
|
||||
database: dbConfig,
|
||||
...moduleOptions,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
const moduleOptions_: InitModulesOptions = {
|
||||
injectedDependencies: {
|
||||
[ContainerRegistrationKeys.PG_CONNECTION]: connection,
|
||||
eventBusService: new MockEventBusService(),
|
||||
[ContainerRegistrationKeys.LOGGER]: console,
|
||||
...injectedDependencies,
|
||||
},
|
||||
modulesConfig: modulesConfig_,
|
||||
databaseConfig: dbConfig,
|
||||
joinerConfig,
|
||||
preventConnectionDestroyWarning: true,
|
||||
}
|
||||
|
||||
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]
|
||||
},
|
||||
}
|
||||
),
|
||||
} as SuiteOptions
|
||||
|
||||
const beforeEach_ = async () => {
|
||||
await MikroOrmWrapper.setupDatabase()
|
||||
const output = await initModules(moduleOptions_)
|
||||
shutdown = output.shutdown
|
||||
medusaApp = output.medusaApp
|
||||
moduleService = output.medusaApp.modules[moduleName]
|
||||
}
|
||||
|
||||
const afterEach_ = async () => {
|
||||
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)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user