diff --git a/backend/config/urls.py b/backend/config/urls.py index 6f3c82b21..526c4fea2 100644 --- a/backend/config/urls.py +++ b/backend/config/urls.py @@ -16,7 +16,6 @@ from metagrid.api_proxy.views import ( do_citation, do_globus_auth, - do_globus_get_endpoint, do_globus_logout, do_globus_search_endpoints, do_search, @@ -58,11 +57,6 @@ class KeycloakLogin(SocialLoginView): path("", include("social_django.urls", namespace="social")), path("proxy/globus-logout/", do_globus_logout, name="globus-logout"), path("proxy/globus-auth/", do_globus_auth, name="globus-auth"), - path( - "proxy/globus-get-endpoint/", - do_globus_get_endpoint, - name="globus-get-endpoint", - ), path( "proxy/globus-search-endpoints/", do_globus_search_endpoints, diff --git a/backend/metagrid/api_proxy/tests/test_views.py b/backend/metagrid/api_proxy/tests/test_views.py index 365a1d12b..9a256dcb0 100644 --- a/backend/metagrid/api_proxy/tests/test_views.py +++ b/backend/metagrid/api_proxy/tests/test_views.py @@ -23,14 +23,6 @@ def test_globus_auth_begin(self): ) self.assertEqual(response.status_code, 302) - def test_do_globus_get_endpoint(self): - url = reverse("globus-get-endpoint") - - data = {"endpoint_id": "0247816e-cc0d-4e03-a509-10903f6dde11"} - response = self.client.get(url, data) - print(response.status_code) - assert response.status_code == status.HTTP_200_OK - def test_do_globus_search_endpoints(self): url = reverse("globus-search-endpoints") diff --git a/backend/metagrid/api_proxy/views.py b/backend/metagrid/api_proxy/views.py index 331d26396..963fc2b13 100644 --- a/backend/metagrid/api_proxy/views.py +++ b/backend/metagrid/api_proxy/views.py @@ -51,27 +51,6 @@ def do_globus_logout(request): return redirect(homepage_url) -@api_view() -@permission_classes([]) -def do_globus_get_endpoint(request): - endpoint_id = request.GET.get("endpoint_id", None) - if request.user.is_authenticated: - tc = load_transfer_client(request.user) # pragma: no cover - else: - client = globus_sdk.ConfidentialAppAuthClient( - settings.SOCIAL_AUTH_GLOBUS_KEY, settings.SOCIAL_AUTH_GLOBUS_SECRET - ) - token_response = client.oauth2_client_credentials_tokens() - globus_transfer_data = token_response.by_resource_server[ - "transfer.api.globus.org" - ] - globus_transfer_token = globus_transfer_data["access_token"] - authorizer = globus_sdk.AccessTokenAuthorizer(globus_transfer_token) - tc = globus_sdk.TransferClient(authorizer=authorizer) - endpoint = tc.get_endpoint(endpoint_id) - return Response(endpoint.data) - - @api_view() @permission_classes([]) def do_globus_search_endpoints(request): diff --git a/frontend/src/components/Globus/DatasetDownload.test.tsx b/frontend/src/components/Globus/DatasetDownload.test.tsx index 581b6f8f6..d25db37cb 100644 --- a/frontend/src/components/Globus/DatasetDownload.test.tsx +++ b/frontend/src/components/Globus/DatasetDownload.test.tsx @@ -1,12 +1,11 @@ import React from 'react'; import userEvent from '@testing-library/user-event'; -import { act, waitFor, within, screen } from '@testing-library/react'; +import { act, within, screen } from '@testing-library/react'; import customRender from '../../test/custom-render'; import { rest, server } from '../../test/mock/server'; import { getSearchFromUrl } from '../../common/utils'; import { ActiveSearchQuery, RawSearchResults } from '../Search/types'; import { - getRowName, globusReadyNode, makeCartItem, mockConfig, @@ -23,7 +22,6 @@ import { globusEndpointFixture, globusAccessTokenFixture, globusTransferTokenFixture, - userCartFixture, } from '../../test/mock/fixtures'; import apiRoutes from '../../api/routes'; import DatasetDownloadForm, { GlobusGoals } from './DatasetDownload'; @@ -883,507 +881,54 @@ describe('DatasetDownload form tests', () => { 'Submitted: 11/30/2023, 3:10:00 PM' ); }); -}); - -xit('If endpoint URL is available, process it and continue to Transfer process', async () => { - // Setting the tokens so that the sign-in step should be completed - mockSaveValue(CartStateKeys.cartItemSelections, userCartFixture()); - mockSaveValue(GlobusStateKeys.accessToken, 'accessToken'); - mockSaveValue(GlobusStateKeys.transferToken, { - id_token: '', - resource_server: '', - other_tokens: { refresh_token: 'something', transfer_token: 'something' }, - created_on: Math.floor(Date.now() / 1000), - expires_in: Math.floor(Date.now() / 1000) + 100, - access_token: '', - refresh_expires_in: 0, - refresh_token: 'something', - scope: - 'openid profile email offline_access urn:globus:auth:scope:transfer.api.globus.org:all', - token_type: '', - } as GlobusTokenResponse); - - const { getByTestId, getByRole, getByText, getAllByText } = customRender( - - ); - - // Wait for results to load - await waitFor(() => - expect(getByText('results found for', { exact: false })).toBeTruthy() - ); - - // Check first row renders and click the checkbox - const firstRow = getByRole('row', { - name: getRowName('plus', 'check', 'foo', '3', '1', '1', true), - }); - - // Check first row has add button and click it - const addBtn = within(firstRow).getByRole('img', { name: 'plus' }); - expect(addBtn).toBeTruthy(); - await act(async () => { - await user.click(addBtn); - }); - - // Check 'Added items(s) to the cart' message appears - const addText = await waitFor( - () => getAllByText('Added item(s) to your cart')[0] - ); - expect(addText).toBeTruthy(); - - // Set endpoint in url - Object.defineProperty(window, 'location', { - value: { - assign: () => {}, - pathname: '/cart/items', - href: - 'https://localhost:3000/cart/items?endpoint=dummyEndpoint&label=dummy&path=nowhere&globfs=empty&endpointid=endpoint1', - search: - '?endpoint=dummyEndpoint&label=dummy&path=nowhere&globfs=empty&endpointid=endpoint1', - replace: () => {}, - }, - }); - - // Switch to the cart page - const cartBtn = getByTestId('cartPageLink'); - await act(async () => { - await user.click(cartBtn); - }); - - // A popup should come asking if user wishes to save endpoint as default - const saveEndpointDialog = getByRole('dialog'); - expect(saveEndpointDialog).toBeTruthy(); - expect(saveEndpointDialog).toBeVisible(); - expect(saveEndpointDialog).toHaveTextContent( - 'Do you want to save this endpoint as default?' - ); - - // Click Yes to save the endpoint as default - const yesBtn = within(saveEndpointDialog).getByText('Yes'); - expect(yesBtn).toBeTruthy(); - await act(async () => { - await user.click(yesBtn); - }); - - // Next step should be to start the Transfer - const globusTransferDialog = getByRole('dialog'); - expect(globusTransferDialog).toBeTruthy(); - - // Select the final transfer step in the dialog - const transferStep = within(globusTransferDialog).getByText( - 'Start Globus transfer.', - { - exact: false, - } - ); - // The transfer step should be the next step to perform - expect(transferStep.innerHTML).toMatch(/-> {2}Start Globus transfer./i); - - // Click Yes to continue transfer steps - const startBtn = within(globusTransferDialog).getByText('Yes'); - expect(startBtn).toBeTruthy(); - await act(async () => { - await user.click(startBtn); - }); - - // Check 'Globus transfer task submitted successfully!' message appears - const taskMsg = await waitFor(() => - getByText('Globus transfer task submitted successfully!', { - exact: false, - }) - ); - expect(taskMsg).toBeTruthy(); - - // Clear all task items - const submitHistory = getByText('Task Submit History', { exact: false }); - expect(submitHistory).toBeTruthy(); - const clearAllBtn = within(submitHistory).getByText('Clear All'); - expect(clearAllBtn).toBeTruthy(); - await act(async () => { - await user.click(clearAllBtn); - }); -}); - -xit('If endpoint URL is set, and sign in tokens in URL, continue to select endpoint', async () => { - // Setting the tokens so that the sign-in step should be completed - - const { getByTestId, getByRole, getByText, getAllByText } = customRender( - - ); - - // Wait for results to load - await waitFor(() => - expect(getByText('results found for', { exact: false })).toBeTruthy() - ); - - // Check first row renders and click the checkbox - const firstRow = getByRole('row', { - name: getRowName('plus', 'check', 'foo', '3', '1', '1', true), - }); - - // Check first row has add button and click it - const addBtn = within(firstRow).getByRole('img', { name: 'plus' }); - expect(addBtn).toBeTruthy(); - await act(async () => { - await user.click(addBtn); - }); - - // Check 'Added items(s) to the cart' message appears - const addText = await waitFor( - () => getAllByText('Added item(s) to your cart')[0] - ); - expect(addText).toBeTruthy(); - - // Switch to the cart page - const cartBtn = getByTestId('cartPageLink'); - await act(async () => { - await user.click(cartBtn); - }); - // Select item for globus transfer - const firstCheckBox = getByRole('checkbox'); - expect(firstCheckBox).toBeTruthy(); - await act(async () => { - await user.click(firstCheckBox); - }); - - // Click Transfer button - const globusTransferBtn = getByRole('button', { - name: /download transfer/i, - }); - expect(globusTransferBtn).toBeTruthy(); - await act(async () => { - await user.click(globusTransferBtn); - }); - - // Get the transfer dialog popup component - const popupModal = getByRole('dialog'); - expect(popupModal).toBeTruthy(); - - // The dialog should be visible - expect(popupModal).toBeVisible(); - - // Select the sign-in step in the dialog - const signInStep = within(popupModal).getByText( - 'Redirect to obtain transfer permission from Globus', - { - exact: false, - } - ); - // It should have a -> symbol next to it to indicate it's the next step - expect(signInStep.innerHTML).toMatch( - '-> Redirect to obtain transfer permission from Globus.' - ); - - // Select the endpoint step in the dialog - const selectEndpointStep = within( - popupModal - ).getByText('Redirect to select an endpoint in Globus', { exact: false }); - // It should NOT have a -> symbol next to it to indicate it's the next step - expect(selectEndpointStep.innerHTML).toMatch( - 'Redirect to select an endpoint in Globus.' - ); - - // Click Yes to start next transfer steps - const yesBtn = getByText('Yes'); - expect(yesBtn).toBeTruthy(); - await act(async () => { - await user.click(yesBtn); - }); - - // Expect the dialog to not be visible - expect(popupModal).not.toBeVisible(); -}); - -xit('Perform Transfer process when sign in tokens and endpoint are BOTH ready', async () => { - // Setting the tokens so that the sign-in step should be completed - mockSaveValue(CartStateKeys.cartItemSelections, userCartFixture()); - mockSaveValue(GlobusStateKeys.accessToken, 'accessToken'); - mockSaveValue(GlobusStateKeys.transferToken, { - id_token: '', - resource_server: '', - other_tokens: { refresh_token: 'something', transfer_token: 'something' }, - created_on: Math.floor(Date.now() / 1000), - expires_in: Math.floor(Date.now() / 1000) + 100, - access_token: '', - refresh_expires_in: 0, - refresh_token: 'something', - scope: - 'openid profile email offline_access urn:globus:auth:scope:transfer.api.globus.org:all', - token_type: '', - } as GlobusTokenResponse); - - const { getByTestId, getByRole, getByText, getAllByText } = customRender( - - ); - - // Wait for results to load - await waitFor(() => - expect(getByText('results found for', { exact: false })).toBeTruthy() - ); - - // Check first row renders and click the checkbox - const firstRow = getByRole('row', { - name: getRowName('plus', 'check', 'foo', '3', '1', '1', true), - }); - - // Check first row has add button and click it - const addBtn = within(firstRow).getByRole('img', { name: 'plus' }); - expect(addBtn).toBeTruthy(); - await act(async () => { - await user.click(addBtn); - }); - - // Check 'Added items(s) to the cart' message appears - const addText = await waitFor( - () => getAllByText('Added item(s) to your cart')[0] - ); - expect(addText).toBeTruthy(); - - // Switch to the cart page - const cartBtn = getByTestId('cartPageLink'); - await act(async () => { - await user.click(cartBtn); - }); - - // Check 'Globus transfer task submitted successfully!' message appears - const taskMsg = await waitFor(() => - getByText('Globus transfer task submitted successfully!', { - exact: false, - }) - ); - expect(taskMsg).toBeTruthy(); - - // Clear all task items - const submitHistory = getByText('Task Submit History', { exact: false }); - expect(submitHistory).toBeTruthy(); - const clearAllBtn = within(submitHistory).getByText('Clear All'); - expect(clearAllBtn).toBeTruthy(); - await act(async () => { - await user.click(clearAllBtn); - }); -}); - -xdescribe('Testing globus transfer related failures', () => { - beforeAll(() => { - jest.spyOn(console, 'error').mockImplementation(jest.fn()); - tempStorageSetMock('pkce-pass', false); - jest.resetModules(); - }); - - it('Shows an error message if transfer task fails', async () => { + it('Shows an alert when a collection search fails in the manage collections form', async () => { server.use( - rest.post(apiRoutes.globusTransfer.path, (_req, res, ctx) => - res(ctx.status(404)) + rest.get(apiRoutes.globusSearchEndpoints.path, (_req, res, ctx) => + res(ctx.status(500)) ) ); - // Setting the tokens so that the sign-in step should be completed - mockSaveValue(CartStateKeys.cartItemSelections, userCartFixture()); - mockSaveValue(GlobusStateKeys.accessToken, 'globusAccessToken'); - mockSaveValue(GlobusStateKeys.transferToken, { - id_token: '', - resource_server: '', - other_tokens: { refresh_token: 'something', transfer_token: 'something' }, - created_on: Math.floor(Date.now() / 1000), - expires_in: Math.floor(Date.now() / 1000) + 100, - access_token: '', - refresh_expires_in: 0, - refresh_token: 'something', - scope: 'openid profile email offline_access ', - token_type: '', - } as GlobusTokenResponse); - - const { getByTestId, getByRole, getByText, getAllByText } = customRender( - - ); - - // Wait for results to load - await waitFor(() => - expect(getByText('results found for', { exact: false })).toBeTruthy() - ); - - // Check first row renders and click the checkbox - const firstRow = getByRole('row', { - name: getRowName('plus', 'check', 'foo', '3', '1', '1', true), - }); - - // Check first row has add button and click it - const addBtn = within(firstRow).getByRole('img', { name: 'plus' }); - expect(addBtn).toBeTruthy(); - await act(async () => { - await user.click(addBtn); - }); - - // Check 'Added items(s) to the cart' message appears - const addText = await waitFor( - () => getAllByText('Added item(s) to your cart')[0] - ); - expect(addText).toBeTruthy(); - - // Switch to the cart page - const cartBtn = getByTestId('cartPageLink'); - await act(async () => { - await user.click(cartBtn); + await initializeComponentForTest({ + ...defaultTestConfig, + savedEndpoints: [], + chosenEndpoint: null, }); - // Check 'Globus transfer task failed' message appears - const taskMsg = await waitFor(() => - getByText('Globus transfer task failed', { - exact: false, - }) - ); - expect(taskMsg).toBeTruthy(); - }); - - // TODO: Figure a reliable way to mock the GlobusAuth.exchangeForAccessToken output values. - /** Until that is done, this test will fail and will need to use istanbul ignore statements - * for the mean time. - */ - xit('Shows error message if url tokens are not valid for transfer', async () => { - // Setting the tokens so that the sign-in step should be skipped - mockSaveValue(CartStateKeys.cartItemSelections, userCartFixture()); - - tempStorageSetMock('pkce-pass', false); - - const { getByTestId, getByRole, getByText, getAllByText } = customRender( - - ); - - // Wait for results to load - await waitFor(() => - expect(getByText('results found for', { exact: false })).toBeTruthy() + // Open download dropdown + const collectionDropdown = await screen.findByTestId( + 'searchCollectionInput' ); - - // Check first row renders and click the checkbox - const firstRow = getByRole('row', { - name: getRowName('plus', 'check', 'foo', '3', '1', '1', true), - }); - - // Check first row has add button and click it - const addBtn = within(firstRow).getByRole('img', { name: 'plus' }); - expect(addBtn).toBeTruthy(); - await act(async () => { - await user.click(addBtn); - }); - - // Check 'Added items(s) to the cart' message appears - const addText = await waitFor( - () => getAllByText('Added item(s) to your cart')[0] + const selectEndpoint = await within(collectionDropdown).findByRole( + 'combobox' ); - expect(addText).toBeTruthy(); - - // Set the tokens in the url - Object.defineProperty(window, 'location', { - value: { - assign: () => {}, - pathname: '/cart/items', - href: - 'https://localhost:3000/cart/items?code=12kj3kjh4&state=testingTransferTokens', - search: '?code=12kj3kjh4&state=testingTransferTokens', - replace: () => {}, - }, - }); - - tempStorageSetMock('pkce-pass', false); + await openDropdownList(user, selectEndpoint); - // Switch to the cart page - const cartBtn = getByTestId('cartPageLink'); + // Select manage collections + const manageEndpointsBtn = await screen.findByText('Manage Collections'); + expect(manageEndpointsBtn).toBeTruthy(); await act(async () => { - await user.click(cartBtn); + await user.click(manageEndpointsBtn); }); - const accessToken = await mockLoadValue(GlobusStateKeys.accessToken); - const transferToken = await mockLoadValue(GlobusStateKeys.transferToken); - - expect(accessToken).toBeFalsy(); - expect(transferToken).toBeFalsy(); - - // Check 'Error occurred when obtaining transfer permission!' message appears - const taskMsg = await waitFor( - () => - getAllByText('Error occured when obtaining transfer permissions.', { - exact: false, - })[0] - ); - expect(taskMsg).toBeTruthy(); - }); -}); - -xdescribe('Testing wget transfer related failures', () => { - it('Wget transfer fails and failure message pops up.', async () => { - server.use( - rest.post(apiRoutes.wget.path, (_req, res, ctx) => res(ctx.status(404))) - ); - - const { getByTestId, getByRole, getByText, getAllByText } = customRender( - - ); - - // Wait for results to load - await waitFor(() => - expect(getByText('results found for', { exact: false })).toBeTruthy() + const manageCollectionsForm = await screen.findByTestId( + 'manageCollectionsForm' ); + expect(manageCollectionsForm).toBeTruthy(); - // Check first row renders and click the checkbox - const firstRow = getByRole('row', { - name: getRowName('plus', 'check', 'foo', '3', '1', '1', true), - }); - - // Check first row has add button and click it - const addBtn = within(firstRow).getByRole('img', { name: 'plus' }); - expect(addBtn).toBeTruthy(); - await act(async () => { - await user.click(addBtn); - }); - - // Check 'Added items(s) to the cart' message appears - const addText = await waitFor( - () => getAllByText('Added item(s) to your cart')[0] + // Type in endpoint search text + const endpointSearchInput = await screen.findByPlaceholderText( + 'Search for a Globus Collection' ); - expect(addText).toBeTruthy(); - - // Switch to the cart page - const cartBtn = getByTestId('cartPageLink'); - await act(async () => { - await user.click(cartBtn); - }); - - // Select item for globus transfer - const firstCheckBox = getByRole('checkbox'); - expect(firstCheckBox).toBeTruthy(); - await act(async () => { - await user.click(firstCheckBox); - }); - - // Open download dropdown - const globusTransferDropdown = within( - getByTestId('downloadTypeSelector') - ).getByRole('combobox'); - - await openDropdownList(user, globusTransferDropdown); - - // Select wget - const wgetOption = getAllByText(/wget/i)[2]; - expect(wgetOption).toBeTruthy(); - await act(async () => { - await user.click(wgetOption); - }); - - // Start wget download - const downloadBtn = getByText('Download'); - expect(downloadBtn).toBeTruthy(); + expect(endpointSearchInput).toBeTruthy(); await act(async () => { - await user.click(downloadBtn); + await user.type(endpointSearchInput, 'lc public{enter}'); }); - // Expect error message to show - await waitFor(() => - expect( - getAllByText( - 'The requested resource at the ESGF wget API service was invalid.', - { exact: false } - ) - ).toBeTruthy() + // Expect an alert to show up + const alertPopup = await screen.findByText( + 'An error occurred while searching for collections. Please try again later.' ); + expect(alertPopup).toBeTruthy(); }); }); diff --git a/frontend/src/components/Globus/DatasetDownload.tsx b/frontend/src/components/Globus/DatasetDownload.tsx index 7e8c23731..c0ccef210 100644 --- a/frontend/src/components/Globus/DatasetDownload.tsx +++ b/frontend/src/components/Globus/DatasetDownload.tsx @@ -542,6 +542,17 @@ const DatasetDownloadForm: React.FC> = () => { } catch (error) { // eslint-disable-next-line no-console console.error(error); + setAlertPopupState({ + content: + 'An error occurred while searching for collections. Please try again later.', + onCancelAction: () => { + setAlertPopupState({ ...alertPopupState, show: false }); + }, + onOkAction: () => { + setAlertPopupState({ ...alertPopupState, show: false }); + }, + show: true, + }); } finally { setLoadingEndpointSearchResults(false); } diff --git a/frontend/src/components/Search/Table.test.tsx b/frontend/src/components/Search/Table.test.tsx index c8d71b610..72fd120fe 100644 --- a/frontend/src/components/Search/Table.test.tsx +++ b/frontend/src/components/Search/Table.test.tsx @@ -10,7 +10,7 @@ import apiRoutes from '../../api/routes'; import customRender from '../../test/custom-render'; import Table, { Props } from './Table'; import { QualityFlag } from './Tabs'; -import { getRowName } from '../../test/jestTestFunctions'; +import { getRowName, mockConfig } from '../../test/jestTestFunctions'; const user = userEvent.setup(); @@ -451,6 +451,27 @@ describe('test main table UI', () => { ); expect(errorMsg).toBeTruthy(); }); + + it('does not render Globus Ready column when globusEnabledNodes is empty', async () => { + // Set names of the globus enabled nodes + mockConfig.globusEnabledNodes = []; + + customRender(); + + // Check table exists + const table = await screen.findByRole('table'); + expect(table).toBeTruthy(); + + // Check first row exists + const firstRow = await screen.findByRole('row', { + name: getRowName('plus', 'question', 'foo', '3', '1', '1'), + }); + expect(firstRow).toBeTruthy(); + + // Check Globus Ready column does not exist + const globusReadyColumn = screen.queryByText('Globus Ready'); + expect(globusReadyColumn).toBeNull(); + }); }); describe('test QualityFlag', () => {