917 lines
64 KiB
TypeScript
917 lines
64 KiB
TypeScript
|
|
import { Buffer } from "node:buffer";
|
|||
|
|
// @ts-ignore TS6133
|
|||
|
|
import { expect, test } from "vitest";
|
|||
|
|
|
|||
|
|
import * as z from "zod/v3";
|
|||
|
|
|
|||
|
|
const minFive = z.string().min(5, "min5");
|
|||
|
|
const maxFive = z.string().max(5, "max5");
|
|||
|
|
const justFive = z.string().length(5);
|
|||
|
|
const nonempty = z.string().nonempty("nonempty");
|
|||
|
|
const includes = z.string().includes("includes");
|
|||
|
|
const includesFromIndex2 = z.string().includes("includes", { position: 2 });
|
|||
|
|
const startsWith = z.string().startsWith("startsWith");
|
|||
|
|
const endsWith = z.string().endsWith("endsWith");
|
|||
|
|
|
|||
|
|
test("passing validations", () => {
|
|||
|
|
minFive.parse("12345");
|
|||
|
|
minFive.parse("123456");
|
|||
|
|
maxFive.parse("12345");
|
|||
|
|
maxFive.parse("1234");
|
|||
|
|
nonempty.parse("1");
|
|||
|
|
justFive.parse("12345");
|
|||
|
|
includes.parse("XincludesXX");
|
|||
|
|
includesFromIndex2.parse("XXXincludesXX");
|
|||
|
|
startsWith.parse("startsWithX");
|
|||
|
|
endsWith.parse("XendsWith");
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("failing validations", () => {
|
|||
|
|
expect(() => minFive.parse("1234")).toThrow();
|
|||
|
|
expect(() => maxFive.parse("123456")).toThrow();
|
|||
|
|
expect(() => nonempty.parse("")).toThrow();
|
|||
|
|
expect(() => justFive.parse("1234")).toThrow();
|
|||
|
|
expect(() => justFive.parse("123456")).toThrow();
|
|||
|
|
expect(() => includes.parse("XincludeXX")).toThrow();
|
|||
|
|
expect(() => includesFromIndex2.parse("XincludesXX")).toThrow();
|
|||
|
|
expect(() => startsWith.parse("x")).toThrow();
|
|||
|
|
expect(() => endsWith.parse("x")).toThrow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("email validations", () => {
|
|||
|
|
const validEmails = [
|
|||
|
|
`email@domain.com`,
|
|||
|
|
`firstname.lastname@domain.com`,
|
|||
|
|
`email@subdomain.domain.com`,
|
|||
|
|
`firstname+lastname@domain.com`,
|
|||
|
|
`1234567890@domain.com`,
|
|||
|
|
`email@domain-one.com`,
|
|||
|
|
`_______@domain.com`,
|
|||
|
|
`email@domain.name`,
|
|||
|
|
`email@domain.co.jp`,
|
|||
|
|
`firstname-lastname@domain.com`,
|
|||
|
|
`very.common@example.com`,
|
|||
|
|
`disposable.style.email.with+symbol@example.com`,
|
|||
|
|
`other.email-with-hyphen@example.com`,
|
|||
|
|
`fully-qualified-domain@example.com`,
|
|||
|
|
`user.name+tag+sorting@example.com`,
|
|||
|
|
`x@example.com`,
|
|||
|
|
`mojojojo@asdf.example.com`,
|
|||
|
|
`example-indeed@strange-example.com`,
|
|||
|
|
`example@s.example`,
|
|||
|
|
`user-@example.org`,
|
|||
|
|
`user@my-example.com`,
|
|||
|
|
`a@b.cd`,
|
|||
|
|
`work+user@mail.com`,
|
|||
|
|
`tom@test.te-st.com`,
|
|||
|
|
`something@subdomain.domain-with-hyphens.tld`,
|
|||
|
|
`common'name@domain.com`,
|
|||
|
|
`francois@etu.inp-n7.fr`,
|
|||
|
|
];
|
|||
|
|
const invalidEmails = [
|
|||
|
|
// no "printable characters"
|
|||
|
|
// `user%example.com@example.org`,
|
|||
|
|
// `mailhost!username@example.org`,
|
|||
|
|
// `test/test@test.com`,
|
|||
|
|
|
|||
|
|
// double @
|
|||
|
|
`francois@@etu.inp-n7.fr`,
|
|||
|
|
// do not support quotes
|
|||
|
|
`"email"@domain.com`,
|
|||
|
|
`"e asdf sadf ?<>ail"@domain.com`,
|
|||
|
|
`" "@example.org`,
|
|||
|
|
`"john..doe"@example.org`,
|
|||
|
|
`"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com`,
|
|||
|
|
// do not support comma
|
|||
|
|
`a,b@domain.com`,
|
|||
|
|
|
|||
|
|
// do not support IPv4
|
|||
|
|
`email@123.123.123.123`,
|
|||
|
|
`email@[123.123.123.123]`,
|
|||
|
|
`postmaster@123.123.123.123`,
|
|||
|
|
`user@[68.185.127.196]`,
|
|||
|
|
`ipv4@[85.129.96.247]`,
|
|||
|
|
`valid@[79.208.229.53]`,
|
|||
|
|
`valid@[255.255.255.255]`,
|
|||
|
|
`valid@[255.0.55.2]`,
|
|||
|
|
`valid@[255.0.55.2]`,
|
|||
|
|
|
|||
|
|
// do not support ipv6
|
|||
|
|
`hgrebert0@[IPv6:4dc8:ac7:ce79:8878:1290:6098:5c50:1f25]`,
|
|||
|
|
`bshapiro4@[IPv6:3669:c709:e981:4884:59a3:75d1:166b:9ae]`,
|
|||
|
|
`jsmith@[IPv6:2001:db8::1]`,
|
|||
|
|
`postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]`,
|
|||
|
|
`postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:192.168.1.1]`,
|
|||
|
|
|
|||
|
|
// microsoft test cases
|
|||
|
|
`plainaddress`,
|
|||
|
|
`#@%^%#$@#$@#.com`,
|
|||
|
|
`@domain.com`,
|
|||
|
|
`Joe Smith <email@domain.com>`,
|
|||
|
|
`email.domain.com`,
|
|||
|
|
`email@domain@domain.com`,
|
|||
|
|
`.email@domain.com`,
|
|||
|
|
`email.@domain.com`,
|
|||
|
|
`email..email@domain.com`,
|
|||
|
|
`あいうえお@domain.com`,
|
|||
|
|
`email@domain.com (Joe Smith)`,
|
|||
|
|
`email@domain`,
|
|||
|
|
`email@-domain.com`,
|
|||
|
|
`email@111.222.333.44444`,
|
|||
|
|
`email@domain..com`,
|
|||
|
|
`Abc.example.com`,
|
|||
|
|
`A@b@c@example.com`,
|
|||
|
|
`colin..hacks@domain.com`,
|
|||
|
|
`a"b(c)d,e:f;g<h>i[j\k]l@example.com`,
|
|||
|
|
`just"not"right@example.com`,
|
|||
|
|
`this is"not\allowed@example.com`,
|
|||
|
|
`this\ still\"not\\allowed@example.com`,
|
|||
|
|
|
|||
|
|
// random
|
|||
|
|
`i_like_underscore@but_its_not_allowed_in_this_part.example.com`,
|
|||
|
|
`QA[icon]CHOCOLATE[icon]@test.com`,
|
|||
|
|
`invalid@-start.com`,
|
|||
|
|
`invalid@end.com-`,
|
|||
|
|
`a.b@c.d`,
|
|||
|
|
`invalid@[1.1.1.-1]`,
|
|||
|
|
`invalid@[68.185.127.196.55]`,
|
|||
|
|
`temp@[192.168.1]`,
|
|||
|
|
`temp@[9.18.122.]`,
|
|||
|
|
`double..point@test.com`,
|
|||
|
|
`asdad@test..com`,
|
|||
|
|
`asdad@hghg...sd...au`,
|
|||
|
|
`asdad@hghg........au`,
|
|||
|
|
`invalid@[256.2.2.48]`,
|
|||
|
|
`invalid@[256.2.2.48]`,
|
|||
|
|
`invalid@[999.465.265.1]`,
|
|||
|
|
`jkibbey4@[IPv6:82c4:19a8::70a9:2aac:557::ea69:d985:28d]`,
|
|||
|
|
`mlivesay3@[9952:143f:b4df:2179:49a1:5e82:b92e:6b6]`,
|
|||
|
|
`gbacher0@[IPv6:bc37:4d3f:5048:2e26:37cc:248e:df8e:2f7f:af]`,
|
|||
|
|
`invalid@[IPv6:5348:4ed3:5d38:67fb:e9b:acd2:c13:192.168.256.1]`,
|
|||
|
|
`test@.com`,
|
|||
|
|
`aaaaaaaaaaaaaaalongemailthatcausesregexDoSvulnerability@test.c`,
|
|||
|
|
];
|
|||
|
|
const emailSchema = z.string().email();
|
|||
|
|
|
|||
|
|
expect(
|
|||
|
|
validEmails.every((email) => {
|
|||
|
|
return emailSchema.safeParse(email).success;
|
|||
|
|
})
|
|||
|
|
).toBe(true);
|
|||
|
|
expect(
|
|||
|
|
invalidEmails.every((email) => {
|
|||
|
|
return emailSchema.safeParse(email).success === false;
|
|||
|
|
})
|
|||
|
|
).toBe(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const validBase64Strings = [
|
|||
|
|
"SGVsbG8gV29ybGQ=", // "Hello World"
|
|||
|
|
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==", // "This is an encoded string"
|
|||
|
|
"TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcms=", // "Many hands make light work"
|
|||
|
|
"UGF0aWVuY2UgaXMgdGhlIGtleSB0byBzdWNjZXNz", // "Patience is the key to success"
|
|||
|
|
"QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg==", // "Base64 encoding is fun"
|
|||
|
|
"MTIzNDU2Nzg5MA==", // "1234567890"
|
|||
|
|
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=", // "abcdefghijklmnopqrstuvwxyz"
|
|||
|
|
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=", // "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|||
|
|
"ISIkJSMmJyonKCk=", // "!\"#$%&'()*"
|
|||
|
|
"", // Empty string is technically valid base64
|
|||
|
|
"w7/Dv8O+w74K", // ÿÿþþ
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
for (const str of validBase64Strings) {
|
|||
|
|
test(`base64 should accept ${str}`, () => {
|
|||
|
|
expect(z.string().base64().safeParse(str).success).toBe(true);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const invalidBase64Strings = [
|
|||
|
|
"12345", // Not padded correctly, not a multiple of 4 characters
|
|||
|
|
"12345===", // Not padded correctly
|
|||
|
|
"SGVsbG8gV29ybGQ", // Missing padding
|
|||
|
|
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw", // Missing padding
|
|||
|
|
"!UGF0aWVuY2UgaXMgdGhlIGtleSB0byBzdWNjZXNz", // Invalid character '!'
|
|||
|
|
"?QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg==", // Invalid character '?'
|
|||
|
|
".MTIzND2Nzg5MC4=", // Invalid character '.'
|
|||
|
|
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo", // Missing padding
|
|||
|
|
"w7_Dv8O-w74K", // Has - and _ characters (is base64url)
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
for (const str of invalidBase64Strings) {
|
|||
|
|
test(`base64 should reject ${str}`, () => {
|
|||
|
|
expect(z.string().base64().safeParse(str).success).toBe(false);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const validBase64URLStrings = [
|
|||
|
|
"SGVsbG8gV29ybGQ", // "Hello World"
|
|||
|
|
"SGVsbG8gV29ybGQ=", // "Hello World" with padding
|
|||
|
|
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw", // "This is an encoded string"
|
|||
|
|
"VGhpcyBpcyBhbiBlbmNvZGVkIHN0cmluZw==", // "This is an encoded string" with padding
|
|||
|
|
"TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcms", // "Many hands make light work"
|
|||
|
|
"TWFueSBoYW5kcyBtYWtlIGxpZ2h0IHdvcms=", // "Many hands make light work" with padding
|
|||
|
|
"UGF0aWVuY2UgaXMgdGhlIGtleSB0byBzdWNjZXNz", // "Patience is the key to success"
|
|||
|
|
"QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg", // "Base64 encoding is fun"
|
|||
|
|
"QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg==", // "Base64 encoding is fun" with padding
|
|||
|
|
"MTIzNDU2Nzg5MA", // "1234567890"
|
|||
|
|
"MTIzNDU2Nzg5MA==", // "1234567890" with padding
|
|||
|
|
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo", // "abcdefghijklmnopqrstuvwxyz"
|
|||
|
|
"YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXo=", // "abcdefghijklmnopqrstuvwxyz with padding"
|
|||
|
|
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo", // "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|||
|
|
"QUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVo=", // "ABCDEFGHIJKLMNOPQRSTUVWXYZ" with padding
|
|||
|
|
"ISIkJSMmJyonKCk", // "!\"#$%&'()*"
|
|||
|
|
"ISIkJSMmJyonKCk=", // "!\"#$%&'()*" with padding
|
|||
|
|
"", // Empty string is technically valid base64url
|
|||
|
|
"w7_Dv8O-w74K", // ÿÿþþ
|
|||
|
|
"123456",
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
for (const str of validBase64URLStrings) {
|
|||
|
|
test(`base64url should accept ${str}`, () => {
|
|||
|
|
expect(z.string().base64url().safeParse(str).success).toBe(true);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const invalidBase64URLStrings = [
|
|||
|
|
"w7/Dv8O+w74K", // Has + and / characters (is base64)
|
|||
|
|
"12345", // Invalid length (not a multiple of 4 characters when adding allowed number of padding characters)
|
|||
|
|
"12345===", // Not padded correctly
|
|||
|
|
"!UGF0aWVuY2UgaXMgdGhlIGtleSB0byBzdWNjZXNz", // Invalid character '!'
|
|||
|
|
"?QmFzZTY0IGVuY29kaW5nIGlzIGZ1bg==", // Invalid character '?'
|
|||
|
|
".MTIzND2Nzg5MC4=", // Invalid character '.'
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
for (const str of invalidBase64URLStrings) {
|
|||
|
|
test(`base64url should reject ${str}`, () => {
|
|||
|
|
expect(z.string().base64url().safeParse(str).success).toBe(false);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function makeJwt(header: object, payload: object) {
|
|||
|
|
const headerBase64 = Buffer.from(JSON.stringify(header)).toString("base64url");
|
|||
|
|
const payloadBase64 = Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|||
|
|
const signature = "signature"; // Placeholder for the signature
|
|||
|
|
return `${headerBase64}.${payloadBase64}.${signature}`;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
test("jwt validations", () => {
|
|||
|
|
const jwt = z.string().jwt();
|
|||
|
|
const jwtWithAlg = z.string().jwt({ alg: "HS256" });
|
|||
|
|
|
|||
|
|
expect(() => jwt.parse("invalid")).toThrow();
|
|||
|
|
expect(() => jwt.parse("invalid.invalid")).toThrow();
|
|||
|
|
expect(() => jwt.parse("invalid.invalid.invalid")).toThrow();
|
|||
|
|
|
|||
|
|
// Valid JWTs
|
|||
|
|
const d1 = makeJwt({ typ: "JWT", alg: "HS256" }, {});
|
|||
|
|
expect(() => jwt.parse(d1)).not.toThrow();
|
|||
|
|
expect(() => jwtWithAlg.parse(d1)).not.toThrow();
|
|||
|
|
|
|||
|
|
// Invalid header
|
|||
|
|
const d2 = makeJwt({}, {});
|
|||
|
|
expect(() => jwt.parse(d2)).toThrow();
|
|||
|
|
|
|||
|
|
// Wrong algorithm
|
|||
|
|
const d3 = makeJwt({ typ: "JWT", alg: "RS256" }, {});
|
|||
|
|
expect(() => jwtWithAlg.parse(d3)).toThrow();
|
|||
|
|
|
|||
|
|
// missing typ is fine
|
|||
|
|
const d4 = makeJwt({ alg: "HS256" }, {});
|
|||
|
|
jwt.parse(d4);
|
|||
|
|
|
|||
|
|
// type isn't JWT
|
|||
|
|
const d5 = makeJwt({ typ: "SUP", alg: "HS256" }, { foo: "bar" });
|
|||
|
|
expect(() => jwt.parse(d5)).toThrow();
|
|||
|
|
|
|||
|
|
// Custom error message
|
|||
|
|
const customMsg = "Invalid JWT token";
|
|||
|
|
const jwtWithMsg = z.string().jwt({ message: customMsg });
|
|||
|
|
try {
|
|||
|
|
jwtWithMsg.parse("invalid");
|
|||
|
|
} catch (error) {
|
|||
|
|
expect((error as z.ZodError).issues[0].message).toBe(customMsg);
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("url validations", () => {
|
|||
|
|
const url = z.string().url();
|
|||
|
|
url.parse("http://google.com");
|
|||
|
|
url.parse("https://google.com/asdf?asdf=ljk3lk4&asdf=234#asdf");
|
|||
|
|
expect(() => url.parse("asdf")).toThrow();
|
|||
|
|
expect(() => url.parse("https:/")).toThrow();
|
|||
|
|
expect(() => url.parse("asdfj@lkjsdf.com")).toThrow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("url error overrides", () => {
|
|||
|
|
try {
|
|||
|
|
z.string().url().parse("https");
|
|||
|
|
} catch (err) {
|
|||
|
|
expect((err as z.ZodError).issues[0].message).toEqual("Invalid url");
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
z.string().url("badurl").parse("https");
|
|||
|
|
} catch (err) {
|
|||
|
|
expect((err as z.ZodError).issues[0].message).toEqual("badurl");
|
|||
|
|
}
|
|||
|
|
try {
|
|||
|
|
z.string().url({ message: "badurl" }).parse("https");
|
|||
|
|
} catch (err) {
|
|||
|
|
expect((err as z.ZodError).issues[0].message).toEqual("badurl");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("emoji validations", () => {
|
|||
|
|
const emoji = z.string().emoji();
|
|||
|
|
|
|||
|
|
emoji.parse("👋👋👋👋");
|
|||
|
|
emoji.parse("🍺👩🚀🫡");
|
|||
|
|
emoji.parse("💚💙💜💛❤️");
|
|||
|
|
emoji.parse("🐛🗝🐏🍡🎦🚢🏨💫🎌☘🗡😹🔒🎬➡️🍹🗂🚨⚜🕑〽️🚦🌊🍴💍🍌💰😳🌺🍃");
|
|||
|
|
emoji.parse("🇹🇷🤽🏿♂️");
|
|||
|
|
emoji.parse(
|
|||
|
|
"😀😁😂🤣😃😄😅😆😉😊😋😎😍😘🥰😗😙😚☺️☺🙂🤗🤩🤔🤨😐😑😶🙄😏😣😥😮🤐😯😪😫😴😌😛😜😝🤤😒😓😔😕🙃🤑😲☹️☹🙁😖😞😟😤😢😭😦😧😨😩🤯😬😰😱🥵🥶😳🤪😵😡😠🤬😷🤒🤕🤢🤮🤧😇🤠🥳🥴🥺🤥🤫🤭🧐🤓😈👿🤡👹👺💀☠️☠👻👽👾🤖💩😺😸😹😻😼😽🙀😿😾🙈🙉🙊🏻🏼🏽🏾🏿👶👶🏻👶🏼👶🏽👶🏾👶🏿🧒🧒🏻🧒🏼🧒🏽🧒🏾🧒🏿👦👦🏻👦🏼👦🏽👦🏾👦🏿👧👧🏻👧🏼👧🏽👧🏾👧🏿🧑🧑🏻🧑🏼🧑🏽🧑🏾🧑🏿👨👨🏻👨🏼👨🏽👨🏾👨🏿👩👩🏻👩🏼👩🏽👩🏾👩🏿🧓🧓🏻🧓🏼🧓🏽🧓🏾🧓🏿👴👴🏻👴🏼👴🏽👴🏾👴🏿👵👵🏻👵🏼👵🏽👵🏾👵🏿👨⚕️👨⚕👨🏻⚕️👨🏻⚕👨🏼⚕️👨🏼⚕👨🏽⚕️👨🏽⚕👨🏾⚕️👨🏾⚕👨🏿⚕️👨🏿⚕👩⚕️👩⚕👩🏻⚕️👩🏻⚕👩🏼⚕️👩🏼⚕👩🏽⚕️👩🏽⚕👩🏾⚕️👩🏾⚕👩🏿⚕️👩🏿⚕👨🎓👨🏻🎓👨🏼🎓👨🏽🎓👨🏾🎓👨🏿🎓👩🎓👩🏻🎓👩🏼🎓👩🏽🎓👩🏾🎓👩🏿🎓👨🏫👨🏻🏫👨🏼🏫👨🏽🏫👨🏾🏫👨🏿🏫👩🏫👩🏻🏫👩🏼🏫👩🏽🏫👩🏾🏫👩🏿🏫👨⚖️👨⚖👨🏻⚖️👨🏻⚖👨🏼⚖️👨🏼⚖👨🏽⚖️👨🏽⚖👨🏾⚖️👨🏾⚖👨🏿⚖️👨🏿⚖👩⚖️👩⚖👩🏻⚖️👩🏻⚖👩🏼⚖️👩🏼⚖👩🏽⚖️👩🏽⚖👩🏾⚖️👩🏾⚖👩🏿⚖️👩🏿⚖👨🌾👨🏻🌾👨🏼🌾👨🏽🌾👨🏾🌾👨🏿🌾👩🌾👩🏻🌾👩🏼🌾👩🏽🌾👩🏾🌾👩🏿🌾👨🍳👨🏻🍳👨🏼🍳👨🏽🍳👨🏾🍳👨🏿🍳👩🍳👩🏻🍳👩🏼🍳👩🏽🍳👩🏾🍳👩🏿🍳👨🔧👨🏻🔧👨🏼🔧👨🏽🔧👨🏾🔧👨🏿🔧👩🔧👩🏻🔧👩🏼🔧👩🏽🔧👩🏾🔧👩🏿🔧👨🏭👨🏻🏭👨🏼🏭👨🏽🏭👨🏾🏭👨🏿🏭👩🏭👩🏻🏭👩🏼🏭👩🏽🏭👩🏾🏭👩🏿🏭👨💼👨🏻💼👨🏼💼👨🏽💼👨🏾💼👨🏿💼👩💼👩🏻💼👩🏼💼👩🏽💼👩🏾💼👩🏿💼👨🔬👨🏻🔬👨🏼🔬👨🏽🔬👨🏾🔬👨🏿🔬👩🔬👩🏻🔬👩🏼🔬👩🏽🔬👩🏾🔬👩🏿🔬👨💻👨🏻💻👨🏼💻👨🏽💻👨🏾💻👨🏿💻👩💻👩🏻💻👩🏼💻👩🏽💻👩🏾💻👩🏿💻👨🎤👨🏻🎤👨🏼🎤👨🏽🎤👨🏾🎤👨🏿🎤👩🎤👩🏻🎤👩🏼🎤👩🏽🎤👩🏾🎤👩🏿🎤👨🎨👨🏻🎨👨🏼🎨👨🏽🎨👨🏾🎨👨🏿🎨👩🎨👩🏻🎨👩🏼🎨👩🏽🎨👩🏾🎨👩🏿🎨👨✈️👨✈👨🏻✈️👨🏻✈👨🏼✈️👨🏼✈👨🏽✈️👨🏽✈👨🏾✈️👨🏾✈👨🏿✈️👨🏿✈👩✈️👩✈👩🏻✈️👩🏻✈👩🏼✈️👩🏼✈👩🏽✈️👩🏽✈👩🏾✈️👩🏾✈👩🏿✈️👩🏿✈👨🚀👨🏻🚀👨🏼🚀👨🏽🚀👨🏾🚀👨🏿🚀👩🚀👩🏻🚀👩🏼🚀👩🏽🚀👩🏾🚀👩🏿🚀👨🚒👨🏻🚒👨<EFBFBD>
|
|||
|
|
);
|
|||
|
|
expect(() => emoji.parse(":-)")).toThrow();
|
|||
|
|
expect(() => emoji.parse("😀 is an emoji")).toThrow();
|
|||
|
|
expect(() => emoji.parse("😀stuff")).toThrow();
|
|||
|
|
expect(() => emoji.parse("stuff😀")).toThrow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("uuid", () => {
|
|||
|
|
const uuid = z.string().uuid("custom error");
|
|||
|
|
uuid.parse("9491d710-3185-4e06-bea0-6a2f275345e0");
|
|||
|
|
uuid.parse("d89e7b01-7598-ed11-9d7a-0022489382fd"); // new sequential id
|
|||
|
|
uuid.parse("00000000-0000-0000-0000-000000000000");
|
|||
|
|
uuid.parse("b3ce60f8-e8b9-40f5-1150-172ede56ff74"); // Variant 0 - RFC 4122: Reserved, NCS backward compatibility
|
|||
|
|
uuid.parse("92e76bf9-28b3-4730-cd7f-cb6bc51f8c09"); // Variant 2 - RFC 4122: Reserved, Microsoft Corporation backward compatibility
|
|||
|
|
const result = uuid.safeParse("9491d710-3185-4e06-bea0-6a2f275345e0X");
|
|||
|
|
expect(result.success).toEqual(false);
|
|||
|
|
if (!result.success) {
|
|||
|
|
expect(result.error.issues[0].message).toEqual("custom error");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("bad uuid", () => {
|
|||
|
|
const uuid = z.string().uuid("custom error");
|
|||
|
|
uuid.parse("9491d710-3185-4e06-bea0-6a2f275345e0");
|
|||
|
|
const result = uuid.safeParse("invalid uuid");
|
|||
|
|
expect(result.success).toEqual(false);
|
|||
|
|
if (!result.success) {
|
|||
|
|
expect(result.error.issues[0].message).toEqual("custom error");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("nanoid", () => {
|
|||
|
|
const nanoid = z.string().nanoid("custom error");
|
|||
|
|
nanoid.parse("lfNZluvAxMkf7Q8C5H-QS");
|
|||
|
|
nanoid.parse("mIU_4PJWikaU8fMbmkouz");
|
|||
|
|
nanoid.parse("Hb9ZUtUa2JDm_dD-47EGv");
|
|||
|
|
nanoid.parse("5Noocgv_8vQ9oPijj4ioQ");
|
|||
|
|
const result = nanoid.safeParse("Xq90uDyhddC53KsoASYJGX");
|
|||
|
|
expect(result.success).toEqual(false);
|
|||
|
|
if (!result.success) {
|
|||
|
|
expect(result.error.issues[0].message).toEqual("custom error");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("bad nanoid", () => {
|
|||
|
|
const nanoid = z.string().nanoid("custom error");
|
|||
|
|
nanoid.parse("ySh_984wpDUu7IQRrLXAp");
|
|||
|
|
const result = nanoid.safeParse("invalid nanoid");
|
|||
|
|
expect(result.success).toEqual(false);
|
|||
|
|
if (!result.success) {
|
|||
|
|
expect(result.error.issues[0].message).toEqual("custom error");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("cuid", () => {
|
|||
|
|
const cuid = z.string().cuid();
|
|||
|
|
cuid.parse("ckopqwooh000001la8mbi2im9");
|
|||
|
|
const result = cuid.safeParse("cifjhdsfhsd-invalid-cuid");
|
|||
|
|
expect(result.success).toEqual(false);
|
|||
|
|
if (!result.success) {
|
|||
|
|
expect(result.error.issues[0].message).toEqual("Invalid cuid");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("cuid2", () => {
|
|||
|
|
const cuid2 = z.string().cuid2();
|
|||
|
|
const validStrings = [
|
|||
|
|
"a", // short string
|
|||
|
|
"tz4a98xxat96iws9zmbrgj3a", // normal string
|
|||
|
|
"kf5vz6ssxe4zjcb409rjgo747tc5qjazgptvotk6", // longer than require("@paralleldrive/cuid2").bigLength
|
|||
|
|
];
|
|||
|
|
for (const s of validStrings) {
|
|||
|
|
cuid2.parse(s);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const invalidStrings = [
|
|||
|
|
"", // empty string
|
|||
|
|
"tz4a98xxat96iws9zMbrgj3a", // include uppercase
|
|||
|
|
"tz4a98xxat96iws-zmbrgj3a", // involve symbols
|
|||
|
|
];
|
|||
|
|
const results = invalidStrings.map((s) => cuid2.safeParse(s));
|
|||
|
|
expect(results.every((r) => !r.success)).toEqual(true);
|
|||
|
|
if (!results[0].success) {
|
|||
|
|
expect(results[0].error.issues[0].message).toEqual("Invalid cuid2");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("ulid", () => {
|
|||
|
|
const ulid = z.string().ulid();
|
|||
|
|
ulid.parse("01ARZ3NDEKTSV4RRFFQ69G5FAV");
|
|||
|
|
const result = ulid.safeParse("invalidulid");
|
|||
|
|
expect(result.success).toEqual(false);
|
|||
|
|
const tooLong = "01ARZ3NDEKTSV4RRFFQ69G5FAVA";
|
|||
|
|
expect(ulid.safeParse(tooLong).success).toEqual(false);
|
|||
|
|
if (!result.success) {
|
|||
|
|
expect(result.error.issues[0].message).toEqual("Invalid ulid");
|
|||
|
|
}
|
|||
|
|
const caseInsensitive = ulid.safeParse("01arZ3nDeKTsV4RRffQ69G5FAV");
|
|||
|
|
expect(caseInsensitive.success).toEqual(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("regex", () => {
|
|||
|
|
z.string()
|
|||
|
|
.regex(/^moo+$/)
|
|||
|
|
.parse("mooooo");
|
|||
|
|
expect(() => z.string().uuid().parse("purr")).toThrow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("regexp error message", () => {
|
|||
|
|
const result = z
|
|||
|
|
.string()
|
|||
|
|
.regex(/^moo+$/)
|
|||
|
|
.safeParse("boooo");
|
|||
|
|
if (!result.success) {
|
|||
|
|
expect(result.error.issues[0].message).toEqual("Invalid");
|
|||
|
|
} else {
|
|||
|
|
throw new Error("validation should have failed");
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
expect(() => z.string().uuid().parse("purr")).toThrow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("regex lastIndex reset", () => {
|
|||
|
|
const schema = z.string().regex(/^\d+$/g);
|
|||
|
|
expect(schema.safeParse("123").success).toEqual(true);
|
|||
|
|
expect(schema.safeParse("123").success).toEqual(true);
|
|||
|
|
expect(schema.safeParse("123").success).toEqual(true);
|
|||
|
|
expect(schema.safeParse("123").success).toEqual(true);
|
|||
|
|
expect(schema.safeParse("123").success).toEqual(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("checks getters", () => {
|
|||
|
|
expect(z.string().email().isEmail).toEqual(true);
|
|||
|
|
expect(z.string().email().isURL).toEqual(false);
|
|||
|
|
expect(z.string().email().isCUID).toEqual(false);
|
|||
|
|
expect(z.string().email().isCUID2).toEqual(false);
|
|||
|
|
expect(z.string().email().isUUID).toEqual(false);
|
|||
|
|
expect(z.string().email().isNANOID).toEqual(false);
|
|||
|
|
expect(z.string().email().isIP).toEqual(false);
|
|||
|
|
expect(z.string().email().isCIDR).toEqual(false);
|
|||
|
|
expect(z.string().email().isULID).toEqual(false);
|
|||
|
|
|
|||
|
|
expect(z.string().url().isEmail).toEqual(false);
|
|||
|
|
expect(z.string().url().isURL).toEqual(true);
|
|||
|
|
expect(z.string().url().isCUID).toEqual(false);
|
|||
|
|
expect(z.string().url().isCUID2).toEqual(false);
|
|||
|
|
expect(z.string().url().isUUID).toEqual(false);
|
|||
|
|
expect(z.string().url().isNANOID).toEqual(false);
|
|||
|
|
expect(z.string().url().isIP).toEqual(false);
|
|||
|
|
expect(z.string().url().isCIDR).toEqual(false);
|
|||
|
|
expect(z.string().url().isULID).toEqual(false);
|
|||
|
|
|
|||
|
|
expect(z.string().cuid().isEmail).toEqual(false);
|
|||
|
|
expect(z.string().cuid().isURL).toEqual(false);
|
|||
|
|
expect(z.string().cuid().isCUID).toEqual(true);
|
|||
|
|
expect(z.string().cuid().isCUID2).toEqual(false);
|
|||
|
|
expect(z.string().cuid().isUUID).toEqual(false);
|
|||
|
|
expect(z.string().cuid().isNANOID).toEqual(false);
|
|||
|
|
expect(z.string().cuid().isIP).toEqual(false);
|
|||
|
|
expect(z.string().cuid().isCIDR).toEqual(false);
|
|||
|
|
expect(z.string().cuid().isULID).toEqual(false);
|
|||
|
|
|
|||
|
|
expect(z.string().cuid2().isEmail).toEqual(false);
|
|||
|
|
expect(z.string().cuid2().isURL).toEqual(false);
|
|||
|
|
expect(z.string().cuid2().isCUID).toEqual(false);
|
|||
|
|
expect(z.string().cuid2().isCUID2).toEqual(true);
|
|||
|
|
expect(z.string().cuid2().isUUID).toEqual(false);
|
|||
|
|
expect(z.string().cuid2().isNANOID).toEqual(false);
|
|||
|
|
expect(z.string().cuid2().isIP).toEqual(false);
|
|||
|
|
expect(z.string().cuid2().isCIDR).toEqual(false);
|
|||
|
|
expect(z.string().cuid2().isULID).toEqual(false);
|
|||
|
|
|
|||
|
|
expect(z.string().uuid().isEmail).toEqual(false);
|
|||
|
|
expect(z.string().uuid().isURL).toEqual(false);
|
|||
|
|
expect(z.string().uuid().isCUID).toEqual(false);
|
|||
|
|
expect(z.string().uuid().isCUID2).toEqual(false);
|
|||
|
|
expect(z.string().uuid().isUUID).toEqual(true);
|
|||
|
|
expect(z.string().uuid().isNANOID).toEqual(false);
|
|||
|
|
expect(z.string().uuid().isIP).toEqual(false);
|
|||
|
|
expect(z.string().uuid().isCIDR).toEqual(false);
|
|||
|
|
expect(z.string().uuid().isULID).toEqual(false);
|
|||
|
|
|
|||
|
|
expect(z.string().nanoid().isEmail).toEqual(false);
|
|||
|
|
expect(z.string().nanoid().isURL).toEqual(false);
|
|||
|
|
expect(z.string().nanoid().isCUID).toEqual(false);
|
|||
|
|
expect(z.string().nanoid().isCUID2).toEqual(false);
|
|||
|
|
expect(z.string().nanoid().isUUID).toEqual(false);
|
|||
|
|
expect(z.string().nanoid().isNANOID).toEqual(true);
|
|||
|
|
expect(z.string().nanoid().isIP).toEqual(false);
|
|||
|
|
expect(z.string().nanoid().isCIDR).toEqual(false);
|
|||
|
|
expect(z.string().nanoid().isULID).toEqual(false);
|
|||
|
|
|
|||
|
|
expect(z.string().ip().isEmail).toEqual(false);
|
|||
|
|
expect(z.string().ip().isURL).toEqual(false);
|
|||
|
|
expect(z.string().ip().isCUID).toEqual(false);
|
|||
|
|
expect(z.string().ip().isCUID2).toEqual(false);
|
|||
|
|
expect(z.string().ip().isUUID).toEqual(false);
|
|||
|
|
expect(z.string().ip().isNANOID).toEqual(false);
|
|||
|
|
expect(z.string().ip().isIP).toEqual(true);
|
|||
|
|
expect(z.string().ip().isCIDR).toEqual(false);
|
|||
|
|
expect(z.string().ip().isULID).toEqual(false);
|
|||
|
|
|
|||
|
|
expect(z.string().cidr().isEmail).toEqual(false);
|
|||
|
|
expect(z.string().cidr().isURL).toEqual(false);
|
|||
|
|
expect(z.string().cidr().isCUID).toEqual(false);
|
|||
|
|
expect(z.string().cidr().isCUID2).toEqual(false);
|
|||
|
|
expect(z.string().cidr().isUUID).toEqual(false);
|
|||
|
|
expect(z.string().cidr().isNANOID).toEqual(false);
|
|||
|
|
expect(z.string().cidr().isIP).toEqual(false);
|
|||
|
|
expect(z.string().cidr().isCIDR).toEqual(true);
|
|||
|
|
expect(z.string().cidr().isULID).toEqual(false);
|
|||
|
|
|
|||
|
|
expect(z.string().ulid().isEmail).toEqual(false);
|
|||
|
|
expect(z.string().ulid().isURL).toEqual(false);
|
|||
|
|
expect(z.string().ulid().isCUID).toEqual(false);
|
|||
|
|
expect(z.string().ulid().isCUID2).toEqual(false);
|
|||
|
|
expect(z.string().ulid().isUUID).toEqual(false);
|
|||
|
|
expect(z.string().ulid().isNANOID).toEqual(false);
|
|||
|
|
expect(z.string().ulid().isIP).toEqual(false);
|
|||
|
|
expect(z.string().ulid().isCIDR).toEqual(false);
|
|||
|
|
expect(z.string().ulid().isULID).toEqual(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("min max getters", () => {
|
|||
|
|
expect(z.string().min(5).minLength).toEqual(5);
|
|||
|
|
expect(z.string().min(5).min(10).minLength).toEqual(10);
|
|||
|
|
expect(z.string().minLength).toEqual(null);
|
|||
|
|
|
|||
|
|
expect(z.string().max(5).maxLength).toEqual(5);
|
|||
|
|
expect(z.string().max(5).max(1).maxLength).toEqual(1);
|
|||
|
|
expect(z.string().maxLength).toEqual(null);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("trim", () => {
|
|||
|
|
expect(z.string().trim().min(2).parse(" 12 ")).toEqual("12");
|
|||
|
|
|
|||
|
|
// ordering of methods is respected
|
|||
|
|
expect(z.string().min(2).trim().parse(" 1 ")).toEqual("1");
|
|||
|
|
expect(() => z.string().trim().min(2).parse(" 1 ")).toThrow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("lowerCase", () => {
|
|||
|
|
expect(z.string().toLowerCase().parse("ASDF")).toEqual("asdf");
|
|||
|
|
expect(z.string().toUpperCase().parse("asdf")).toEqual("ASDF");
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("datetime", () => {
|
|||
|
|
const a = z.string().datetime({});
|
|||
|
|
expect(a.isDatetime).toEqual(true);
|
|||
|
|
|
|||
|
|
const b = z.string().datetime({ offset: true });
|
|||
|
|
expect(b.isDatetime).toEqual(true);
|
|||
|
|
|
|||
|
|
const c = z.string().datetime({ precision: 3 });
|
|||
|
|
expect(c.isDatetime).toEqual(true);
|
|||
|
|
|
|||
|
|
const d = z.string().datetime({ offset: true, precision: 0 });
|
|||
|
|
expect(d.isDatetime).toEqual(true);
|
|||
|
|
|
|||
|
|
const { isDatetime } = z.string().datetime();
|
|||
|
|
expect(isDatetime).toEqual(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("datetime parsing", () => {
|
|||
|
|
const datetime = z.string().datetime();
|
|||
|
|
datetime.parse("1970-01-01T00:00:00.000Z");
|
|||
|
|
datetime.parse("2022-10-13T09:52:31.816Z");
|
|||
|
|
datetime.parse("2022-10-13T09:52:31.8162314Z");
|
|||
|
|
datetime.parse("1970-01-01T00:00:00Z");
|
|||
|
|
datetime.parse("2022-10-13T09:52:31Z");
|
|||
|
|
datetime.parse("2022-10-13T09:52Z");
|
|||
|
|
expect(() => datetime.parse("")).toThrow();
|
|||
|
|
expect(() => datetime.parse("foo")).toThrow();
|
|||
|
|
expect(() => datetime.parse("2020-10-14")).toThrow();
|
|||
|
|
expect(() => datetime.parse("T18:45:12.123")).toThrow();
|
|||
|
|
expect(() => datetime.parse("2020-10-14T17:42:29+00:00")).toThrow();
|
|||
|
|
expect(() => datetime.parse("2020-10-14T17:42.123+00:00")).toThrow();
|
|||
|
|
|
|||
|
|
const datetimeNoMs = z.string().datetime({ precision: 0 });
|
|||
|
|
datetimeNoMs.parse("1970-01-01T00:00:00Z");
|
|||
|
|
datetimeNoMs.parse("2022-10-13T09:52:31Z");
|
|||
|
|
datetimeNoMs.parse("2022-10-13T09:52Z");
|
|||
|
|
expect(() => datetimeNoMs.parse("tuna")).toThrow();
|
|||
|
|
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.000Z")).toThrow();
|
|||
|
|
expect(() => datetimeNoMs.parse("1970-01-01T00:00:00.Z")).toThrow();
|
|||
|
|
expect(() => datetimeNoMs.parse("2022-10-13T09:52:31.816Z")).toThrow();
|
|||
|
|
|
|||
|
|
const datetime3Ms = z.string().datetime({ precision: 3 });
|
|||
|
|
datetime3Ms.parse("1970-01-01T00:00:00.000Z");
|
|||
|
|
datetime3Ms.parse("2022-10-13T09:52:31.123Z");
|
|||
|
|
expect(() => datetime3Ms.parse("tuna")).toThrow();
|
|||
|
|
expect(() => datetime3Ms.parse("1970-01-01T00:00:00.1Z")).toThrow();
|
|||
|
|
expect(() => datetime3Ms.parse("1970-01-01T00:00:00.12Z")).toThrow();
|
|||
|
|
expect(() => datetime3Ms.parse("2022-10-13T09:52:31Z")).toThrow();
|
|||
|
|
expect(() => datetime3Ms.parse("2022-10-13T09:52Z")).toThrow();
|
|||
|
|
|
|||
|
|
const datetimeOffset = z.string().datetime({ offset: true });
|
|||
|
|
datetimeOffset.parse("1970-01-01T00:00:00.000Z");
|
|||
|
|
datetimeOffset.parse("2022-10-13T09:52:31.816234134Z");
|
|||
|
|
datetimeOffset.parse("1970-01-01T00:00:00Z");
|
|||
|
|
datetimeOffset.parse("2022-10-13T09:52:31.4Z");
|
|||
|
|
datetimeOffset.parse("2020-10-14T17:42:29+00:00");
|
|||
|
|
datetimeOffset.parse("2020-10-14T17:42:29+03:15");
|
|||
|
|
datetimeOffset.parse("2020-10-14T17:42:29+0315");
|
|||
|
|
datetimeOffset.parse("2020-10-14T17:42+0315");
|
|||
|
|
expect(() => datetimeOffset.parse("2020-10-14T17:42:29+03"));
|
|||
|
|
expect(() => datetimeOffset.parse("tuna")).toThrow();
|
|||
|
|
expect(() => datetimeOffset.parse("2022-10-13T09:52:31.Z")).toThrow();
|
|||
|
|
|
|||
|
|
const datetimeOffsetNoMs = z.string().datetime({ offset: true, precision: 0 });
|
|||
|
|
datetimeOffsetNoMs.parse("1970-01-01T00:00:00Z");
|
|||
|
|
datetimeOffsetNoMs.parse("2022-10-13T09:52:31Z");
|
|||
|
|
datetimeOffsetNoMs.parse("2020-10-14T17:42:29+00:00");
|
|||
|
|
datetimeOffsetNoMs.parse("2020-10-14T17:42:29+0000");
|
|||
|
|
datetimeOffsetNoMs.parse("2020-10-14T17:42+0000");
|
|||
|
|
expect(() => datetimeOffsetNoMs.parse("2020-10-14T17:42:29+00")).toThrow();
|
|||
|
|
expect(() => datetimeOffsetNoMs.parse("tuna")).toThrow();
|
|||
|
|
expect(() => datetimeOffsetNoMs.parse("1970-01-01T00:00:00.000Z")).toThrow();
|
|||
|
|
expect(() => datetimeOffsetNoMs.parse("1970-01-01T00:00:00.Z")).toThrow();
|
|||
|
|
expect(() => datetimeOffsetNoMs.parse("2022-10-13T09:52:31.816Z")).toThrow();
|
|||
|
|
expect(() => datetimeOffsetNoMs.parse("2020-10-14T17:42:29.124+00:00")).toThrow();
|
|||
|
|
|
|||
|
|
const datetimeOffset4Ms = z.string().datetime({ offset: true, precision: 4 });
|
|||
|
|
datetimeOffset4Ms.parse("1970-01-01T00:00:00.1234Z");
|
|||
|
|
datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+00:00");
|
|||
|
|
datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+0000");
|
|||
|
|
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42:29.1234+00")).toThrow();
|
|||
|
|
expect(() => datetimeOffset4Ms.parse("tuna")).toThrow();
|
|||
|
|
expect(() => datetimeOffset4Ms.parse("1970-01-01T00:00:00.123Z")).toThrow();
|
|||
|
|
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42:29.124+00:00")).toThrow();
|
|||
|
|
expect(() => datetimeOffset4Ms.parse("2020-10-14T17:42+00:00")).toThrow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("date", () => {
|
|||
|
|
const a = z.string().date();
|
|||
|
|
expect(a.isDate).toEqual(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("date parsing", () => {
|
|||
|
|
const date = z.string().date();
|
|||
|
|
date.parse("1970-01-01");
|
|||
|
|
date.parse("2022-01-31");
|
|||
|
|
date.parse("2022-03-31");
|
|||
|
|
date.parse("2022-04-30");
|
|||
|
|
date.parse("2022-05-31");
|
|||
|
|
date.parse("2022-06-30");
|
|||
|
|
date.parse("2022-07-31");
|
|||
|
|
date.parse("2022-08-31");
|
|||
|
|
date.parse("2022-09-30");
|
|||
|
|
date.parse("2022-10-31");
|
|||
|
|
date.parse("2022-11-30");
|
|||
|
|
date.parse("2022-12-31");
|
|||
|
|
|
|||
|
|
date.parse("2000-02-29");
|
|||
|
|
date.parse("2400-02-29");
|
|||
|
|
expect(() => date.parse("2022-02-29")).toThrow();
|
|||
|
|
expect(() => date.parse("2100-02-29")).toThrow();
|
|||
|
|
expect(() => date.parse("2200-02-29")).toThrow();
|
|||
|
|
expect(() => date.parse("2300-02-29")).toThrow();
|
|||
|
|
expect(() => date.parse("2500-02-29")).toThrow();
|
|||
|
|
|
|||
|
|
expect(() => date.parse("")).toThrow();
|
|||
|
|
expect(() => date.parse("foo")).toThrow();
|
|||
|
|
expect(() => date.parse("200-01-01")).toThrow();
|
|||
|
|
expect(() => date.parse("20000-01-01")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-0-01")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-011-01")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-01-0")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-01-011")).toThrow();
|
|||
|
|
expect(() => date.parse("2000/01/01")).toThrow();
|
|||
|
|
expect(() => date.parse("01-01-2022")).toThrow();
|
|||
|
|
expect(() => date.parse("01/01/2022")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-01-01 00:00:00Z")).toThrow();
|
|||
|
|
expect(() => date.parse("2020-10-14T17:42:29+00:00")).toThrow();
|
|||
|
|
expect(() => date.parse("2020-10-14T17:42:29Z")).toThrow();
|
|||
|
|
expect(() => date.parse("2020-10-14T17:42:29")).toThrow();
|
|||
|
|
expect(() => date.parse("2020-10-14T17:42:29.123Z")).toThrow();
|
|||
|
|
|
|||
|
|
expect(() => date.parse("2000-00-12")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-12-00")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-01-32")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-13-01")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-21-01")).toThrow();
|
|||
|
|
|
|||
|
|
expect(() => date.parse("2000-02-30")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-02-31")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-04-31")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-06-31")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-09-31")).toThrow();
|
|||
|
|
expect(() => date.parse("2000-11-31")).toThrow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("time", () => {
|
|||
|
|
const a = z.string().time();
|
|||
|
|
expect(a.isTime).toEqual(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("time parsing", () => {
|
|||
|
|
const time = z.string().time();
|
|||
|
|
time.parse("00:00:00");
|
|||
|
|
time.parse("23:00:00");
|
|||
|
|
time.parse("00:59:00");
|
|||
|
|
time.parse("00:00:59");
|
|||
|
|
time.parse("23:59:59");
|
|||
|
|
time.parse("09:52:31");
|
|||
|
|
time.parse("23:59:59.9999999");
|
|||
|
|
time.parse("23:59");
|
|||
|
|
expect(() => time.parse("")).toThrow();
|
|||
|
|
expect(() => time.parse("foo")).toThrow();
|
|||
|
|
expect(() => time.parse("00:00:00Z")).toThrow();
|
|||
|
|
expect(() => time.parse("0:00:00")).toThrow();
|
|||
|
|
expect(() => time.parse("00:0:00")).toThrow();
|
|||
|
|
expect(() => time.parse("00:00:0")).toThrow();
|
|||
|
|
expect(() => time.parse("00:00:00.000+00:00")).toThrow();
|
|||
|
|
|
|||
|
|
expect(() => time.parse("24:00:00")).toThrow();
|
|||
|
|
expect(() => time.parse("00:60:00")).toThrow();
|
|||
|
|
expect(() => time.parse("00:00:60")).toThrow();
|
|||
|
|
expect(() => time.parse("24:60:60")).toThrow();
|
|||
|
|
expect(() => time.parse("24:60")).toThrow();
|
|||
|
|
|
|||
|
|
const time2 = z.string().time({ precision: 2 });
|
|||
|
|
time2.parse("00:00:00.00");
|
|||
|
|
time2.parse("09:52:31.12");
|
|||
|
|
time2.parse("23:59:59.99");
|
|||
|
|
expect(() => time2.parse("")).toThrow();
|
|||
|
|
expect(() => time2.parse("foo")).toThrow();
|
|||
|
|
expect(() => time2.parse("00:00:00")).toThrow();
|
|||
|
|
expect(() => time2.parse("00:00:00.00Z")).toThrow();
|
|||
|
|
expect(() => time2.parse("00:00:00.0")).toThrow();
|
|||
|
|
expect(() => time2.parse("00:00:00.000")).toThrow();
|
|||
|
|
expect(() => time2.parse("00:00:00.00+00:00")).toThrow();
|
|||
|
|
expect(() => time2.parse("23:59")).toThrow();
|
|||
|
|
|
|||
|
|
// const time3 = z.string().time({ offset: true });
|
|||
|
|
// time3.parse("00:00:00Z");
|
|||
|
|
// time3.parse("09:52:31Z");
|
|||
|
|
// time3.parse("00:00:00+00:00");
|
|||
|
|
// time3.parse("00:00:00+0000");
|
|||
|
|
// time3.parse("00:00:00.000Z");
|
|||
|
|
// time3.parse("00:00:00.000+00:00");
|
|||
|
|
// time3.parse("00:00:00.000+0000");
|
|||
|
|
// expect(() => time3.parse("")).toThrow();
|
|||
|
|
// expect(() => time3.parse("foo")).toThrow();
|
|||
|
|
// expect(() => time3.parse("00:00:00")).toThrow();
|
|||
|
|
// expect(() => time3.parse("00:00:00.000")).toThrow();
|
|||
|
|
|
|||
|
|
// const time4 = z.string().time({ offset: true, precision: 0 });
|
|||
|
|
// time4.parse("00:00:00Z");
|
|||
|
|
// time4.parse("09:52:31Z");
|
|||
|
|
// time4.parse("00:00:00+00:00");
|
|||
|
|
// time4.parse("00:00:00+0000");
|
|||
|
|
// expect(() => time4.parse("")).toThrow();
|
|||
|
|
// expect(() => time4.parse("foo")).toThrow();
|
|||
|
|
// expect(() => time4.parse("00:00:00.0")).toThrow();
|
|||
|
|
// expect(() => time4.parse("00:00:00.000")).toThrow();
|
|||
|
|
// expect(() => time4.parse("00:00:00.000+00:00")).toThrow();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("duration", () => {
|
|||
|
|
const duration = z.string().duration();
|
|||
|
|
expect(duration.isDuration).toEqual(true);
|
|||
|
|
|
|||
|
|
const validDurations = [
|
|||
|
|
"P3Y6M4DT12H30M5S",
|
|||
|
|
"P2Y9M3DT12H31M8.001S",
|
|||
|
|
"+P3Y6M4DT12H30M5S",
|
|||
|
|
"-PT0.001S",
|
|||
|
|
"+PT0.001S",
|
|||
|
|
"PT0,001S",
|
|||
|
|
"PT12H30M5S",
|
|||
|
|
"-P2M1D",
|
|||
|
|
"P-2M-1D",
|
|||
|
|
"-P5DT10H",
|
|||
|
|
"P-5DT-10H",
|
|||
|
|
"P1Y",
|
|||
|
|
"P2MT30M",
|
|||
|
|
"PT6H",
|
|||
|
|
"P5W",
|
|||
|
|
"P0.5Y",
|
|||
|
|
"P0,5Y",
|
|||
|
|
"P42YT7.004M",
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const invalidDurations = ["foo bar", "", " ", "P", "T1H", "P0.5Y1D", "P0,5Y6M", "P1YT"];
|
|||
|
|
|
|||
|
|
for (const val of validDurations) {
|
|||
|
|
const result = duration.safeParse(val);
|
|||
|
|
if (!result.success) {
|
|||
|
|
throw Error(`Valid duration could not be parsed: ${val}`);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
for (const val of invalidDurations) {
|
|||
|
|
const result = duration.safeParse(val);
|
|||
|
|
|
|||
|
|
if (result.success) {
|
|||
|
|
throw Error(`Invalid duration was successful parsed: ${val}`);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
expect(result.error.issues[0].message).toEqual("Invalid duration");
|
|||
|
|
}
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("IP validation", () => {
|
|||
|
|
const ip = z.string().ip();
|
|||
|
|
expect(ip.safeParse("122.122.122.122").success).toBe(true);
|
|||
|
|
|
|||
|
|
const ipv4 = z.string().ip({ version: "v4" });
|
|||
|
|
expect(() => ipv4.parse("6097:adfa:6f0b:220d:db08:5021:6191:7990")).toThrow();
|
|||
|
|
|
|||
|
|
const ipv6 = z.string().ip({ version: "v6" });
|
|||
|
|
expect(() => ipv6.parse("254.164.77.1")).toThrow();
|
|||
|
|
|
|||
|
|
const validIPs = [
|
|||
|
|
"1e5e:e6c8:daac:514b:114b:e360:d8c0:682c",
|
|||
|
|
"9d4:c956:420f:5788:4339:9b3b:2418:75c3",
|
|||
|
|
"474f:4c83::4e40:a47:ff95:0cda",
|
|||
|
|
"d329:0:25b4:db47:a9d1:0:4926:0000",
|
|||
|
|
"e48:10fb:1499:3e28:e4b6:dea5:4692:912c",
|
|||
|
|
"114.71.82.94",
|
|||
|
|
"0.0.0.0",
|
|||
|
|
"37.85.236.115",
|
|||
|
|
"2001:4888:50:ff00:500:d::",
|
|||
|
|
"2001:4888:50:ff00:0500:000d:000:0000",
|
|||
|
|
"2001:4888:50:ff00:0500:000d:0000:0000",
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const invalidIPs = [
|
|||
|
|
"d329:1be4:25b4:db47:a9d1:dc71:4926:992c:14af",
|
|||
|
|
"d5e7:7214:2b78::3906:85e6:53cc:709:32ba",
|
|||
|
|
"8f69::c757:395e:976e::3441",
|
|||
|
|
"54cb::473f:d516:0.255.256.22",
|
|||
|
|
"54cb::473f:d516:192.168.1",
|
|||
|
|
"256.0.4.4",
|
|||
|
|
"-1.0.555.4",
|
|||
|
|
"0.0.0.0.0",
|
|||
|
|
"1.1.1",
|
|||
|
|
];
|
|||
|
|
// no parameters check IPv4 or IPv6
|
|||
|
|
const ipSchema = z.string().ip();
|
|||
|
|
expect(validIPs.every((ip) => ipSchema.safeParse(ip).success)).toBe(true);
|
|||
|
|
expect(invalidIPs.every((ip) => ipSchema.safeParse(ip).success === false)).toBe(true);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
test("CIDR validation", () => {
|
|||
|
|
const ipv4Cidr = z.string().cidr({ version: "v4" });
|
|||
|
|
expect(() => ipv4Cidr.parse("2001:0db8:85a3::8a2e:0370:7334/64")).toThrow();
|
|||
|
|
|
|||
|
|
const ipv6Cidr = z.string().cidr({ version: "v6" });
|
|||
|
|
expect(() => ipv6Cidr.parse("192.168.0.1/24")).toThrow();
|
|||
|
|
|
|||
|
|
const validCidrs = [
|
|||
|
|
"192.168.0.0/24",
|
|||
|
|
"10.0.0.0/8",
|
|||
|
|
"203.0.113.0/24",
|
|||
|
|
"192.0.2.0/24",
|
|||
|
|
"127.0.0.0/8",
|
|||
|
|
"172.16.0.0/12",
|
|||
|
|
"192.168.1.0/24",
|
|||
|
|
"fc00::/7",
|
|||
|
|
"fd00::/8",
|
|||
|
|
"2001:db8::/32",
|
|||
|
|
"2607:f0d0:1002:51::4/64",
|
|||
|
|
"2001:0db8:85a3:0000:0000:8a2e:0370:7334/128",
|
|||
|
|
"2001:0db8:1234:0000::/64",
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const invalidCidrs = [
|
|||
|
|
"192.168.1.1/33",
|
|||
|
|
"10.0.0.1/-1",
|
|||
|
|
"192.168.1.1/24/24",
|
|||
|
|
"192.168.1.0/abc",
|
|||
|
|
"2001:db8::1/129",
|
|||
|
|
"2001:db8::1/-1",
|
|||
|
|
"2001:db8::1/64/64",
|
|||
|
|
"2001:db8::1/abc",
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
// no parameters check IPv4 or IPv6
|
|||
|
|
const cidrSchema = z.string().cidr();
|
|||
|
|
expect(validCidrs.every((ip) => cidrSchema.safeParse(ip).success)).toBe(true);
|
|||
|
|
expect(invalidCidrs.every((ip) => cidrSchema.safeParse(ip).success === false)).toBe(true);
|
|||
|
|
});
|