diff --git a/CHANGELOG.md b/CHANGELOG.md index bf8e966..406890c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Added private functions: + - `Get-CompositeResourceSchemaPropertyContent` - Returns markdown for + composite resource properties returned by `Get-CompositeSchemaObject`. + - `New-DscCompositeResourceWikiPage` - Returns the markdown content for a + wiki page for a DSC composite resource. + +### Changed + +- `New-DscResourceWikiPage` + - Added support for creating wiki pages for composite resources. + ## [0.9.0] - 2021-07-08 ### Added diff --git a/source/Private/Get-CompositeResourceSchemaPropertyContent.ps1 b/source/Private/Get-CompositeResourceSchemaPropertyContent.ps1 new file mode 100644 index 0000000..8fe8bc2 --- /dev/null +++ b/source/Private/Get-CompositeResourceSchemaPropertyContent.ps1 @@ -0,0 +1,84 @@ +<# + .SYNOPSIS + Get-CompositeResourceSchemaPropertyContent is used to generate the parameter content + for the wiki page. + + .DESCRIPTION + Get-CompositeResourceSchemaPropertyContent is used to generate the parameter content + for the wiki page. + + .PARAMETER Property + A hash table with properties that is returned by Get-CompositeSchemaObject in + the property 'property'. + + .PARAMETER UseMarkdown + If certain text should be output as markdown, for example values of the + hashtable property ValueMap. + + .EXAMPLE + $content = Get-CompositeResourceSchemaPropertyContent -Property @( + @{ + Name = 'StringProperty' + State = 'Required' + Type = 'String' + ValidateSet = @('Value1','Value2') + Description = 'Any description' + } + ) + + Returns the parameter content based on the passed array of parameter metadata. +#> +function Get-CompositeResourceSchemaPropertyContent +{ + [CmdletBinding()] + [OutputType([System.String[]])] + param + ( + [Parameter(Mandatory = $true)] + [System.Collections.Hashtable[]] + $Property, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $UseMarkdown + ) + + $stringArray = [System.String[]] @() + + $stringArray += '| Parameter | Attribute | DataType | Description | Allowed Values |' + $stringArray += '| --- | --- | --- | --- | --- |' + + foreach ($currentProperty in $Property) + { + $propertyLine = "| **$($currentProperty.Name)** " + ` + "| $($currentProperty.State) " + ` + "| $($currentProperty.Type) |" + + if (-not [System.String]::IsNullOrEmpty($currentProperty.Description)) + { + $propertyLine += ' ' + $currentProperty.Description + } + + $propertyLine += ' |' + + if (-not [System.String]::IsNullOrEmpty($currentProperty.ValidateSet)) + { + $valueMap = $currentProperty.ValidateSet + + if ($UseMarkdown.IsPresent) + { + $valueMap = $valueMap | ForEach-Object -Process { + '`{0}`' -f $_ + } + } + + $propertyLine += ' ' + ($valueMap -join ', ') + } + + $propertyLine += ' |' + + $stringArray += $propertyLine + } + + return (, $stringArray) +} diff --git a/source/Private/New-DscCompositeResourceWikiPage.ps1 b/source/Private/New-DscCompositeResourceWikiPage.ps1 new file mode 100644 index 0000000..474dd92 --- /dev/null +++ b/source/Private/New-DscCompositeResourceWikiPage.ps1 @@ -0,0 +1,127 @@ +<# + .SYNOPSIS + New-DscCompositeResourceWikiPage generates wiki pages for composite resources + that can be uploaded to GitHub to use as public documentation for a module. + + .DESCRIPTION + The New-DscCompositeResourceWikiPage cmdlet will review all of the composite and + in a specified module directory and will output the Markdown files to the + specified directory. These help files include details on the property types + for each resource, as well as a text description and examples where they exist. + + .PARAMETER OutputPath + Where should the files be saved to. + + .PARAMETER SourcePath + The path to the root of the DSC resource module (where the PSD1 file is found, + not the folder for and individual DSC resource). + + .PARAMETER BuiltModulePath + The path to the root of the built DSC resource module, e.g. + 'output/MyResource/1.0.0'. + + .PARAMETER Force + Overwrites any existing file when outputting the generated content. + + .EXAMPLE + New-DscCompositeResourceWikiPage ` + -SourcePath C:\repos\MyResource\source ` + -BuiltModulePath C:\repos\MyResource\output\MyResource\1.0.0 ` + -OutputPath C:\repos\MyResource\output\WikiContent + + This example shows how to generate wiki documentation for a specific module. +#> +function New-DscCompositeResourceWikiPage +{ + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '')] + [CmdletBinding()] + param + ( + [Parameter(Mandatory = $true)] + [System.String] + $OutputPath, + + [Parameter(Mandatory = $true)] + [System.String] + $SourcePath, + + [Parameter(Mandatory = $true)] + [System.String] + $BuiltModulePath, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + $compositeSearchPath = Join-Path -Path $SourcePath -ChildPath '\**\*.schema.psm1' + $compositeSchemaFiles = @(Get-ChildItem -Path $compositeSearchPath -Recurse) + + Write-Verbose -Message ($script:localizedData.FoundCompositeFilesMessage -f $compositeSchemaFiles.Count, $SourcePath) + + # Loop through all the Schema files found in the modules folder + foreach ($compositeSchemaFile in $compositeSchemaFiles) + { + $compositeSchemaObject = Get-CompositeSchemaObject -FileName $compositeSchemaFile.FullName + + [System.Array] $readmeFile = Get-ChildItem -Path $compositeSchemaFile.DirectoryName | + Where-Object -FilterScript { + $_.Name -like 'readme.md' + } + + if ($readmeFile.Count -eq 1) + { + Write-Verbose -Message ($script:localizedData.GenerateWikiPageMessage -f $compositeSchemaObject.Name) + + $output = New-Object -TypeName System.Text.StringBuilder + + $null = $output.AppendLine("# $($compositeSchemaObject.Name)") + $null = $output.AppendLine('') + $null = $output.AppendLine('## Parameters') + $null = $output.AppendLine('') + + $propertyContent = Get-CompositeResourceSchemaPropertyContent -Property $compositeSchemaObject.Parameters -UseMarkdown + + foreach ($line in $propertyContent) + { + $null = $output.AppendLine($line) + } + + $descriptionContent = Get-Content -Path $readmeFile.FullName -Raw + + # Change the description H1 header to an H2 + $descriptionContent = $descriptionContent -replace '# Description', '## Description' + $null = $output.AppendLine() + $null = $output.AppendLine($descriptionContent) + + $examplesPath = Join-Path -Path $SourcePath -ChildPath ('Examples\Resources\{0}' -f $compositeSchemaObject.Name) + + $examplesOutput = Get-ResourceExampleAsMarkdown -Path $examplesPath + + if ($examplesOutput.Length -gt 0) + { + $null = $output.Append($examplesOutput) + } + + $outputFileName = "$($compositeSchemaObject.Name).md" + $savePath = Join-Path -Path $OutputPath -ChildPath $outputFileName + + Write-Verbose -Message ($script:localizedData.OutputWikiPageMessage -f $savePath) + + $null = Out-File ` + -InputObject ($output.ToString() -replace '\r?\n', "`r`n") ` + -FilePath $savePath ` + -Encoding utf8 ` + -Force:$Force + } + elseif ($readmeFile.Count -gt 1) + { + Write-Warning -Message ($script:localizedData.MultipleDescriptionFileFoundWarning -f $compositeSchemaObject.Name, $readmeFile.Count) + } + else + { + Write-Warning -Message ($script:localizedData.NoDescriptionFileFoundWarning -f $compositeSchemaObject.Name) + } + } +} diff --git a/source/Public/New-DscResourceWikiPage.ps1 b/source/Public/New-DscResourceWikiPage.ps1 index 368b933..7d60abd 100644 --- a/source/Public/New-DscResourceWikiPage.ps1 +++ b/source/Public/New-DscResourceWikiPage.ps1 @@ -4,11 +4,11 @@ to use as public documentation for a module. .DESCRIPTION - The New-DscResourceWikiPage cmdlet will review all of the MOF-based and - class-based resources in a specified module directory and will output the - Markdown files to the specified directory. These help files include details - on the property types for each resource, as well as a text description and - examples where they exist. + The New-DscResourceWikiPage cmdlet will review all of the MOF-based, + class-based and composite resources in a specified module directory and will + output the Markdown files to the specified directory. These help files include + details on the property types for each resource, as well as a text description + and examples where they exist. .PARAMETER OutputPath Where should the files be saved to. @@ -58,4 +58,6 @@ function New-DscResourceWikiPage New-DscMofResourceWikiPage @PSBoundParameters New-DscClassResourceWikiPage @PSBoundParameters + + New-DscCompositeResourceWikiPage @PSBoundParameters } diff --git a/source/en-US/DscResource.DocGenerator.strings.psd1 b/source/en-US/DscResource.DocGenerator.strings.psd1 index 75b8d8b..a3b38e8 100644 --- a/source/en-US/DscResource.DocGenerator.strings.psd1 +++ b/source/en-US/DscResource.DocGenerator.strings.psd1 @@ -35,7 +35,7 @@ ConvertFrom-StringData @' InvokeGitStandardOutputMessage = git standard output: '{0}' InvokeGitStandardErrorMessage = git standard error: '{0}' InvokeGitExitCodeMessage = git exit code: '{0}' - FoundCompositeFilesMessage = Found {0} composite files in path '{1}'. + FoundCompositeFilesMessage = Found {0} composite schema files in path '{1}'. CommentBasedHelpBlockNotFound = A comment-based help block in source file '{0}' could not be found. CommentBasedHelpBlockNotAtTopMessage = A comment-based help block in source file '{0}' was found, but is not at the top of the file. CompositeResourceMultiConfigError = {1} composite resources were found in the source file '{0}'. This is not currently supported. Please separate these into different scripts. diff --git a/tests/unit/private/Get-CompositeResourceSchemaPropertyContent.Tests.ps1 b/tests/unit/private/Get-CompositeResourceSchemaPropertyContent.Tests.ps1 new file mode 100644 index 0000000..98b9d9a --- /dev/null +++ b/tests/unit/private/Get-CompositeResourceSchemaPropertyContent.Tests.ps1 @@ -0,0 +1,62 @@ +#region HEADER +$script:projectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try + { + Test-ModuleManifest -Path $_.FullName -ErrorAction Stop + } + catch + { + $false + }) + }).BaseName + +$script:moduleName = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 +Remove-Module -Name $script:moduleName -Force -ErrorAction 'SilentlyContinue' + +Import-Module $script:moduleName -Force -ErrorAction 'Stop' +#endregion HEADER + +InModuleScope $script:moduleName { + Describe 'Get-CompositeResourceSchemaPropertyContent' { + BeforeAll { + $mockClassProperties = @( + @{ + Name = 'StringProperty' + State = 'Required' + Type = 'String' + ValidateSet = $null + Description = 'Any description' + } + @{ + Name = 'StringValueMapProperty' + State = 'Required' + Type = 'String' + ValidateSet = @( + 'Value1' + 'Value2' + ) + Description = 'Any description' + } + @{ + Name = 'StringArrayWriteProperty' + State = 'Write' + Type = 'String[]' + ValidateSet = $null + Description = 'Any description' + } + ) + } + + It 'Should return the expected content as a string array' { + $result = Get-CompositeResourceSchemaPropertyContent -Property $mockClassProperties + + $result[0] | Should -Be '| Parameter | Attribute | DataType | Description | Allowed Values |' + $result[1] | Should -Be '| --- | --- | --- | --- | --- |' + $result[2] | Should -Be '| **StringProperty** | Required | String | Any description | |' + $result[3] | Should -Be '| **StringValueMapProperty** | Required | String | Any description | Value1, Value2 |' + $result[4] | Should -Be '| **StringArrayWriteProperty** | Write | String[] | Any description | |' + } + } +} diff --git a/tests/unit/private/New-DscCompositeResourceWikiPage.Tests.ps1 b/tests/unit/private/New-DscCompositeResourceWikiPage.Tests.ps1 new file mode 100644 index 0000000..1bfd449 --- /dev/null +++ b/tests/unit/private/New-DscCompositeResourceWikiPage.Tests.ps1 @@ -0,0 +1,914 @@ +#region HEADER +$script:projectPath = "$PSScriptRoot\..\..\.." | Convert-Path +$script:projectName = (Get-ChildItem -Path "$script:projectPath\*\*.psd1" | Where-Object -FilterScript { + ($_.Directory.Name -match 'source|src' -or $_.Directory.Name -eq $_.BaseName) -and + $(try + { + Test-ModuleManifest -Path $_.FullName -ErrorAction Stop + } + catch + { + $false + }) + }).BaseName + +$script:moduleName = Get-Module -Name $script:projectName -ListAvailable | Select-Object -First 1 +Remove-Module -Name $script:moduleName -Force -ErrorAction 'SilentlyContinue' + +Import-Module $script:moduleName -Force -ErrorAction 'Stop' +#endregion HEADER + +Import-Module -Name (Join-Path -Path $PSScriptRoot -ChildPath '../../helpers/DscResource.DocGenerator.TestHelper.psm1') -Force + +InModuleScope $script:moduleName { + <# + .NOTES + This stub function is created because when original Out-File is + mocked in PowerShell 6.x it changes the type of the Encoding + parameter to [System.Text.Encoding] which when called with + `OutFile -Encoding 'ascii'` fails with the error message + "Cannot process argument transformation on parameter 'Encoding'. + Cannot convert the "ascii" value of type "System.String" to type + "System.Text.Encoding". + #> + function Out-File + { + [CmdletBinding()] + param + ( + [Parameter(ValueFromPipeline = $true)] + [System.String] + $InputObject, + + [Parameter()] + [System.String] + $FilePath, + + [Parameter()] + [System.String] + $Encoding, + + [Parameter()] + [System.Management.Automation.SwitchParameter] + $Force + ) + + throw 'StubNotImplemented' + } + + Describe 'New-DscCompositeResourceWikiPage' { + Context 'When generating documentation for composite resources' { + $script:mockOutputPath = Join-Path -Path $TestDrive -ChildPath 'docs' + $script:mockSourcePath = Join-Path -Path $TestDrive -ChildPath 'module' + + # Schema file info + $script:mockCompositeResourceName = 'MyCompositeResource' + $script:expectedSchemaPath = Join-Path -Path $script:mockSourcePath -ChildPath '\**\*.schema.psm1' + $script:mockSchemaBaseName = "$($script:mockCompositeResourceName).schema" + $script:mockSchemaFileName = "$($script:mockSchemaBaseName).psm1" + $script:mockSchemaFolder = Join-Path -Path $script:mockSourcePath -ChildPath "DSCResources\$($script:mockCompositeResourceName)" + $script:mockSchemaFilePath = Join-Path -Path $script:mockSchemaFolder -ChildPath $script:mockSchemaFileName + $script:mockSchemaFiles = @( + @{ + FullName = $script:mockSchemaFilePath + Name = $script:mockSchemaFileName + DirectoryName = $script:mockSchemaFolder + BaseName = $script:mockSchemaBaseName + } + ) + + $script:mockGetCompositeSchemaObject = @{ + Name = 'MyCompositeResource' + Parameters = @( + @{ + Name = 'StringProperty' + State = 'Required' + Type = 'String' + ValidateSet = $null + Description = 'Any description' + } + @{ + Name = 'StringValueMapProperty' + State = 'Required' + Type = 'String' + ValidateSet = @( + 'Value1' + 'Value2' + ) + Description = 'Any description' + } + @{ + Name = 'StringArrayWriteProperty' + State = 'Write' + Type = 'String[]' + ValidateSet = $null + Description = 'Any description' + } + ) + ModuleVersion = '1.0.0' + Description = 'Composite resource.' + } + + # Example file info + $script:mockExampleFilePath = Join-Path -Path $script:mockSourcePath -ChildPath "\Examples\Resources\$($script:mockCompositeResourceName)\$($script:mockCompositeResourceName)_Example1_Config.ps1" + $script:expectedExamplePath = Join-Path -Path $script:mockSourcePath -ChildPath "\Examples\Resources\$($script:mockCompositeResourceName)\*.ps1" + $script:mockExampleFiles = @( + @{ + Name = "$($script:mockCompositeResourceName)_Example1_Config.ps1" + FullName = $script:mockExampleFilePath + } + ) + + $script:mockExampleContent = '.EXAMPLE 1 + +Example description. + +Configuration Example +{ + Import-DSCResource -ModuleName MyModule + Node localhost + { + MyCompositeResource Something + { + Id = ''MyId'' + Enum = ''Value1'' + Int = 1 + } + } +}' + + # General mock values + $script:mockReadmePath = Join-Path -Path $script:mockSchemaFolder -ChildPath 'readme.md' + $script:mockReadmeFolder = $script:mockSchemaFolder + $script:mockOutputFile = Join-Path -Path $script:mockOutputPath -ChildPath "$($script:mockCompositeResourceName).md" + $script:mockSavePath = Join-Path -Path $script:mockOutputPath -ChildPath "$($script:mockCompositeResourceName).md" + $script:mockGetContentReadme = '# Description + +The description of the resource. +Second row of description. +' + $script:mockWikiContentOutput = '# MyCompositeResource + +## Parameters + +| Parameter | Attribute | DataType | Description | Allowed Values | +| --- | --- | --- | --- | --- | +| **StringProperty** | Required | String | Any description | | +| **StringValueMapProperty** | Required | String | Any description | `Value1`, `Value2` | +| **StringArrayWriteProperty** | Write | String[] | Any description | | + +## Description + +The description of the resource. +Second row of description. + +## Examples + +.EXAMPLE 1 + +Example description. + +Configuration Example +{ + Import-DSCResource -ModuleName MyModule + Node localhost + { + MyCompositeResource Something + { + Id = ''MyId'' + Enum = ''Value1'' + Int = 1 + } + } +} +' -replace '\r?\n', "`r`n" + + # Parameter filters + $script:getChildItemSchema_parameterFilter = { + $Path -eq $script:expectedSchemaPath + } + + $script:getChildItemDescription_parameterFilter = { + $Path -eq $script:mockReadmeFolder + } + + $script:getChildItemExample_parameterFilter = { + $Path -eq $script:expectedExamplePath + } + + $script:getCompositeSchemaObjectSchema_parameterFilter = { + $Filename -eq $script:mockSchemaFilePath + } + + $script:getTestPathReadme_parameterFilter = { + $Path -eq $script:mockReadmePath + } + + $script:getContentReadme_parameterFilter = { + $Path -eq $script:mockReadmePath + } + + $script:getDscResourceWikiExampleContent_parameterFilter = { + $ExamplePath -eq $script:mockExampleFilePath -and $ExampleNumber -eq 1 + } + + $script:outFile_parameterFilter = { + $FilePath -eq $script:mockSavePath + } + + $script:outFileContent_parameterFilter = { + if ($InputObject -ne $script:mockWikiContentOutput) + { + # Helper to output the diff. + Out-Diff -Expected $script:mockWikiContentOutput -Actual $InputObject + } + + $InputObject -eq $script:mockWikiContentOutput + } + + $script:writeWarningDescription_parameterFilter = { + $Message -eq ($script:localizedData.NoDescriptionFileFoundWarning -f $script:mockCompositeResourceName) + } + + $script:writeWarningMultipleDescription_parameterFilter = { + $Message -eq ($script:localizedData.MultipleDescriptionFileFoundWarning -f $script:mockCompositeResourceName, 2) + } + + $script:writeWarningExample_parameterFilter = { + $Message -eq ($script:localizedData.NoExampleFileFoundWarning -f $script:mockCompositeResourceName) + } + + # Function call parameters + $script:newDscResourceWikiPage_parameters = @{ + SourcePath = $script:mockSourcePath + Verbose = $true + } + + $script:newDscResourceWikiPageOutput_parameters = @{ + SourcePath = $script:mockSourcePath + OutputPath = $script:mockOutputPath + BuiltModulePath = '.' # Not used for composite resources + Verbose = $true + } + + Context 'When there are no schemas found in the module folder' { + BeforeAll { + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter + + Mock ` + -CommandName Out-File ` + -ParameterFilter $script:outFile_parameterFilter + } + + It 'Should not throw an exception' { + { New-DscCompositeResourceWikiPage @script:newDscResourceWikiPageOutput_parameters } | Should -Not -Throw + } + + It 'Should call the expected mocks ' { + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Out-File ` + -Exactly -Times 0 + } + } + + Context 'When there is no resource description found' { + BeforeAll { + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -MockWith { $script:mockSchemaFiles } + + Mock ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -MockWith { $script:mockGetCompositeSchemaObject } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -MockWith { $null } + + Mock ` + -CommandName Out-File ` + -ParameterFilter $script:outFile_parameterFilter + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter + } + + It 'Should not throw an exception' { + { New-DscCompositeResourceWikiPage @script:newDscResourceWikiPageOutput_parameters } | Should -Not -Throw + } + + It 'Should call the expected mocks ' { + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter $script:outFile_parameterFilter ` + -Exactly -Times 0 + } + } + + Context 'When there are multiple resource descriptions found' { + BeforeAll { + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -MockWith { $script:mockSchemaFiles } + + Mock ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -MockWith { $script:mockGetCompositeSchemaObject } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -MockWith { + return @( + @{ + Name = 'README.MD' + FullName = $script:mockReadmePath + }, + @{ + Name = 'Readme.md' + FullName = $script:mockReadmePath + } + ) + } + + Mock ` + -CommandName Out-File ` + -ParameterFilter $script:outFile_parameterFilter + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningMultipleDescription_parameterFilter + } + + It 'Should not throw an exception' { + { New-DscCompositeResourceWikiPage @script:newDscResourceWikiPageOutput_parameters } | Should -Not -Throw + } + + It 'Should call the expected mocks ' { + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningMultipleDescription_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter $script:outFile_parameterFilter ` + -Exactly -Times 0 + } + } + + Context 'When there is no resource example file found' { + BeforeAll { + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -MockWith { $script:mockSchemaFiles } + + Mock ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -MockWith { $script:mockGetCompositeSchemaObject } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -MockWith { + return @( + @{ + Name = 'README.MD' + FullName = $script:mockReadmePath + } + ) + } + + Mock ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -MockWith { $script:mockGetContentReadme } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter + + Mock ` + -CommandName Get-DscResourceWikiExampleContent + + Mock ` + -CommandName Out-File ` + -ParameterFilter $script:outFile_parameterFilter + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter + } + + It 'Should not throw an exception' { + { New-DscCompositeResourceWikiPage @script:newDscResourceWikiPageOutput_parameters } | Should -Not -Throw + } + + It 'Should call the expected mocks ' { + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -Exactly -Times 0 + + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter $script:outFile_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter ` + -Exactly -Times 0 + } + } + + Context 'When there is one schema found in the module folder and one example using .EXAMPLE and the OutputPath is specified' { + BeforeAll { + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -MockWith { $script:mockSchemaFiles } + + Mock ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -MockWith { $script:mockGetCompositeSchemaObject } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -MockWith { + return @( + @{ + Name = 'README.MD' + FullName = $script:mockReadmePath + } + ) + } + + Mock ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -MockWith { $script:mockGetContentReadme } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -MockWith { $script:mockExampleFiles } + + Mock ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -MockWith { $script:mockExampleContent } + + Mock ` + -CommandName Out-File + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter + } + + It 'Should not throw an exception' { + { New-DscCompositeResourceWikiPage @script:newDscResourceWikiPageOutput_parameters } | Should -Not -Throw + } + + It 'Should produce the correct output' { + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter $script:outFileContent_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter $script:outFile_parameterFilter ` + -Exactly -Times 1 + } + + It 'Should call the expected mocks ' { + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter ` + -Exactly -Times 0 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter ` + -Exactly -Times 0 + } + } + + Context 'When there is one schema found in the module folder and one example using .EXAMPLE and only the parameter SourcePath is specified' { + BeforeAll { + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -MockWith { $script:mockSchemaFiles } + + Mock ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -MockWith { $script:mockGetCompositeSchemaObject } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -MockWith { + return @( + @{ + Name = 'README.MD' + FullName = $script:mockReadmePath + } + ) + } + + Mock ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -MockWith { $script:mockGetContentReadme } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -MockWith { $script:mockExampleFiles } + + Mock ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -MockWith { $script:mockExampleContent } + + Mock ` + -CommandName Out-File + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter + } + + It 'Should not throw an exception' { + { New-DscCompositeResourceWikiPage @script:newDscResourceWikiPageOutput_parameters } | Should -Not -Throw + } + + It 'Should produce the correct output' { + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter $script:outFileContent_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter $script:outFile_parameterFilter ` + -Exactly -Times 1 + } + + It 'Should call the expected mocks ' { + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter ` + -Exactly -Times 0 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter ` + -Exactly -Times 0 + } + } + + Context 'When the schema is using an embedded instance' { + BeforeAll { + <# + This is the mocked embedded schema that is to be returned + together with the resource schema (which is mocked above) + for the mocked function Get-CompositeSchemaObject. + #> + $script:mockEmbeddedSchemaObject = @{ + ClassName = 'DSC_EmbeddedInstance' + ClassVersion = '1.0.0' + FriendlyName = 'EmbeddedInstance' + Attributes = @( + @{ + State = 'Key' + DataType = 'String' + ValueMap = @() + IsArray = $false + Name = 'EmbeddedId' + Description = 'Id Description' + EmbeddedInstance = '' + }, + @{ + State = 'Write' + DataType = 'String' + ValueMap = @( 'Value1', 'Value2', 'Value3' ) + IsArray = $false + Name = 'EmbeddedEnum' + Description = 'Enum Description.' + EmbeddedInstance = '' + }, + @{ + State = 'Required' + DataType = 'Uint32' + ValueMap = @() + IsArray = $false + Name = 'EmbeddedInt' + Description = 'Int Description.' + EmbeddedInstance = '' + }, + @{ + State = 'Read' + DataType = 'String' + ValueMap = @() + IsArray = $false + Name = 'EmbeddedRead' + Description = 'Read Description.' + EmbeddedInstance = '' + } + ) + } + + $mockWikiContentOutput = '# MyCompositeResource + +## Parameters + +| Parameter | Attribute | DataType | Description | Allowed Values | +| --- | --- | --- | --- | --- | +| **StringProperty** | Required | String | Any description | | +| **StringValueMapProperty** | Required | String | Any description | `Value1`, `Value2` | +| **StringArrayWriteProperty** | Write | String[] | Any description | | + +## Description + +The description of the resource. +Second row of description. + +## Examples + +.EXAMPLE 1 + +Example description. + +Configuration Example +{ + Import-DSCResource -ModuleName MyModule + Node localhost + { + MyCompositeResource Something + { + Id = ''MyId'' + Enum = ''Value1'' + Int = 1 + } + } +} +' -replace '\r?\n', "`r`n" + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -MockWith { $script:mockSchemaFiles } + + Mock ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -MockWith { + return @( + $script:mockGetCompositeSchemaObject + $script:mockEmbeddedSchemaObject + ) + } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -MockWith { + return @( + @{ + Name = 'README.MD' + FullName = $script:mockReadmePath + } + ) + } + + Mock ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -MockWith { $script:mockGetContentReadme } + + Mock ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -MockWith { $script:mockExampleFiles } + + Mock ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -MockWith { $script:mockExampleContent } + + Mock ` + -CommandName Out-File + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter + + Mock ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter + } + + It 'Should not throw an exception' { + { New-DscCompositeResourceWikiPage @script:newDscResourceWikiPageOutput_parameters } | Should -Not -Throw + } + + It 'Should produce the correct output' { + Assert-MockCalled ` + -CommandName Out-File ` + -ParameterFilter { + if ($InputObject -ne $mockWikiContentOutput) + { + # Helper to output the diff. + Out-Diff -Expected $mockWikiContentOutput -Actual $InputObject + } + + $InputObject -eq $mockWikiContentOutput + } ` + -Exactly -Times 1 + } + + It 'Should call the expected mocks ' { + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-CompositeSchemaObject ` + -ParameterFilter $script:getCompositeSchemaObjectSchema_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemDescription_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-Content ` + -ParameterFilter $script:getContentReadme_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-ChildItem ` + -ParameterFilter $script:getChildItemExample_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Get-DscResourceWikiExampleContent ` + -ParameterFilter $script:getDscResourceWikiExampleContent_parameterFilter ` + -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningExample_parameterFilter ` + -Exactly -Times 0 + + Assert-MockCalled ` + -CommandName Write-Warning ` + -ParameterFilter $script:writeWarningDescription_parameterFilter ` + -Exactly -Times 0 + } + } + } + } +} diff --git a/tests/unit/public/New-DscResourceWikiPage.Tests.ps1 b/tests/unit/public/New-DscResourceWikiPage.Tests.ps1 index bda20d5..7f7d63a 100644 --- a/tests/unit/public/New-DscResourceWikiPage.Tests.ps1 +++ b/tests/unit/public/New-DscResourceWikiPage.Tests.ps1 @@ -48,6 +48,10 @@ InModuleScope $script:moduleName { -CommandName New-DscClassResourceWikiPage ` -ParameterFilter $script:newDscResourceWikiPage_parameterFilter + Mock ` + -CommandName New-DscCompositeResourceWikiPage ` + -ParameterFilter $script:newDscResourceWikiPage_parameterFilter + It 'Should not throw an exception' { { New-DscResourceWikiPage @script:newDscResourceWikiPage_parameters } | Should -Not -Throw } @@ -62,6 +66,11 @@ InModuleScope $script:moduleName { -CommandName New-DscClassResourceWikiPage ` -ParameterFilter $script:newDscResourceWikiPage_parameterFilter ` -Exactly -Times 1 + + Assert-MockCalled ` + -CommandName New-DscCompositeResourceWikiPage ` + -ParameterFilter $script:newDscResourceWikiPage_parameterFilter ` + -Exactly -Times 1 } } }