Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: [M3-8565] - Add unit tests for rest of NodeBalancers package #10945

Merged
merged 9 commits into from
Sep 18, 2024
5 changes: 5 additions & 0 deletions packages/manager/.changeset/pr-10945-tests-1726507532580.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Tests
---

Add unit tests for rest of NodeBalancers package ([#10945](https://github.com/linode/manager/pull/10945))
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { Linode } from '@linode/api-v4/lib/linodes';
import { Box } from '@mui/material';
import * as React from 'react';

import { SelectedIcon } from 'src/components/Autocomplete/Autocomplete.styles';
import { LinodeSelect } from 'src/features/Linodes/LinodeSelect/LinodeSelect';
import { privateIPRegex } from 'src/utilities/ipUtils';

import type { Linode } from '@linode/api-v4/lib/linodes';
import type { TextFieldProps } from 'src/components/TextField';

interface ConfigNodeIPSelectProps {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import { fireEvent } from '@testing-library/react';
import * as React from 'react';

import { renderWithTheme } from 'src/utilities/testHelpers';

import { NodeBalancerConfigNode } from './NodeBalancerConfigNode';

import type { NodeBalancerConfigNodeProps } from './NodeBalancerConfigNode';

const node = {
address: 'some address',
label: 'some label',
};

const props: NodeBalancerConfigNodeProps = {
configIdx: 1,
disabled: false,
forEdit: true,
idx: 1,
node,
onNodeAddressChange: vi.fn(),
onNodeLabelChange: vi.fn(),
onNodeModeChange: vi.fn(),
onNodePortChange: vi.fn(),
onNodeWeightChange: vi.fn(),
removeNode: vi.fn(),
};

describe('NodeBalancerConfigNode', () => {
it('renders the NodeBAlancerConfigNode', () => {
coliu-akamai marked this conversation as resolved.
Show resolved Hide resolved
const { getByLabelText, getByText, queryByText } = renderWithTheme(
<NodeBalancerConfigNode {...props} />
);

expect(getByLabelText('Label')).toBeVisible();
expect(getByLabelText('Port')).toBeVisible();
expect(getByLabelText('Weight')).toBeVisible();
expect(getByText('Mode')).toBeVisible();
expect(getByText('Remove')).toBeVisible();
expect(queryByText('Status')).not.toBeInTheDocument();
});

it('renders the node status', () => {
const { getByText } = renderWithTheme(
<NodeBalancerConfigNode {...props} node={{ ...node, status: 'DOWN' }} />
);

expect(getByText('Status')).toBeVisible();
expect(getByText('DOWN')).toBeVisible();
});

it('cannot change the mode if the node is not for edit', () => {
const { queryByText } = renderWithTheme(
<NodeBalancerConfigNode {...props} forEdit={false} />
);

expect(queryByText('Mode')).not.toBeInTheDocument();
});

it('cannot remove the node if the node is not for edit or is the first node', () => {
const { queryByText } = renderWithTheme(
<NodeBalancerConfigNode {...props} forEdit={false} idx={0} />
);

expect(queryByText('Remove')).not.toBeInTheDocument();
});

it('removes the node', () => {
const { getByText } = renderWithTheme(
<NodeBalancerConfigNode {...props} />
);

fireEvent.click(getByText('Remove'));
expect(props.removeNode).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import Grid from '@mui/material/Unstable_Grid2';
import { styled } from '@mui/material/styles';
import Grid from '@mui/material/Unstable_Grid2';
import * as React from 'react';

import { Autocomplete } from 'src/components/Autocomplete/Autocomplete';
Expand All @@ -13,8 +13,8 @@ import { Typography } from 'src/components/Typography';
import { getErrorMap } from 'src/utilities/errorUtils';

import { ConfigNodeIPSelect } from './ConfigNodeIPSelect';
import { NodeBalancerConfigNodeFields } from './types';

import type { NodeBalancerConfigNodeFields } from './types';
import type { NodeBalancerConfigNodeMode } from '@linode/api-v4';

export interface NodeBalancerConfigNodeProps {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const node: NodeBalancerConfigNodeFields = {
weight: 100,
};

const props: NodeBalancerConfigPanelProps = {
export const nbConfigPanelMockPropsForTest: NodeBalancerConfigPanelProps = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to not stick with props for the variable name?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I exported the props to use in NodeBalancerPassiveCheck.test.tsx - thought it might be helpful to mirror how we update the names of interfaces for component props to be more specific when exporting them, and avoid something like import { props } from file.

Can definitely change back to props though!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh gotcha, in that case I agree with how you handled it originally here πŸ‘πŸΎ

addNode: vi.fn(),
algorithm: 'roundrobin',
checkBody: '',
Expand Down Expand Up @@ -70,7 +70,17 @@ const props: NodeBalancerConfigPanelProps = {
sslCertificate: '',
};

const activeHealthChecks = ['Interval', 'Timeout', 'Attempts'];
const activeHealthChecksFormInputs = ['Interval', 'Timeout', 'Attempts'];

const activeHealthChecksHelperText = [
'Seconds between health check probes',
'Seconds to wait before considering the probe a failure. 1-30. Must be less than check_interval.',
'Number of failed probes before taking a node out of rotation. 1-30',
];

const sslCertificate = 'ssl-certificate';
const privateKey = 'private-key';
const proxyProtocol = 'Proxy Protocol';

describe('NodeBalancerConfigPanel', () => {
it('renders the NodeBalancerConfigPanel', () => {
Expand All @@ -79,7 +89,10 @@ describe('NodeBalancerConfigPanel', () => {
getByText,
queryByLabelText,
queryByTestId,
} = renderWithTheme(<NodeBalancerConfigPanel {...props} />);
queryByText,
} = renderWithTheme(
<NodeBalancerConfigPanel {...nbConfigPanelMockPropsForTest} />
);

expect(getByLabelText('Protocol')).toBeVisible();
expect(getByLabelText('Algorithm')).toBeVisible();
Expand Down Expand Up @@ -109,75 +122,107 @@ describe('NodeBalancerConfigPanel', () => {
expect(getByText('Add a Node')).toBeVisible();
expect(getByText('Backend Nodes')).toBeVisible();

activeHealthChecks.forEach((type) => {
expect(queryByLabelText(type)).not.toBeInTheDocument();
activeHealthChecksFormInputs.forEach((formLabel) => {
expect(queryByLabelText(formLabel)).not.toBeInTheDocument();
});
expect(queryByTestId('ssl-certificate')).not.toBeInTheDocument();
expect(queryByTestId('private-key')).not.toBeInTheDocument();
activeHealthChecksHelperText.forEach((helperText) => {
expect(queryByText(helperText)).not.toBeInTheDocument();
});
expect(queryByTestId(sslCertificate)).not.toBeInTheDocument();
expect(queryByTestId(privateKey)).not.toBeInTheDocument();
expect(queryByTestId('http-path')).not.toBeInTheDocument();
expect(queryByTestId('http-body')).not.toBeInTheDocument();
expect(queryByLabelText('Proxy Protocol')).not.toBeInTheDocument();
expect(queryByLabelText(proxyProtocol)).not.toBeInTheDocument();
});

it('renders form fields specific to the HTTPS protocol', () => {
const { getByTestId, queryByLabelText } = renderWithTheme(
<NodeBalancerConfigPanel {...props} protocol="https" />
<NodeBalancerConfigPanel
{...nbConfigPanelMockPropsForTest}
protocol="https"
/>
);

expect(getByTestId('ssl-certificate')).toBeVisible();
expect(getByTestId('private-key')).toBeVisible();
expect(queryByLabelText('Proxy Protocol')).not.toBeInTheDocument();
expect(getByTestId(sslCertificate)).toBeVisible();
expect(getByTestId(privateKey)).toBeVisible();
expect(queryByLabelText(proxyProtocol)).not.toBeInTheDocument();
});

it('renders form fields specific to the TCP protocol', () => {
const { getByLabelText, queryByTestId } = renderWithTheme(
<NodeBalancerConfigPanel {...props} protocol="tcp" />
<NodeBalancerConfigPanel
{...nbConfigPanelMockPropsForTest}
protocol="tcp"
/>
);

expect(getByLabelText('Proxy Protocol')).toBeVisible();
expect(queryByTestId('ssl-certificate')).not.toBeInTheDocument();
expect(queryByTestId('private-key')).not.toBeInTheDocument();
expect(getByLabelText(proxyProtocol)).toBeVisible();
expect(queryByTestId(sslCertificate)).not.toBeInTheDocument();
expect(queryByTestId(privateKey)).not.toBeInTheDocument();
});

it('renders fields specific to the Active Health Check type of TCP Connection', () => {
const { getByLabelText, queryByTestId } = renderWithTheme(
<NodeBalancerConfigPanel {...props} healthCheckType="connection" />
const { getByLabelText, getByText, queryByTestId } = renderWithTheme(
<NodeBalancerConfigPanel
{...nbConfigPanelMockPropsForTest}
healthCheckType="connection"
/>
);

activeHealthChecks.forEach((type) => {
expect(getByLabelText(type)).toBeVisible();
activeHealthChecksFormInputs.forEach((formLabel) => {
expect(getByLabelText(formLabel)).toBeVisible();
});
activeHealthChecksHelperText.forEach((helperText) => {
expect(getByText(helperText)).toBeVisible();
});
expect(queryByTestId('http-path')).not.toBeInTheDocument();
expect(queryByTestId('http-body')).not.toBeInTheDocument();
});

it('renders fields specific to the Active Health Check type of HTTP Status', () => {
const { getByLabelText, getByTestId, queryByTestId } = renderWithTheme(
<NodeBalancerConfigPanel {...props} healthCheckType="http" />
const {
getByLabelText,
getByTestId,
getByText,
queryByTestId,
} = renderWithTheme(
<NodeBalancerConfigPanel
{...nbConfigPanelMockPropsForTest}
healthCheckType="http"
/>
);

activeHealthChecks.forEach((type) => {
expect(getByLabelText(type)).toBeVisible();
activeHealthChecksFormInputs.forEach((formLabel) => {
expect(getByLabelText(formLabel)).toBeVisible();
});
activeHealthChecksHelperText.forEach((helperText) => {
expect(getByText(helperText)).toBeVisible();
});
expect(getByTestId('http-path')).toBeVisible();
expect(queryByTestId('http-body')).not.toBeInTheDocument();
});

it('renders fields specific to the Active Health Check type of HTTP Body', () => {
const { getByLabelText, getByTestId } = renderWithTheme(
<NodeBalancerConfigPanel {...props} healthCheckType="http_body" />
const { getByLabelText, getByTestId, getByText } = renderWithTheme(
<NodeBalancerConfigPanel
{...nbConfigPanelMockPropsForTest}
healthCheckType="http_body"
/>
);

activeHealthChecks.forEach((type) => {
expect(getByLabelText(type)).toBeVisible();
activeHealthChecksFormInputs.forEach((formLabel) => {
expect(getByLabelText(formLabel)).toBeVisible();
});
activeHealthChecksHelperText.forEach((helperText) => {
expect(getByText(helperText)).toBeVisible();
});
expect(getByTestId('http-path')).toBeVisible();
expect(getByTestId('http-body')).toBeVisible();
});

it('renders the relevant helper text for the Round Robin algorithm', () => {
const { getByText, queryByText } = renderWithTheme(
<NodeBalancerConfigPanel {...props} />
<NodeBalancerConfigPanel {...nbConfigPanelMockPropsForTest} />
);

expect(getByText(ROUND_ROBIN_ALGORITHM_HELPER_TEXT)).toBeVisible();
Expand All @@ -189,7 +234,10 @@ describe('NodeBalancerConfigPanel', () => {

it('renders the relevant helper text for the Least Connections algorithm', () => {
const { getByText, queryByText } = renderWithTheme(
<NodeBalancerConfigPanel {...props} algorithm={'leastconn'} />
<NodeBalancerConfigPanel
{...nbConfigPanelMockPropsForTest}
algorithm={'leastconn'}
/>
);

expect(getByText(LEAST_CONNECTIONS_ALGORITHM_HELPER_TEXT)).toBeVisible();
Expand All @@ -201,7 +249,10 @@ describe('NodeBalancerConfigPanel', () => {

it('renders the relevant helper text for the Source algorithm', () => {
const { getByText, queryByText } = renderWithTheme(
<NodeBalancerConfigPanel {...props} algorithm={'source'} />
<NodeBalancerConfigPanel
{...nbConfigPanelMockPropsForTest}
algorithm={'source'}
/>
);

expect(getByText(SOURCE_ALGORITHM_HELPER_TEXT)).toBeVisible();
Expand All @@ -215,49 +266,55 @@ describe('NodeBalancerConfigPanel', () => {

it('adds another backend node', () => {
const { getByText } = renderWithTheme(
<NodeBalancerConfigPanel {...props} />
<NodeBalancerConfigPanel {...nbConfigPanelMockPropsForTest} />
);

const addNodeButton = getByText('Add a Node');
fireEvent.click(addNodeButton);
expect(props.addNode).toHaveBeenCalled();
expect(nbConfigPanelMockPropsForTest.addNode).toHaveBeenCalled();
});

it('cannot remove a backend node if there is only one node', () => {
const { queryByText } = renderWithTheme(
<NodeBalancerConfigPanel {...props} />
<NodeBalancerConfigPanel {...nbConfigPanelMockPropsForTest} />
);

expect(queryByText('Remove')).not.toBeInTheDocument();
});

it('removes a backend node', () => {
const { getByText } = renderWithTheme(
<NodeBalancerConfigPanel {...props} nodes={[{ ...node }, node]} />
<NodeBalancerConfigPanel
{...nbConfigPanelMockPropsForTest}
nodes={[{ ...node }, node]}
/>
);

const removeNodeButton = getByText('Remove');
fireEvent.click(removeNodeButton);
expect(props.removeNode).toHaveBeenCalled();
expect(nbConfigPanelMockPropsForTest.removeNode).toHaveBeenCalled();
});

it('deletes the configuration panel', () => {
const { getByText } = renderWithTheme(
<NodeBalancerConfigPanel {...props} />
<NodeBalancerConfigPanel {...nbConfigPanelMockPropsForTest} />
);

const deleteConfigButton = getByText('Delete');
fireEvent.click(deleteConfigButton);
expect(props.onDelete).toHaveBeenCalled();
expect(nbConfigPanelMockPropsForTest.onDelete).toHaveBeenCalled();
});

it('saves the input after editing the configuration', () => {
const { getByText } = renderWithTheme(
<NodeBalancerConfigPanel {...props} forEdit={true} />
<NodeBalancerConfigPanel
{...nbConfigPanelMockPropsForTest}
forEdit={true}
/>
);

const editConfigButton = getByText('Save');
fireEvent.click(editConfigButton);
expect(props.onSave).toHaveBeenCalled();
expect(nbConfigPanelMockPropsForTest.onSave).toHaveBeenCalled();
});
});
Loading
Loading