Skip to content

Commit ca1a26f

Browse files
Copilotmitchdenny
andcommitted
Update project name validation to support Unicode characters and add comprehensive tests
Co-authored-by: mitchdenny <513398+mitchdenny@users.noreply.github.com>
1 parent c5ed9a2 commit ca1a26f

File tree

2 files changed

+150
-1
lines changed

2 files changed

+150
-1
lines changed

src/Aspire.Cli/Commands/NewCommand.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ public virtual async Task<ITemplate> PromptForTemplateAsync(ITemplate[] validTem
222222

223223
internal static partial class ProjectNameValidator
224224
{
225-
[GeneratedRegex(@"^[a-zA-Z0-9_][a-zA-Z0-9_.]{0,253}[a-zA-Z0-9_]$", RegexOptions.Compiled)]
225+
[GeneratedRegex(@"^[\p{L}\p{N}]([\p{L}\p{N}\p{Pc}.\-\p{Mn}\p{Mc}]{0,252}[\p{L}\p{N}][\p{Mn}\p{Mc}]*|[\p{L}\p{N}\p{Pc}.\-\p{Mn}\p{Mc}]{0,252}[\p{L}\p{N}])?$", RegexOptions.Compiled)]
226226
internal static partial Regex GetAssemblyNameRegex();
227227

228228
public static bool IsProjectNameValid(string projectName)
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Cli.Commands;
5+
6+
namespace Aspire.Cli.Tests.Commands;
7+
8+
public class ProjectNameValidatorTests
9+
{
10+
[Theory]
11+
[InlineData("项目1", true)] // Chinese
12+
[InlineData("Проект1", true)] // Cyrillic
13+
[InlineData("プロジェクト1", true)] // Japanese
14+
[InlineData("مشروع1", true)] // Arabic
15+
[InlineData("Project_1", true)] // Latin with underscore
16+
[InlineData("Project-1", true)] // Latin with dash
17+
[InlineData("Project.1", true)] // Latin with dot
18+
[InlineData("MyApp", true)] // Simple ASCII
19+
[InlineData("A", true)] // Single character
20+
[InlineData("1", true)] // Single number
21+
[InlineData("プ", true)] // Single Unicode character
22+
[InlineData("Test123", true)] // Mixed letters and numbers
23+
[InlineData("My_Cool-Project.v2", true)] // Complex valid name
24+
public void IsProjectNameValid_ValidNames_ReturnsTrue(string projectName, bool expected)
25+
{
26+
// Act
27+
var result = ProjectNameValidator.IsProjectNameValid(projectName);
28+
29+
// Assert
30+
Assert.Equal(expected, result);
31+
}
32+
33+
[Theory]
34+
[InlineData("Project/1", false)] // Forward slash (unsafe)
35+
[InlineData("Project\\1", false)] // Backslash (unsafe)
36+
[InlineData("Project:1", false)] // Colon (unsafe)
37+
[InlineData("Project*1", false)] // Asterisk (unsafe)
38+
[InlineData("Project?1", false)] // Question mark (unsafe)
39+
[InlineData("Project\"1", false)] // Quote (unsafe)
40+
[InlineData("Project<1", false)] // Less than (unsafe)
41+
[InlineData("Project>1", false)] // Greater than (unsafe)
42+
[InlineData("Project|1", false)] // Pipe (unsafe)
43+
[InlineData("", false)] // Empty string
44+
[InlineData(" ", false)] // Space only
45+
[InlineData("Project ", false)] // Ends with space
46+
[InlineData(" Project", false)] // Starts with space
47+
[InlineData("Pro ject", false)] // Space in middle
48+
[InlineData("-Project", false)] // Starts with dash
49+
[InlineData("Project-", false)] // Ends with dash
50+
[InlineData(".Project", false)] // Starts with dot
51+
[InlineData("Project.", false)] // Ends with dot
52+
[InlineData("_Project", false)] // Starts with underscore
53+
[InlineData("Project_", false)] // Ends with underscore
54+
public void IsProjectNameValid_InvalidNames_ReturnsFalse(string projectName, bool expected)
55+
{
56+
// Act
57+
var result = ProjectNameValidator.IsProjectNameValid(projectName);
58+
59+
// Assert
60+
Assert.Equal(expected, result);
61+
}
62+
63+
[Fact]
64+
public void IsProjectNameValid_MaxLength254_ReturnsTrue()
65+
{
66+
// Arrange
67+
var projectName = new string('A', 254);
68+
69+
// Act
70+
var result = ProjectNameValidator.IsProjectNameValid(projectName);
71+
72+
// Assert
73+
Assert.True(result);
74+
}
75+
76+
[Fact]
77+
public void IsProjectNameValid_Length255_ReturnsFalse()
78+
{
79+
// Arrange
80+
var projectName = new string('A', 255);
81+
82+
// Act
83+
var result = ProjectNameValidator.IsProjectNameValid(projectName);
84+
85+
// Assert
86+
Assert.False(result);
87+
}
88+
89+
[Theory]
90+
[InlineData("项目测试名称很长的中文项目名称")] // Long Chinese name
91+
[InlineData("очень_длинное_русское_имя_проекта")] // Long Russian name
92+
[InlineData("とても長い日本語のプロジェクト名")] // Long Japanese name
93+
[InlineData("اسم_مشروع_طويل_جدا_بالعربية")] // Long Arabic name
94+
public void IsProjectNameValid_LongUnicodeNames_ReturnsTrue(string projectName)
95+
{
96+
// Act
97+
var result = ProjectNameValidator.IsProjectNameValid(projectName);
98+
99+
// Assert
100+
Assert.True(result, $"Unicode project name should be valid: {projectName}");
101+
}
102+
103+
[Theory]
104+
[InlineData("Ελληνικά", true)] // Greek
105+
[InlineData("עברית", true)] // Hebrew
106+
[InlineData("हिन्दी", true)] // Hindi
107+
[InlineData("ไทย", true)] // Thai
108+
[InlineData("한국어", true)] // Korean
109+
[InlineData("Türkçe", true)] // Turkish
110+
[InlineData("Português", true)] // Portuguese with accent
111+
[InlineData("Français", true)] // French with accent
112+
[InlineData("Español", true)] // Spanish with accent
113+
[InlineData("Deutsch", true)] // German
114+
public void IsProjectNameValid_VariousLanguages_ReturnsTrue(string projectName, bool expected)
115+
{
116+
// Act
117+
var result = ProjectNameValidator.IsProjectNameValid(projectName);
118+
119+
// Assert
120+
Assert.Equal(expected, result);
121+
}
122+
123+
[Theory]
124+
[InlineData("Test123-Project_Name.v2")] // Complex valid with all allowed characters
125+
[InlineData("A1-B2_C3.D4")] // Mixed with separators
126+
[InlineData("项目-测试_版本.1")] // Unicode with separators
127+
public void IsProjectNameValid_ComplexValidNames_ReturnsTrue(string projectName)
128+
{
129+
// Act
130+
var result = ProjectNameValidator.IsProjectNameValid(projectName);
131+
132+
// Assert
133+
Assert.True(result, $"Complex valid project name should be valid: {projectName}");
134+
}
135+
136+
[Theory]
137+
[InlineData("Test..Name")] // Double dot
138+
[InlineData("Test--Name")] // Double dash
139+
[InlineData("Test__Name")] // Double underscore
140+
public void IsProjectNameValid_ConsecutiveSpecialChars_ReturnsTrue(string projectName)
141+
{
142+
// These should be valid as the spec doesn't prohibit consecutive allowed characters
143+
// Act
144+
var result = ProjectNameValidator.IsProjectNameValid(projectName);
145+
146+
// Assert
147+
Assert.True(result, $"Consecutive allowed characters should be valid: {projectName}");
148+
}
149+
}

0 commit comments

Comments
 (0)