Implement APIs of sonarqube-backend plugin's router

Also update the standalone server to add router dependencies

Signed-off-by: Neemys <36508659+Neemys@users.noreply.github.com>
This commit is contained in:
Neemys
2022-07-22 14:33:50 +02:00
parent 55a31cc753
commit e9dcea3c92
3 changed files with 147 additions and 22 deletions
@@ -19,13 +19,23 @@ import express from 'express';
import request from 'supertest';
import { createRouter } from './router';
import { SonarqubeFindings } from './sonarqubeInfoProvider';
describe('createRouter', () => {
let app: express.Express;
const getBaseUrlMock: jest.Mock<string, [string]> = jest.fn();
const getFindingsMock: jest.Mock<
Promise<SonarqubeFindings | undefined>,
[string, string]
> = jest.fn();
beforeAll(async () => {
const router = await createRouter({
logger: getVoidLogger(),
sonarqubeInfoProvider: {
getBaseUrl: getBaseUrlMock,
getFindings: getFindingsMock,
},
});
app = express().use(router);
});
@@ -35,17 +45,76 @@ describe('createRouter', () => {
});
describe('GET /findings', () => {
const DUMMY_COMPONENT_KEY = 'my:component';
const DUMMY_INSTANCE_KEY = 'myInstance';
it('returns ok', async () => {
const measures = {
analysisDate: '2022-01-01T00:00:00Z',
measures: [{ metric: 'vulnerabilities', value: '54' }],
};
getFindingsMock.mockReturnValue(Promise.resolve(measures));
const response = await request(app)
.get('/findings')
.set('componentKey', 'my:app')
.query({
componentKey: DUMMY_COMPONENT_KEY,
instanceKey: DUMMY_INSTANCE_KEY,
})
.send();
expect(getFindingsMock).toBeCalledTimes(1);
expect(getFindingsMock).toBeCalledWith(
DUMMY_COMPONENT_KEY,
DUMMY_INSTANCE_KEY,
);
expect(response.status).toEqual(200);
expect(response.body).toEqual(measures);
});
it('returns an error when component key is not defined', async () => {
const response = await request(app)
.get('/findings')
.query({
instanceKey: DUMMY_INSTANCE_KEY,
})
.send();
expect(response.status).toEqual(400);
});
it('use an empty string as instance name when instance key not provided', async () => {
const measures = {
analysisDate: '2021-04-08',
measures: [{ metric: 'vulnerabilities', value: '54' }],
};
getFindingsMock.mockReturnValue(Promise.resolve(measures));
const response = await request(app)
.get('/findings')
.query({
componentKey: DUMMY_COMPONENT_KEY,
})
.send();
expect(getFindingsMock).toBeCalledTimes(1);
expect(getFindingsMock).toBeCalledWith(DUMMY_COMPONENT_KEY, '');
expect(response.status).toEqual(200);
expect(response.body).toEqual({
analysisDate: '2022-10-22T04:55:23Z',
measures: [{ metric: 'coverage', value: '50' }],
});
expect(response.body).toEqual(measures);
});
});
describe('GET /instanceUrl', () => {
const DUMMY_INSTANCE_KEY = 'myInstance';
const DUMMY_INSTANCE_URL = 'http://sonarqube.example.com';
it('returns ok', async () => {
getBaseUrlMock.mockReturnValue(DUMMY_INSTANCE_URL);
const response = await request(app)
.get('/instanceUrl')
.query({
instanceKey: DUMMY_INSTANCE_KEY,
})
.send();
expect(getBaseUrlMock).toBeCalledTimes(1);
expect(getBaseUrlMock).toBeCalledWith(DUMMY_INSTANCE_KEY);
expect(response.status).toEqual(200);
expect(response.body).toEqual({ instanceUrl: DUMMY_INSTANCE_URL });
});
});
});
+65 -15
View File
@@ -15,37 +15,87 @@
*/
import { errorHandler } from '@backstage/backend-common';
import express from 'express';
import express, { RequestHandler } from 'express';
import Router from 'express-promise-router';
import { Logger } from 'winston';
import {
SonarqubeFindings,
SonarqubeInfoProvider,
} from './sonarqubeInfoProvider';
import { InputError } from '../../../../packages/errors';
/**
* Dependencies needed by the router
* @public
*/
export interface RouterOptions {
/**
* Logger for logging purposes
*/
logger: Logger;
/**
* Info provider to be able to get all necessary information for the APIs
*/
sonarqubeInfoProvider: SonarqubeInfoProvider;
}
/**
* @public
*
* Constructs a sonarqube router.
*
* Expose endpoint to get information on or for a sonarqube instance.
*
* @param options - Dependencies of the router
*/
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { logger } = options;
const { logger, sonarqubeInfoProvider } = options;
const router = Router();
router.use(express.json());
// mock api for now
router.get('/findings', (request, response) => {
logger.info(request.params);
router.get('/findings', (async (request, response) => {
const componentKey = request.query.componentKey;
let instanceKey = request.query.instanceKey;
if (!componentKey)
throw new InputError('ComponentKey must be provided as a single string.');
if (!instanceKey) {
instanceKey = '';
logger.info(
`Retrieving findings for component ${componentKey} in default sonarqube instance`,
);
} else {
logger.info(
`Retrieving findings for component ${componentKey} in sonarqube instance name ${instanceKey}`,
);
}
response.send(
await sonarqubeInfoProvider.getFindings(componentKey, instanceKey),
);
}) as RequestHandler<unknown, SonarqubeFindings | undefined, unknown, { componentKey: string; instanceKey: string }>);
router.get('/instanceUrl', ((request, response) => {
let requestedInstanceKey = request.query.instanceKey;
if (requestedInstanceKey) {
logger.info(
`Retrieving sonarqube instance URL for key ${requestedInstanceKey}`,
);
} else {
requestedInstanceKey = '';
logger.info(
`Retrieving default sonarqube instance URL as parameter is inexistant, empty or malformed`,
);
}
response.send({
analysisDate: '2022-10-22T04:55:23Z',
measures: [{ metric: 'coverage', value: '50' }],
instanceUrl: sonarqubeInfoProvider.getBaseUrl(requestedInstanceKey),
});
});
router.get('/instanceUrl', (request, response) => {
logger.info(request.params);
response.send({
instanceUrl: `https://instance.local?${encodeURI(
request.query.instanceKey as string,
)}`,
});
});
}) as RequestHandler<unknown, { instanceUrl: string }, unknown, { instanceKey: string }>);
router.use(errorHandler());
return router;
}
@@ -14,10 +14,14 @@
* limitations under the License.
*/
import { createServiceBuilder } from '@backstage/backend-common';
import {
createServiceBuilder,
loadBackendConfig,
} from '@backstage/backend-common';
import { Server } from 'http';
import { Logger } from 'winston';
import { createRouter } from './router';
import { DefaultSonarqubeInfoProvider } from './sonarqubeInfoProvider';
export interface ServerOptions {
port: number;
@@ -30,13 +34,15 @@ export async function startStandaloneServer(
): Promise<Server> {
const logger = options.logger.child({ service: 'sonarqube-backend-backend' });
logger.debug('Starting application server...');
const config = await loadBackendConfig({ logger, argv: process.argv });
const router = await createRouter({
logger,
sonarqubeInfoProvider: DefaultSonarqubeInfoProvider.fromConfig(config),
});
let service = createServiceBuilder(module)
.setPort(options.port)
.addRouter('/sonarqube-backend', router);
.addRouter('/sonarqube', router);
if (options.enableCors) {
service = service.enableCors({ origin: 'http://localhost:3000' });
}