Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 105 additions & 3 deletions e2e/tests/reverse-proxy-services-https.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { test, expect } from "../helpers/fixtures";
import { navigateTo } from "../helpers/auth";
import { generateRandomName } from "../helpers/utils";
import { deleteNetworksByPrefix, deleteServicesByPrefix } from "../helpers/api";
import { gotoReverseProxyPage, selectProxyDomain, CUSTOM_PORTS_DOMAIN } from "../helpers/reverse-proxy-l4";
import { gotoReverseProxyPage, selectL4Resource, selectProxyDomain, CUSTOM_PORTS_DOMAIN } from "../helpers/reverse-proxy-l4";

let createdNetwork = "";
let createdResource = "";
Expand Down Expand Up @@ -70,7 +70,7 @@ test.describe.serial("Reverse Proxy - Services (HTTPS) @reverse-proxy", () => {
port: 4433,
});

const targetsSection = page.getByText("HTTPS Targets").locator("..");
const targetsSection = page.getByTestId("https-targets");
await expect(targetsSection.locator("table tbody tr")).toHaveCount(2);

await page.getByTestId("proxy-continue").click();
Expand Down Expand Up @@ -124,6 +124,108 @@ test.describe.serial("Reverse Proxy - Services (HTTPS) @reverse-proxy", () => {
await expect(page.locator("tr").filter({ hasText: subdomain })).toBeVisible({ timeout: 30_000 });
});

test("Should reuse the HTTPS hostname for a non-conflicting TCP service", async ({
dashboardAsOwner: page,
}) => {
test.setTimeout(60_000);
await gotoReverseProxyPage(page, "/reverse-proxy/services");

await page.getByTestId("add-service").first().click();
await page.getByTestId("proxy-subdomain-input").fill(createdSubdomain);
await selectProxyDomain(page, CUSTOM_PORTS_DOMAIN);
await expect(page.getByText("This domain is already used by another service.")).toBeVisible();

await page.getByTestId("service-mode-select-button").click({ force: true });
await page.getByTestId("service-mode-option-tls").click({ force: true });
await expect(page.getByText("This domain is already used by another service.")).toBeVisible();

await page.getByTestId("service-mode-select-button").click({ force: true });
await page.getByTestId("service-mode-option-tcp").click({ force: true });
await expect(page.getByText("This domain is already used by another service.")).not.toBeVisible();

await selectL4Resource(page, createdResource);
await page.getByTestId("listen-port-input").fill("1773");
await page.getByTestId("destination-port-input").fill("1773");
await page.getByTestId("add-port-mapping").click();
const secondMapping = page.getByTestId("port-mapping-1");
await secondMapping.getByRole("combobox").click({ force: true });
await page.getByRole("option", { name: "TLS", exact: true }).click();
await expect(page.getByText("This domain is already used by another service.")).toBeVisible();
await secondMapping.getByRole("combobox").click({ force: true });
await page.getByRole("option", { name: "TCP", exact: true }).click();
await expect(page.getByText("This domain is already used by another service.")).not.toBeVisible();

await page.getByTestId("listen-port-start-1").fill("1984");
await page.getByTestId("listen-port-end-1").fill("1986");
await page.getByTestId("destination-port-start-1").fill("1984");
await page.getByTestId("destination-port-end-1").fill("1986");

const originalViewport = page.viewportSize() ?? { width: 1280, height: 720 };
try {
for (const width of [375, 768]) {
await page.setViewportSize({ width, height: 900 });
const horizontalOverflows = await page
.locator('[data-testid^="port-mapping-"]')
.evaluateAll((cards) =>
cards.map((card) => {
const bounds = card.getBoundingClientRect();
return {
internal: card.scrollWidth - card.clientWidth,
left: bounds.left,
right: bounds.right,
viewport: window.innerWidth,
};
}),
);
expect(
horizontalOverflows.every(
({ internal, left, right, viewport }) =>
internal <= 1 && left >= -1 && right <= viewport + 1,
),
).toBe(true);

const dialogBounds = await page.getByRole("dialog").boundingBox();
expect(dialogBounds).not.toBeNull();
expect(dialogBounds!.x).toBeGreaterThanOrEqual(-1);
expect(dialogBounds!.x + dialogBounds!.width).toBeLessThanOrEqual(
width + 1,
);
}
} finally {
await page.setViewportSize(originalViewport);
}

await page.getByTestId("proxy-continue").click();
await page.getByTestId("proxy-continue").click();

const createResponse = page.waitForResponse(
(response) =>
response.url().includes("/api/reverse-proxies/services") &&
response.request().method() === "POST",
{ timeout: 15_000 },
);
await page.getByTestId("submit-service").click();
await page.getByTestId("confirmation.confirm").click({ force: true });
expect([200, 201]).toContain((await createResponse).status());

await resetServiceFilters(page);
const sharedDomainRows = page.locator("tr").filter({ hasText: createdSubdomain });
await expect(sharedDomainRows).toHaveCount(2);
const tcpRow = sharedDomainRows.filter({ has: page.getByText("TCP", { exact: true }) });
await expect(tcpRow).toHaveCount(1);
await tcpRow.getByTestId("service-actions").click({ force: true });
await page.getByTestId("delete-service").click({ force: true });
await page.getByTestId("confirmation.confirm").click({ force: true });
await expect(sharedDomainRows).toHaveCount(1);

const httpsRow = sharedDomainRows;
await httpsRow.getByTestId("service-actions").click({ force: true });
await page.getByTestId("edit-service").click({ force: true });
const httpsTargets = page.getByTestId("https-targets");
await expect(httpsTargets.locator("table tbody tr")).toHaveCount(2);
await page.getByTestId("modal-close").click();
});

