Skip to content

Commit

Permalink
add test for tx-observer-dispatch
Browse files Browse the repository at this point in the history
Signed-off-by: Babatunde Sanusi <swisskid95@gmail.com>
  • Loading branch information
tunedev committed Oct 2, 2024
1 parent be9ebeb commit 02b94a9
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 40 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -102,22 +102,18 @@ class TxObserverDispatch extends TxObserverInterface {
return;
}

const metadata = {
workerIndex: this.workerIndex,
roundIndex: this.currentRound,
};
const resultWithMetadata = Array.isArray(results)
? results.map(result => ({
...result,
...metadata,
}))
: {
...results,
...metadata
};
if (Array.isArray(results)) {
for (let result of results) {
result.workerIndex = this.workerIndex;
result.roundIndex = this.currentRound;
}
}else {
results.workerIndex = this.workerIndex;
results.roundIndex = this.currentRound;
}

for (let observer of this.txObservers) {
observer.txFinished(resultWithMetadata);
observer.txFinished(results);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,34 +22,37 @@ const mockery = require('mockery');
chai.use(require('sinon-chai'));

describe('When monitoring transaction activity', () => {
let createLoggingTxObserver, CaliperUtils, observer, logStub;
let createLoggingTxObserver, CaliperUtils, observer, logStubs;

before(() => {
mockery.enable({
warnOnReplace: false,
warnOnUnregistered: false
});

logStubs = {
info: sinon.stub(),
error: sinon.stub(),
warn: sinon.stub()
};

CaliperUtils = {
getLogger: sinon.stub().returns({
info: sinon.stub(),
error: sinon.stub(),
warn: sinon.stub()
})
getLogger: sinon.stub().returns(logStubs)
};

mockery.registerMock('../../common/utils/caliper-utils', CaliperUtils);
createLoggingTxObserver = require('../../../lib/worker/tx-observers/logging-tx-observer').createTxObserver;
});

beforeEach(() => {
logStub = CaliperUtils.getLogger().info;
logStubs.info.resetHistory();
logStubs.warn.resetHistory();
logStubs.error.resetHistory();
observer = createLoggingTxObserver({ messageLevel: 'info' }, null, 0);
});

afterEach(() => {
sinon.restore();
logStub.resetHistory();
});

after(() => {
Expand All @@ -60,47 +63,59 @@ describe('When monitoring transaction activity', () => {
describe('On initialization', () => {
it('should set up the logger with default settings if no options are provided', () => {
observer = createLoggingTxObserver({}, null, 0);
expect(logStub).to.exist;
expect(logStubs.info).to.exist;
});

it('should use a specific log level if provided in options', () => {
observer = createLoggingTxObserver({ messageLevel: 'warn' }, null, 0);
const warnLogger = CaliperUtils.getLogger().warn;
expect(warnLogger).to.exist;
const logLevels = ['info', 'warn', 'error'];

logLevels.forEach(level => {
it(`should use the '${level}' log level if provided in options`, () => {
observer = createLoggingTxObserver({ messageLevel: level }, null, 0);

// Simulate a finished transaction
const result = { status: 'success' };
observer.txFinished(result);

// Ensure the correct logger was called
expect(logStubs[level]).to.have.been.calledOnce;
expect(logStubs[level]).to.have.been.calledWith(JSON.stringify({
status: 'success',
}));

// Ensure other loggers were not called
Object.keys(logStubs).forEach(otherLevel => {
if (otherLevel !== level) {
expect(logStubs[otherLevel]).to.not.have.been.called;
}
});
});
});

});

describe('When processing submitted transactions', () => {
it('should ignore submissions and not log any data', () => {
observer.txSubmitted(5);
expect(logStub).to.not.have.been.called;
expect(logStubs.info).to.not.have.been.called;
});
});

describe('When processing finished transactions', () => {
it('should log multiple transaction results', () => {
const results = [{ status: 'success' }, { status: 'failed' }];
observer.txFinished(results);
expect(logStub).to.have.been.calledTwice;
expect(logStub.firstCall).to.have.been.calledWithMatch(JSON.stringify({
expect(logStubs.info).to.have.been.calledTwice;
expect(logStubs.info.firstCall).to.have.been.calledWithMatch(JSON.stringify({
status: 'success',
}));
expect(logStub.secondCall).to.have.been.calledWithMatch(JSON.stringify({
expect(logStubs.info.secondCall).to.have.been.calledWithMatch(JSON.stringify({
status: 'failed',
}));
});

it('should handle empty results without logging', () => {
observer.txFinished([]);
expect(logStub).to.not.have.been.called;
});

it('should not modify the original result object properties', () => {
const result = { status: 'success' };
observer.txFinished(result);

expect(result).to.not.have.property('workerIndex');
expect(result).to.not.have.property('roundIndex');
expect(logStubs.info).to.not.have.been.called;
});
});
});
209 changes: 209 additions & 0 deletions packages/caliper-core/test/worker/tx-observers/tx-observer-dispatch.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const sinon = require('sinon');
const chai = require('chai');
const expect = chai.expect;

const TxObserverDispatch = require('../../../lib/worker/tx-observers/tx-observer-dispatch');
const TxObserverInterface = require('../../../lib/worker/tx-observers/tx-observer-interface');
const CaliperUtils = require('../../../lib/common/utils/caliper-utils');
const { TxStatus } = require('../../../');

describe('Transaction Observer Dispatch behavior', function() {
let mockMessenger;
let internalObserver;
let dispatcher;
let mockFactory;

beforeEach(function() {
// Mocks and stubs
mockMessenger = sinon.stub();
internalObserver = sinon.createStubInstance(TxObserverInterface);

// Mock a factory function for creating TX observers
mockFactory = sinon.stub().returns({
activate: sinon.stub(),
deactivate: sinon.stub(),
txSubmitted: sinon.stub(),
txFinished: sinon.spy(),
});

// Stub the utils to return the mock factory function
sinon.stub(CaliperUtils, 'loadModuleFunction').returns(mockFactory);

// Instantiate the dispatcher
dispatcher = new TxObserverDispatch(mockMessenger, internalObserver, 'managerUuid', 1);
});

afterEach(function() {
// Restore any stubs or mocks
sinon.restore();
});

describe('When Activated', function() {
it('should activate all registered observers', async function() {
await dispatcher.activate(0, 'test-round');

// Ensure txObservers is not empty
expect(dispatcher.txObservers).to.not.be.empty;
expect(internalObserver.activate.calledOnce).to.be.true;
dispatcher.txObservers.forEach(observer => {
expect(observer.activate.calledOnce).to.be.true;
});
});
});

describe('When Deactivated', function() {
it('should deactivate all registered observers', async function() {
await dispatcher.activate(0, 'test-round');
// Deactivate the dispatcher
await dispatcher.deactivate();

// Ensure txObservers is not empty
expect(dispatcher.txObservers).to.not.be.empty;
expect(internalObserver.deactivate.calledOnce).to.be.true;
dispatcher.txObservers.forEach(observer => {
expect(observer).to.have.property('deactivate');
expect(observer.deactivate.calledOnce).to.be.true;
});
});

});

describe('When Transaction is Submitted', function() {
it('should forward the transaction submission event to all observers after the dispatcher is activated', async function() {
// Activate the dispatcher first
await dispatcher.activate(0, 'test-round');

// Call txSubmitted
dispatcher.txSubmitted(5);


// Ensure txObservers is not empty
expect(dispatcher.txObservers).to.not.be.empty;
// Ensure each observer's txSubmitted method was called with the correct count
dispatcher.txObservers.forEach(observer => {
expect(observer.txSubmitted.calledWith(5)).to.be.true;
});
});


it('should not forward the transaction submission event to observers if the dispatcher is not active', function() {
dispatcher.active = false;
dispatcher.txSubmitted(5);

// Ensure txObservers is not empty
expect(dispatcher.txObservers).to.not.be.empty;
dispatcher.txObservers.forEach(observer => {
expect(observer.txSubmitted.called).to.be.false;
});
});

});

describe('When Transaction is Completed', function() {
it('should forward the transaction completion event to all observers after the dispatcher is activated', async function() {
const mockResult = { status: 'success' };

// Activate the dispatcher first
await dispatcher.activate(0, 'test-round');

// Call txFinished
dispatcher.txFinished(mockResult);


// Ensure txObservers is not empty
expect(dispatcher.txObservers).to.not.be.empty;
// Ensure each observer's txFinished method was called with the correct result
dispatcher.txObservers.forEach(observer => {
expect(observer.txFinished.calledWith(sinon.match({
status: 'success',
workerIndex: dispatcher.workerIndex,
roundIndex: dispatcher.currentRound,
}))).to.be.true;
});
});

it('should not forward the transaction completion event to observers if the dispatcher is not active', function() {
dispatcher.active = false;
const mockResult = { status: 'success' };
dispatcher.txFinished(mockResult);


// Ensure txObservers is not empty
expect(dispatcher.txObservers).to.not.be.empty;
dispatcher.txObservers.forEach(observer => {
expect(observer.txFinished.called).to.be.false;
});
});

it('should correctly process a single TxStatus object', async function() {
// Create a TxStatus object with a string result field
const txStatus = new TxStatus('tx1');
txStatus.SetStatusSuccess();
txStatus.result = 'Some string result';

// Activate the dispatcher first
await dispatcher.activate(0, 'test-round');

// Call txFinished with the TxStatus object
dispatcher.txFinished(txStatus);

// Ensure txObservers is not empty
expect(dispatcher.txObservers).to.not.be.empty;

// Assert that txStatus now has workerIndex and roundIndex set
expect(txStatus.workerIndex).to.equal(dispatcher.workerIndex);
expect(txStatus.roundIndex).to.equal(dispatcher.currentRound);

dispatcher.txObservers.forEach(observer => {
expect(observer.txFinished.calledOnce).to.be.true;
const calledArg = observer.txFinished.getCall(0).args[0];
expect(calledArg).to.equal(txStatus);
});
});

it('should correctly process an array of TxStatus objects', async function() {
const txStatus1 = new TxStatus('tx1');
txStatus1.SetStatusSuccess();
txStatus1.result = 'Result 1';

const txStatus2 = new TxStatus('tx2');
txStatus2.SetStatusFail();
txStatus2.result = 'Result 2';

const resultsArray = [txStatus1, txStatus2];
await dispatcher.activate(0, 'test-round');

dispatcher.txFinished(resultsArray);
expect(dispatcher.txObservers).to.not.be.empty;

resultsArray.forEach(txStatus => {
expect(txStatus.workerIndex).to.equal(dispatcher.workerIndex);
expect(txStatus.roundIndex).to.equal(dispatcher.currentRound);
});

dispatcher.txObservers.forEach(observer => {
expect(observer.txFinished.calledOnce).to.be.true;
const calledArg = observer.txFinished.getCall(0).args[0];
expect(calledArg).to.deep.equal(resultsArray);
});
});


});
});

0 comments on commit 02b94a9

Please sign in to comment.