diff --git a/src/cmd-check.mjs b/src/cmd-check.mjs index 72964d6..2ca8fe3 100644 --- a/src/cmd-check.mjs +++ b/src/cmd-check.mjs @@ -7,20 +7,11 @@ import { getRoster } from './roster.mjs'; import { queryLocks } from './lock-ops.mjs'; import { todayDateStr } from './utils.mjs'; -export async function cmdCheck(cadence, deps = {}) { - const { - getRelaysFn = getRelays, - getNamespaceFn = getNamespace, - loadTorchConfigFn = loadTorchConfig, - getRosterFn = getRoster, - queryLocksFn = queryLocks, - todayDateStrFn = todayDateStr, - } = deps; - - const relays = getRelaysFn(); - const namespace = getNamespaceFn(); - const dateStr = todayDateStrFn(); - const config = loadTorchConfigFn(); +export async function cmdCheck(cadence) { + const relays = await getRelays(); + const namespace = await getNamespace(); + const dateStr = todayDateStr(); + const config = await loadTorchConfig(); const pausedAgents = (cadence === 'daily' ? config.scheduler.paused.daily : config.scheduler.paused.weekly) || []; console.error(`Checking locks: namespace=${namespace}, cadence=${cadence}, date=${dateStr}`); @@ -31,7 +22,7 @@ export async function cmdCheck(cadence, deps = {}) { const locks = await queryLocksFn(relays, cadence, dateStr, namespace); const lockedAgents = [...new Set(locks.map((l) => l.agent).filter(Boolean))]; - const roster = getRosterFn(cadence); + const roster = await getRoster(cadence); const excludedAgents = [...new Set([...lockedAgents, ...pausedAgents])]; const unknownLockedAgents = lockedAgents.filter((agent) => !roster.includes(agent)); diff --git a/src/cmd-list.mjs b/src/cmd-list.mjs index 727b982..65a9166 100644 --- a/src/cmd-list.mjs +++ b/src/cmd-list.mjs @@ -7,8 +7,8 @@ import { queryLocks } from './lock-ops.mjs'; import { todayDateStr, nowUnix } from './utils.mjs'; export async function cmdList(cadence) { - const relays = getRelays(); - const namespace = getNamespace(); + const relays = await getRelays(); + const namespace = await getNamespace(); const dateStr = todayDateStr(); const cadences = cadence ? [cadence] : ['daily', 'weekly']; @@ -47,7 +47,7 @@ export async function cmdList(cadence) { ); } - const roster = getRoster(c); + const roster = await getRoster(c); const lockedAgents = new Set(locks.map((l) => l.agent).filter(Boolean)); const unknownLockedAgents = [...lockedAgents].filter((agent) => !roster.includes(agent)); const available = roster.filter((a) => !lockedAgents.has(a)); diff --git a/src/cmd-lock.mjs b/src/cmd-lock.mjs index ca4318c..195baa5 100644 --- a/src/cmd-lock.mjs +++ b/src/cmd-lock.mjs @@ -14,10 +14,10 @@ import { todayDateStr, nowUnix } from './utils.mjs'; import { ExitError } from './errors.mjs'; export async function cmdLock(agent, cadence, dryRun = false) { - const relays = getRelays(); - const namespace = getNamespace(); + const relays = await getRelays(); + const namespace = await getNamespace(); const dateStr = todayDateStr(); - const ttl = getTtl(); + const ttl = await getTtl(); const now = nowUnix(); const expiresAt = now + ttl; @@ -25,7 +25,7 @@ export async function cmdLock(agent, cadence, dryRun = false) { console.error(`TTL: ${ttl}s, expires: ${new Date(expiresAt * 1000).toISOString()}`); console.error(`Relays: ${relays.join(', ')}`); - const roster = getRoster(cadence); + const roster = await getRoster(cadence); if (!roster.includes(agent)) { console.error(`ERROR: agent "${agent}" is not in the ${cadence} roster`); console.error(`Allowed ${cadence} agents: ${roster.join(', ')}`); diff --git a/src/dashboard.mjs b/src/dashboard.mjs index caeb578..bf9eea2 100644 --- a/src/dashboard.mjs +++ b/src/dashboard.mjs @@ -22,9 +22,10 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0. // Resolve package root relative to this file (src/dashboard.mjs) // this file is in /src/dashboard.mjs, so '..' goes to const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); - const auth = getDashboardAuth(); + const auth = await getDashboardAuth(); const server = http.createServer(async (req, res) => { + try { // Basic Auth check if (auth) { const authHeader = req.headers.authorization; @@ -181,20 +182,28 @@ export async function cmdDashboard(port = DEFAULT_DASHBOARD_PORT, host = '127.0. res.writeHead(200, { 'Content-Type': contentType }); fs.createReadStream(filePath).pipe(res); - }); - - server.listen(port, host, () => { - const listenUrl = `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}/dashboard/`; - console.log(`Dashboard running at ${listenUrl}`); - if (auth) { - console.log('Authentication: enabled (Basic Auth)'); - } else { - console.warn('Authentication: DISABLED (Dashboard is public)'); + } catch (err) { + console.error('Dashboard Server Error:', err); + if (!res.headersSent) { + res.writeHead(500); + res.end('Internal Server Error'); + } } - console.log(`Serving files from ${packageRoot}`); - console.log(`Using configuration from ${process.cwd()}`); }); - // Keep process alive - return server; + return new Promise((resolve, reject) => { + server.listen(port, host, () => { + const listenUrl = `http://${host === '0.0.0.0' ? 'localhost' : host}:${port}/dashboard/`; + console.log(`Dashboard running at ${listenUrl}`); + if (auth) { + console.log('Authentication: enabled (Basic Auth)'); + } else { + console.warn('Authentication: DISABLED (Dashboard is public)'); + } + console.log(`Serving files from ${packageRoot}`); + console.log(`Using configuration from ${process.cwd()}`); + resolve(server); + }); + server.on('error', reject); + }); } diff --git a/src/lib.mjs b/src/lib.mjs index e002fb5..ec97d94 100644 --- a/src/lib.mjs +++ b/src/lib.mjs @@ -75,10 +75,10 @@ export async function cmdCheck(cadence, deps = {}) { quiet = false, } = deps; - const relays = getRelays(); - const namespace = getNamespace(); + const relays = await getRelays(); + const namespace = await getNamespace(); const dateStr = getDateStr(); - const config = loadTorchConfig(); + const config = await loadTorchConfig(); const pausedAgents = config.scheduler.paused[cadence] || []; if (!quiet) { @@ -99,7 +99,7 @@ export async function cmdCheck(cadence, deps = {}) { const locks = await queryLocks(relays, cadence, dateStr, namespace); const lockedAgents = [...new Set(locks.map((l) => l.agent).filter(Boolean))]; - const roster = getRoster(cadence); + const roster = await getRoster(cadence); const rosterSet = new Set(roster); const excludedAgentsSet = new Set([...lockedAgents, ...pausedAgents, ...completedAgents]); @@ -180,11 +180,11 @@ export async function cmdLock(agent, cadence, optionsOrDryRun = false, deps = {} error = console.error } = deps; - const relays = getRelays(); - const namespace = getNamespace(); - const hashtag = getHashtag(); + const relays = await getRelays(); + const namespace = await getNamespace(); + const hashtag = await getHashtag(); const dateStr = getDateStr(); - const ttl = getTtl(); + const ttl = await getTtl(); const now = nowUnix(); const expiresAt = now + ttl; @@ -193,7 +193,7 @@ export async function cmdLock(agent, cadence, optionsOrDryRun = false, deps = {} error(`TTL: ${ttl}s, expires: ${new Date(expiresAt * 1000).toISOString()}`); error(`Relays: ${relays.join(', ')}`); - const roster = getRoster(cadence); + const roster = await getRoster(cadence); if (!roster.includes(agent)) { error(`ERROR: agent "${agent}" is not in the ${cadence} roster`); error(`Allowed ${cadence} agents: ${roster.join(', ')}`); @@ -319,8 +319,8 @@ export async function cmdList(cadence, deps = {}) { error = console.error } = deps; - const relays = getRelays(); - const namespace = getNamespace(); + const relays = await getRelays(); + const namespace = await getNamespace(); const dateStr = getDateStr(); const cadences = cadence ? [cadence] : [...VALID_CADENCES]; @@ -365,7 +365,7 @@ export async function cmdList(cadence, deps = {}) { ); } - const roster = getRoster(c); + const roster = await getRoster(c); const rosterSet = new Set(roster); const lockedAgents = new Set(locks.map((l) => l.agent).filter(Boolean)); const unknownLockedAgents = [...lockedAgents].filter((agent) => !rosterSet.has(agent)); @@ -413,9 +413,9 @@ export async function cmdComplete(agent, cadence, optionsOrDryRun = false, deps error = console.error } = deps; - const relays = getRelays(); - const namespace = getNamespace(); - const hashtag = getHashtag(); + const relays = await getRelays(); + const namespace = await getNamespace(); + const hashtag = await getHashtag(); const dateStr = getDateStr(); const now = nowUnix(); diff --git a/src/lock-ops.mjs b/src/lock-ops.mjs index df31b7e..3b7f38b 100644 --- a/src/lock-ops.mjs +++ b/src/lock-ops.mjs @@ -306,11 +306,11 @@ export async function queryLocks(relays, cadence, dateStr, namespace, deps = {}) const pool = poolFactory(); const tagFilter = `${namespace}-lock-${cadence}-${dateStr}`; - const queryTimeoutMs = getQueryTimeoutMsFn(); - const fallbackRelays = getRelayFallbacksFn().filter((relay) => !relays.includes(relay)); + const queryTimeoutMs = await getQueryTimeoutMsFn(); + const fallbackRelays = (await getRelayFallbacksFn()).filter((relay) => !relays.includes(relay)); const healthConfig = buildRelayHealthConfig({ ...deps, - minActiveRelayPool: getMinActiveRelayPoolFn(), + minActiveRelayPool: await getMinActiveRelayPoolFn(), }); const runQuery = async (relaySet, phase) => { @@ -419,13 +419,13 @@ export async function publishLock(relays, event, deps = {}) { const attemptId = diagnostics.attemptId || process.env.SCHEDULER_LOCK_ATTEMPT_ID || '1'; const pool = poolFactory(); - const publishTimeoutMs = getPublishTimeoutMsFn(); - const minSuccesses = getMinSuccessfulRelayPublishesFn(); - const fallbackRelays = getRelayFallbacksFn().filter((relay) => !relays.includes(relay)); + const publishTimeoutMs = await getPublishTimeoutMsFn(); + const minSuccesses = await getMinSuccessfulRelayPublishesFn(); + const fallbackRelays = (await getRelayFallbacksFn()).filter((relay) => !relays.includes(relay)); const maxAttempts = Math.max(1, Math.floor(retryAttempts)); const healthConfig = buildRelayHealthConfig({ ...deps, - minActiveRelayPool: getMinActiveRelayPoolFn(), + minActiveRelayPool: await getMinActiveRelayPoolFn(), }); const publishOnce = async () => { diff --git a/src/perf/benchmark-config-load.mjs b/src/perf/benchmark-config-load.mjs new file mode 100644 index 0000000..4e00f1d --- /dev/null +++ b/src/perf/benchmark-config-load.mjs @@ -0,0 +1,45 @@ +import { loadTorchConfig, _resetTorchConfigCache } from '../torch-config.mjs'; +import fs from 'node:fs'; +import path from 'node:path'; + +const configPath = path.resolve(process.cwd(), 'torch-config.json'); +const originalConfig = fs.existsSync(configPath) ? fs.readFileSync(configPath) : null; + +// Ensure a config file exists for benchmark +if (!fs.existsSync(configPath)) { + fs.writeFileSync(configPath, JSON.stringify({ + nostrLock: { + relays: ["wss://relay.damus.io"], + namespace: "test-ns" + } + }, null, 2)); +} + +const ITERATIONS = 1000; +const start = process.hrtime.bigint(); + +async function run() { + for (let i = 0; i < ITERATIONS; i++) { + _resetTorchConfigCache(); + try { + const res = await loadTorchConfig(); + // Use res to prevent dead code elimination if JIT is smart (though side effects exist) + if (!res) throw new Error("Failed"); + } catch (e) { + // ignore + } + } + + const end = process.hrtime.bigint(); + const duration = Number(end - start) / 1e6; // ms + + console.log(`[ASYNC] Loaded config ${ITERATIONS} times in ${duration.toFixed(2)}ms`); + console.log(`[ASYNC] Average: ${(duration / ITERATIONS).toFixed(4)}ms per load`); + + // Cleanup + if (!originalConfig && fs.existsSync(configPath)) { + fs.unlinkSync(configPath); + } +} + +run(); diff --git a/src/relay-health.mjs b/src/relay-health.mjs index 67c4943..0c0a824 100644 --- a/src/relay-health.mjs +++ b/src/relay-health.mjs @@ -196,15 +196,22 @@ async function appendHistory(historyPath, entry) { export async function runRelayHealthCheck(options = {}) { const { cadence = 'daily', - relays = getRelays(), - namespace = getNamespace(), timeoutMs = 6000, - historyPath = path.resolve(process.cwd(), 'task-logs', 'relay-health', `${cadence}.jsonl`), allRelaysDownMinutes = 10, minSuccessRate = 0.7, windowMinutes = 60, } = options; + let { + relays, + namespace, + historyPath, + } = options; + + if (!relays) relays = await getRelays(); + if (!namespace) namespace = await getNamespace(); + if (!historyPath) historyPath = path.resolve(process.cwd(), 'task-logs', 'relay-health', `${cadence}.jsonl`); + const relayResults = []; for (const relay of relays) { const relayResult = { diff --git a/src/roster.mjs b/src/roster.mjs index bfac153..99ab414 100644 --- a/src/roster.mjs +++ b/src/roster.mjs @@ -94,8 +94,8 @@ function parseEnvRoster(value) { .filter(Boolean); } -export function getRoster(cadence) { - const config = loadTorchConfig(); +export async function getRoster(cadence) { + const config = await loadTorchConfig(); const dailyFromEnv = parseEnvRoster(process.env.NOSTR_LOCK_DAILY_ROSTER); const weeklyFromEnv = parseEnvRoster(process.env.NOSTR_LOCK_WEEKLY_ROSTER); const canonical = loadCanonicalRoster(); diff --git a/src/torch-config.mjs b/src/torch-config.mjs index 8b03dc6..a759051 100644 --- a/src/torch-config.mjs +++ b/src/torch-config.mjs @@ -127,18 +127,20 @@ export function _resetTorchConfigCache() { cachedConfig = null; } -export function loadTorchConfig(fileSystem = fs) { +export async function loadTorchConfig(fileSystem = fs) { if (cachedConfig) return cachedConfig; const configPath = getTorchConfigPath(); let raw = {}; - if (fileSystem.existsSync(configPath)) { - try { - raw = JSON.parse(fileSystem.readFileSync(configPath, 'utf8')); - } catch (err) { + try { + const content = await fileSystem.promises.readFile(configPath, 'utf8'); + raw = JSON.parse(content); + } catch (err) { + if (err.code !== 'ENOENT') { throw new Error(`Failed to parse ${configPath}: ${err.message}`, { cause: err }); } + // If file missing (ENOENT), we use empty object defaults } cachedConfig = parseTorchConfig(raw, configPath); @@ -199,8 +201,8 @@ function resolveIntegerConfig(envKey, configValue, defaultValue, validator, conf return value; } -export function getRelays() { - const config = loadTorchConfig(); +export async function getRelays() { + const config = await loadTorchConfig(); const envRelays = process.env.NOSTR_LOCK_RELAYS; if (envRelays) { return parseEnvRelayList(envRelays, 'NOSTR_LOCK_RELAYS'); @@ -214,8 +216,8 @@ export function getRelays() { return DEFAULT_RELAYS; } -export function getRelayFallbacks() { - const config = loadTorchConfig(); +export async function getRelayFallbacks() { + const config = await loadTorchConfig(); const envRelays = process.env.NOSTR_LOCK_RELAY_FALLBACKS; if (envRelays) { return parseEnvRelayList(envRelays, 'NOSTR_LOCK_RELAY_FALLBACKS'); @@ -223,14 +225,14 @@ export function getRelayFallbacks() { return config.nostrLock.relayFallbacks || []; } -export function getNamespace() { - const config = loadTorchConfig(); +export async function getNamespace() { + const config = await loadTorchConfig(); const namespace = (process.env.NOSTR_LOCK_NAMESPACE || config.nostrLock.namespace || DEFAULT_NAMESPACE).trim(); return namespace || DEFAULT_NAMESPACE; } -export function getTtl() { - const config = loadTorchConfig(); +export async function getTtl() { + const config = await loadTorchConfig(); const envTtl = process.env.NOSTR_LOCK_TTL; if (envTtl) { const parsed = parseInt(envTtl, 10); @@ -242,8 +244,8 @@ export function getTtl() { return DEFAULT_TTL; } -export function getQueryTimeoutMs() { - const config = loadTorchConfig(); +export async function getQueryTimeoutMs() { + const config = await loadTorchConfig(); return resolveIntegerConfig( 'NOSTR_LOCK_QUERY_TIMEOUT_MS', config.nostrLock.queryTimeoutMs, @@ -253,8 +255,8 @@ export function getQueryTimeoutMs() { ); } -export function getPublishTimeoutMs() { - const config = loadTorchConfig(); +export async function getPublishTimeoutMs() { + const config = await loadTorchConfig(); return resolveIntegerConfig( 'NOSTR_LOCK_PUBLISH_TIMEOUT_MS', config.nostrLock.publishTimeoutMs, @@ -264,8 +266,8 @@ export function getPublishTimeoutMs() { ); } -export function getMinSuccessfulRelayPublishes() { - const config = loadTorchConfig(); +export async function getMinSuccessfulRelayPublishes() { + const config = await loadTorchConfig(); return resolveIntegerConfig( 'NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES', config.nostrLock.minSuccessfulRelayPublishes, @@ -275,8 +277,8 @@ export function getMinSuccessfulRelayPublishes() { ); } -export function getMinActiveRelayPool() { - const config = loadTorchConfig(); +export async function getMinActiveRelayPool() { + const config = await loadTorchConfig(); return resolveIntegerConfig( 'NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL', config.nostrLock.minActiveRelayPool, @@ -286,8 +288,8 @@ export function getMinActiveRelayPool() { ); } -export function getHashtag() { - const config = loadTorchConfig(); +export async function getHashtag() { + const config = await loadTorchConfig(); const envValue = process.env.NOSTR_LOCK_HASHTAG; if (envValue) { return envValue.trim(); @@ -295,12 +297,12 @@ export function getHashtag() { if (config.dashboard.hashtag) { return config.dashboard.hashtag; } - const namespace = getNamespace(); + const namespace = await getNamespace(); return `${namespace}-agent-lock`; } -export function getDashboardAuth() { - const config = loadTorchConfig(); +export async function getDashboardAuth() { + const config = await loadTorchConfig(); const envValue = process.env.TORCH_DASHBOARD_AUTH; if (envValue) { return envValue.trim(); diff --git a/test/dashboard-security.test.mjs b/test/dashboard-security.test.mjs index f86e5a5..88391e8 100644 --- a/test/dashboard-security.test.mjs +++ b/test/dashboard-security.test.mjs @@ -5,15 +5,17 @@ import http from 'node:http'; import { cmdDashboard } from '../src/dashboard.mjs'; test('Dashboard File Access Security', async (t) => { - const testPort = 4175; + let testPort = 0; let server; t.beforeEach(async () => { - server = await cmdDashboard(testPort); + server = await cmdDashboard(0); + testPort = server.address().port; }); - t.afterEach(() => { - if (server) server.close(); + t.afterEach((done) => { + if (server) server.close(done); + else done(); }); function get(path) { diff --git a/test/torch-config-unit.test.mjs b/test/torch-config-unit.test.mjs index 5c41d11..95b4913 100644 --- a/test/torch-config-unit.test.mjs +++ b/test/torch-config-unit.test.mjs @@ -7,83 +7,96 @@ describe('loadTorchConfig (Unit Tests with Mocked FS)', () => { _resetTorchConfigCache(); }); - it('loads valid config correctly', () => { + it('loads valid config correctly', async () => { const mockFs = { existsSync: mock.fn(() => true), - readFileSync: mock.fn(() => JSON.stringify({ - nostrLock: { namespace: 'unit-test-ns' } - })) + promises: { + readFile: mock.fn(async () => JSON.stringify({ + nostrLock: { namespace: 'unit-test-ns' } + })) + } }; - const config = loadTorchConfig(mockFs); + const config = await loadTorchConfig(mockFs); assert.strictEqual(config.nostrLock.namespace, 'unit-test-ns'); - assert.strictEqual(mockFs.existsSync.mock.calls.length, 1); - assert.strictEqual(mockFs.readFileSync.mock.calls.length, 1); + // loadTorchConfig no longer calls existsSync on the passed fs, only readFile (and catches ENOENT) + // Actually, getTorchConfigPath calls existsSync on REAL fs. + // So checking mockFs.existsSync is wrong if loadTorchConfig doesn't call it. + // In my change, loadTorchConfig calls await fileSystem.promises.readFile. + assert.strictEqual(mockFs.promises.readFile.mock.calls.length, 1); }); - it('returns default config when file is missing', () => { + it('returns default config when file is missing', async () => { const mockFs = { - existsSync: mock.fn(() => false), - readFileSync: mock.fn() + promises: { + readFile: mock.fn(async () => { + const err = new Error('ENOENT'); + err.code = 'ENOENT'; + throw err; + }) + } }; - const config = loadTorchConfig(mockFs); + const config = await loadTorchConfig(mockFs); assert.strictEqual(config.nostrLock.namespace, null); - assert.strictEqual(mockFs.existsSync.mock.calls.length, 1); - assert.strictEqual(mockFs.readFileSync.mock.calls.length, 0); + assert.strictEqual(mockFs.promises.readFile.mock.calls.length, 1); }); - it('throws error on malformed JSON', () => { + it('throws error on malformed JSON', async () => { const mockFs = { - existsSync: mock.fn(() => true), - readFileSync: mock.fn(() => '{ invalid json }') + promises: { + readFile: mock.fn(async () => '{ invalid json }') + } }; - assert.throws(() => { - loadTorchConfig(mockFs); + await assert.rejects(async () => { + await loadTorchConfig(mockFs); }, /Failed to parse/); }); - it('caches the config and ignores subsequent fs calls', () => { + it('caches the config and ignores subsequent fs calls', async () => { const mockFs = { - existsSync: mock.fn(() => true), - readFileSync: mock.fn(() => JSON.stringify({ nostrLock: { namespace: 'cached' } })) + promises: { + readFile: mock.fn(async () => JSON.stringify({ nostrLock: { namespace: 'cached' } })) + } }; - const config1 = loadTorchConfig(mockFs); + const config1 = await loadTorchConfig(mockFs); assert.strictEqual(config1.nostrLock.namespace, 'cached'); // Second call with a different "fs" (or same) should not trigger fs operations const mockFs2 = { - existsSync: mock.fn(() => true), - readFileSync: mock.fn(() => JSON.stringify({ nostrLock: { namespace: 'changed' } })) + promises: { + readFile: mock.fn(async () => JSON.stringify({ nostrLock: { namespace: 'changed' } })) + } }; - const config2 = loadTorchConfig(mockFs2); + const config2 = await loadTorchConfig(mockFs2); assert.strictEqual(config2.nostrLock.namespace, 'cached'); assert.strictEqual(config1, config2); - assert.strictEqual(mockFs.existsSync.mock.calls.length, 1); - assert.strictEqual(mockFs2.existsSync.mock.calls.length, 0); + assert.strictEqual(mockFs.promises.readFile.mock.calls.length, 1); + assert.strictEqual(mockFs2.promises.readFile.mock.calls.length, 0); }); - it('returns null for empty string lists (new consistent behavior)', () => { + it('returns null for empty string lists (new consistent behavior)', async () => { const mockFs = { - existsSync: mock.fn(() => true), - readFileSync: mock.fn(() => JSON.stringify({ - nostrLock: { - relays: [], - relayFallbacks: [] - }, - dashboard: { - relays: [] - } - })) + promises: { + readFile: mock.fn(async () => JSON.stringify({ + nostrLock: { + relays: [], + relayFallbacks: [] + }, + dashboard: { + relays: [] + } + })) + } }; - const config = loadTorchConfig(mockFs); + const config = await loadTorchConfig(mockFs); // New behavior: all return null assert.strictEqual(config.nostrLock.relays, null); diff --git a/test/torch-config.test.mjs b/test/torch-config.test.mjs index 190cf84..2ea4728 100644 --- a/test/torch-config.test.mjs +++ b/test/torch-config.test.mjs @@ -78,22 +78,22 @@ describe('torch-config', () => { _resetTorchConfigCache(); }); - it('throws fatal error for invalid relay URL', () => { + it('throws fatal error for invalid relay URL', async () => { fs.writeFileSync(tempConfigPath, JSON.stringify({ nostrLock: { relays: ['https://bad'] } })); process.env.TORCH_CONFIG_PATH = tempConfigPath; - assert.throws(() => loadTorchConfig(), /Invalid relay URL in nostrLock\.relays/); + await assert.rejects(() => loadTorchConfig(), /Invalid relay URL in nostrLock\.relays/); }); - it('throws fatal error for invalid timeout range', () => { + it('throws fatal error for invalid timeout range', async () => { fs.writeFileSync(tempConfigPath, JSON.stringify({ nostrLock: { queryTimeoutMs: 50 } })); process.env.TORCH_CONFIG_PATH = tempConfigPath; - assert.throws(() => loadTorchConfig(), /Invalid nostrLock\.queryTimeoutMs/); + await assert.rejects(() => loadTorchConfig(), /Invalid nostrLock\.queryTimeoutMs/); }); - it('throws error for malformed JSON', () => { + it('throws error for malformed JSON', async () => { fs.writeFileSync(badConfigPath, '{ invalid json }'); process.env.TORCH_CONFIG_PATH = badConfigPath; - assert.throws(() => loadTorchConfig(), /Failed to parse/); + await assert.rejects(() => loadTorchConfig(), /Failed to parse/); }); }); @@ -125,28 +125,28 @@ describe('torch-config', () => { _resetTorchConfigCache(); }); - it('returns default relays', () => { - assert.deepStrictEqual(getRelays(), DEFAULT_RELAYS); + it('returns default relays', async () => { + assert.deepStrictEqual(await getRelays(), DEFAULT_RELAYS); }); - it('validates env knob values', () => { + it('validates env knob values', async () => { process.env.NOSTR_LOCK_QUERY_TIMEOUT_MS = '99'; - assert.throws(() => getQueryTimeoutMs(), /NOSTR_LOCK_QUERY_TIMEOUT_MS/); + await assert.rejects(() => getQueryTimeoutMs(), /NOSTR_LOCK_QUERY_TIMEOUT_MS/); process.env.NOSTR_LOCK_QUERY_TIMEOUT_MS = '5000'; process.env.NOSTR_LOCK_PUBLISH_TIMEOUT_MS = 'abc'; - assert.throws(() => getPublishTimeoutMs(), /NOSTR_LOCK_PUBLISH_TIMEOUT_MS/); + await assert.rejects(() => getPublishTimeoutMs(), /NOSTR_LOCK_PUBLISH_TIMEOUT_MS/); process.env.NOSTR_LOCK_PUBLISH_TIMEOUT_MS = '8000'; process.env.NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES = '0'; - assert.throws(() => getMinSuccessfulRelayPublishes(), /NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES/); + await assert.rejects(() => getMinSuccessfulRelayPublishes(), /NOSTR_LOCK_MIN_SUCCESSFUL_PUBLISHES/); process.env.NOSTR_LOCK_RELAY_FALLBACKS = 'https://bad'; - assert.throws(() => getRelayFallbacks(), /NOSTR_LOCK_RELAY_FALLBACKS/); + await assert.rejects(() => getRelayFallbacks(), /NOSTR_LOCK_RELAY_FALLBACKS/); process.env.NOSTR_LOCK_RELAY_FALLBACKS = ''; process.env.NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL = '0'; - assert.throws(() => getMinActiveRelayPool(), /NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL/); + await assert.rejects(() => getMinActiveRelayPool(), /NOSTR_LOCK_MIN_ACTIVE_RELAY_POOL/); }); }); });