winget-cli

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

commit fb540a28c54a4ec9fbd688985570cd2fcc118c1d
parent 11b19469932e7511c4e1b1d30945804276d5fc4b
Author: Ryan <69221034+ryfu-msft@users.noreply.github.com>
Date:   Mon,  4 Dec 2023 11:24:04 -0800

Add WingetDSC E2E tests (#3939)


Diffstat:
M.github/actions/spelling/allow.txt | 1+
Mazure-pipelines.yml | 7+++++++
Msrc/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 | 101++++++++++++++++++++++++++++++++++++-------------------------------------------
Asrc/PowerShell/tests/Microsoft.WinGet.DSC.Tests.ps1 | 241+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/PowerShell/tests/RunTests.ps1 | 9++++++++-
5 files changed, 303 insertions(+), 56 deletions(-)

diff --git a/.github/actions/spelling/allow.txt b/.github/actions/spelling/allow.txt @@ -189,6 +189,7 @@ nonexistentsetting norestart normalizednameandpublisher normalizedpackagenameandpublisher +notcontains NTSTATUS nullsoft nunit diff --git a/azure-pipelines.yml b/azure-pipelines.yml @@ -466,6 +466,13 @@ jobs: Contents: '**\*' TargetFolder: '$(Build.ArtifactStagingDirectory)' + - task: CopyFiles@2 + displayName: 'Copy Microsoft.WinGet.DSC module to staging directory' + inputs: + SourceFolder: '$(Build.SourcesDirectory)\src\PowerShell\Microsoft.WinGet.DSC' + Contents: '**\*' + TargetFolder: '$(Build.ArtifactStagingDirectory)\Microsoft.WinGet.DSC' + - task: PowerShell@2 displayName: Install Tests Dependencies inputs: diff --git a/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 b/src/PowerShell/Microsoft.WinGet.DSC/Microsoft.WinGet.DSC.psm1 @@ -221,10 +221,11 @@ class WinGetSources foreach ($packageCatalogReference in $packageCatalogReferences) { $source = @{ - Arg = $packageCatalogReference.Info.Argument - Identifier = $packageCatalogReference.Info.Id - Name = $packageCatalogReference.Info.Name - Type = $packageCatalogReference.Info.Type + $packageCatalogReference.Name = @{ + Identifier = $packageCatalogReference.Id + Arg = $packageCatalogReference.Argument + Type = $packageCatalogReference.Type + } } $wingetSources.Add($source) } @@ -240,46 +241,39 @@ class WinGetSources [bool] Test() { $currentSources = $this.Get().Sources + $currentState = [Ensure]::Present - # If this is a full match and the counts are different give up. - if (($this.Action -eq [WinGetAction]::Full) -and ($this.Sources.Count -ne $currentSources.Count)) + # If this is a full match and the counts are different return false. This only applies if we want to ensure the full source is present. + if (($this.Action -eq [WinGetAction]::Full) -and ($this.Sources.Count -ne $currentSources.Count) -and ($this.Ensure -eq [Ensure]::Present)) { return $false } - # There's no need to differentiate between Partial and Full anymore. - foreach ($source in $this.Sources) + foreach ($sourceName in $this.Sources.Keys) { - # Require Name and Arg. - if ((-not $source.ContainsKey("Name")) -or [string]::IsNullOrWhiteSpace($source.Name)) - { - # TODO: Localize. - throw "Invalid source input. Name is required." - } - - if ((-not $source.ContainsKey("Arg")) -or [string]::IsNullOrWhiteSpace($source.Arg)) + #Check if the source name exists, if it doesn't, then return false. + $result = $currentSources.Keys | Where-Object { $_ -eq $sourceName } + if ($null -eq $result) { - # TODO: Localize. - throw "Invalid source input. Arg is required." + $currentState = [Ensure]::Absent } # Type has a default value. - $sourceType = "Microsoft.PreIndexed.Package" + $source = $this.Sources.$($sourceName) + $sourceType = "Microsoft.PreIndexed.Package" # default source type if ($source.ContainsKey("Type") -and (-not([string]::IsNullOrWhiteSpace($source.Type)))) { $sourceType = $source.Type } - $result = $currentSources | Where-Object { $_.Name -eq $source.Name -and $_.Arg -eq $source.Arg -and $_.Type -eq $sourceType } - - # Source not found. - if ($null -eq $result) + $existingSource = $currentSources.$($sourceName) + if ($source.Arg -ne $existingSource.Arg -or $sourceType -ne $existingSource.Type) { - return $false + $currentState = [Ensure]::Absent } } - return $true + return $currentState -eq $this.Ensure } # Sets the desired properties. @@ -290,40 +284,37 @@ class WinGetSources Assert-WinGetCommand "Reset-WinGetSource" Assert-WinGetCommand "Remove-WinGetSource" - foreach ($source in $this.Sources) + if (-not $this.Test()) { - $sourceType = "Microsoft.PreIndexed.Package" - - # Require Name and Arg. - if ((-not $source.ContainsKey("Name")) -or [string]::IsNullOrWhiteSpace($source.Name)) - { - # TODO: Localize. - throw "Invalid source input. Name is required." - } - - if ((-not $source.ContainsKey("Arg")) -or [string]::IsNullOrWhiteSpace($source.Arg)) - { - # TODO: Localize. - throw "Invalid source input. Arg is required." - } - - if ($source.ContainsKey("Type") -and (-not([string]::IsNullOrWhiteSpace($source.Type)))) - { - $sourceType = $source.Type - } - - if ($this.Ensure -eq [Ensure]::Present) + foreach ($sourceName in $this.Sources.Keys) { - Add-WinGetSource -Name $source.Name -Argument $source.Argument -Type $sourceType - - if ($this.Reset) + $sourceType = "Microsoft.PreIndexed.Package" + $source = $this.Sources.$($sourceName) + + if ((-not $source.ContainsKey("Arg")) -or [string]::IsNullOrWhiteSpace($source.Arg)) { - Reset-WinGetSource -Name $source.Name + # TODO: Localize. + throw "Invalid source input. Arg is required." + } + + if ($source.ContainsKey("Type") -and (-not([string]::IsNullOrWhiteSpace($source.Type)))) + { + $sourceType = $source.Type + } + + if ($this.Ensure -eq [Ensure]::Present) + { + Add-WinGetSource -Name $sourceName -Argument $source.Arg -Type $sourceType + + if ($this.Reset) + { + Reset-WinGetSource -Name $sourceName + } + } + else + { + Remove-WinGetSource -Name $sourceName } - } - else - { - Remove-WinGetSource -Name $source.Name } } } diff --git a/src/PowerShell/tests/Microsoft.WinGet.DSC.Tests.ps1 b/src/PowerShell/tests/Microsoft.WinGet.DSC.Tests.ps1 @@ -0,0 +1,241 @@ +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. + +<# +.Synopsis + Pester tests related to the Microsoft.WinGet.DSC PowerShell module. + The tests require the localhost web server to be running and serving the test data. + 'Invoke-Pester' should be called in an admin PowerShell window. +#> + +BeforeAll { + Install-Module -Name PSDesiredStateConfiguration -Force -SkipPublisherCheck + Import-Module Microsoft.WinGet.Client + Import-Module Microsoft.WinGet.DSC + + # Helper function for calling Invoke-DscResource on the Microsoft.WinGet.DSC module. + function InvokeWinGetDSC() { + param ( + [Parameter()] + [string]$Name, + + [Parameter()] + [string]$Method, + + [Parameter()] + [hashtable]$Property + ) + + return Invoke-DscResource -Name $Name -ModuleName Microsoft.WinGet.DSC -Method $Method -Property $Property + } +} + +Describe 'List available DSC resources'{ + It 'Shows DSC Resources'{ + $expectedDSCResources = "WinGetAdminSettings", "WinGetPackage", "WinGetPackageManager", "WinGetSources", "WinGetUserSettings" + $availableDSCResources = (Get-DscResource -Module Microsoft.WinGet.DSC).Name + $availableDSCResources.length | Should -Be 5 + $availableDSCResources | Where-Object {$expectedDSCResources -notcontains $_} | Should -BeNullOrEmpty -ErrorAction Stop + } +} + +Describe 'WinGetAdminSettings' { + + BeforeAll { + $initialAdminSettings = (Get-WinGetSettings).adminSettings + $adminSettingsHash = @{ + BypassCertificatePinningForMicrosoftStore = !$initialAdminSettings.BypassCertificatePinningForMicrosoftStore; + InstallerHashOverride = !$initialAdminSettings.InstallerHashOverride; + LocalManifestFiles = !$initialAdminSettings.LocalManifestFiles; + LocalArchiveMalwareScanOverride = !$initialAdminSettings.LocalArchiveMalwareScanOverride; + } + } + + It 'Get admin settings' { + $result = InvokeWinGetDSC -Name WinGetAdminSettings -Method Get -Property @{ Settings = $adminSettingsHash } + $adminSettings = $result.Settings + $adminSettings.BypassCertificatePinningForMicrosoftStore | Should -Be $initialAdminSettings.BypassCertificatePinningForMicrosoftStore + $adminSettings.InstallerHashOverride | Should -Be $initialAdminSettings.InstallerHashOverride + $adminSettings.LocalManifestFiles | Should -Be $initialAdminSettings.LocalManifestFiles + $adminSettings.LocalArchiveMalwareScanOverride | Should -Be $initialAdminSettings.LocalArchiveMalwareScanOverride + } + + It 'Test admin settings' { + $result = InvokeWinGetDSC -Name WinGetAdminSettings -Method Test -Property @{ Settings = $adminSettingsHash } + $result.InDesiredState | Should -Be $false + } + + It 'Set admin settings' { + InvokeWinGetDSC -Name WinGetAdminSettings -Method Set -Property @{ Settings = $adminSettingsHash } + + # Verify settings were applied. + $result = InvokeWinGetDSC -Name WinGetAdminSettings -Method Get -Property @{ Settings = $adminSettingsHash } + $adminSettings = $result.Settings + $adminSettings.BypassCertificatePinningForMicrosoftStore | Should -Not -Be $initialAdminSettings.BypassCertificatePinningForMicrosoftStore + $adminSettings.InstallerHashOverride | Should -Not -Be $initialAdminSettings.InstallerHashOverride + $adminSettings.LocalManifestFiles | Should -Not -Be $initialAdminSettings.LocalManifestFiles + $adminSettings.LocalArchiveMalwareScanOverride | Should -Not -Be $initialAdminSettings.LocalArchiveMalwareScanOverride + + $testResult = InvokeWinGetDSC -Name WinGetAdminSettings -Method Test -Property @{ Settings = $adminSettingsHash } + $testResult | Should -Be $true + } + + AfterAll { + InvokeWinGetDSC -Name WinGetAdminSettings -Method Set -Property @{ Settings = $initialAdminSettings } + } +} + +Describe 'WinGetUserSettings' { + BeforeAll { + # Delete existing user settings file. + $settingsFilePath = (Get-WinGetSettings).userSettingsFile + $backupSettingsFilePath = $settingsFilePath + ".backup" + + if (Test-Path -Path $settingsFilePath) + { + Remove-Item $settingsFilePath + } + + if (Test-Path -Path $backupSettingsFilePath) + { + Remove-Item $backupSettingsFilePath + } + + $userSettingsHash = @{ + experimentalFeatures = @{ directMSI = $true }; + installBehavior = @{ Preferences = @{ Scope = 'User' }} + } + } + + It 'Get user settings' { + $result = InvokeWinGetDSC -Name WinGetUserSettings -Method Get -Property @{ Settings = $userSettingsHash } + $result.Settings.Count | Should -Be 0 + } + + It 'Test user settings' { + $result = InvokeWinGetDSC -Name WinGetUserSettings -Method Test -Property @{ Settings = $userSettingsHash } + $result.InDesiredState | Should -Be $false + } + + It 'Set user settings' { + InvokeWinGetDSC -Name WinGetUserSettings -Method Set -Property @{ Settings = $userSettingsHash } + + # Verify user settings were applied. + $result = InvokeWinGetDSC -Name WinGetUserSettings -Method Get -Property @{ Settings = $userSettingsHash } + $userSettings = $result.Settings + $userSettings.experimentalFeatures.directMSI | Should -Be $true + $userSettings.installBehavior.Preferences.Scope | Should -Be 'User' + } +} + +Describe 'WinGetSources' { + BeforeAll { + $testSourceName = 'TestSource' + + $testSourceValue = @{ + Type = 'Microsoft.PreIndexed.Package' + Arg = 'https://localhost:5001/TestKit/' + } + + InvokeWinGetDSC -Name WinGetSources -Method Set -Property @{ Action = 'Partial'; Ensure = 'Absent'; Sources = @{ $testSourceName = $testSourceValue }} + } + + It 'Get WinGet source' { + $result = InvokeWinGetDSC -Name WinGetSources -Method Get -Property @{ Sources = @{ $testSourceName = $testSourceValue }} + $result.Sources.Keys | Should -Not -Contain $testSourceName + } + + It 'Test WinGet source' { + $result = InvokeWinGetDSC -Name WinGetSources -Method Test -Property @{ Ensure='Present'; Sources = @{ $testSourceName = $testSourceValue }} + $result.InDesiredState | Should -Be $false + } + + It 'Set WinGet source' { + InvokeWinGetDSC -Name WinGetSources -Method Set -Property @{ Ensure = 'Present'; Sources = @{ $testSourceName = $testSourceValue }} + $result = InvokeWinGetDSC -Name WinGetSources -Method Get -Property @{ Sources = @{ $testSourceName = $testSourceValue }} + $result.Sources.Keys | Should -Contain $testSourceName + + $testSource = $result.Sources.$($testSourceName) + $testSource.Type | Should -Be 'Microsoft.PreIndexed.Package' + $testSource.Arg | Should -Be 'https://localhost:5001/TestKit/' + $testSource.Identifier | Should -Be $null + } +} + +Describe 'WinGetPackage' { + BeforeAll { + $testPackageId = 'AppInstallerTest.TestExeInstaller' + $testPackageVersion = '1.0.0.0' + + # Add test source. + InvokeWinGetDSC -Name WinGetSources -Method Set -Property @{ Action = 'Partial'; Ensure = 'Present'; Sources = @{ TestSource = @{ Arg = 'https://localhost:5001/TestKit/'; Type = 'Microsoft.PreIndexed.Package' }}} + } + + It 'Get WinGetPackage' { + $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Id = $testPackageId; Version = $testPackageVersion } + $result.IsInstalled | Should -Be $false + } + + It 'Test WinGetPackage' { + $result = InvokeWinGetDSC -Name WinGetPackage -Method Test -Property @{ Id = $testPackageId; Version = $testPackageVersion } + $result.InDesiredState | Should -Be $false + } + + It 'Install WinGetPackage' { + InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Id = $testPackageId; Version = $testPackageVersion } + + # Verify package installed. + $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Id = $testPackageId; Version = $testPackageVersion } + $result.IsInstalled | Should -Be $true + $result.IsUpdateAvailable | Should -Be $true + $result.InstalledVersion | Should -Be 1.0.0.0 + } + + It 'Update WinGetPackage' { + $testResult = InvokeWinGetDSC -Name WinGetPackage -Method Test -Property @{ Id = $testPackageId; UseLatest = $true } + $testResult.InDesiredState | Should -Be $false + + InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Id = $testPackageId; UseLatest = $true } + + # Verify package updated. + $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Id = $testPackageId; UseLatest = $true } + $result.IsInstalled | Should -Be $true + $result.IsUpdateAvailable | Should -Be $false + $result.InstalledVersion | Should -Not -Be 1.0.0.0 + } + + It 'Uninstall WinGetPackage' { + InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Id = $testPackageId; UseLatest = $true } + + $testResult = InvokeWinGetDSC -Name WinGetPackage -Method Test -Property @{ Ensure = 'Absent'; Id = $testPackageId } + $testResult.InDesiredState | Should -Be $false + + InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Ensure = 'Absent'; Id = $testPackageId } + + # Verify package uninstalled. + $result = InvokeWinGetDSC -Name WinGetPackage -Method Get -Property @{ Ensure = 'Absent'; Id = $testPackageId } + $result.IsInstalled | Should -Be $false + } + + AfterAll { + InvokeWinGetDSC -Name WinGetPackage -Method Set -Property @{ Ensure = 'Absent'; Id = $testPackageId} + } +} + +Describe 'WinGetPackageManager' { + It 'Get WinGet version' { + $result = InvokeWinGetDSC -Name WinGetPackageManager -Method Get -Property @{} + $result.Version | Should -Not -Be $null + } + + It 'Test WinGet version' { + $result = InvokeWinGetDSC -Name WinGetPackageManager -Method Test -Property @{ Version = "1.2.3.4" } + $result.InDesiredState | Should -Be $false + + $currentVersion = Get-WinGetVersion + $result = InvokeWinGetDSC -Name WinGetPackageManager -Method Test -Property @{ Version = $currentVersion } + $result.InDesiredState | Should -Be $true + } + + # TODO: Add test to verify Set method for WinGetPackageManager +} diff --git a/src/PowerShell/tests/RunTests.ps1 b/src/PowerShell/tests/RunTests.ps1 @@ -69,5 +69,12 @@ if ($PSEdition -eq "Core") $configConfig.Run.Container = New-PesterContainer -Path "$PSScriptRoot\Microsoft.WinGet.Configuration.Tests.ps1" -Data @{ ConfigurationTestDataPath = $ConfigurationTestDataPath } Invoke-Pester -Configuration $configConfig -} + $dscConfig = New-PesterConfiguration + $dscConfig.TestResult.OutputFormat = "NUnitXML" + $dscConfig.TestResult.OutputPath = "$outputPath\Tests-WinGetDSC.XML" + $dscConfig.TestResult.Enabled = $true + $dscConfig.Run.Container = New-PesterContainer -Path "$PSScriptRoot\Microsoft.WinGet.DSC.Tests.ps1" + + Invoke-Pester -Configuration $dscConfig +}