diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index d92dba9069..af0d9d584e 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -65,19 +65,17 @@ "yup": "^1.0.0" }, "devDependencies": { + "@backstage/backend-app-api": "workspace:^", "@backstage/backend-defaults": "workspace:^", "@backstage/backend-test-utils": "workspace:^", "@backstage/cli": "workspace:^", "@backstage/config-loader": "workspace:^", "@backstage/errors": "workspace:^", "@types/http-proxy-middleware": "^1.0.0", - "@types/supertest": "^2.0.8", "@types/uuid": "^9.0.0", "@types/yup": "^0.32.0", - "msw": "^1.0.0", - "node-fetch": "^2.6.7", - "portfinder": "^1.0.32", - "supertest": "^6.1.3" + "msw": "^2.0.0", + "node-fetch": "^2.6.7" }, "configSchema": "config.d.ts" } diff --git a/plugins/proxy-backend/src/service/router.config.test.ts b/plugins/proxy-backend/src/service/router.config.test.ts index 0de4787658..ba81553a4f 100644 --- a/plugins/proxy-backend/src/service/router.config.test.ts +++ b/plugins/proxy-backend/src/service/router.config.test.ts @@ -15,73 +15,41 @@ */ import { - HostDiscovery, - loggerToWinstonLogger, -} from '@backstage/backend-common'; + coreServices, + createServiceFactory, +} from '@backstage/backend-plugin-api'; +import { + setupRequestMockHandlers, + startTestBackend, +} from '@backstage/backend-test-utils'; import { ConfigSources, MutableConfigSource, StaticConfigSource, } from '@backstage/config-loader'; -import express from 'express'; -import { rest } from 'msw'; +import { HttpResponse, http, passthrough } from 'msw'; import { setupServer } from 'msw/node'; -import request from 'supertest'; -import { createRouter } from './router'; -import { mockServices } from '@backstage/backend-test-utils'; +import fetch from 'node-fetch'; // this test is stored in its own file to work around the mocked // http-proxy-middleware module used in the main test file describe('createRouter reloadable configuration', () => { - const server = setupServer( - rest.get('https://non-existing-example.com/', (req, res, ctx) => - res( - ctx.status(200), - ctx.json({ - url: req.url.toString(), - headers: req.headers.all(), - }), - ), - ), - ); - - beforeAll(() => - server.listen({ - onUnhandledRequest: ({ headers }, print) => { - if (headers.get('User-Agent') === 'supertest') { - return; - } - print.error(); - }, - }), - ); - - afterAll(() => server.close()); - afterEach(() => server.resetHandlers()); + const server = setupServer(); + setupRequestMockHandlers(server); it('should be able to observe the config', async () => { - const logger = loggerToWinstonLogger(mockServices.logger.mock()); - // Grab the subscriber function and use mutable config data to mock a config file change const mutableConfigSource = MutableConfigSource.create({ data: {} }); const config = await ConfigSources.toConfig( ConfigSources.merge([ StaticConfigSource.create({ data: { - backend: { - baseUrl: 'http://localhost:7007', - listen: { - port: 7007, - }, - }, proxy: { endpoints: { '/test': { target: 'https://non-existing-example.com', - pathRewrite: { - '.*': '/', - }, + credentials: 'dangerously-allow-unauthenticated', }, }, }, @@ -91,40 +59,53 @@ describe('createRouter reloadable configuration', () => { ]), ); - const discovery = HostDiscovery.fromConfig(config); - const router = await createRouter({ - config, - logger, - discovery, + const backend = await startTestBackend({ + features: [ + import('../alpha'), + createServiceFactory({ + service: coreServices.rootConfig, + deps: {}, + factory: () => config, + }), + ], }); - expect(router).toBeDefined(); - const app = express(); - app.use(router); + try { + const baseUrl = `http://localhost:${backend.server.port()}`; - const agent = request.agent(app); - // this is set to let msw pass test requests through the mock server - agent.set('User-Agent', 'supertest'); + server.use( + http.all(`${baseUrl}/*`, passthrough), + http.get('https://non-existing-example.com/*', req => + HttpResponse.json({ + url: req.request.url.toString(), + headers: req.request.headers, + }), + ), + ); - const response1 = await agent.get('/test'); + await expect(fetch(`${baseUrl}/api/proxy/test`)).resolves.toMatchObject({ + status: 200, + }); + await expect( + fetch(`${baseUrl}/api/proxy/test2`), + ).resolves.not.toMatchObject({ status: 200 }); - expect(response1.status).toEqual(200); - - mutableConfigSource.setData({ - proxy: { - endpoints: { - '/test2': { - target: 'https://non-existing-example.com', - pathRewrite: { - '.*': '/', + mutableConfigSource.setData({ + proxy: { + endpoints: { + '/test2': { + target: 'https://non-existing-example.com', + credentials: 'dangerously-allow-unauthenticated', }, }, }, - }, - }); + }); - const response2 = await agent.get('/test2'); - - expect(response2.status).toEqual(200); + await expect(fetch(`${baseUrl}/api/proxy/test2`)).resolves.toMatchObject({ + status: 200, + }); + } finally { + await backend.stop(); + } }); }); diff --git a/plugins/proxy-backend/src/service/router.credentials.test.ts b/plugins/proxy-backend/src/service/router.credentials.test.ts index 1496b6ffb6..54c865ba11 100644 --- a/plugins/proxy-backend/src/service/router.credentials.test.ts +++ b/plugins/proxy-backend/src/service/router.credentials.test.ts @@ -14,17 +14,20 @@ * limitations under the License. */ -import { createBackend } from '@backstage/backend-defaults'; +import { + authServiceFactory, + httpAuthServiceFactory, +} from '@backstage/backend-app-api'; import { mockServices, setupRequestMockHandlers, + startTestBackend, } from '@backstage/backend-test-utils'; import { ResponseError } from '@backstage/errors'; import { JsonObject } from '@backstage/types'; -import { rest } from 'msw'; +import { HttpResponse, http, passthrough } from 'msw'; import { setupServer } from 'msw/node'; import fetch from 'node-fetch'; -import portFinder from 'portfinder'; // this test is stored in its own file to work around the mocked // http-proxy-middleware module used in the main test file @@ -34,14 +37,8 @@ describe('credentials', () => { setupRequestMockHandlers(worker); it('handles all valid credentials settings', async () => { - const host = 'localhost'; - const port = await portFinder.getPortPromise(); - const baseUrl = `http://${host}:${port}`; - const config = { backend: { - baseUrl, - listen: { host, port }, auth: { externalAccess: [ { @@ -81,43 +78,42 @@ describe('credentials', () => { }, }; - worker.use( - rest.all(`${baseUrl}/*`, req => req.passthrough()), - rest.get('http://target.com/*', (req, res, ctx) => { - const auth = req.headers.get('authorization'); - return res( - ctx.status(200), - ctx.json({ payload: { forwardedAuthorization: auth ?? false } }), - ); - }), - ); - - async function call(options: { - endpoint: string; - authorization: string | false; - }): Promise { - const { endpoint, authorization } = options; - return fetch(`${baseUrl}/api/proxy/${endpoint}/just-some-path`, { - headers: authorization ? { Authorization: authorization } : {}, - }).then(async res => { - if (!res.ok) { - throw await ResponseError.fromResponse(res); - } - return res.json(); - }); - } - - // Create an actual backend instead of a test backend, because we want to - // use the real HTTP server that provides the protection middleware etc. A - // bit harder to test, but at least we can use static external access tokens - // for it. - const backend = createBackend(); - backend.add(import('../alpha')); - backend.add(mockServices.rootConfig.factory({ data: config })); - backend.add(mockServices.rootLogger.factory()); - await backend.start(); + const backend = await startTestBackend({ + features: [ + import('../alpha'), + mockServices.rootConfig.factory({ data: config }), + authServiceFactory(), + httpAuthServiceFactory(), + ], + }); try { + const baseUrl = `http://localhost:${backend.server.port()}`; + worker.use( + http.all(`${baseUrl}/*`, passthrough), + http.get('http://target.com/*', req => { + const auth = req.request.headers.get('authorization'); + return HttpResponse.json({ + payload: { forwardedAuthorization: auth ?? false }, + }); + }), + ); + + const call = async (options: { + endpoint: string; + authorization: string | false; + }): Promise => { + const { endpoint, authorization } = options; + return fetch(`${baseUrl}/api/proxy/${endpoint}/just-some-path`, { + headers: authorization ? { Authorization: authorization } : {}, + }).then(async res => { + if (!res.ok) { + throw await ResponseError.fromResponse(res); + } + return res.json(); + }); + }; + // simple credentials config await expect( call({ endpoint: 'simple', authorization: false }), diff --git a/yarn.lock b/yarn.lock index f7aaf741ac..c143a86e28 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6454,6 +6454,7 @@ __metadata: version: 0.0.0-use.local resolution: "@backstage/plugin-proxy-backend@workspace:plugins/proxy-backend" dependencies: + "@backstage/backend-app-api": "workspace:^" "@backstage/backend-common": "workspace:^" "@backstage/backend-defaults": "workspace:^" "@backstage/backend-plugin-api": "workspace:^" @@ -6465,17 +6466,14 @@ __metadata: "@backstage/types": "workspace:^" "@types/express": ^4.17.6 "@types/http-proxy-middleware": ^1.0.0 - "@types/supertest": ^2.0.8 "@types/uuid": ^9.0.0 "@types/yup": ^0.32.0 express: ^4.17.1 express-promise-router: ^4.1.0 http-proxy-middleware: ^2.0.0 morgan: ^1.10.0 - msw: ^1.0.0 + msw: ^2.0.0 node-fetch: ^2.6.7 - portfinder: ^1.0.32 - supertest: ^6.1.3 uuid: ^9.0.0 winston: ^3.2.1 yaml: ^2.0.0