From bb7579b2622385d3f010832cbf8885635cd8b755 Mon Sep 17 00:00:00 2001 From: George Raduta Date: Fri, 16 Jan 2026 17:26:52 +0100 Subject: [PATCH 1/6] Use transitionStatus not to duplicate events sent --- .../common/library/enums/transition.enum.js | 13 +++++++++++++ QualityControl/lib/services/RunModeService.js | 7 ++++--- .../lib/services/external/AliEcsSynchronizer.js | 13 ++++++++++++- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/QualityControl/common/library/enums/transition.enum.js b/QualityControl/common/library/enums/transition.enum.js index fe9b41897..3dbaccde1 100644 --- a/QualityControl/common/library/enums/transition.enum.js +++ b/QualityControl/common/library/enums/transition.enum.js @@ -21,3 +21,16 @@ export const Transition = Object.freeze({ START_ACTIVITY: 'START_ACTIVITY', STOP_ACTIVITY: 'STOP_ACTIVITY', }); + +/** + * Enumeration for different statuses of a transitions as per: + * @link https://github.com/AliceO2Group/Control/blob/master/common/protos/events.proto#L35 + */ +export const TransitionStatus = Object.freeze({ + NULL: 'NULL', + STARTED: 'STARTED', + ONGOING: 'ONGOING', + DONE_OK: 'DONE_OK', + DONE_ERROR: 'DONE_ERROR', + DONE_TIMEOUT: 'DONE_TIMEOUT', +}); diff --git a/QualityControl/lib/services/RunModeService.js b/QualityControl/lib/services/RunModeService.js index 1294289b2..47d07e680 100644 --- a/QualityControl/lib/services/RunModeService.js +++ b/QualityControl/lib/services/RunModeService.js @@ -14,7 +14,7 @@ import { LogManager, WebSocketMessage } from '@aliceo2/web-ui'; import { EmitterKeys } from '../../common/library/enums/emitterKeys.enum.js'; -import { Transition } from '../../common/library/enums/transition.enum.js'; +import { Transition, TransitionStatus } from '../../common/library/enums/transition.enum.js'; import { RunStatus } from '../../common/library/runStatus.enum.js'; import { parseObjects } from '../../common/library/qcObject/utils.js'; import QCObjectDto from '../dtos/QCObjectDto.js'; @@ -138,10 +138,11 @@ export class RunModeService { * @param {object} runEvent - Object containing runNumber and transition type. * @param {number} runEvent.runNumber - The run number associated with the event. * @param {string} runEvent.transition - The transition type (e.g., 'START_ACTIVITY', 'STOP_ACTIVITY'). + * @param {string} runEvent.transitionStatus - The status of the transition (e.g., 'DONE_OK'). * @returns {Promise} */ - async _onRunTrackEvent({ runNumber, transition }) { - if (transition === Transition.START_ACTIVITY) { + async _onRunTrackEvent({ runNumber, transition, transitionStatus }) { + if (transition === Transition.START_ACTIVITY && transitionStatus === TransitionStatus.DONE_OK) { await this._initializeRunData(runNumber); const wsMessage = new WebSocketMessage(); diff --git a/QualityControl/lib/services/external/AliEcsSynchronizer.js b/QualityControl/lib/services/external/AliEcsSynchronizer.js index ed59a8ffd..1bd0391bf 100644 --- a/QualityControl/lib/services/external/AliEcsSynchronizer.js +++ b/QualityControl/lib/services/external/AliEcsSynchronizer.js @@ -18,6 +18,13 @@ import { ServiceStatus } from '../../../common/library/enums/Status/serviceStatu const LOG_FACILITY = `${process.env.npm_config_log_label ?? 'qcg'}/ecs-synchronizer`; const RUN_TOPICS = ['aliecs.run']; +/** + * @type {RunEvent} + * @property {number} runNumber - The run number associated with the event. + * @property {Transition} transition - The type of transition (e.g., START_ACTIVITY, END_ACTIVITY). + * @property {TransitionStatus} transitionStatus - The status of the transition (e.g., DONE_OK, DONE_ERROR). + */ + /** * Service for processing events sent via Kafka from AliECS with proto objects */ @@ -73,6 +80,9 @@ export class AliEcsSynchronizer { * @returns {void} */ async _onRunMessage(eventMessage) { + /** + * @param {RunEvent} - eventMessage - message received on run topic + */ const { runEvent, timestamp } = eventMessage; if (!runEvent) { this._logger.warnMessage('Received run message on run topic without runEvent field'); @@ -82,9 +92,10 @@ export class AliEcsSynchronizer { } else if (!runEvent.transition) { this._logger.warnMessage('Received run message on run topic without runEvent.transition field'); } else { - const { runNumber, transition } = runEvent; + const { runNumber, transition, transitionStatus } = runEvent; this._eventEmitter.emit(EmitterKeys.RUN_TRACK, { runNumber, + transitionStatus, transition, timestamp: timestamp.toNumber(), }); From 67fe90e81ba80d5fdc968b1caa150b2193a33a86 Mon Sep 17 00:00:00 2001 From: George Raduta Date: Fri, 16 Jan 2026 17:27:05 +0100 Subject: [PATCH 2/6] handleWsRunTrack should be called --- .../model/NotificationRunStartModel.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/QualityControl/public/common/notifications/model/NotificationRunStartModel.js b/QualityControl/public/common/notifications/model/NotificationRunStartModel.js index 37e9ad8eb..70ed4fc81 100644 --- a/QualityControl/public/common/notifications/model/NotificationRunStartModel.js +++ b/QualityControl/public/common/notifications/model/NotificationRunStartModel.js @@ -34,7 +34,7 @@ export default class NotificationRunStartModel extends Observable { this.model.ws.addListener('command', (message) => { if (message.command === `${EmitterKeys.RUN_TRACK}:${Transition.START_ACTIVITY}`) { - this._handleWSRunTrack.bind(this, message.payload); + this._handleWSRunTrack(message.payload); } }); } @@ -82,21 +82,15 @@ export default class NotificationRunStartModel extends Observable { } showNativeBrowserNotification({ - title: `RUN ${runNumber ?? 'unknown'} has started`, + title: `RUN ${runNumber ?? 'unknown'} has started. Click here to enter RunMode`, onclick: () => { - // On notification click we always navigate to the `objectTree` page. - // Additionally, we view the run using the given `runNumber`. - this.model.router.go(`?page=objectTree&RunNumber=${runNumber}`); - + this.model.filterModel.setFilterValue('RunNumber', runNumber?.toString(), false); // If RunMode is not activated, we should enable it const { isRunModeActivated } = this.model.filterModel; if (!isRunModeActivated) { this.model.filterModel.activateRunsMode(this.model.filterModel.getPageTargetModel()); } - - // We select the given `runNumber` in RunMode. - // We do not have to set the parameter in the URL, as this is already achieved on navigation. - this.model.filterModel.setFilterValue('RunNumber', runNumber?.toString()); + this.model.router.go(`?page=objectTree&RunNumber=${runNumber}`); }, }); } From 804390ee1af6cee8052b4e9ecbf53af17a6ef90c Mon Sep 17 00:00:00 2001 From: George Raduta Date: Mon, 19 Jan 2026 17:21:38 +0100 Subject: [PATCH 3/6] Filter is updated on router load, thus no need to update again Fix backend tests --- .../common/library/enums/transition.enum.js | 1 + QualityControl/lib/services/RunModeService.js | 2 +- .../model/NotificationRunStartModel.js | 5 ++--- .../test/lib/services/RunModeService.test.js | 16 +++++++++++----- .../services/external/AliEcsSynchronizer.test.js | 7 ++++--- 5 files changed, 19 insertions(+), 12 deletions(-) diff --git a/QualityControl/common/library/enums/transition.enum.js b/QualityControl/common/library/enums/transition.enum.js index 3dbaccde1..ae12c4449 100644 --- a/QualityControl/common/library/enums/transition.enum.js +++ b/QualityControl/common/library/enums/transition.enum.js @@ -18,6 +18,7 @@ * @readonly */ export const Transition = Object.freeze({ + NULL: 'NULL', // custom QCG value for no transition START_ACTIVITY: 'START_ACTIVITY', STOP_ACTIVITY: 'STOP_ACTIVITY', }); diff --git a/QualityControl/lib/services/RunModeService.js b/QualityControl/lib/services/RunModeService.js index 47d07e680..4bceb6e6e 100644 --- a/QualityControl/lib/services/RunModeService.js +++ b/QualityControl/lib/services/RunModeService.js @@ -141,7 +141,7 @@ export class RunModeService { * @param {string} runEvent.transitionStatus - The status of the transition (e.g., 'DONE_OK'). * @returns {Promise} */ - async _onRunTrackEvent({ runNumber, transition, transitionStatus }) { + async _onRunTrackEvent({ runNumber, transition = Transition.NULL, transitionStatus = TransitionStatus.NULL }) { if (transition === Transition.START_ACTIVITY && transitionStatus === TransitionStatus.DONE_OK) { await this._initializeRunData(runNumber); diff --git a/QualityControl/public/common/notifications/model/NotificationRunStartModel.js b/QualityControl/public/common/notifications/model/NotificationRunStartModel.js index 70ed4fc81..d5e2a0776 100644 --- a/QualityControl/public/common/notifications/model/NotificationRunStartModel.js +++ b/QualityControl/public/common/notifications/model/NotificationRunStartModel.js @@ -84,13 +84,12 @@ export default class NotificationRunStartModel extends Observable { showNativeBrowserNotification({ title: `RUN ${runNumber ?? 'unknown'} has started. Click here to enter RunMode`, onclick: () => { - this.model.filterModel.setFilterValue('RunNumber', runNumber?.toString(), false); - // If RunMode is not activated, we should enable it + this.model.router.go(`?page=objectTree&RunNumber=${runNumber}`); + const { isRunModeActivated } = this.model.filterModel; if (!isRunModeActivated) { this.model.filterModel.activateRunsMode(this.model.filterModel.getPageTargetModel()); } - this.model.router.go(`?page=objectTree&RunNumber=${runNumber}`); }, }); } diff --git a/QualityControl/test/lib/services/RunModeService.test.js b/QualityControl/test/lib/services/RunModeService.test.js index c28f81341..d28fa9baf 100644 --- a/QualityControl/test/lib/services/RunModeService.test.js +++ b/QualityControl/test/lib/services/RunModeService.test.js @@ -19,7 +19,7 @@ import sinon from 'sinon'; import { RunModeService } from '../../../lib/services/RunModeService.js'; import { RunStatus } from '../../../common/library/runStatus.enum.js'; import { EmitterKeys } from '../../../common/library/enums/emitterKeys.enum.js'; -import { Transition } from '../../../common/library/enums/transition.enum.js'; +import { Transition, TransitionStatus } from '../../../common/library/enums/transition.enum.js'; import { delayAndCheck } from '../../testUtils/delay.js'; import { WebSocketMessage } from '@aliceo2/web-ui'; @@ -152,7 +152,7 @@ export const runModeServiceTestSuite = async () => { suite('_onRunTrackEvent - test suite', () => { test('should correctly parse event to RUN_TRACK and update ongoing runs map', async () => { - const runEvent = { runNumber: 1234, transition: 'START_ACTIVITY' }; + const runEvent = { runNumber: 1234, transition: 'START_ACTIVITY', transitionStatus: TransitionStatus.DONE_OK }; runModeService._dataService.getObjectsLatestVersionList = sinon.stub().resolves([{ path: '/path/from/event' }]); await runModeService._onRunTrackEvent(runEvent); @@ -164,7 +164,9 @@ export const runModeServiceTestSuite = async () => { }); test('should listen to events on RUN_TRACK and update ongoing runs map', async () => { - const runEvent = { runNumber: 1234, transition: Transition.START_ACTIVITY }; + const runEvent = { + runNumber: 1234, transition: Transition.START_ACTIVITY, transitionStatus: TransitionStatus.DONE_OK, + }; runModeService._dataService.getObjectsLatestVersionList = sinon.stub().resolves([{ path: '/path/from/event' }]); eventEmitter.emit(EmitterKeys.RUN_TRACK, runEvent); @@ -177,7 +179,9 @@ export const runModeServiceTestSuite = async () => { test('should listen to events on RUN_TRACK and broadcast to websocket', async () => { const runNumber = 1234; - const runEvent = { runNumber, transition: Transition.START_ACTIVITY }; + const runEvent = { + runNumber, transition: Transition.START_ACTIVITY, transitionStatus: TransitionStatus.DONE_OK, + }; runModeService._dataService.getObjectsLatestVersionList = sinon.stub().resolves([{ path: '/path/from/event' }]); eventEmitter.emit(EmitterKeys.RUN_TRACK, runEvent); @@ -194,7 +198,9 @@ export const runModeServiceTestSuite = async () => { }); test('should remove run from ongoing runs map on STOP_ACTIVITY event', async () => { - const runEventStop = { runNumber: 5678, transition: Transition.STOP_ACTIVITY }; + const runEventStop = { + runNumber: 5678, transition: Transition.STOP_ACTIVITY, transitionStatus: TransitionStatus.DONE_OK, + }; runModeService._ongoingRuns.set(runEventStop.runNumber, [{ path: '/some/path' }]); eventEmitter.emit(EmitterKeys.RUN_TRACK, runEventStop); diff --git a/QualityControl/test/lib/services/external/AliEcsSynchronizer.test.js b/QualityControl/test/lib/services/external/AliEcsSynchronizer.test.js index 630925b67..a04ed782d 100644 --- a/QualityControl/test/lib/services/external/AliEcsSynchronizer.test.js +++ b/QualityControl/test/lib/services/external/AliEcsSynchronizer.test.js @@ -16,7 +16,7 @@ import { ok, deepStrictEqual } from 'node:assert'; import { test, beforeEach, afterEach } from 'node:test'; import { stub, restore } from 'sinon'; import { AliEcsSynchronizer } from '../../../../lib/services/external/AliEcsSynchronizer.js'; -import { Transition } from '../../../../common/library/enums/transition.enum.js'; +import { Transition, TransitionStatus } from '../../../../common/library/enums/transition.enum.js'; import { EmitterKeys } from '../../../../common/library/enums/emitterKeys.enum.js'; export const aliecsSynchronizerTestSuite = async () => { @@ -50,13 +50,14 @@ export const aliecsSynchronizerTestSuite = async () => { test('should emit a run track event when a valid run message is received', () => { const runNumber = 123; const transition = Transition.START_ACTIVITY; + const transitionStatus = TransitionStatus.DONE_OK; const fixedTimestamp = Date.now(); const timestamp = { toNumber: () => fixedTimestamp }; - aliecsSynchronizer._onRunMessage({ runEvent: { runNumber, transition }, timestamp }); + aliecsSynchronizer._onRunMessage({ runEvent: { runNumber, transition, transitionStatus }, timestamp }); ok(eventEmitterMock.emit.called); deepStrictEqual(eventEmitterMock.emit.firstCall.args[0], EmitterKeys.RUN_TRACK); deepStrictEqual(eventEmitterMock.emit.firstCall.args[1], { - runNumber, transition, timestamp: timestamp.toNumber(), + runNumber, transition, transitionStatus, timestamp: timestamp.toNumber(), }); }); }; From 5ea5960786a9a701d3b7856a18c54f9212257f6a Mon Sep 17 00:00:00 2001 From: George Raduta Date: Mon, 19 Jan 2026 17:26:30 +0100 Subject: [PATCH 4/6] Update kafka event to mock --- QualityControl/test/setup/mockKafkaEvents.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/QualityControl/test/setup/mockKafkaEvents.js b/QualityControl/test/setup/mockKafkaEvents.js index f656bbaf0..f7fcad0fc 100644 --- a/QualityControl/test/setup/mockKafkaEvents.js +++ b/QualityControl/test/setup/mockKafkaEvents.js @@ -13,11 +13,12 @@ */ import { EmitterKeys } from '../../common/library/enums/emitterKeys.enum.js'; -import { Transition } from '../../common/library/enums/transition.enum.js'; +import { Transition, TransitionStatus } from '../../common/library/enums/transition.enum.js'; /** * Mock Kafka events for testing purposes * @param {EventEmitter} eventEmitter - Event emitter to emit mock events + * @returns {Array} - Array of mock ongoing run numbers */ export const setupMockKafkaEvents = (eventEmitter) => { // Simulate some ongoing runs being started @@ -29,10 +30,11 @@ export const setupMockKafkaEvents = (eventEmitter) => { eventEmitter.emit(EmitterKeys.RUN_TRACK, { runNumber: parseInt(runNumber, 10), transition: Transition.START_ACTIVITY, + transitionStatus: TransitionStatus.DONE_OK, timestamp: Date.now(), }); }); - }, 100); + }, 500); return mockOngoingRuns; }; From 739391adc67987d663c6a2fcd79744bea5f4789d Mon Sep 17 00:00:00 2001 From: George Raduta Date: Mon, 19 Jan 2026 17:44:50 +0100 Subject: [PATCH 5/6] Use an actual mocked ongoing run number --- QualityControl/test/public/components/profileHeader.test.js | 4 ++-- QualityControl/test/setup/mockKafkaEvents.js | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/QualityControl/test/public/components/profileHeader.test.js b/QualityControl/test/public/components/profileHeader.test.js index e269b9e94..d9fe1abea 100644 --- a/QualityControl/test/public/components/profileHeader.test.js +++ b/QualityControl/test/public/components/profileHeader.test.js @@ -19,6 +19,7 @@ import { getLocalStorageAsJson } from '../../testUtils/localStorage.js'; import { IntegratedServices } from '../../../common/library/enums/Status/integratedServices.enum.js'; import { ServiceStatus } from '../../../common/library/enums/Status/serviceStatus.enum.js'; import { integratedServiceInterceptor } from '../../testUtils/interceptors/integratedServiceInterceptor.js'; +import { ONGOING_RUN_NUMBER } from '../../setup/mockKafkaEvents.js'; /** * Performs a series of automated tests on the layoutList page using Puppeteer. @@ -199,8 +200,6 @@ export const profileHeaderTests = async (url, page, timeout = 1000, testParent) }); await testParent.test('should enable RunMode when browser notification is clicked', { timeout }, async () => { - const RUN_NUMBER = 1234; - /* * Kafka must be enabled for the browser notification feature to function correctly. * We intercept the request and return a SUCCESS state of the kafka service. @@ -261,6 +260,7 @@ export const profileHeaderTests = async (url, page, timeout = 1000, testParent) window.Notification = MockNotification; }); + const RUN_NUMBER = ONGOING_RUN_NUMBER; // Trigger native browser notification by simulating websocket message await page.evaluate( (wsMessage) => window.model.notificationRunStartModel._handleWSRunTrack(wsMessage), diff --git a/QualityControl/test/setup/mockKafkaEvents.js b/QualityControl/test/setup/mockKafkaEvents.js index f7fcad0fc..5ee5fa173 100644 --- a/QualityControl/test/setup/mockKafkaEvents.js +++ b/QualityControl/test/setup/mockKafkaEvents.js @@ -15,6 +15,8 @@ import { EmitterKeys } from '../../common/library/enums/emitterKeys.enum.js'; import { Transition, TransitionStatus } from '../../common/library/enums/transition.enum.js'; +export const ONGOING_RUN_NUMBER = 500001; + /** * Mock Kafka events for testing purposes * @param {EventEmitter} eventEmitter - Event emitter to emit mock events @@ -22,7 +24,7 @@ import { Transition, TransitionStatus } from '../../common/library/enums/transit */ export const setupMockKafkaEvents = (eventEmitter) => { // Simulate some ongoing runs being started - const mockOngoingRuns = ['500001', '500002', '500003']; + const mockOngoingRuns = [ONGOING_RUN_NUMBER.toString(), '500002', '500003']; // Emit START_ACTIVITY events for mock runs after a short delay setTimeout(() => { From 50eb1542e0806f83ef25cb560944af20e1b3d9f5 Mon Sep 17 00:00:00 2001 From: George Raduta Date: Mon, 19 Jan 2026 18:05:23 +0100 Subject: [PATCH 6/6] Improve test --- .../test/public/features/runMode.test.js | 10 +++---- QualityControl/test/setup/mockKafkaEvents.js | 11 +++----- QualityControl/test/setup/testSetupForBkp.js | 27 ++++++++++++++++--- 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/QualityControl/test/public/features/runMode.test.js b/QualityControl/test/public/features/runMode.test.js index 6bc71744e..9d8bada41 100644 --- a/QualityControl/test/public/features/runMode.test.js +++ b/QualityControl/test/public/features/runMode.test.js @@ -12,16 +12,17 @@ */ /* eslint-disable @stylistic/js/max-len */ -import { strictEqual, ok } from 'node:assert'; +import { strictEqual, ok, deepStrictEqual } from 'node:assert'; import { delay } from '../../testUtils/delay.js'; import { IntegratedServices } from '../../../common/library/enums/Status/integratedServices.enum.js'; import { ServiceStatus } from '../../../common/library/enums/Status/serviceStatus.enum.js'; import { integratedServiceInterceptor } from '../../testUtils/interceptors/integratedServiceInterceptor.js'; +import { ONGOING_RUN_NUMBER } from '../../setup/mockKafkaEvents.js'; // If using nock for HTTP mocking (uncomment if available) // import nock from 'nock'; export const runModeTests = async (url, page, timeout = 5000, testParent) => { - const mockedTestRunNumber = 500001; + const mockedTestRunNumber = ONGOING_RUN_NUMBER; let countOngoingRunsCalls = 0; let countRunStatusCalls = 0; let expectCountRunStatusCalls = 0; @@ -185,10 +186,7 @@ export const runModeTests = async (url, page, timeout = 5000, testParent) => { .filter((value) => value !== ''); }); - ok(availableOptions.length > 0, 'Should have ongoing runs available in selector'); - ['500001', '500002', '500003'].forEach((run) => { - ok(availableOptions.includes(run), `Should include mock run ${run}`); - }); + deepStrictEqual(availableOptions, ['500001', '500002', '500003'], 'Ongoing runs selector should have correct options'); }); await testParent.test('should automatically select first run and update URL', { timeout }, async () => { diff --git a/QualityControl/test/setup/mockKafkaEvents.js b/QualityControl/test/setup/mockKafkaEvents.js index 5ee5fa173..9a979f7ad 100644 --- a/QualityControl/test/setup/mockKafkaEvents.js +++ b/QualityControl/test/setup/mockKafkaEvents.js @@ -16,19 +16,16 @@ import { EmitterKeys } from '../../common/library/enums/emitterKeys.enum.js'; import { Transition, TransitionStatus } from '../../common/library/enums/transition.enum.js'; export const ONGOING_RUN_NUMBER = 500001; +export const ONGOING_RUNS_LIST = [ONGOING_RUN_NUMBER, 500002, 500003]; /** * Mock Kafka events for testing purposes * @param {EventEmitter} eventEmitter - Event emitter to emit mock events - * @returns {Array} - Array of mock ongoing run numbers */ export const setupMockKafkaEvents = (eventEmitter) => { - // Simulate some ongoing runs being started - const mockOngoingRuns = [ONGOING_RUN_NUMBER.toString(), '500002', '500003']; - // Emit START_ACTIVITY events for mock runs after a short delay setTimeout(() => { - mockOngoingRuns.forEach((runNumber) => { + ONGOING_RUNS_LIST.forEach((runNumber) => { eventEmitter.emit(EmitterKeys.RUN_TRACK, { runNumber: parseInt(runNumber, 10), transition: Transition.START_ACTIVITY, @@ -36,7 +33,5 @@ export const setupMockKafkaEvents = (eventEmitter) => { timestamp: Date.now(), }); }); - }, 500); - - return mockOngoingRuns; + }, 200); }; diff --git a/QualityControl/test/setup/testSetupForBkp.js b/QualityControl/test/setup/testSetupForBkp.js index e6caee2ee..41fa94835 100644 --- a/QualityControl/test/setup/testSetupForBkp.js +++ b/QualityControl/test/setup/testSetupForBkp.js @@ -16,6 +16,7 @@ import nock from 'nock'; import { config } from '../config.js'; import { BKP_MOCK_DATA } from './seeders/bkp-mock-data.js'; import { GET_BKP_GUI_STATUS_PATH } from '../../lib/services/BookkeepingService.js'; +import { ONGOING_RUN_NUMBER } from './mockKafkaEvents.js'; const BKP_URL = `${config.bookkeeping.url}`; const TOKEN_PATH = `?token=${config.bookkeeping.token}`; @@ -160,25 +161,43 @@ export const initializeNockForBkp = () => { }, }); nock(BKP_URL) - .get(`/api/runs/500001${TOKEN_PATH}`) + .get(`/api/runs/${ONGOING_RUN_NUMBER}${TOKEN_PATH}`) .reply(200, { data: { timeO2End: null, }, }) - .get(`/api/runs/500001${TOKEN_PATH}`) + .get(`/api/runs/${ONGOING_RUN_NUMBER}${TOKEN_PATH}`) .reply(200, { data: { timeO2End: null, }, }) - .get(`/api/runs/500001${TOKEN_PATH}`) + .get(`/api/runs/${ONGOING_RUN_NUMBER}${TOKEN_PATH}`) + .reply(200, { + data: { + timeO2End: null, + }, + }) + .get(`/api/runs/${ONGOING_RUN_NUMBER}${TOKEN_PATH}`) + .reply(200, { + data: { + timeO2End: null, + }, + }) + .get(`/api/runs/${ONGOING_RUN_NUMBER}${TOKEN_PATH}`) + .reply(200, { + data: { + timeO2End: null, + }, + }) + .get(`/api/runs/${ONGOING_RUN_NUMBER}${TOKEN_PATH}`) .reply(200, { data: { timeO2End: '2023-12-01T10:30:00Z', }, }) - .get(`/api/runs/500001${TOKEN_PATH}`) + .get(`/api/runs/${ONGOING_RUN_NUMBER}${TOKEN_PATH}`) .reply(200, { data: { timeO2End: '2023-12-01T10:30:00Z',