Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/nip66-relay-monitor-integration-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"nostream": patch
---

test(nip66): add integration tests for RelayMonitorWorker snapshot storage
27 changes: 27 additions & 0 deletions test/integration/features/nip-66/nip-66.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
@nip-66
Feature: NIP-66 relay monitoring
Scenario: probe run stores latest snapshot in Redis
Given NIP-66 relay monitoring is enabled
And the probe target is "ws://localhost:18808"
When the relay monitor worker completes a probe run
Then the latest probe snapshot in Redis has status "ok"
And the snapshot includes probe results for "ws://localhost:18808"

Scenario: disabled monitoring does not write a snapshot
Given NIP-66 relay monitoring is disabled
When the relay monitor worker is started
Then the relay monitor worker does not store a probe snapshot

Scenario: empty targets fall back to info.relay_url
Given NIP-66 relay monitoring is enabled
And no explicit NIP-66 probe targets are configured
When the relay monitor worker completes a probe run
Then the snapshot uses the configured relay URL as its probe target
And the latest probe snapshot in Redis has status "ok"

Scenario: invalid probe targets are skipped
Given NIP-66 relay monitoring is enabled
And invalid and valid NIP-66 probe targets are configured
When the relay monitor worker completes a probe run
Then the latest probe snapshot in Redis has status "ok"
And the snapshot includes probe results for "ws://localhost:18808"
155 changes: 155 additions & 0 deletions test/integration/features/nip-66/nip-66.feature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import EventEmitter from 'events'

import { After, Before, Given, Then, When } from '@cucumber/cucumber'
import { expect } from 'chai'
import { assocPath, mergeDeepRight, pipe } from 'ramda'

import { RelayProbeRunSnapshot } from '../../../../src/@types/relay-probe-snapshot'
import { RedisAdapter } from '../../../../src/adapters/redis-adapter'
import { RelayMonitorWorker } from '../../../../src/app/relay-monitor-worker'
import { getCacheClient } from '../../../../src/cache/client'
import { Settings } from '../../../../src/@types/settings'
import { RELAY_PROBE_SNAPSHOT_KEY, RelayProbeSnapshotStore } from '../../../../src/utils/relay-probe-snapshot'
import { SettingsStatic } from '../../../../src/utils/settings'

const INTEGRATION_RELAY_URL = 'ws://localhost:18808'
const SNAPSHOT_WAIT_MS = 15_000
const SNAPSHOT_POLL_MS = 100
const DISABLED_PROBE_WAIT_MS = 500

const defaultNip66Settings = {
enabled: true,
probeIntervalSeconds: 60,
targets: [INTEGRATION_RELAY_URL],
timeouts: {
dnsMs: 10_000,
tlsMs: 10_000,
wsRttMs: 10_000,
nip11Ms: 10_000,
},
dnsCacheTtlSeconds: 300,
}

let monitorWorker: RelayMonitorWorker | undefined
let snapshotStore: RelayProbeSnapshotStore | undefined
let cacheAdapter: RedisAdapter | undefined
let savedSettings: Settings | undefined

const waitForSnapshot = async (): Promise<RelayProbeRunSnapshot> => {
const deadline = Date.now() + SNAPSHOT_WAIT_MS

while (Date.now() < deadline) {
const snapshot = await snapshotStore!.getLatest()

if (snapshot) {
return snapshot
}

await new Promise((resolve) => setTimeout(resolve, SNAPSHOT_POLL_MS))
}

throw new Error(`Timed out waiting for probe snapshot at ${RELAY_PROBE_SNAPSHOT_KEY}`)
}

const createMonitorProcess = (): NodeJS.Process => new EventEmitter() as NodeJS.Process

Comment on lines +54 to +55
const startMonitorWorker = (): RelayMonitorWorker => {
const worker = new RelayMonitorWorker(
createMonitorProcess(),
() => SettingsStatic._settings!,
snapshotStore!,
)

worker.run()

return worker
}

Before({ tags: '@nip-66' }, async function () {
savedSettings = SettingsStatic._settings
cacheAdapter = new RedisAdapter(getCacheClient())
snapshotStore = new RelayProbeSnapshotStore(cacheAdapter)
await cacheAdapter.deleteKey(RELAY_PROBE_SNAPSHOT_KEY)
})

After({ tags: '@nip-66' }, async function () {
monitorWorker?.close()
monitorWorker = undefined

if (cacheAdapter) {
await cacheAdapter.deleteKey(RELAY_PROBE_SNAPSHOT_KEY)
}

SettingsStatic._settings = savedSettings
savedSettings = undefined
snapshotStore = undefined
cacheAdapter = undefined
})

Given('NIP-66 relay monitoring is disabled', function () {
SettingsStatic._settings = pipe(
assocPath(['nip66'], mergeDeepRight(defaultNip66Settings, { enabled: false })),
assocPath(['info', 'relay_url'], INTEGRATION_RELAY_URL),
)(SettingsStatic._settings) as Settings
})

Given('NIP-66 relay monitoring is enabled', function () {
SettingsStatic._settings = pipe(
assocPath(['nip66'], defaultNip66Settings),
assocPath(['info', 'relay_url'], INTEGRATION_RELAY_URL),
)(SettingsStatic._settings) as Settings
})

Given('the probe target is {string}', function (target: string) {
SettingsStatic._settings = assocPath(['nip66', 'targets'], [target], SettingsStatic._settings) as Settings
})

Given('no explicit NIP-66 probe targets are configured', function () {
SettingsStatic._settings = assocPath(['nip66', 'targets'], [], SettingsStatic._settings) as Settings
})

Given('invalid and valid NIP-66 probe targets are configured', function () {
SettingsStatic._settings = assocPath(
['nip66', 'targets'],
['not-a-url', INTEGRATION_RELAY_URL],
SettingsStatic._settings,
) as Settings
})

When('the relay monitor worker completes a probe run', async function () {
monitorWorker = startMonitorWorker()
this.parameters.probeSnapshot = await waitForSnapshot()
monitorWorker.close()
monitorWorker = undefined
})

When('the relay monitor worker is started', function () {
monitorWorker = startMonitorWorker()
})

Then('the relay monitor worker does not store a probe snapshot', async function () {
await new Promise((resolve) => setTimeout(resolve, DISABLED_PROBE_WAIT_MS))

const snapshot = await snapshotStore!.getLatest()
expect(snapshot).to.equal(null)
})

Then('the latest probe snapshot in Redis has status {string}', function (status: string) {
expect(this.parameters.probeSnapshot.status).to.equal(status)
})

Then('the snapshot includes probe results for {string}', function (target: string) {
const snapshot = this.parameters.probeSnapshot as RelayProbeRunSnapshot

expect(snapshot.targets).to.deep.equal([target])
expect(snapshot.results).to.have.length(1)
expect(snapshot.results[0].target.relayUrl).to.equal(target)
expect(snapshot.results[0].checkedAt).to.be.a('string')
expect(snapshot.results[0].wsRtt.status).to.equal('ok')
})

Then('the snapshot uses the configured relay URL as its probe target', function () {
const snapshot = this.parameters.probeSnapshot as RelayProbeRunSnapshot

expect(snapshot.targets).to.deep.equal([INTEGRATION_RELAY_URL])
})
Loading