1- using System . Text . Json ;
2- using Xunit ;
1+ using System ;
32using System . IO ;
3+ using System . Text . Json ;
44using Mcpb . Json ;
5+ using Xunit ;
56
67namespace Mcpb . Tests ;
78
89public class CliPackFileValidationTests
910{
1011 private string CreateTempDir ( )
1112 {
12- var dir = Path . Combine ( Path . GetTempPath ( ) , "mcpb_cli_pack_files_" + Guid . NewGuid ( ) . ToString ( "N" ) ) ;
13+ var dir = Path . Combine (
14+ Path . GetTempPath ( ) ,
15+ "mcpb_cli_pack_files_" + Guid . NewGuid ( ) . ToString ( "N" )
16+ ) ;
1317 Directory . CreateDirectory ( dir ) ;
1418 Directory . CreateDirectory ( Path . Combine ( dir , "server" ) ) ;
1519 return dir ;
1620 }
17- private ( int exitCode , string stdout , string stderr ) InvokeCli ( string workingDir , params string [ ] args )
21+
22+ private ( int exitCode , string stdout , string stderr ) InvokeCli (
23+ string workingDir ,
24+ params string [ ] args
25+ )
1826 {
1927 var root = Mcpb . Commands . CliRoot . Build ( ) ;
2028 var prev = Directory . GetCurrentDirectory ( ) ;
@@ -26,23 +34,31 @@ private string CreateTempDir()
2634 var code = CommandRunner . Invoke ( root , args , swOut , swErr ) ;
2735 return ( code , swOut . ToString ( ) , swErr . ToString ( ) ) ;
2836 }
29- finally { Directory . SetCurrentDirectory ( prev ) ; }
37+ finally
38+ {
39+ Directory . SetCurrentDirectory ( prev ) ;
40+ }
3041 }
3142
32- private Mcpb . Core . McpbManifest BaseManifest ( ) => new Mcpb . Core . McpbManifest
33- {
34- Name = "demo" ,
35- Description = "desc" ,
36- Author = new Mcpb . Core . McpbManifestAuthor { Name = "A" } ,
37- Icon = "icon.png" ,
38- Screenshots = new List < string > { "shots/s1.png" } ,
39- Server = new Mcpb . Core . McpbManifestServer
43+ private Mcpb . Core . McpbManifest BaseManifest ( ) =>
44+ new Mcpb . Core . McpbManifest
4045 {
41- Type = "node" ,
42- EntryPoint = "server/index.js" ,
43- McpConfig = new Mcpb . Core . McpServerConfigWithOverrides { Command = "node" , Args = new List < string > { "${__dirname}/server/index.js" } }
44- }
45- } ;
46+ Name = "demo" ,
47+ Description = "desc" ,
48+ Author = new Mcpb . Core . McpbManifestAuthor { Name = "A" } ,
49+ Icon = "icon.png" ,
50+ Screenshots = new List < string > { "shots/s1.png" } ,
51+ Server = new Mcpb . Core . McpbManifestServer
52+ {
53+ Type = "node" ,
54+ EntryPoint = "server/index.js" ,
55+ McpConfig = new Mcpb . Core . McpServerConfigWithOverrides
56+ {
57+ Command = "node" ,
58+ Args = new List < string > { "${__dirname}/server/index.js" } ,
59+ } ,
60+ } ,
61+ } ;
4662
4763 [ Fact ]
4864 public void Pack_MissingIcon_Fails ( )
@@ -52,7 +68,10 @@ public void Pack_MissingIcon_Fails()
5268 Directory . CreateDirectory ( Path . Combine ( dir , "shots" ) ) ;
5369 File . WriteAllText ( Path . Combine ( dir , "shots" , "s1.png" ) , "fake" ) ;
5470 var manifest = BaseManifest ( ) ;
55- File . WriteAllText ( Path . Combine ( dir , "manifest.json" ) , JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions ) ) ;
71+ File . WriteAllText (
72+ Path . Combine ( dir , "manifest.json" ) ,
73+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
74+ ) ;
5675 var ( code , _, stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
5776 Assert . NotEqual ( 0 , code ) ;
5877 Assert . Contains ( "Missing icon file" , stderr ) ;
@@ -66,7 +85,10 @@ public void Pack_MissingEntryPoint_Fails()
6685 Directory . CreateDirectory ( Path . Combine ( dir , "shots" ) ) ;
6786 File . WriteAllText ( Path . Combine ( dir , "shots" , "s1.png" ) , "fake" ) ;
6887 var manifest = BaseManifest ( ) ;
69- File . WriteAllText ( Path . Combine ( dir , "manifest.json" ) , JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions ) ) ;
88+ File . WriteAllText (
89+ Path . Combine ( dir , "manifest.json" ) ,
90+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
91+ ) ;
7092 var ( code , _, stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
7193 Assert . NotEqual ( 0 , code ) ;
7294 Assert . Contains ( "Missing entry_point file" , stderr ) ;
@@ -79,7 +101,10 @@ public void Pack_MissingScreenshot_Fails()
79101 File . WriteAllText ( Path . Combine ( dir , "icon.png" ) , "fake" ) ;
80102 File . WriteAllText ( Path . Combine ( dir , "server" , "index.js" ) , "// js" ) ;
81103 var manifest = BaseManifest ( ) ;
82- File . WriteAllText ( Path . Combine ( dir , "manifest.json" ) , JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions ) ) ;
104+ File . WriteAllText (
105+ Path . Combine ( dir , "manifest.json" ) ,
106+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
107+ ) ;
83108 var ( code , _, stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
84109 Assert . NotEqual ( 0 , code ) ;
85110 Assert . Contains ( "Missing screenshot file" , stderr ) ;
@@ -96,12 +121,103 @@ public void Pack_PathLikeCommandMissing_Fails()
96121 var manifest = BaseManifest ( ) ;
97122 // Make command path-like to trigger validation
98123 manifest . Server . McpConfig . Command = "${__dirname}/server/missing.js" ;
99- File . WriteAllText ( Path . Combine ( dir , "manifest.json" ) , JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions ) ) ;
124+ File . WriteAllText (
125+ Path . Combine ( dir , "manifest.json" ) ,
126+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
127+ ) ;
100128 var ( code , _, stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
101129 Assert . NotEqual ( 0 , code ) ;
102130 Assert . Contains ( "Missing server.command file" , stderr ) ;
103131 }
104132
133+ [ Fact ]
134+ public void Pack_CommandWindowsAlias_Succeeds ( )
135+ {
136+ var aliasDir = Path . Combine (
137+ Path . GetTempPath ( ) ,
138+ "mcpb_windows_alias_" + Guid . NewGuid ( ) . ToString ( "N" )
139+ ) ;
140+ Directory . CreateDirectory ( aliasDir ) ;
141+ var aliasName = "alias-command.exe" ;
142+ File . WriteAllText ( Path . Combine ( aliasDir , aliasName ) , "alias" ) ;
143+ var previousAliases = Environment . GetEnvironmentVariable ( "MCPB_WINDOWS_APP_ALIAS_DIRS" ) ;
144+ Environment . SetEnvironmentVariable ( "MCPB_WINDOWS_APP_ALIAS_DIRS" , aliasDir ) ;
145+ try
146+ {
147+ var dir = CreateTempDir ( ) ;
148+ File . WriteAllText ( Path . Combine ( dir , "icon.png" ) , "fake" ) ;
149+ File . WriteAllText ( Path . Combine ( dir , "server" , "index.js" ) , "// js" ) ;
150+ Directory . CreateDirectory ( Path . Combine ( dir , "shots" ) ) ;
151+ File . WriteAllText ( Path . Combine ( dir , "shots" , "s1.png" ) , "fake" ) ;
152+ var manifest = BaseManifest ( ) ;
153+ manifest . Server . McpConfig . Command = aliasName ;
154+ File . WriteAllText (
155+ Path . Combine ( dir , "manifest.json" ) ,
156+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
157+ ) ;
158+ var ( code , stdout , stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
159+ Assert . Equal ( 0 , code ) ;
160+ Assert . Contains ( "demo@" , stdout ) ;
161+ Assert . DoesNotContain ( "Missing server.command" , stderr ) ;
162+ }
163+ finally
164+ {
165+ Environment . SetEnvironmentVariable ( "MCPB_WINDOWS_APP_ALIAS_DIRS" , previousAliases ) ;
166+ try
167+ {
168+ Directory . Delete ( aliasDir , true ) ;
169+ }
170+ catch
171+ {
172+ // Ignore cleanup failures in tests
173+ }
174+ }
175+ }
176+
177+ [ Fact ]
178+ public void Pack_EntryPointWindowsAlias_Succeeds ( )
179+ {
180+ var aliasDir = Path . Combine (
181+ Path . GetTempPath ( ) ,
182+ "mcpb_windows_alias_" + Guid . NewGuid ( ) . ToString ( "N" )
183+ ) ;
184+ Directory . CreateDirectory ( aliasDir ) ;
185+ var aliasName = "alias-entry.exe" ;
186+ File . WriteAllText ( Path . Combine ( aliasDir , aliasName ) , "alias" ) ;
187+ var previousAliases = Environment . GetEnvironmentVariable ( "MCPB_WINDOWS_APP_ALIAS_DIRS" ) ;
188+ Environment . SetEnvironmentVariable ( "MCPB_WINDOWS_APP_ALIAS_DIRS" , aliasDir ) ;
189+ try
190+ {
191+ var dir = CreateTempDir ( ) ;
192+ File . WriteAllText ( Path . Combine ( dir , "icon.png" ) , "fake" ) ;
193+ File . WriteAllText ( Path . Combine ( dir , "server" , "index.js" ) , "// js" ) ;
194+ Directory . CreateDirectory ( Path . Combine ( dir , "shots" ) ) ;
195+ File . WriteAllText ( Path . Combine ( dir , "shots" , "s1.png" ) , "fake" ) ;
196+ var manifest = BaseManifest ( ) ;
197+ manifest . Server . EntryPoint = aliasName ;
198+ File . WriteAllText (
199+ Path . Combine ( dir , "manifest.json" ) ,
200+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
201+ ) ;
202+ var ( code , stdout , stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
203+ Assert . Equal ( 0 , code ) ;
204+ Assert . Contains ( "demo@" , stdout ) ;
205+ Assert . DoesNotContain ( "Missing entry_point" , stderr ) ;
206+ }
207+ finally
208+ {
209+ Environment . SetEnvironmentVariable ( "MCPB_WINDOWS_APP_ALIAS_DIRS" , previousAliases ) ;
210+ try
211+ {
212+ Directory . Delete ( aliasDir , true ) ;
213+ }
214+ catch
215+ {
216+ // Ignore cleanup failures in tests
217+ }
218+ }
219+ }
220+
105221 [ Fact ]
106222 public void Pack_AllFilesPresent_Succeeds ( )
107223 {
@@ -112,7 +228,10 @@ public void Pack_AllFilesPresent_Succeeds()
112228 File . WriteAllText ( Path . Combine ( dir , "shots" , "s1.png" ) , "fake" ) ;
113229 var manifest = BaseManifest ( ) ;
114230 // Ensure command not path-like (node) so validation doesn't require it to exist as file
115- File . WriteAllText ( Path . Combine ( dir , "manifest.json" ) , JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions ) ) ;
231+ File . WriteAllText (
232+ Path . Combine ( dir , "manifest.json" ) ,
233+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
234+ ) ;
116235 var ( code , stdout , stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
117236 Assert . Equal ( 0 , code ) ;
118237 Assert . Contains ( "demo@" , stdout ) ;
@@ -129,9 +248,12 @@ public void Pack_MissingIconsFile_Fails()
129248 manifest . ManifestVersion = "0.3" ;
130249 manifest . Icons = new List < Mcpb . Core . McpbManifestIcon >
131250 {
132- new ( ) { Src = "icon-16.png" , Size = "16x16" }
251+ new ( ) { Src = "icon-16.png" , Size = "16x16" } ,
133252 } ;
134- File . WriteAllText ( Path . Combine ( dir , "manifest.json" ) , JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions ) ) ;
253+ File . WriteAllText (
254+ Path . Combine ( dir , "manifest.json" ) ,
255+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
256+ ) ;
135257 var ( code , _, stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
136258 Assert . NotEqual ( 0 , code ) ;
137259 Assert . Contains ( "Missing icons[0] file" , stderr ) ;
@@ -149,9 +271,12 @@ public void Pack_IconsFilePresent_Succeeds()
149271 manifest . Screenshots = null ; // Remove screenshots requirement for this test
150272 manifest . Icons = new List < Mcpb . Core . McpbManifestIcon >
151273 {
152- new ( ) { Src = "icon-16.png" , Size = "16x16" }
274+ new ( ) { Src = "icon-16.png" , Size = "16x16" } ,
153275 } ;
154- File . WriteAllText ( Path . Combine ( dir , "manifest.json" ) , JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions ) ) ;
276+ File . WriteAllText (
277+ Path . Combine ( dir , "manifest.json" ) ,
278+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
279+ ) ;
155280 var ( code , stdout , stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
156281 Assert . True ( code == 0 , $ "Pack failed with code { code } . Stderr: { stderr } ") ;
157282 Assert . Contains ( "demo@" , stdout ) ;
@@ -168,9 +293,12 @@ public void Pack_MissingLocalizationResources_Fails()
168293 manifest . Localization = new Mcpb . Core . McpbManifestLocalization
169294 {
170295 Resources = "locales/${locale}/messages.json" ,
171- DefaultLocale = "en-US"
296+ DefaultLocale = "en-US" ,
172297 } ;
173- File . WriteAllText ( Path . Combine ( dir , "manifest.json" ) , JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions ) ) ;
298+ File . WriteAllText (
299+ Path . Combine ( dir , "manifest.json" ) ,
300+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
301+ ) ;
174302 var ( code , _, stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
175303 Assert . NotEqual ( 0 , code ) ;
176304 Assert . Contains ( "Missing localization resources" , stderr ) ;
@@ -190,9 +318,12 @@ public void Pack_LocalizationResourcesPresent_Succeeds()
190318 manifest . Localization = new Mcpb . Core . McpbManifestLocalization
191319 {
192320 Resources = "locales/${locale}/messages.json" ,
193- DefaultLocale = "en-US"
321+ DefaultLocale = "en-US" ,
194322 } ;
195- File . WriteAllText ( Path . Combine ( dir , "manifest.json" ) , JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions ) ) ;
323+ File . WriteAllText (
324+ Path . Combine ( dir , "manifest.json" ) ,
325+ JsonSerializer . Serialize ( manifest , McpbJsonContext . WriteOptions )
326+ ) ;
196327 var ( code , stdout , stderr ) = InvokeCli ( dir , "pack" , dir , "--no-discover" ) ;
197328 Assert . True ( code == 0 , $ "Pack failed with code { code } . Stderr: { stderr } ") ;
198329 Assert . Contains ( "demo@" , stdout ) ;
0 commit comments