Skip to content

Commit

Permalink
Update url validation rules
Browse files Browse the repository at this point in the history
  • Loading branch information
OKendigelyan committed Oct 7, 2024
1 parent 3762a6e commit d22622e
Show file tree
Hide file tree
Showing 4 changed files with 322 additions and 32 deletions.
6 changes: 3 additions & 3 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,11 @@
"vite": "^5.4.8",
"vite-plugin-checker": "^0.8.0",
"vite-plugin-node-polyfills": "^0.17.0",
"zod": "^3.23.8"
"zod": "^3.23.8",
"@hookform/resolvers": "^3.9.0"
},
"packageManager": "[email protected]",
"dependencies": {
"electron-updater": "6.3.9",
"@hookform/resolvers": "^3.9.0"
"electron-updater": "6.3.9"
}
}
115 changes: 115 additions & 0 deletions packages/components/src/utils/validationSchemes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { type Network } from "@umami/tezos";

import { getNetworkValidationScheme } from "./validationSchemes";

describe("getNetworkValidationScheme", () => {
const mockAvailableNetworks: Network[] = [
{
name: "Existing Network",
rpcUrl: "https://example.com",
tzktApiUrl: "https://api.example.com",
tzktExplorerUrl: "https://explorer.example.com",
},
];

it("should validate a correct network input", () => {
const schema = getNetworkValidationScheme(mockAvailableNetworks);
const validInput = {
name: "New Network",
rpcUrl: "https://rpc.example.com",
tzktApiUrl: "https://api.tzkt.example.com",
tzktExplorerUrl: "https://explorer.tzkt.example.com",
buyTezUrl: "https://buy.example.com",
};

const result = schema.safeParse(validInput);
expect(result.success).toBe(true);
});

it("should reject a network with an existing name", () => {
const schema = getNetworkValidationScheme(mockAvailableNetworks);
const invalidInput = {
name: "Existing Network",
rpcUrl: "https://rpc.example.com",
tzktApiUrl: "https://api.tzkt.example.com",
tzktExplorerUrl: "https://explorer.tzkt.example.com",
};

const result = schema.safeParse(invalidInput);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues[0].message).toBe("Network with this name already exists");
}
});

it("should reject invalid URLs", () => {
const schema = getNetworkValidationScheme(mockAvailableNetworks);
const invalidInput = {
name: "Invalid URL Network",
rpcUrl: "http://insecure.com",
tzktApiUrl: "not-a-url",
tzktExplorerUrl: "ftp://invalid-protocol.com",
};

const result = schema.safeParse(invalidInput);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues).toEqual(
expect.arrayContaining([
expect.objectContaining({ message: "RPC URL must be secure and start with 'https://'" }),
expect.objectContaining({ message: "Enter a valid Tzkt API URL" }),
expect.objectContaining({
message: "Tzkt Explorer URL must be secure and start with 'https://'",
}),
])
);
}
});

it("should allow empty buyTezUrl", () => {
const schema = getNetworkValidationScheme(mockAvailableNetworks);
const validInput = {
name: "No Buy Tez URL",
rpcUrl: "https://rpc.example.com",
tzktApiUrl: "https://api.tzkt.example.com",
tzktExplorerUrl: "https://explorer.tzkt.example.com",
buyTezUrl: "",
};

const result = schema.safeParse(validInput);
expect(result.success).toBe(true);
});

it("should make name optional when editing an existing network", () => {
const existingNetwork: Network = {
name: "Existing Network",
rpcUrl: "https://rpc.example.com",
tzktApiUrl: "https://api.tzkt.example.com",
tzktExplorerUrl: "https://explorer.tzkt.example.com",
};
const schema = getNetworkValidationScheme(mockAvailableNetworks, existingNetwork);
const validInput = {
rpcUrl: "https://new-rpc.example.com",
tzktApiUrl: "https://new-api.tzkt.example.com",
tzktExplorerUrl: "https://new-explorer.tzkt.example.com",
};

const result = schema.safeParse(validInput);
expect(result.success).toBe(true);
});

it("should require name when adding a new network", () => {
const schema = getNetworkValidationScheme(mockAvailableNetworks);
const invalidInput = {
rpcUrl: "https://rpc.example.com",
tzktApiUrl: "https://api.tzkt.example.com",
tzktExplorerUrl: "https://explorer.tzkt.example.com",
};

const result = schema.safeParse(invalidInput);
expect(result.success).toBe(false);
if (!result.success) {
expect(result.error.issues[0].message).toBe("Name is required");
}
});
});
27 changes: 20 additions & 7 deletions packages/components/src/utils/validationSchemes.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,22 @@
import { type Network } from "@umami/tezos";
import { z } from "zod";

const URL_REGEX = new RegExp(
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR IP (v4) address
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
"(\\#[-a-z\\d_]*)?$",
"i"
);

const urlScheme = (urlType: string) =>
z
.string()
.min(1, `${urlType} URL is required`)
.regex(URL_REGEX, `Enter a valid ${urlType} URL`)
.startsWith("https://", { message: `${urlType} URL must be secure and start with 'https://'` });

export const getNetworkValidationScheme = (availableNetworks: Network[], network?: Network) =>
z.object({
name: network
Expand All @@ -11,11 +27,8 @@ export const getNetworkValidationScheme = (availableNetworks: Network[], network
.refine(name => !availableNetworks.find(n => n.name === name), {
message: "Network with this name already exists",
}),
rpcUrl: z.string().min(1, "RPC URL is required").url("Enter a valid RPC URL"),
tzktApiUrl: z.string().min(1, "Tzkt API URL is required").url("Enter a valid Tzkt API URL"),
tzktExplorerUrl: z
.string()
.min(1, "Tzkt Explorer URL is required")
.url("Enter a valid Tzkt Explorer URL"),
buyTezUrl: z.string().url("Enter a valid Buy Tez URL").or(z.literal("")),
rpcUrl: urlScheme("RPC"),
tzktApiUrl: urlScheme("Tzkt API"),
tzktExplorerUrl: urlScheme("Tzkt Explorer"),
buyTezUrl: urlScheme("Buy Tez").or(z.literal("")),
});
Loading

0 comments on commit d22622e

Please sign in to comment.