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 e1aba72
Show file tree
Hide file tree
Showing 4 changed files with 325 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"
}
}
118 changes: 118 additions & 0 deletions packages/components/src/utils/validationSchemes.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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",
buyTezUrl: "",
};

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 = {
name: "",
rpcUrl: "https://rpc.example.com",
tzktApiUrl: "https://api.tzkt.example.com",
tzktExplorerUrl: "https://explorer.tzkt.example.com",
buyTezUrl: "",
};

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

1 comment on commit e1aba72

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Title Lines Statements Branches Functions
apps/desktop Coverage: 83%
83.6% (1765/2111) 78.81% (837/1062) 78.14% (447/572)
apps/web Coverage: 83%
83.6% (1765/2111) 78.81% (837/1062) 78.14% (447/572)
packages/components Coverage: 97%
97.28% (179/184) 95.23% (80/84) 86.53% (45/52)
packages/core Coverage: 81%
82.22% (222/270) 71.73% (99/138) 81.96% (50/61)
packages/crypto Coverage: 100%
100% (28/28) 100% (3/3) 100% (5/5)
packages/data-polling Coverage: 97%
95.27% (141/148) 87.5% (21/24) 92.85% (39/42)
packages/multisig Coverage: 98%
98.47% (129/131) 85.71% (18/21) 100% (35/35)
packages/social-auth Coverage: 100%
100% (21/21) 100% (11/11) 100% (3/3)
packages/state Coverage: 85%
84.61% (792/936) 80.97% (166/205) 78.72% (296/376)
packages/tezos Coverage: 86%
85.57% (89/104) 89.47% (17/19) 82.75% (24/29)
packages/tzkt Coverage: 86%
84.05% (58/69) 81.25% (13/16) 76.92% (30/39)

Please sign in to comment.