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. $isPester5 = (Get-Module -Name Pester).Version -ge '5.1.0' # Only run if Pester 5.1. if (-not $isPester5) { Write-Verbose -Message 'Repository is using old Pester version, new HQRM tests for Pester 5 are skipped.' -Verbose return } 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) } } } } } |