Skip to content

Commit

Permalink
fix: static routes edit and delete lp#2060176 lp#2060177 MAASENG-2966 (
Browse files Browse the repository at this point in the history
…#5385)

- add e2e test for static routes
  • Loading branch information
petermakowski committed Apr 4, 2024
1 parent 7deb99c commit e4c49ba
Show file tree
Hide file tree
Showing 13 changed files with 162 additions and 33 deletions.
17 changes: 1 addition & 16 deletions cypress/e2e/with-users/subnets/add.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,21 +30,6 @@ context("Subnets - Add", () => {
cy.findByRole("button", { name: "Add VLAN" }).click();
};

const completeAddSubnetForm = (
subnetName: string,
cidr: string,
fabric: string,
vid: string,
vlan: string
) => {
openAddForm("Subnet");
cy.findByRole("textbox", { name: "CIDR" }).type(cidr);
cy.findByRole("textbox", { name: "Name" }).type(subnetName);
cy.findByRole("combobox", { name: "Fabric" }).select(fabric);
cy.findByRole("combobox", { name: "VLAN" }).select(`${vid} (${vlan})`);
cy.findByRole("button", { name: "Add Subnet" }).click();
};

const completeForm = (formName: string, name: string) => {
openAddForm(formName);
cy.findByRole("textbox", { name: "Name (optional)" }).type(name);
Expand Down Expand Up @@ -115,7 +100,7 @@ context("Subnets - Add", () => {
completeForm("Fabric", fabricName);
completeForm("Space", spaceName);
completeAddVlanForm(vid, vlanName, fabricName, spaceName);
completeAddSubnetForm(subnetName, cidr, fabricName, vid, vlanName);
cy.addSubnet(subnetName, cidr, fabricName, vid, vlanName);

cy.findAllByRole("link", { name: fabricName }).should("have.length", 2);

Expand Down
80 changes: 80 additions & 0 deletions cypress/e2e/with-users/subnets/staticroutes.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { generateMAASURL } from "../../utils";

context("Static Routes", () => {
beforeEach(() => {
cy.login();
cy.visit(generateMAASURL("/networks?by=fabric"));
cy.waitForPageToLoad();
cy.viewport("macbook-11");
});
it("allows adding, editing, and deleting a static route", () => {
// Add static route
cy.findByRole("grid", { name: "Subnets by Fabric" }).within(() => {
cy.get("tbody").find('td[aria-label="subnet"]').find("a").first().click();
});
cy.findByRole("heading", { level: 1 }).invoke("text").as("subnet");

cy.findByRole("button", { name: /add static route/i }).click();
cy.get("@subnet").then((subnet: unknown) => {
cy.findByRole("complementary", { name: /Add static route/i }).within(
() => {
const staticRoute = (subnet as string).split("/")[0];
cy.wrap(staticRoute).as("staticRoute");
cy.findByLabelText(/gateway ip/i).type(staticRoute);
cy.findByLabelText(/destination/i).select(1);
}
);
});
cy.findByRole("button", { name: /save/i }).click();
cy.findByRole("complementary", { name: /Add static route/i }).should(
"not.exist"
);

// Edit static route
cy.findByRole("region", { name: /Static routes/i }).within(() => {
cy.get("tbody tr")
.first()
.findByRole("button", { name: /edit/i })
.click();
});

cy.findByRole("complementary", { name: /Edit static route/i }).within(
() => {
cy.findByLabelText(/gateway ip/i).type("{Backspace}1");
cy.findByRole("button", { name: /save/i }).click();
}
);

// Verify the change has been saved and side panel closed
cy.get("@staticRoute").then((staticRoute: unknown) => {
cy.findByRole("region", { name: /Static routes/i }).within(() => {
cy.findByText(staticRoute as string);
});
});
cy.findByRole("complementary", { name: /Add static route/i }).should(
"not.exist"
);

// Delete the static route
cy.findByRole("region", { name: /Static routes/i }).within(() => {
cy.get("tbody tr")
.first()
.findByRole("gridcell", { name: /actions/i })
.findByRole("button", { name: /delete/i })
.click();
});

// Verify it's been deleted and side panel closed
cy.findByRole("complementary", { name: /Delete static route/i }).within(
() => {
cy.findByRole("button", { name: /delete/i }).click();
}
);
cy.get("@staticRoute").then((staticRoute: unknown) => {
cy.findByRole("region", { name: /Static routes/i }).within(() => {
cy.findByText(staticRoute as string).should("not.exist");
});
});
cy.findByRole("region", { name: /side panel/i }).should("not.exist");
});
});
28 changes: 27 additions & 1 deletion cypress/support/commands.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import "@testing-library/cypress/add-commands";
import type { Result } from "axe-core";
import { LONG_TIMEOUT } from "../constants";
import { generateMAASURL, generateMac, generateName } from "../e2e/utils";
import {
generateId,
generateMAASURL,
generateMac,
generateName,
generateVid,
} from "../e2e/utils";
import type { A11yPageContext } from "./e2e";

Cypress.Commands.add("login", (options) => {
Expand Down Expand Up @@ -101,6 +107,26 @@ Cypress.Commands.add("addMachines", (hostnames: string[]) => {
});
});

Cypress.Commands.add(
"addSubnet",
({
subnetName = `cy-subnet-${generateId()}`,
cidr = "192.168.122.18",
fabric = `cy-fabric-${generateId()}`,
vid = generateVid(),
vlan = `cy-vlan-${vid}`,
}) => {
cy.visit(generateMAASURL("/networks?by=fabric"));
cy.findByRole("button", { name: "Add" }).click();
cy.findByRole("button", { name: "Subnet" }).click();
cy.findByRole("textbox", { name: "CIDR" }).type(cidr);
cy.findByRole("textbox", { name: "Name" }).type(subnetName);
cy.findByRole("combobox", { name: "Fabric" }).select(fabric);
cy.findByRole("combobox", { name: "VLAN" }).select(`${vid} (${vlan})`);
cy.findByRole("button", { name: "Add Subnet" }).click();
}
);

function logViolations(violations: Result[], pageContext: A11yPageContext) {
const divider =
"\n====================================================================================================\n";
Expand Down
8 changes: 8 additions & 0 deletions cypress/support/e2e.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ import "@percy/cypress";
import "./commands";

export type A11yPageContext = { url?: string; title?: string };
export type SubnetOptions = {
subnetName?: string;
cidr?: string;
fabric?: string;
vid?: string;
vlan?: string;
};
declare global {
namespace Cypress {
interface Chainable {
Expand All @@ -27,6 +34,7 @@ declare global {
}): Cypress.Chainable<JQuery<HTMLElement>>;
getMainNavigation(): Cypress.Chainable<JQuery<HTMLElement>>;
expandMainNavigation(): void;
addSubnet(options: SubnetOptions): void;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const mockStore = configureStore<RootState>();

const subnet = factory.subnet({ id: 1, cidr: "172.16.1.0/24" });
const destinationSubnet = factory.subnet({ id: 2, cidr: "223.16.1.0/24" });
const staticroute = factory.staticRoute({ id: 1, destination: subnet.id });
state = factory.rootState({
user: factory.userState({
auth: factory.authState({
Expand All @@ -21,7 +22,7 @@ state = factory.rootState({
}),
staticroute: factory.staticRouteState({
loaded: true,
items: [],
items: [staticroute],
}),
subnet: factory.subnetState({
loaded: true,
Expand All @@ -31,7 +32,10 @@ state = factory.rootState({

it("renders", () => {
renderWithBrowserRouter(
<DeleteStaticRouteForm id={subnet.id} setActiveForm={vi.fn()} />,
<DeleteStaticRouteForm
setActiveForm={vi.fn()}
staticRouteId={staticroute.id}
/>,
{ state }
);

Expand All @@ -41,7 +45,10 @@ it("renders", () => {
it("dispatches the correct action to delete a static route", async () => {
const store = mockStore(state);
renderWithBrowserRouter(
<DeleteStaticRouteForm id={subnet.id} setActiveForm={vi.fn()} />,
<DeleteStaticRouteForm
setActiveForm={vi.fn()}
staticRouteId={staticroute.id}
/>,
{ store }
);

Expand All @@ -51,5 +58,5 @@ it("dispatches the correct action to delete a static route", async () => {
.getActions()
.find((action) => action.type === staticRouteActions.delete.type);

expect(action).toStrictEqual(staticRouteActions.delete(subnet.id));
expect(action).toStrictEqual(staticRouteActions.delete(staticroute.id));
});
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,35 @@ import ModelActionForm from "@/app/base/components/ModelActionForm";
import type { SetSidePanelContent } from "@/app/base/side-panel-context";
import { staticRouteActions } from "@/app/store/staticroute";
import staticRouteSelectors from "@/app/store/staticroute/selectors";
import type { Subnet, SubnetMeta } from "@/app/store/subnet/types";
import type {
StaticRoute,
StaticRouteMeta,
} from "@/app/store/staticroute/types";

type Props = {
id: Subnet[SubnetMeta.PK];
staticRouteId?: StaticRoute[StaticRouteMeta.PK];
setActiveForm: SetSidePanelContent;
};

const DeleteStaticRouteForm = ({ id, setActiveForm }: Props) => {
const DeleteStaticRouteForm = ({ staticRouteId, setActiveForm }: Props) => {
const dispatch = useDispatch();
const saved = useSelector(staticRouteSelectors.saved);
const saving = useSelector(staticRouteSelectors.saving);

if (!staticRouteId) {
return null;
}
return (
<ModelActionForm
aria-label="Confirm static route deletion"
initialValues={{}}
modelType="static route"
onCancel={() => setActiveForm(null)}
onSubmit={() => {
dispatch(staticRouteActions.delete(id));
dispatch(staticRouteActions.delete(staticRouteId));
}}
onSuccess={() => {
setActiveForm(null);
}}
saved={saved}
saving={saving}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ it("displays loading text on load", async () => {
render(
<Provider store={store}>
<MemoryRouter initialEntries={[{ pathname: "/" }]}>
<EditStaticRouteForm id={1} setActiveForm={vi.fn()} />
<EditStaticRouteForm setActiveForm={vi.fn()} staticRouteId={1} />
</MemoryRouter>
</Provider>
);
Expand Down Expand Up @@ -69,7 +69,10 @@ it("dispatches a correct action on edit static route form submit", async () => {
render(
<Provider store={store}>
<MemoryRouter initialEntries={[{ pathname: "/" }]}>
<EditStaticRouteForm id={staticRoute.id} setActiveForm={vi.fn()} />
<EditStaticRouteForm
setActiveForm={vi.fn()}
staticRouteId={staticRoute.id}
/>
</MemoryRouter>
</Provider>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ const editStaticRouteSchema = Yup.object().shape({
});

export type Props = {
id: StaticRoute[StaticRouteMeta.PK];
staticRouteId?: StaticRoute[StaticRouteMeta.PK];
setActiveForm: SetSidePanelContent;
};
const EditStaticRouteForm = ({
id,
staticRouteId,
setActiveForm,
}: Props): JSX.Element | null => {
const staticRouteErrors = useSelector(staticRouteSelectors.errors);
Expand All @@ -55,15 +55,15 @@ const EditStaticRouteForm = ({
const subnetsLoading = useSelector(subnetSelectors.loading);
const loading = staticRoutesLoading || subnetsLoading;
const staticRoute = useSelector((state: RootState) =>
staticRouteSelectors.getById(state, id)
staticRouteSelectors.getById(state, staticRouteId)
);
const source = useSelector((state: RootState) =>
subnetSelectors.getById(state, staticRoute?.source)
);

useFetchActions([staticRouteActions.fetch, subnetActions.fetch]);

if (!staticRoute || loading) {
if (!staticRouteId || !staticRoute || loading) {
return (
<Spinner data-testid="edit-static-route-form-loading" text="Loading..." />
);
Expand All @@ -90,7 +90,7 @@ const EditStaticRouteForm = ({
dispatch(staticRouteActions.cleanup());
dispatch(
staticRouteActions.update({
id: id,
id: staticRouteId,
source: staticRoute.source,
gateway_ip,
destination: toFormikNumber(destination) as number,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,13 +67,15 @@ const generateRows = (
view: SubnetDetailsSidePanelViews[
SubnetActionTypes.DeleteStaticRoute
],
extras: { staticRouteId: staticRoute.id },
});
}}
onEdit={() => {
setSidePanelContent({
view: SubnetDetailsSidePanelViews[
SubnetActionTypes.EditStaticRoute
],
extras: { staticRouteId: staticRoute.id },
});
}}
/>
Expand Down
1 change: 1 addition & 0 deletions src/app/subnets/views/SubnetDetails/SubnetDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const SubnetDetails = (): JSX.Element => {
activeForm={activeForm}
id={subnet.id}
setActiveForm={setSidePanelContent}
{...sidePanelContent?.extras}
/>
) : null
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const SubnetActionForms = ({
id,
activeForm,
setActiveForm,
staticRouteId,
}: SubnetActionProps): JSX.Element => {
const FormComponent = activeForm ? FormComponents[activeForm] : () => null;

Expand All @@ -40,6 +41,7 @@ const SubnetActionForms = ({
activeForm={activeForm}
id={id}
setActiveForm={setActiveForm}
staticRouteId={staticRouteId}
/>
);
};
Expand Down
2 changes: 1 addition & 1 deletion src/app/subnets/views/SubnetDetails/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ export const SubnetDetailsSidePanelViews = {

export type SubnetDetailsSidePanelContent = SidePanelContent<
ValueOf<typeof SubnetDetailsSidePanelViews>,
{ createType?: IPRangeType; ipRangeId?: number }
{ createType?: IPRangeType; ipRangeId?: number; staticRouteId?: number }
>;
5 changes: 5 additions & 0 deletions src/app/subnets/views/SubnetDetails/types.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type { SubnetActionTypes } from "./constants";

import type { SetSidePanelContent } from "@/app/base/side-panel-context";
import type {
StaticRoute,
StaticRouteMeta,
} from "@/app/store/staticroute/types";
import type { Subnet, SubnetMeta } from "@/app/store/subnet/types";

export type SubnetAction = keyof typeof SubnetActionTypes;

export interface SubnetActionProps {
id: Subnet[SubnetMeta.PK];
staticRouteId?: StaticRoute[StaticRouteMeta.PK];
activeForm: SubnetAction;
setActiveForm: SetSidePanelContent;
}

0 comments on commit e4c49ba

Please sign in to comment.