Merge pull request #27070 from backstage/freben/catalog-expect-too

rewrite some more tests to use actual fake catalog instead of mock
This commit is contained in:
Fredrik Adelöw
2024-10-21 15:44:32 +02:00
committed by GitHub
10 changed files with 144 additions and 179 deletions
@@ -17,11 +17,11 @@
import { TokenManager } from '@backstage/backend-common';
import {
RELATION_MEMBER_OF,
UserEntity,
UserEntityV1alpha1,
} from '@backstage/catalog-model';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
import { CatalogIdentityClient } from './CatalogIdentityClient';
import { mockServices } from '@backstage/backend-test-utils';
describe('CatalogIdentityClient', () => {
const tokenManager: jest.Mocked<TokenManager> = {
@@ -32,14 +32,25 @@ describe('CatalogIdentityClient', () => {
afterEach(() => jest.resetAllMocks());
it('findUser passes through the correct search params', async () => {
const catalogApi = catalogServiceMock.mock({
getEntities: jest
.fn()
.mockResolvedValueOnce({ items: [{} as UserEntity] }),
const catalogApi = catalogServiceMock({
entities: [
{
apiVersion: 'backstage.io/v1beta1',
kind: 'User',
metadata: {
name: 'user',
namespace: 'default',
annotations: { key: 'value' },
},
spec: {},
},
],
});
jest.spyOn(catalogApi, 'getEntities');
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
const client = new CatalogIdentityClient({
discovery: {} as any,
discovery: mockServices.discovery(),
catalogApi,
tokenManager,
});
@@ -94,9 +105,8 @@ describe('CatalogIdentityClient', () => {
],
},
];
const catalogApi = catalogServiceMock.mock({
getEntities: jest.fn().mockResolvedValueOnce({ items: mockUsers }),
});
const catalogApi = catalogServiceMock({ entities: mockUsers });
jest.spyOn(catalogApi, 'getEntities');
tokenManager.getToken.mockResolvedValue({ token: 'my-token' });
const client = new CatalogIdentityClient({
@@ -26,9 +26,8 @@ describe('CatalogAuthResolverContext', () => {
jest.clearAllMocks();
});
const catalogApi = catalogServiceMock.mock({
getEntities: jest.fn().mockResolvedValue({ items: [] }),
});
const catalogApi = catalogServiceMock();
jest.spyOn(catalogApi, 'getEntities');
it('adds kind to filter when missing', async () => {
const context = CatalogAuthResolverContext.create({
@@ -153,7 +153,7 @@ describe('BitbucketCloudEntityProvider', () => {
it('no provider config', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock.mock();
const catalogApi = catalogServiceMock();
const config = new ConfigReader({});
const events = DefaultEventsService.create({ logger });
const providers = BitbucketCloudEntityProvider.fromConfig(config, {
@@ -169,7 +169,7 @@ describe('BitbucketCloudEntityProvider', () => {
it('single simple provider config', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock.mock();
const catalogApi = catalogServiceMock();
const events = DefaultEventsService.create({ logger });
const providers = BitbucketCloudEntityProvider.fromConfig(simpleConfig, {
auth,
@@ -187,7 +187,7 @@ describe('BitbucketCloudEntityProvider', () => {
it('fail without schedule and scheduler', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock.mock();
const catalogApi = catalogServiceMock();
const events = DefaultEventsService.create({ logger });
expect(() =>
@@ -202,7 +202,7 @@ describe('BitbucketCloudEntityProvider', () => {
it('fail with scheduler but no schedule config', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock.mock();
const catalogApi = catalogServiceMock();
const events = DefaultEventsService.create({ logger });
const scheduler = mockServices.scheduler.mock();
const config = new ConfigReader({
@@ -230,7 +230,7 @@ describe('BitbucketCloudEntityProvider', () => {
it('single simple provider config with schedule in config', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock.mock();
const catalogApi = catalogServiceMock();
const events = DefaultEventsService.create({ logger });
const scheduler = mockServices.scheduler.mock();
const config = new ConfigReader({
@@ -263,7 +263,7 @@ describe('BitbucketCloudEntityProvider', () => {
it('multiple provider configs', () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock.mock();
const catalogApi = catalogServiceMock();
const config = new ConfigReader({
catalog: {
providers: {
@@ -298,7 +298,7 @@ describe('BitbucketCloudEntityProvider', () => {
it('apply full update on scheduled execution', async () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock.mock();
const catalogApi = catalogServiceMock();
const events = DefaultEventsService.create({ logger });
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
@@ -610,7 +610,8 @@ describe('BitbucketCloudEntityProvider', () => {
it('no onRepoPush update on non-matching workspace slug', async () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock.mock();
const catalogApi = catalogServiceMock();
jest.spyOn(catalogApi, 'refreshEntity');
const events = DefaultEventsService.create({ logger });
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
@@ -641,7 +642,8 @@ describe('BitbucketCloudEntityProvider', () => {
it('no onRepoPush update on non-matching repo slug', async () => {
const auth = mockServices.auth.mock();
const catalogApi = catalogServiceMock.mock();
const catalogApi = catalogServiceMock();
jest.spyOn(catalogApi, 'refreshEntity');
const events = DefaultEventsService.create({ logger });
const provider = BitbucketCloudEntityProvider.fromConfig(defaultConfig, {
auth,
@@ -14,7 +14,6 @@
* limitations under the License.
*/
import { Config, ConfigReader } from '@backstage/config';
import {
ANNOTATION_KUBERNETES_API_SERVER,
ANNOTATION_KUBERNETES_API_SERVER_CA,
@@ -28,8 +27,8 @@ import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
describe('getCombinedClusterSupplier', () => {
it('should retrieve cluster details from config', async () => {
const config: Config = new ConfigReader(
{
const config = mockServices.rootConfig({
data: {
kubernetes: {
clusterLocatorMethods: [
{
@@ -51,8 +50,7 @@ describe('getCombinedClusterSupplier', () => {
],
},
},
'ctx',
);
});
const mockStrategy: jest.Mocked<AuthenticationStrategy> = {
getCredential: jest.fn(),
validateCluster: jest.fn().mockReturnValue([]),
@@ -98,17 +96,16 @@ describe('getCombinedClusterSupplier', () => {
});
it('throws an error when using an unsupported cluster locator', async () => {
const config: Config = new ConfigReader(
{ kubernetes: { clusterLocatorMethods: [{ type: 'magic' }] } },
'ctx',
);
const config = mockServices.rootConfig({
data: { kubernetes: { clusterLocatorMethods: [{ type: 'magic' }] } },
});
const auth = mockServices.auth();
expect(() =>
getCombinedClusterSupplier(
config,
catalogServiceMock.mock(),
catalogServiceMock(),
new DispatchStrategy({ authStrategyMap: {} }),
mockServices.logger.mock(),
undefined,
@@ -122,8 +119,8 @@ describe('getCombinedClusterSupplier', () => {
it('logs a warning when two clusters have the same name', async () => {
const logger = mockServices.logger.mock();
const warn = jest.spyOn(logger, 'warn');
const config: Config = new ConfigReader(
{
const config = mockServices.rootConfig({
data: {
kubernetes: {
clusterLocatorMethods: [
{
@@ -136,8 +133,7 @@ describe('getCombinedClusterSupplier', () => {
],
},
},
'ctx',
);
});
const mockStrategy: jest.Mocked<AuthenticationStrategy> = {
getCredential: jest.fn(),
validateCluster: jest.fn().mockReturnValue([]),
@@ -35,6 +35,10 @@ const DEFAULT_ENTITIES_RESPONSE = {
items: [
{
kind: 'User',
metadata: {
name: 'mock',
namespace: 'default',
},
spec: {
profile: {
email: 'mock@backstage.io',
@@ -64,7 +68,6 @@ const DEFAULT_SENDMAIL_CONFIG = {
describe('NotificationsEmailProcessor', () => {
const logger = mockServices.logger.mock();
const auth = mockServices.auth();
const catalog = catalogServiceMock.mock();
beforeEach(() => {
jest.resetAllMocks();
@@ -93,7 +96,7 @@ describe('NotificationsEmailProcessor', () => {
},
},
}),
catalog,
catalogServiceMock(),
auth,
);
@@ -140,7 +143,7 @@ describe('NotificationsEmailProcessor', () => {
},
},
}),
catalog,
catalogServiceMock(),
auth,
);
@@ -184,7 +187,7 @@ describe('NotificationsEmailProcessor', () => {
},
},
}),
catalog,
catalogServiceMock(),
auth,
);
@@ -212,13 +215,10 @@ describe('NotificationsEmailProcessor', () => {
it('should send user email', async () => {
(createTransport as jest.Mock).mockReturnValue(mockTransport);
catalog.getEntityByRef.mockResolvedValue(
DEFAULT_ENTITIES_RESPONSE.items[0],
);
const processor = new NotificationsEmailProcessor(
logger,
mockServices.rootConfig({ data: DEFAULT_SENDMAIL_CONFIG }),
catalog,
catalogServiceMock({ entities: [DEFAULT_ENTITIES_RESPONSE.items[0]] }),
auth,
);
@@ -248,7 +248,6 @@ describe('NotificationsEmailProcessor', () => {
it('should send email to all', async () => {
(createTransport as jest.Mock).mockReturnValue(mockTransport);
catalog.getEntities.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE);
const processor = new NotificationsEmailProcessor(
logger,
mockServices.rootConfig({
@@ -266,7 +265,7 @@ describe('NotificationsEmailProcessor', () => {
},
},
}),
catalog,
catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items }),
auth,
);
@@ -296,7 +295,6 @@ describe('NotificationsEmailProcessor', () => {
it('should send email to configured addresses', async () => {
(createTransport as jest.Mock).mockReturnValue(mockTransport);
catalog.getEntities.mockResolvedValue(DEFAULT_ENTITIES_RESPONSE);
const processor = new NotificationsEmailProcessor(
logger,
mockServices.rootConfig({
@@ -315,7 +313,7 @@ describe('NotificationsEmailProcessor', () => {
},
},
}),
catalog,
catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items }),
auth,
);
@@ -345,15 +343,12 @@ describe('NotificationsEmailProcessor', () => {
it('should send email with relative link to given address', async () => {
(createTransport as jest.Mock).mockReturnValue(mockTransport);
catalog.getEntityByRef.mockResolvedValue(
DEFAULT_ENTITIES_RESPONSE.items[0],
);
const processor = new NotificationsEmailProcessor(
logger,
mockServices.rootConfig({
data: DEFAULT_SENDMAIL_CONFIG,
}),
catalog,
catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items }),
auth,
);
@@ -412,15 +407,12 @@ describe('NotificationsEmailProcessor', () => {
it('should send email with absolute link to given address', async () => {
(createTransport as jest.Mock).mockReturnValue(mockTransport);
catalog.getEntityByRef.mockResolvedValue(
DEFAULT_ENTITIES_RESPONSE.items[0],
);
const processor = new NotificationsEmailProcessor(
logger,
mockServices.rootConfig({
data: DEFAULT_SENDMAIL_CONFIG,
}),
catalog,
catalogServiceMock({ entities: DEFAULT_ENTITIES_RESPONSE.items }),
auth,
);
@@ -28,13 +28,14 @@ describe('getUsersForEntityRef', () => {
await expect(
getUsersForEntityRef(null, [], {
auth: mockServices.auth(),
catalogClient: catalogServiceMock.mock(),
catalogClient: catalogServiceMock(),
}),
).resolves.toEqual([]);
});
it('should resolve users without calling catalog', async () => {
const catalogClient = catalogServiceMock.mock();
const catalogClient = catalogServiceMock();
jest.spyOn(catalogClient, 'getEntitiesByRefs');
await expect(
getUsersForEntityRef(['user:foo', 'user:ignored'], ['user:ignored'], {
auth: mockServices.auth(),
@@ -55,7 +55,7 @@ describe('createRouter', () => {
const config = mockServices.rootConfig({
data: { app: { baseUrl: 'http://localhost' } },
});
const catalog = catalogServiceMock.mock();
const catalog = catalogServiceMock();
beforeAll(async () => {
const router = await createRouter({
@@ -23,7 +23,15 @@ import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
describe('catalog:fetch examples', () => {
const catalogClient = catalogServiceMock.mock();
const entity = {
kind: 'Component',
metadata: {
name: 'name',
namespace: 'default',
},
} as Entity;
const catalogClient = catalogServiceMock({ entities: [entity] });
const action = createFetchCatalogEntityAction({
catalogClient,
@@ -43,18 +51,11 @@ describe('catalog:fetch examples', () => {
beforeEach(() => {
jest.resetAllMocks();
jest.spyOn(catalogClient, 'getEntityByRef');
});
describe('fetch single entity', () => {
it('should return entity from catalog', async () => {
catalogClient.getEntityByRef.mockResolvedValueOnce({
metadata: {
namespace: 'default',
name: 'name',
},
kind: 'Component',
} as Entity);
await action.handler({
...mockContext,
input: yaml.parse(examples[0].example).steps[0].input,
@@ -64,13 +65,7 @@ describe('catalog:fetch examples', () => {
'component:default/name',
{ token },
);
expect(mockContext.output).toHaveBeenCalledWith('entity', {
metadata: {
namespace: 'default',
name: 'name',
},
kind: 'Component',
});
expect(mockContext.output).toHaveBeenCalledWith('entity', entity);
});
});
});
@@ -21,12 +21,13 @@ import { mockCredentials, mockServices } from '@backstage/backend-test-utils';
import { catalogServiceMock } from '@backstage/plugin-catalog-node/testUtils';
describe('catalog:fetch', () => {
const catalogClient = catalogServiceMock.mock();
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
});
const component = {
kind: 'Component',
metadata: {
name: 'test',
namespace: 'default',
},
} as Entity;
const credentials = mockCredentials.user();
@@ -45,13 +46,12 @@ describe('catalog:fetch', () => {
describe('fetch single entity', () => {
it('should return entity from catalog', async () => {
catalogClient.getEntityByRef.mockResolvedValueOnce({
metadata: {
namespace: 'default',
name: 'test',
},
kind: 'Component',
} as Entity);
const catalogClient = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogClient, 'getEntityByRef');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
});
await action.handler({
...mockContext,
@@ -62,22 +62,19 @@ describe('catalog:fetch', () => {
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(
'component:default/test',
{
token,
},
{ token },
);
expect(mockContext.output).toHaveBeenCalledWith('entity', {
metadata: {
namespace: 'default',
name: 'test',
},
kind: 'Component',
});
expect(mockContext.output).toHaveBeenCalledWith('entity', component);
});
it('should throw error if entity fetch fails from catalog and optional is false', async () => {
catalogClient.getEntityByRef.mockImplementationOnce(() => {
throw new Error('Not found');
const catalogClient = catalogServiceMock.mock({
getEntityByRef: () => Promise.reject(new Error('Not found')),
});
jest.spyOn(catalogClient, 'getEntityByRef');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
});
await expect(
@@ -91,15 +88,18 @@ describe('catalog:fetch', () => {
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(
'component:default/test',
{
token,
},
{ token },
);
expect(mockContext.output).not.toHaveBeenCalled();
});
it('should throw error if entity not in catalog and optional is false', async () => {
catalogClient.getEntityByRef.mockResolvedValueOnce(null as any);
const catalogClient = catalogServiceMock({ entities: [] });
jest.spyOn(catalogClient, 'getEntityByRef');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
});
await expect(
action.handler({
@@ -112,22 +112,25 @@ describe('catalog:fetch', () => {
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(
'component:default/test',
{
token,
},
{ token },
);
expect(mockContext.output).not.toHaveBeenCalled();
});
it('should use defaultKind and defaultNamespace if provided', async () => {
const entity: Entity = {
metadata: {
namespace: 'ns',
name: 'test',
},
const entity = {
kind: 'Group',
metadata: {
name: 'test',
namespace: 'ns',
},
} as Entity;
catalogClient.getEntityByRef.mockResolvedValueOnce(entity);
const catalogClient = catalogServiceMock({ entities: [entity] });
jest.spyOn(catalogClient, 'getEntityByRef');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
});
await action.handler({
...mockContext,
@@ -140,9 +143,7 @@ describe('catalog:fetch', () => {
expect(catalogClient.getEntityByRef).toHaveBeenCalledWith(
'group:ns/test',
{
token,
},
{ token },
);
expect(mockContext.output).toHaveBeenCalledWith('entity', entity);
});
@@ -150,16 +151,11 @@ describe('catalog:fetch', () => {
describe('fetch multiple entities', () => {
it('should return entities from catalog', async () => {
catalogClient.getEntitiesByRefs.mockResolvedValueOnce({
items: [
{
metadata: {
namespace: 'default',
name: 'test',
},
kind: 'Component',
} as Entity,
],
const catalogClient = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogClient, 'getEntitiesByRefs');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
});
await action.handler({
@@ -171,33 +167,17 @@ describe('catalog:fetch', () => {
expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith(
{ entityRefs: ['component:default/test'] },
{
token,
},
{ token },
);
expect(mockContext.output).toHaveBeenCalledWith('entities', [
{
metadata: {
namespace: 'default',
name: 'test',
},
kind: 'Component',
},
]);
expect(mockContext.output).toHaveBeenCalledWith('entities', [component]);
});
it('should throw error if undefined is returned for some entity', async () => {
catalogClient.getEntitiesByRefs.mockResolvedValueOnce({
items: [
{
metadata: {
namespace: 'default',
name: 'test',
},
kind: 'Component',
} as Entity,
undefined,
],
const catalogClient = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogClient, 'getEntitiesByRefs');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
});
await expect(
@@ -211,26 +191,20 @@ describe('catalog:fetch', () => {
).rejects.toThrow('Entity component:default/test2 not found');
expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith(
{ entityRefs: ['component:default/test', 'component:default/test2'] },
{
token,
entityRefs: ['component:default/test', 'component:default/test2'],
},
{ token },
);
expect(mockContext.output).not.toHaveBeenCalled();
});
it('should return null in case some of the entities not found and optional is true', async () => {
catalogClient.getEntitiesByRefs.mockResolvedValueOnce({
items: [
{
metadata: {
namespace: 'default',
name: 'test',
},
kind: 'Component',
} as Entity,
undefined,
],
const catalogClient = catalogServiceMock({ entities: [component] });
jest.spyOn(catalogClient, 'getEntitiesByRefs');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
});
await action.handler({
@@ -243,39 +217,36 @@ describe('catalog:fetch', () => {
expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith(
{ entityRefs: ['component:default/test', 'component:default/test2'] },
{
token,
},
{ token },
);
expect(mockContext.output).toHaveBeenCalledWith('entities', [
{
metadata: {
namespace: 'default',
name: 'test',
},
kind: 'Component',
},
component,
null,
]);
});
it('should use defaultKind and defaultNamespace if provided', async () => {
const entity1: Entity = {
const entity1 = {
metadata: {
namespace: 'ns',
name: 'test',
},
kind: 'Group',
} as Entity;
const entity2: Entity = {
const entity2 = {
metadata: {
namespace: 'default',
name: 'test',
},
kind: 'User',
} as Entity;
catalogClient.getEntitiesByRefs.mockResolvedValueOnce({
items: [entity1, entity2],
const catalogClient = catalogServiceMock({
entities: [entity1, entity2],
});
jest.spyOn(catalogClient, 'getEntitiesByRefs');
const action = createFetchCatalogEntityAction({
catalogClient,
auth: mockServices.auth(),
});
await action.handler({
@@ -289,9 +260,7 @@ describe('catalog:fetch', () => {
expect(catalogClient.getEntitiesByRefs).toHaveBeenCalledWith(
{ entityRefs: ['group:ns/test', 'user:default/test'] },
{
token,
},
{ token },
);
expect(mockContext.output).toHaveBeenCalledWith('entities', [
@@ -59,7 +59,8 @@ describe('CachedEntityLoader', () => {
});
it('returns entities from cache', async () => {
const catalog = catalogServiceMock.mock();
const catalog = catalogServiceMock();
jest.spyOn(catalog, 'getEntityByRef');
cache.get.mockResolvedValue(entity);
const loader = new CachedEntityLoader({ catalog, cache });