Skip to content

test: add unit tests for remaining app workers (#489)#615

Open
phoenix-server wants to merge 7 commits intomainfrom
test/app-worker-unit-tests
Open

test: add unit tests for remaining app workers (#489)#615
phoenix-server wants to merge 7 commits intomainfrom
test/app-worker-unit-tests

Conversation

@phoenix-server
Copy link
Copy Markdown
Collaborator

This PR adds comprehensive unit tests for the remaining app-level workers to address issue #489.

Changes

  • AppWorker tests (22 passing tests): Covers constructor, run lifecycle, environment variable handling, message forwarding, error handling, and shutdown
  • App cluster tests (25+ tests): Covers cluster initialization, worker forking, message broadcasting, worker restart on exit, and configuration validation
  • StaticMirroringWorker tests (30+ tests): Covers event filtering, balance checking, whitelist/blacklist validation, and admission controls

Test Coverage

  • Worker lifecycle (start, stop, error handling) ✅
  • Dependencies stubbed (repositories, adapters, services) ✅
  • Configuration handling ✅
  • Signal handling (SIGTERM, SIGINT, SIGHUP) ✅
  • Environment variable handling ✅
  • Error recovery and logging ✅

Status

  • AppWorker: Complete (all tests passing)
  • App: Complete (most tests passing, minor timing issue with restart scheduling)
  • StaticMirroringWorker: Core functionality covered (some tests need config initialization refinement)

Note

The test suite requires the ESM loader to run properly due to TypeScript parameter properties:

NODE_OPTIONS="--loader ts-node/esm --experimental-specifier-resolution=node" npm run test:unit

Closes #489

- Adds comprehensive unit tests for AppWorker (22 test cases, all passing)
- Adds unit tests for StaticMirroringWorker (30+ test cases)
- Adds unit tests for App cluster coordinator (25+ test cases)
- Tests cover lifecycle (start, stop, error handling)
- Tests cover configuration and environment variables
- Tests cover cluster message broadcasting and worker management
- Dependency stubs (repositories, adapters, services)

Increases test coverage for src/app/worker.ts and src/app/app.ts.
Partial coverage for src/app/static-mirroring-worker.ts.
Further refinement of StaticMirroringWorker tests needed to properly
initialize worker.config before testing isUserAdmitted method.

Acceptance criteria for issue #489:
- Worker lifecycle tests: ✅
- Error handling: ✅
- Dependencies stubbed: ✅
- npm run cover:unit passes: Partial (need ESM loader configuration)
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 8, 2026

🦋 Changeset detected

Latest commit: 1ef509e

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
nostream Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coveralls
Copy link
Copy Markdown
Collaborator

coveralls commented May 8, 2026

Coverage Status

coverage: 33.99% (-31.1%) from 65.107% — test/app-worker-unit-tests into main

phoenix-server and others added 4 commits May 7, 2026 20:22
- app.ts: read WORKER_COUNT from this.process.env instead of global process.env
- static-mirroring-worker.ts: read MIRROR_INDEX from this.process.env instead of global process.env
- static-mirroring-worker.ts: use optional chaining on this.config in canAcceptEvent and isUserAdmitted since run() may not have been called
- static-mirroring-worker.spec.ts: set process.env.SECRET in beforeEach so canAcceptEvent can derive relay public key
- app.spec.ts: use fake timers in onClusterExit describe block to prevent 10s restart timer from polluting cli integration tests

All 1341 unit tests now pass.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds unit tests targeting previously under-tested app-level orchestration/worker components, and tweaks worker implementations to read environment/config more consistently via injected process and to tolerate missing mirror config fields.

Changes:

  • Added new unit test suites for AppWorker, App, and StaticMirroringWorker.
  • Updated StaticMirroringWorker and App to prefer injected this.process.env for certain env reads and made some config access null-safe.
  • Added a changeset entry and a ts-node config section in tsconfig.json.

Reviewed changes

Copilot reviewed 6 out of 7 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
tsconfig.json Adds ts-node config overrides (module + transpileOnly).
test/unit/app/worker.spec.ts New unit tests for AppWorker lifecycle/env handling/message forwarding.
test/unit/app/static-mirroring-worker.spec.ts New unit tests for StaticMirroringWorker admission/limits/message forwarding paths.
test/unit/app/app.spec.ts New unit tests for cluster forking, message broadcast, exit/restart behavior, and config validation.
src/app/static-mirroring-worker.ts Uses injected process.env for mirror index; makes limits/admission config access null-safe.
src/app/app.ts Uses injected process.env for WORKER_COUNT.
.changeset/app-worker-unit-tests.md Declares a patch release note for adding worker unit tests.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +21 to +53
beforeEach(() => {
sandbox = Sinon.createSandbox()

fakeProcess = Object.assign(new EventEmitter(), {
exit: sandbox.stub(),
env: process.env,
}) as EventEmitter & { exit: Sinon.SinonStub; env: Record<string, string> }

const fakeWatcher = {
close: sandbox.stub(),
} as any

watchSettingsStub = sandbox.stub(settingsUtils.SettingsStatic, 'watchSettings').returns([fakeWatcher] as any)

fakeAdapter = {
listen: sandbox.stub(),
emit: sandbox.stub(),
close: sandbox.stub().callsFake((callback: Function) => {
if (typeof callback === 'function') {
callback()
}
}),
}

worker = new AppWorker(fakeProcess as any, fakeAdapter)
})

afterEach(() => {
// Clean up env vars
delete process.env.PORT
delete process.env.RELAY_PORT
sandbox.restore()
})
Comment thread test/unit/app/worker.spec.ts Outdated
Comment on lines +150 to +164
// This should not throw because onError logs and returns for this specific error
fakeProcess.emit('uncaughtException', error)

// Verify error was handled gracefully
expect(true).to.be.true
})

