Skip to content

Commit

Permalink
feat: Delete reports (#460)
Browse files Browse the repository at this point in the history
  • Loading branch information
goemen authored May 27, 2024
1 parent 12d658c commit 9bde67a
Show file tree
Hide file tree
Showing 27 changed files with 423 additions and 65 deletions.
1 change: 1 addition & 0 deletions .github/workflows/.deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ jobs:
--set-string global.secrets.bceidWsAuthUserName="${{ secrets.BCEID_WS_BASIC_AUTH_USERNAME }}" \
--set-string global.secrets.bceidWsOnlineServiceId="${{ secrets.BCEID_WS_ONLINE_SERVICE_ID }}" \
--set-string global.secrets.externalConsumerApiKey="${{ secrets.EXTERNAL_CONSUMER_API_KEY }}" \
--set-string global.secrets.externalConsumerDeleteReportsApiKey="${{ secrets.EXTERNAL_CONSUMER_DELETE_REPORTS_API_KEY }}" \
--set-string global.secrets.chesTokenURL="${{ secrets.CHES_TOKEN_ENDPOINT }}" \
--set-string global.secrets.chesClientID="${{ secrets.CHES_CLIENT_ID }}" \
--set-string global.secrets.chesClientSecret="${{ secrets.CHES_CLIENT_SECRET }}" \
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/.integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,5 +61,6 @@ jobs:
env:
BASE_URL: ${{ inputs.backend-external-url }}
EXTERNAL_CONSUMER_API_KEY: ${{ secrets.EXTERNAL_CONSUMER_API_KEY }}
EXTERNAL_CONSUMER_DELETE_REPORTS_API_KEY: ${{ secrets.EXTERNAL_CONSUMER_DELETE_REPORTS_API_KEY }}
run: |
npm run test:integration
11 changes: 6 additions & 5 deletions backend-external/src/app.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,30 +55,31 @@ describe('app', () => {
});
});

