diff --git a/jest.config.js b/jest.config.js index 45f5a46..7d39b51 100644 --- a/jest.config.js +++ b/jest.config.js @@ -34,6 +34,7 @@ module.exports = { coveragePathIgnorePatterns: [ '/node_modules/', '/src/api-docs/', + '/src/app-server.js' ], // A list of reporter names that Jest uses when writing coverage reports diff --git a/requests/Switcher API.postman_collection.json b/requests/Switcher API.postman_collection.json index d7a2f6c..4f101f0 100644 --- a/requests/Switcher API.postman_collection.json +++ b/requests/Switcher API.postman_collection.json @@ -3882,7 +3882,7 @@ "method": "GET", "header": [], "url": { - "raw": "{{url}}/config?group=5e0ece916f4f994eac9007b0", + "raw": "{{url}}/config?group=groupid", "host": [ "{{url}}" ], @@ -3890,6 +3890,15 @@ "config" ], "query": [ + { + "key": "group", + "value": "groupid" + }, + { + "key": "fields", + "value": "key", + "disabled": true + }, { "key": "sortBy", "value": "createdAt:asc", @@ -3904,10 +3913,6 @@ "key": "skip", "value": "2", "disabled": true - }, - { - "key": "group", - "value": "5e0ece916f4f994eac9007b0" } ] } diff --git a/src/api-docs/paths/path-config.js b/src/api-docs/paths/path-config.js index d0c5b01..8bd0024 100644 --- a/src/api-docs/paths/path-config.js +++ b/src/api-docs/paths/path-config.js @@ -323,7 +323,8 @@ export default { security: [{ bearerAuth: [] }], parameters: [ ...pagination, - queryParameter('group', 'Group ID', true) + queryParameter('group', 'Group ID', true), + queryParameter('fields', 'Fields to return', false, 'string', 'E.g.: key,description,activated.default') ], responses: { '200': { diff --git a/src/app-server.js b/src/app-server.js index 0e7367d..c014fe1 100644 --- a/src/app-server.js +++ b/src/app-server.js @@ -1,6 +1,7 @@ import https from 'https'; import http from 'http'; import fs from 'fs'; +import Logger from './helpers/logger'; export const createServer = (app) => { if (process.env.SSL_CERT && process.env.SSL_KEY) { @@ -9,10 +10,10 @@ export const createServer = (app) => { cert: fs.readFileSync(process.env.SSL_CERT) }; - console.log('SSL enabled'); + Logger.info('SSL enabled'); return https.createServer(options, app); } - - console.log('SSL disabled'); + + Logger.info('SSL disabled'); return http.createServer(app); }; \ No newline at end of file diff --git a/src/exceptions/index.js b/src/exceptions/index.js index 7b75265..4ebafd5 100644 --- a/src/exceptions/index.js +++ b/src/exceptions/index.js @@ -37,7 +37,7 @@ export class FeatureUnavailableError extends Error { } export function responseException(res, err, code, feature = undefined) { - if (process.env.SWITCHER_API_LOGGER == 'true' && feature) { + if (feature) { Logger.info(`Feature [${feature}]`, { log: Switcher.getLogger(feature) }); } @@ -45,9 +45,7 @@ export function responseException(res, err, code, feature = undefined) { } export function responseExceptionSilent(res, err, code, message) { - if (process.env.SWITCHER_API_LOGGER == 'true') { - Logger.httpError(err.constructor.name, err.code, err.message, err); - } + Logger.httpError(err.constructor.name, err.code, err.message, err); if (err.code) { return res.status(err.code).send({ error: message }); diff --git a/src/helpers/logger.js b/src/helpers/logger.js index 671d7b8..236d0b2 100644 --- a/src/helpers/logger.js +++ b/src/helpers/logger.js @@ -7,16 +7,22 @@ const logger = pino({ }); export default class Logger { - + static get logger() { + return logger; + } + static info(message, obj) { - logger.info(obj, message); + if (process.env.SWITCHER_API_LOGGER == 'true') + logger.info(obj, message); } static error(message, err) { - logger.error(err, message); + if (process.env.SWITCHER_API_LOGGER == 'true') + logger.error(err, message); } static httpError(name, code, message, err) { - logger.error(err, `${name} [${code}]: ${message}`); + if (process.env.SWITCHER_API_LOGGER == 'true') + logger.error(err, `${name} [${code}]: ${message}`); } } \ No newline at end of file diff --git a/src/routers/common/index.js b/src/routers/common/index.js new file mode 100644 index 0000000..2c5f6cf --- /dev/null +++ b/src/routers/common/index.js @@ -0,0 +1,29 @@ +export function getFields(elements, fields) { + return elements.map(element => { + const newElement = {}; + fields.split(',').forEach(field => { + if (field.includes('.')) { + const nestedFields = field.split('.'); + const nextNestedFields = field.substring(field.indexOf('.') + 1); + + const nestedElement = getElement(element, nestedFields[0]); + newElement[nestedFields[0]] = getFields([nestedElement], nextNestedFields)[0]; + } else { + newElement[field] = getElement(element, field); + } + }); + return newElement; + }); +} + +function getElement(element, field) { + if (!element) { + return undefined; + } + + if (element.get) { + return element.get(field); + } + + return element[field]; +} \ No newline at end of file diff --git a/src/routers/config.js b/src/routers/config.js index 4d1d776..9aa586c 100644 --- a/src/routers/config.js +++ b/src/routers/config.js @@ -12,6 +12,7 @@ import * as Services from '../services/config'; import { getHistory, deleteHistory } from '../services/history'; import { getGroupConfigById } from '../services/group-config'; import { SwitcherKeys } from '../external/switcher-api-facade'; +import { getFields } from './common'; const router = new express.Router(); @@ -30,9 +31,14 @@ router.post('/config/create', // GET /config?group=ID&limit=10&skip=20 // GET /config?group=ID&sortBy=createdAt:desc +// GET /config?group=ID&fields=key,description // GET /config?group=ID router.get('/config', auth, [ - query('group').isMongoId() + query('group').isMongoId(), + query('fields').isString().optional(), + query('limit').isInt().optional(), + query('skip').isInt().optional(), + query('sortBy').isString().optional() ], validate, async (req, res) => { try { const groupConfig = await getGroupConfigById(req.query.group); @@ -46,8 +52,12 @@ router.get('/config', auth, [ }); let configs = groupConfig.config; - configs = await verifyOwnership(req.admin, configs, groupConfig.domain, ActionTypes.READ, RouterTypes.CONFIG, true); + + if (req.query.fields) { + configs = getFields(configs, req.query.fields); + } + res.send(configs); } catch (e) { responseException(res, e, 500); diff --git a/src/services/slack.js b/src/services/slack.js index 0f564a1..e441834 100644 --- a/src/services/slack.js +++ b/src/services/slack.js @@ -79,10 +79,7 @@ export async function checkAvailability(admin, feature) { SwitcherKeys.SLACK_UPDATE ]); - if (process.env.SWITCHER_API_LOGGER == 'true') { - Logger.info(`checkAvailability [${feature}]`, { log: Switcher.getLogger(feature) }); - } - + Logger.info(`checkAvailability [${feature}]`, { log: Switcher.getLogger(feature) }); return result; } diff --git a/tests/config.test.js b/tests/config.test.js index bc131b8..8e10246 100644 --- a/tests/config.test.js +++ b/tests/config.test.js @@ -99,6 +99,21 @@ describe('Testing fetch configuration info', () => { expect(response.body[0].activated[EnvType.DEFAULT]).toEqual(config1Document.activated.get(EnvType.DEFAULT)); }); + test('CONFIG_SUITE - Should get Config information - only fields (key, activated.default)', async () => { + let response = await request(app) + .get(`/config?group=${groupConfigId}&fields=key,activated.default`) + .set('Authorization', `Bearer ${adminMasterAccountToken}`) + .send().expect(200); + + expect(response.body.length).toEqual(2); + expect(response.body[0]).toMatchObject({ + key: config1Document.key, + activated: { + default: config1Document.activated.get(EnvType.DEFAULT) + } + }); + }); + test('CONFIG_SUITE - Should get Configs by sorting ascending and descending', async () => { // given a config that was sent to the past const configKey1 = await Config.findOne({ key: 'TEST_CONFIG_KEY_1' }).exec(); diff --git a/tests/unit-test/helpers/logger.test.js b/tests/unit-test/helpers/logger.test.js new file mode 100644 index 0000000..c56b078 --- /dev/null +++ b/tests/unit-test/helpers/logger.test.js @@ -0,0 +1,49 @@ +import Logger from '../../../src/helpers/logger'; + +describe('Helper: Logger', () => { + beforeAll(() => { + process.env.SWITCHER_API_LOGGER = 'true'; + }); + + test('Should call logger.info', () => { + const spy = jest.spyOn(Logger.logger, 'info'); + Logger.info('test'); + expect(spy).toHaveBeenCalled(); + }); + + test('Should call logger.error', () => { + const spy = jest.spyOn(Logger.logger, 'error'); + Logger.error('test'); + expect(spy).toHaveBeenCalled(); + }); + + test('Should call logger.httpError', () => { + const spy = jest.spyOn(Logger.logger, 'error'); + Logger.httpError('test'); + expect(spy).toHaveBeenCalled(); + }); +}); + +describe('Helper: Logger (disabled)', () => { + beforeAll(() => { + process.env.SWITCHER_API_LOGGER = 'false'; + }); + + test('Should call logger.info', () => { + const spy = jest.spyOn(Logger.logger, 'info'); + Logger.info('test'); + expect(spy).not.toHaveBeenCalled(); + }); + + test('Should call logger.error', () => { + const spy = jest.spyOn(Logger.logger, 'error'); + Logger.error('test'); + expect(spy).not.toHaveBeenCalled(); + }); + + test('Should call logger.httpError', () => { + const spy = jest.spyOn(Logger.logger, 'error'); + Logger.httpError('test'); + expect(spy).not.toHaveBeenCalled(); + }); +}); \ No newline at end of file diff --git a/tests/unit-test/routers/common-index.test.js b/tests/unit-test/routers/common-index.test.js new file mode 100644 index 0000000..54fe342 --- /dev/null +++ b/tests/unit-test/routers/common-index.test.js @@ -0,0 +1,55 @@ +import { getFields } from '../../../src/routers/common/index.js'; + +/* Fixtures */ + +const switcherActivated = new Map(); +switcherActivated.set('default', true); + +const relayActivated = new Map(); +relayActivated.set('default', true); +relayActivated.set('staging', false); + +const config1 = { + key: 'SWITCHER_KEY_1', + description: '[description]', + activated: switcherActivated, + relay: { + description: '[relay description]', + activated: relayActivated + } +}; + +const config2 = { + key: 'SWITCHER_KEY_2', + description: '[description]', + activated: switcherActivated +}; + +/* Tests */ + +describe('Helper: Router::common::getFields', () => { + test('Should return the fields from the object', () => { + const result = getFields([config1], 'key,activated.default,relay.activated.default'); + expect(result).toEqual([{ + key: 'SWITCHER_KEY_1', + activated: { + default: true + }, + relay: { + activated: { + default: true + } + } + }]); + }); + + test('Should return the fields from the object - with non-existing field path', () => { + const result = getFields([config2], 'key,relay.activated.default'); + expect(result).toEqual([{ + key: 'SWITCHER_KEY_2', + relay: { + activated: {} + } + }]); + }); +}); \ No newline at end of file