test("Should edit the service, remove auth and rules, then delete", async ({
dashboardAsOwner: page,
}) => {
Expand All @@ -132,7 +234,7 @@ test.describe.serial("Reverse Proxy - Services (HTTPS) @reverse-proxy", () => {
await page.getByTestId("edit-service").click({ force: true });

// Edit first target
const targetsSection = page.getByText("HTTPS Targets").locator("..");
const targetsSection = page.getByTestId("https-targets");
await targetsSection.locator("table tbody tr").first().click({ force: true });
await page.getByTestId("target-location-input").fill("/new-location");
await page.getByTestId("submit-target").click();
Expand Down
62 changes: 51 additions & 11 deletions e2e/tests/reverse-proxy-services-tcp.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ let tcpResource = "";
let tcpSubdomain = "";

test.describe.serial("Reverse Proxy - Services (TCP) @reverse-proxy", () => {
test("Should create a network with a resource", async ({ dashboardAsOwner: page }) => {
test("Should create a network with a resource", async ({
dashboardAsOwner: page,
}) => {
await deleteServicesByPrefix(page, "tcp-svc-");
await deleteNetworksByPrefix(page, "rp-tcp-net-");
await navigateTo(page, "/networks");
Expand Down Expand Up @@ -54,39 +56,78 @@ test.describe.serial("Reverse Proxy - Services (TCP) @reverse-proxy", () => {
}
});

test("Should create a TCP service", async ({ dashboardAsOwner: page }) => {
test("Should create a mixed multi-port service", async ({
dashboardAsOwner: page,
}) => {
await gotoReverseProxyPage(page, "/reverse-proxy/services");
const subdomain = generateRandomName("tcp-svc-");
tcpSubdomain = subdomain;

await page.getByTestId("add-service").first().click();
await expect(page.getByTestId("proxy-subdomain-input")).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("proxy-subdomain-input")).toBeVisible({
timeout: 10_000,
});
await page.getByTestId("proxy-subdomain-input").fill(subdomain);
await selectProxyDomain(page, CUSTOM_PORTS_DOMAIN);
await page.getByTestId("service-mode-select-button").click({ force: true });
await page.getByTestId("service-mode-option-tcp").click({ force: true });
await expect(page.getByTestId("group-selector-dropdown")).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("group-selector-dropdown")).toBeVisible({
timeout: 10_000,
});

await selectL4Resource(page, tcpResource);
await expect(page.getByTestId("listen-port-input")).toBeEnabled({ timeout: 10_000 });
await expect(page.getByTestId("listen-port-input")).toBeEnabled({
timeout: 10_000,
});
await page.getByTestId("listen-port-input").fill("3306");
await page.getByTestId("destination-port-input").fill("3306");

await page.getByTestId("add-port-mapping").click();
await page.getByTestId("listen-port-start-1").fill("3307");
await page.getByTestId("destination-port-start-1").fill("3307");
await page.getByTestId("destination-port-end-1").fill("3308");
await expect(page.getByText(/same number of ports/i)).toBeVisible();
await page.getByTestId("destination-port-end-1").fill("3307");

await page.getByTestId("add-port-mapping").click();
const udpMapping = page.getByTestId("port-mapping-2");
await udpMapping.getByRole("combobox").click({ force: true });
await page.getByRole("option", { name: "UDP", exact: true }).click();
await page.getByTestId("listen-port-start-2").fill("3308");
await page.getByTestId("destination-port-start-2").fill("3308");
await page.getByRole("button", { name: "Move mapping 3 up" }).click();

await page.getByTestId("proxy-continue").click();

await addAccessControlRules(page);
await page.getByTestId("proxy-continue").click();

await page.getByTestId("connection-timeout-input").fill("20s");
await page.getByTestId("udp-session-timeout-input").fill("30s");
await page.getByTestId("toggle-preserve-client-ip").click();
await page.getByTestId("submit-service").click();

await resetServiceFilters(page);
await expect(page.locator("tr").filter({ hasText: subdomain }).getByText("TCP", { exact: true })).toBeVisible({ timeout: 30_000 });
await expect(
page
.locator("tr")
.filter({ hasText: subdomain })
.getByText("TCP", { exact: true }),
).toBeVisible({ timeout: 30_000 });
});

test("Should edit the TCP service and delete it", async ({ dashboardAsOwner: page }) => {
test("Should edit the TCP service and delete it", async ({
dashboardAsOwner: page,
}) => {
await openServiceEdit(page, tcpSubdomain);

await expect(page.locator('[data-testid^="port-mapping-"]')).toHaveCount(3);

await page.getByRole("button", { name: "Move mapping 2 up" }).click();
await page.getByRole("button", { name: "Move mapping 1 down" }).click();
await page.getByRole("button", { name: "Remove mapping 3" }).click();
await expect(page.locator('[data-testid^="port-mapping-"]')).toHaveCount(2);

await page.getByTestId("listen-port-input").fill("5432");
await page.getByTestId("destination-port-input").fill("5432");

Expand All @@ -101,10 +142,9 @@ test.describe.serial("Reverse Proxy - Services (TCP) @reverse-proxy", () => {

await resetServiceFilters(page);
const row = page.locator("tr").filter({ hasText: tcpSubdomain });
await expect(row.locator("[data-access-control-cell]")).toContainText(
"0",
{ timeout: 10_000 },
);
await expect(row.locator("[data-access-control-cell]")).toContainText("0", {
timeout: 10_000,
});

await deleteService(page, tcpSubdomain);
});
Expand Down
47 changes: 36 additions & 11 deletions e2e/tests/reverse-proxy-services-udp-no-custom-ports.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,11 @@ let udpNetwork = "";
let udpResource = "";
let udpSubdomain = "";

test.describe.serial("Reverse Proxy - Services (UDP, no custom ports) @reverse-proxy", () => {
test("Should create a network with a resource", async ({ dashboardAsOwner: page }) => {
test.describe
.serial("Reverse Proxy - Services (UDP, no custom ports) @reverse-proxy", () => {
test("Should create a network with a resource", async ({
dashboardAsOwner: page,
}) => {
await deleteServicesByPrefix(page, "udp-np-svc-");
await deleteNetworksByPrefix(page, "rp-udp-np-net-");
await navigateTo(page, "/networks");
Expand Down Expand Up @@ -54,43 +57,65 @@ test.describe.serial("Reverse Proxy - Services (UDP, no custom ports) @reverse-p
}
});

test("Should create a UDP service on the no-custom-ports cluster", async ({ dashboardAsOwner: page }) => {
test("Should create a UDP service on the no-custom-ports cluster", async ({
dashboardAsOwner: page,
}) => {
await gotoReverseProxyPage(page, "/reverse-proxy/services");
const subdomain = generateRandomName("udp-np-svc-");
udpSubdomain = subdomain;

await page.getByTestId("add-service").first().click();
await expect(page.getByTestId("proxy-subdomain-input")).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("proxy-subdomain-input")).toBeVisible({
timeout: 10_000,
});
await page.getByTestId("proxy-subdomain-input").fill(subdomain);

await selectProxyDomain(page, NO_CUSTOM_PORTS_DOMAIN);

await page.getByTestId("service-mode-select-button").click({ force: true });
await page.getByTestId("service-mode-option-udp").click({ force: true });
await expect(page.getByTestId("group-selector-dropdown")).toBeVisible({ timeout: 10_000 });
await expect(page.getByTestId("group-selector-dropdown")).toBeVisible({
timeout: 10_000,
});

await selectL4Resource(page, udpResource);

// Listen port is auto-assigned when the cluster has custom ports disabled
await expect(page.getByTestId("listen-port-input")).toBeDisabled({ timeout: 10_000 });
await expect(page.getByTestId("listen-port-input")).toHaveAttribute("placeholder", "Auto");
await expect(page.getByTestId("listen-port-input")).toBeDisabled({
timeout: 10_000,
});
await expect(page.getByTestId("listen-port-input")).toHaveAttribute(
"placeholder",
"Auto",
);

await page.getByTestId("destination-port-input").fill("5060");
await page.getByTestId("destination-port-end-0").fill("5061");
await expect(
page.getByText(
"An auto-assigned listener supports one destination port, not a range.",
),
).toBeVisible();
await page.getByTestId("destination-port-end-0").fill("5060");
await page.getByTestId("proxy-continue").click();

await addAccessControlRules(page);
await page.getByTestId("proxy-continue").click();

await page.getByTestId("connection-timeout-input").fill("30s");
await page.getByTestId("udp-session-timeout-input").fill("30s");
await page.getByTestId("submit-service").click();

await resetServiceFilters(page);
const row = page.locator("tr").filter({ hasText: subdomain });
await expect(row.getByText("UDP", { exact: true })).toBeVisible({ timeout: 30_000 });
await expect(row.getByText("UDP", { exact: true })).toBeVisible({
timeout: 30_000,
});
await expect(row).toContainText(NO_CUSTOM_PORTS_DOMAIN);
});

test("Should edit the UDP service and delete it", async ({ dashboardAsOwner: page }) => {
test("Should edit the UDP service and delete it", async ({
dashboardAsOwner: page,
}) => {
await openServiceEdit(page, udpSubdomain);

// Listen port must remain auto-assigned on this cluster
Expand All @@ -102,7 +127,7 @@ test.describe.serial("Reverse Proxy - Services (UDP, no custom ports) @reverse-p
await removeAllAccessControlRules(page);

await page.getByTestId("proxy-tab-settings").click({ force: true });
await page.getByTestId("connection-timeout-input").fill("");
await page.getByTestId("udp-session-timeout-input").fill("");

await saveServiceEdit(page);

Expand Down
Loading