chore: Cleanup medusa test utils (#7464)

* chore: Cleanup medusa test utils

* update yarn

* cleanup

* fix key

* cleanup

* fixes

* fixes

* fixes

* fixes

* fix runners

* fix bootstrap

* naming

* Improve runners options

* Improve runners options

* Improve runners options

* cleanup

* fix tests
This commit is contained in:
Adrien de Peretti authored and GitHub committed 2024-05-27 14:14:32 +02:00
1 parent ab2e8fcd45
commit e275e01d85
19 files changed
+172 -412

No files matched your search

@@ -12,7 +12,6 @@ const adminHeaders = {
jest.setTimeout(30000) jest.setTimeout(30000)
medusaIntegrationTestRunner({ medusaIntegrationTestRunner({
force_modules_migration: true,
env: { env: {
MEDUSA_FF_MEDUSA_V2: true, MEDUSA_FF_MEDUSA_V2: true,
}, },
@@ -5,14 +5,13 @@ import { medusaIntegrationTestRunner } from "medusa-test-utils/dist"
jest.setTimeout(5000000) jest.setTimeout(5000000)
medusaIntegrationTestRunner({ medusaIntegrationTestRunner({
force_modules_migration: true, testSuite: ({ getContainer, dbConfig: { clientUrl } }) => {
testSuite: ({ dbConnection, getContainer }) => {
let DB_URL let DB_URL
let container let container
let links let links
beforeAll(async () => { beforeAll(async () => {
DB_URL = dbConnection.manager.connection.options.url DB_URL = clientUrl
container = getContainer() container = getContainer()
const linkDefinition: ModuleJoinerConfig[] = [ const linkDefinition: ModuleJoinerConfig[] = [
@@ -5,11 +5,10 @@ import { IProductModuleService } from "@medusajs/types"
jest.setTimeout(30000) jest.setTimeout(30000)
medusaIntegrationTestRunner({ medusaIntegrationTestRunner({
force_modules_migration: true, testSuite: ({ dbConfig: { clientUrl } }) => {
testSuite: ({ dbConnection }) => {
describe("Standalone Modules", () => { describe("Standalone Modules", () => {
beforeAll(async () => { beforeAll(async () => {
process.env.POSTGRES_URL = dbConnection.manager.connection.options.url process.env.POSTGRES_URL = clientUrl
}) })
afterAll(async () => { afterAll(async () => {
+1 -5
View File
@@ -36,8 +36,7 @@
"axios": "^0.28.0", "axios": "^0.28.0",
"express": "^4.18.3", "express": "^4.18.3",
"get-port": "^5.1.0", "get-port": "^5.1.0",
"pg-god": "^1.0.12", "pg-god": "^1.0.12"
"typeorm": "^0.2.43"
}, },
"peerDependenciesMeta": { "peerDependenciesMeta": {
"@medusajs/medusa": { "@medusajs/medusa": {
@@ -54,9 +53,6 @@
}, },
"pg-god": { "pg-god": {
"optional": true "optional": true
},
"typeorm": {
"optional": true
} }
}, },
"dependencies": { "dependencies": {
@@ -2,8 +2,6 @@ export * as TestDatabaseUtils from "./database"
export * as TestEventUtils from "./events" export * as TestEventUtils from "./events"
export * as JestUtils from "./jest" export * as JestUtils from "./jest"
export { default as IdMap } from "./id-map" export { default as IdMap } from "./id-map"
export { default as MockManager } from "./mock-manager"
export { default as MockRepository } from "./mock-repository"
export * from "./init-modules" export * from "./init-modules"
export { default as MockEventBusService } from "./mock-event-bus-service" export { default as MockEventBusService } from "./mock-event-bus-service"
export * from "./module-test-runner" export * from "./module-test-runner"
@@ -58,6 +58,8 @@ export async function initModules({
async function shutdown() { async function shutdown() {
if (shouldDestroyConnectionAutomatically) { if (shouldDestroyConnectionAutomatically) {
await medusaApp.onApplicationPrepareShutdown()
await promiseAll([ await promiseAll([
(sharedPgConnection as any).context?.destroy(), (sharedPgConnection as any).context?.destroy(),
(sharedPgConnection as any).destroy(), (sharedPgConnection as any).destroy(),
@@ -1,81 +0,0 @@
const path = require("path")
const express = require("express")
const getPort = require("get-port")
const { isObject, promiseAll } = require("@medusajs/utils")
// TODO: fix that once we find the appropriate place to put this util
const {
GracefulShutdownServer,
} = require("@medusajs/medusa/dist/utils/graceful-shutdown-server")
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,
})
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,83 @@
import express from "express"
import getPort from "get-port"
import { resolve } from "path"
import { isObject, promiseAll } from "@medusajs/utils"
import { MedusaContainer } from "@medusajs/types"
async function bootstrapApp({
cwd,
env = {},
}: { cwd?: string; env?: Record<any, any> } = {}) {
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: resolve(cwd || process.cwd()),
expressApp: app,
})
const PORT = await getPort()
return {
shutdown,
container,
app,
port: PORT,
}
}
export async function startApp({
cwd,
env = {},
}: { cwd?: string; env?: Record<any, any> } = {}): Promise<{
shutdown: () => Promise<void>
container: MedusaContainer
port: number
}> {
const {
app,
port,
container,
shutdown: medusaShutdown,
} = await bootstrapApp({
cwd,
env,
})
let expressServer
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).on("error", async (err) => {
await shutdown()
return reject(err)
})
process.send?.(port)
resolve({
shutdown,
container,
port,
})
// TODO: fix that once we find the appropriate place to put this util
const {
GracefulShutdownServer,
} = require("@medusajs/medusa/dist/utils/graceful-shutdown-server")
expressServer = GracefulShutdownServer.create(server)
})
}
@@ -1,101 +0,0 @@
const path = require("path")
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 {
getModuleSharedResources,
} = require("@medusajs/medusa/dist/commands/utils/get-migrations")
const { migrations: moduleMigrations, models: moduleModels } =
getModuleSharedResources(configModule, featureFlagRouter)
const dbDataSource = new DataSource({
type: "postgres",
url: dbUrl || configModule.projectConfig.database_url,
entities: moduleModels,
migrations: 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,51 @@
import {
ContainerRegistrationKeys,
createMedusaContainer,
isObject,
} from "@medusajs/utils"
import { asValue } from "awilix"
export async function initDb({
cwd,
env = {},
}: {
cwd: string
env?: Record<any, any>
}) {
if (isObject(env)) {
Object.entries(env).forEach(([k, v]) => (process.env[k] = v))
}
const container = createMedusaContainer()
const configModule =
await require("@medusajs/medusa/dist/loaders/config").default(cwd)
const pgConnection =
await require("@medusajs/medusa/dist/loaders/pg-connection").default({
configModule,
container,
})
const featureFlagRouter =
require("@medusajs/medusa/dist/loaders/feature-flags").default(configModule)
container.register({
[ContainerRegistrationKeys.CONFIG_MODULE]: asValue(configModule),
[ContainerRegistrationKeys.LOGGER]: asValue(console),
[ContainerRegistrationKeys.PG_CONNECTION]: asValue(pgConnection),
[ContainerRegistrationKeys.FEATURE_FLAG_ROUTER]: asValue(featureFlagRouter),
})
try {
const {
migrateMedusaApp,
} = require("@medusajs/medusa/dist/loaders/medusa-app")
await migrateMedusaApp({ configModule, container })
} catch (err) {
console.error("Something went wrong while running the migrations")
throw err
}
return pgConnection
}
@@ -1,8 +1,11 @@
import { ContainerLike, MedusaContainer } from "@medusajs/types" import { ContainerLike, MedusaContainer } from "@medusajs/types"
import { createMedusaContainer } from "@medusajs/utils" import {
ContainerRegistrationKeys,
createMedusaContainer,
} from "@medusajs/utils"
import { createDatabase, dropDatabase } from "pg-god" import { createDatabase, dropDatabase } from "pg-god"
import { getDatabaseURL } from "./database" import { getDatabaseURL } from "./database"
import { startBootstrapApp } from "./medusa-test-runner-utils/bootstrap-app" import { startApp } from "./medusa-test-runner-utils/bootstrap-app"
import { initDb } from "./medusa-test-runner-utils/use-db" import { initDb } from "./medusa-test-runner-utils/use-db"
const axios = require("axios").default const axios = require("axios").default
@@ -18,45 +21,35 @@ const pgGodCredentials = {
} }
const dbTestUtilFactory = (): any => ({ const dbTestUtilFactory = (): any => ({
db_: null,
pgConnection_: null, pgConnection_: null,
clear: async function () {
this.db_?.synchronize(true)
},
create: async function (dbName: string) { create: async function (dbName: string) {
await createDatabase({ databaseName: dbName }, pgGodCredentials) await createDatabase({ databaseName: dbName }, pgGodCredentials)
}, },
teardown: async function ({ teardown: async function ({ schema }: { schema?: string } = {}) {
forceDelete, if (!this.pgConnection_) {
schema,
}: { forceDelete?: string[]; schema?: string } = {}) {
forceDelete ??= []
if (!this.db_) {
return return
} }
const manager = this.db_.manager const runRawQuery = this.pgConnection_.raw.bind(this.pgConnection_)
schema ??= "public" schema ??= "public"
await manager.query(`SET session_replication_role = 'replica';`) await runRawQuery(`SET session_replication_role = 'replica';`)
const tableNames = await manager.query(`SELECT table_name const { rows: tableNames } = await runRawQuery(`SELECT table_name
FROM information_schema.tables FROM information_schema.tables
WHERE table_schema = '${schema}';`) WHERE table_schema = '${schema}';`)
for (const { table_name } of tableNames) { for (const { table_name } of tableNames) {
await manager.query(`DELETE await runRawQuery(`DELETE
FROM ${schema}."${table_name}";`) FROM ${schema}."${table_name}";`)
} }
await manager.query(`SET session_replication_role = 'origin';`) await runRawQuery(`SET session_replication_role = 'origin';`)
}, },
shutdown: async function (dbName: string) { shutdown: async function (dbName: string) {
await this.db_?.destroy()
await this.pgConnection_?.context?.destroy() await this.pgConnection_?.context?.destroy()
await this.pgConnection_?.destroy() await this.pgConnection_?.destroy()
@@ -68,8 +61,7 @@ const dbTestUtilFactory = (): any => ({
}) })
export interface MedusaSuiteOptions<TService = unknown> { export interface MedusaSuiteOptions<TService = unknown> {
dbUtils: any dbConnection: any // knex instance
dbConnection: any // Legacy typeorm connection
getContainer: () => MedusaContainer getContainer: () => MedusaContainer
api: any api: any
dbConfig: { dbConfig: {
@@ -84,7 +76,6 @@ export function medusaIntegrationTestRunner({
dbName, dbName,
schema = "public", schema = "public",
env = {}, env = {},
force_modules_migration = false,
debug = false, debug = false,
testSuite, testSuite,
}: { }: {
@@ -93,7 +84,6 @@ export function medusaIntegrationTestRunner({
dbName?: string dbName?: string
schema?: string schema?: string
debug?: boolean debug?: boolean
force_modules_migration?: boolean
testSuite: <TService = unknown>(options: MedusaSuiteOptions<TService>) => void testSuite: <TService = unknown>(options: MedusaSuiteOptions<TService>) => void
}) { }) {
const tempName = parseInt(process.env.JEST_WORKER_ID || "1") const tempName = parseInt(process.env.JEST_WORKER_ID || "1")
@@ -130,12 +120,11 @@ export function medusaIntegrationTestRunner({
const cwd = process.cwd() const cwd = process.cwd()
let shutdown = async () => void 0 let shutdown = async () => void 0
let dbUtils = dbTestUtilFactory() const dbUtils = dbTestUtilFactory()
let container: ContainerLike let container: ContainerLike
let apiUtils: any let apiUtils: any
let options = { let options = {
dbUtils,
api: new Proxy( api: new Proxy(
{}, {},
{ {
@@ -148,11 +137,16 @@ export function medusaIntegrationTestRunner({
{}, {},
{ {
get: (target, prop) => { get: (target, prop) => {
return dbUtils.db_[prop] return dbUtils.pgConnection_[prop]
}, },
} }
), ),
getContainer: () => container, getContainer: () => container,
dbConfig: {
dbName,
schema,
clientUrl: dbConfig.clientUrl,
},
} as MedusaSuiteOptions } as MedusaSuiteOptions
let isFirstTime = true let isFirstTime = true
@@ -160,38 +154,26 @@ export function medusaIntegrationTestRunner({
const beforeAll_ = async () => { const beforeAll_ = async () => {
await dbUtils.create(dbName) await dbUtils.create(dbName)
let dataSourceRes
let pgConnectionRes
try { try {
const { dbDataSource, pgConnection } = await initDb({ dbUtils.pgConnection_ = await initDb({
cwd, cwd,
env, env,
force_modules_migration,
database_extra: {},
dbUrl: dbConfig.clientUrl,
dbSchema: dbConfig.schema,
}) })
dataSourceRes = dbDataSource
pgConnectionRes = pgConnection
} catch (error) { } catch (error) {
console.error("Error initializing database", error?.message) console.error("Error initializing database", error?.message)
throw error throw error
} }
dbUtils.db_ = dataSourceRes
dbUtils.pgConnection_ = pgConnectionRes
let containerRes let containerRes
let serverShutdownRes let serverShutdownRes
let portRes let portRes
try { try {
const { const {
shutdown = () => void 0, shutdown = () => void 0,
container, container,
port, port,
} = await startBootstrapApp({ } = await startApp({
cwd, cwd,
env, env,
}) })
@@ -233,7 +215,9 @@ export function medusaIntegrationTestRunner({
require("@medusajs/medusa/dist/loaders/medusa-app").runModulesLoader require("@medusajs/medusa/dist/loaders/medusa-app").runModulesLoader
await medusaAppLoaderRunner({ await medusaAppLoaderRunner({
container: copiedContainer, container: copiedContainer,
configModule: container.resolve("configModule"), configModule: container.resolve(
ContainerRegistrationKeys.CONFIG_MODULE
),
}) })
} catch (error) { } catch (error) {
console.error("Error runner modules loaders", error?.message) console.error("Error runner modules loaders", error?.message)
@@ -1,31 +0,0 @@
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)
}
},
}
@@ -1,127 +0,0 @@
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)
}
@@ -66,9 +66,6 @@ export function moduleIntegrationTestRunner({
definition: moduleSdkImports.ModulesDefinition[moduleName], definition: moduleSdkImports.ModulesDefinition[moduleName],
resolve, resolve,
options: { options: {
defaultAdapterOptions: {
database: dbConfig,
},
database: dbConfig, database: dbConfig,
...moduleOptions, ...moduleOptions,
}, },
@@ -110,6 +107,10 @@ export function moduleIntegrationTestRunner({
}, },
} }
), ),
dbConfig: {
schema,
clientUrl: dbConfig.clientUrl,
},
} as SuiteOptions } as SuiteOptions
const beforeEach_ = async () => { const beforeEach_ = async () => {
-2
View File
@@ -1,4 +1,3 @@
import { revertIsolatedModulesMigration } from "./utils/get-migrations"
import Logger from "../loaders/logger" import Logger from "../loaders/logger"
import { migrateMedusaApp, revertMedusaApp } from "../loaders/medusa-app" import { migrateMedusaApp, revertMedusaApp } from "../loaders/medusa-app"
import { initializeContainer } from "../loaders" import { initializeContainer } from "../loaders"
@@ -22,7 +21,6 @@ const main = async function ({ directory }) {
process.exit() process.exit()
} else if (args[0] === "revert") { } else if (args[0] === "revert") {
await revertMedusaApp({ configModule, container }) await revertMedusaApp({ configModule, container })
await revertIsolatedModulesMigration(configModule)
Logger.info("Migrations reverted.") Logger.info("Migrations reverted.")
} else if (args[0] === "show") { } else if (args[0] === "show") {
@@ -1,5 +1,4 @@
import { Queue, Worker } from "bullmq" import { Queue, Worker } from "bullmq"
import { MockManager } from "medusa-test-utils"
import RedisEventBusService from "../event-bus-redis" import RedisEventBusService from "../event-bus-redis"
jest.genMockFromModule("bullmq") jest.genMockFromModule("bullmq")
@@ -15,7 +14,7 @@ const loggerMock = {
const simpleModuleOptions = { redisUrl: "test-url" } const simpleModuleOptions = { redisUrl: "test-url" }
const moduleDeps = { const moduleDeps = {
manager: MockManager, manager: {},
logger: loggerMock, logger: loggerMock,
eventBusRedisConnection: {}, eventBusRedisConnection: {},
} }
@@ -4,12 +4,6 @@ import { DB_URL } from "./database"
export function getInitModuleConfig() { export function getInitModuleConfig() {
const moduleOptions = { const moduleOptions = {
defaultAdapterOptions: {
database: {
clientUrl: DB_URL,
schema: process.env.MEDUSA_PAYMENT_DB_SCHEMA,
},
},
providers: [ providers: [
{ {
resolve: "@medusajs/payment-stripe", resolve: "@medusajs/payment-stripe",
-3
View File
@@ -23712,7 +23712,6 @@ __metadata:
express: ^4.18.3 express: ^4.18.3
get-port: ^5.1.0 get-port: ^5.1.0
pg-god: ^1.0.12 pg-god: ^1.0.12
typeorm: ^0.2.43
peerDependenciesMeta: peerDependenciesMeta:
"@medusajs/medusa": "@medusajs/medusa":
optional: true optional: true
@@ -23724,8 +23723,6 @@ __metadata:
optional: true optional: true
pg-god: pg-god:
optional: true optional: true
typeorm:
optional: true
languageName: unknown languageName: unknown
linkType: soft linkType: soft