From 7709c264cabc4c2fc8b0c9d609c02c4cc8abeacf Mon Sep 17 00:00:00 2001 From: Vincenzo Scamporlino Date: Tue, 20 Jun 2023 15:22:50 +0200 Subject: [PATCH] catalog-react: add useOwnedEntitiesCount tests Signed-off-by: Vincenzo Scamporlino --- .../useOwnedEntitiesCount.test.tsx | 235 ++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.test.tsx diff --git a/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.test.tsx b/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.test.tsx new file mode 100644 index 0000000000..6a79e63c8f --- /dev/null +++ b/plugins/catalog-react/src/components/UserListPicker/useOwnedEntitiesCount.test.tsx @@ -0,0 +1,235 @@ +/* + * Copyright 2023 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 React, { PropsWithChildren } from 'react'; +import { CatalogApi } from '@backstage/catalog-client'; +import { renderHook } from '@testing-library/react-hooks'; +import { + DefaultEntityFilters, + EntityListProvider, + useEntityList, +} from '../../hooks'; +import { catalogApiRef } from '../../api'; +import { + ApiRef, + IdentityApi, + identityApiRef, +} from '@backstage/core-plugin-api'; +import { MemoryRouter } from 'react-router-dom'; +import { useOwnedEntitiesCount } from './useOwnedEntitiesCount'; +import { + EntityNamespaceFilter, + EntityOwnerFilter, + EntityUserListFilter, +} from '../../filters'; +import { useMountEffect } from '@react-hookz/web'; + +const mockQueryEntities: jest.MockedFn = jest.fn(); +const mockCatalogApi: jest.Mocked> = { + queryEntities: mockQueryEntities, +}; + +const mockGetBackstageIdentity: jest.MockedFn< + IdentityApi['getBackstageIdentity'] +> = jest.fn(); + +jest.mock('@backstage/core-plugin-api', () => { + const actual = jest.requireActual('@backstage/core-plugin-api'); + return { + ...actual, + useApi: (ref: ApiRef) => { + if (ref === catalogApiRef) { + return mockCatalogApi; + } + if (ref === identityApiRef) { + return { + getBackstageIdentity: mockGetBackstageIdentity, + }; + } + + return actual.useApi(ref); + }, + }; +}); + +describe('useOwnedEntitiesCount', () => { + beforeEach(() => { + jest.clearAllMocks(); + + mockGetBackstageIdentity.mockResolvedValue({ + ownershipEntityRefs: ['user:default/spiderman', 'user:group/a-group'], + userEntityRef: 'user:default/spiderman', + type: 'user', + }); + }); + + it(`shouldn't invoke queryEntities when filters are loading`, async () => { + mockQueryEntities.mockResolvedValue({ + items: [], + totalItems: 10, + pageInfo: {}, + }); + + const { result, waitFor } = renderHook(() => useOwnedEntitiesCount(), { + wrapper: createWrapperWithInitialFilters({}), + }); + + await waitFor(() => expect(mockGetBackstageIdentity).toHaveBeenCalled()); + + await expect( + waitFor(() => expect(mockQueryEntities).toHaveBeenCalled()), + ).rejects.toThrow(); + + expect(result.current).toEqual({ + count: 0, + loading: false, + filter: EntityUserListFilter.owned([ + 'user:default/spiderman', + 'user:group/a-group', + ]), + ownershipEntityRefs: ['user:default/spiderman', 'user:group/a-group'], + }); + }); + + it(`should properly apply the filters`, async () => { + mockQueryEntities.mockResolvedValue({ + items: [], + totalItems: 10, + pageInfo: {}, + }); + + const { result, waitFor } = renderHook(() => useOwnedEntitiesCount(), { + wrapper: createWrapperWithInitialFilters({ + namespace: new EntityNamespaceFilter(['a-namespace']), + }), + }); + + await waitFor(() => expect(mockGetBackstageIdentity).toHaveBeenCalled()); + + await waitFor(() => + expect(mockQueryEntities).toHaveBeenCalledWith({ + filter: { + 'metadata.namespace': ['a-namespace'], + 'relations.ownedBy': ['user:default/spiderman', 'user:group/a-group'], + }, + limit: 0, + }), + ); + + expect(result.current).toEqual({ + count: 10, + loading: false, + filter: EntityUserListFilter.owned([ + 'user:default/spiderman', + 'user:group/a-group', + ]), + ownershipEntityRefs: ['user:default/spiderman', 'user:group/a-group'], + }); + }); + + it(`should return count 0 without invoking queryEntities if owners filter doesn't have claims on common with logged in user`, async () => { + mockQueryEntities.mockResolvedValue({ + items: [], + totalItems: 10, + pageInfo: {}, + }); + + const { result, waitFor } = renderHook(() => useOwnedEntitiesCount(), { + wrapper: createWrapperWithInitialFilters({ + namespace: new EntityNamespaceFilter(['a-namespace']), + owners: new EntityOwnerFilter(['group:default/monsters']), + }), + }); + + await waitFor(() => expect(mockGetBackstageIdentity).toHaveBeenCalled()); + + await expect( + waitFor(() => expect(mockQueryEntities).toHaveBeenCalled()), + ).rejects.toThrow(); + + expect(result.current).toEqual({ + count: 0, + loading: false, + filter: EntityUserListFilter.owned([ + 'user:default/spiderman', + 'user:group/a-group', + ]), + ownershipEntityRefs: ['user:default/spiderman', 'user:group/a-group'], + }); + }); + + it(`should send claims in common between owners filter and logged in user`, async () => { + mockQueryEntities.mockResolvedValue({ + items: [], + totalItems: 10, + pageInfo: {}, + }); + + const { result, waitFor } = renderHook(() => useOwnedEntitiesCount(), { + wrapper: createWrapperWithInitialFilters({ + namespace: new EntityNamespaceFilter(['a-namespace']), + owners: new EntityOwnerFilter([ + 'group:default/monsters', + 'user:group/a-group', + ]), + }), + }); + + await waitFor(() => expect(mockGetBackstageIdentity).toHaveBeenCalled()); + + await waitFor(() => + expect(mockQueryEntities).toHaveBeenCalledWith({ + filter: { + 'metadata.namespace': ['a-namespace'], + 'relations.ownedBy': ['user:group/a-group'], + }, + limit: 0, + }), + ); + + expect(result.current).toEqual({ + count: 10, + loading: false, + filter: EntityUserListFilter.owned([ + 'user:default/spiderman', + 'user:group/a-group', + ]), + ownershipEntityRefs: ['user:default/spiderman', 'user:group/a-group'], + }); + }); +}); + +function createWrapperWithInitialFilters( + filters: Partial, +) { + function WrapFilters(props: PropsWithChildren<{}>) { + const { updateFilters } = useEntityList(); + + useMountEffect(() => { + updateFilters(filters); + }); + return <>{props.children}; + } + + return function Wrapper(props: PropsWithChildren<{}>) { + return ( + + + {props.children} + + + ); + }; +}