// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. // SPDX-License-Identifier: Apache-2.0 import { API } from '@aws-amplify/api'; import { I18n } from '@aws-amplify/core'; import { fireEvent, queryByAttribute, render, screen, waitFor } from '@testing-library/react'; import { MemoryRouter, Route, Routes } from 'react-router-dom'; import ConnectionForm from '../connection/ConnectionForm'; import Dashboard from '../connection/Dashboard'; import { ConnectionControl, GetConnectionResponse, ListConnectionsResponse, ListLogsResponse, LogType, MachineProtocol } from '../../util/types'; import { API_NAME } from '../../util/utils'; // Mock API const mockAPI = { get: jest.fn(), post: jest.fn() }; jest.mock('@aws-amplify/api'); API.get = mockAPI.get; API.post = mockAPI.post; const getById = queryByAttribute.bind(null, 'id'); const response: ListConnectionsResponse = { connections: [ { connectionName: 'mock-connection-1', machineName: 'mock-machine-1', protocol: MachineProtocol.OPCDA, status: ConnectionControl.START, sendDataToIoTSiteWise: false, sendDataToIoTTopic: true, sendDataToKinesisDataStreams: false, sendDataToHistorian: false }, { connectionName: 'mock-connection-2', machineName: 'mock-machine-2', protocol: MachineProtocol.OPCUA, status: ConnectionControl.STOP, sendDataToIoTSiteWise: true, sendDataToIoTTopic: false, sendDataToKinesisDataStreams: true, sendDataToHistorian: false }, { connectionName: 'mock-connection-3', machineName: 'mock-machine-3', protocol: MachineProtocol.OSIPI, status: ConnectionControl.STOP, sendDataToIoTSiteWise: true, sendDataToIoTTopic: false, sendDataToKinesisDataStreams: true, sendDataToHistorian: false } ] }; const updateResponse: GetConnectionResponse = { connectionName: 'mock-connection-id', greengrassCoreDeviceName: 'mock-greengrass-device-name', protocol: MachineProtocol.OPCDA, sendDataToIoTSiteWise: true, sendDataToIoTTopic: true, sendDataToKinesisDataStreams: true, sendDataToTimestream: true, sendDataToHistorian: true, area: 'mock-area', machineName: 'mock-machine', opcDa: { machineIp: '1.2.3.4', serverName: 'mock-opcda-server-name', iterations: 3, interval: 3, listTags: ['listTag1.*', 'listTag2.*'], tags: ['tag1', 'tag2'] } }; const logResponse: ListLogsResponse = { logs: [ { connectionName: 'mock-connection-id', logType: LogType.INFO, timestamp: new Date(0).getTime(), message: JSON.stringify({ message: 'info message' }) }, { connectionName: 'mock-connection-id', logType: LogType.ERROR, timestamp: new Date(0).getTime(), message: JSON.stringify({ message: 'error message' }) } ] }; beforeEach(() => { mockAPI.get.mockReset(); mockAPI.post.mockReset(); }); test('renders the Dashboard component when connections are empty', async () => { mockAPI.get.mockResolvedValueOnce({ connections: [] }); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'empty-connection-jumbotron'); }); expect(mockAPI.get).toHaveBeenCalledTimes(1); expect(mockAPI.get).toHaveBeenCalledWith(API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(dashboard.container).toMatchSnapshot(); }); test('renders the Dashboard component when getting connections fails', async () => { mockAPI.get.mockRejectedValueOnce('Error'); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'empty-connection-jumbotron'); }); expect(mockAPI.get).toHaveBeenCalledTimes(1); expect(mockAPI.get).toHaveBeenCalledWith(API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(dashboard.container).toMatchSnapshot(); }); test('renders the Dashboard component when connections exist', async () => { mockAPI.get.mockResolvedValueOnce(response); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); expect(mockAPI.get).toHaveBeenCalledTimes(1); expect(mockAPI.get).toHaveBeenCalledWith(API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(dashboard.container).toMatchSnapshot(); }); test('renders the Dashboard component when connections and the next token exist ', async () => { mockAPI.get.mockResolvedValueOnce({ ...response, nextToken: JSON.stringify({ connectionName: 'mock-connection-2' }) }); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); expect(mockAPI.get).toHaveBeenCalledTimes(1); expect(mockAPI.get).toHaveBeenCalledWith(API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(dashboard.container).toMatchSnapshot(); }); test('tests next and prev buttons', async () => { mockAPI.get .mockResolvedValueOnce({ ...response, nextToken: JSON.stringify({ connectionName: 'mock-connection-2' }) }) .mockResolvedValueOnce({ ...response, nextToken: JSON.stringify({ connectionName: 'mock-connection-3' }) }) .mockResolvedValueOnce({ ...response, nextToken: undefined }) .mockResolvedValueOnce({ ...response, nextToken: JSON.stringify({ connectionName: 'mock-connection-2' }) }); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); for (let i = 0; i < 2; i++) { fireEvent.click(screen.getByText(I18n.get('next.page'))); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); } fireEvent.click(screen.getByText(I18n.get('prev.page'))); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); expect(mockAPI.get).toHaveBeenCalledTimes(4); expect(mockAPI.get).toHaveBeenNthCalledWith(1, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.get).toHaveBeenNthCalledWith(2, API_NAME, '/connections', { queryStringParameters: { nextToken: JSON.stringify({ connectionName: 'mock-connection-2' }) } }); expect(mockAPI.get).toHaveBeenNthCalledWith(3, API_NAME, '/connections', { queryStringParameters: { nextToken: JSON.stringify({ connectionName: 'mock-connection-3' }) } }); expect(mockAPI.get).toHaveBeenNthCalledWith(4, API_NAME, '/connections', { queryStringParameters: { nextToken: JSON.stringify({ connectionName: 'mock-connection-2' }) } }); }); test('tests refresh button', async () => { mockAPI.get.mockResolvedValueOnce(response); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); fireEvent.click(screen.getByText(I18n.get('refresh'))); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); expect(mockAPI.get).toHaveBeenCalledTimes(2); expect(mockAPI.get).toHaveBeenNthCalledWith(1, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.get).toHaveBeenNthCalledWith(2, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); }); test('tests create connection button', async () => { mockAPI.get.mockResolvedValueOnce({ connections: [] }); const dashboard = render( } /> } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); fireEvent.click(screen.getByText(I18n.get('create.connection'))); await waitFor(() => { screen.findByText('mock-connection-id'); }); expect(screen.findByText('mock-connection-id')).not.toBeNull(); }); test('tests stop button', async () => { const connection = response.connections[0]; mockAPI.get.mockResolvedValue({ connections: [connection] }); mockAPI.post.mockResolvedValueOnce({ connectionName: connection.connectionName }); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); fireEvent.click(screen.getByText(I18n.get('stop'))); await waitFor(() => { screen.findByText(I18n.get('info.message.stop.connection')); }); fireEvent.click(screen.getByText(I18n.get('ok'))); expect(mockAPI.get).toHaveBeenCalledTimes(2); expect(mockAPI.get).toHaveBeenNthCalledWith(1, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.get).toHaveBeenNthCalledWith(2, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.post).toHaveBeenCalledTimes(1); expect(mockAPI.post).toHaveBeenCalledWith(API_NAME, '/connections', { body: { connectionName: connection.connectionName, control: ConnectionControl.STOP, protocol: connection.protocol } }); }); test('tests start button', async () => { const connection = response.connections[1]; mockAPI.get.mockResolvedValue({ connections: [connection] }); mockAPI.post.mockResolvedValueOnce({ connectionName: connection.connectionName }); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); fireEvent.click(screen.getByText(I18n.get('start'))); await waitFor(() => { screen.findByText(I18n.get('info.message.start.connection')); }); fireEvent.click(screen.getByText(I18n.get('ok'))); expect(mockAPI.get).toHaveBeenCalledTimes(2); expect(mockAPI.get).toHaveBeenNthCalledWith(1, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.get).toHaveBeenNthCalledWith(2, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.post).toHaveBeenCalledTimes(1); expect(mockAPI.post).toHaveBeenCalledWith(API_NAME, '/connections', { body: { connectionName: connection.connectionName, control: ConnectionControl.START, protocol: connection.protocol } }); }); test('tests update connection button', async () => { const connection = response.connections[0]; mockAPI.get.mockResolvedValueOnce({ connections: [connection] }).mockResolvedValueOnce(updateResponse); const dashboard = render( } /> } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); const manageConnectionButton = getById(dashboard.container, `manage-connection-${connection.connectionName}`); expect(manageConnectionButton).not.toBeNull(); fireEvent.click(manageConnectionButton as Element); await waitFor(() => { screen.findByText(I18n.get('update.connection')); }); expect(screen.getByText(I18n.get('update.connection'))).not.toBeNull(); fireEvent.click(screen.getByText(I18n.get('update.connection'))); await waitFor(() => { screen.findByText(connection.connectionName); }); expect(mockAPI.get).toHaveBeenCalledTimes(2); expect(mockAPI.get).toHaveBeenNthCalledWith(1, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.get).toHaveBeenNthCalledWith(2, API_NAME, `/connections/${connection.connectionName}`, {}); }); test('tests check connectivity button', async () => { const connection = response.connections[0]; mockAPI.get.mockResolvedValueOnce({ connections: [connection] }); mockAPI.post.mockResolvedValueOnce({ connectionName: connection.connectionName }); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); const manageConnectionButton = getById(dashboard.container, `manage-connection-${connection.connectionName}`); expect(manageConnectionButton).not.toBeNull(); fireEvent.click(manageConnectionButton as Element); await waitFor(() => { screen.findByText(I18n.get('check.connectivity')); }); expect(screen.getByText(I18n.get('check.connectivity'))).not.toBeNull(); fireEvent.click(screen.getByText(I18n.get('check.connectivity'))); expect(mockAPI.get).toHaveBeenCalledTimes(1); expect(mockAPI.get).toHaveBeenCalledWith(API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.post).toHaveBeenCalledTimes(1); expect(mockAPI.post).toHaveBeenCalledWith(API_NAME, '/connections', { body: { connectionName: connection.connectionName, control: ConnectionControl.PUSH, protocol: connection.protocol } }); }); test('tests get connection configuration button', async () => { const connection = response.connections[0]; mockAPI.get.mockResolvedValueOnce({ connections: [connection] }); mockAPI.post.mockResolvedValueOnce({ connectionName: connection.connectionName }); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); const manageConnectionButton = getById(dashboard.container, `manage-connection-${connection.connectionName}`); expect(manageConnectionButton).not.toBeNull(); fireEvent.click(manageConnectionButton as Element); await waitFor(() => { screen.findByText(I18n.get('get.connection.configuration')); }); expect(screen.getByText(I18n.get('get.connection.configuration'))).not.toBeNull(); fireEvent.click(screen.getByText(I18n.get('get.connection.configuration'))); expect(mockAPI.get).toHaveBeenCalledTimes(1); expect(mockAPI.get).toHaveBeenCalledWith(API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.post).toHaveBeenCalledTimes(1); expect(mockAPI.post).toHaveBeenCalledWith(API_NAME, '/connections', { body: { connectionName: connection.connectionName, control: ConnectionControl.PULL, protocol: connection.protocol } }); }); test('tests view connection logs button', async () => { const connection = response.connections[0]; mockAPI.get.mockResolvedValueOnce({ connections: [connection] }).mockResolvedValueOnce({ logs: logResponse }); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); const manageConnectionButton = getById(dashboard.container, `manage-connection-${connection.connectionName}`); expect(manageConnectionButton).not.toBeNull(); fireEvent.click(manageConnectionButton as Element); await waitFor(() => { screen.findByText(I18n.get('view.connection.logs')); }); expect(screen.getByText(I18n.get('view.connection.logs'))).not.toBeNull(); fireEvent.click(screen.getByText(I18n.get('view.connection.logs'))); await waitFor(() => { getById(dashboard.container, 'connection-logs-table'); }); expect(mockAPI.get).toHaveBeenCalledTimes(2); expect(mockAPI.get).toHaveBeenNthCalledWith(1, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.get).toHaveBeenNthCalledWith(2, API_NAME, `/logs/${connection.connectionName}`, { queryStringParameters: { nextToken: undefined } }); }); test('tests API errors to manage connection, only demonstrates STOP', async () => { const connection = response.connections[0]; mockAPI.get.mockResolvedValue({ connections: [connection] }); mockAPI.post.mockRejectedValueOnce({ message: 'API Failure' }); const dashboard = render( } /> ); await waitFor(() => { getById(dashboard.container, 'connections-table'); }); fireEvent.click(screen.getByText(I18n.get('stop'))); await waitFor(() => { screen.findByText(I18n.get('error.message.control.connection')); }); fireEvent.click(screen.getByText(I18n.get('ok'))); expect(mockAPI.get).toHaveBeenCalledTimes(2); expect(mockAPI.get).toHaveBeenNthCalledWith(1, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.get).toHaveBeenNthCalledWith(2, API_NAME, '/connections', { queryStringParameters: { nextToken: undefined } }); expect(mockAPI.post).toHaveBeenCalledTimes(1); expect(mockAPI.post).toHaveBeenCalledWith(API_NAME, '/connections', { body: { connectionName: connection.connectionName, control: ConnectionControl.STOP, protocol: connection.protocol } }); });