implement discovery too

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2024-10-09 16:33:39 +02:00
parent d081861694
commit 3afafea87b
12 changed files with 129 additions and 75 deletions
@@ -14,15 +14,14 @@
* limitations under the License.
*/
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { mockApis } from '@backstage/test-utils';
import { PluginProtocolResolverFetchMiddleware } from './PluginProtocolResolverFetchMiddleware';
describe('PluginProtocolResolverFetchMiddleware', () => {
it.each([['https://passthrough.com/a']])(
'passes through regular URLs, %p',
async url => {
const resolve = jest.fn();
const discoveryApi = { getBaseUrl: resolve } as unknown as DiscoveryApi;
const discoveryApi = mockApis.discovery();
const middleware = new PluginProtocolResolverFetchMiddleware(
discoveryApi,
);
@@ -31,7 +30,7 @@ describe('PluginProtocolResolverFetchMiddleware', () => {
await outer(url);
expect(inner.mock.calls[0][0]).toBe(url);
expect(resolve).not.toHaveBeenCalled();
expect(discoveryApi.getBaseUrl).not.toHaveBeenCalled();
},
);
@@ -76,30 +75,29 @@ describe('PluginProtocolResolverFetchMiddleware', () => {
])(
'resolves backstage URLs, %p',
async (original, host, resolved, result) => {
const resolve = jest.fn();
const discoveryApi = { getBaseUrl: resolve } as unknown as DiscoveryApi;
const discoveryApi = mockApis.discovery.mock({
getBaseUrl: async () => resolved,
});
const middleware = new PluginProtocolResolverFetchMiddleware(
discoveryApi,
);
const inner = jest.fn();
const outer = middleware.apply(inner);
resolve.mockResolvedValueOnce(resolved);
await outer(original);
expect(inner.mock.calls[0][0]).toBe(result);
expect(resolve).toHaveBeenLastCalledWith(host);
expect(discoveryApi.getBaseUrl).toHaveBeenLastCalledWith(host);
},
);
it('properly supports transferring request bodies too', async () => {
const resolve = jest.fn();
const discoveryApi = { getBaseUrl: resolve } as unknown as DiscoveryApi;
const discoveryApi = mockApis.discovery.mock({
getBaseUrl: async () => 'https://elsewhere.com',
});
const middleware = new PluginProtocolResolverFetchMiddleware(discoveryApi);
const inner = jest.fn();
const outer = middleware.apply(inner);
resolve.mockResolvedValue('https://elsewhere.com');
await outer('plugin://a', {
method: 'POST',
body: '123',
@@ -878,9 +878,9 @@ describe('Integration Test', () => {
}),
}),
};
const discoveryApiMock = {
getBaseUrl: jest.fn().mockResolvedValue('http://localhost:7007/app'),
};
const discoveryApiMock = mockApis.discovery.mock({
getBaseUrl: async () => 'http://localhost:7007/app',
});
const app = new AppManager({
icons,
@@ -20,6 +20,7 @@ import { rest } from 'msw';
import { setupServer } from 'msw/node';
import {
TestApiProvider,
mockApis,
registerMswTestHooks,
wrapInTestApp,
} from '@backstage/test-utils';
@@ -37,9 +38,7 @@ describe('ProxiedSignInPage', () => {
apis={[
[
discoveryApiRef,
{
getBaseUrl: async () => 'http://example.com/api/auth',
},
mockApis.discovery({ baseUrl: 'http://example.com' }),
],
]}
>
+39 -16
View File
@@ -115,6 +115,25 @@ export namespace mockApis {
const mock: (partialImpl?: Partial<Config> | undefined) => ApiMock<Config>;
}
// (undocumented)
export function discovery(options?: { baseUrl?: string }): jest.Mocked<{
getBaseUrl(pluginId: string): Promise<string>;
}>;
// (undocumented)
export namespace discovery {
const // (undocumented)
factory: (
options?:
| {
baseUrl?: string | undefined;
}
| undefined,
) => ApiFactory<DiscoveryApi, DiscoveryApi, {}>;
const // (undocumented)
mock: (
partialImpl?: Partial<DiscoveryApi> | undefined,
) => ApiMock<DiscoveryApi>;
}
// (undocumented)
export function identity(options?: {
userEntityRef?: string;
ownershipEntityRefs?: string[];
@@ -421,20 +440,24 @@ export function wrapInTestApp(
// src/testUtils/apis/mockApis.d.ts:48:5 - (ae-undocumented) Missing documentation for "analytics".
// src/testUtils/apis/mockApis.d.ts:49:15 - (ae-undocumented) Missing documentation for "factory".
// src/testUtils/apis/mockApis.d.ts:50:15 - (ae-undocumented) Missing documentation for "mock".
// src/testUtils/apis/mockApis.d.ts:101:5 - (ae-undocumented) Missing documentation for "identity".
// src/testUtils/apis/mockApis.d.ts:109:5 - (ae-undocumented) Missing documentation for "identity".
// src/testUtils/apis/mockApis.d.ts:110:15 - (ae-undocumented) Missing documentation for "factory".
// src/testUtils/apis/mockApis.d.ts:118:15 - (ae-undocumented) Missing documentation for "mock".
// src/testUtils/apis/mockApis.d.ts:120:5 - (ae-undocumented) Missing documentation for "permission".
// src/testUtils/apis/mockApis.d.ts:123:5 - (ae-undocumented) Missing documentation for "permission".
// src/testUtils/apis/mockApis.d.ts:124:15 - (ae-undocumented) Missing documentation for "factory".
// src/testUtils/apis/mockApis.d.ts:127:15 - (ae-undocumented) Missing documentation for "mock".
// src/testUtils/apis/mockApis.d.ts:129:5 - (ae-undocumented) Missing documentation for "storage".
// src/testUtils/apis/mockApis.d.ts:132:5 - (ae-undocumented) Missing documentation for "storage".
// src/testUtils/apis/mockApis.d.ts:133:15 - (ae-undocumented) Missing documentation for "factory".
// src/testUtils/apis/mockApis.d.ts:136:15 - (ae-undocumented) Missing documentation for "mock".
// src/testUtils/apis/mockApis.d.ts:138:5 - (ae-undocumented) Missing documentation for "translation".
// src/testUtils/apis/mockApis.d.ts:139:5 - (ae-undocumented) Missing documentation for "translation".
// src/testUtils/apis/mockApis.d.ts:140:15 - (ae-undocumented) Missing documentation for "factory".
// src/testUtils/apis/mockApis.d.ts:141:15 - (ae-undocumented) Missing documentation for "mock".
// src/testUtils/apis/mockApis.d.ts:101:5 - (ae-undocumented) Missing documentation for "discovery".
// src/testUtils/apis/mockApis.d.ts:106:5 - (ae-undocumented) Missing documentation for "discovery".
// src/testUtils/apis/mockApis.d.ts:107:15 - (ae-undocumented) Missing documentation for "factory".
// src/testUtils/apis/mockApis.d.ts:110:15 - (ae-undocumented) Missing documentation for "mock".
// src/testUtils/apis/mockApis.d.ts:112:5 - (ae-undocumented) Missing documentation for "identity".
// src/testUtils/apis/mockApis.d.ts:120:5 - (ae-undocumented) Missing documentation for "identity".
// src/testUtils/apis/mockApis.d.ts:121:15 - (ae-undocumented) Missing documentation for "factory".
// src/testUtils/apis/mockApis.d.ts:129:15 - (ae-undocumented) Missing documentation for "mock".
// src/testUtils/apis/mockApis.d.ts:131:5 - (ae-undocumented) Missing documentation for "permission".
// src/testUtils/apis/mockApis.d.ts:134:5 - (ae-undocumented) Missing documentation for "permission".
// src/testUtils/apis/mockApis.d.ts:135:15 - (ae-undocumented) Missing documentation for "factory".
// src/testUtils/apis/mockApis.d.ts:138:15 - (ae-undocumented) Missing documentation for "mock".
// src/testUtils/apis/mockApis.d.ts:140:5 - (ae-undocumented) Missing documentation for "storage".
// src/testUtils/apis/mockApis.d.ts:143:5 - (ae-undocumented) Missing documentation for "storage".
// src/testUtils/apis/mockApis.d.ts:144:15 - (ae-undocumented) Missing documentation for "factory".
// src/testUtils/apis/mockApis.d.ts:147:15 - (ae-undocumented) Missing documentation for "mock".
// src/testUtils/apis/mockApis.d.ts:149:5 - (ae-undocumented) Missing documentation for "translation".
// src/testUtils/apis/mockApis.d.ts:150:5 - (ae-undocumented) Missing documentation for "translation".
// src/testUtils/apis/mockApis.d.ts:151:15 - (ae-undocumented) Missing documentation for "factory".
// src/testUtils/apis/mockApis.d.ts:152:15 - (ae-undocumented) Missing documentation for "mock".
```
@@ -79,6 +79,34 @@ describe('mockApis', () => {
});
});
describe('discovery', () => {
it('can create an instance and make assertions on it', async () => {
const empty = mockApis.discovery();
await expect(empty.getBaseUrl('catalog')).resolves.toBe(
'http://example.com/api/catalog',
);
expect(empty.getBaseUrl).toHaveBeenCalledTimes(1);
const notEmpty = mockApis.discovery({ baseUrl: 'https://other.net' });
await expect(notEmpty.getBaseUrl('catalog')).resolves.toBe(
'https://other.net/api/catalog',
);
expect(notEmpty.getBaseUrl).toHaveBeenCalledTimes(1);
});
it('can create a mock and make assertions on it', async () => {
const empty = mockApis.discovery.mock();
expect(empty.getBaseUrl('catalog')).toBeUndefined();
expect(empty.getBaseUrl).toHaveBeenCalledTimes(1);
const notEmpty = mockApis.discovery.mock({
getBaseUrl: async () => 'replaced',
});
await expect(notEmpty.getBaseUrl('catalog')).resolves.toBe('replaced');
expect(notEmpty.getBaseUrl).toHaveBeenCalledTimes(1);
});
});
describe('identity', () => {
it('can create an instance and make assertions on it', async () => {
const empty = mockApis.identity();
@@ -20,14 +20,20 @@ import {
ApiFactory,
ApiRef,
ConfigApi,
DiscoveryApi,
IdentityApi,
StorageApi,
analyticsApiRef,
configApiRef,
createApiFactory,
discoveryApiRef,
identityApiRef,
storageApiRef,
} from '@backstage/core-plugin-api';
import {
TranslationApi,
translationApiRef,
} from '@backstage/core-plugin-api/alpha';
import {
AuthorizeResult,
EvaluatePermissionRequest,
@@ -40,10 +46,6 @@ import { JsonObject } from '@backstage/types';
import { ApiMock } from './ApiMock';
import { MockPermissionApi } from './PermissionApi';
import { MockStorageApi } from './StorageApi';
import {
TranslationApi,
translationApiRef,
} from '@backstage/core-plugin-api/alpha';
import { MockTranslationApi } from './TranslationApi';
/** @internal */
@@ -200,6 +202,26 @@ export namespace mockApis {
}));
}
const discoveryMockSkeleton = (): jest.Mocked<DiscoveryApi> => ({
getBaseUrl: jest.fn(),
});
export function discovery(options?: { baseUrl?: string }) {
const baseUrl = options?.baseUrl ?? 'http://example.com';
return simpleInstance(
discoveryApiRef,
{
async getBaseUrl(pluginId: string) {
return `${baseUrl}/api/${pluginId}`;
},
},
discoveryMockSkeleton,
);
}
export namespace discovery {
export const factory = simpleFactory(discoveryApiRef, discovery);
export const mock = simpleMock(discoveryApiRef, discoveryMockSkeleton);
}
const identityMockSkeleton = (): jest.Mocked<IdentityApi> => ({
getBackstageIdentity: jest.fn(),
getCredentials: jest.fn(),
@@ -30,11 +30,7 @@ import {
} from '@backstage/core-plugin-api';
describe('CookieAuthRefreshProvider', () => {
const discoveryApiMock = {
getBaseUrl: jest
.fn()
.mockResolvedValue('http://localhost:7000/api/techdocs'),
};
const discoveryApiMock = mockApis.discovery();
function getExpiresAtInFuture() {
const tenMinutesInMilliseconds = 10 * 60 * 1000;
@@ -118,7 +114,7 @@ describe('CookieAuthRefreshProvider', () => {
await waitFor(() =>
expect(fetchApiMock.fetch).toHaveBeenCalledWith(
'http://localhost:7000/api/techdocs/.backstage/auth/v1/cookie',
'http://example.com/api/techdocs/.backstage/auth/v1/cookie',
{ credentials: 'include' },
),
);
@@ -17,15 +17,11 @@
import React from 'react';
import { renderHook, waitFor } from '@testing-library/react';
import { fetchApiRef, discoveryApiRef } from '@backstage/core-plugin-api';
import { TestApiProvider } from '@backstage/test-utils';
import { TestApiProvider, mockApis } from '@backstage/test-utils';
import { useCookieAuthRefresh } from './useCookieAuthRefresh';
describe('useCookieAuthRefresh', () => {
const discoveryApiMock = {
getBaseUrl: jest
.fn()
.mockResolvedValue('http://localhost:7000/api/techdocs'),
};
const discoveryApiMock = mockApis.discovery();
const now = 1710316886171;
const tenMinutesInMilliseconds = 10 * 60 * 1000;
@@ -269,7 +265,7 @@ describe('useCookieAuthRefresh', () => {
await waitFor(() =>
expect(fetchApiMock.fetch).toHaveBeenCalledWith(
'http://localhost:7000/api/techdocs/.backstage/auth/v1/cookie',
'http://example.com/api/techdocs/.backstage/auth/v1/cookie',
{ credentials: 'include' },
),
);
@@ -14,8 +14,9 @@
* limitations under the License.
*/
import { DiscoveryApi, discoveryApiRef } from '@backstage/core-plugin-api';
import { discoveryApiRef } from '@backstage/core-plugin-api';
import {
mockApis,
renderInTestApp,
TestApiProvider,
textContentMatcher,
@@ -37,9 +38,7 @@ describe('PodExecTerminal', () => {
const podName = 'pod1';
const podNamespace = 'podNamespace';
const mockDiscoveryApi: Partial<DiscoveryApi> = {
getBaseUrl: () => Promise.resolve('http://localhost'),
};
const mockDiscoveryApi = mockApis.discovery();
it('Should render an XTerm web terminal', async () => {
await renderInTestApp(
@@ -62,7 +61,7 @@ describe('PodExecTerminal', () => {
it('Should connect to WebSocket server & render response', async () => {
const server = new WS(
'ws://localhost/proxy/api/v1/namespaces/podNamespace/pods/pod1/exec?container=container2&stdin=true&stdout=true&stderr=true&tty=true&command=%2Fbin%2Fsh',
'ws://example.com/api/kubernetes/proxy/api/v1/namespaces/podNamespace/pods/pod1/exec?container=container2&stdin=true&stdout=true&stderr=true&tty=true&command=%2Fbin%2Fsh',
);
await renderInTestApp(
@@ -17,19 +17,21 @@
import React from 'react';
import { screen } from '@testing-library/react';
import { TestApiProvider, renderInTestApp } from '@backstage/test-utils';
import {
TestApiProvider,
mockApis,
renderInTestApp,
} from '@backstage/test-utils';
import '@testing-library/jest-dom';
import { PodDrawer } from './PodDrawer';
import { DiscoveryApi, discoveryApiRef } from '@backstage/core-plugin-api';
import { discoveryApiRef } from '@backstage/core-plugin-api';
jest.mock('../../../hooks/useIsPodExecTerminalSupported');
describe('PodDrawer', () => {
it('Should show title and container names', async () => {
const mockDiscoveryApi: Partial<DiscoveryApi> = {
getBaseUrl: () => Promise.resolve('http://localhost'),
};
const mockDiscoveryApi = mockApis.discovery();
await renderInTestApp(
<TestApiProvider apis={[[discoveryApiRef, mockDiscoveryApi]]}>
@@ -14,24 +14,21 @@
* limitations under the License.
*/
import { DiscoveryApi } from '@backstage/core-plugin-api';
import { mockApis } from '@backstage/test-utils';
import WS from 'jest-websocket-mock';
import { SignalClient } from './SignalClient';
describe('SignalsClient', () => {
const baseUrlFunction = jest.fn();
const identity = mockApis.identity({ token: '12345' });
const discoveryApi = {
getBaseUrl: baseUrlFunction,
} as unknown as DiscoveryApi;
const discoveryApi = mockApis.discovery({ baseUrl: 'http://localhost:1234' });
let server: WS;
beforeEach(async () => {
jest.clearAllMocks();
baseUrlFunction.mockResolvedValue('http://localhost:1234');
server = new WS('ws://localhost:1234', { jsonProtocol: true });
server = new WS('ws://localhost:1234/api/signals', {
jsonProtocol: true,
});
});
afterEach(() => {
@@ -81,12 +81,6 @@ const techdocsStorageApiMock: jest.Mocked<typeof techdocsStorageApiRef.T> = {
syncEntityDocs: jest.fn(),
};
const discoveryApiMock = {
getBaseUrl: jest
.fn()
.mockResolvedValue('https://localhost:7000/api/techdocs'),
};
const fetchApiMock = {
fetch: jest.fn().mockResolvedValue({
ok: true,
@@ -116,7 +110,7 @@ const Wrapper = ({ children }: { children: React.ReactNode }) => {
<TestApiProvider
apis={[
[fetchApiRef, fetchApiMock],
[discoveryApiRef, discoveryApiMock],
[discoveryApiRef, mockApis.discovery()],
[scmIntegrationsApiRef, {}],
[configApiRef, configApi],
[techdocsApiRef, techdocsApiMock],