import t from "tap";
import { expect } from "chai";
import { z } from "zod";
import {
ALLOWED_INPUT_REGEX,
DOMAIN_FRIENDLY_INPUT_REGEX,
NAME_REGEX,
NICKNAME_REGEX,
GITHUB_USERNAME_REGEX,
DISCORD_TAG_REGEX,
URL_DETECTION_REGEX,
MAX_LENGTH_STANDARD,
MAX_LENGTH_EXTENDED,
} from "../src/lib/validation-helpers";
t.test("Validation Helpers - Regex Tests", async () => {
t.test("ALLOWED_INPUT_REGEX", async (t) => {
t.test("should allow valid inputs", async () => {
const validInputs = [
"Hello World",
"Test 123",
"O'Brien",
"Name-With-Dashes",
"Name_With_Underscores",
"Name (with parentheses)",
"Name+Plus",
"L'Hôpital",
"Café",
"Zürich",
"",
];
for (const input of validInputs) {
expect(ALLOWED_INPUT_REGEX.test(input)).to.be.true;
}
});
t.test("should reject inputs with dots (domain names)", async () => {
const invalidInputs = [
"example.com",
"my.domain",
"user@email.com",
"https://example.com",
"http://test.com",
"/path/to/file",
];
for (const input of invalidInputs) {
expect(ALLOWED_INPUT_REGEX.test(input)).to.be.false;
}
});
});
t.test("DOMAIN_FRIENDLY_INPUT_REGEX", async (t) => {
t.test("should allow valid inputs including domain names", async () => {
const validInputs = [
"Hello World",
"Test 123",
"O'Brien",
"Name-With-Dashes",
"Name_With_Underscores",
"Name (with parentheses)",
"Name+Plus",
"L'Hôpital",
"Café",
"Zürich",
// Domain names (NEW - these should now be allowed)
"example.com",
"my.domain",
"subdomain.example.com",
"test-site.io",
"My Workspace (example.com)",
"Production - api.company.com",
"",
];
for (const input of validInputs) {
expect(DOMAIN_FRIENDLY_INPUT_REGEX.test(input)).to.be.true;
}
});
t.test("should reject inputs with URL schemes and @ symbols", async () => {
const invalidInputs = [
"user@email.com",
"https://example.com",
"http://test.com",
"/path/to/file",
"ftp://server.com",
"mailto:user@example.com",
];
for (const input of invalidInputs) {
expect(DOMAIN_FRIENDLY_INPUT_REGEX.test(input)).to.be.false;
}
});
});
t.test("NAME_REGEX", async (t) => {
t.test("should allow valid names", async () => {
const validNames = [
"John Doe",
"O'Brien",
"Mary-Jane",
"José",
"François",
"Müller",
"",
];
for (const name of validNames) {
expect(NAME_REGEX.test(name)).to.be.true;
}
});
t.test(
"should reject names with special characters or domain-like patterns",
async () => {
const invalidNames = [
"john.doe",
"user@example",
"name_with_underscore",
"name+plus",
"example.com",
"(parentheses)",
];
for (const name of invalidNames) {
expect(NAME_REGEX.test(name)).to.be.false;
}
},
);
});
t.test("NICKNAME_REGEX", async (t) => {
t.test(
"should allow valid nicknames with extended character set",
async () => {
const validNicknames = [
"JohnDoe",
"John Doe",
"O'Brien",
"Mary-Jane",
"user_name", // underscore
"name+tag", // plus
"Smith & Jones", // ampersand
"john.doe", // period
"Player#1234", // hash
"Cool!", // exclamation
"Name (Admin)", // parentheses
"José", // accents
"François",
"Müller",
"user_name+tag", // combination
"Player#1_Pro!", // multiple special chars
"",
];
for (const nickname of validNicknames) {
expect(NICKNAME_REGEX.test(nickname)).to.be.true;
}
},
);
t.test("should reject nicknames with URL-enabling characters", async () => {
const invalidNicknames = [
"user@example.com", // @ symbol (email-like)
"http://example.com", // URL scheme
"https://test.com", // URL scheme
"/path/to/file", // forward slash
"user:password", // colon
"ftp://server.com", // URL scheme
];
for (const nickname of invalidNicknames) {
expect(NICKNAME_REGEX.test(nickname)).to.be.false;
}
});
t.test("should be more permissive than NAME_REGEX", async () => {
// These should pass NICKNAME_REGEX but fail NAME_REGEX
const nicknameOnlyValid = [
"user_name",
"name+tag",
"john.doe",
"Player#1234",
"Cool!",
"Name (Admin)",
"Smith & Jones",
];
for (const input of nicknameOnlyValid) {
expect(NICKNAME_REGEX.test(input)).to.be.true;
expect(NAME_REGEX.test(input)).to.be.false;
}
});
});
t.test("GITHUB_USERNAME_REGEX", async (t) => {
t.test("should allow valid GitHub usernames", async () => {
const validUsernames = [
"", // Empty string (optional field)
"john-doe",
"user123",
"a",
"test-user-123",
"User-Name",
"x".repeat(39), // Max length
];
for (const username of validUsernames) {
expect(GITHUB_USERNAME_REGEX.test(username)).to.be.true;
}
});
t.test("should reject invalid GitHub usernames", async () => {
const invalidUsernames = [
"-john", // Starts with hyphen
"john-", // Ends with hyphen
"john--doe", // Consecutive hyphens
"x".repeat(40), // Too long
"user@name", // Invalid character
];
for (const username of invalidUsernames) {
expect(GITHUB_USERNAME_REGEX.test(username)).to.be.false;
}
});
});
t.test("DISCORD_TAG_REGEX", async (t) => {
t.test("should allow valid Discord usernames", async () => {
const validTags = [
"", // Empty string (optional field)
"username",
"user.name",
"user_name",
"username#1234",
"cool.user",
"ab", // Min length
"a".repeat(32), // Max length
];
for (const tag of validTags) {
expect(DISCORD_TAG_REGEX.test(tag)).to.be.true;
}
});
t.test("should reject invalid Discord usernames", async () => {
const invalidTags = [
"discord", // Reserved word
"here", // Reserved word
"everyone", // Reserved word
"user..name", // Consecutive periods
"a", // Too short
"a".repeat(33), // Too long
"user#123", // Invalid discriminator (not 4 digits)
];
for (const tag of invalidTags) {
expect(DISCORD_TAG_REGEX.test(tag)).to.be.false;
}
});
});
t.test("URL_DETECTION_REGEX", async (t) => {
t.test("should detect URLs", async () => {
const urls = [
"https://example.com",
"http://test.com",
"www.example.com",
"example.com",
"test.io",
"subdomain.example.co.uk",
];
for (const url of urls) {
expect(URL_DETECTION_REGEX.test(url)).to.be.true;
}
});
t.test("should not detect non-URLs", async () => {
const nonUrls = [
"just some text",
"no url here",
"localhost",
"192.168.1.1",
];
for (const text of nonUrls) {
expect(URL_DETECTION_REGEX.test(text)).to.be.false;
}
});
});
t.test("Max Length Validations", async (t) => {
t.test(
"should enforce standard character limit for user profile fields",
async () => {
// User profile fields: firstName, lastName, nickname, githubUsername, discordUsername
const schemaStandard = z.string().max(MAX_LENGTH_STANDARD);
// Should pass with exactly MAX_LENGTH_STANDARD characters
const validStandard = "a".repeat(MAX_LENGTH_STANDARD);
expect(schemaStandard.safeParse(validStandard).success).to.be.true;
// Should fail with MAX_LENGTH_STANDARD + 1 characters
const invalidStandard = "a".repeat(MAX_LENGTH_STANDARD + 1);
expect(schemaStandard.safeParse(invalidStandard).success).to.be.false;
},
);
t.test(
"should enforce standard character limit for workspace display names",
async () => {
const schemaStandard = z.string().max(MAX_LENGTH_STANDARD);
// Should pass with exactly MAX_LENGTH_STANDARD characters
const validStandard = "a".repeat(MAX_LENGTH_STANDARD);
expect(schemaStandard.safeParse(validStandard).success).to.be.true;
// Should fail with MAX_LENGTH_STANDARD + 1 characters
const invalidStandard = "a".repeat(MAX_LENGTH_STANDARD + 1);
expect(schemaStandard.safeParse(invalidStandard).success).to.be.false;
},
);
t.test(
"should enforce extended character limit for workspace descriptions",
async () => {
const schemaExtended = z.string().max(MAX_LENGTH_EXTENDED);
// Should pass with exactly MAX_LENGTH_EXTENDED characters
const validExtended = "a".repeat(MAX_LENGTH_EXTENDED);
expect(schemaExtended.safeParse(validExtended).success).to.be.true;
// Should fail with MAX_LENGTH_EXTENDED + 1 characters
const invalidExtended = "a".repeat(MAX_LENGTH_EXTENDED + 1);
expect(schemaExtended.safeParse(invalidExtended).success).to.be.false;
},
);
t.test("should allow domain names within length limits", async () => {
// Workspace display name: MAX_LENGTH_STANDARD chars with domain
const displayNameSchema = z
.string()
.max(MAX_LENGTH_STANDARD)
.regex(DOMAIN_FRIENDLY_INPUT_REGEX);
const validDisplayName = "My Production Workspace - example.com";
expect(displayNameSchema.safeParse(validDisplayName).success).to.be.true;
// Workspace description: MAX_LENGTH_EXTENDED chars with domain
const descriptionSchema = z
.string()
.max(MAX_LENGTH_EXTENDED)
.regex(DOMAIN_FRIENDLY_INPUT_REGEX);
const validDescription = `This workspace manages api.example.com and handles all production traffic for our services. ${"a".repeat(
400,
)}`;
expect(descriptionSchema.safeParse(validDescription).success).to.be.true;
expect(validDescription.length).to.be.lessThan(MAX_LENGTH_EXTENDED);
});
});
});