Tests/QA/Localization.builtModule.v5.Tests.ps1
<# .NOTES To run manually: $dscResourceModuleName = 'FileSystemDsc' $pathToHQRMTests = Join-Path -Path (Get-Module DscResource.Test).ModuleBase -ChildPath 'Tests\QA' $container = New-PesterContainer -Path "$pathToHQRMTests/Localization.builtModule.*.Tests.ps1" -Data @{ ModuleBase = "./output/$dscResourceModuleName/*" ProjectPath = '.' } Invoke-Pester -Container $container -Output Detailed #> param ( [Parameter(Mandatory = $true)] [System.String] $ModuleBase, [Parameter()] [System.String] $ProjectPath, [Parameter(ValueFromRemainingArguments = $true)] $Args ) # This test _must_ be outside the BeforeDiscovery-block since Pester 4 does not recognizes it. $isPesterMinimum5 = (Get-Module -Name Pester).Version -ge '5.1.0' # Only run if Pester 5.1 or higher. if (-not $isPesterMinimum5) { Write-Verbose -Message 'Repository is using old Pester version, new HQRM tests for Pester v5 and v6 are skipped.' -Verbose return } <# This _must_ be outside any Pester blocks for correct script parsing. Sets It and Context block's default parameter value to handle Pester v6's ForEach change, to keep same behavior as with Pester v5. The default parameter is removed at the end of the script to avoid affecting other tests. #> $PSDefaultParameterValues['Context:AllowNullOrEmptyForEach'] = $true $PSDefaultParameterValues['It:AllowNullOrEmptyForEach'] = $true BeforeDiscovery { # Re-imports the private (and public) functions. Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../DscResource.Test.psm1') -Force # trim * from modulebase for -Depth to work $localModuleBase = $ModuleBase.Replace('*', '') $moduleFiles = @(Get-ChildItem -Path $localModuleBase -Filter '*.psm1' -Depth 1) if ($ProjectPath) { # Expand the project folder if it is a relative path. $resolvedProjectPath = (Resolve-Path -Path $ProjectPath).Path } else { $resolvedProjectPath = $ModuleBase } <# Exclude empty PSM1. Only expect localization for Module files with some functions defined #> $moduleFiles = $moduleFiles | Where-Object -FilterScript { <# Ignore parse errors in the script files. Parse error will be caught in the tests in ModuleScriptFiles.common. #> $currentPath = $_.FullName try { Get-FunctionDefinitionAst -FullName $currentPath $valid = $true } catch { # Outputting the error just in case there is another error than parse error. Write-Warning -Message ('File ''{0}'' is skipped because it could not be parsed. Error message: {1}' -f $currentPath, $_.Exception.Message) $valid = $false } $_.Length -gt 0 -and $valid } $scriptLocalizationToTest = @() $thisLocalizationToTest = @() foreach ($file in $moduleFiles) { if ($VerbosePreference -ne 'SilentlyContinue') { Write-Verbose -Message "$($file | ConvertTo-Json)" } # Use the project folder to extrapolate relative path. $descriptiveName = Get-RelativePathFromModuleRoot -FilePath $file.FullName -ModuleRootFilePath $resolvedProjectPath $scriptTestProperties = @{ File = $file DescriptiveName = $descriptiveName LocalizationFolderPath = (Join-Path -Path $file.Directory.FullName -ChildPath 'en-US') LocalizationFile = (Join-Path -Path $file.Directory.FullName -ChildPath (Join-Path -Path 'en-US' -ChildPath "$($file.BaseName).strings.psd1")) } $localizedKeyToTest = @() $usedLocalizedKeyToTest = @() <# Build test cases for all localized strings that are in the localized string file and all that are used in the module file. Skips a file that do not exist yet (it are caught in a test) #> if (Test-Path -Path $scriptTestProperties.LocalizationFile) { Import-LocalizedData ` -BindingVariable 'englishLocalizedStrings' ` -FileName ('{0}.strings.psd1' -f $scriptTestProperties.File.BaseName) ` -BaseDirectory $scriptTestProperties.LocalizationFolderPath ` -UICulture 'en-US' foreach ($localizedKey in $englishLocalizedStrings.Keys) { $localizedKeyToTest += @{ LocalizedKey = $localizedKey } } $definitionAst = [System.Management.Automation.Language.Parser]::ParseFile($scriptTestProperties.File.FullName, [ref] $null, [ref] $null) # Look for script:localizedData $astFilter = { $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and $args[0].Parent -is [System.Management.Automation.Language.MemberExpressionAst] -and $args[0].Parent.Expression -is [System.Management.Automation.Language.VariableExpressionAst] -and $args[0].Parent.Expression.VariablePath.UserPath -eq 'script:localizedData' } $localizationStringConstantsAst = $definitionAst.FindAll($astFilter, $true) if ($localizationStringConstantsAst) { $usedLocalizationKeys = $localizationStringConstantsAst.Value | Sort-Object -Unique foreach ($localizedKey in $usedLocalizationKeys) { $usedLocalizedKeyToTest += @{ LocalizedKey = $localizedKey } } } } $scriptTestProperties.EnUsLocalizedKeys = $localizedKeyToTest $scriptTestProperties.UsedLocalizedKeys = $usedLocalizedKeyToTest $otherLanguageToTest = @() # Get all localization folders except the en-US (regardless of casing). $localizationFolders = Get-ChildItem -Path $scriptTestProperties.File.Directory.FullName -Directory -Filter '*-*' | Where-Object -FilterScript { $_.Name -ne 'en-US' } foreach ($localizationFolder in $localizationFolders) { $cultureToTest = @{ LocalizationFolderName = Split-Path -Path $localizationFolder.FullName -Leaf LocalizationFolderPath = $localizationFolder.FullName } $localizedKeyToTest = @() <# Build test cases for all localized strings that are in the culture's localized string file. Skips a file that do not exist yet (it are caught in a test) #> if (Test-Path -Path $cultureToTest.LocalizationFolderPath) { Import-LocalizedData ` -BindingVariable 'cultureLocalizedStrings' ` -FileName "$($scriptTestProperties.File.BaseName).strings.psd1" ` -BaseDirectory $cultureToTest.LocalizationFolderPath ` -UICulture $otherLanguageToTest.LocalizationFolderName foreach ($localizedKey in $cultureLocalizedStrings.Keys) { $localizedKeyToTest += @{ CultureLocalizedKey = $localizedKey } } } $cultureToTest.CultureLocalizedKeys = $localizedKeyToTest $otherLanguageToTest += $cultureToTest } $scriptTestProperties.OtherLanguages = $otherLanguageToTest $scriptLocalizationToTest += $scriptTestProperties # Now check the class localization for this file $classDefinitionAst = Get-ClassDefinitionAst -FullName $file.FullName foreach ($class in $classDefinitionAst) { if ($class.Attributes.TypeName.Name -ieq 'DscResource') { $classTestProperties = @{ ClassName = $class.Name LocalizationFolderPath = $scriptTestProperties.LocalizationFolderPath LocalizationFile = (Join-Path -Path $file.Directory.FullName -ChildPath (Join-Path -Path 'en-US' -ChildPath "$($class.Name).strings.psd1")) } } else { # if this is not a DscResource then skip to next item continue } $localizedKeyToTest = @() $usedLocalizedKeyToTest = @() <# Build test cases for all localized strings that are in the localized string file and all that are used in the module file. Skips a file that do not exist yet (it are caught in a test) #> if (Test-Path -Path $classTestProperties.LocalizationFile) { Import-LocalizedData ` -BindingVariable 'englishLocalizedStrings' ` -FileName ('{0}.strings.psd1' -f $class.Name) ` -BaseDirectory $classTestProperties.LocalizationFolderPath ` -UICulture 'en-US' foreach ($localizedKey in $englishLocalizedStrings.Keys) { $localizedKeyToTest += @{ LocalizedKey = $localizedKey } } # Look for $this.localizedData $astFilter = { $args[0] -is [System.Management.Automation.Language.StringConstantExpressionAst] -and $args[0].Parent -is [System.Management.Automation.Language.MemberExpressionAst] -and $args[0].Parent.Expression.Member.Value -eq 'localizedData' } $localizationStringConstantsAst = $class.FindAll($astFilter, $true) if ($localizationStringConstantsAst) { $usedLocalizationKeys = $localizationStringConstantsAst.Value | Sort-Object -Unique foreach ($localizedKey in $usedLocalizationKeys) { $usedLocalizedKeyToTest += @{ LocalizedKey = $localizedKey } } } } $classTestProperties.EnUsLocalizedKeys = $localizedKeyToTest $classTestProperties.UsedLocalizedKeys = $usedLocalizedKeyToTest $otherLanguageToTest = @() # Get all localization folders except the en-US (regardless of casing). $localizationFolders = Get-ChildItem -Path $file.Directory.FullName -Directory -Filter '*-*' | Where-Object -FilterScript { $_.Name -ne 'en-US' } foreach ($localizationFolder in $localizationFolders) { $cultureToTest = @{ LocalizationFolderName = Split-Path -Path $localizationFolder.FullName -Leaf LocalizationFolderPath = $localizationFolder.FullName ClassName = $class.Name } $localizedKeyToTest = @() <# Build test cases for all localized strings that are in the culture's localized string file. Skips a file that do not exist yet (it are caught in a test) #> $localizationFile = (Join-Path -Path $cultureToTest.LocalizationFolderPath -ChildPath "$($class.Name).strings.psd1") $cultureToTest.LocalizationFile = $localizationFile if (Test-Path -Path $localizationFile) { Import-LocalizedData ` -BindingVariable 'cultureLocalizedStrings' ` -FileName "$($class.Name).strings.psd1" ` -BaseDirectory $cultureToTest.LocalizationFolderPath ` -UICulture $otherLanguageToTest.LocalizationFolderName foreach ($localizedKey in $cultureLocalizedStrings.Keys) { $localizedKeyToTest += @{ CultureLocalizedKey = $localizedKey } } } $cultureToTest.CultureLocalizedKeys = $localizedKeyToTest $otherLanguageToTest += $cultureToTest } $classTestProperties.OtherLanguages = $otherLanguageToTest $thisLocalizationToTest += $classTestProperties } } } BeforeAll { # Re-imports the private (and public) functions. Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../DscResource.Test.psm1') -Force } AfterAll { # Re-import just the public functions. Import-Module -Name 'DscResource.Test' -Force } Describe 'BuiltModule Tests - Validate Localization' -Tag 'BuiltModule Tests - Validate Localization' { Context 'When the module ''<DescriptiveName>'' exists' -ForEach $scriptLocalizationToTest { It 'Should have en-US localization folder' { Test-Path -Path $LocalizationFolderPath | Should -BeTrue -Because "the en-US folder $LocalizationFolderPath must exist" } It 'Should have en-US localization folder with the correct casing' { <# This will return both 'en-us' and 'en-US' folders so we can evaluate casing. #> $localizationFolderOnDisk = Get-Item -Path $LocalizationFolderPath -ErrorAction 'SilentlyContinue' $localizationFolderOnDisk.Name | Should -MatchExactly 'en-US' -Because 'the en-US folder must have the correct casing' } It 'Should have en-US localization string resource file' { Test-Path -Path $LocalizationFile | Should -BeTrue -Because "the string resource file $LocalizationFile must exist in the localization folder en-US" } Context 'When the en-US localized resource file have localized strings' { <# This ForEach is using the key EnUsLocalizedKeys from inside the $fileToTest that is set on the Context-block's ForEach above. #> It 'Should use the localized string key ''<LocalizedKey>'' in the code' -ForEach $EnUsLocalizedKeys { $UsedLocalizedKeys.LocalizedKey | Should -Contain $LocalizedKey -Because 'the key exists in the localized string resource file so it should also exist in the resource/module script file' } <# This ForEach is using the key UsedLocalizedKeys from inside the $fileToTest that is set on the Context-block's ForEach above. #> It 'Should not be missing the localized string key ''<LocalizedKey>'' in the localization resource file' -ForEach $UsedLocalizedKeys { $EnUsLocalizedKeys.LocalizedKey | Should -Contain $LocalizedKey -Because 'the key is used in the resource/module script file so it should also exist in the localized string resource files' } } Context 'When a resource or module is localized in the language <LocalizationFolderName>' -ForEach $OtherLanguages { It 'Should have a localization string file in the localization folder' { $localizationResourceFilePath = Join-Path -Path $LocalizationFolderPath -ChildPath "$($File.BaseName).strings.psd1" Test-Path -Path $localizationResourceFilePath | Should -BeTrue -Because ('there must exist a string resource file ''{0}.strings.psd1'' in the localization folder ''{1}''' -f $File.BaseName, $LocalizationFolderPath) } It 'Should be an accurate localization folder with the correct casing' { $localizationFolderOnDisk = Get-Item -Path $LocalizationFolderPath -ErrorAction 'SilentlyContinue' $localizationFolderOnDisk.Name -cin ([System.Globalization.CultureInfo]::GetCultures([System.Globalization.CultureTypes]::AllCultures)).Name | Should -BeTrue } Context 'When the <LocalizationFolderName> localized resource file have localized strings' { <# This ForEach is using the key CultureLocalizedKeys from inside the $fileToTest that is set on the Context-block's ForEach above. #> It 'Should have the string key <CultureLocalizedKey> in the en-US localization resource file' -ForEach $CultureLocalizedKeys { $EnUsLocalizedKeys.LocalizedKey | Should -Contain $CultureLocalizedKey -Because ('the key exists in the {0} localization resource file it must also also exist in the en-US localization resource file' -f $LocalizationFolderName) } <# This ForEach is using the key EnUsLocalizedKeys from inside the $fileToTest that is set on the Context-block's ForEach above. #> It 'Should not be missing the localization string key <LocalizedKey>'-ForEach $EnUsLocalizedKeys { $CultureLocalizedKeys.CultureLocalizedKey | Should -Contain $LocalizedKey -Because ('the key exists in the en-US localization resource file so it should also exist in the {0} localization resource file (if you cannot translate the english string in the localized file, then please just add the en-US localization string key together with the en-US text string)' -f $LocalizationFolderName) } } } } Context 'When the class ''<ClassName>'' exists' -ForEach $thisLocalizationToTest { It 'Should have en-US localization folder' { Test-Path -Path $LocalizationFolderPath | Should -BeTrue -Because "the en-US folder $LocalizationFolderPath must exist" } It 'Should have en-US localization folder with the correct casing' { <# This will return both 'en-us' and 'en-US' folders so we can evaluate casing. #> $localizationFolderOnDisk = Get-Item -Path $LocalizationFolderPath -ErrorAction 'SilentlyContinue' $localizationFolderOnDisk.Name | Should -MatchExactly 'en-US' -Because 'the en-US folder must have the correct casing' } It 'Should have en-US localization string resource file' { Test-Path -Path $LocalizationFile | Should -BeTrue -Because "the string resource file $LocalizationFile must exist in the localization folder en-US" } Context 'When the en-US localized resource file have localized strings' { <# This ForEach is using the key EnUsLocalizedKeys from inside the $fileToTest that is set on the Context-block's ForEach above. #> It 'Should use the localized string key ''<LocalizedKey>'' in the code' -ForEach $EnUsLocalizedKeys { $UsedLocalizedKeys.LocalizedKey | Should -Contain $LocalizedKey -Because 'the key exists in the localized string resource file so it should also exist in the resource/module script file' } <# This ForEach is using the key UsedLocalizedKeys from inside the $fileToTest that is set on the Context-block's ForEach above. #> It 'Should not be missing the localized string key ''<LocalizedKey>'' in the localization resource file' -ForEach $UsedLocalizedKeys { $EnUsLocalizedKeys.LocalizedKey | Should -Contain $LocalizedKey -Because 'the key is used in the resource/module script file so it should also exist in the localized string resource files' } } Context 'When a resource or module is localized in the language <LocalizationFolderName>' -ForEach $OtherLanguages { It 'Should have a localization string file in the localization folder' { Test-Path -Path $LocalizationFile | Should -BeTrue -Because ('there must exist a string resource file ''{0}.strings.psd1'' in the localization folder ''{1}''' -f $ClassName, $LocalizationFolderPath) } It 'Should be an accurate localization folder with the correct casing' { $localizationFolderOnDisk = Get-Item -Path $LocalizationFolderPath -ErrorAction 'SilentlyContinue' $localizationFolderOnDisk.Name -cin ([System.Globalization.CultureInfo]::GetCultures([System.Globalization.CultureTypes]::AllCultures)).Name | Should -BeTrue } Context 'When the <LocalizationFolderName> localized resource file have localized strings' { <# This ForEach is using the key CultureLocalizedKeys from inside the $fileToTest that is set on the Context-block's ForEach above. #> It 'Should have the string key <CultureLocalizedKey> in the en-US localization resource file' -ForEach $CultureLocalizedKeys { $EnUsLocalizedKeys.LocalizedKey | Should -Contain $CultureLocalizedKey -Because ('the key exists in the {0} localization resource file it must also also exist in the en-US localization resource file' -f $LocalizationFolderName) } <# This ForEach is using the key EnUsLocalizedKeys from inside the $fileToTest that is set on the Context-block's ForEach above. #> It 'Should not be missing the localization string key <LocalizedKey>'-ForEach $EnUsLocalizedKeys { $CultureLocalizedKeys.CultureLocalizedKey | Should -Contain $LocalizedKey -Because ('the key exists in the en-US localization resource file so it should also exist in the {0} localization resource file (if you cannot translate the english string in the localized file, then please just add the en-US localization string key together with the en-US text string)' -f $LocalizationFolderName) } } } } } $PSDefaultParameterValues.Remove('Context:AllowNullOrEmptyForEach') $PSDefaultParameterValues.Remove('It:AllowNullOrEmptyForEach') |