diff --git a/lib/application.js b/lib/application.js index 343425c0ab..f5b4c35a39 100644 --- a/lib/application.js +++ b/lib/application.js @@ -65,6 +65,20 @@ class BookkeepingApplication { } } + /** + * Connects to the database and applies pending migrations, then, and only then, starts the application. + * Servers, consumers and synchronizers must not access the database while migrations are being applied + * + * @returns {Promise} resolves with true if the application has been started, false if the database could not be prepared + */ + async start() { + if (!await this.connectDatabase()) { + return false; + } + await this.run(); + return true; + } + /** * Causes the application to be scheduled for execution. * diff --git a/lib/main.js b/lib/main.js index 31d647f052..09f8d63737 100644 --- a/lib/main.js +++ b/lib/main.js @@ -13,8 +13,12 @@ const application = require('./application'); -application.run(); -application.connectDatabase(); +application.start().then((started) => { + if (!started) { + // Nothing has been started yet (database unavailable or migration failed), there is nothing to stop gracefully + process.exit(1); + } +}); /* * 'SIGTERM' and 'SIGINT' have default handlers on non-Windows platforms that reset the terminal mode before exiting diff --git a/test/index.js b/test/index.js index 29b76dfd1f..ca5e4a1ae4 100644 --- a/test/index.js +++ b/test/index.js @@ -11,6 +11,7 @@ * or submit itself to any jurisdiction. */ +const ApplicationSuite = require('./lib/application.test.js'); const DatabaseSuite = require('./lib/database'); const PresentationSuite = require('./presentation'); const PublicSuite = require('./lib/public'); @@ -22,19 +23,13 @@ const GrpcSuite = require('./gRPC'); const APISuite = require('./api'); const FrontendSuite = require('./public'); -const application = require('../lib/application'); +const { useApplication } = require('./utilities/useApplication.js'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('Unit Suite', () => { + describe('Application', ApplicationSuite); describe('Database', DatabaseSuite); describe('Presentation', PresentationSuite); describe('Public', PublicSuite); diff --git a/test/lib/application.test.js b/test/lib/application.test.js new file mode 100644 index 0000000000..a0e50b4076 --- /dev/null +++ b/test/lib/application.test.js @@ -0,0 +1,57 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const sinon = require('sinon'); +const { expect } = require('chai'); +const application = require('../../lib/application'); + +module.exports = () => { + describe('Application start-up', () => { + let connectDatabaseStub; + let runStub; + let resolveMigration; + + beforeEach(() => { + runStub = sinon.stub(application, 'run').resolves(); + connectDatabaseStub = sinon.stub(application, 'connectDatabase'); + }); + + afterEach(() => { + sinon.restore(); + }); + + it('should not start anything (servers, synchronizers...) while migrations are still running', async () => { + connectDatabaseStub.returns(new Promise((resolve) => { + resolveMigration = resolve; + })); + + const startPromise = application.start(); + + // Let any (wrongly) scheduled work run + await new Promise((resolve) => setImmediate(resolve)); + expect(runStub.called).to.be.false; + + resolveMigration(true); + expect(await startPromise).to.be.true; + expect(runStub.calledOnce).to.be.true; + expect(runStub.calledAfter(connectDatabaseStub)).to.be.true; + }); + + it('should never start the application if the database could not be prepared', async () => { + connectDatabaseStub.resolves(false); + + expect(await application.start()).to.be.false; + expect(runStub.called).to.be.false; + }); + }); +}; diff --git a/test/scripts/test-about.js b/test/scripts/test-about.js index 11330016b5..bf75ecba33 100644 --- a/test/scripts/test-about.js +++ b/test/scripts/test-about.js @@ -11,18 +11,11 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const OverviewSuite = require('../public/about/overview.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('About', () => { describe('Overview Page', OverviewSuite); diff --git a/test/scripts/test-api.js b/test/scripts/test-api.js index cfecca08f2..79b42d8398 100644 --- a/test/scripts/test-api.js +++ b/test/scripts/test-api.js @@ -13,17 +13,10 @@ const APISuite = require('../api'); -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('API', APISuite); }); diff --git a/test/scripts/test-data-passes.js b/test/scripts/test-data-passes.js index 9dcd517d12..5e8d9c0cec 100644 --- a/test/scripts/test-data-passes.js +++ b/test/scripts/test-data-passes.js @@ -11,19 +11,12 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const OverviewPerLhcPeriodSuite = require('../public/dataPasses/overviewPerLhcPeriod.test'); const OverviewPerSimulationPassSuite = require('../public/dataPasses/overviewPerSimulationPass.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('DataPasses', () => { describe('Overview Per LHC Period Page', OverviewPerLhcPeriodSuite); diff --git a/test/scripts/test-envs.js b/test/scripts/test-envs.js index b22fba5f86..2434bfb1ad 100644 --- a/test/scripts/test-envs.js +++ b/test/scripts/test-envs.js @@ -11,19 +11,12 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const OverviewSuite = require('../public/envs/overview.test'); const DetailsPageSuite = require('../public/envs/detailsPage.test.js'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('Envs', () => { describe('Overview Page', OverviewSuite); diff --git a/test/scripts/test-eos-report.js b/test/scripts/test-eos-report.js index 675382abc1..9ac5a49836 100644 --- a/test/scripts/test-eos-report.js +++ b/test/scripts/test-eos-report.js @@ -11,7 +11,7 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const DcsCreationSuite = require('../public/eosReport/dcs-creation.test.js'); const EcsCreationSuite = require('../public/eosReport/ecs-creation.test.js'); const QcPdpCreationSuite = require('../public/eosReport/qc-pdp-creation.test.js'); @@ -19,14 +19,7 @@ const ShiftLeaderCreationSuite = require('../public/eosReport/shift-leader-creat const SlimosCreationSuite = require('../public/eosReport/slimos-creation.test.js'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('EosReport', () => { describe('DCS Creation Page', DcsCreationSuite); diff --git a/test/scripts/test-flps.js b/test/scripts/test-flps.js index 45c5096f00..53e4d11b81 100644 --- a/test/scripts/test-flps.js +++ b/test/scripts/test-flps.js @@ -11,20 +11,13 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const OverviewSuite = require('../public/flps/overview.test'); // eslint-disable-next-line capitalized-comments const DetailSuite = require('../public/flps/detail.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('Flps', () => { describe('Overview Page', OverviewSuite); diff --git a/test/scripts/test-header.js b/test/scripts/test-header.js index 289705b445..07531dfe82 100644 --- a/test/scripts/test-header.js +++ b/test/scripts/test-header.js @@ -11,18 +11,11 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const HeaderSuite = require('../public/header'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('Header', HeaderSuite); }); diff --git a/test/scripts/test-home.js b/test/scripts/test-home.js index 352bb474cd..b78cf99f86 100644 --- a/test/scripts/test-home.js +++ b/test/scripts/test-home.js @@ -11,18 +11,11 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const OverviewSuite = require('../public/home/overview.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('Home', () => { describe('Overview Page', OverviewSuite); diff --git a/test/scripts/test-lhc-fills.js b/test/scripts/test-lhc-fills.js index b9d24e1314..2c88c2b490 100644 --- a/test/scripts/test-lhc-fills.js +++ b/test/scripts/test-lhc-fills.js @@ -11,19 +11,12 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const OverviewSuite = require('../public/lhcFills/overview.test'); const DetailsSuite = require('../public/lhcFills/detail.test.js'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('LhcFills', () => { describe('Overview Page', OverviewSuite); diff --git a/test/scripts/test-lhc-periods.js b/test/scripts/test-lhc-periods.js index 955fc59948..cb36b565a2 100644 --- a/test/scripts/test-lhc-periods.js +++ b/test/scripts/test-lhc-periods.js @@ -11,18 +11,11 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const OverviewSuite = require('../public/lhcPeriods/overview.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('lhcPeriods', () => { describe('Overview Page', OverviewSuite); diff --git a/test/scripts/test-logs.js b/test/scripts/test-logs.js index 8adcefdb65..9aa5173a48 100644 --- a/test/scripts/test-logs.js +++ b/test/scripts/test-logs.js @@ -11,20 +11,13 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const OverviewSuite = require('../public/logs/overview.test'); const CreateSuite = require('../public/logs/create.test'); const DetailSuite = require('../public/logs/detail.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('Logs', () => { describe('Overview Page', OverviewSuite); diff --git a/test/scripts/test-qc-flag-types.js b/test/scripts/test-qc-flag-types.js index 0a78e9e898..101adca34b 100644 --- a/test/scripts/test-qc-flag-types.js +++ b/test/scripts/test-qc-flag-types.js @@ -11,19 +11,12 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const QcFlagTypesOverviewSuite = require('../public/qcFlagTypes/overview.test'); const QcFlagTypeCreationSuite = require('../public/qcFlagTypes/creation.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('QcFlagTypes', () => { describe('Overview Page', QcFlagTypesOverviewSuite); diff --git a/test/scripts/test-qc-flags.js b/test/scripts/test-qc-flags.js index 23b0b2f3cf..8dd74b37ed 100644 --- a/test/scripts/test-qc-flags.js +++ b/test/scripts/test-qc-flags.js @@ -11,7 +11,7 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const SynchronousOverviewSuite = require('../public/qcFlags/synchronousOverview.test'); const ForDataPassOverviewSuite = require('../public/qcFlags/forDataPassOverview.test'); const ForSimulationPassOverviewSuite = require('../public/qcFlags/forSimulationPassOverview.test'); @@ -22,14 +22,7 @@ const DetailsForSimulationPassPageSuite = require('../public/qcFlags/detailsForS const GaqOverviewPageSuite = require('../public/qcFlags/gaqOverview.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('QcFlags', () => { describe('Details For Data Pass Page', DetailsForDataPassPageSuite); diff --git a/test/scripts/test-runs-per-period-or-pass.js b/test/scripts/test-runs-per-period-or-pass.js index 9322c7744f..62b165163f 100644 --- a/test/scripts/test-runs-per-period-or-pass.js +++ b/test/scripts/test-runs-per-period-or-pass.js @@ -11,20 +11,13 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const RunsPerPeriodOverviewSuite = require('../public/runsPerPeriodOrPass/runsPerLhcPeriod.overview.test'); const RunsPerDataPassOverviewPage = require('../public/runsPerPeriodOrPass/runsPerDataPass.overview.test'); const RunsPerSimulationPassOverviewPage = require('../public/runsPerPeriodOrPass/runsPerSimulationPass.overview.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('Runs', () => { describe('Runs Per LHC Period Overview Page', RunsPerPeriodOverviewSuite); diff --git a/test/scripts/test-runs.js b/test/scripts/test-runs.js index 81eba1b22f..bce36e8e3a 100644 --- a/test/scripts/test-runs.js +++ b/test/scripts/test-runs.js @@ -11,19 +11,12 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const OverviewSuite = require('../public/runs/overview.test'); const DetailSuite = require('../public/runs/detail.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('Runs', () => { describe('Overview Page', OverviewSuite); diff --git a/test/scripts/test-simulation-passes.js b/test/scripts/test-simulation-passes.js index d26257b97f..4657600033 100644 --- a/test/scripts/test-simulation-passes.js +++ b/test/scripts/test-simulation-passes.js @@ -11,19 +11,12 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const PerLhcPeriodOverviewSuite = require('../public/simulationPasses/overviewPerLhcPeriod.test'); const AnchoredSimulationPassesOverviewSuite = require('../public/simulationPasses/overviewAnchoredSimulationPasses.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('SimulationPasses', () => { describe('Per LHC Period Overview Page', PerLhcPeriodOverviewSuite); diff --git a/test/scripts/test-tags.js b/test/scripts/test-tags.js index 5eca7ac4eb..6a2f3f0dc9 100644 --- a/test/scripts/test-tags.js +++ b/test/scripts/test-tags.js @@ -11,20 +11,13 @@ * or submit itself to any jurisdiction. */ -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); const OverviewSuite = require('../public/tags/overview.test'); const DetailSuite = require('../public/tags/detail.test'); const CreateSuite = require('../public/tags/create.test'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('Tags', () => { describe('Overview Page', OverviewSuite); diff --git a/test/scripts/test-unit.js b/test/scripts/test-unit.js index 3192f3b578..011a8079f6 100644 --- a/test/scripts/test-unit.js +++ b/test/scripts/test-unit.js @@ -11,6 +11,7 @@ * or submit itself to any jurisdiction. */ +const ApplicationSuite = require('../lib/application.test.js'); const DatabaseSuite = require('../lib/database'); const PresentationSuite = require('../presentation'); const PublicSuite = require('../lib/public'); @@ -19,19 +20,13 @@ const UseCasesSuite = require('../lib/usecases'); const UtilitiesSuite = require('../lib/utilities'); const GrpcSuite = require('../gRPC'); -const application = require('../../lib/application'); +const { useApplication } = require('../utilities/useApplication.js'); describe('Bookkeeping', () => { - before(async () => { - await application.run(); - await application.connectDatabase(); - }); - - after(async () => { - await application.stop(true); - }); + useApplication(); describe('Unit Suite', () => { + describe('Application', ApplicationSuite); describe('Database', DatabaseSuite); describe('Presentation', PresentationSuite); describe('Public', PublicSuite); diff --git a/test/utilities/useApplication.js b/test/utilities/useApplication.js new file mode 100644 index 0000000000..abcf7c1a12 --- /dev/null +++ b/test/utilities/useApplication.js @@ -0,0 +1,32 @@ +/** + * @license + * Copyright CERN and copyright holders of ALICE O2. This software is + * distributed under the terms of the GNU General Public License v3 (GPL + * Version 3), copied verbatim in the file "COPYING". + * + * See http://alice-o2.web.cern.ch/license for full licensing information. + * + * In applying this license CERN does not waive the privileges and immunities + * granted to it by virtue of its status as an Intergovernmental Organization + * or submit itself to any jurisdiction. + */ + +const application = require('../../lib/application.js'); + +/** + * Register the hooks starting the application (migrations are applied before anything else is started) before the tests of the current + * suite, and stopping it afterwards + * + * @return {void} + */ +exports.useApplication = () => { + before(async () => { + if (!await application.start()) { + throw new Error('Application could not be started'); + } + }); + + after(async () => { + await application.stop(true); + }); +};