diff --git a/packages/caliper-core/lib/worker/tx-observers/tx-observer-dispatch.js b/packages/caliper-core/lib/worker/tx-observers/tx-observer-dispatch.js index 80d7997b8..63fb4418c 100644 --- a/packages/caliper-core/lib/worker/tx-observers/tx-observer-dispatch.js +++ b/packages/caliper-core/lib/worker/tx-observers/tx-observer-dispatch.js @@ -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 in 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); } } } diff --git a/packages/caliper-core/test/worker/tx-observers/logging-tx-observer.js b/packages/caliper-core/test/worker/tx-observers/logging-tx-observer.js index 3c7db2589..a0ca447ac 100644 --- a/packages/caliper-core/test/worker/tx-observers/logging-tx-observer.js +++ b/packages/caliper-core/test/worker/tx-observers/logging-tx-observer.js @@ -22,7 +22,7 @@ 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({ @@ -30,12 +30,14 @@ describe('When monitoring transaction activity', () => { 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); @@ -43,13 +45,14 @@ describe('When monitoring transaction activity', () => { }); 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(() => { @@ -60,20 +63,40 @@ 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; }); }); @@ -81,26 +104,18 @@ describe('When monitoring transaction activity', () => { 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; }); }); }); diff --git a/packages/caliper-core/test/worker/tx-observers/tx-observer-dispatch.js b/packages/caliper-core/test/worker/tx-observers/tx-observer-dispatch.js new file mode 100644 index 000000000..d9caf7eca --- /dev/null +++ b/packages/caliper-core/test/worker/tx-observers/tx-observer-dispatch.js @@ -0,0 +1,153 @@ +/* +* 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'); + +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.stub(), + }); + + // 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; + }); + }); + }); +});