From b5db483116d574a7c2b168029d4d47103b77f887 Mon Sep 17 00:00:00 2001 From: Morgan PERRE Date: Fri, 2 Jun 2023 09:56:09 +0200 Subject: [PATCH] fix: allow to instantly refresh permissions when they change (#1008) --- package.json | 2 +- src/context/build-services.js | 23 ++++++++++++++-- src/context/service-builder.js | 2 +- src/index.js | 3 ++ test/context/index.test.js | 50 ++++++++++++++++++++++++++++++++++ test/helpers/create-server.js | 5 ++++ test/index.test.js | 41 ++++++++++++++++++++++++++++ yarn.lock | 8 +++--- 8 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 test/context/index.test.js diff --git a/package.json b/package.json index 4399eb0c0..5772bd748 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "dependencies": { "@babel/runtime": "7.19.0", "@forestadmin/context": "1.31.0", - "@forestadmin/forestadmin-client": "1.2.0", + "@forestadmin/forestadmin-client": "1.2.1", "base32-encode": "1.1.1", "bitwise-xor": "0.0.0", "bluebird": "3.7.1", diff --git a/src/context/build-services.js b/src/context/build-services.js index 1eb625b54..c689ec6d1 100644 --- a/src/context/build-services.js +++ b/src/context/build-services.js @@ -1,13 +1,32 @@ /* eslint-disable global-require */ const createForestAdminClient = require('@forestadmin/forestadmin-client').default; -module.exports = (context) => +const loggerLevels = { + Error: 0, + Warn: 1, + Info: 2, + Debug: 3, +}; + +function makeLogger({ env, logger }) { + return (level, ...args) => { + const loggerLevel = env.FOREST_LOGGER_LEVEL ?? 'Info'; + + if (loggerLevels[level] <= loggerLevels[loggerLevel]) { + logger[level.toLowerCase()](...args); + } + }; +} + +module.exports.makeLogger = makeLogger; + +module.exports.default = (context) => context .addInstance('logger', () => require('../services/logger')) .addUsingFunction('forestAdminClient', ({ env, forestUrl, logger }) => createForestAdminClient({ envSecret: env.FOREST_ENV_SECRET, forestServerUrl: forestUrl, - logger: (level, ...args) => (env.DEBUG ? logger[level.toLowerCase()](...args) : {}), + logger: makeLogger({ env, logger }), instantCacheRefresh: true, })) .addInstance('chartHandler', ({ forestAdminClient }) => forestAdminClient.chartHandler) diff --git a/src/context/service-builder.js b/src/context/service-builder.js index 9931aacb7..87c9e92bf 100644 --- a/src/context/service-builder.js +++ b/src/context/service-builder.js @@ -3,4 +3,4 @@ module.exports = (plan) => plan .addPackage('externals', require('./build-external')) .addPackage('values', require('./build-values')) .addPackage('utils', require('./build-utils')) - .addPackage('services', require('./build-services')); + .addPackage('services', require('./build-services').default); diff --git a/src/index.js b/src/index.js index 9f7527d6c..821b90cfd 100644 --- a/src/index.js +++ b/src/index.js @@ -375,6 +375,9 @@ exports.init = async (Implementation) => { configStore.lianaOptions.expressParentApp.use('/forest', app); } + // Server events - Used to refresh permissions + await inject().forestAdminClient.subscribeToServerEvents(); + return app; } catch (error) { reportSchemaComputeError(error); diff --git a/test/context/index.test.js b/test/context/index.test.js new file mode 100644 index 000000000..9ff938f65 --- /dev/null +++ b/test/context/index.test.js @@ -0,0 +1,50 @@ +const { makeLogger } = require('../../src/context/build-services'); + +describe('context > build-services', () => { + describe('makeLogger', () => { + it('should create logger with default value (log level Info)', async () => { + const env = {}; + const logger = { + info: jest.fn(), + }; + + const internalLogger = makeLogger({ env, logger }); + + internalLogger('Info', 'Message'); + + expect(logger.info).toHaveBeenCalledOnce(); + expect(logger.info).toHaveBeenCalledWith('Message'); + }); + + describe('when FOREST_LOGGER_LEVEL is equal or higher than the actual log level', () => { + it('should log the message', async () => { + const env = { FOREST_LOGGER_LEVEL: 'Debug' }; + const logger = { + debug: jest.fn(), + }; + + const internalLogger = makeLogger({ env, logger }); + + internalLogger('Debug', 'Message'); + + expect(logger.debug).toHaveBeenCalledOnce(); + expect(logger.debug).toHaveBeenCalledWith('Message'); + }); + }); + + describe('when FOREST_LOGGER_LEVEL is lower than the actual log level', () => { + it('should not log anything', async () => { + const env = { FOREST_LOGGER_LEVEL: 'Error' }; + const logger = { + info: jest.fn(), + }; + + const internalLogger = makeLogger({ env, logger }); + + internalLogger('Info', 'Message'); + + expect(logger.info).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/test/helpers/create-server.js b/test/helpers/create-server.js index 6985a51e2..c683dec0f 100644 --- a/test/helpers/create-server.js +++ b/test/helpers/create-server.js @@ -1,6 +1,8 @@ const express = require('express'); const bodyParser = require('body-parser'); const { expressjwt: jwt } = require('express-jwt'); +const { inject } = require('@forestadmin/context'); + const forestExpress = require('../../src'); const { getJWTConfiguration } = require('../../src/config/jwt'); const request = require('./request'); @@ -37,6 +39,9 @@ module.exports = async function createServer(envSecret, authSecret) { implementation.getOrmVersion = () => {}; implementation.getDatabaseType = () => {}; + // We don't want to subscribe Server Events in tests + jest.spyOn(inject().forestAdminClient, 'subscribeToServerEvents').mockResolvedValue(); + const forestApp = await forestExpress.init(implementation); app.use(forestApp); request.init(); diff --git a/test/index.test.js b/test/index.test.js index 38a5c462e..0bf9fd133 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -5,10 +5,21 @@ const fs = require('fs'); const request = require('supertest'); const express = require('express'); +const mockSubscribeToServerEvents = jest.fn().mockResolvedValue(); +const options = {}; +const mockForestAdminClient = jest.fn().mockReturnValue({ + options, + subscribeToServerEvents: mockSubscribeToServerEvents, +}); + jest.mock('require-all', () => jest.fn()); +jest.mock('@forestadmin/forestadmin-client', () => ({ + default: mockForestAdminClient, +})); function resetRequireIndex(mockExtraModules) { jest.resetModules(); + jest.clearAllMocks(); let modules; if (mockExtraModules) { @@ -99,6 +110,36 @@ describe('liana > index', () => { expect(response.status).not.toBe(404); }); + it('should build forestAdminClient and change its options', async () => { + const forestExpress = resetRequireIndex(); + const implementation = createFakeImplementation(); + + implementation.opts.envSecret = 'envSecret'; + await forestExpress.init(implementation); + + expect(mockForestAdminClient).toHaveBeenCalledOnce(); + expect(mockForestAdminClient).toHaveBeenCalledWith({ + // Take the value from the environment variable first + envSecret: undefined, + forestServerUrl: 'https://api.forestadmin.com', + logger: expect.any(Function), + instantCacheRefresh: true, + }); + // Then updates ForestAdminClient options at runtime + expect(options).toStrictEqual({ + envSecret: 'envSecret', + }); + }); + + it('should subscribe to server events', async () => { + const forestExpress = resetRequireIndex(); + const implementation = createFakeImplementation(); + + await forestExpress.init(implementation); + + expect(mockSubscribeToServerEvents).toHaveBeenCalledOnce(); + }); + describe('when `Liana.init` is called twice', () => { it('should return the same express app', async () => { const forestExpress = resetRequireIndex(); diff --git a/yarn.lock b/yarn.lock index 1ea7963de..3089b29e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1246,10 +1246,10 @@ resolved "https://registry.yarnpkg.com/@forestadmin/context/-/context-1.31.0.tgz#b4b5a3b589e52d337a1f45807db22c2860e640a7" integrity sha512-RQkDBkq+6ySMv+YNezz9VWSyCsqD7fj/+bXrXhQ6lJ62nbRUIUheH7ApvXwfnwFR1u55oT6Yhar11t6DaiE9Ig== -"@forestadmin/forestadmin-client@1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@forestadmin/forestadmin-client/-/forestadmin-client-1.2.0.tgz#39d80c25eebf155bf3bb0a05db110e31e512f966" - integrity sha512-I6TgfDUuTDOEYXE35eXLbXDOIj32ii0TbKBWXkkSFAE+pEKYU6q4O9XEupOk6iKvnS9D0O1RIdGjNuySf8qPRw== +"@forestadmin/forestadmin-client@1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@forestadmin/forestadmin-client/-/forestadmin-client-1.2.1.tgz#5ee07fa73096ae55880d1d19cae449c4fb182ba6" + integrity sha512-4ZvvERcH2IDsWOWrmN0SBx88aFvfj6vJt1xTGIrwXkGY/agod7TH5kn9XqbYtmyOxLApmwrd5+08Bq6t4mDKug== dependencies: eventsource "2.0.2" json-api-serializer "^2.6.6"