diff --git a/packages/core-app-api/src/apis/implementations/AuthErrorApi/SignInAuthErrorApi.test.ts b/packages/core-app-api/src/apis/implementations/AuthErrorApi/SignInAuthErrorApi.test.ts new file mode 100644 index 0000000000..c4d6fe83de --- /dev/null +++ b/packages/core-app-api/src/apis/implementations/AuthErrorApi/SignInAuthErrorApi.test.ts @@ -0,0 +1,94 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DiscoveryApi } from '@backstage/core-plugin-api'; +import { serializeError } from '@backstage/errors'; +import { SignInAuthErrorApi } from './SignInAuthErrorApi'; + +describe('SignInAuthErrorApi', () => { + const mockDiscoveryApi = { + getBaseUrl: jest.fn(), + } as unknown as jest.Mocked; + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should create an instance of SignInAuthErrorApi', async () => { + const api = SignInAuthErrorApi.create({ + discovery: mockDiscoveryApi, + }); + + mockDiscoveryApi.getBaseUrl.mockResolvedValue( + 'http://localhost:7007/api/auth', + ); + + expect(api).toBeInstanceOf(SignInAuthErrorApi); + }); + + it('should return an error when the cookie returns an error object', async () => { + const errorObject = { + name: 'TestError', + message: 'This is a test error', + }; + const serializedError = serializeError(errorObject); + mockDiscoveryApi.getBaseUrl.mockResolvedValue( + 'http://localhost:7000/api/auth', + ); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue(serializedError), + } as unknown as Response); + + const api = SignInAuthErrorApi.create({ discovery: mockDiscoveryApi }); + const error = await api.getSignInAuthError(); + + expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('auth'); + expect(fetch).toHaveBeenCalledWith( + 'http://localhost:7000/api/auth/.backstage/error', + { + credentials: 'include', + }, + ); + expect(error).toBeInstanceOf(Error); + expect((error as Error).name).toEqual(errorObject.name); + expect((error as Error).message).toEqual(errorObject.message); + }); + + it('should return undefined when the backend does not return an error object', async () => { + mockDiscoveryApi.getBaseUrl.mockResolvedValue( + 'http://localhost:7000/api/auth', + ); + + global.fetch = jest.fn().mockResolvedValue({ + ok: true, + json: jest.fn().mockResolvedValue(undefined), + } as unknown as Response); + + const api = SignInAuthErrorApi.create({ discovery: mockDiscoveryApi }); + const error = await api.getSignInAuthError(); + + expect(mockDiscoveryApi.getBaseUrl).toHaveBeenCalledWith('auth'); + expect(fetch).toHaveBeenCalledWith( + 'http://localhost:7000/api/auth/.backstage/error', + { + credentials: 'include', + }, + ); + expect(error).toBeUndefined(); + }); +}); diff --git a/plugins/auth-backend/src/service/createCookieAuthErrorMiddleware.test.ts b/plugins/auth-backend/src/service/createCookieAuthErrorMiddleware.test.ts new file mode 100644 index 0000000000..1d270a953f --- /dev/null +++ b/plugins/auth-backend/src/service/createCookieAuthErrorMiddleware.test.ts @@ -0,0 +1,67 @@ +/* + * Copyright 2024 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import express from 'express'; +import request from 'supertest'; +import { createCookieAuthErrorMiddleware } from './createCookieAuthErrorMiddleware'; + +const AUTH_ERROR_COOKIE = 'auth-error'; + +describe('createCookieAuthErrorMiddleware', () => { + let app: express.Express; + + beforeEach(() => { + app = express(); + app.use(express.json()); + app.use(express.urlencoded({ extended: true })); + + // Mock cookie parser middleware + app.use((req, _, next) => { + req.cookies = {}; + const cookieHeader = req.headers.cookie; + if (cookieHeader) { + const cookies = cookieHeader.split(';'); + cookies.forEach(cookie => { + const [name, ...rest] = cookie.split('='); + req.cookies[name.trim()] = decodeURIComponent(rest.join('=')); + }); + } + next(); + }); + + app.use( + createCookieAuthErrorMiddleware( + 'http://localhost:3000', + 'http://localhost:7000', + ), + ); + }); + + it('should return cookie content if error cookie exists', async () => { + const error = 'test'; + const res = await request(app) + .get('/.backstage/error') + .set('Cookie', `${AUTH_ERROR_COOKIE}=${encodeURIComponent(error)}`); + + expect(res.status).toBe(200); + expect(res.body).toEqual('test'); + }); + + it('should return 404 if error cookie does not exist', async () => { + const res = await request(app).get('/.backstage/error'); + expect(res.status).toBe(404); + }); +}); diff --git a/plugins/auth-backend/src/service/createCookieAuthErrorMiddleware.ts b/plugins/auth-backend/src/service/createCookieAuthErrorMiddleware.ts index 325126d831..30724fc3cd 100644 --- a/plugins/auth-backend/src/service/createCookieAuthErrorMiddleware.ts +++ b/plugins/auth-backend/src/service/createCookieAuthErrorMiddleware.ts @@ -44,7 +44,7 @@ export function createCookieAuthErrorMiddleware( }); res.status(200).json(error); } else { - res.status(404); + res.status(404).end(); } });