Public/Tests/New-ModuleCustomTemplate.Tests.ps1
|
BeforeDiscovery { $HasScriptAnalyzer = [bool](Get-Module -ListAvailable -Name PSScriptAnalyzer) } BeforeAll { $env:TCS_CONFIG_ROOT = Join-Path -Path $TestDrive -ChildPath 'config' $env:TCS_SKIP_UPDATE_CHECK = '1' $env:TCS_TELEMETRY_OPTOUT = '1' $ModuleRoot = Split-Path -Path (Split-Path -Path $PSScriptRoot -Parent) -Parent Import-Module -Name (Join-Path -Path $ModuleRoot -ChildPath 'tcs.utils.psd1') -Force } AfterAll { Remove-Module -Name tcs.utils -Force -ErrorAction SilentlyContinue } Describe 'New-ModuleCustomTemplate' { BeforeAll { function Get-ParseErrorCount { param([string]$Path) $tokens = $null $errors = $null $null = [System.Management.Automation.Language.Parser]::ParseFile($Path, [ref]$tokens, [ref]$errors) @($errors).Count } $SettingsPath = Join-Path -Path (Split-Path -Path (Split-Path -Path $ModuleRoot -Parent) -Parent) -ChildPath 'PSScriptAnalyzerSettings.psd1' } BeforeEach { $Target = Join-Path -Path $TestDrive -ChildPath ([guid]::NewGuid().ToString('N')) $null = New-Item -Path $Target -ItemType Directory } Context 'Generated files' { BeforeEach { $Output = New-ModuleCustomTemplate -Names 'my.module' -Path $Target -Author "O'Brien" -Description 'Test module' -RequiredModules 'Other.Module' $Generated = Join-Path -Path $Target -ChildPath 'my.module' $Psm1 = Join-Path -Path $Generated -ChildPath 'my.module.psm1' $Psd1 = Join-Path -Path $Generated -ChildPath 'my.module.psd1' $Config = Join-Path -Path $Generated -ChildPath 'Config.ps1' $Psm1Content = Get-Content -Path $Psm1 -Raw } It 'Writes nothing to the pipeline' { $Output | Should -BeNullOrEmpty } It 'Creates the folder structure and files' { foreach ($relative in @('Classes', 'Public', 'Private', 'Public/Tests', 'Private/Tests', 'README.md', '.gitignore', 'Config.ps1', 'my.module.psm1', 'my.module.psd1')) { Test-Path -Path (Join-Path -Path $Generated -ChildPath $relative) | Should -BeTrue -Because "$relative should exist" } Get-ChildItem -Path $Generated -Recurse | Where-Object { $_.Name -match '\\' } | Should -BeNullOrEmpty -Because 'names must not contain literal backslashes' } It 'Creates PowerShell files that parse without errors' { Get-ParseErrorCount -Path $Psm1 | Should -Be 0 Get-ParseErrorCount -Path $Config | Should -Be 0 Get-ParseErrorCount -Path $Psd1 | Should -Be 0 } It 'Uses the current config, telemetry and update-check pattern in the psm1' { $Psm1Content | Should -Match ([regex]::Escape('$CurrentConfig = Get-ModuleConfig -CommandPath $PSCommandPath -ErrorAction Stop')) $Psm1Content | Should -Match ([regex]::Escape("-CommandName 'Import-Module' -ExecutionID ([guid]::NewGuid().ToString()) -Stage 'Module-Load'")) $Psm1Content | Should -Match ([regex]::Escape('$null = Get-ModuleStatus -ShowMessage')) $Psm1Content | Should -Match ([regex]::Escape('my.module configuration could not be loaded')) $Psm1Content | Should -Match 'Join-Path' } It 'Does not contain obsolete code' { foreach ($file in @($Psm1, $Config)) { $content = Get-Content -Path $file -Raw $content | Should -Not -Match 'NOTYETDEFINED' $content | Should -Not -Match 'TelmetryArgs' $content | Should -Not -Match 'Minimal' $content | Should -Not -Match 'BasicTelemetry' $content | Should -Not -Match '###TEMPLATE' $content | Should -Not -Match '\$PSScriptRoot\\' } } It 'Leaves Config.ps1 disabled unless -UncommentConfig is used' { $Psm1Content | Should -Match '(?m)^#\. \(Join-Path' } It 'Creates a Config.ps1 that never writes into the module folder' { $content = Get-Content -Path $Config -Raw $content | Should -Not -Match 'Out-File|Set-Content|New-Item|Add-Content' $content | Should -Match 'ModuleConfigPath' } It 'Creates a valid manifest that requires tcs.core 0.3.0 and lists no wildcard exports' { $manifest = Import-PowerShellDataFile -Path $Psd1 $manifest.RootModule | Should -Be 'my.module.psm1' $manifest.ModuleVersion | Should -Be '0.1.0' $manifest.Author | Should -Be "O'Brien" $manifest.Description | Should -Be 'Test module' $manifest.PowerShellVersion | Should -Be '5.1' $manifest.CompatiblePSEditions | Should -Be @('Desktop', 'Core') $manifest.RequiredModules[0].ModuleName | Should -Be 'tcs.core' $manifest.RequiredModules[0].ModuleVersion | Should -Be '0.3.0' $manifest.RequiredModules[1] | Should -Be 'Other.Module' @($manifest.FunctionsToExport).Count | Should -Be 0 $manifest.Keys | Should -Not -Contain 'NestedModules' { [guid]$manifest.GUID } | Should -Not -Throw } It 'Has no PSScriptAnalyzer findings with the repository settings' -Skip:(-not $HasScriptAnalyzer) { foreach ($file in @($Psm1, $Config, $Psd1)) { Invoke-ScriptAnalyzer -Path $file -Settings $SettingsPath | Should -BeNullOrEmpty -Because "$file should be clean" } } } Context 'Importing a generated module' { It 'Imports without errors and without writing into the module folder' { New-ModuleCustomTemplate -Names 'gen.module' -Path $Target -UncommentConfig $generated = Join-Path -Path $Target -ChildPath 'gen.module' $before = @(Get-ChildItem -Path $generated -Recurse -Force | ForEach-Object FullName | Sort-Object) $settingsFolder = Join-Path -Path $env:TCS_CONFIG_ROOT -ChildPath 'gen.module' $null = New-Item -Path $settingsFolder -ItemType Directory -Force Set-Content -Path (Join-Path -Path $settingsFolder -ChildPath 'Config.psd1') -Value "@{ Greeting = 'hello' }" try { $output = Import-Module -Name (Join-Path -Path $generated -ChildPath 'gen.module.psd1') -Force -PassThru -ErrorVariable importErrors -WarningVariable importWarnings $importErrors | Should -BeNullOrEmpty $importWarnings | Should -BeNullOrEmpty @($output).Count | Should -Be 1 & $output { $Greeting } | Should -Be 'hello' $after = @(Get-ChildItem -Path $generated -Recurse -Force | ForEach-Object FullName | Sort-Object) $after | Should -Be $before } finally { Remove-Module -Name 'gen.module' -Force -ErrorAction SilentlyContinue } } } Context 'Behaviour' { It 'Creates several modules from the pipeline' { 'one.module', 'two.module' | New-ModuleCustomTemplate -Path $Target Test-Path -Path (Join-Path -Path $Target -ChildPath 'one.module/one.module.psd1') | Should -BeTrue Test-Path -Path (Join-Path -Path $Target -ChildPath 'two.module/two.module.psd1') | Should -BeTrue } It 'Keeps existing files unless -Force is used' { New-ModuleCustomTemplate -Names 'keep.module' -Path $Target $readme = Join-Path -Path $Target -ChildPath 'keep.module/README.md' Set-Content -Path $readme -Value 'my notes' New-ModuleCustomTemplate -Names 'keep.module' -Path $Target -WarningAction SilentlyContinue (Get-Content -Path $readme -Raw).Trim() | Should -Be 'my notes' New-ModuleCustomTemplate -Names 'keep.module' -Path $Target -Force (Get-Content -Path $readme -Raw) | Should -Match '# keep.module PowerShell Module' } It 'Creates nothing with -WhatIf' { New-ModuleCustomTemplate -Names 'whatif.module' -Path $Target -WhatIf Test-Path -Path (Join-Path -Path $Target -ChildPath 'whatif.module') | Should -BeFalse } It 'Sends telemetry Start and End once per call' { Mock -ModuleName tcs.utils Invoke-TelemetryCollection { } 'a.module', 'b.module' | New-ModuleCustomTemplate -Path $Target Should -Invoke -ModuleName tcs.utils Invoke-TelemetryCollection -Times 1 -Exactly -ParameterFilter { $Stage -eq 'Start' } Should -Invoke -ModuleName tcs.utils Invoke-TelemetryCollection -Times 1 -Exactly -ParameterFilter { $Stage -eq 'End' -and -not $Failed } } } } |