From 8ba7481eb8ac7ff28aab2ec89c917d6fd6462e3a Mon Sep 17 00:00:00 2001 From: Oliver Sand Date: Wed, 20 Oct 2021 15:16:12 +0200 Subject: [PATCH] Add more tests Signed-off-by: Oliver Sand --- .../catalog-backend-module-msgraph/README.md | 2 +- .../src/microsoftGraph/config.test.ts | 17 ++ .../src/microsoftGraph/config.ts | 6 + .../MicrosoftGraphOrgEntityProvider.test.ts | 174 ++++++++++++++++++ .../MicrosoftGraphOrgEntityProvider.ts | 8 +- .../MicrosoftGraphOrgReaderProcessor.test.ts | 143 ++++++++++++++ .../MicrosoftGraphOrgReaderProcessor.ts | 6 - 7 files changed, 342 insertions(+), 14 deletions(-) create mode 100644 plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts create mode 100644 plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.test.ts diff --git a/plugins/catalog-backend-module-msgraph/README.md b/plugins/catalog-backend-module-msgraph/README.md index b89f3af460..0e088d3a4e 100644 --- a/plugins/catalog-backend-module-msgraph/README.md +++ b/plugins/catalog-backend-module-msgraph/README.md @@ -84,7 +84,7 @@ builder.addEntityProvider(msGraphOrgEntityProvider); // Trigger a read every 5 minutes useHotCleanup( module, - runPeriodically(async () => msGraphOrgEntityProvider.read(), 5 * 60 * 1000), + runPeriodically(() => msGraphOrgEntityProvider.read(), 5 * 60 * 1000), ); ``` diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts index 5164439448..efbbca1f5c 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.test.ts @@ -72,4 +72,21 @@ describe('readMicrosoftGraphConfig', () => { ]; expect(actual).toEqual(expected); }); + + it('should fail if both userFilter and userGroupMemberFilter are set', () => { + const config = { + providers: [ + { + target: 'target', + tenantId: 'tenantId', + clientId: 'clientId', + clientSecret: 'clientSecret', + authority: 'https://login.example.com/', + userFilter: 'accountEnabled eq true', + userGroupMemberFilter: 'any', + }, + ], + }; + expect(() => readMicrosoftGraphConfig(new ConfigReader(config))).toThrow(); + }); }); diff --git a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts index fe0678372a..91a6d49b57 100644 --- a/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts +++ b/plugins/catalog-backend-module-msgraph/src/microsoftGraph/config.ts @@ -87,6 +87,12 @@ export function readMicrosoftGraphConfig( ); const groupFilter = providerConfig.getOptionalString('groupFilter'); + if (userFilter && userGroupMemberFilter) { + throw new Error( + `userFilter and userGroupMemberFilter are mutually exclusive, only one can be specified.`, + ); + } + providers.push({ target, authority, diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts new file mode 100644 index 0000000000..eab2d93526 --- /dev/null +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.test.ts @@ -0,0 +1,174 @@ +/* + * Copyright 2021 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 { getVoidLogger } from '@backstage/backend-common'; +import { + GroupEntity, + LOCATION_ANNOTATION, + ORIGIN_LOCATION_ANNOTATION, + UserEntity, +} from '@backstage/catalog-model'; +import { EntityProviderConnection } from '@backstage/plugin-catalog-backend'; +import { + MicrosoftGraphClient, + MICROSOFT_GRAPH_USER_ID_ANNOTATION, + readMicrosoftGraphOrg, +} from '../microsoftGraph'; +import { + MicrosoftGraphOrgEntityProvider, + withLocations, +} from './MicrosoftGraphOrgEntityProvider'; + +jest.mock('../microsoftGraph', () => { + return { + ...jest.requireActual('../microsoftGraph'), + readMicrosoftGraphOrg: jest.fn(), + }; +}); + +const readMicrosoftGraphOrgMocked = readMicrosoftGraphOrg as jest.Mock< + Promise<{ users: UserEntity[]; groups: GroupEntity[] }> +>; + +describe('MicrosoftGraphOrgEntityProvider', () => { + afterEach(() => jest.resetAllMocks()); + + it('should apply mutation', async () => { + jest + .spyOn(MicrosoftGraphClient, 'create') + .mockReturnValue({} as unknown as MicrosoftGraphClient); + + readMicrosoftGraphOrgMocked.mockResolvedValue({ + users: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'u1', + }, + spec: { + memberOf: [], + }, + }, + ], + groups: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'g1', + }, + spec: { + type: 'team', + children: [], + }, + }, + ], + }); + + const entityProviderConnection: EntityProviderConnection = { + applyMutation: jest.fn(), + }; + const provider = new MicrosoftGraphOrgEntityProvider({ + id: 'test', + logger: getVoidLogger(), + provider: { + target: 'https://example.com', + tenantId: 'tenant', + clientId: 'clientid', + clientSecret: 'clientsecret', + }, + }); + + provider.connect(entityProviderConnection); + + await provider.read(); + + expect(entityProviderConnection.applyMutation).toBeCalledWith({ + entities: [ + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + annotations: { + 'backstage.io/managed-by-location': 'msgraph:test/u1', + 'backstage.io/managed-by-origin-location': 'msgraph:test/u1', + }, + name: 'u1', + }, + spec: { + memberOf: [], + }, + }, + locationKey: 'msgraph-org-provider:test', + }, + { + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + annotations: { + 'backstage.io/managed-by-location': 'msgraph:test/g1', + 'backstage.io/managed-by-origin-location': 'msgraph:test/g1', + }, + name: 'g1', + }, + spec: { + children: [], + type: 'team', + }, + }, + locationKey: 'msgraph-org-provider:test', + }, + ], + type: 'full', + }); + }); +}); + +describe('withLocations', () => { + it('should set location annotations', () => { + expect( + withLocations('test', { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'u1', + annotations: { + [MICROSOFT_GRAPH_USER_ID_ANNOTATION]: 'uid', + }, + }, + spec: { + memberOf: [], + }, + }), + ).toEqual({ + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'u1', + annotations: { + [MICROSOFT_GRAPH_USER_ID_ANNOTATION]: 'uid', + [LOCATION_ANNOTATION]: 'msgraph:test/uid', + [ORIGIN_LOCATION_ANNOTATION]: 'msgraph:test/uid', + }, + }, + spec: { + memberOf: [], + }, + }); + }); +}); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts index 55b0933b99..ea396162dd 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgEntityProvider.ts @@ -66,12 +66,6 @@ export class MicrosoftGraphOrgEntityProvider implements EntityProvider { ); } - if (provider.userFilter && provider.userGroupMemberFilter) { - throw new Error( - `userFilter and userGroupMemberFilter are mutually exclusive, only one can be specified.`, - ); - } - const logger = options.logger.child({ target: options.target, }); @@ -166,7 +160,7 @@ function trackProgress(logger: Logger) { } // Makes sure that emitted entities have a proper location based on their uuid -function withLocations(providerId: string, entity: Entity): Entity { +export function withLocations(providerId: string, entity: Entity): Entity { const uuid = entity.metadata.annotations?.[MICROSOFT_GRAPH_USER_ID_ANNOTATION] || entity.metadata.annotations?.[MICROSOFT_GRAPH_GROUP_ID_ANNOTATION] || diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.test.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.test.ts new file mode 100644 index 0000000000..8236dc52e1 --- /dev/null +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.test.ts @@ -0,0 +1,143 @@ +/* + * 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. + */ + +import { getVoidLogger } from '@backstage/backend-common'; +import { GroupEntity, UserEntity } from '@backstage/catalog-model'; +import { MicrosoftGraphClient, readMicrosoftGraphOrg } from '../microsoftGraph'; +import { MicrosoftGraphOrgReaderProcessor } from './MicrosoftGraphOrgReaderProcessor'; + +jest.mock('../microsoftGraph', () => { + return { + ...jest.requireActual('../microsoftGraph'), + readMicrosoftGraphOrg: jest.fn(), + }; +}); + +const readMicrosoftGraphOrgMocked = readMicrosoftGraphOrg as jest.Mock< + Promise<{ users: UserEntity[]; groups: GroupEntity[] }> +>; + +describe('MicrosoftGraphOrgReaderProcessor', () => { + const emit = jest.fn(); + let processor: MicrosoftGraphOrgReaderProcessor; + + beforeEach(() => { + processor = new MicrosoftGraphOrgReaderProcessor({ + providers: [ + { + target: 'https://example.com', + tenantId: 'tenant', + clientId: 'clientid', + clientSecret: 'clientsecret', + }, + ], + logger: getVoidLogger(), + }); + + jest + .spyOn(MicrosoftGraphClient, 'create') + .mockReturnValue({} as unknown as MicrosoftGraphClient); + }); + + afterEach(() => jest.resetAllMocks()); + + it('should process microsoft-graph-org locations', async () => { + const location = { + type: 'microsoft-graph-org', + target: 'https://example.com', + }; + + readMicrosoftGraphOrgMocked.mockResolvedValue({ + users: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'u1', + }, + spec: { + memberOf: [], + }, + }, + ], + groups: [ + { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'g1', + }, + spec: { + type: 'team', + children: [], + }, + }, + ], + }); + + const processed = await processor.readLocation(location, false, emit); + + expect(processed).toBe(true); + expect(emit).toBeCalledTimes(2); + expect(emit).toBeCalledWith({ + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Group', + metadata: { + name: 'g1', + }, + spec: { + children: [], + type: 'team', + }, + }, + location: { + target: 'https://example.com', + type: 'microsoft-graph-org', + }, + type: 'entity', + }); + expect(emit).toBeCalledWith({ + entity: { + apiVersion: 'backstage.io/v1alpha1', + kind: 'User', + metadata: { + name: 'u1', + }, + spec: { + memberOf: [], + }, + }, + location: { + target: 'https://example.com', + type: 'microsoft-graph-org', + }, + type: 'entity', + }); + }); + + it('should ignore other locations', async () => { + const location = { + type: 'url', + target: 'https://example.com', + }; + + const processed = await processor.readLocation(location, false, emit); + + expect(processed).toBe(false); + expect(emit).toBeCalledTimes(0); + }); +}); diff --git a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts index 7f4e55c0cc..cb7729e04a 100644 --- a/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts +++ b/plugins/catalog-backend-module-msgraph/src/processors/MicrosoftGraphOrgReaderProcessor.ts @@ -90,12 +90,6 @@ export class MicrosoftGraphOrgReaderProcessor implements CatalogProcessor { ); } - if (provider.userFilter && provider.userGroupMemberFilter) { - throw new Error( - `userFilter and userGroupMemberFilter are mutually exclusive, only one can be specified.`, - ); - } - // Read out all of the raw data const startTimestamp = Date.now(); this.logger.info('Reading Microsoft Graph users and groups');