Skip to content

Commit

Permalink
add test for test-logging-tx-observer (#1630)
Browse files Browse the repository at this point in the history
* add test for test-logging-tx-observer

Signed-off-by: Babatunde Sanusi <[email protected]>

* add test for tx-observer-dispatch

Signed-off-by: Babatunde Sanusi <[email protected]>

---------

Signed-off-by: Babatunde Sanusi <[email protected]>
  • Loading branch information
tunedev authored Nov 6, 2024
1 parent aa11adf commit 6082ede
Show file tree
Hide file tree
Showing 4 changed files with 335 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,12 @@ class LoggingTxObserver extends TxObserverInterface{
* @param {TxStatus | TxStatus[]} results The result information of the finished TXs. Can be a collection of results for a batch of TXs.
*/
txFinished(results) {
// TODO: appending metadata should be done by the dispatch
if (Array.isArray(results)) {
for (let result of results) {
// add extra metadata
result.workerIndex = this.workerIndex;
result.roundIndex = this.roundIndex;

// TODO: use fast-json-stringify
this.logFunction(JSON.stringify(result));
}
} else {
// add extra metadata
results.workerIndex = this.workerIndex;
results.roundIndex = this.roundIndex;

// TODO: use fast-json-stringify
this.logFunction(JSON.stringify(results));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,33 +102,20 @@ class TxObserverDispatch extends TxObserverInterface {
return;
}

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(results);
}
}
}

module.exports = TxObserverDispatch;























116 changes: 116 additions & 0 deletions packages/caliper-core/test/worker/tx-observers/logging-tx-observer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* 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 chai = require('chai');
const sinon = require('sinon');
const expect = chai.expect;
const mockery = require('mockery');

chai.use(require('sinon-chai'));

describe('When monitoring transaction activity', () => {
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(logStubs)
};

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

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

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

after(() => {
mockery.deregisterAll();
mockery.disable();
});

describe('On initialization', () => {
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(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(logStubs.info).to.have.been.calledTwice;
expect(logStubs.info.firstCall).to.have.been.calledWithMatch(JSON.stringify({
status: 'success',
}));
expect(logStubs.info.secondCall).to.have.been.calledWithMatch(JSON.stringify({
status: 'failed',
}));
});

it('should handle empty results without logging', () => {
observer.txFinished([]);
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 6082ede

Please sign in to comment.