diff --git a/app/pages/project/networking/VpcPage/VpcPage.spec.ts b/app/pages/project/networking/VpcPage/VpcPage.spec.ts index 2734fedf84..a61d72ddf2 100644 --- a/app/pages/project/networking/VpcPage/VpcPage.spec.ts +++ b/app/pages/project/networking/VpcPage/VpcPage.spec.ts @@ -1,9 +1,11 @@ import { + clickByRole, renderAppAt, screen, - waitForElementToBeRemoved, - clickByRole, typeByRole, + waitForElementToBeRemoved, + userEvent, + getByRole, } from 'app/test-utils' import { defaultFirewallRules } from '@oxide/api-mocks' @@ -22,7 +24,7 @@ describe('VpcPage', () => { expect(screen.queryByTestId('create-vpc-subnet-modal')).toBeNull() // click button to open modal - clickByRole('button', 'New subnet') + await clickByRole('button', 'New subnet') // modal is open screen.getByRole('dialog', { name: 'Create subnet' }) @@ -32,7 +34,7 @@ describe('VpcPage', () => { typeByRole('textbox', 'Name', 'mock-subnet-2') // submit the form - clickByRole('button', 'Create subnet') + await clickByRole('button', 'Create subnet') // wait for modal to close await waitForElementToBeRemoved(() => @@ -48,7 +50,7 @@ describe('VpcPage', () => { describe('firewall rule', () => { it('create works', async () => { renderAppAt('/orgs/maze-war/projects/mock-project/vpcs/mock-vpc') - clickByRole('tab', 'Firewall Rules') + await clickByRole('tab', 'Firewall Rules') // default rules show up in the table for (const { name } of defaultFirewallRules) { @@ -63,45 +65,45 @@ describe('VpcPage', () => { ).toBeNull() // click button to open modal - clickByRole('button', 'New rule') + await clickByRole('button', 'New rule') // modal is open - await screen.findByText('Create firewall rule') + screen.getByRole('dialog', { name: 'Create firewall rule' }) typeByRole('textbox', 'Name', 'my-new-rule') - clickByRole('radio', 'Outgoing') + await clickByRole('radio', 'Outgoing') // input type="number" becomes spinbutton for some reason typeByRole('spinbutton', 'Priority', '5') - clickByRole('button', 'Target type') - clickByRole('option', 'VPC') + await clickByRole('button', 'Target type') + await clickByRole('option', 'VPC') typeByRole('textbox', 'Target name', 'my-target-vpc') - clickByRole('button', 'Add target') + await clickByRole('button', 'Add target') // target is added to targets table screen.getByRole('cell', { name: 'my-target-vpc' }) - clickByRole('button', 'Host type') - clickByRole('option', 'Instance') - typeByRole('textbox', 'Value', 'my-target-instance') - clickByRole('button', 'Add host filter') + await clickByRole('button', 'Host type') + await clickByRole('option', 'Instance') + typeByRole('textbox', 'Value', 'host-filter-instance') + await clickByRole('button', 'Add host filter') // host is added to hosts table - screen.getByRole('cell', { name: 'my-target-instance' }) + screen.getByRole('cell', { name: 'host-filter-instance' }) // TODO: test invalid port range once I put an error message in there typeByRole('textbox', 'Port filter', '123-456') - clickByRole('button', 'Add port filter') + await clickByRole('button', 'Add port filter') // port range is added to port ranges table screen.getByRole('cell', { name: '123-456' }) - clickByRole('checkbox', 'UDP') + await clickByRole('checkbox', 'UDP') // submit the form - clickByRole('button', 'Create rule') + await clickByRole('button', 'Create rule') // wait for modal to close await waitForElementToBeRemoved( @@ -112,9 +114,99 @@ describe('VpcPage', () => { // table refetches and now includes the new rule as well as the originals await screen.findByText('my-new-rule') + screen.getByRole('cell', { + name: 'instance host-filter-instance UDP 123-456', + }) + for (const { name } of defaultFirewallRules) { screen.getByText(name) } }, 10000) + + it('edit works', async () => { + renderAppAt('/orgs/maze-war/projects/mock-project/vpcs/mock-vpc') + await clickByRole('tab', 'Firewall Rules') + + // default rules show up in the table + for (const { name } of defaultFirewallRules) { + await screen.findByText(name) + } + expect(screen.getAllByRole('row').length).toEqual(5) // 4 + header + + // the one we'll be adding is not there + expect(screen.queryByRole('cell', { name: 'new-rule-name' })).toBeNull() + + // modal is not already open + expect( + screen.queryByRole('dialog', { name: 'Edit firewall rule' }) + ).toBeNull() + + // click more button on allow-icmp row to get menu, then click Edit + const allowIcmpRow = screen.getByRole('row', { name: /allow-icmp/ }) + const more = getByRole(allowIcmpRow, 'button', { name: 'More' }) + await userEvent.click(more) + await clickByRole('menuitem', 'Edit') + + // now the modal is open + screen.getByRole('dialog', { name: 'Edit firewall rule' }) + + // name is populated + const name = screen.getByRole('textbox', { + name: 'Name', + }) as HTMLInputElement + expect(name.value).toEqual('allow-icmp') + + // priority is populated + const priority = screen.getByRole('spinbutton', { + name: 'Priority', + }) as HTMLInputElement + expect(priority.value).toEqual('65534') + + // protocol is populated + expect(screen.getByRole('checkbox', { name: /ICMP/ })).toBeChecked() + expect(screen.getByRole('checkbox', { name: /TCP/ })).not.toBeChecked() + expect(screen.getByRole('checkbox', { name: /UDP/ })).not.toBeChecked() + + // targets default vpc + screen.getByRole('cell', { name: 'vpc' }) + screen.getByRole('cell', { name: 'default' }) + + // update name + await userEvent.clear(name) + await userEvent.type(name, 'new-rule-name') + + await clickByRole('button', 'Host type') + await clickByRole('option', 'Instance') + typeByRole('textbox', 'Value', 'edit-filter-instance') + await clickByRole('button', 'Add host filter') + + // host is added to hosts table + screen.getByRole('cell', { name: 'edit-filter-instance' }) + + // submit the form + await clickByRole('button', 'Update rule') + + // wait for modal to close + await waitForElementToBeRemoved( + () => screen.queryByText('Edit firewall rule'), + // fails in CI without a longer timeout (default 1000). boo + { timeout: 2000 } + ) + + // table refetches and now includes the updated rule name, not the old name + await screen.findByText('new-rule-name') + expect(screen.queryByRole('cell', { name: 'allow-icmp' })).toBeNull() + expect(screen.getAllByRole('row').length).toEqual(5) // 4 + header + + screen.getByRole('cell', { + name: 'instance edit-filter-instance ICMP', + }) + + // other 3 rules are still there + const rest = defaultFirewallRules.filter((r) => r.name !== 'allow-icmp') + for (const { name } of rest) { + screen.getByRole('cell', { name }) + } + }, 20000) }) }) diff --git a/app/pages/project/networking/VpcPage/modals/firewall-rules.tsx b/app/pages/project/networking/VpcPage/modals/firewall-rules.tsx index 9325373d4b..a311ad5bac 100644 --- a/app/pages/project/networking/VpcPage/modals/firewall-rules.tsx +++ b/app/pages/project/networking/VpcPage/modals/firewall-rules.tsx @@ -498,6 +498,8 @@ type EditProps = { existingRules: VpcFirewallRule[] } +// TODO: this whole thing. shouldn't take much to fill in the initialValues +// based on the rule being edited export function EditFirewallRuleModal({ onDismiss, orgName, @@ -527,7 +529,7 @@ export function EditFirewallRuleModal({ return (