describe('/api/v1/pay-transparency GET', () => {
describe('/api/v1/pay-transparency/reports GET', () => {
describe('with API Key', () => {
it('should get reports when api key is valid', async () => {
mockGetPayTransparencyData.mockReturnValue({
status: 200,
data: [{ id: 1 }],
});
const response = await request(app)
.get('/api/v1/pay-transparency')
.get('/api/v1/pay-transparency/reports')
.set('x-api-key', 'api-key');
expect(response.status).toBe(200);
expect(response.body).toEqual([{ id: 1 }]);
});
it('should fail when api key is valid', async () => {
const response = await request(app)
.get('/api/v1/pay-transparency')
.get('/api/v1/pay-transparency/reports')
.set('x-api-key', 'api-key-invalid');
expect(response.status).toBe(401);
});
});
describe('without API Key', () => {
it('should fail when api key is not available', async () => {
const response = await request(app)
.get('/api/v1/pay-transparency')
const response = await request(app).get(
'/api/v1/pay-transparency/reports',
);
expect(response.status).toBe(400);
});
});
Expand Down
22 changes: 3 additions & 19 deletions backend-external/src/app.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import express, { json, NextFunction, Request, Response } from 'express';
import express, { json, Request, Response } from 'express';
import swaggerJsdoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
import helmet from 'helmet';
Expand Down Expand Up @@ -80,30 +80,14 @@ app.use(/(\/api)?/, apiRouter);
apiRouter.get('/', (_req, res) => {
res.sendStatus(200); // generally for route verification and health check.
});
const globalMiddleware = (req: Request, res: Response, next: NextFunction) => {
const apiKey = req.header('x-api-key');
if (apiKey) {
if (config.get('server:apiKey') === apiKey) {
next();
} else {
logger.error('Invalid API Key');
res.status(401).send({ message: 'Invalid API Key' });
}
} else {
logger.error('API Key is missing in the request header');
res.status(400).send({
message: 'API Key is missing in the request header',
});
}
};

const specs = swaggerJsdoc(utils.swaggerDocsOptions);
apiRouter.use(
'/v1/docs',
swaggerUi.serve,
swaggerUi.setup(specs, { explorer: true }),
);
apiRouter.use(globalMiddleware);
apiRouter.use('/v1/pay-transparency', payTransparencyRouter);
apiRouter.use('/v1/pay-transparency/reports', payTransparencyRouter);
// Handle 500

// Handle 404
Expand Down
10 changes: 6 additions & 4 deletions backend-external/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,19 @@ config.defaults({
logLevel: process.env.LOG_LEVEL,
morganFormat: 'dev',
apiKey: process.env.EXTERNAL_CONSUMER_API_KEY || 'api-key',
deleteReportsApiKey:
process.env.EXTERNAL_CONSUMER_DELETE_REPORTS_API_KEY || 'api-delete-reports-key',
port: process.env.PORT || 3002,
rateLimit: {
enabled: process.env.IS_RATE_LIMIT_ENABLED || false, // Disable if rate limiting is not required
windowMs: process.env.RATE_LIMIT_WINDOW_MS || 60000, // 1 minute
limit: process.env.RATE_LIMIT_LIMIT || 100, // Limit each IP to 100 requests per `window` (here, per 1 minute)
},
baseURL: process.env.BASE_URL || 'http://localhost:3002'
baseURL: process.env.BASE_URL || 'http://localhost:3002',
},
backend:{
backend: {
apiKey: process.env.BACKEND_EXTERNAL_API_KEY || 'api-key',
url: process.env.BACKEND_URL || 'http://localhost:3010'
}
url: process.env.BACKEND_URL || 'http://localhost:3010',
},
});
export { config };
2 changes: 1 addition & 1 deletion backend-external/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const swaggerOpenAPIOptions: Options = {
},
servers: [
{
url: `${config.get('server:baseURL')}/api/v1/pay-transparency`,
url: `${config.get('server:baseURL')}/api/v1/pay-transparency/`,
},
],
},
Expand Down
54 changes: 51 additions & 3 deletions backend-external/src/v1/routes/pay-transparency-routes.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import request from 'supertest';
import router from './pay-transparency-routes';

const mockGetPayTransparencyData = jest.fn();
const mockDeleteReports = jest.fn();
jest.mock('../services/pay-transparency-service', () => ({
payTransparencyService: {
getPayTransparencyData: (...args) => mockGetPayTransparencyData(...args),
deleteReports: (...args) => mockDeleteReports(...args),
},
}));

Expand All @@ -19,13 +21,14 @@ describe('pay-transparency-routes', () => {
});

describe('/ GET', () => {
it('should return data if user doeas not send query params', () => {
it('should return data if user does not send query params', () => {
mockGetPayTransparencyData.mockReturnValue({
status: 200,
data: [{ id: 1 }],
});
return request(app)
.get('')
.set('x-api-key', 'api-key')
.expect(200)
.expect(({ body }) => {
expect(body).toHaveLength(1);
Expand All @@ -38,13 +41,58 @@ describe('pay-transparency-routes', () => {
});
return request(app)
.get('')
.query({page: 'one', pageSize: '1oooo'})
.set('x-api-key', 'api-key')
.query({ page: 'one', pageSize: '1oooo' })
.expect(400);
});
it('should fail if request fails to get reports', () => {
mockGetPayTransparencyData.mockRejectedValue({})
mockGetPayTransparencyData.mockRejectedValue({});
return request(app).get('').set('x-api-key', 'api-key').expect(500);
});

it('should return 400 if the getPayTransparencyData has error', () => {
mockGetPayTransparencyData.mockReturnValue({
data: { message: 'Failed to get reports', error: true },
});
return request(app)
.get('')
.set('x-api-key', 'api-key')
.expect(400);
});
});

describe('/reports, DELETE', () => {
it('should delete reports', () => {
const message = 'Report deleted';
mockDeleteReports.mockReturnValue({
status: 200,
data: { message },
});
return request(app)
.delete('/')
.query({ companyName: '1234567890' })
.set('x-api-key', 'api-delete-reports-key')
.expect(200)
.expect(({ body }) => {
expect(body).toEqual({ message });
});
});
it('should return 400 if the deleteReports has error', () => {
mockDeleteReports.mockReturnValue({
data: { message: 'Failed to delete reports', error: true },
});
return request(app)
.delete('/')
.set('x-api-key', 'api-delete-reports-key')
.query({ companyName: '' })
.expect(400);
});
it('should fail if request fails to get reports', () => {
mockDeleteReports.mockRejectedValue({ message: 'Error happened' });
return request(app)
.delete('/')
.query({ companyName: '1234567890' })
.set('x-api-key', 'api-delete-reports-key')
.expect(500);
});
});
Expand Down
76 changes: 73 additions & 3 deletions backend-external/src/v1/routes/pay-transparency-routes.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,29 @@
import express, { Request, Response } from 'express';
import express, { NextFunction, Request, Response } from 'express';
import { payTransparencyService } from '../services/pay-transparency-service';
import { utils } from '../../utils';
import { logger } from '../../logger';
import { config } from '../../config';

const router = express.Router();
const validateApiKey =
(validKey: string) =>
(req: Request, res: Response, next: NextFunction) => {
const apiKey = req.header('x-api-key');
if (apiKey) {
if (validKey === apiKey) {
next();
} else {
logger.error('Invalid API Key');
res.status(401).send({ message: 'Invalid API Key' });
}
} else {
logger.error('API Key is missing in the request header');
res.status(400).send({
message: 'API Key is missing in the request header',
});
}
};

/**
* @swagger
* components:
Expand Down Expand Up @@ -71,7 +92,7 @@ const router = express.Router();
* items:
* $ref: "#/components/schemas/CalculatedData"
* Report:
* allOf:
* allOf:
* - $ref: "#/components/schemas/ReportItem"
*
* PaginatedReports:
Expand All @@ -96,7 +117,7 @@ const router = express.Router();
* @swagger
* tags:
* name: Reports
* /:
* /reports:
* get:
* summary: Get published reports with update date within a date range (date range defaults to the last 30 days)
* tags: [Reports]
Expand Down Expand Up @@ -144,6 +165,7 @@ const router = express.Router();
*/
router.get(
'/',
validateApiKey(config.get('server:apiKey')),
utils.asyncHandler(async (req: Request, res: Response) => {
try {
const startDate = req.query.startDate?.toString();
Expand All @@ -170,4 +192,52 @@ router.get(
}
}),
);

/**
* @swagger
* tags:
* name: End to end testing utils
* description: This endpoint is used by developers to teardown test data after end to end tests. Only developers can access this endpoint
* /reports:
* delete:
* summary: Delete reports
* tags: ["End to end testing utils"]
* security:
* - ApiKeyAuth: []
* parameters:
* - in: query
* name: companyName
* required: true
* schema:
* type: string
* responses:
* 200:
* description: Successfully deleted reports
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
*/
router.delete(
'/',
validateApiKey(
config.get('server:deleteReportsApiKey'),
),
async (req, res) => {
try {
const { data } = await payTransparencyService.deleteReports(req);
if (data.error) {
return res.status(400).json({ message: data.message });
}

return res.status(200).json({ message: data.message });
} catch (error) {
return res.status(500).json({ message: error.message });
}
},
);

export default router;
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { payTransparencyService } from './pay-transparency-service';

const mockGet = jest.fn();
const mockDelete = jest.fn();

jest.mock('../../utils', () => ({
utils: {
backendAxios: () => ({
get: (...args) => mockGet(...args),
delete: (...args) => mockDelete(...args),
}),
},
}));
Expand All @@ -23,7 +25,7 @@ describe('pay-transparency-service', () => {
0,
1000,
);
expect(mockGet).toHaveBeenCalledWith('/external-consumer-api/v1/', {
expect(mockGet).toHaveBeenCalledWith('/external-consumer-api/v1/reports', {
params: {
startDate: 'start',
endDate: 'end',
Expand All @@ -33,4 +35,23 @@ describe('pay-transparency-service', () => {
});
});
});

describe('deleteReports', () => {
it('should delete reports', async () => {
mockDelete.mockReturnValue({});
await payTransparencyService.deleteReports({
params: { companyName: '1234567890' },
} as any);

expect(mockDelete).toHaveBeenCalledWith(
'/external-consumer-api/v1/reports',
{
params: { companyName: '1234567890' },
headers: {
'x-api-key': 'api-key',
},
},
);
});
});
});
Loading

0 comments on commit 9bde67a

Please sign in to comment.