Skip to content

Commit

Permalink
fix: allow to instantly refresh permissions when they change (#1008)
Browse files Browse the repository at this point in the history
  • Loading branch information
Thenkei committed Jun 2, 2023
1 parent 2f50e9e commit b5db483
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 8 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
23 changes: 21 additions & 2 deletions src/context/build-services.js
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/context/service-builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
3 changes: 3 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
50 changes: 50 additions & 0 deletions test/context/index.test.js
Original file line number Diff line number Diff line change
@@ -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();
});
});
});
});
5 changes: 5 additions & 0 deletions test/helpers/create-server.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down Expand Up @@ -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();
Expand Down
41 changes: 41 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down
8 changes: 4 additions & 4 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down

0 comments on commit b5db483

Please sign in to comment.