11// Copyright (c) Microsoft Corporation. All rights reserved.
22// Licensed under the MIT License.
33
4+ using System . IO . Compression ;
45using System . Security . Cryptography . X509Certificates ;
6+ using System . Text . RegularExpressions ;
57using Winsdk . Cli . Services ;
68
79namespace Winsdk . Cli . Tests ;
@@ -14,6 +16,7 @@ public class PackageCommandTests : BaseCommandTests
1416 private IConfigService _configService = null ! ;
1517 private IBuildToolsService _buildToolsService = null ! ;
1618 private IMsixService _msixService = null ! ;
19+ private IWorkspaceSetupService _workspaceSetupService = null ! ;
1720 private ICertificateService _certificateService = null ! ;
1821
1922 /// <summary>
@@ -62,6 +65,7 @@ public void Setup()
6265
6366 _buildToolsService = GetRequiredService < IBuildToolsService > ( ) ;
6467 _msixService = GetRequiredService < IMsixService > ( ) ;
68+ _workspaceSetupService = GetRequiredService < IWorkspaceSetupService > ( ) ;
6569 _certificateService = GetRequiredService < ICertificateService > ( ) ;
6670 }
6771
@@ -271,7 +275,7 @@ await Assert.ThrowsExactlyAsync<DirectoryNotFoundException>(async () =>
271275 {
272276 await _msixService . CreateMsixPackageAsync (
273277 inputFolder : nonExistentDir ,
274- outputPath : null ,
278+ outputPath : _tempDirectory ,
275279 packageName : "TestPackage" ,
276280 skipPri : true ,
277281 autoSign : false ,
@@ -295,7 +299,7 @@ await Assert.ThrowsExactlyAsync<FileNotFoundException>(async () =>
295299 {
296300 await _msixService . CreateMsixPackageAsync (
297301 inputFolder : packageDir ,
298- outputPath : null ,
302+ outputPath : _tempDirectory ,
299303 packageName : "TestPackage" ,
300304 skipPri : true ,
301305 autoSign : false ,
@@ -335,7 +339,7 @@ public async Task CreateMsixPackageAsync_ExternalManifestWithAssets_CopiesManife
335339
336340 var result = await _msixService . CreateMsixPackageAsync (
337341 inputFolder : packageDir ,
338- outputPath : null ,
342+ outputPath : _tempDirectory ,
339343 packageName : "ExternalTestPackage" ,
340344 skipPri : true ,
341345 autoSign : false ,
@@ -375,7 +379,7 @@ public async Task CreateMsixPackageAsync_WithSigningAndMatchingPublishers_Should
375379 // Act & Assert - This should succeed because publishers match
376380 var result = await _msixService . CreateMsixPackageAsync (
377381 inputFolder : packageDir ,
378- outputPath : null ,
382+ outputPath : _tempDirectory ,
379383 packageName : "SigningTestPackage" ,
380384 skipPri : true ,
381385 autoSign : true ,
@@ -413,7 +417,7 @@ public async Task CreateMsixPackageAsync_WithSigningAndMismatchedPublishers_Shou
413417 {
414418 await _msixService . CreateMsixPackageAsync (
415419 inputFolder : packageDir ,
416- outputPath : null ,
420+ outputPath : _tempDirectory ,
417421 packageName : "MismatchedSigningTest" ,
418422 skipPri : true ,
419423 autoSign : true ,
@@ -460,7 +464,7 @@ await _certificateService.GenerateDevCertificateAsync(
460464 {
461465 await _msixService . CreateMsixPackageAsync (
462466 inputFolder : packageDir ,
463- outputPath : null ,
467+ outputPath : _tempDirectory ,
464468 packageName : "ExternalMismatchTest" ,
465469 skipPri : true ,
466470 autoSign : true ,
@@ -579,4 +583,94 @@ await _certificateService.GenerateDevCertificateAsync(
579583 Assert . Contains ( $ "Error: Publisher in { manifestPath } (CN=ManifestPublisher)", ex . Message ) ;
580584 Assert . Contains ( $ "does not match the publisher in the certificate { certPath } (CN=CertificatePublisher)", ex . Message ) ;
581585 }
586+
587+ [ TestMethod ]
588+ public async Task CreateMsixPackageAsync_WithWindowsAppSdkDependency_AddsPackageDependencyOnNewLine ( )
589+ {
590+ // Arrange - Create package structure with a manifest that has Dependencies but no WinAppSDK dependency
591+ var packageDir = Path . Combine ( _tempDirectory , "WinAppSdkDependencyTest" ) ;
592+ Directory . CreateDirectory ( packageDir ) ;
593+
594+ File . WriteAllText ( Path . Combine ( packageDir , "AppxManifest.xml" ) , StandardTestManifestContent ) ;
595+
596+ // Create Assets directory and files
597+ var assetsDir = Path . Combine ( packageDir , "Assets" ) ;
598+ Directory . CreateDirectory ( assetsDir ) ;
599+ File . WriteAllText ( Path . Combine ( assetsDir , "Logo.png" ) , "fake png content" ) ;
600+ File . WriteAllText ( Path . Combine ( packageDir , "TestApp.exe" ) , "fake exe content" ) ;
601+
602+ // Create winsdk.yaml with Windows App SDK package to trigger dependency injection
603+ var configContent = @"packages:
604+ - name: Microsoft.WindowsAppSDK
605+ version: 2.0.250930001-experimental1" ;
606+ await File . WriteAllTextAsync ( _configService . ConfigPath , configContent ) ;
607+
608+ // Restore
609+ await _workspaceSetupService . SetupWorkspaceAsync ( new WorkspaceSetupOptions
610+ {
611+ BaseDirectory = _tempDirectory ,
612+ ConfigDir = _tempDirectory ,
613+ RequireExistingConfig = true ,
614+ ForceLatestBuildTools = false
615+ } , CancellationToken . None ) ;
616+
617+ // Act - Create package (this should trigger the Windows App SDK dependency injection)
618+ var result = await _msixService . CreateMsixPackageAsync (
619+ inputFolder : packageDir ,
620+ outputPath : _tempDirectory ,
621+ packageName : "WinAppSdkDependencyTest" ,
622+ skipPri : true ,
623+ autoSign : false ,
624+ selfContained : false ,
625+ cancellationToken : CancellationToken . None
626+ ) ;
627+
628+ // Assert - Read the manifest from the package and verify PackageDependency is on its own line
629+ Assert . IsNotNull ( result , "Result should not be null" ) ;
630+ Assert . IsTrue ( File . Exists ( result . MsixPath ) , "MSIX package file should exist" ) ;
631+
632+ // Extract and read the manifest from the created package
633+ var extractDir = Path . Combine ( _tempDirectory , "extracted" ) ;
634+ Directory . CreateDirectory ( extractDir ) ;
635+ ZipFile . ExtractToDirectory ( result . MsixPath , extractDir ) ;
636+
637+ var extractedManifestPath = Path . Combine ( extractDir , "AppxManifest.xml" ) ;
638+ Assert . IsTrue ( File . Exists ( extractedManifestPath ) , "Extracted manifest should exist" ) ;
639+
640+ var finalManifestContent = await File . ReadAllTextAsync ( extractedManifestPath ) ;
641+
642+ // Verify the PackageDependency exists
643+ Assert . Contains ( "<PackageDependency Name=\" Microsoft.WindowsAppRuntime" , finalManifestContent ,
644+ "Manifest should contain Windows App SDK PackageDependency" ) ;
645+
646+ // Verify it's on its own line (not on the same line as </Dependencies>)
647+ // The pattern we're checking: there should be a newline after the PackageDependency closing tag
648+ // and before the </Dependencies> tag
649+ var dependenciesSectionPattern = @"<Dependencies>.*?</Dependencies>" ;
650+ var dependenciesMatch = Regex . Match ( finalManifestContent , dependenciesSectionPattern , RegexOptions . Singleline ) ;
651+ Assert . IsTrue ( dependenciesMatch . Success , "Should find Dependencies section" ) ;
652+
653+ var dependenciesSection = dependenciesMatch . Value ;
654+
655+ // Check that PackageDependency and </Dependencies> are NOT on the same line
656+ var lines = dependenciesSection . Split ( '\n ' ) ;
657+ var packageDependencyLine = lines . FirstOrDefault ( l => l . Contains ( "<PackageDependency" ) ) ;
658+ var closingTagLine = lines . FirstOrDefault ( l => l . Trim ( ) == "</Dependencies>" ) ;
659+
660+ Assert . IsNotNull ( packageDependencyLine , "Should find PackageDependency line" ) ;
661+ Assert . IsNotNull ( closingTagLine , "Should find closing Dependencies tag line" ) ;
662+
663+ // Verify they are different lines
664+ Assert . AreNotEqual ( packageDependencyLine , closingTagLine ,
665+ "PackageDependency and </Dependencies> should be on separate lines" ) ;
666+
667+ // Also verify proper formatting - there should be whitespace/newline between them
668+ var packageDependencyIndex = dependenciesSection . IndexOf ( "<PackageDependency" , StringComparison . InvariantCulture ) ;
669+ var closingBracketIndex = dependenciesSection . IndexOf ( "/>" , packageDependencyIndex , StringComparison . InvariantCulture ) + 2 ;
670+ var closingTagIndex = dependenciesSection . IndexOf ( "</Dependencies>" , closingBracketIndex , StringComparison . InvariantCulture ) ;
671+
672+ var betweenContent = dependenciesSection . Substring ( closingBracketIndex , closingTagIndex - closingBracketIndex ) ;
673+ Assert . Contains ( "\n " , betweenContent ,
674+ "There should be a newline between PackageDependency closing and </Dependencies> tag" ) ;
675+ }
582676}
0 commit comments