@hashgraph/solo
Version:
An opinionated CLI tool to deploy and manage private Hedera Networks.
222 lines • 11 kB
JavaScript
// SPDX-License-Identifier: Apache-2.0
import sinon from 'sinon';
import { expect } from 'chai';
import { describe, it, beforeEach } from 'mocha';
import { DefaultSoloEventBus } from '../../../../src/core/events/default-solo-event-bus.js';
import { SoloEventType } from '../../../../src/core/events/event-types/solo-event.js';
import { NetworkDeployedEvent } from '../../../../src/core/events/event-types/network-deployed-event.js';
import { MirrorNodeDeployedEvent } from '../../../../src/core/events/event-types/mirror-node-deployed-event.js';
import { container } from 'tsyringe-neo';
import { InjectTokens } from '../../../../src/core/dependency-injection/inject-tokens.js';
import { Duration } from '../../../../src/core/time/duration.js';
import { SoloError } from '../../../../src/core/errors/solo-error.js';
describe('SoloEventBus', () => {
let bus;
const networkEvent = new NetworkDeployedEvent('my-deployment');
const mirrorEvent = new MirrorNodeDeployedEvent('my-deployment');
beforeEach(() => {
// resolve the test logger from the DI container
const testLogger = container.resolve(InjectTokens.SoloLogger);
bus = new DefaultSoloEventBus(testLogger);
});
it('should call a registered handler when the matching event is emitted', () => {
const handler = sinon.spy();
bus.on(SoloEventType.NetworkDeployed, handler);
bus.emit(networkEvent);
expect(handler).to.have.been.calledOnceWithExactly(networkEvent);
});
it('should not call a handler after it has been removed with off()', () => {
const handler = sinon.spy();
bus.on(SoloEventType.NetworkDeployed, handler);
bus.off(SoloEventType.NetworkDeployed, handler);
bus.emit(networkEvent);
expect(handler).not.to.have.been.called;
});
it('should call all registered handlers for the same event type', () => {
const handlerA = sinon.spy();
const handlerB = sinon.spy();
bus.on(SoloEventType.NetworkDeployed, handlerA);
bus.on(SoloEventType.NetworkDeployed, handlerB);
bus.emit(networkEvent);
expect(handlerA).to.have.been.calledOnceWithExactly(networkEvent);
expect(handlerB).to.have.been.calledOnceWithExactly(networkEvent);
});
it('should not call a handler registered for a different event type', () => {
const handler = sinon.spy();
bus.on(SoloEventType.MirrorNodeDeployed, handler);
bus.emit(networkEvent);
expect(handler).not.to.have.been.called;
});
it('should call handlers for different event types independently', () => {
const networkHandler = sinon.spy();
const mirrorHandler = sinon.spy();
bus.on(SoloEventType.NetworkDeployed, networkHandler);
bus.on(SoloEventType.MirrorNodeDeployed, mirrorHandler);
bus.emit(networkEvent);
bus.emit(mirrorEvent);
expect(networkHandler).to.have.been.calledOnceWithExactly(networkEvent);
expect(mirrorHandler).to.have.been.calledOnceWithExactly(mirrorEvent);
});
it('should only remove the specific handler passed to off(), leaving others intact', () => {
const handlerA = sinon.spy();
const handlerB = sinon.spy();
bus.on(SoloEventType.NetworkDeployed, handlerA);
bus.on(SoloEventType.NetworkDeployed, handlerB);
bus.off(SoloEventType.NetworkDeployed, handlerA);
bus.emit(networkEvent);
expect(handlerA).not.to.have.been.called;
expect(handlerB).to.have.been.calledOnceWithExactly(networkEvent);
});
it('should call a handler each time the event is emitted', () => {
const handler = sinon.spy();
bus.on(SoloEventType.NetworkDeployed, handler);
bus.emit(networkEvent);
bus.emit(networkEvent);
expect(handler).to.have.been.calledTwice;
});
it('waitFor() should resolve with the event when it is emitted', async () => {
const promise = bus.waitFor(SoloEventType.NetworkDeployed);
bus.emit(networkEvent);
const result = await promise;
expect(result).to.equal(networkEvent);
});
it('waitFor() should resolve only once even if the event is emitted multiple times', async () => {
const results = [];
const promise = bus.waitFor(SoloEventType.NetworkDeployed);
promise.then((soloEvent) => results.push(soloEvent));
bus.emit(networkEvent);
bus.emit(networkEvent);
await promise;
expect(results).to.have.lengthOf(1);
});
it('waitFor() should not resolve for a different event type', async () => {
let resolved = false;
bus.waitFor(SoloEventType.NetworkDeployed).then(() => {
resolved = true;
});
bus.emit(mirrorEvent);
await Promise.resolve(); // flush microtask queue
expect(resolved).to.be.false;
});
it('waitFor() with predicate should resolve when the predicate returns true', async () => {
const target = new NetworkDeployedEvent('target');
const other = new NetworkDeployedEvent('other');
const promise = bus.waitFor(SoloEventType.NetworkDeployed, (soloEvent) => soloEvent.deployment === 'target');
bus.emit(other);
bus.emit(target);
expect(await promise).to.equal(target);
});
it('waitFor() with predicate should skip events that do not match', async () => {
let resolved = false;
bus
.waitFor(SoloEventType.NetworkDeployed, (soloEvent) => soloEvent.deployment === 'target')
.then(() => {
resolved = true;
});
bus.emit(new NetworkDeployedEvent('other'));
await Promise.resolve();
expect(resolved).to.be.false;
});
it('waitFor() with predicate should resolve only once even if multiple matching events are emitted', async () => {
const results = [];
const promise = bus.waitFor(SoloEventType.NetworkDeployed, () => true);
promise.then((soloEvent) => results.push(soloEvent));
bus.emit(networkEvent);
bus.emit(networkEvent);
await promise;
expect(results).to.have.lengthOf(1);
});
it('waitFor() for different types resolves each independently', async () => {
const networkPromise = bus.waitFor(SoloEventType.NetworkDeployed);
const mirrorPromise = bus.waitFor(SoloEventType.MirrorNodeDeployed);
bus.emit(networkEvent);
bus.emit(mirrorEvent);
expect(await networkPromise).to.equal(networkEvent);
expect(await mirrorPromise).to.equal(mirrorEvent);
});
it('NetworkDeployedEvent should have the correct type and deployment', () => {
const event = new NetworkDeployedEvent('solo-deployment');
expect(event.type).to.equal(SoloEventType.NetworkDeployed);
expect(event.deployment).to.equal('solo-deployment');
});
it('MirrorNodeDeployedEvent should have the correct type and deployment', () => {
const event = new MirrorNodeDeployedEvent('solo-deployment');
expect(event.type).to.equal(SoloEventType.MirrorNodeDeployed);
expect(event.deployment).to.equal('solo-deployment');
});
it('waitFor() should resolve if event was emitted before waitFor is called', async () => {
// Emit the event first (before waitFor is called)
bus.emit(networkEvent);
// Now call waitFor; it should resolve immediately with the past event
const promise = bus.waitFor(SoloEventType.NetworkDeployed);
const result = await promise;
expect(result).to.equal(networkEvent);
});
describe('waitFor() timeout', () => {
it('should reject with a SoloError when the timeout elapses before the event is emitted', async () => {
const promise = bus.waitFor(SoloEventType.NetworkDeployed, undefined, Duration.ofMillis(10));
await expect(promise).to.be.rejectedWith(SoloError, /timed out/);
});
it('should resolve before the timeout if the event arrives in time', async () => {
const promise = bus.waitFor(SoloEventType.NetworkDeployed, undefined, Duration.ofMillis(100));
bus.emit(networkEvent);
expect(await promise).to.equal(networkEvent);
});
it('should resolve immediately from history before the timeout fires', async () => {
bus.emit(networkEvent);
const result = await bus.waitFor(SoloEventType.NetworkDeployed, undefined, Duration.ofMillis(10));
expect(result).to.equal(networkEvent);
});
});
describe('clearHistory()', () => {
it('should prevent waitFor() from resolving against a cleared event type', async () => {
bus.emit(networkEvent);
bus.clearHistory(SoloEventType.NetworkDeployed);
let resolved = false;
bus.waitFor(SoloEventType.NetworkDeployed).then(() => {
resolved = true;
});
await Promise.resolve();
expect(resolved).to.be.false;
});
it('should not affect history for other event types when clearing a specific type', async () => {
bus.emit(networkEvent);
bus.emit(mirrorEvent);
bus.clearHistory(SoloEventType.NetworkDeployed);
// mirror history should still resolve waitFor immediately
const result = await bus.waitFor(SoloEventType.MirrorNodeDeployed);
expect(result).to.equal(mirrorEvent);
});
it('should clear all event type histories when called with no argument', async () => {
bus.emit(networkEvent);
bus.emit(mirrorEvent);
bus.clearHistory();
let networkResolved = false;
let mirrorResolved = false;
bus.waitFor(SoloEventType.NetworkDeployed).then(() => {
networkResolved = true;
});
bus.waitFor(SoloEventType.MirrorNodeDeployed).then(() => {
mirrorResolved = true;
});
await Promise.resolve();
expect(networkResolved).to.be.false;
expect(mirrorResolved).to.be.false;
});
it('should allow new events to be recorded after history is cleared', async () => {
bus.emit(networkEvent);
bus.clearHistory(SoloEventType.NetworkDeployed);
const newEvent = new NetworkDeployedEvent('new-deployment');
bus.emit(newEvent);
const result = await bus.waitFor(SoloEventType.NetworkDeployed);
expect(result).to.equal(newEvent);
});
it('should be a no-op when called on a type with no recorded history', () => {
expect(() => bus.clearHistory(SoloEventType.NetworkDeployed)).not.to.throw();
});
it('should be a no-op when called with no argument on an empty bus', () => {
expect(() => bus.clearHistory()).not.to.throw();
});
});
});
//# sourceMappingURL=solo-event-bus.test.js.map