2026-02-22 19:19:26 +05:30
|
|
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
|
|
|
|
|
|
import { gatewaysStatusApiV1GatewaysStatusGet } from "@/api/generated/gateways/gateways";
|
|
|
|
|
|
|
|
|
|
import { checkGatewayConnection, validateGatewayUrl } from "./gateway-form";
|
|
|
|
|
|
|
|
|
|
vi.mock("@/api/generated/gateways/gateways", () => ({
|
|
|
|
|
gatewaysStatusApiV1GatewaysStatusGet: vi.fn(),
|
|
|
|
|
}));
|
|
|
|
|
|
|
|
|
|
const mockedGatewaysStatusApiV1GatewaysStatusGet = vi.mocked(
|
|
|
|
|
gatewaysStatusApiV1GatewaysStatusGet,
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
describe("validateGatewayUrl", () => {
|
2026-02-21 16:25:12 +00:00
|
|
|
it("accepts ws:// with explicit non-default port", () => {
|
|
|
|
|
expect(validateGatewayUrl("ws://localhost:18789")).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("accepts wss:// with explicit non-default port", () => {
|
|
|
|
|
expect(validateGatewayUrl("wss://gateway.example.com:8443")).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("accepts wss:// with explicit default port 443", () => {
|
|
|
|
|
expect(validateGatewayUrl("wss://devbot.tailcc2080.ts.net:443")).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("accepts ws:// with explicit default port 80", () => {
|
|
|
|
|
expect(validateGatewayUrl("ws://localhost:80")).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("accepts URLs with a path after the port", () => {
|
|
|
|
|
expect(validateGatewayUrl("wss://host.example.com:443/gateway")).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("trims surrounding whitespace before validating", () => {
|
|
|
|
|
expect(validateGatewayUrl(" wss://host:443 ")).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-23 02:00:07 +05:30
|
|
|
it("accepts IPv6 URLs with explicit non-default port", () => {
|
|
|
|
|
expect(validateGatewayUrl("wss://[::1]:8080")).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("accepts IPv6 URLs with explicit default port", () => {
|
|
|
|
|
expect(validateGatewayUrl("wss://[2001:db8::1]:443")).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("accepts userinfo URLs with explicit port", () => {
|
|
|
|
|
expect(validateGatewayUrl("ws://user:pass@gateway.example.com:8080")).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("accepts userinfo URLs with IPv6 host and explicit port", () => {
|
|
|
|
|
expect(validateGatewayUrl("wss://user@[::1]:443")).toBeNull();
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-21 16:25:12 +00:00
|
|
|
it("rejects empty string", () => {
|
|
|
|
|
expect(validateGatewayUrl("")).toBe("Gateway URL is required.");
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("rejects wss:// with no port at all", () => {
|
|
|
|
|
expect(validateGatewayUrl("wss://gateway.example.com")).toBe(
|
|
|
|
|
"Gateway URL must include an explicit port.",
|
2026-02-22 19:19:26 +05:30
|
|
|
);
|
2026-02-21 16:25:12 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("rejects ws:// with no port at all", () => {
|
|
|
|
|
expect(validateGatewayUrl("ws://localhost")).toBe(
|
2026-02-22 19:19:26 +05:30
|
|
|
"Gateway URL must include an explicit port.",
|
|
|
|
|
);
|
2026-02-21 16:25:12 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("rejects https:// scheme", () => {
|
|
|
|
|
expect(validateGatewayUrl("https://gateway.example.com:443")).toBe(
|
|
|
|
|
"Gateway URL must start with ws:// or wss://.",
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("rejects http:// scheme", () => {
|
|
|
|
|
expect(validateGatewayUrl("http://localhost:8080")).toBe(
|
|
|
|
|
"Gateway URL must start with ws:// or wss://.",
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("rejects completely invalid URL", () => {
|
|
|
|
|
expect(validateGatewayUrl("not-a-url")).toBe(
|
|
|
|
|
"Enter a valid gateway URL including port.",
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-23 02:00:07 +05:30
|
|
|
it("rejects out-of-range ports", () => {
|
|
|
|
|
expect(validateGatewayUrl("wss://gateway.example.com:65536")).toBe(
|
|
|
|
|
"Enter a valid gateway URL including port.",
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("rejects userinfo URLs with no explicit port", () => {
|
|
|
|
|
expect(validateGatewayUrl("ws://user:pass@gateway.example.com")).toBe(
|
|
|
|
|
"Gateway URL must include an explicit port.",
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-21 16:25:12 +00:00
|
|
|
it("rejects URL with only whitespace", () => {
|
|
|
|
|
expect(validateGatewayUrl(" ")).toBe("Gateway URL is required.");
|
2026-02-22 19:19:26 +05:30
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe("checkGatewayConnection", () => {
|
|
|
|
|
beforeEach(() => {
|
|
|
|
|
mockedGatewaysStatusApiV1GatewaysStatusGet.mockReset();
|
|
|
|
|
});
|
|
|
|
|
|
2026-02-22 20:20:19 +05:30
|
|
|
it("passes pairing and TLS toggles to gateway status API", async () => {
|
2026-02-22 19:19:26 +05:30
|
|
|
mockedGatewaysStatusApiV1GatewaysStatusGet.mockResolvedValue({
|
|
|
|
|
status: 200,
|
|
|
|
|
data: { connected: true },
|
|
|
|
|
} as never);
|
|
|
|
|
|
|
|
|
|
const result = await checkGatewayConnection({
|
|
|
|
|
gatewayUrl: "ws://gateway.example:18789",
|
|
|
|
|
gatewayToken: "secret-token",
|
|
|
|
|
gatewayDisableDevicePairing: true,
|
2026-02-22 20:20:19 +05:30
|
|
|
gatewayAllowInsecureTls: true,
|
2026-02-22 19:19:26 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(mockedGatewaysStatusApiV1GatewaysStatusGet).toHaveBeenCalledWith({
|
|
|
|
|
gateway_url: "ws://gateway.example:18789",
|
|
|
|
|
gateway_token: "secret-token",
|
|
|
|
|
gateway_disable_device_pairing: true,
|
2026-02-22 20:20:19 +05:30
|
|
|
gateway_allow_insecure_tls: true,
|
2026-02-22 19:19:26 +05:30
|
|
|
});
|
|
|
|
|
expect(result).toEqual({ ok: true, message: "Gateway reachable." });
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it("returns gateway-provided error message when offline", async () => {
|
|
|
|
|
mockedGatewaysStatusApiV1GatewaysStatusGet.mockResolvedValue({
|
|
|
|
|
status: 200,
|
|
|
|
|
data: {
|
|
|
|
|
connected: false,
|
|
|
|
|
error: "missing required scope",
|
|
|
|
|
},
|
|
|
|
|
} as never);
|
|
|
|
|
|
|
|
|
|
const result = await checkGatewayConnection({
|
|
|
|
|
gatewayUrl: "ws://gateway.example:18789",
|
|
|
|
|
gatewayToken: "",
|
|
|
|
|
gatewayDisableDevicePairing: false,
|
2026-02-22 20:20:19 +05:30
|
|
|
gatewayAllowInsecureTls: false,
|
2026-02-22 19:19:26 +05:30
|
|
|
});
|
|
|
|
|
|
|
|
|
|
expect(result).toEqual({ ok: false, message: "missing required scope" });
|
|
|
|
|
});
|
|
|
|
|
});
|