refacto & cleanup of toplevel functions, use @p42/p42Modules, test microframework
This commit is contained in:
+179
@@ -0,0 +1,179 @@
|
||||
import yargs from 'yargs/yargs'
|
||||
import { hideBin } from 'yargs/helpers'
|
||||
import { pathToFileURL } from 'url'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { dirname, join } from 'path'
|
||||
import { RedisConnexion } from '../redisConnexion.js'
|
||||
import { configHelper } from '../configHelper.js'
|
||||
import { MySQLClient } from '@p42/p42modules'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
|
||||
const LOG_COLORS = {
|
||||
action: '\x1b[37m',
|
||||
success: '\x1b[32m',
|
||||
error: '\x1b[31m',
|
||||
reset: '\x1b[0m',
|
||||
}
|
||||
|
||||
function createTestMesh(handlers) {
|
||||
return({
|
||||
dispatchMessage(_cnx, msg, chan) {
|
||||
for(const handler of handlers) handler(msg, chan)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function buildBaseParser() {
|
||||
return(yargs(hideBin(process.argv))
|
||||
.usage('$0 <module> [options]')
|
||||
.command('$0 <module>', 'Run a GodDaemons test module', y => y
|
||||
.positional('module', {
|
||||
describe: 'Test module name (e.g. maestro1)',
|
||||
type: 'string',
|
||||
})
|
||||
)
|
||||
.options({
|
||||
config: {
|
||||
alias: 'c',
|
||||
describe: 'Path to config.json',
|
||||
default: join(__dirname, '..', 'config.json'),
|
||||
type: 'string',
|
||||
},
|
||||
guiDatabase: {
|
||||
describe: 'Override mysql.guiDatabase for this run',
|
||||
type: 'string',
|
||||
},
|
||||
simDatabase: {
|
||||
describe: 'Override mysql.simDatabase for this run',
|
||||
type: 'string',
|
||||
},
|
||||
})
|
||||
.help()
|
||||
.version(false)
|
||||
)
|
||||
}
|
||||
|
||||
function meshRedisConn(mesh, meshName, rootConfig, meshModule) {
|
||||
const { redis, ...meshConfig } = mesh
|
||||
return(redis.map(cfg =>
|
||||
new RedisConnexion({
|
||||
debug: false,
|
||||
config: { ...cfg, ...meshConfig, ...rootConfig },
|
||||
redisId: cfg.redisId,
|
||||
meshName,
|
||||
meshModule,
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
async function loadTestModule(moduleName) {
|
||||
const modulePath = join(__dirname, 'modules', `${moduleName}.js`)
|
||||
try {
|
||||
return(await import(pathToFileURL(modulePath).href))
|
||||
} catch(err) {
|
||||
if(err.code === 'ERR_MODULE_NOT_FOUND') {
|
||||
throw(new Error(`Test module not found: ${moduleName} (expected ${modulePath})`))
|
||||
}
|
||||
throw(err)
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const preArgv = buildBaseParser().parseSync()
|
||||
const moduleName = preArgv.module ?? preArgv._[0]
|
||||
if(!moduleName) {
|
||||
buildBaseParser().showHelp()
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const testModule = await loadTestModule(moduleName)
|
||||
|
||||
let parser = buildBaseParser()
|
||||
if(typeof(testModule.configureYargs) === 'function') {
|
||||
parser = testModule.configureYargs(parser)
|
||||
}
|
||||
const argv = parser.parseSync()
|
||||
const resolvedModule = argv.module ?? argv._[0]
|
||||
|
||||
const cfgh = new configHelper({ localfile: argv.config })
|
||||
await cfgh.fetchConfigFile()
|
||||
const config = cfgh.config
|
||||
|
||||
if(argv.guiDatabase) config.mysql.guiDatabase = argv.guiDatabase
|
||||
if(argv.simDatabase) config.mysql.simDatabase = argv.simDatabase
|
||||
|
||||
const arenaHandlers = new Set()
|
||||
const testMesh = createTestMesh(arenaHandlers)
|
||||
|
||||
const systemConns = meshRedisConn(config.systemMesh, 'system', config, null)
|
||||
const arenaConns = meshRedisConn(config.arenaMesh, 'arena', config, testMesh)
|
||||
const systemCnx = systemConns.find(c => c.redisConfig.role === 'primary') ?? systemConns[0]
|
||||
const arenaCnx = arenaConns.find(c => c.redisConfig.role === 'primary') ?? arenaConns[0]
|
||||
|
||||
const log = (type, message) => {
|
||||
const color = LOG_COLORS[type] ?? LOG_COLORS.action
|
||||
console.log(`${color}${message}${LOG_COLORS.reset}`)
|
||||
}
|
||||
|
||||
log('action', `Loading test module: ${resolvedModule}`)
|
||||
log('action', `Config: ${argv.config}`)
|
||||
|
||||
const databases = MySQLClient.resolveDatabases(config.mysql)
|
||||
if(argv.guiDatabase || argv.simDatabase) {
|
||||
log('action', `MySQL databases: gui=${databases.guiDatabase}, sim=${databases.simDatabase}`)
|
||||
}
|
||||
|
||||
log('action', 'Connecting to Redis (system)...')
|
||||
await systemCnx.redisLogin()
|
||||
log('success', `System Redis connected (${systemCnx.redisConfig.host}:${systemCnx.redisConfig.port})`)
|
||||
|
||||
log('action', 'Connecting to Redis (arena)...')
|
||||
await arenaCnx.redisLogin()
|
||||
await arenaCnx.redisChansStart()
|
||||
log('success', `Arena Redis connected (${arenaCnx.redisConfig.host}:${arenaCnx.redisConfig.port})`)
|
||||
|
||||
log('action', 'Connecting to MySQL...')
|
||||
const db = await MySQLClient.createPool(config.mysql)
|
||||
log('success', 'MySQL pool ready')
|
||||
|
||||
const ctx = {
|
||||
argv,
|
||||
config,
|
||||
databases,
|
||||
db,
|
||||
systemCnx,
|
||||
arenaCnx,
|
||||
log,
|
||||
onArenaMessage(handler) {
|
||||
arenaHandlers.add(handler)
|
||||
},
|
||||
offArenaMessage(handler) {
|
||||
arenaHandlers.delete(handler)
|
||||
},
|
||||
}
|
||||
|
||||
let exitCode = 0
|
||||
try {
|
||||
if(typeof(testModule.run) !== 'function') {
|
||||
throw(new Error(`Test module ${resolvedModule} must export a run(ctx) function`))
|
||||
}
|
||||
await testModule.run(ctx)
|
||||
log('success', `Test module ${resolvedModule} finished`)
|
||||
} catch(err) {
|
||||
log('error', err.message ?? String(err))
|
||||
exitCode = 1
|
||||
} finally {
|
||||
await db.end()
|
||||
await systemCnx.redisClient.quit()
|
||||
await arenaCnx.redisClient.quit()
|
||||
if(arenaCnx.redisSubscriber) await arenaCnx.redisSubscriber.quit()
|
||||
}
|
||||
|
||||
process.exit(exitCode)
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(`${LOG_COLORS.error}${err.message ?? err}${LOG_COLORS.reset}`)
|
||||
process.exit(1)
|
||||
})
|
||||
Reference in New Issue
Block a user