From 7549c787b97a99e1f104633cecdf63bc3ecba2e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Tue, 30 Sep 2025 12:15:49 -0300 Subject: [PATCH 1/3] Add ban-word metadata and validate character names against it --- Maple2.Database/Context/MetadataContext.cs | 8 + .../Storage/Metadata/BanWordStorage.cs | 40 +++++ Maple2.File.Ingest/Maple2.File.Ingest.csproj | 2 +- Maple2.File.Ingest/Mapper/BanWordMapper.cs | 32 ++++ Maple2.File.Ingest/Program.cs | 1 + Maple2.Model/Metadata/BanWordMetadata.cs | 3 + .../Validators/CharacterNameValidator.cs | 48 +++++ Maple2.Server.Core/Modules/DataDbModule.cs | 1 + .../CheckCharacterNameHandler.cs | 15 +- .../CharacterManagementHandler.cs | 15 +- .../Validators/CharacterNameValidatorTests.cs | 165 ++++++++++++++++++ 11 files changed, 319 insertions(+), 11 deletions(-) create mode 100644 Maple2.Database/Storage/Metadata/BanWordStorage.cs create mode 100644 Maple2.File.Ingest/Mapper/BanWordMapper.cs create mode 100644 Maple2.Model/Metadata/BanWordMetadata.cs create mode 100644 Maple2.Model/Validators/CharacterNameValidator.cs create mode 100644 Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs diff --git a/Maple2.Database/Context/MetadataContext.cs b/Maple2.Database/Context/MetadataContext.cs index 528561097..4d2772c3f 100644 --- a/Maple2.Database/Context/MetadataContext.cs +++ b/Maple2.Database/Context/MetadataContext.cs @@ -31,6 +31,7 @@ public sealed class MetadataContext(DbContextOptions options) : DbContext(option public DbSet FunctionCubeMetadata { get; set; } = null!; public DbSet MapDataMetadata { get; set; } = null!; public DbSet TriggerMetadata { get; set; } = null!; + public DbSet BanWordMetadata { get; set; } = null!; protected override void OnModelCreating(ModelBuilder modelBuilder) { base.OnModelCreating(modelBuilder); @@ -58,6 +59,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity(ConfigureNXSMeshMetadata); modelBuilder.Entity(ConfigureFunctionCubeMetadata); modelBuilder.Entity(ConfigureTriggerMetadata); + modelBuilder.Entity(BanWordMetadataConfigure); } private static void ConfigureAdditionalEffectMetadata(EntityTypeBuilder builder) { @@ -260,4 +262,10 @@ private static void ConfigureTriggerMetadata(EntityTypeBuilder trigger.Name, }); } + + private static void BanWordMetadataConfigure(EntityTypeBuilder builder) { + builder.ToTable("ban-word"); + builder.Property(banWord => banWord.Id).ValueGeneratedOnAdd(); + builder.HasKey(banWord => banWord.Id); + } } diff --git a/Maple2.Database/Storage/Metadata/BanWordStorage.cs b/Maple2.Database/Storage/Metadata/BanWordStorage.cs new file mode 100644 index 000000000..8b8ce6cb7 --- /dev/null +++ b/Maple2.Database/Storage/Metadata/BanWordStorage.cs @@ -0,0 +1,40 @@ +using System.Diagnostics.CodeAnalysis; +using Maple2.Database.Context; +using Microsoft.EntityFrameworkCore; + +namespace Maple2.Database.Storage; + +public class BanWordStorage(MetadataContext context) { + private HashSet banWords = new(StringComparer.CurrentCultureIgnoreCase); + private HashSet ugcBanWords = new(StringComparer.CurrentCultureIgnoreCase); + + public bool ContainsBannedWord(string word) { + if (banWords.Count == 0) { + lock (context) { + if (banWords.Count == 0) { + banWords = context.BanWordMetadata + .AsNoTracking() + .Where(bw => !bw.Ugc) + .Select(bw => bw.Value) + .ToHashSet(StringComparer.CurrentCultureIgnoreCase); + } + } + } + return banWords.Any(bw => word.Contains(bw, StringComparison.CurrentCultureIgnoreCase)); + } + + public bool ContainsUgcBannedWord(string word) { + if (ugcBanWords.Count == 0) { + lock (context) { + if (ugcBanWords.Count == 0) { + ugcBanWords = context.BanWordMetadata + .AsNoTracking() + .Where(bw => bw.Ugc) + .Select(bw => bw.Value) + .ToHashSet(StringComparer.CurrentCultureIgnoreCase); + } + } + } + return ugcBanWords.Any(bw => word.Contains(bw, StringComparison.CurrentCultureIgnoreCase)); + } +} diff --git a/Maple2.File.Ingest/Maple2.File.Ingest.csproj b/Maple2.File.Ingest/Maple2.File.Ingest.csproj index fe9431516..833aa2fef 100644 --- a/Maple2.File.Ingest/Maple2.File.Ingest.csproj +++ b/Maple2.File.Ingest/Maple2.File.Ingest.csproj @@ -19,7 +19,7 @@ - + diff --git a/Maple2.File.Ingest/Mapper/BanWordMapper.cs b/Maple2.File.Ingest/Mapper/BanWordMapper.cs new file mode 100644 index 000000000..e330a55b6 --- /dev/null +++ b/Maple2.File.Ingest/Mapper/BanWordMapper.cs @@ -0,0 +1,32 @@ +using Maple2.File.IO; +using Maple2.File.Parser; +using Maple2.Model.Metadata; + +namespace Maple2.File.Ingest.Mapper; + +public class BanWordMapper : TypeMapper { + private readonly BanWordParser parser; + + public BanWordMapper(M2dReader xmlReader) { + parser = new BanWordParser(xmlReader); + } + + protected override IEnumerable Map() { + var hashSet = new HashSet(StringComparer.CurrentCultureIgnoreCase); + foreach ((int Id, string Name) word in parser.ParseBanWords()) { + if (hashSet.Add(word.Name)) { + yield return new BanWordMetadata( + word.Id, word.Name, false + ); + } + } + + foreach ((int Id, string Name) word in parser.ParseUgcBanWords()) { + if (hashSet.Add(word.Name)) { + yield return new BanWordMetadata( + word.Id, word.Name, true + ); + } + } + } +} diff --git a/Maple2.File.Ingest/Program.cs b/Maple2.File.Ingest/Program.cs index a6886c9b2..b9e450fda 100644 --- a/Maple2.File.Ingest/Program.cs +++ b/Maple2.File.Ingest/Program.cs @@ -214,6 +214,7 @@ UpdateDatabase(metadataContext, new TableMapper(xmlReader, language)); UpdateDatabase(metadataContext, new AchievementMapper(xmlReader)); UpdateDatabase(metadataContext, new FunctionCubeMapper(xmlReader)); +UpdateDatabase(metadataContext, new BanWordMapper(xmlReader)); NifParserHelper.ParseNif(modelReaders); diff --git a/Maple2.Model/Metadata/BanWordMetadata.cs b/Maple2.Model/Metadata/BanWordMetadata.cs new file mode 100644 index 000000000..b1804a8e2 --- /dev/null +++ b/Maple2.Model/Metadata/BanWordMetadata.cs @@ -0,0 +1,3 @@ +namespace Maple2.Model.Metadata; + +public record BanWordMetadata(long Id, string Value, bool Ugc); diff --git a/Maple2.Model/Validators/CharacterNameValidator.cs b/Maple2.Model/Validators/CharacterNameValidator.cs new file mode 100644 index 000000000..2fecb8559 --- /dev/null +++ b/Maple2.Model/Validators/CharacterNameValidator.cs @@ -0,0 +1,48 @@ +using System.Text.RegularExpressions; +using Maple2.Model.Error; +using Maple2.Model.Metadata; + +namespace Maple2.Model.Validators; + +public static partial class CharacterNameValidator { + // Regex pattern for valid character names (Unicode letters, numbers, and some special characters, no spaces) + private static readonly Regex ValidNamePattern = NamePatternRegex(); + + /// + /// Validates a character name according to all rules. + /// + /// The character name to validate + /// CharacterCreateError code if invalid, null if valid + public static CharacterCreateError? ValidateName(string name) { + if (string.IsNullOrWhiteSpace(name)) { + return CharacterCreateError.s_char_err_name; + } + + // Use the original name for validation + string validatedName = name; + + // Check length constraints + if (validatedName.Length < Constant.CharacterNameLengthMin) { + return CharacterCreateError.s_char_err_name; + } + + if (validatedName.Length > Constant.CharacterNameLengthMax) { + return CharacterCreateError.s_char_err_system; + } + + // Check character pattern (Unicode letters, numbers, hyphens, underscores only, no spaces) + if (!ValidNamePattern.IsMatch(validatedName)) { + return CharacterCreateError.s_char_err_ban_all; + } + + // Check for names that are only whitespace/special characters + if (validatedName.All(c => !char.IsLetterOrDigit(c))) { + return CharacterCreateError.s_char_err_name; + } + + return null; // Valid name + } + + [GeneratedRegex(@"^[\p{L}0-9\-_]+$", RegexOptions.Compiled)] + private static partial Regex NamePatternRegex(); +} diff --git a/Maple2.Server.Core/Modules/DataDbModule.cs b/Maple2.Server.Core/Modules/DataDbModule.cs index fa84701d1..4278fbff6 100644 --- a/Maple2.Server.Core/Modules/DataDbModule.cs +++ b/Maple2.Server.Core/Modules/DataDbModule.cs @@ -54,5 +54,6 @@ protected override void Load(ContainerBuilder builder) { builder.RegisterType().SingleInstance(); builder.RegisterType().SingleInstance(); builder.RegisterType().SingleInstance(); + builder.RegisterType().SingleInstance(); } } diff --git a/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs b/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs index 87b449e6e..f6bd8d2b3 100644 --- a/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs @@ -1,5 +1,7 @@ using Maple2.Database.Storage; +using Maple2.Model.Error; using Maple2.Model.Metadata; +using Maple2.Model.Validators; using Maple2.PacketLib.Tools; using Maple2.Server.Core.Constants; using Maple2.Server.Game.PacketHandlers.Field; @@ -16,6 +18,7 @@ public class CheckCharacterNameHandler : FieldPacketHandler { #region Autofac Autowired // ReSharper disable MemberCanBePrivate.Global public required GameStorage GameStorage { private get; init; } + public required BanWordStorage BanWordStorage { private get; init; } // ReSharper restore All #endregion @@ -23,18 +26,20 @@ public override void Handle(GameSession session, IByteReader packet) { string characterName = packet.ReadUnicodeString(); long itemUid = packet.ReadLong(); - if (characterName.Length < Constant.CharacterNameLengthMin) { - session.Send(CharacterListPacket.CreateError(s_char_err_name)); + if (BanWordStorage.ContainsBannedWord(characterName)) { + session.Send(CharacterListPacket.CreateError(s_char_err_ban_any)); return; } - if (characterName.Length > Constant.CharacterNameLengthMax) { - session.Send(CharacterListPacket.CreateError(s_char_err_system)); + // Validate character name + CharacterCreateError? nameError = CharacterNameValidator.ValidateName(characterName); + if (nameError != null) { + session.Send(CharacterListPacket.CreateError(nameError.Value)); return; } using GameStorage.Request db = GameStorage.Context(); long existingId = db.GetCharacterId(characterName); - session.Send(CheckCharacterNamePacket.Result(existingId != default, characterName, itemUid)); + session.Send(CheckCharacterNamePacket.Result(existingId != 0, characterName, itemUid)); } } diff --git a/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs b/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs index a57530bc3..4cc93f008 100644 --- a/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs +++ b/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs @@ -7,8 +7,10 @@ using Maple2.Database.Storage; using Maple2.Model.Common; using Maple2.Model.Enum; +using Maple2.Model.Error; using Maple2.Model.Game; using Maple2.Model.Metadata; +using Maple2.Model.Validators; using Maple2.PacketLib.Tools; using Maple2.Server.Core.Constants; using Maple2.Server.Core.PacketHandlers; @@ -41,6 +43,7 @@ private enum Command : byte { // ReSharper disable MemberCanBePrivate.Global public required WorldClient World { private get; init; } public required GameStorage GameStorage { private get; init; } + public required BanWordStorage BanWordStorage { private get; init; } public required ItemMetadataStorage ItemMetadata { private get; init; } public required TableMetadataStorage TableMetadata { private get; init; } // ReSharper restore All @@ -113,19 +116,21 @@ private void HandleCreate(LoginSession session, IByteReader packet) { var job = (Job) ((int) jobCode * 10); string name = packet.ReadUnicodeString(); - if (name.Length < Constant.CharacterNameLengthMin) { - session.Send(CharacterListPacket.CreateError(s_char_err_name)); + if (BanWordStorage.ContainsBannedWord(name)) { + session.Send(CharacterListPacket.CreateError(s_char_err_ban_any)); return; } - if (name.Length > Constant.CharacterNameLengthMax) { - session.Send(CharacterListPacket.CreateError(s_char_err_system)); + // Validate character name + CharacterCreateError? nameError = CharacterNameValidator.ValidateName(name); + if (nameError != null) { + session.Send(CharacterListPacket.CreateError(nameError.Value)); return; } using GameStorage.Request db = GameStorage.Context(); long existingId = db.GetCharacterId(name); - if (existingId != default) { + if (existingId != 0) { session.Send(CharacterListPacket.CreateError(s_char_err_already_taken)); return; } diff --git a/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs b/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs new file mode 100644 index 000000000..12a7b25d5 --- /dev/null +++ b/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs @@ -0,0 +1,165 @@ +using Maple2.Model.Error; +using Maple2.Model.Validators; + +namespace Maple2.Server.Tests.Validators; + +public class CharacterNameValidatorTests { + [Test] + public void ValidName_ShouldReturnNull() { + // Valid names should return null (no error) + Assert.That(CharacterNameValidator.ValidateName("ValidName"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("Test123"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("User_Name"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("Cool-Name"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("ab"), Is.Null); // minimum length + Assert.That(CharacterNameValidator.ValidateName("abcdefghijkl"), Is.Null); // maximum length + } + + [Test] + public void TooShortName_ShouldReturnNameError() { + // Names shorter than minimum should return name error + Assert.That(CharacterNameValidator.ValidateName("a"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(""), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(" "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void TooLongName_ShouldReturnSystemError() { + // Names longer than maximum should return system error + Assert.That(CharacterNameValidator.ValidateName("abcdefghijklm"), Is.EqualTo(CharacterCreateError.s_char_err_system)); + Assert.That(CharacterNameValidator.ValidateName("ThisNameIsTooLong"), Is.EqualTo(CharacterCreateError.s_char_err_system)); + } + + [Test] + public void InvalidCharacters_ShouldReturnNameError() { + // Names with invalid characters should return name error + Assert.That(CharacterNameValidator.ValidateName("test@name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("name#test"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("test$name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("test%name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("test*name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void OnlySpecialCharacters_ShouldReturnNameError() { + // Names with only special characters should return name error + Assert.That(CharacterNameValidator.ValidateName("--"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("__"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(" "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("-_-"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void NullOrWhitespace_ShouldReturnNameError() { + // Null or whitespace names should return name error + Assert.That(CharacterNameValidator.ValidateName(null!), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(""), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(" "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void SpaceValidation_ShouldWork() { + // Names with spaces should be invalid + Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Cool Player"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("a b"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + + // Names with leading or trailing spaces should be invalid + Assert.That(CharacterNameValidator.ValidateName(" ValidName"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("ValidName "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(" ValidName "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + + // Names with multiple consecutive spaces should be invalid + Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("A B"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void JapaneseCharacterNames_ShouldBeValid() { + // Japanese Hiragana characters should be valid + Assert.That(CharacterNameValidator.ValidateName("さくら"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("ひろし"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("あいうえお"), Is.Null); + + // Japanese Katakana characters should be valid + Assert.That(CharacterNameValidator.ValidateName("サクラ"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("ヒロシ"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("アイウエオ"), Is.Null); + + // Japanese Kanji characters should be valid + Assert.That(CharacterNameValidator.ValidateName("田中"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("山田"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("佐藤"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("鈴木"), Is.Null); + + // Mixed Japanese characters should be valid + Assert.That(CharacterNameValidator.ValidateName("さくら123"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("田中ひろし"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("サクラ_田中"), Is.Null); + } + + [Test] + public void KoreanCharacterNames_ShouldBeValid() { + // Korean Hangul characters should be valid + Assert.That(CharacterNameValidator.ValidateName("김철수"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("이영희"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("박민수"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("정수진"), Is.Null); + + // Korean with numbers should be valid + Assert.That(CharacterNameValidator.ValidateName("김철수123"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("이영희_456"), Is.Null); + + // Korean with allowed special characters should be valid + Assert.That(CharacterNameValidator.ValidateName("박민수-정"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("정수진_김"), Is.Null); + } + + [Test] + public void ChineseCharacterNames_ShouldBeValid() { + // Simplified Chinese characters should be valid + Assert.That(CharacterNameValidator.ValidateName("王小明"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("李小红"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("张三"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("刘德华"), Is.Null); + + // Traditional Chinese characters should be valid + Assert.That(CharacterNameValidator.ValidateName("王小明"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("陳大文"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("黃志強"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("林美玲"), Is.Null); + + // Chinese with numbers should be valid + Assert.That(CharacterNameValidator.ValidateName("王小明123"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("李小红_456"), Is.Null); + + // Chinese with allowed special characters should be valid + Assert.That(CharacterNameValidator.ValidateName("张三-李四"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("刘德华_陈"), Is.Null); + } + + [Test] + public void SpecialCharacters_ShouldReturnNameError() { + // Names with special or non-ASCII symbols should return name error + Assert.That(CharacterNameValidator.ValidateName("Name★"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Name."), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Name!"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Name<3"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Name♪"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Name~"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Name*"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Name♥"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + } + + [Test] + public void NamesWithAccents_ShouldReturnNull() { + // Names with accented Latin characters should be valid + Assert.That(CharacterNameValidator.ValidateName("José"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("Renée"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("Beyoncé"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("André"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("Zoë"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("François"), Is.Null); + } +} From 7a88daf4d16b4d05913fae5df2962847a0dba64f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Tue, 30 Sep 2025 16:34:40 -0300 Subject: [PATCH 2/3] add guilds and clubs --- Maple2.Model/Metadata/Constants.cs | 2 + .../Validators/CharacterNameValidator.cs | 25 +++-- Maple2.Model/Validators/ClubNameValidator.cs | 39 +++++++ Maple2.Model/Validators/GuildNameValidator.cs | 39 +++++++ .../PacketHandlers/ClubHandler.cs | 24 +++++ .../PacketHandlers/GuildHandler.cs | 14 ++- .../Validators/CharacterNameValidatorTests.cs | 102 +++++++++++------- .../Validators/ClubNameValidatorTests.cs | 62 +++++++++++ .../Validators/GuildNameValidatorTests.cs | 62 +++++++++++ 9 files changed, 319 insertions(+), 50 deletions(-) create mode 100644 Maple2.Model/Validators/ClubNameValidator.cs create mode 100644 Maple2.Model/Validators/GuildNameValidator.cs create mode 100644 Maple2.Server.Tests/Validators/ClubNameValidatorTests.cs create mode 100644 Maple2.Server.Tests/Validators/GuildNameValidatorTests.cs diff --git a/Maple2.Model/Metadata/Constants.cs b/Maple2.Model/Metadata/Constants.cs index 37372a07e..df28a2d2b 100644 --- a/Maple2.Model/Metadata/Constants.cs +++ b/Maple2.Model/Metadata/Constants.cs @@ -119,6 +119,8 @@ public static class Constant { public const bool DebugTriggers = false; // Set to true to enable debug triggers. (It'll write triggers to files and load triggers from files instead of DB) + public const bool AllowUnicodeInNames = false; // Allow Unicode characters in character and guild names + public static IReadOnlyDictionary ContentRewards { get; } = new Dictionary { {"miniGame", 1005}, {"dungeonHelper", 1006}, diff --git a/Maple2.Model/Validators/CharacterNameValidator.cs b/Maple2.Model/Validators/CharacterNameValidator.cs index 2fecb8559..a54d58118 100644 --- a/Maple2.Model/Validators/CharacterNameValidator.cs +++ b/Maple2.Model/Validators/CharacterNameValidator.cs @@ -5,8 +5,9 @@ namespace Maple2.Model.Validators; public static partial class CharacterNameValidator { - // Regex pattern for valid character names (Unicode letters, numbers, and some special characters, no spaces) - private static readonly Regex ValidNamePattern = NamePatternRegex(); + // Regex patterns for valid character names + private static readonly Regex ValidNamePatternAscii = NamePatternAsciiRegex(); + private static readonly Regex ValidNamePatternUnicode = NamePatternUnicodeRegex(); /// /// Validates a character name according to all rules. @@ -18,24 +19,23 @@ public static partial class CharacterNameValidator { return CharacterCreateError.s_char_err_name; } - // Use the original name for validation string validatedName = name; // Check length constraints - if (validatedName.Length < Constant.CharacterNameLengthMin) { + if (validatedName.Length is < Constant.CharacterNameLengthMin) { return CharacterCreateError.s_char_err_name; } - if (validatedName.Length > Constant.CharacterNameLengthMax) { return CharacterCreateError.s_char_err_system; } - // Check character pattern (Unicode letters, numbers, hyphens, underscores only, no spaces) - if (!ValidNamePattern.IsMatch(validatedName)) { + // Select pattern + Regex pattern = Constant.AllowUnicodeInNames ? ValidNamePatternUnicode : ValidNamePatternAscii; + if (!pattern.IsMatch(validatedName)) { return CharacterCreateError.s_char_err_ban_all; } - // Check for names that are only whitespace/special characters + // Check for names that are only special characters if (validatedName.All(c => !char.IsLetterOrDigit(c))) { return CharacterCreateError.s_char_err_name; } @@ -43,6 +43,11 @@ public static partial class CharacterNameValidator { return null; // Valid name } - [GeneratedRegex(@"^[\p{L}0-9\-_]+$", RegexOptions.Compiled)] - private static partial Regex NamePatternRegex(); + // ASCII only: A-Z, a-z, 0-9 (no dash, no underscore) + [GeneratedRegex(@"^[A-Za-z0-9]+$", RegexOptions.Compiled)] + private static partial Regex NamePatternAsciiRegex(); + + // Unicode: \p{L} (all letters, including accents), 0-9 (no dash, no underscore) + [GeneratedRegex(@"^[\p{L}0-9]+$", RegexOptions.Compiled)] + private static partial Regex NamePatternUnicodeRegex(); } diff --git a/Maple2.Model/Validators/ClubNameValidator.cs b/Maple2.Model/Validators/ClubNameValidator.cs new file mode 100644 index 000000000..1b35ab511 --- /dev/null +++ b/Maple2.Model/Validators/ClubNameValidator.cs @@ -0,0 +1,39 @@ +using System.Text.RegularExpressions; +using Maple2.Model.Error; +using Maple2.Model.Metadata; + +namespace Maple2.Model.Validators; + +public static partial class ClubNameValidator { + private static readonly Regex ValidNamePatternAscii = NamePatternAsciiRegex(); + private static readonly Regex ValidNamePatternUnicode = NamePatternUnicodeRegex(); + + /// + /// Validates a club name according to all rules. + /// + /// The club name to validate + /// ClubError code if invalid, null if valid + public static ClubError? ValidateName(string name) { + if (string.IsNullOrWhiteSpace(name)) { + return ClubError.s_club_err_name_value; + } + if (name.Length is < Constant.ClubNameLengthMin or > Constant.ClubNameLengthMax) { + return ClubError.s_club_err_name_value; + } + Regex pattern = Constant.AllowUnicodeInNames ? ValidNamePatternUnicode : ValidNamePatternAscii; + if (!pattern.IsMatch(name)) { + return ClubError.s_club_err_name_value; + } + if (name.All(c => !char.IsLetterOrDigit(c))) { + return ClubError.s_club_err_name_value; + } + return null; // Valid name + } + + // ASCII only: A-Z, a-z, 0-9 (no dash, no underscore) + [GeneratedRegex(@"^[A-Za-z0-9]+$", RegexOptions.Compiled)] + private static partial Regex NamePatternAsciiRegex(); + // Unicode: \p{L} (all letters, including accents/CJK), 0-9 (no dash, no underscore) + [GeneratedRegex(@"^[\p{L}0-9]+$", RegexOptions.Compiled)] + private static partial Regex NamePatternUnicodeRegex(); +} diff --git a/Maple2.Model/Validators/GuildNameValidator.cs b/Maple2.Model/Validators/GuildNameValidator.cs new file mode 100644 index 000000000..05c49e062 --- /dev/null +++ b/Maple2.Model/Validators/GuildNameValidator.cs @@ -0,0 +1,39 @@ +using System.Text.RegularExpressions; +using Maple2.Model.Error; +using Maple2.Model.Metadata; + +namespace Maple2.Model.Validators; + +public static partial class GuildNameValidator { + private static readonly Regex ValidNamePatternAscii = NamePatternAsciiRegex(); + private static readonly Regex ValidNamePatternUnicode = NamePatternUnicodeRegex(); + + /// + /// Validates a guild name according to all rules. + /// + /// The guild name to validate + /// GuildError code if invalid, null if valid + public static GuildError? ValidateName(string name) { + if (string.IsNullOrWhiteSpace(name)) { + return GuildError.s_guild_err_name_value; + } + if (name.Length is < Constant.GuildNameLengthMin or > Constant.GuildNameLengthMax) { + return GuildError.s_guild_err_name_value; + } + Regex pattern = Constant.AllowUnicodeInNames ? ValidNamePatternUnicode : ValidNamePatternAscii; + if (!pattern.IsMatch(name)) { + return GuildError.s_guild_err_name_value; + } + if (name.All(c => !char.IsLetterOrDigit(c))) { + return GuildError.s_guild_err_name_value; + } + return null; // Valid name + } + + // ASCII only: A-Z, a-z, 0-9 (no dash, no underscore) + [GeneratedRegex(@"^[A-Za-z0-9]+$", RegexOptions.Compiled)] + private static partial Regex NamePatternAsciiRegex(); + // Unicode: \p{L} (all letters, including accents/CJK), 0-9 (no dash, no underscore) + [GeneratedRegex(@"^[\p{L}0-9]+$", RegexOptions.Compiled)] + private static partial Regex NamePatternUnicodeRegex(); +} diff --git a/Maple2.Server.Game/PacketHandlers/ClubHandler.cs b/Maple2.Server.Game/PacketHandlers/ClubHandler.cs index b34dfe75d..03f266d5e 100644 --- a/Maple2.Server.Game/PacketHandlers/ClubHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/ClubHandler.cs @@ -4,6 +4,7 @@ using Maple2.Model.Game.Club; using Maple2.Model.Game.Party; using Maple2.Model.Metadata; +using Maple2.Model.Validators; using Maple2.PacketLib.Tools; using Maple2.Server.Core.Constants; using Maple2.Server.Game.PacketHandlers.Field; @@ -32,6 +33,7 @@ private enum Command : byte { #region Autofac Autowired // ReSharper disable MemberCanBePrivate.Global public required WorldClient World { private get; init; } + public required BanWordStorage BanWordStorage { private get; init; } // ReSharper restore All #endregion @@ -65,6 +67,17 @@ public override void Handle(GameSession session, IByteReader packet) { private void HandleCreate(GameSession session, IByteReader packet) { string clubName = packet.ReadUnicodeString(); + if (BanWordStorage.ContainsBannedWord(clubName)) { + session.Send(ClubPacket.Error(ClubError.s_club_err_name_value)); + return; + } + + ClubError? result = ClubNameValidator.ValidateName(clubName); + if (result is not null) { + session.Send(ClubPacket.Error(result.Value)); + return; + } + // Grabbing party. Clubs can only be created by party leaders. Party? party = session.Party.Party; if (party is null || party.LeaderCharacterId != session.Player.Value.Character.Id) { @@ -218,6 +231,17 @@ private void HandleRename(GameSession session, IByteReader packet) { long clubId = packet.ReadLong(); string newName = packet.ReadUnicodeString(); + if (BanWordStorage.ContainsBannedWord(newName)) { + session.Send(ClubPacket.Error(ClubError.s_club_err_name_value)); + return; + } + + ClubError? result = ClubNameValidator.ValidateName(newName); + if (result is not null) { + session.Send(ClubPacket.Error(result.Value)); + return; + } + if (!session.Clubs.ContainsKey(clubId)) { return; } diff --git a/Maple2.Server.Game/PacketHandlers/GuildHandler.cs b/Maple2.Server.Game/PacketHandlers/GuildHandler.cs index 9a7a504ea..33c83e4ec 100644 --- a/Maple2.Server.Game/PacketHandlers/GuildHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/GuildHandler.cs @@ -4,6 +4,7 @@ using Maple2.Model.Error; using Maple2.Model.Game; using Maple2.Model.Metadata; +using Maple2.Model.Validators; using Maple2.PacketLib.Tools; using Maple2.Server.Core.Constants; using Maple2.Server.Core.PacketHandlers; @@ -63,6 +64,7 @@ private enum Command : byte { // ReSharper disable MemberCanBePrivate.Global public required WorldClient World { private get; init; } public required TableMetadataStorage TableMetadata { private get; init; } + public required BanWordStorage BanWordStorage { private get; init; } // ReSharper restore All #endregion @@ -186,10 +188,18 @@ private void HandleCreate(GameSession session, IByteReader packet) { if (session.Guild.Guild != null) { return; // Already in a guild. } - if (guildName.Length is < Constant.GuildNameLengthMin or > Constant.GuildNameLengthMax) { - session.Send(GuildPacket.Error(GuildError.s_guild_err_name_value)); // temp + + if (BanWordStorage.ContainsBannedWord(guildName)) { + session.Send(GuildPacket.Error(GuildError.s_guild_err_name_value)); return; } + + GuildError? result = GuildNameValidator.ValidateName(guildName); + if (result is not null) { + session.Send(GuildPacket.Error(result.Value)); + return; + } + if (session.Player.Value.Character.Level < Constant.GuildCreateMinLevel) { session.Send(GuildPacket.Error(GuildError.s_guild_err_not_enough_level)); return; diff --git a/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs b/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs index 12a7b25d5..e3321dbcb 100644 --- a/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs +++ b/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs @@ -1,4 +1,5 @@ using Maple2.Model.Error; +using Maple2.Model.Metadata; using Maple2.Model.Validators; namespace Maple2.Server.Tests.Validators; @@ -9,8 +10,7 @@ public void ValidName_ShouldReturnNull() { // Valid names should return null (no error) Assert.That(CharacterNameValidator.ValidateName("ValidName"), Is.Null); Assert.That(CharacterNameValidator.ValidateName("Test123"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("User_Name"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("Cool-Name"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("ab"), Is.Null); // minimum length Assert.That(CharacterNameValidator.ValidateName("abcdefghijkl"), Is.Null); // maximum length } @@ -33,20 +33,24 @@ public void TooLongName_ShouldReturnSystemError() { [Test] public void InvalidCharacters_ShouldReturnNameError() { // Names with invalid characters should return name error - Assert.That(CharacterNameValidator.ValidateName("test@name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("name#test"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("test$name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("test%name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("test*name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("test@name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("name#test"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("test$name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("test%name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("test*name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + + // Dashes and underscores are not allowed + Assert.That(CharacterNameValidator.ValidateName("User_Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Cool-Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); } [Test] public void OnlySpecialCharacters_ShouldReturnNameError() { // Names with only special characters should return name error - Assert.That(CharacterNameValidator.ValidateName("--"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("__"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("--"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("__"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); Assert.That(CharacterNameValidator.ValidateName(" "), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("-_-"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("-_-"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); } [Test] @@ -60,23 +64,26 @@ public void NullOrWhitespace_ShouldReturnNameError() { [Test] public void SpaceValidation_ShouldWork() { // Names with spaces should be invalid - Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("Cool Player"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("a b"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Cool Player"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("a b"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); // Names with leading or trailing spaces should be invalid - Assert.That(CharacterNameValidator.ValidateName(" ValidName"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("ValidName "), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName(" ValidName "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName(" ValidName"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("ValidName "), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName(" ValidName "), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); // Names with multiple consecutive spaces should be invalid - Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("A B"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("A B"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); } [Test] public void JapaneseCharacterNames_ShouldBeValid() { + if (!Constant.AllowUnicodeInNames) { + return; + } // Japanese Hiragana characters should be valid Assert.That(CharacterNameValidator.ValidateName("さくら"), Is.Null); Assert.That(CharacterNameValidator.ValidateName("ひろし"), Is.Null); @@ -96,11 +103,15 @@ public void JapaneseCharacterNames_ShouldBeValid() { // Mixed Japanese characters should be valid Assert.That(CharacterNameValidator.ValidateName("さくら123"), Is.Null); Assert.That(CharacterNameValidator.ValidateName("田中ひろし"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("サクラ_田中"), Is.Null); + // Mixed with dashes/underscores should be invalid + Assert.That(CharacterNameValidator.ValidateName("サクラ_田中"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); } [Test] public void KoreanCharacterNames_ShouldBeValid() { + if (!Constant.AllowUnicodeInNames) { + return; + } // Korean Hangul characters should be valid Assert.That(CharacterNameValidator.ValidateName("김철수"), Is.Null); Assert.That(CharacterNameValidator.ValidateName("이영희"), Is.Null); @@ -109,15 +120,18 @@ public void KoreanCharacterNames_ShouldBeValid() { // Korean with numbers should be valid Assert.That(CharacterNameValidator.ValidateName("김철수123"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("이영희_456"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("이영희456"), Is.Null); - // Korean with allowed special characters should be valid - Assert.That(CharacterNameValidator.ValidateName("박민수-정"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("정수진_김"), Is.Null); + // Korean with dashes/underscores should be invalid + Assert.That(CharacterNameValidator.ValidateName("박민수-정"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("정수진_김"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); } [Test] public void ChineseCharacterNames_ShouldBeValid() { + if (!Constant.AllowUnicodeInNames) { + return; + } // Simplified Chinese characters should be valid Assert.That(CharacterNameValidator.ValidateName("王小明"), Is.Null); Assert.That(CharacterNameValidator.ValidateName("李小红"), Is.Null); @@ -132,29 +146,41 @@ public void ChineseCharacterNames_ShouldBeValid() { // Chinese with numbers should be valid Assert.That(CharacterNameValidator.ValidateName("王小明123"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("李小红_456"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("李小红456"), Is.Null); - // Chinese with allowed special characters should be valid - Assert.That(CharacterNameValidator.ValidateName("张三-李四"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("刘德华_陈"), Is.Null); + // Chinese with dashes/underscores should be invalid + Assert.That(CharacterNameValidator.ValidateName("张三-李四"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("刘德华_陈"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); } [Test] public void SpecialCharacters_ShouldReturnNameError() { // Names with special or non-ASCII symbols should return name error - Assert.That(CharacterNameValidator.ValidateName("Name★"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("Name."), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("Name!"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("Name<3"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("Name♪"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("Name~"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("Name*"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("Name♥"), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(CharacterNameValidator.ValidateName("Name★"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Name."), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Name!"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Name<3"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Name♪"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Name~"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Name*"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Name♥"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + } + + [Test] + public void DashesAndUnderscores_ShouldReturnNameError() { + Assert.That(CharacterNameValidator.ValidateName("Name-Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("Name_Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(CharacterNameValidator.ValidateName("A-B_C-D"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); } [Test] - public void NamesWithAccents_ShouldReturnNull() { - // Names with accented Latin characters should be valid + public void AccentedNames_ShouldRespectAllowAccentsFlag() { + if (!Constant.AllowUnicodeInNames) { + return; + } + Assert.That(CharacterNameValidator.ValidateName("José"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("Renée"), Is.Null); + Assert.That(CharacterNameValidator.ValidateName("Beyoncé"), Is.Null); Assert.That(CharacterNameValidator.ValidateName("José"), Is.Null); Assert.That(CharacterNameValidator.ValidateName("Renée"), Is.Null); Assert.That(CharacterNameValidator.ValidateName("Beyoncé"), Is.Null); diff --git a/Maple2.Server.Tests/Validators/ClubNameValidatorTests.cs b/Maple2.Server.Tests/Validators/ClubNameValidatorTests.cs new file mode 100644 index 000000000..44cbdf2bb --- /dev/null +++ b/Maple2.Server.Tests/Validators/ClubNameValidatorTests.cs @@ -0,0 +1,62 @@ +using Maple2.Model.Error; +using Maple2.Model.Metadata; +using Maple2.Model.Validators; +using NUnit.Framework; + +namespace Maple2.Server.Tests.Validators; + +public class ClubNameValidatorTests { + [Test] + public void ValidName_ShouldReturnNull() { + Assert.That(ClubNameValidator.ValidateName("ClubName"), Is.Null); + Assert.That(ClubNameValidator.ValidateName("Club123"), Is.Null); + Assert.That(ClubNameValidator.ValidateName("ab"), Is.Null); // minimum length + Assert.That(ClubNameValidator.ValidateName("0123456789012345678912345"), Is.Null); // maximum length + } + + [Test] + public void TooShortName_ShouldReturnNameValueError() { + Assert.That(ClubNameValidator.ValidateName("Club_Name"), Is.EqualTo(ClubError.s_club_err_name_value)); + Assert.That(ClubNameValidator.ValidateName("a"), Is.EqualTo(ClubError.s_club_err_name_value)); + Assert.That(ClubNameValidator.ValidateName(""), Is.EqualTo(ClubError.s_club_err_name_value)); + Assert.That(ClubNameValidator.ValidateName(" "), Is.EqualTo(ClubError.s_club_err_name_value)); + Assert.That(ClubNameValidator.ValidateName("ThisNameIsWayTooLongForClub"), Is.EqualTo(ClubError.s_club_err_name_value)); + } + + [Test] + public void InvalidCharacters_ShouldReturnNameValueError() { + Assert.That(ClubNameValidator.ValidateName("club-name"), Is.EqualTo(ClubError.s_club_err_name_value)); // dash not allowed + Assert.That(ClubNameValidator.ValidateName("club name"), Is.EqualTo(ClubError.s_club_err_name_value)); // space not allowed + Assert.That(ClubNameValidator.ValidateName("club@name"), Is.EqualTo(ClubError.s_club_err_name_value)); + Assert.That(ClubNameValidator.ValidateName("club#name"), Is.EqualTo(ClubError.s_club_err_name_value)); + Assert.That(ClubNameValidator.ValidateName("club$name"), Is.EqualTo(ClubError.s_club_err_name_value)); + } + + [Test] + public void OnlySpecialCharacters_ShouldReturnNameValueError() { + Assert.That(ClubNameValidator.ValidateName("__"), Is.EqualTo(ClubError.s_club_err_name_value)); + Assert.That(ClubNameValidator.ValidateName(" "), Is.EqualTo(ClubError.s_club_err_name_value)); + } + + [Test] + public void DashesAndUnderscores_ShouldReturnNameValueError() { + Assert.That(ClubNameValidator.ValidateName("Club-Name"), Is.EqualTo(ClubError.s_club_err_name_value)); + Assert.That(ClubNameValidator.ValidateName("Club_Name"), Is.EqualTo(ClubError.s_club_err_name_value)); + Assert.That(ClubNameValidator.ValidateName("A-B_C-D"), Is.EqualTo(ClubError.s_club_err_name_value)); + } + + [Test] + public void UnicodeNames_ShouldRespectAllowUnicodeFlag() { + if (!Constant.AllowUnicodeInNames) { + return; + } + Assert.That(ClubNameValidator.ValidateName("김철수"), Is.Null); + Assert.That(ClubNameValidator.ValidateName("王小明"), Is.Null); + Assert.That(ClubNameValidator.ValidateName("さくら"), Is.Null); + Assert.That(ClubNameValidator.ValidateName("Renée"), Is.Null); + Assert.That(ClubNameValidator.ValidateName("김철수"), Is.Null); + Assert.That(ClubNameValidator.ValidateName("王小明"), Is.Null); + Assert.That(ClubNameValidator.ValidateName("さくら"), Is.Null); + Assert.That(ClubNameValidator.ValidateName("Renée"), Is.Null); + } +} diff --git a/Maple2.Server.Tests/Validators/GuildNameValidatorTests.cs b/Maple2.Server.Tests/Validators/GuildNameValidatorTests.cs new file mode 100644 index 000000000..439a60317 --- /dev/null +++ b/Maple2.Server.Tests/Validators/GuildNameValidatorTests.cs @@ -0,0 +1,62 @@ +using Maple2.Model.Error; +using Maple2.Model.Metadata; +using Maple2.Model.Validators; +using NUnit.Framework; + +namespace Maple2.Server.Tests.Validators; + +public class GuildNameValidatorTests { + [Test] + public void ValidName_ShouldReturnNull() { + Assert.That(GuildNameValidator.ValidateName("GuildName"), Is.Null); + Assert.That(GuildNameValidator.ValidateName("Guild123"), Is.Null); + Assert.That(GuildNameValidator.ValidateName("abc"), Is.Null); // minimum length + Assert.That(GuildNameValidator.ValidateName("0123456789012345678912345"), Is.Null); // maximum length + } + + [Test] + public void TooShortName_ShouldReturnNameValueError() { + Assert.That(GuildNameValidator.ValidateName("a"), Is.EqualTo(GuildError.s_guild_err_name_value)); + Assert.That(GuildNameValidator.ValidateName(""), Is.EqualTo(GuildError.s_guild_err_name_value)); + Assert.That(GuildNameValidator.ValidateName(" "), Is.EqualTo(GuildError.s_guild_err_name_value)); + Assert.That(GuildNameValidator.ValidateName("ThisNameIsWayTooLongForGuild"), Is.EqualTo(GuildError.s_guild_err_name_value)); + } + + [Test] + public void InvalidCharacters_ShouldReturnNameValueError() { + Assert.That(GuildNameValidator.ValidateName("guild-name"), Is.EqualTo(GuildError.s_guild_err_name_value)); // dash not allowed + Assert.That(GuildNameValidator.ValidateName("Guild_Name"), Is.EqualTo(GuildError.s_guild_err_name_value)); + Assert.That(GuildNameValidator.ValidateName("guild name"), Is.EqualTo(GuildError.s_guild_err_name_value)); // space not allowed + Assert.That(GuildNameValidator.ValidateName("guild@name"), Is.EqualTo(GuildError.s_guild_err_name_value)); + Assert.That(GuildNameValidator.ValidateName("guild#name"), Is.EqualTo(GuildError.s_guild_err_name_value)); + Assert.That(GuildNameValidator.ValidateName("guild$name"), Is.EqualTo(GuildError.s_guild_err_name_value)); + } + + [Test] + public void OnlySpecialCharacters_ShouldReturnNameValueError() { + Assert.That(GuildNameValidator.ValidateName("__"), Is.EqualTo(GuildError.s_guild_err_name_value)); + Assert.That(GuildNameValidator.ValidateName(" "), Is.EqualTo(GuildError.s_guild_err_name_value)); + } + + [Test] + public void DashesAndUnderscores_ShouldReturnNameValueError() { + Assert.That(GuildNameValidator.ValidateName("Guild-Name"), Is.EqualTo(GuildError.s_guild_err_name_value)); + Assert.That(GuildNameValidator.ValidateName("Guild_Name"), Is.EqualTo(GuildError.s_guild_err_name_value)); + Assert.That(GuildNameValidator.ValidateName("A-B_C-D"), Is.EqualTo(GuildError.s_guild_err_name_value)); + } + + [Test] + public void UnicodeNames_ShouldRespectAllowUnicodeFlag() { + if (!Constant.AllowUnicodeInNames) { + return; + } + Assert.That(GuildNameValidator.ValidateName("김철수"), Is.Null); + Assert.That(GuildNameValidator.ValidateName("王小明"), Is.Null); + Assert.That(GuildNameValidator.ValidateName("さくら"), Is.Null); + Assert.That(GuildNameValidator.ValidateName("Renée"), Is.Null); + Assert.That(GuildNameValidator.ValidateName("김철수"), Is.Null); + Assert.That(GuildNameValidator.ValidateName("王小明"), Is.Null); + Assert.That(GuildNameValidator.ValidateName("さくら"), Is.Null); + Assert.That(GuildNameValidator.ValidateName("Renée"), Is.Null); + } +} From 3d158bdeaca53692e579d018ec7a773635edd879 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=82ngelo=20Tadeucci?= Date: Tue, 30 Sep 2025 17:42:41 -0300 Subject: [PATCH 3/3] refactor: one name validator for character, club, guild --- .../Validators/CharacterNameValidator.cs | 38 ++-- Maple2.Model/Validators/ClubNameValidator.cs | 39 ---- Maple2.Model/Validators/GuildNameValidator.cs | 39 ---- .../CheckCharacterNameHandler.cs | 19 +- .../PacketHandlers/ClubHandler.cs | 28 ++- .../PacketHandlers/GuildHandler.cs | 14 +- .../CharacterManagementHandler.cs | 19 +- .../Validators/CharacterNameValidatorTests.cs | 189 +++++++++--------- .../Validators/ClubNameValidatorTests.cs | 62 ------ .../Validators/GuildNameValidatorTests.cs | 62 ------ 10 files changed, 168 insertions(+), 341 deletions(-) delete mode 100644 Maple2.Model/Validators/ClubNameValidator.cs delete mode 100644 Maple2.Model/Validators/GuildNameValidator.cs delete mode 100644 Maple2.Server.Tests/Validators/ClubNameValidatorTests.cs delete mode 100644 Maple2.Server.Tests/Validators/GuildNameValidatorTests.cs diff --git a/Maple2.Model/Validators/CharacterNameValidator.cs b/Maple2.Model/Validators/CharacterNameValidator.cs index a54d58118..9fda0897e 100644 --- a/Maple2.Model/Validators/CharacterNameValidator.cs +++ b/Maple2.Model/Validators/CharacterNameValidator.cs @@ -1,46 +1,36 @@ -using System.Text.RegularExpressions; +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; using Maple2.Model.Error; using Maple2.Model.Metadata; namespace Maple2.Model.Validators; -public static partial class CharacterNameValidator { - // Regex patterns for valid character names +public static partial class NameValidator { + // Regex patterns for valid names private static readonly Regex ValidNamePatternAscii = NamePatternAsciiRegex(); private static readonly Regex ValidNamePatternUnicode = NamePatternUnicodeRegex(); /// - /// Validates a character name according to all rules. + /// Validates a name according to all rules. /// - /// The character name to validate - /// CharacterCreateError code if invalid, null if valid - public static CharacterCreateError? ValidateName(string name) { + /// The name to validate + /// True if valid, false if invalid + public static bool ValidName(string name) { if (string.IsNullOrWhiteSpace(name)) { - return CharacterCreateError.s_char_err_name; + return false; // Null or whitespace } - - string validatedName = name; - - // Check length constraints - if (validatedName.Length is < Constant.CharacterNameLengthMin) { - return CharacterCreateError.s_char_err_name; - } - if (validatedName.Length > Constant.CharacterNameLengthMax) { - return CharacterCreateError.s_char_err_system; - } - // Select pattern Regex pattern = Constant.AllowUnicodeInNames ? ValidNamePatternUnicode : ValidNamePatternAscii; - if (!pattern.IsMatch(validatedName)) { - return CharacterCreateError.s_char_err_ban_all; + if (!pattern.IsMatch(name)) { + return false; } // Check for names that are only special characters - if (validatedName.All(c => !char.IsLetterOrDigit(c))) { - return CharacterCreateError.s_char_err_name; + if (name.All(c => !char.IsLetterOrDigit(c))) { + return false; } - return null; // Valid name + return true; // Valid name } // ASCII only: A-Z, a-z, 0-9 (no dash, no underscore) diff --git a/Maple2.Model/Validators/ClubNameValidator.cs b/Maple2.Model/Validators/ClubNameValidator.cs deleted file mode 100644 index 1b35ab511..000000000 --- a/Maple2.Model/Validators/ClubNameValidator.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Text.RegularExpressions; -using Maple2.Model.Error; -using Maple2.Model.Metadata; - -namespace Maple2.Model.Validators; - -public static partial class ClubNameValidator { - private static readonly Regex ValidNamePatternAscii = NamePatternAsciiRegex(); - private static readonly Regex ValidNamePatternUnicode = NamePatternUnicodeRegex(); - - /// - /// Validates a club name according to all rules. - /// - /// The club name to validate - /// ClubError code if invalid, null if valid - public static ClubError? ValidateName(string name) { - if (string.IsNullOrWhiteSpace(name)) { - return ClubError.s_club_err_name_value; - } - if (name.Length is < Constant.ClubNameLengthMin or > Constant.ClubNameLengthMax) { - return ClubError.s_club_err_name_value; - } - Regex pattern = Constant.AllowUnicodeInNames ? ValidNamePatternUnicode : ValidNamePatternAscii; - if (!pattern.IsMatch(name)) { - return ClubError.s_club_err_name_value; - } - if (name.All(c => !char.IsLetterOrDigit(c))) { - return ClubError.s_club_err_name_value; - } - return null; // Valid name - } - - // ASCII only: A-Z, a-z, 0-9 (no dash, no underscore) - [GeneratedRegex(@"^[A-Za-z0-9]+$", RegexOptions.Compiled)] - private static partial Regex NamePatternAsciiRegex(); - // Unicode: \p{L} (all letters, including accents/CJK), 0-9 (no dash, no underscore) - [GeneratedRegex(@"^[\p{L}0-9]+$", RegexOptions.Compiled)] - private static partial Regex NamePatternUnicodeRegex(); -} diff --git a/Maple2.Model/Validators/GuildNameValidator.cs b/Maple2.Model/Validators/GuildNameValidator.cs deleted file mode 100644 index 05c49e062..000000000 --- a/Maple2.Model/Validators/GuildNameValidator.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System.Text.RegularExpressions; -using Maple2.Model.Error; -using Maple2.Model.Metadata; - -namespace Maple2.Model.Validators; - -public static partial class GuildNameValidator { - private static readonly Regex ValidNamePatternAscii = NamePatternAsciiRegex(); - private static readonly Regex ValidNamePatternUnicode = NamePatternUnicodeRegex(); - - /// - /// Validates a guild name according to all rules. - /// - /// The guild name to validate - /// GuildError code if invalid, null if valid - public static GuildError? ValidateName(string name) { - if (string.IsNullOrWhiteSpace(name)) { - return GuildError.s_guild_err_name_value; - } - if (name.Length is < Constant.GuildNameLengthMin or > Constant.GuildNameLengthMax) { - return GuildError.s_guild_err_name_value; - } - Regex pattern = Constant.AllowUnicodeInNames ? ValidNamePatternUnicode : ValidNamePatternAscii; - if (!pattern.IsMatch(name)) { - return GuildError.s_guild_err_name_value; - } - if (name.All(c => !char.IsLetterOrDigit(c))) { - return GuildError.s_guild_err_name_value; - } - return null; // Valid name - } - - // ASCII only: A-Z, a-z, 0-9 (no dash, no underscore) - [GeneratedRegex(@"^[A-Za-z0-9]+$", RegexOptions.Compiled)] - private static partial Regex NamePatternAsciiRegex(); - // Unicode: \p{L} (all letters, including accents/CJK), 0-9 (no dash, no underscore) - [GeneratedRegex(@"^[\p{L}0-9]+$", RegexOptions.Compiled)] - private static partial Regex NamePatternUnicodeRegex(); -} diff --git a/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs b/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs index f6bd8d2b3..db6855efc 100644 --- a/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/CheckCharacterNameHandler.cs @@ -26,15 +26,28 @@ public override void Handle(GameSession session, IByteReader packet) { string characterName = packet.ReadUnicodeString(); long itemUid = packet.ReadLong(); + if (string.IsNullOrWhiteSpace(characterName)) { + session.Send(CharacterListPacket.CreateError(s_char_err_name)); + return; + } + + if (characterName.Length < Constant.CharacterNameLengthMin) { + session.Send(CharacterListPacket.CreateError(s_char_err_name)); + return; + } + if (characterName.Length > Constant.CharacterNameLengthMax) { + session.Send(CharacterListPacket.CreateError(s_char_err_system)); + return; + } + if (BanWordStorage.ContainsBannedWord(characterName)) { session.Send(CharacterListPacket.CreateError(s_char_err_ban_any)); return; } // Validate character name - CharacterCreateError? nameError = CharacterNameValidator.ValidateName(characterName); - if (nameError != null) { - session.Send(CharacterListPacket.CreateError(nameError.Value)); + if (NameValidator.ValidName(characterName)) { + session.Send(CharacterListPacket.CreateError(s_char_err_ban_all)); return; } diff --git a/Maple2.Server.Game/PacketHandlers/ClubHandler.cs b/Maple2.Server.Game/PacketHandlers/ClubHandler.cs index 03f266d5e..a513f2e05 100644 --- a/Maple2.Server.Game/PacketHandlers/ClubHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/ClubHandler.cs @@ -67,14 +67,22 @@ public override void Handle(GameSession session, IByteReader packet) { private void HandleCreate(GameSession session, IByteReader packet) { string clubName = packet.ReadUnicodeString(); + if (string.IsNullOrWhiteSpace(clubName)) { + session.Send(ClubPacket.Error(ClubError.s_club_err_name_value)); + return; + } + if (clubName.Length is < Constant.ClubNameLengthMin or > Constant.ClubNameLengthMax) { + session.Send(ClubPacket.Error(ClubError.s_club_err_name_value)); + return; + } + if (BanWordStorage.ContainsBannedWord(clubName)) { session.Send(ClubPacket.Error(ClubError.s_club_err_name_value)); return; } - ClubError? result = ClubNameValidator.ValidateName(clubName); - if (result is not null) { - session.Send(ClubPacket.Error(result.Value)); + if (!NameValidator.ValidName(clubName)) { + session.Send(ClubPacket.Error(ClubError.s_club_err_name_value)); return; } @@ -231,14 +239,22 @@ private void HandleRename(GameSession session, IByteReader packet) { long clubId = packet.ReadLong(); string newName = packet.ReadUnicodeString(); + if (string.IsNullOrWhiteSpace(newName)) { + session.Send(ClubPacket.Error(ClubError.s_club_err_name_value)); + return; + } + if (newName.Length is < Constant.ClubNameLengthMin or > Constant.ClubNameLengthMax) { + session.Send(ClubPacket.Error(ClubError.s_club_err_name_value)); + return; + } + if (BanWordStorage.ContainsBannedWord(newName)) { session.Send(ClubPacket.Error(ClubError.s_club_err_name_value)); return; } - ClubError? result = ClubNameValidator.ValidateName(newName); - if (result is not null) { - session.Send(ClubPacket.Error(result.Value)); + if (!NameValidator.ValidName(newName)) { + session.Send(ClubPacket.Error(ClubError.s_club_err_name_value)); return; } diff --git a/Maple2.Server.Game/PacketHandlers/GuildHandler.cs b/Maple2.Server.Game/PacketHandlers/GuildHandler.cs index 33c83e4ec..f54052620 100644 --- a/Maple2.Server.Game/PacketHandlers/GuildHandler.cs +++ b/Maple2.Server.Game/PacketHandlers/GuildHandler.cs @@ -189,14 +189,22 @@ private void HandleCreate(GameSession session, IByteReader packet) { return; // Already in a guild. } + if (string.IsNullOrWhiteSpace(guildName)) { + session.Send(GuildPacket.Error(GuildError.s_guild_err_name_value)); + return; + } + if (guildName.Length is < Constant.GuildNameLengthMin or > Constant.GuildNameLengthMax) { + session.Send(GuildPacket.Error(GuildError.s_guild_err_name_value)); + return; + } + if (BanWordStorage.ContainsBannedWord(guildName)) { session.Send(GuildPacket.Error(GuildError.s_guild_err_name_value)); return; } - GuildError? result = GuildNameValidator.ValidateName(guildName); - if (result is not null) { - session.Send(GuildPacket.Error(result.Value)); + if (!NameValidator.ValidName(guildName)) { + session.Send(GuildPacket.Error(GuildError.s_guild_err_name_value)); return; } diff --git a/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs b/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs index 4cc93f008..47efd75d2 100644 --- a/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs +++ b/Maple2.Server.Login/PacketHandlers/CharacterManagementHandler.cs @@ -116,15 +116,28 @@ private void HandleCreate(LoginSession session, IByteReader packet) { var job = (Job) ((int) jobCode * 10); string name = packet.ReadUnicodeString(); + if (string.IsNullOrWhiteSpace(name)) { + session.Send(CharacterListPacket.CreateError(s_char_err_name)); + return; + } + + if (name.Length < Constant.CharacterNameLengthMin) { + session.Send(CharacterListPacket.CreateError(s_char_err_name)); + return; + } + if (name.Length > Constant.CharacterNameLengthMax) { + session.Send(CharacterListPacket.CreateError(s_char_err_system)); + return; + } + if (BanWordStorage.ContainsBannedWord(name)) { session.Send(CharacterListPacket.CreateError(s_char_err_ban_any)); return; } // Validate character name - CharacterCreateError? nameError = CharacterNameValidator.ValidateName(name); - if (nameError != null) { - session.Send(CharacterListPacket.CreateError(nameError.Value)); + if (NameValidator.ValidName(name)) { + session.Send(CharacterListPacket.CreateError(s_char_err_ban_all)); return; } diff --git a/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs b/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs index e3321dbcb..851387abc 100644 --- a/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs +++ b/Maple2.Server.Tests/Validators/CharacterNameValidatorTests.cs @@ -4,79 +4,68 @@ namespace Maple2.Server.Tests.Validators; -public class CharacterNameValidatorTests { +public class NameValidatorTests { [Test] - public void ValidName_ShouldReturnNull() { + public void ValidName_ShouldReturnFalse() { // Valid names should return null (no error) - Assert.That(CharacterNameValidator.ValidateName("ValidName"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("Test123"), Is.Null); - - Assert.That(CharacterNameValidator.ValidateName("ab"), Is.Null); // minimum length - Assert.That(CharacterNameValidator.ValidateName("abcdefghijkl"), Is.Null); // maximum length + Assert.That(NameValidator.ValidName("ValidName"), Is.True); + Assert.That(NameValidator.ValidName("Test123"), Is.True); } [Test] - public void TooShortName_ShouldReturnNameError() { + public void EmptyName_ShouldReturnFalse() { // Names shorter than minimum should return name error - Assert.That(CharacterNameValidator.ValidateName("a"), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName(""), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName(" "), Is.EqualTo(CharacterCreateError.s_char_err_name)); - } - - [Test] - public void TooLongName_ShouldReturnSystemError() { - // Names longer than maximum should return system error - Assert.That(CharacterNameValidator.ValidateName("abcdefghijklm"), Is.EqualTo(CharacterCreateError.s_char_err_system)); - Assert.That(CharacterNameValidator.ValidateName("ThisNameIsTooLong"), Is.EqualTo(CharacterCreateError.s_char_err_system)); + Assert.That(NameValidator.ValidName(""), Is.False); + Assert.That(NameValidator.ValidName(" "), Is.False); } [Test] - public void InvalidCharacters_ShouldReturnNameError() { + public void InvalidCharacters_ShouldReturnFalse() { // Names with invalid characters should return name error - Assert.That(CharacterNameValidator.ValidateName("test@name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("name#test"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("test$name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("test%name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("test*name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(NameValidator.ValidName("test@name"), Is.False); + Assert.That(NameValidator.ValidName("name#test"), Is.False); + Assert.That(NameValidator.ValidName("test$name"), Is.False); + Assert.That(NameValidator.ValidName("test%name"), Is.False); + Assert.That(NameValidator.ValidName("test*name"), Is.False); // Dashes and underscores are not allowed - Assert.That(CharacterNameValidator.ValidateName("User_Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Cool-Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(NameValidator.ValidName("User_Name"), Is.False); + Assert.That(NameValidator.ValidName("Cool-Name"), Is.False); } [Test] - public void OnlySpecialCharacters_ShouldReturnNameError() { + public void OnlySpecialCharacters_ShouldReturnFalse() { // Names with only special characters should return name error - Assert.That(CharacterNameValidator.ValidateName("--"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("__"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName(" "), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName("-_-"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(NameValidator.ValidName("--"), Is.False); + Assert.That(NameValidator.ValidName("__"), Is.False); + Assert.That(NameValidator.ValidName(" "), Is.False); + Assert.That(NameValidator.ValidName("-_-"), Is.False); } [Test] - public void NullOrWhitespace_ShouldReturnNameError() { + public void NullOrWhitespace_ShouldReturnFalse() { // Null or whitespace names should return name error - Assert.That(CharacterNameValidator.ValidateName(null!), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName(""), Is.EqualTo(CharacterCreateError.s_char_err_name)); - Assert.That(CharacterNameValidator.ValidateName(" "), Is.EqualTo(CharacterCreateError.s_char_err_name)); + Assert.That(NameValidator.ValidName(null!), Is.False); + Assert.That(NameValidator.ValidName(""), Is.False); + Assert.That(NameValidator.ValidName(" "), Is.False); } [Test] - public void SpaceValidation_ShouldWork() { + public void SpaceValidation_ShouldReturnFalse() { // Names with spaces should be invalid - Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Cool Player"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("a b"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(NameValidator.ValidName("Test Name"), Is.False); + Assert.That(NameValidator.ValidName("Cool Player"), Is.False); + Assert.That(NameValidator.ValidName("a b"), Is.False); // Names with leading or trailing spaces should be invalid - Assert.That(CharacterNameValidator.ValidateName(" ValidName"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("ValidName "), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName(" ValidName "), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(NameValidator.ValidName(" ValidName"), Is.False); + Assert.That(NameValidator.ValidName("ValidName "), Is.False); + Assert.That(NameValidator.ValidName(" ValidName "), Is.False); // Names with multiple consecutive spaces should be invalid - Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Test Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("A B"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(NameValidator.ValidName("Test Name"), Is.False); + Assert.That(NameValidator.ValidName("Test Name"), Is.False); + Assert.That(NameValidator.ValidName("A B"), Is.False); } [Test] @@ -85,26 +74,26 @@ public void JapaneseCharacterNames_ShouldBeValid() { return; } // Japanese Hiragana characters should be valid - Assert.That(CharacterNameValidator.ValidateName("さくら"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("ひろし"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("あいうえお"), Is.Null); + Assert.That(NameValidator.ValidName("さくら"), Is.True); + Assert.That(NameValidator.ValidName("ひろし"), Is.True); + Assert.That(NameValidator.ValidName("あいうえお"), Is.True); // Japanese Katakana characters should be valid - Assert.That(CharacterNameValidator.ValidateName("サクラ"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("ヒロシ"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("アイウエオ"), Is.Null); + Assert.That(NameValidator.ValidName("サクラ"), Is.True); + Assert.That(NameValidator.ValidName("ヒロシ"), Is.True); + Assert.That(NameValidator.ValidName("アイウエオ"), Is.True); // Japanese Kanji characters should be valid - Assert.That(CharacterNameValidator.ValidateName("田中"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("山田"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("佐藤"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("鈴木"), Is.Null); + Assert.That(NameValidator.ValidName("田中"), Is.True); + Assert.That(NameValidator.ValidName("山田"), Is.True); + Assert.That(NameValidator.ValidName("佐藤"), Is.True); + Assert.That(NameValidator.ValidName("鈴木"), Is.True); // Mixed Japanese characters should be valid - Assert.That(CharacterNameValidator.ValidateName("さくら123"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("田中ひろし"), Is.Null); + Assert.That(NameValidator.ValidName("さくら123"), Is.True); + Assert.That(NameValidator.ValidName("田中ひろし"), Is.True); // Mixed with dashes/underscores should be invalid - Assert.That(CharacterNameValidator.ValidateName("サクラ_田中"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(NameValidator.ValidName("サクラ_田中"), Is.False); } [Test] @@ -113,18 +102,18 @@ public void KoreanCharacterNames_ShouldBeValid() { return; } // Korean Hangul characters should be valid - Assert.That(CharacterNameValidator.ValidateName("김철수"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("이영희"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("박민수"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("정수진"), Is.Null); + Assert.That(NameValidator.ValidName("김철수"), Is.True); + Assert.That(NameValidator.ValidName("이영희"), Is.True); + Assert.That(NameValidator.ValidName("박민수"), Is.True); + Assert.That(NameValidator.ValidName("정수진"), Is.True); // Korean with numbers should be valid - Assert.That(CharacterNameValidator.ValidateName("김철수123"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("이영희456"), Is.Null); + Assert.That(NameValidator.ValidName("김철수123"), Is.True); + Assert.That(NameValidator.ValidName("이영희456"), Is.True); // Korean with dashes/underscores should be invalid - Assert.That(CharacterNameValidator.ValidateName("박민수-정"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("정수진_김"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(NameValidator.ValidName("박민수-정"), Is.False); + Assert.That(NameValidator.ValidName("정수진_김"), Is.False); } [Test] @@ -133,44 +122,44 @@ public void ChineseCharacterNames_ShouldBeValid() { return; } // Simplified Chinese characters should be valid - Assert.That(CharacterNameValidator.ValidateName("王小明"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("李小红"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("张三"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("刘德华"), Is.Null); + Assert.That(NameValidator.ValidName("王小明"), Is.True); + Assert.That(NameValidator.ValidName("李小红"), Is.True); + Assert.That(NameValidator.ValidName("张三"), Is.True); + Assert.That(NameValidator.ValidName("刘德华"), Is.True); // Traditional Chinese characters should be valid - Assert.That(CharacterNameValidator.ValidateName("王小明"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("陳大文"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("黃志強"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("林美玲"), Is.Null); + Assert.That(NameValidator.ValidName("王小明"), Is.True); + Assert.That(NameValidator.ValidName("陳大文"), Is.True); + Assert.That(NameValidator.ValidName("黃志強"), Is.True); + Assert.That(NameValidator.ValidName("林美玲"), Is.True); // Chinese with numbers should be valid - Assert.That(CharacterNameValidator.ValidateName("王小明123"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("李小红456"), Is.Null); + Assert.That(NameValidator.ValidName("王小明123"), Is.True); + Assert.That(NameValidator.ValidName("李小红456"), Is.True); // Chinese with dashes/underscores should be invalid - Assert.That(CharacterNameValidator.ValidateName("张三-李四"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("刘德华_陈"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(NameValidator.ValidName("张三-李四"), Is.False); + Assert.That(NameValidator.ValidName("刘德华_陈"), Is.False); } [Test] - public void SpecialCharacters_ShouldReturnNameError() { + public void SpecialCharacters_ShouldReturnFalse() { // Names with special or non-ASCII symbols should return name error - Assert.That(CharacterNameValidator.ValidateName("Name★"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Name."), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Name!"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Name<3"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Name♪"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Name~"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Name*"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Name♥"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + Assert.That(NameValidator.ValidName("Name★"), Is.False); + Assert.That(NameValidator.ValidName("Name."), Is.False); + Assert.That(NameValidator.ValidName("Name!"), Is.False); + Assert.That(NameValidator.ValidName("Name<3"), Is.False); + Assert.That(NameValidator.ValidName("Name♪"), Is.False); + Assert.That(NameValidator.ValidName("Name~"), Is.False); + Assert.That(NameValidator.ValidName("Name*"), Is.False); + Assert.That(NameValidator.ValidName("Name♥"), Is.False); } [Test] - public void DashesAndUnderscores_ShouldReturnNameError() { - Assert.That(CharacterNameValidator.ValidateName("Name-Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("Name_Name"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); - Assert.That(CharacterNameValidator.ValidateName("A-B_C-D"), Is.EqualTo(CharacterCreateError.s_char_err_ban_all)); + public void DashesAndUnderscores_ShouldReturnFalse() { + Assert.That(NameValidator.ValidName("Name-Name"), Is.False); + Assert.That(NameValidator.ValidName("Name_Name"), Is.False); + Assert.That(NameValidator.ValidName("A-B_C-D"), Is.False); } [Test] @@ -178,14 +167,14 @@ public void AccentedNames_ShouldRespectAllowAccentsFlag() { if (!Constant.AllowUnicodeInNames) { return; } - Assert.That(CharacterNameValidator.ValidateName("José"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("Renée"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("Beyoncé"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("José"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("Renée"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("Beyoncé"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("André"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("Zoë"), Is.Null); - Assert.That(CharacterNameValidator.ValidateName("François"), Is.Null); + Assert.That(NameValidator.ValidName("José"), Is.True); + Assert.That(NameValidator.ValidName("Renée"), Is.True); + Assert.That(NameValidator.ValidName("Beyoncé"), Is.True); + Assert.That(NameValidator.ValidName("José"), Is.True); + Assert.That(NameValidator.ValidName("Renée"), Is.True); + Assert.That(NameValidator.ValidName("Beyoncé"), Is.True); + Assert.That(NameValidator.ValidName("André"), Is.True); + Assert.That(NameValidator.ValidName("Zoë"), Is.True); + Assert.That(NameValidator.ValidName("François"), Is.True); } } diff --git a/Maple2.Server.Tests/Validators/ClubNameValidatorTests.cs b/Maple2.Server.Tests/Validators/ClubNameValidatorTests.cs deleted file mode 100644 index 44cbdf2bb..000000000 --- a/Maple2.Server.Tests/Validators/ClubNameValidatorTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Maple2.Model.Error; -using Maple2.Model.Metadata; -using Maple2.Model.Validators; -using NUnit.Framework; - -namespace Maple2.Server.Tests.Validators; - -public class ClubNameValidatorTests { - [Test] - public void ValidName_ShouldReturnNull() { - Assert.That(ClubNameValidator.ValidateName("ClubName"), Is.Null); - Assert.That(ClubNameValidator.ValidateName("Club123"), Is.Null); - Assert.That(ClubNameValidator.ValidateName("ab"), Is.Null); // minimum length - Assert.That(ClubNameValidator.ValidateName("0123456789012345678912345"), Is.Null); // maximum length - } - - [Test] - public void TooShortName_ShouldReturnNameValueError() { - Assert.That(ClubNameValidator.ValidateName("Club_Name"), Is.EqualTo(ClubError.s_club_err_name_value)); - Assert.That(ClubNameValidator.ValidateName("a"), Is.EqualTo(ClubError.s_club_err_name_value)); - Assert.That(ClubNameValidator.ValidateName(""), Is.EqualTo(ClubError.s_club_err_name_value)); - Assert.That(ClubNameValidator.ValidateName(" "), Is.EqualTo(ClubError.s_club_err_name_value)); - Assert.That(ClubNameValidator.ValidateName("ThisNameIsWayTooLongForClub"), Is.EqualTo(ClubError.s_club_err_name_value)); - } - - [Test] - public void InvalidCharacters_ShouldReturnNameValueError() { - Assert.That(ClubNameValidator.ValidateName("club-name"), Is.EqualTo(ClubError.s_club_err_name_value)); // dash not allowed - Assert.That(ClubNameValidator.ValidateName("club name"), Is.EqualTo(ClubError.s_club_err_name_value)); // space not allowed - Assert.That(ClubNameValidator.ValidateName("club@name"), Is.EqualTo(ClubError.s_club_err_name_value)); - Assert.That(ClubNameValidator.ValidateName("club#name"), Is.EqualTo(ClubError.s_club_err_name_value)); - Assert.That(ClubNameValidator.ValidateName("club$name"), Is.EqualTo(ClubError.s_club_err_name_value)); - } - - [Test] - public void OnlySpecialCharacters_ShouldReturnNameValueError() { - Assert.That(ClubNameValidator.ValidateName("__"), Is.EqualTo(ClubError.s_club_err_name_value)); - Assert.That(ClubNameValidator.ValidateName(" "), Is.EqualTo(ClubError.s_club_err_name_value)); - } - - [Test] - public void DashesAndUnderscores_ShouldReturnNameValueError() { - Assert.That(ClubNameValidator.ValidateName("Club-Name"), Is.EqualTo(ClubError.s_club_err_name_value)); - Assert.That(ClubNameValidator.ValidateName("Club_Name"), Is.EqualTo(ClubError.s_club_err_name_value)); - Assert.That(ClubNameValidator.ValidateName("A-B_C-D"), Is.EqualTo(ClubError.s_club_err_name_value)); - } - - [Test] - public void UnicodeNames_ShouldRespectAllowUnicodeFlag() { - if (!Constant.AllowUnicodeInNames) { - return; - } - Assert.That(ClubNameValidator.ValidateName("김철수"), Is.Null); - Assert.That(ClubNameValidator.ValidateName("王小明"), Is.Null); - Assert.That(ClubNameValidator.ValidateName("さくら"), Is.Null); - Assert.That(ClubNameValidator.ValidateName("Renée"), Is.Null); - Assert.That(ClubNameValidator.ValidateName("김철수"), Is.Null); - Assert.That(ClubNameValidator.ValidateName("王小明"), Is.Null); - Assert.That(ClubNameValidator.ValidateName("さくら"), Is.Null); - Assert.That(ClubNameValidator.ValidateName("Renée"), Is.Null); - } -} diff --git a/Maple2.Server.Tests/Validators/GuildNameValidatorTests.cs b/Maple2.Server.Tests/Validators/GuildNameValidatorTests.cs deleted file mode 100644 index 439a60317..000000000 --- a/Maple2.Server.Tests/Validators/GuildNameValidatorTests.cs +++ /dev/null @@ -1,62 +0,0 @@ -using Maple2.Model.Error; -using Maple2.Model.Metadata; -using Maple2.Model.Validators; -using NUnit.Framework; - -namespace Maple2.Server.Tests.Validators; - -public class GuildNameValidatorTests { - [Test] - public void ValidName_ShouldReturnNull() { - Assert.That(GuildNameValidator.ValidateName("GuildName"), Is.Null); - Assert.That(GuildNameValidator.ValidateName("Guild123"), Is.Null); - Assert.That(GuildNameValidator.ValidateName("abc"), Is.Null); // minimum length - Assert.That(GuildNameValidator.ValidateName("0123456789012345678912345"), Is.Null); // maximum length - } - - [Test] - public void TooShortName_ShouldReturnNameValueError() { - Assert.That(GuildNameValidator.ValidateName("a"), Is.EqualTo(GuildError.s_guild_err_name_value)); - Assert.That(GuildNameValidator.ValidateName(""), Is.EqualTo(GuildError.s_guild_err_name_value)); - Assert.That(GuildNameValidator.ValidateName(" "), Is.EqualTo(GuildError.s_guild_err_name_value)); - Assert.That(GuildNameValidator.ValidateName("ThisNameIsWayTooLongForGuild"), Is.EqualTo(GuildError.s_guild_err_name_value)); - } - - [Test] - public void InvalidCharacters_ShouldReturnNameValueError() { - Assert.That(GuildNameValidator.ValidateName("guild-name"), Is.EqualTo(GuildError.s_guild_err_name_value)); // dash not allowed - Assert.That(GuildNameValidator.ValidateName("Guild_Name"), Is.EqualTo(GuildError.s_guild_err_name_value)); - Assert.That(GuildNameValidator.ValidateName("guild name"), Is.EqualTo(GuildError.s_guild_err_name_value)); // space not allowed - Assert.That(GuildNameValidator.ValidateName("guild@name"), Is.EqualTo(GuildError.s_guild_err_name_value)); - Assert.That(GuildNameValidator.ValidateName("guild#name"), Is.EqualTo(GuildError.s_guild_err_name_value)); - Assert.That(GuildNameValidator.ValidateName("guild$name"), Is.EqualTo(GuildError.s_guild_err_name_value)); - } - - [Test] - public void OnlySpecialCharacters_ShouldReturnNameValueError() { - Assert.That(GuildNameValidator.ValidateName("__"), Is.EqualTo(GuildError.s_guild_err_name_value)); - Assert.That(GuildNameValidator.ValidateName(" "), Is.EqualTo(GuildError.s_guild_err_name_value)); - } - - [Test] - public void DashesAndUnderscores_ShouldReturnNameValueError() { - Assert.That(GuildNameValidator.ValidateName("Guild-Name"), Is.EqualTo(GuildError.s_guild_err_name_value)); - Assert.That(GuildNameValidator.ValidateName("Guild_Name"), Is.EqualTo(GuildError.s_guild_err_name_value)); - Assert.That(GuildNameValidator.ValidateName("A-B_C-D"), Is.EqualTo(GuildError.s_guild_err_name_value)); - } - - [Test] - public void UnicodeNames_ShouldRespectAllowUnicodeFlag() { - if (!Constant.AllowUnicodeInNames) { - return; - } - Assert.That(GuildNameValidator.ValidateName("김철수"), Is.Null); - Assert.That(GuildNameValidator.ValidateName("王小明"), Is.Null); - Assert.That(GuildNameValidator.ValidateName("さくら"), Is.Null); - Assert.That(GuildNameValidator.ValidateName("Renée"), Is.Null); - Assert.That(GuildNameValidator.ValidateName("김철수"), Is.Null); - Assert.That(GuildNameValidator.ValidateName("王小明"), Is.Null); - Assert.That(GuildNameValidator.ValidateName("さくら"), Is.Null); - Assert.That(GuildNameValidator.ValidateName("Renée"), Is.Null); - } -}