import React from 'react'; import { renderWithRedux, waitFor, patientlyWaitFor, fireEvent, act } from 'react-testing-lib-wrapper'; import { nockInstance, assertNockRequest, mockForemanAutocomplete } from '../../../../../test-utils/nockWrapper'; import { foremanApi } from '../../../../../services/api'; import { REX_FEATURES } from '../RemoteExecutionConstants'; import { HOST_TRACES_KEY, TRACES_SEARCH_QUERY } from '../TracesTab/HostTracesConstants'; import TracesTab from '../TracesTab/TracesTab.js'; import mockTraceData from './traces.fixtures.json'; import mockResolveTraceTask from './resolveTraces.fixtures.json'; import emptyTraceResults from './tracerEmptyTraceResults.fixtures.json'; import mockJobInvocationStatus from './tracerEnableJobInvocation.fixtures.json'; const hostName = 'client.example.com'; jest.mock('../../hostDetailsHelpers', () => ({ ...jest.requireActual('../../hostDetailsHelpers'), userPermissionsFromHostDetails: () => ({ create_job_invocations: true, }), })); const tracerInstalledResponse = { id: 1, name: 'client.example.com', content_facet_attributes: { katello_tracer_installed: true, }, }; const tracerNotInstalledResponse = { ...tracerInstalledResponse, content_facet_attributes: { katello_tracer_installed: false, }, }; const renderOptions = isTracerInstalled => ({ // sets initial Redux state apiNamespace: HOST_TRACES_KEY, initialState: { API: { HOST_DETAILS: { name: hostName, id: 1, response: isTracerInstalled ? tracerInstalledResponse : tracerNotInstalledResponse, status: 'RESOLVED', }, }, }, }); const actionMenuToTheRightOf = node => node.nextElementSibling.firstElementChild.firstElementChild; const hostTraces = foremanApi.getApiUrl('/hosts/1/traces'); const autocompleteUrl = '/hosts/1/traces/auto_complete_search'; const jobInvocations = foremanApi.getApiUrl('/job_invocations'); let firstTrace; describe('With tracer installed', () => { beforeEach(() => { const { results } = mockTraceData; [firstTrace] = results; }); test('Can call API for traces and show on screen on page load', async (done) => { // Setup autocomplete with mockForemanAutoComplete since we aren't adding /katello const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl); // return tracedata results when we look for traces const scope = nockInstance .get(hostTraces) .query(true) .reply(200, mockTraceData); const { queryByText } = renderWithRedux(, renderOptions(true)); // Assert that the traces are now showing on the screen, but wait for them to appear. await patientlyWaitFor(() => expect(queryByText(firstTrace.application)).toBeInTheDocument()); // Assert request was made and completed, see helper function assertNockRequest(autocompleteScope); assertNockRequest(scope, done); }); test('Can handle no traces being present', async (done) => { // Setup autocomplete with mockForemanAutoComplete since we aren't adding /katello const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl); const scope = nockInstance .get(hostTraces) .query(true) .reply(200, emptyTraceResults); const { queryByText } = renderWithRedux(, renderOptions(true)); // Assert that there are not any traces showing on the screen. await patientlyWaitFor(() => expect(queryByText('No applications to restart')).toBeInTheDocument()); // Assert request was made and completed, see helper function assertNockRequest(autocompleteScope); assertNockRequest(scope, done); }); test('Can bulk restart traces via Restart App button', async (done) => { // Setup autocomplete with mockForemanAutoComplete since we aren't adding /katello const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl); const scope = nockInstance .get(hostTraces) .query(true) .reply(200, mockTraceData); const resolveTracesScope = nockInstance .post(jobInvocations) .reply(201, mockResolveTraceTask); const { getByText, getByLabelText } = renderWithRedux( , renderOptions(true), ); let traceCheckbox; // Find the trace checkbox. await patientlyWaitFor(() => { traceCheckbox = getByLabelText('Select row 0'); }); fireEvent.click(traceCheckbox); expect(traceCheckbox.checked).toEqual(true); const restartAppButton = getByText('Restart app'); // wait 50ms so that the button is enabled await waitFor(() => { expect(restartAppButton.parentElement).not.toHaveClass('pf-m-disabled'); restartAppButton.click(); }); assertNockRequest(autocompleteScope); assertNockRequest(resolveTracesScope); assertNockRequest(scope, done); }); test('Can bulk restart traces via remote execution', async (done) => { // This is the same test as above, // but using the table action bar instead of the Restart app button const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl); const scope = nockInstance .get(hostTraces) .query(true) .reply(200, mockTraceData); const resolveTracesScope = nockInstance .post(jobInvocations) .reply(201, mockResolveTraceTask); const { getByLabelText, queryByText } = renderWithRedux( , renderOptions(true), ); let traceCheckbox; // Find the trace. await patientlyWaitFor(() => { traceCheckbox = getByLabelText('Select row 0'); }); fireEvent.click(traceCheckbox); expect(traceCheckbox.checked).toEqual(true); const actionMenu = getByLabelText('bulk_actions'); actionMenu.click(); const viaRexAction = queryByText('Restart via remote execution'); expect(viaRexAction).toBeInTheDocument(); viaRexAction.click(); assertNockRequest(autocompleteScope); assertNockRequest(resolveTracesScope); assertNockRequest(scope, done); }); test('Can select all, exclude and bulk restart traces via remote execution', async (done) => { // This is the same test as above, // but using the table action bar instead of the Restart app button const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl); const thirdTrace = mockTraceData.results[2]; const scope = nockInstance .get(hostTraces) .query(true) .reply(200, mockTraceData); const jobInvocationBody = ({ job_invocation: { inputs } }) => inputs[TRACES_SEARCH_QUERY] === `id !^ (${firstTrace.id},${thirdTrace.id})`; const resolveTracesScope = nockInstance .post(jobInvocations, jobInvocationBody) .reply(201, mockResolveTraceTask); const { getByLabelText, getByText, } = renderWithRedux( , renderOptions(true), ); let traceCheckbox; // Find the trace. await patientlyWaitFor(() => { traceCheckbox = getByLabelText('Select row 0'); }); const selectAllCheckbox = getByLabelText('Select all'); fireEvent.click(selectAllCheckbox); expect(traceCheckbox.checked).toEqual(true); fireEvent.click(getByLabelText('Select row 0')); // de select fireEvent.click(getByLabelText('Select row 2')); // de select fireEvent.click(getByText('Restart app')); assertNockRequest(autocompleteScope); assertNockRequest(resolveTracesScope); assertNockRequest(scope, done); }); test('Can restart a single trace via remote execution', async (done) => { const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl); const scope = nockInstance .get(hostTraces) .query(true) .reply(200, mockTraceData); const resolveTracesScope = nockInstance .post(jobInvocations) .reply(201, mockResolveTraceTask); const { getByText } = renderWithRedux( , renderOptions(true), ); let traceActionMenu; await patientlyWaitFor(() => { const traceNameNode = getByText(firstTrace.helper); traceActionMenu = actionMenuToTheRightOf(traceNameNode); expect(traceActionMenu).toHaveAttribute('aria-label', 'Actions'); }); traceActionMenu.click(); let viaRexAction; await patientlyWaitFor(() => { viaRexAction = getByText('Restart via remote execution'); expect(viaRexAction).toBeInTheDocument(); }); viaRexAction.click(); assertNockRequest(autocompleteScope); assertNockRequest(resolveTracesScope); assertNockRequest(scope, done); }); test('Can restart a single trace via customized remote execution', async (done) => { const feature = REX_FEATURES.KATELLO_HOST_TRACER_RESOLVE; const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl); const scope = nockInstance .get(hostTraces) .query(true) .reply(200, mockTraceData); const { getByText } = renderWithRedux( , renderOptions(true), ); let traceActionMenu; await patientlyWaitFor(() => { const traceNameNode = getByText(firstTrace.helper); traceActionMenu = actionMenuToTheRightOf(traceNameNode); expect(traceActionMenu).toHaveAttribute('aria-label', 'Actions'); }); fireEvent.click(traceActionMenu); let viaCustomizedRexAction; await patientlyWaitFor(() => { viaCustomizedRexAction = getByText('Restart via customized remote execution'); expect(viaCustomizedRexAction).toBeInTheDocument(); }); expect(viaCustomizedRexAction).toHaveAttribute( 'href', `/job_invocations/new?feature=${feature}&search=name%20%5E%20(${hostName})&inputs%5BTraces%20search%20query%5D=id%20=%20${firstTrace.id}`, ); assertNockRequest(autocompleteScope); assertNockRequest(scope, done); }); test('Can bulk restart traces via customized remote execution', async (done) => { const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl); const feature = REX_FEATURES.KATELLO_HOST_TRACER_RESOLVE; const scope = nockInstance .get(hostTraces) .query(true) .reply(200, mockTraceData); const { getByLabelText, queryByText } = renderWithRedux( , renderOptions(true), ); let traceCheckbox; await patientlyWaitFor(() => { traceCheckbox = getByLabelText('Select row 0'); }); fireEvent.click(traceCheckbox); expect(traceCheckbox.checked).toEqual(true); const actionMenu = getByLabelText('bulk_actions'); fireEvent.click(actionMenu); const viaCustomizedRexAction = queryByText('Restart via customized remote execution'); expect(viaCustomizedRexAction).toBeInTheDocument(); expect(viaCustomizedRexAction).toHaveAttribute( 'href', `/job_invocations/new?feature=${feature}&search=name%20%5E%20(${hostName})&inputs%5BTraces%20search%20query%5D=id%20%5E%20(${firstTrace.id})`, ); assertNockRequest(autocompleteScope); assertNockRequest(scope, done); }); describe('Remote execution URL helper logic', () => { beforeEach(() => { const { results } = mockTraceData; [firstTrace] = results; }); test('Does not allow selection of session type traces', async (done) => { const autocompleteScope = mockForemanAutocomplete(nockInstance, autocompleteUrl); const scope = nockInstance .get(hostTraces) .query(true) .reply(200, mockTraceData); const { getByLabelText } = renderWithRedux( , renderOptions(true), ); let traceCheckbox; await patientlyWaitFor(() => { traceCheckbox = getByLabelText('Select row 1'); }); expect(traceCheckbox.disabled).toEqual(true); assertNockRequest(autocompleteScope); assertNockRequest(scope, done); }); }); }); describe('Without tracer installed', () => { test('Shows Enable Tracer empty state', async () => { const { queryByText } = renderWithRedux(, renderOptions(false)); await patientlyWaitFor(() => expect(queryByText('Traces are not enabled')).toBeInTheDocument()); // Assert request was made and completed, see helper function }); test('Shows Enable Tracer modal', async () => { const { getByText, queryByText } = renderWithRedux(, renderOptions(false)); await patientlyWaitFor(() => expect(queryByText('Traces are not enabled')).toBeInTheDocument()); const enableTracesButton = getByText('Enable Traces'); enableTracesButton.click(); expect(queryByText('via remote execution')).toBeInTheDocument(); const cancelLink = queryByText('Cancel'); cancelLink.click(); expect(queryByText('via remote execution')).not.toBeInTheDocument(); }); test('Can enable tracer via remote execution', async (done) => { const jobInvocationScope = nockInstance .post(jobInvocations) .reply(201, mockJobInvocationStatus); const { getByText, getByRole, queryByText } = renderWithRedux(, renderOptions(false)); await patientlyWaitFor(() => expect(queryByText('Traces are not enabled')).toBeInTheDocument()); const enableTracesButton = getByText('Enable Traces'); enableTracesButton.click(); expect(queryByText('via remote execution')).toBeVisible(); const enableTracesModalButton = getByRole('button', { name: 'Enable Tracer' }); fireEvent.click(enableTracesModalButton); expect(queryByText('via remote execution')).not.toBeInTheDocument(); assertNockRequest(jobInvocationScope, done); }); test('Can enable tracer via customized remote execution', async () => { const feature = REX_FEATURES.KATELLO_PACKAGE_INSTALL; const { getByText, getByRole, queryByText } = renderWithRedux(, renderOptions(false)); await patientlyWaitFor(() => expect(queryByText('Traces are not enabled')).toBeInTheDocument()); const enableTracesButton = getByText('Enable Traces'); enableTracesButton.click(); const dropdown = queryByText('via remote execution'); await act(async () => fireEvent.click(dropdown)); const viaCustomizedRex = queryByText('via customized remote execution'); expect(viaCustomizedRex).toBeVisible(); viaCustomizedRex.click(); expect(queryByText('via remote execution')).not.toBeInTheDocument(); const enableTracesModalLink = getByRole('link', { name: 'Enable Tracer' }); expect(enableTracesModalLink) .toHaveAttribute( 'href', `/job_invocations/new?feature=${feature}&search=name%20%5E%20(${hostName})&inputs%5Bpackage%5D=katello-host-tools-tracer`, ); enableTracesModalLink.click(); expect(enableTracesModalLink).toHaveClass('pf-m-in-progress'); }); });