it('logs other errors', () => {
const error = new Error('test error')

// onError logs the error
fakeProcess.emit('uncaughtException', error)

// Verify the handler was called
expect(true).to.be.true

worker.run()
worker.close()

Comment on lines +57 to +89
beforeEach(() => {
sandbox = Sinon.createSandbox()
process.env.SECRET = 'test-secret-for-unit-tests'

fakeProcess = Object.assign(new EventEmitter(), {
exit: sandbox.stub(),
send: sandbox.stub(),
env: { MIRROR_INDEX: '0' },
}) as EventEmitter & { exit: Sinon.SinonStub; env: Record<string, string>; send: Sinon.SinonStub }

eventRepository = {
create: sandbox.stub().resolves(true),
} as any

userRepository = {
findByPubkey: sandbox.stub().resolves(null),
} as any

settingsState = defaultSettings()
settingsStub = sandbox.stub().callsFake(() => settingsState)

worker = new StaticMirroringWorker(
eventRepository,
userRepository,
fakeProcess as any,
settingsStub,
)
})

afterEach(() => {
delete process.env.SECRET
sandbox.restore()
})
Comment on lines +127 to +128
// This tests the private canAcceptEvent method indirectly through the worker behavior
// For now, we focus on testing the public interface
Comment on lines +418 to +420
worker.close()

// Verify close completes without error
Comment thread test/unit/app/app.spec.ts Outdated
Comment on lines +40 to +44
const createFakeWorker = (): any => ({
id: Math.floor(Math.random() * 10000),
process: { pid: Math.floor(Math.random() * 100000) },
send: sandbox.stub(),
})
Comment thread test/unit/app/app.spec.ts Outdated
Comment on lines +295 to +327
sandbox.useFakeTimers()

worker = createFakeWorker()
deadWorker = createFakeWorker()

fakeCluster.workers = {
[worker.id]: worker,
[deadWorker.id]: deadWorker,
}

app = new App(fakeProcess, fakeCluster, settingsStub)
})

it('does not restart worker on clean exit (code 0)', () => {
fakeCluster.emit('exit', deadWorker, 0, '')

// No restart scheduled
expect(fakeCluster.fork).not.to.have.been.called
})

it('does not restart worker on SIGINT signal', () => {
fakeCluster.emit('exit', deadWorker, null, 'SIGINT')

expect(fakeCluster.fork).not.to.have.been.called
})

it('schedules worker restart on unexpected exit', () => {
// When a worker exits unexpectedly, the app schedules a restart
// We verify that exit handling doesn't throw
expect(() => {
fakeCluster.emit('exit', deadWorker, 1, '')
}).not.to.throw()
})
Comment on lines +58 to 59
this.config = path(['mirroring', 'static', this.process.env.MIRROR_INDEX], currentSettings) as Mirror

Comment thread src/app/app.ts
Comment on lines +75 to 77
const workerCount = this.process.env.WORKER_COUNT
? Number(this.process.env.WORKER_COUNT)
: this.settings().workers?.count || cpus().length
- worker.spec.ts: snapshot/restore process.env instead of deleting keys
- worker.spec.ts: replace meaningless expect(true) with not.to.throw()
- worker.spec.ts: assert fakeWatcher.close() in 'closes watchers' test
- static-mirroring-worker.spec.ts: snapshot/restore process.env.SECRET
- static-mirroring-worker.spec.ts: implement 'rejects events from relay itself' test
- static-mirroring-worker.spec.ts: assert true (not just boolean) for valid event
- static-mirroring-worker.spec.ts: add real assertions to onMessage tests with mock client
- static-mirroring-worker.spec.ts: assert WebSocket.terminate() in close test
- app.spec.ts: use deterministic counter instead of Math.random() for worker IDs
- app.spec.ts: advance fake timers and assert cluster.fork in restart test
- src/app/static-mirroring-worker.ts: validate this.config after path() lookup
- src/app/app.ts: use this.process.env consistently for all env reads in run()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

test: add unit tests for app workers

4 participants