diff --git a/server/skillhub-domain/pom.xml b/server/skillhub-domain/pom.xml index 32d91ae0..8c546ed5 100644 --- a/server/skillhub-domain/pom.xml +++ b/server/skillhub-domain/pom.xml @@ -15,5 +15,10 @@ jakarta.persistence jakarta.persistence-api + + org.junit.jupiter + junit-jupiter + test + diff --git a/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/namespace/SlugValidator.java b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/namespace/SlugValidator.java new file mode 100644 index 00000000..c166cd49 --- /dev/null +++ b/server/skillhub-domain/src/main/java/com/iflytek/skillhub/domain/namespace/SlugValidator.java @@ -0,0 +1,36 @@ +package com.iflytek.skillhub.domain.namespace; + +import java.util.Set; +import java.util.regex.Pattern; + +public class SlugValidator { + + private static final int MIN_LENGTH = 2; + private static final int MAX_LENGTH = 64; + private static final Pattern SLUG_PATTERN = Pattern.compile("^[a-z0-9]([a-z0-9-]*[a-z0-9])?$"); + private static final Set RESERVED_SLUGS = Set.of( + "admin", "api", "dashboard", "search", "auth", + "me", "global", "system", "static", "assets", "health" + ); + + public static void validate(String slug) { + if (slug == null || slug.isBlank()) { + throw new IllegalArgumentException("Slug cannot be null or blank"); + } + if (slug.length() < MIN_LENGTH || slug.length() > MAX_LENGTH) { + throw new IllegalArgumentException( + String.format("Slug length must be between %d and %d characters", MIN_LENGTH, MAX_LENGTH)); + } + if (!SLUG_PATTERN.matcher(slug).matches()) { + throw new IllegalArgumentException( + "Slug must contain only lowercase alphanumeric characters and hyphens, " + + "and must start and end with an alphanumeric character"); + } + if (slug.contains("--")) { + throw new IllegalArgumentException("Slug cannot contain consecutive hyphens"); + } + if (RESERVED_SLUGS.contains(slug)) { + throw new IllegalArgumentException("Slug '" + slug + "' is reserved and cannot be used"); + } + } +} diff --git a/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/namespace/SlugValidatorTest.java b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/namespace/SlugValidatorTest.java new file mode 100644 index 00000000..1caf9d39 --- /dev/null +++ b/server/skillhub-domain/src/test/java/com/iflytek/skillhub/domain/namespace/SlugValidatorTest.java @@ -0,0 +1,57 @@ +package com.iflytek.skillhub.domain.namespace; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class SlugValidatorTest { + @Test + void shouldAcceptValidSlug() { + assertDoesNotThrow(() -> SlugValidator.validate("my-namespace")); + assertDoesNotThrow(() -> SlugValidator.validate("ab")); + assertDoesNotThrow(() -> SlugValidator.validate("test123")); + assertDoesNotThrow(() -> SlugValidator.validate("my-team-2024")); + } + @Test + void shouldRejectTooShort() { + Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("a")); + assertTrue(ex.getMessage().contains("length")); + } + @Test + void shouldRejectTooLong() { + String longSlug = "a".repeat(65); + Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate(longSlug)); + assertTrue(ex.getMessage().contains("length")); + } + @Test + void shouldRejectUpperCase() { + Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("MyNamespace")); + assertTrue(ex.getMessage().contains("lowercase")); + } + @Test + void shouldRejectStartingWithHyphen() { + Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("-namespace")); + assertTrue(ex.getMessage().contains("alphanumeric")); + } + @Test + void shouldRejectEndingWithHyphen() { + Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("namespace-")); + assertTrue(ex.getMessage().contains("alphanumeric")); + } + @Test + void shouldRejectDoubleHyphen() { + Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("my--namespace")); + assertTrue(ex.getMessage().contains("consecutive")); + } + @Test + void shouldRejectReservedWords() { + assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("admin")); + assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("api")); + assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("global")); + assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("system")); + } + @Test + void shouldRejectSpecialCharacters() { + Exception ex = assertThrows(IllegalArgumentException.class, () -> SlugValidator.validate("my_namespace")); + assertTrue(ex.getMessage().contains("lowercase") || ex.getMessage().contains("alphanumeric")); + } +}