Add queue hardening by modifying DeploymentJobTargets, DeploymentJobSteps, and DeploymentJobs tables
- Drop existing indexes on DeploymentJobTargets and DeploymentJobSteps - Alter Status column to nvarchar(450) in DeploymentJobTargets, DeploymentJobSteps, and DeploymentJobs - Add new columns: Finished, OutputMetadataJson, Started to DeploymentJobTargets; ErrorMessage, Finished, OutputMetadataJson, Started to DeploymentJobSteps; CorrelationId, HeartbeatAt, Priority, RowVersion, ScheduledAt, WorkerName to DeploymentJobs - Create new indexes for improved query performance - Add constraints to ensure OutputMetadataJson is valid JSON - Record migration in __EFMigrationsHistory
This commit is contained in:
421
.tests/BgwMerge.Api.Tests.ps1
Normal file
421
.tests/BgwMerge.Api.Tests.ps1
Normal file
@@ -0,0 +1,421 @@
|
||||
param(
|
||||
[string]$ApiBaseUrl = 'http://localhost:5286/api',
|
||||
|
||||
[string]$BgwRoot = ('F:\Kunden Auftr' + [char]0x00E4 + 'ge\BGW'),
|
||||
|
||||
[string]$DeploymentBatchId = '80000000-0000-0000-0000-000000000101',
|
||||
|
||||
[string]$MergeModulePath = 'F:\Projekte\Coding\PowerShell\Merge-DSCConfigurationData\Merge-DSCConfigurationData.psd1',
|
||||
|
||||
[string]$ResolveModulePath = 'F:\Projekte\Coding\PowerShell\Resolve-DSCConfigurationData\Resolve-DSCConfigurationData.psd1',
|
||||
|
||||
[int]$ApiTimeoutSec = 30,
|
||||
|
||||
[bool]$UseDefaultCredentials = $true
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
function Invoke-TestBgwMergeApiJson {
|
||||
param(
|
||||
[ValidateSet('GET', 'POST', 'PUT', 'DELETE')]
|
||||
[string]$Method,
|
||||
|
||||
[string]$Path
|
||||
)
|
||||
|
||||
$uri = ('{0}/{1}' -f $ApiBaseUrl.TrimEnd('/'), $Path.TrimStart('/'))
|
||||
$parameters = @{
|
||||
Method = $Method
|
||||
Uri = $uri
|
||||
TimeoutSec = $ApiTimeoutSec
|
||||
ErrorAction = 'Stop'
|
||||
}
|
||||
|
||||
if ($UseDefaultCredentials) {
|
||||
$parameters.UseDefaultCredentials = $true
|
||||
}
|
||||
|
||||
try {
|
||||
Invoke-RestMethod @parameters
|
||||
}
|
||||
catch {
|
||||
$responseBody = '<empty response body>'
|
||||
if ($_.Exception.Response) {
|
||||
try {
|
||||
$stream = $_.Exception.Response.GetResponseStream()
|
||||
if ($stream) {
|
||||
$reader = [System.IO.StreamReader]::new($stream)
|
||||
$text = $reader.ReadToEnd()
|
||||
if (-not [string]::IsNullOrWhiteSpace($text)) {
|
||||
$responseBody = $text
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$responseBody = '<could not read response body>'
|
||||
}
|
||||
}
|
||||
|
||||
throw "API request failed. Method=[$Method], Uri=[$uri], Response=[$responseBody]. $($_.Exception.Message)"
|
||||
}
|
||||
}
|
||||
|
||||
function ConvertTo-TestBgwMergeConfigurationValue {
|
||||
param([object]$Value)
|
||||
|
||||
if ($null -eq $Value) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if ($Value -is [string] -or $Value -is [bool] -or $Value -is [int] -or $Value -is [long] -or $Value -is [double] -or $Value -is [decimal]) {
|
||||
return $Value
|
||||
}
|
||||
|
||||
if ($Value -is [System.Collections.IDictionary]) {
|
||||
$result = [ordered]@{}
|
||||
foreach ($key in $Value.Keys) {
|
||||
$result[[string]$key] = ConvertTo-TestBgwMergeConfigurationValue -Value $Value[$key]
|
||||
}
|
||||
|
||||
Write-Output -NoEnumerate $result
|
||||
return
|
||||
}
|
||||
|
||||
if ($Value -is [pscustomobject]) {
|
||||
$result = [ordered]@{}
|
||||
foreach ($property in $Value.PSObject.Properties) {
|
||||
$result[$property.Name] = ConvertTo-TestBgwMergeConfigurationValue -Value $property.Value
|
||||
}
|
||||
|
||||
Write-Output -NoEnumerate $result
|
||||
return
|
||||
}
|
||||
|
||||
if ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) {
|
||||
$items = New-Object System.Collections.Generic.List[object]
|
||||
foreach ($item in $Value) {
|
||||
$items.Add((ConvertTo-TestBgwMergeConfigurationValue -Value $item))
|
||||
}
|
||||
|
||||
Write-Output -NoEnumerate $items.ToArray()
|
||||
return
|
||||
}
|
||||
|
||||
return $Value
|
||||
}
|
||||
|
||||
function ConvertFrom-TestBgwMergeTemplateJson {
|
||||
param([string]$JsonData)
|
||||
|
||||
$document = $JsonData | ConvertFrom-Json
|
||||
$metadata = ConvertTo-TestBgwMergeConfigurationValue -Value $document.metadata
|
||||
if ($null -eq $metadata) {
|
||||
$metadata = [ordered]@{}
|
||||
}
|
||||
|
||||
if (-not $metadata.Contains('TemplateType')) {
|
||||
$metadata['TemplateType'] = $document.templateType
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
Metadata = $metadata
|
||||
Parameters = ConvertTo-TestBgwMergeConfigurationValue -Value $document.parameters
|
||||
Variables = ConvertTo-TestBgwMergeConfigurationValue -Value $document.variables
|
||||
Resources = ConvertTo-TestBgwMergeConfigurationValue -Value $document.resources
|
||||
}
|
||||
}
|
||||
|
||||
function ConvertFrom-TestBgwMergeTargetAssignments {
|
||||
param([object[]]$TargetAssignments)
|
||||
|
||||
@($TargetAssignments | Sort-Object sortOrder | ForEach-Object {
|
||||
$nodeData = ConvertTo-TestBgwMergeConfigurationValue -Value ($_.nodeDataJson | ConvertFrom-Json)
|
||||
$node = @{}
|
||||
|
||||
foreach ($key in $nodeData.Keys) {
|
||||
if ($key -eq 'nodeName') {
|
||||
$node.NodeName = $nodeData[$key]
|
||||
}
|
||||
else {
|
||||
$node[$key] = $nodeData[$key]
|
||||
}
|
||||
}
|
||||
|
||||
$node
|
||||
})
|
||||
}
|
||||
|
||||
function ConvertTo-TestBgwMergeStableValue {
|
||||
param([object]$Value)
|
||||
|
||||
if ($null -eq $Value) {
|
||||
return $null
|
||||
}
|
||||
|
||||
if ($Value -is [string] -or $Value -is [bool] -or $Value -is [int] -or $Value -is [long] -or $Value -is [double] -or $Value -is [decimal]) {
|
||||
return $Value
|
||||
}
|
||||
|
||||
if ($Value -is [System.Collections.IDictionary]) {
|
||||
$result = [ordered]@{}
|
||||
foreach ($key in @($Value.Keys | Sort-Object)) {
|
||||
$result[[string]$key] = ConvertTo-TestBgwMergeStableValue -Value $Value[$key]
|
||||
}
|
||||
|
||||
Write-Output -NoEnumerate $result
|
||||
return
|
||||
}
|
||||
|
||||
if ($Value -is [pscustomobject]) {
|
||||
$result = [ordered]@{}
|
||||
foreach ($property in @($Value.PSObject.Properties | Sort-Object Name)) {
|
||||
$result[$property.Name] = ConvertTo-TestBgwMergeStableValue -Value $property.Value
|
||||
}
|
||||
|
||||
Write-Output -NoEnumerate $result
|
||||
return
|
||||
}
|
||||
|
||||
if ($Value -is [System.Collections.IEnumerable] -and $Value -isnot [string]) {
|
||||
$items = New-Object System.Collections.Generic.List[object]
|
||||
foreach ($item in $Value) {
|
||||
$items.Add((ConvertTo-TestBgwMergeStableValue -Value $item))
|
||||
}
|
||||
|
||||
Write-Output -NoEnumerate $items.ToArray()
|
||||
return
|
||||
}
|
||||
|
||||
return $Value
|
||||
}
|
||||
|
||||
function ConvertTo-TestBgwMergeStableJson {
|
||||
param([object]$Value)
|
||||
|
||||
(ConvertTo-TestBgwMergeStableValue -Value $Value) | ConvertTo-Json -Depth 100 -Compress
|
||||
}
|
||||
|
||||
function Invoke-TestBgwMergeSnapshotScript {
|
||||
param([string]$Script)
|
||||
|
||||
$encodedCommand = [Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($Script))
|
||||
$output = & powershell.exe -NoProfile -ExecutionPolicy Bypass -EncodedCommand $encodedCommand
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "Snapshot PowerShell process failed with exit code [$LASTEXITCODE]. Output: $($output -join [Environment]::NewLine)"
|
||||
}
|
||||
|
||||
$json = ($output | Where-Object { -not [string]::IsNullOrWhiteSpace($_) }) -join [Environment]::NewLine
|
||||
if ([string]::IsNullOrWhiteSpace($json)) {
|
||||
throw 'Snapshot PowerShell process returned no JSON output.'
|
||||
}
|
||||
|
||||
$json | ConvertFrom-Json
|
||||
}
|
||||
|
||||
function New-TestBgwMergeSnapshotScript {
|
||||
param(
|
||||
[string]$ConfigurationDataPath,
|
||||
[switch]$BuildLocalBaseline
|
||||
)
|
||||
|
||||
$escapedBgwRoot = $BgwRoot.Replace("'", "''")
|
||||
$escapedMergeModulePath = $MergeModulePath.Replace("'", "''")
|
||||
$escapedResolveModulePath = $ResolveModulePath.Replace("'", "''")
|
||||
$escapedConfigurationDataPath = if ($ConfigurationDataPath) { $ConfigurationDataPath.Replace("'", "''") } else { '' }
|
||||
$buildLocalBaselineText = if ($BuildLocalBaseline) { 'True' } else { 'False' }
|
||||
|
||||
@"
|
||||
`$ErrorActionPreference = 'Stop'
|
||||
`$ProgressPreference = 'SilentlyContinue'
|
||||
Import-Module '$escapedMergeModulePath' -Force
|
||||
Import-Module '$escapedResolveModulePath' -Force
|
||||
|
||||
function ConvertTo-SnapshotStableValue {
|
||||
param([object]`$Value)
|
||||
|
||||
if (`$null -eq `$Value) { return `$null }
|
||||
if (`$Value -is [string] -or `$Value -is [bool] -or `$Value -is [int] -or `$Value -is [long] -or `$Value -is [double] -or `$Value -is [decimal]) { return `$Value }
|
||||
|
||||
if (`$Value -is [System.Collections.IDictionary]) {
|
||||
`$result = [ordered]@{}
|
||||
foreach (`$key in @(`$Value.Keys | Sort-Object)) {
|
||||
`$result[[string]`$key] = ConvertTo-SnapshotStableValue -Value `$Value[`$key]
|
||||
}
|
||||
Write-Output -NoEnumerate `$result
|
||||
return
|
||||
}
|
||||
|
||||
if (`$Value -is [pscustomobject]) {
|
||||
`$result = [ordered]@{}
|
||||
foreach (`$property in @(`$Value.PSObject.Properties | Sort-Object Name)) {
|
||||
`$result[`$property.Name] = ConvertTo-SnapshotStableValue -Value `$property.Value
|
||||
}
|
||||
Write-Output -NoEnumerate `$result
|
||||
return
|
||||
}
|
||||
|
||||
if (`$Value -is [System.Collections.IEnumerable] -and `$Value -isnot [string]) {
|
||||
`$items = New-Object System.Collections.Generic.List[object]
|
||||
foreach (`$item in `$Value) {
|
||||
`$items.Add((ConvertTo-SnapshotStableValue -Value `$item))
|
||||
}
|
||||
Write-Output -NoEnumerate `$items.ToArray()
|
||||
return
|
||||
}
|
||||
|
||||
return `$Value
|
||||
}
|
||||
|
||||
function ConvertTo-SnapshotStableJson {
|
||||
param([object]`$Value)
|
||||
(ConvertTo-SnapshotStableValue -Value `$Value) | ConvertTo-Json -Depth 100 -Compress
|
||||
}
|
||||
|
||||
if ('$buildLocalBaselineText' -eq 'True') {
|
||||
`$root = '$escapedBgwRoot'
|
||||
`$sourceTemplates = @(
|
||||
(Join-Path -Path `$root -ChildPath 'Environment\Test.psd1')
|
||||
(Join-Path -Path `$root -ChildPath 'Domain\Contoso.psd1')
|
||||
(Join-Path -Path `$root -ChildPath 'Service\SharePoint\Contoso.psd1')
|
||||
(Join-Path -Path `$root -ChildPath 'Stage\Install.psd1')
|
||||
)
|
||||
|
||||
`$allNodes = @(
|
||||
@{ NodeName = 'CLD-SHP-01'; RunCentralAdministration = `$true }
|
||||
@{ NodeName = 'CLD-SHP-02'; RunCentralAdministration = `$false }
|
||||
@{ NodeName = 'CLD-SHP-03'; RunCentralAdministration = `$false }
|
||||
)
|
||||
|
||||
`$configurationData = New-DSCConfigurationDataDeployment -Name 'Contoso-Test-SharePoint' -DeploymentId '8f6c2c1a' -SourceTemplatePath `$sourceTemplates -AllNodes `$allNodes
|
||||
}
|
||||
else {
|
||||
`$configurationData = Import-PowerShellDataFile -LiteralPath '$escapedConfigurationDataPath'
|
||||
}
|
||||
|
||||
`$resolved = Resolve-DSCConfigurationData -ConfigurationData `$configurationData -SkipSecrets
|
||||
`$snapshot = [ordered]@{
|
||||
ParametersJson = ConvertTo-SnapshotStableJson -Value `$configurationData.Parameters
|
||||
VariablesJson = ConvertTo-SnapshotStableJson -Value `$configurationData.Variables
|
||||
ResourcesJson = ConvertTo-SnapshotStableJson -Value `$configurationData.Resources
|
||||
DatabasePrefix = `$resolved.Variables.DatabasePrefix
|
||||
ServiceDbPrefix = `$resolved.Variables.ServiceDbPrefix
|
||||
ConfigDbName = `$resolved.Variables.ConfigDbName
|
||||
LcmConfigurationMode = `$resolved.Resources.NonNodeData.LocalConfigurationManager.ConfigurationMode
|
||||
AllNodes = @(`$resolved.Resources.AllNodes | Sort-Object NodeName | ForEach-Object {
|
||||
[ordered]@{
|
||||
NodeName = `$_.NodeName
|
||||
RunCentralAdministration = `$_.RunCentralAdministration
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
`$snapshot | ConvertTo-Json -Depth 100 -Compress
|
||||
"@
|
||||
}
|
||||
|
||||
function New-TestBgwMergeLocalSnapshot {
|
||||
Invoke-TestBgwMergeSnapshotScript -Script (New-TestBgwMergeSnapshotScript -BuildLocalBaseline)
|
||||
}
|
||||
|
||||
function New-TestBgwMergeSnapshotFromConfigurationData {
|
||||
param([hashtable]$ConfigurationData)
|
||||
|
||||
Import-Module $MergeModulePath -Force
|
||||
|
||||
$tempPath = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ("BgwMergeApiSnapshot-{0}.psd1" -f ([guid]::NewGuid().ToString('N')))
|
||||
try {
|
||||
Export-PowerShellDataFile -InputObject $ConfigurationData -Path $tempPath -Force
|
||||
Invoke-TestBgwMergeSnapshotScript -Script (New-TestBgwMergeSnapshotScript -ConfigurationDataPath $tempPath)
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $tempPath) {
|
||||
Remove-Item -LiteralPath $tempPath -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function New-TestBgwMergeApiConfigurationData {
|
||||
param([object]$Composition)
|
||||
|
||||
Import-Module $MergeModulePath -Force
|
||||
|
||||
$mergedTemplateData = @{}
|
||||
|
||||
foreach ($selection in @($Composition.templateSelections | Sort-Object sortOrder)) {
|
||||
$templateData = ConvertFrom-TestBgwMergeTemplateJson -JsonData $selection.templateVersion.jsonData
|
||||
$mergedTemplateData = Merge-DSCConfigurationData -Template $mergedTemplateData -Deployment $templateData
|
||||
}
|
||||
|
||||
$deploymentData = @{
|
||||
Resources = @{
|
||||
AllNodes = ConvertFrom-TestBgwMergeTargetAssignments -TargetAssignments @($Composition.targetAssignments)
|
||||
}
|
||||
}
|
||||
|
||||
Merge-DSCConfigurationData -Template $mergedTemplateData -Deployment $deploymentData
|
||||
}
|
||||
|
||||
Describe 'BGW Test.Merge.ps1 API DemoData composition' {
|
||||
BeforeAll {
|
||||
$script:expectedTemplateAliases = @(
|
||||
'Environment-Test'
|
||||
'Domain-Contoso'
|
||||
'Service-SharePoint-Contoso'
|
||||
'Stage-Install'
|
||||
)
|
||||
|
||||
$script:expectedTemplateRoles = @(
|
||||
'Environment'
|
||||
'Domain'
|
||||
'Service'
|
||||
'Stage'
|
||||
)
|
||||
|
||||
$script:localSnapshot = New-TestBgwMergeLocalSnapshot
|
||||
$script:apiComposition = Invoke-TestBgwMergeApiJson -Method GET -Path "deployment-batches/$DeploymentBatchId/composition"
|
||||
$script:apiConfigurationData = New-TestBgwMergeApiConfigurationData -Composition $script:apiComposition
|
||||
$script:apiSnapshot = New-TestBgwMergeSnapshotFromConfigurationData -ConfigurationData $script:apiConfigurationData
|
||||
}
|
||||
|
||||
It 'loads the seeded deployment composition from the API' {
|
||||
$script:apiComposition.deploymentBatchId | Should Be $DeploymentBatchId
|
||||
@($script:apiComposition.templateSelections).Count | Should Be 4
|
||||
@($script:apiComposition.targetAssignments).Count | Should Be 3
|
||||
}
|
||||
|
||||
It 'keeps the template selection order from Test.Merge.ps1' {
|
||||
$aliases = @($script:apiComposition.templateSelections | Sort-Object sortOrder | ForEach-Object { $_.alias })
|
||||
$roles = @($script:apiComposition.templateSelections | Sort-Object sortOrder | ForEach-Object { $_.templateRole })
|
||||
|
||||
($aliases -join '|') | Should Be ($script:expectedTemplateAliases -join '|')
|
||||
($roles -join '|') | Should Be ($script:expectedTemplateRoles -join '|')
|
||||
}
|
||||
|
||||
It 'keeps the AllNodes data from Test.Merge.ps1' {
|
||||
$nodes = @($script:apiSnapshot.AllNodes | Sort-Object NodeName)
|
||||
|
||||
$nodes.Count | Should Be 3
|
||||
$nodes[0].NodeName | Should Be 'CLD-SHP-01'
|
||||
$nodes[0].RunCentralAdministration | Should Be $true
|
||||
$nodes[1].NodeName | Should Be 'CLD-SHP-02'
|
||||
$nodes[1].RunCentralAdministration | Should Be $false
|
||||
$nodes[2].NodeName | Should Be 'CLD-SHP-03'
|
||||
$nodes[2].RunCentralAdministration | Should Be $false
|
||||
}
|
||||
|
||||
It 'resolves the same core values as the local Test.Merge.ps1 baseline' {
|
||||
$script:apiSnapshot.DatabasePrefix | Should Be $script:localSnapshot.DatabasePrefix
|
||||
$script:apiSnapshot.ServiceDbPrefix | Should Be $script:localSnapshot.ServiceDbPrefix
|
||||
$script:apiSnapshot.ConfigDbName | Should Be $script:localSnapshot.ConfigDbName
|
||||
$script:apiSnapshot.LcmConfigurationMode | Should Be $script:localSnapshot.LcmConfigurationMode
|
||||
}
|
||||
|
||||
It 'matches merged Parameters, Variables and Resources' {
|
||||
$script:apiSnapshot.ParametersJson | Should Be $script:localSnapshot.ParametersJson
|
||||
$script:apiSnapshot.VariablesJson | Should Be $script:localSnapshot.VariablesJson
|
||||
$script:apiSnapshot.ResourcesJson | Should Be $script:localSnapshot.ResourcesJson
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user