Merge pull request #8769 from backstage/freben/techdocs-clients
Some more usage of the `FetchApi`
This commit is contained in:
@@ -359,6 +359,7 @@ export class FetchMiddlewares {
|
||||
identityApi: IdentityApi;
|
||||
config?: Config;
|
||||
urlPrefixAllowlist?: string[];
|
||||
allowUrl?: (url: string) => boolean;
|
||||
header?: {
|
||||
name: string;
|
||||
value: (backstageToken: string) => string;
|
||||
|
||||
@@ -56,14 +56,16 @@ export class FetchMiddlewares {
|
||||
*
|
||||
* The header injection only happens on allowlisted URLs. Per default, if the
|
||||
* `config` option is passed in, the `backend.baseUrl` is allowlisted, unless
|
||||
* the `urlPrefixAllowlist` option is passed in, in which case it takes
|
||||
* precedence. If you pass in neither config nor an allowlist, the middleware
|
||||
* will have no effect.
|
||||
* the `urlPrefixAllowlist` or `allowUrl` options are passed in, in which case
|
||||
* they take precedence. If you pass in neither config nor an
|
||||
* allowlist/callback, the middleware will have no effect since effectively no
|
||||
* request will match the (nonexistent) rules.
|
||||
*/
|
||||
static injectIdentityAuth(options: {
|
||||
identityApi: IdentityApi;
|
||||
config?: Config;
|
||||
urlPrefixAllowlist?: string[];
|
||||
allowUrl?: (url: string) => boolean;
|
||||
header?: {
|
||||
name: string;
|
||||
value: (backstageToken: string) => string;
|
||||
|
||||
+10
-40
@@ -23,7 +23,7 @@ describe('IdentityAuthInjectorFetchMiddleware', () => {
|
||||
const middleware = IdentityAuthInjectorFetchMiddleware.create({
|
||||
identityApi: undefined as any,
|
||||
});
|
||||
expect(middleware.urlPrefixAllowlist).toEqual([]);
|
||||
expect(middleware.allowUrl('anything')).toEqual(false);
|
||||
expect(middleware.headerName).toEqual('authorization');
|
||||
expect(middleware.headerValue('t')).toEqual('Bearer t');
|
||||
});
|
||||
@@ -36,7 +36,9 @@ describe('IdentityAuthInjectorFetchMiddleware', () => {
|
||||
}),
|
||||
header: { name: 'auth', value: t => `${t}!` },
|
||||
});
|
||||
expect(middleware.urlPrefixAllowlist).toEqual(['https://example.com/api']);
|
||||
expect(middleware.allowUrl('https://example.com/api')).toEqual(true);
|
||||
expect(middleware.allowUrl('https://example.com/api/sss')).toEqual(true);
|
||||
expect(middleware.allowUrl('https://evil.com/api')).toEqual(false);
|
||||
expect(middleware.headerName).toEqual('auth');
|
||||
expect(middleware.headerValue('t')).toEqual('t!');
|
||||
});
|
||||
@@ -49,10 +51,10 @@ describe('IdentityAuthInjectorFetchMiddleware', () => {
|
||||
}),
|
||||
urlPrefixAllowlist: ['https://a.com', 'http://b.com:8080/'],
|
||||
});
|
||||
expect(middleware.urlPrefixAllowlist).toEqual([
|
||||
'https://a.com',
|
||||
'http://b.com:8080',
|
||||
]);
|
||||
expect(middleware.allowUrl('https://a.com')).toEqual(true);
|
||||
expect(middleware.allowUrl('https://a.com:8080')).toEqual(false);
|
||||
expect(middleware.allowUrl('https://a.com/sss')).toEqual(true);
|
||||
expect(middleware.allowUrl('http://b.com:8080')).toEqual(true);
|
||||
});
|
||||
|
||||
it('injects the header only when a token is available', async () => {
|
||||
@@ -63,7 +65,7 @@ describe('IdentityAuthInjectorFetchMiddleware', () => {
|
||||
|
||||
const middleware = new IdentityAuthInjectorFetchMiddleware(
|
||||
identityApi,
|
||||
['https://example.com'],
|
||||
() => true,
|
||||
'Authorization',
|
||||
token => `Bearer ${token}`,
|
||||
);
|
||||
@@ -95,7 +97,7 @@ describe('IdentityAuthInjectorFetchMiddleware', () => {
|
||||
|
||||
const middleware = new IdentityAuthInjectorFetchMiddleware(
|
||||
identityApi,
|
||||
['https://example.com'],
|
||||
() => true,
|
||||
'Authorization',
|
||||
token => `Bearer ${token}`,
|
||||
);
|
||||
@@ -118,36 +120,4 @@ describe('IdentityAuthInjectorFetchMiddleware', () => {
|
||||
['authorization', 'do-not-clobber'],
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not affect requests outside the allowlist', async () => {
|
||||
const identityApi = {
|
||||
getCredentials: () => ({ token: 'token' }),
|
||||
} as unknown as IdentityApi;
|
||||
|
||||
const middleware = new IdentityAuthInjectorFetchMiddleware(
|
||||
identityApi,
|
||||
['https://example.com:8080/root'],
|
||||
'Authorization',
|
||||
token => `Bearer ${token}`,
|
||||
);
|
||||
|
||||
const inner = jest.fn();
|
||||
const outer = middleware.apply(inner);
|
||||
|
||||
await outer(new Request('https://example.com:8080/root'));
|
||||
await outer(new Request('https://example.com:8080/root/sub'));
|
||||
await outer(new Request('https://example.com:8080/root2'));
|
||||
await outer(new Request('https://example.com/root'));
|
||||
await outer(new Request('http://example.com:8080/root'));
|
||||
await outer(new Request('https://example.com/root'));
|
||||
|
||||
const no: string[][] = [];
|
||||
const yes: string[][] = [['authorization', 'Bearer token']];
|
||||
expect([...inner.mock.calls[0][0].headers.entries()]).toEqual(yes);
|
||||
expect([...inner.mock.calls[1][0].headers.entries()]).toEqual(yes);
|
||||
expect([...inner.mock.calls[2][0].headers.entries()]).toEqual(no);
|
||||
expect([...inner.mock.calls[3][0].headers.entries()]).toEqual(no);
|
||||
expect([...inner.mock.calls[4][0].headers.entries()]).toEqual(no);
|
||||
expect([...inner.mock.calls[5][0].headers.entries()]).toEqual(no);
|
||||
});
|
||||
});
|
||||
|
||||
+29
-14
@@ -27,24 +27,19 @@ export class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {
|
||||
identityApi: IdentityApi;
|
||||
config?: Config;
|
||||
urlPrefixAllowlist?: string[];
|
||||
allowUrl?: (url: string) => boolean;
|
||||
header?: {
|
||||
name: string;
|
||||
value: (backstageToken: string) => string;
|
||||
};
|
||||
}): IdentityAuthInjectorFetchMiddleware {
|
||||
const allowlist: string[] = [];
|
||||
if (options.urlPrefixAllowlist) {
|
||||
allowlist.push(...options.urlPrefixAllowlist);
|
||||
} else if (options.config) {
|
||||
allowlist.push(options.config.getString('backend.baseUrl'));
|
||||
}
|
||||
|
||||
const matcher = buildMatcher(options);
|
||||
const headerName = options.header?.name || 'authorization';
|
||||
const headerValue = options.header?.value || (token => `Bearer ${token}`);
|
||||
|
||||
return new IdentityAuthInjectorFetchMiddleware(
|
||||
options.identityApi,
|
||||
allowlist.map(prefix => prefix.replace(/\/$/, '')),
|
||||
matcher,
|
||||
headerName,
|
||||
headerValue,
|
||||
);
|
||||
@@ -52,7 +47,7 @@ export class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {
|
||||
|
||||
constructor(
|
||||
public readonly identityApi: IdentityApi,
|
||||
public readonly urlPrefixAllowlist: string[],
|
||||
public readonly allowUrl: (url: string) => boolean,
|
||||
public readonly headerName: string,
|
||||
public readonly headerValue: (pluginId: string) => string,
|
||||
) {}
|
||||
@@ -65,12 +60,9 @@ export class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {
|
||||
const { token } = await this.identityApi.getCredentials();
|
||||
if (
|
||||
request.headers.get(this.headerName) ||
|
||||
!this.urlPrefixAllowlist.some(
|
||||
prefix =>
|
||||
request.url === prefix || request.url.startsWith(`${prefix}/`),
|
||||
) ||
|
||||
typeof token !== 'string' ||
|
||||
!token
|
||||
!token ||
|
||||
!this.allowUrl(request.url)
|
||||
) {
|
||||
return next(input, init);
|
||||
}
|
||||
@@ -80,3 +72,26 @@ export class IdentityAuthInjectorFetchMiddleware implements FetchMiddleware {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function buildMatcher(options: {
|
||||
config?: Config;
|
||||
urlPrefixAllowlist?: string[];
|
||||
allowUrl?: (url: string) => boolean;
|
||||
}): (url: string) => boolean {
|
||||
if (options.allowUrl) {
|
||||
return options.allowUrl;
|
||||
} else if (options.urlPrefixAllowlist) {
|
||||
return buildPrefixMatcher(options.urlPrefixAllowlist);
|
||||
} else if (options.config) {
|
||||
return buildPrefixMatcher([options.config.getString('backend.baseUrl')]);
|
||||
}
|
||||
return () => false;
|
||||
}
|
||||
|
||||
function buildPrefixMatcher(prefixes: string[]): (url: string) => boolean {
|
||||
const trimmedPrefixes = prefixes.map(prefix => prefix.replace(/\/$/, ''));
|
||||
return url =>
|
||||
trimmedPrefixes.some(
|
||||
prefix => url === prefix || url.startsWith(`${prefix}/`),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,9 @@ import { ApiRef, createApiRef } from '../system';
|
||||
* @public
|
||||
*/
|
||||
export type FetchApi = {
|
||||
/**
|
||||
* The `fetch` implementation.
|
||||
*/
|
||||
fetch: typeof fetch;
|
||||
};
|
||||
|
||||
|
||||
@@ -13,10 +13,14 @@ import { AuthorizeResult } from '@backstage/plugin-permission-common';
|
||||
import { ComponentType } from 'react';
|
||||
import { Config } from '@backstage/config';
|
||||
import { ConfigApi } from '@backstage/core-plugin-api';
|
||||
import crossFetch from 'cross-fetch';
|
||||
import { DiscoveryApi } from '@backstage/core-plugin-api';
|
||||
import { ErrorApi } from '@backstage/core-plugin-api';
|
||||
import { ErrorApiError } from '@backstage/core-plugin-api';
|
||||
import { ErrorApiErrorContext } from '@backstage/core-plugin-api';
|
||||
import { ExternalRouteRef } from '@backstage/core-plugin-api';
|
||||
import { FetchApi } from '@backstage/core-plugin-api';
|
||||
import { IdentityApi } from '@backstage/core-plugin-api';
|
||||
import { JsonObject } from '@backstage/types';
|
||||
import { JsonValue } from '@backstage/types';
|
||||
import { Observable } from '@backstage/types';
|
||||
@@ -117,6 +121,31 @@ export type MockErrorApiOptions = {
|
||||
collect?: boolean;
|
||||
};
|
||||
|
||||
// @public
|
||||
export class MockFetchApi implements FetchApi {
|
||||
constructor(options?: MockFetchApiOptions);
|
||||
// (undocumented)
|
||||
get fetch(): typeof crossFetch;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface MockFetchApiOptions {
|
||||
baseImplementation?: undefined | 'none' | typeof crossFetch;
|
||||
injectIdentityAuth?:
|
||||
| undefined
|
||||
| {
|
||||
token: string;
|
||||
}
|
||||
| {
|
||||
identityApi: Pick<IdentityApi, 'getCredentials'>;
|
||||
};
|
||||
resolvePluginProtocol?:
|
||||
| undefined
|
||||
| {
|
||||
discoveryApi: Pick<DiscoveryApi, 'getBaseUrl'>;
|
||||
};
|
||||
}
|
||||
|
||||
// @public
|
||||
export class MockPermissionApi implements PermissionApi {
|
||||
constructor(
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
"@testing-library/jest-dom": "^5.10.1",
|
||||
"@testing-library/react": "^11.2.5",
|
||||
"@testing-library/user-event": "^13.1.8",
|
||||
"cross-fetch": "^3.0.6",
|
||||
"react-router": "6.0.0-beta.0",
|
||||
"react-router-dom": "6.0.0-beta.0",
|
||||
"zen-observable": "^0.8.15"
|
||||
@@ -52,7 +53,8 @@
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "^0.12.0-next.0",
|
||||
"@types/jest": "^26.0.7",
|
||||
"@types/node": "^14.14.32"
|
||||
"@types/node": "^14.14.32",
|
||||
"msw": "^0.35.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
||||
@@ -15,3 +15,4 @@
|
||||
*/
|
||||
|
||||
import '@testing-library/jest-dom';
|
||||
import 'cross-fetch/polyfill';
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright 2022 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 { rest } from 'msw';
|
||||
import { setupServer } from 'msw/node';
|
||||
import { setupRequestMockHandlers } from '../../msw';
|
||||
import { MockFetchApi } from './MockFetchApi';
|
||||
|
||||
describe('MockFetchApi', () => {
|
||||
const worker = setupServer();
|
||||
setupRequestMockHandlers(worker);
|
||||
|
||||
it('works with default constructor', async () => {
|
||||
worker.use(
|
||||
rest.get('http://example.com/data.json', (_, res, ctx) =>
|
||||
res(ctx.status(200), ctx.json({ a: 'foo' })),
|
||||
),
|
||||
);
|
||||
const m = new MockFetchApi();
|
||||
const response = await m.fetch('http://example.com/data.json');
|
||||
await expect(response.json()).resolves.toEqual({ a: 'foo' });
|
||||
});
|
||||
|
||||
describe('baseImplementation', () => {
|
||||
it('works with a mock implementation', async () => {
|
||||
const inner = jest.fn();
|
||||
const m = new MockFetchApi({ baseImplementation: inner });
|
||||
await m.fetch('http://example.com/data.json');
|
||||
expect(inner).lastCalledWith('http://example.com/data.json');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolvePluginProtocol', () => {
|
||||
it('works', async () => {
|
||||
const inner = jest.fn();
|
||||
const m = new MockFetchApi({
|
||||
baseImplementation: inner,
|
||||
resolvePluginProtocol: {
|
||||
discoveryApi: {
|
||||
getBaseUrl: async id => `https://blah.com/api/${id}`,
|
||||
},
|
||||
},
|
||||
});
|
||||
await m.fetch('plugin://the-plugin/a/data.json');
|
||||
expect(inner.mock.calls[0][0]).toBe(
|
||||
'https://blah.com/api/the-plugin/a/data.json',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('injectIdentityAuth', () => {
|
||||
it('works with token', async () => {
|
||||
const inner = jest.fn();
|
||||
const m = new MockFetchApi({
|
||||
baseImplementation: inner,
|
||||
injectIdentityAuth: { token: 'hello' },
|
||||
});
|
||||
await m.fetch('http://example.com/data.json');
|
||||
expect(inner.mock.calls[0][0].headers?.get('authorization')).toBe(
|
||||
'Bearer hello',
|
||||
);
|
||||
});
|
||||
|
||||
it('works with identityApi', async () => {
|
||||
const inner = jest.fn();
|
||||
const m = new MockFetchApi({
|
||||
baseImplementation: inner,
|
||||
injectIdentityAuth: {
|
||||
identityApi: {
|
||||
async getCredentials() {
|
||||
return { token: 'hello2' };
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await m.fetch('http://example.com/data.json');
|
||||
expect(inner.mock.calls[0][0].headers?.get('authorization')).toBe(
|
||||
'Bearer hello2',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,167 @@
|
||||
/*
|
||||
* Copyright 2022 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 {
|
||||
createFetchApi,
|
||||
FetchMiddleware,
|
||||
FetchMiddlewares,
|
||||
} from '@backstage/core-app-api';
|
||||
import {
|
||||
DiscoveryApi,
|
||||
FetchApi,
|
||||
IdentityApi,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import crossFetch, { Response } from 'cross-fetch';
|
||||
|
||||
/**
|
||||
* The options given when constructing a {@link MockFetchApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export interface MockFetchApiOptions {
|
||||
/**
|
||||
* Define the underlying base `fetch` implementation.
|
||||
*
|
||||
* @defaultValue undefined
|
||||
* @remarks
|
||||
*
|
||||
* Leaving out this parameter or passing `undefined`, makes the API use the
|
||||
* global `fetch` implementation to make real network requests.
|
||||
*
|
||||
* `'none'` swallows all calls and makes no requests at all.
|
||||
*
|
||||
* You can also pass in any `fetch` compatible callback, such as a
|
||||
* `jest.fn()`, if you want to use a custom implementation or to just track
|
||||
* and assert on calls.
|
||||
*/
|
||||
baseImplementation?: undefined | 'none' | typeof crossFetch;
|
||||
|
||||
/**
|
||||
* Add translation from `plugin://` URLs to concrete http(s) URLs, basically
|
||||
* simulating what
|
||||
* {@link @backstage/core-app-api#FetchMiddlewares.resolvePluginProtocol}
|
||||
* does.
|
||||
*
|
||||
* @defaultValue undefined
|
||||
* @remarks
|
||||
*
|
||||
* Leaving out this parameter or passing `undefined`, disables plugin protocol
|
||||
* translation.
|
||||
*
|
||||
* To enable the feature, pass in a discovery API which is then used to
|
||||
* resolve the URLs.
|
||||
*/
|
||||
resolvePluginProtocol?:
|
||||
| undefined
|
||||
| { discoveryApi: Pick<DiscoveryApi, 'getBaseUrl'> };
|
||||
|
||||
/**
|
||||
* Add token based Authorization headers to requests, basically simulating
|
||||
* what {@link @backstage/core-app-api#FetchMiddlewares.injectIdentityAuth}
|
||||
* does.
|
||||
*
|
||||
* @defaultValue undefined
|
||||
* @remarks
|
||||
*
|
||||
* Leaving out this parameter or passing `undefined`, disables auth injection.
|
||||
*
|
||||
* To enable the feature, pass in either a static token or an identity API
|
||||
* which is queried on each request for a token.
|
||||
*/
|
||||
injectIdentityAuth?:
|
||||
| undefined
|
||||
| { token: string }
|
||||
| { identityApi: Pick<IdentityApi, 'getCredentials'> };
|
||||
}
|
||||
|
||||
/**
|
||||
* A test helper implementation of {@link @backstage/core-plugin-api#FetchApi}.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export class MockFetchApi implements FetchApi {
|
||||
private readonly implementation: FetchApi;
|
||||
|
||||
/**
|
||||
* Creates a mock {@link @backstage/core-plugin-api#FetchApi}.
|
||||
*/
|
||||
constructor(options?: MockFetchApiOptions) {
|
||||
this.implementation = build(options);
|
||||
}
|
||||
|
||||
/** {@inheritdoc @backstage/core-plugin-api#FetchApi.fetch} */
|
||||
get fetch(): typeof crossFetch {
|
||||
return this.implementation.fetch;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Helpers
|
||||
//
|
||||
|
||||
function build(options?: MockFetchApiOptions): FetchApi {
|
||||
return createFetchApi({
|
||||
baseImplementation: baseImplementation(options),
|
||||
middleware: [
|
||||
resolvePluginProtocol(options),
|
||||
injectIdentityAuth(options),
|
||||
].filter((x): x is FetchMiddleware => Boolean(x)),
|
||||
});
|
||||
}
|
||||
|
||||
function baseImplementation(
|
||||
options: MockFetchApiOptions | undefined,
|
||||
): typeof crossFetch {
|
||||
const implementation = options?.baseImplementation;
|
||||
if (!implementation) {
|
||||
return crossFetch;
|
||||
} else if (implementation === 'none') {
|
||||
return () => Promise.resolve(new Response());
|
||||
}
|
||||
return implementation;
|
||||
}
|
||||
|
||||
function resolvePluginProtocol(
|
||||
allOptions: MockFetchApiOptions | undefined,
|
||||
): FetchMiddleware | undefined {
|
||||
const options = allOptions?.resolvePluginProtocol;
|
||||
if (!options) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return FetchMiddlewares.resolvePluginProtocol({
|
||||
discoveryApi: options.discoveryApi,
|
||||
});
|
||||
}
|
||||
|
||||
function injectIdentityAuth(
|
||||
allOptions: MockFetchApiOptions | undefined,
|
||||
): FetchMiddleware | undefined {
|
||||
const options = allOptions?.injectIdentityAuth;
|
||||
if (!options) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const identityApi: Pick<IdentityApi, 'getCredentials'> =
|
||||
'token' in options
|
||||
? { getCredentials: async () => ({ token: options.token }) }
|
||||
: options.identityApi;
|
||||
|
||||
return FetchMiddlewares.injectIdentityAuth({
|
||||
identityApi: identityApi as IdentityApi,
|
||||
allowUrl: () => true,
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2022 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.
|
||||
*/
|
||||
|
||||
export { MockFetchApi } from './MockFetchApi';
|
||||
export type { MockFetchApiOptions } from './MockFetchApi';
|
||||
@@ -17,5 +17,6 @@
|
||||
export * from './AnalyticsApi';
|
||||
export * from './ConfigApi';
|
||||
export * from './ErrorApi';
|
||||
export * from './FetchApi';
|
||||
export * from './PermissionApi';
|
||||
export * from './StorageApi';
|
||||
|
||||
@@ -14,17 +14,4 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets up handlers for request mocking
|
||||
* @public
|
||||
* @param worker - service worker
|
||||
*/
|
||||
export function setupRequestMockHandlers(worker: {
|
||||
listen: (t: any) => void;
|
||||
close: () => void;
|
||||
resetHandlers: () => void;
|
||||
}) {
|
||||
beforeAll(() => worker.listen({ onUnhandledRequest: 'error' }));
|
||||
afterAll(() => worker.close());
|
||||
afterEach(() => worker.resetHandlers());
|
||||
}
|
||||
export { setupRequestMockHandlers } from './setupRequestMockHandlers';
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright 2020 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Sets up handlers for request mocking
|
||||
* @public
|
||||
* @param worker - service worker
|
||||
*/
|
||||
export function setupRequestMockHandlers(worker: {
|
||||
listen: (t: any) => void;
|
||||
close: () => void;
|
||||
resetHandlers: () => void;
|
||||
}) {
|
||||
beforeAll(() => worker.listen({ onUnhandledRequest: 'error' }));
|
||||
afterAll(() => worker.close());
|
||||
afterEach(() => worker.resetHandlers());
|
||||
}
|
||||
Reference in New Issue
Block a user