Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 39 additions & 0 deletions requests/Switcher API.postman_collection.json
Original file line number Diff line number Diff line change
Expand Up @@ -5723,6 +5723,45 @@
}
]
},
{
"name": "API Management",
"item": [
{
"name": "Management - Feature",
"request": {
"method": "POST",
"header": [
{
"key": "Content-Type",
"name": "Content-Type",
"type": "text",
"value": "application/json"
}
],
"body": {
"mode": "raw",
"raw": "{\n\t\"feature\": \"FEATURE\",\n\t\"parameters\": {\n \"value\": \"test\"\n }\n}",
"options": {
"raw": {
"language": "json"
}
}
},
"url": {
"raw": "{{url}}/api-management/feature",
"host": [
"{{url}}"
],
"path": [
"api-management",
"feature"
]
}
},
"response": []
}
]
},
{
"name": "API Check",
"event": [
Expand Down
43 changes: 43 additions & 0 deletions src/api-docs/paths/path-api-management.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
export default {
'/api-management/feature': {
post: {
tags: ['API Management'],
description: 'Run feature validation',
security: [{ appAuth: [] }],
requestBody: {
content: {
'application/json': {
schema: {
type: 'object',
properties: {
feature: {
type: 'string'
},
parameters: {
type: 'object'
}
}
}
}
}
},
responses: {
200: {
description: 'Success',
content: {
'application/json': {
schema: {
type: 'object',
properties: {
status: {
type: 'boolean'
}
}
}
}
}
}
}
}
}
};
4 changes: 3 additions & 1 deletion src/api-docs/swagger-document.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import pathPermission from './paths/path-permission';
import pathMetric from './paths/path-metric';
import pathSlack from './paths/path-slack';
import pathClient from './paths/path-client';
import pathApiManagement from './paths/path-api-management';

import { commonSchema } from './schemas/common';
import adminSchema from './schemas/admin';
Expand Down Expand Up @@ -90,6 +91,7 @@ export default {
...pathPermission,
...pathMetric,
...pathClient,
...pathSlack
...pathSlack,
...pathApiManagement
}
};
2 changes: 2 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import metricRouter from './routers/metric';
import teamRouter from './routers/team';
import permissionRouter from './routers/permission';
import slackRouter from './routers/slack';
import apiManagementRouter from './routers/api-management';
import schema from './client/schema';
import { appAuth, auth, resourcesAuth, slackAuth } from './middleware/auth';
import { clientLimiter, defaultLimiter } from './middleware/limiter';
Expand Down Expand Up @@ -50,6 +51,7 @@ app.use(metricRouter);
app.use(teamRouter);
app.use(permissionRouter);
app.use(slackRouter);
app.use(apiManagementRouter);

/**
* GraphQL Routes
Expand Down
14 changes: 14 additions & 0 deletions src/external/switcher-api-facade.js
Original file line number Diff line number Diff line change
Expand Up @@ -246,4 +246,18 @@ export async function checkHttpsAgent(value) {
return;

return checkFeature(SwitcherKeys.HTTPS_AGENT, [checkRegex(value)]);
}

export async function checkManagementFeature(feature, params) {
if (process.env.SWITCHER_API_ENABLE != 'true')
return true;

const switcher = Switcher.factory();
const entries = [];

if (params?.value) {
entries.push(checkValue(params.value));
}

return switcher.isItOn(feature, entries);
}
23 changes: 23 additions & 0 deletions src/routers/api-management.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import express from 'express';
import { auth } from '../middleware/auth';
import { responseException } from '../exceptions';
import * as SwitcherAPI from '../external/switcher-api-facade';
import { validate, verifyInputUpdateParameters } from '../middleware/validators';
import { body } from 'express-validator';

const router = new express.Router();

router.post('/api-management/feature', auth,
verifyInputUpdateParameters(['feature', 'parameters']), [
body('feature').isString().notEmpty(),
body('parameters').optional().isObject()
], validate, async (req, res) => {
try {
const status = await SwitcherAPI.checkManagementFeature(req.body.feature, req.body.parameters);
res.send({ status });
} catch (e) {
responseException(res, e, 400);
}
});

export default router;
122 changes: 122 additions & 0 deletions tests/api-management.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import mongoose from 'mongoose';
import app from '../src/app';
import request from 'supertest';
import {
adminMasterAccount,
setupDatabase
} from './fixtures/db_api';
import { Switcher } from 'switcher-client';

afterAll(async () => {
await new Promise(resolve => setTimeout(resolve, 1000));
await mongoose.disconnect();
});

describe('API Management', () => {

let token;

beforeAll(async () => {
await setupDatabase();

const res = await request(app)
.post('/admin/login')
.send({
email: adminMasterAccount.email,
password: adminMasterAccount.password
}).expect(200);

token = res.body.jwt.token;
});

beforeEach(async () => {
process.env.SWITCHER_API_ENABLE = true;
Switcher.forget('MY_FEATURE');
});

test('API_MANAGEMENT - Should return TRUE when SWITCHER_API_ENABLE disabled', async () => {
process.env.SWITCHER_API_ENABLE = false;

const res = await request(app)
.post('/api-management/feature')
.set('Authorization', `Bearer ${token}`)
.send({
feature: 'MY_FEATURE'
}).expect(200);

expect(res.body.status).toEqual(true);
});

test('API_MANAGEMENT - Should return TRUE when requesting feature `MY_FEATURE`', async () => {
Switcher.assume('MY_FEATURE').true();

const res = await request(app)
.post('/api-management/feature')
.set('Authorization', `Bearer ${token}`)
.send({
feature: 'MY_FEATURE'
}).expect(200);

expect(res.body.status).toEqual(true);
});

test('API_MANAGEMENT - Should return TRUE when requesting feature `MY_FEATURE` with parameters - value', async () => {
Switcher.assume('MY_FEATURE').false();

const res = await request(app)
.post('/api-management/feature')
.set('Authorization', `Bearer ${token}`)
.send({
feature: 'MY_FEATURE',
parameters: {
value: 'my-value'
}
}).expect(200);

expect(res.body.status).toEqual(false);
});

test('API_MANAGEMENT - Should NOT return when body has invalid payload', async () => {
await request(app)
.post('/api-management/feature')
.set('Authorization', `Bearer ${token}`)
.send({
features: ['MY_FEATURE']
}).expect(400);
});

test('API_MANAGEMENT - Should NOT return when API cannot respond', async () => {
await request(app)
.post('/api-management/feature')
.set('Authorization', `Bearer ${token}`)
.send({
feature: 'MY_FEATURE_1'
}).expect(400);
});

test('API_MANAGEMENT - Should NOT return when feature not specified', async () => {
await request(app)
.post('/api-management/feature')
.set('Authorization', `Bearer ${token}`)
.send().expect(422);
});

test('API_MANAGEMENT - Should NOT return when paramaters is not an object', async () => {
await request(app)
.post('/api-management/feature')
.set('Authorization', `Bearer ${token}`)
.send({
feature: 'MY_FEATURE',
parameters: 'my-value'
}).expect(422);
});

test('API_MANAGEMENT - Should NOT return when not logged', async () => {
await request(app)
.post('/api-management/feature')
.send({
feature: 'MY_FEATURE'
}).expect(401);
});

});