param( [string]$ApiBaseUrl = 'http://localhost:5286/api', [string]$DeploymentBatchId = '80000000-0000-0000-0000-000000000101', [int]$ApiTimeoutSec = 30, [bool]$UseDefaultCredentials = $true, [System.Management.Automation.PSCredential]$Credential ) $ErrorActionPreference = 'Stop' function Invoke-TestMergeApiJson { 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 ($Credential) { $parameters.Credential = $Credential } elseif ($UseDefaultCredentials) { $parameters.UseDefaultCredentials = $true } try { Invoke-RestMethod @parameters } catch { $responseBody = '' 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 = '' } } throw "API request failed. Method=[$Method], Uri=[$uri], Response=[$responseBody]. $($_.Exception.Message)" } } function ConvertFrom-TestJson { param([string]$Json) $Json | ConvertFrom-Json } function Get-TestDefinition { param( [object[]]$Definitions, [string]$RevisionId, [string]$Kind, [string]$Name ) @($Definitions | Where-Object { [string]$_.templateRevisionId -eq $RevisionId -and $_.kind -eq $Kind -and $_.name -eq $Name })[0] } function Get-TestFirst { param([object[]]$Items) @($Items | Select-Object -First 1)[0] } function Get-TestSelectionByRole { param( [object]$Composition, [string]$Role ) Get-TestFirst -Items @($Composition.templateSelections | Where-Object { $_.templateRole -eq $Role }) } function Get-TestRevisionDefinitions { param([object]$Selection) $templateId = [string]$Selection.templateVersion.templateId $versionId = [string]$Selection.templateVersionId $revisionId = [string]$Selection.templateRevisionId @(Invoke-TestMergeApiJson -Method GET -Path "Template/$templateId/Versions/$versionId/Revisions/$revisionId/Definitions") } function Assert-TestApiIsReachable { try { Invoke-TestMergeApiJson -Method GET -Path "deployment-batches/$DeploymentBatchId/composition" | Out-Null } catch { if ($_.Exception.Message -match '401|Nicht autorisiert|Unauthorized') { throw @" API authentication failed for [$ApiBaseUrl]. Start the API with Windows authentication enabled and run the test from a session that can authenticate to it. You can also pass explicit credentials: Invoke-Pester -Script @{ Path = '.Net\Microsoft.SelfService.Portal.Core.API\.tests\Api.Tests.ps1'; Parameters = @{ Credential = (Get-Credential) } } $($_.Exception.Message) "@ } throw } } Describe ' API DemoData composition' { BeforeAll { Assert-TestApiIsReachable $script:expectedTemplateAliases = @( 'Environment-Test' 'Domain-Contoso' 'Service-SharePoint-Contoso' 'Stage-Install' ) $script:expectedTemplateRoles = @( 'Environment' 'Domain' 'Service' 'Stage' ) $script:expectedTemplateRevisionIds = @( '72000000-0000-0000-0000-000000000101' '72000000-0000-0000-0000-000000000102' '72000000-0000-0000-0000-000000000103' '72000000-0000-0000-0000-000000000104' ) $script:apiComposition = Invoke-TestMergeApiJson -Method GET -Path "deployment-batches/$DeploymentBatchId/composition" $script:environmentSelection = Get-TestSelectionByRole -Composition $script:apiComposition -Role 'Environment' $script:domainSelection = Get-TestSelectionByRole -Composition $script:apiComposition -Role 'Domain' $script:sharePointSelection = Get-TestSelectionByRole -Composition $script:apiComposition -Role 'Service' $script:stageSelection = Get-TestSelectionByRole -Composition $script:apiComposition -Role 'Stage' $script:environmentRevisionId = [string]$script:environmentSelection.templateRevisionId $script:domainRevisionId = [string]$script:domainSelection.templateRevisionId $script:sharePointRevisionId = [string]$script:sharePointSelection.templateRevisionId $script:environmentDefinitions = Get-TestRevisionDefinitions -Selection $script:environmentSelection $script:domainDefinitions = Get-TestRevisionDefinitions -Selection $script:domainSelection $script:sharePointDefinitions = Get-TestRevisionDefinitions -Selection $script:sharePointSelection $script:stageDefinitions = Get-TestRevisionDefinitions -Selection $script:stageSelection $script:deploymentArtifacts = @(Invoke-TestMergeApiJson -Method GET -Path "deployment-artifacts?deploymentGroupId=$DeploymentBatchId") $script:credentialSecrets = @(Invoke-TestMergeApiJson -Method GET -Path 'credential-secrets') $script:configurationDefinitions = @(Invoke-TestMergeApiJson -Method GET -Path 'configuration-definitions') $script:parameterDefinitions = @(Invoke-TestMergeApiJson -Method GET -Path 'configuration-definitions?kind=Parameter') $script:variableDefinitions = @(Invoke-TestMergeApiJson -Method GET -Path 'configuration-definitions?kind=Variable') $script:configurationValues = @(Invoke-TestMergeApiJson -Method GET -Path 'configuration-values') } 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 the seeded deployment' { $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 'pins every template selection to an immutable template revision' { $revisionIds = @($script:apiComposition.templateSelections | Sort-Object sortOrder | ForEach-Object { [string]$_.templateRevisionId }) ($revisionIds -join '|') | Should Be ($script:expectedTemplateRevisionIds -join '|') @($revisionIds | Where-Object { [string]::IsNullOrWhiteSpace($_) }).Count | Should Be 0 } It 'loads each pinned template revision and its definitions from the API' { foreach ($selection in @($script:apiComposition.templateSelections | Sort-Object sortOrder)) { $templateId = [string]$selection.templateVersion.templateId $versionId = [string]$selection.templateVersionId $revisionId = [string]$selection.templateRevisionId $revision = Invoke-TestMergeApiJson -Method GET -Path "Template/$templateId/Versions/$versionId/Revisions/$revisionId" $definitions = @(Invoke-TestMergeApiJson -Method GET -Path "Template/$templateId/Versions/$versionId/Revisions/$revisionId/Definitions") $revision.id | Should Be $revisionId $revision.templateVersionId | Should Be $versionId $revision.revisionNumber | Should Be 1 $revision.isCurrent | Should Be $true $revision.isPublished | Should Be $true if ($selection.templateRole -ne 'Stage') { @($definitions).Count | Should BeGreaterThan 0 } } } It 'exposes deployment artifact and credential store API surfaces' { @($script:deploymentArtifacts).Count | Should Be 1 $script:deploymentArtifacts[0].deploymentGroupId | Should Be $DeploymentBatchId $script:deploymentArtifacts[0].artifactType | Should Be 'ResolvedConfigurationData' $script:deploymentArtifacts[0].isStale | Should Be $false $secretNames = @($script:credentialSecrets | ForEach-Object { $_.name } | Sort-Object) $secretNames -contains 'Windows/SharePoint/SetupAccount' | Should Be $true $secretNames -contains 'Windows/SharePoint/FarmAccount' | Should Be $true $secretNames -contains 'Windows/SharePoint/FarmPassphrase' | Should Be $true $secretNames -contains 'Windows/SharePoint/DefaultServiceAccount' | Should Be $true $secretNames -contains 'Windows/SharePoint/SearchAccount' | Should Be $true } It 'returns resolved deployment artifact values without reading local PSD1 files' { $deploymentJson = ConvertFrom-TestJson -Json $script:deploymentArtifacts[0].deploymentJson $deploymentJson.variables.DatabasePrefix | Should Be 'SharePoint_Contoso_Test' $deploymentJson.variables.ServiceDbPrefix | Should Be 'SharePoint_Contoso_Test_Services' $deploymentJson.variables.ConfigDbName | Should Be 'SharePoint_Contoso_Test_Farm_Config' $deploymentJson.resources.NonNodeData.LocalConfigurationManager.ConfigurationMode | Should Be 'ApplyOnly' @($deploymentJson.resources.AllNodes).Count | Should Be 3 $deploymentJson.resources.AllNodes[0].NodeName | Should Be 'CLD-SHP-01' $deploymentJson.resources.AllNodes[0].RunCentralAdministration | Should Be $true } It 'seeds parameter and variable definitions in the Configuration Definition API' { @($script:configurationDefinitions).Count | Should BeGreaterThan 0 @($script:parameterDefinitions).Count | Should BeGreaterThan 0 @($script:variableDefinitions).Count | Should BeGreaterThan 0 $landscape = Get-TestDefinition -Definitions $script:environmentDefinitions -RevisionId $script:environmentRevisionId -Kind 'Parameter' -Name 'Landscape' $domainFqdn = Get-TestDefinition -Definitions $script:domainDefinitions -RevisionId $script:domainRevisionId -Kind 'Parameter' -Name 'DomainFQDN' $databasePrefix = Get-TestDefinition -Definitions $script:sharePointDefinitions -RevisionId $script:sharePointRevisionId -Kind 'Parameter' -Name 'DatabasePrefix' $configDbName = Get-TestDefinition -Definitions $script:environmentDefinitions -RevisionId $script:environmentRevisionId -Kind 'Variable' -Name 'ConfigDbName' $landscape.id | Should Not BeNullOrEmpty $domainFqdn.id | Should Not BeNullOrEmpty $databasePrefix.id | Should Not BeNullOrEmpty $configDbName.id | Should Not BeNullOrEmpty (ConvertFrom-TestJson -Json $landscape.propertiesJson).AllowedValues -contains 'Test' | Should Be $true (ConvertFrom-TestJson -Json $databasePrefix.propertiesJson).DefaultValue | Should Be 'SharePoint' (ConvertFrom-TestJson -Json $configDbName.propertiesJson).Expression | Should Match 'joinNotEmpty' } It 'exposes configuration definitions through the standalone API and keeps revision definitions typed' { $environmentDefinitions = @(Invoke-TestMergeApiJson -Method GET -Path "configuration-definitions?templateRevisionId=$($script:environmentRevisionId)") $environmentVariables = @($script:environmentDefinitions | Where-Object { $_.kind -eq 'Variable' }) @($environmentDefinitions).Count | Should BeGreaterThan 0 @($environmentVariables).Count | Should BeGreaterThan 0 @($environmentVariables | Where-Object { $_.kind -ne 'Variable' }).Count | Should Be 0 $environmentDefinitionNames = @($script:environmentDefinitions | ForEach-Object { $_.name }) $environmentVariableNames = @($environmentVariables | ForEach-Object { $_.name }) $environmentDefinitionNames -contains 'Landscape' | Should Be $true $environmentVariableNames -contains 'ConfigDbName' | Should Be $true } It 'seeds configuration values for parameters and variables' { @($script:configurationValues).Count | Should BeGreaterThan 0 $landscape = Get-TestDefinition -Definitions $script:environmentDefinitions -RevisionId $script:environmentRevisionId -Kind 'Parameter' -Name 'Landscape' $domainFqdn = Get-TestDefinition -Definitions $script:domainDefinitions -RevisionId $script:domainRevisionId -Kind 'Parameter' -Name 'DomainFQDN' $configDbName = Get-TestDefinition -Definitions $script:environmentDefinitions -RevisionId $script:environmentRevisionId -Kind 'Variable' -Name 'ConfigDbName' $landscapeValues = @(Invoke-TestMergeApiJson -Method GET -Path "configuration-definitions/$($landscape.id)/values") $domainFqdnValues = @(Invoke-TestMergeApiJson -Method GET -Path "configuration-values?definitionId=$($domainFqdn.id)") $configDbNameValues = @(Invoke-TestMergeApiJson -Method GET -Path "configuration-values?definitionId=$($configDbName.id)") @($landscapeValues).Count | Should Be 1 @($domainFqdnValues).Count | Should Be 1 @($configDbNameValues).Count | Should Be 1 (ConvertFrom-TestJson -Json $landscapeValues[0].valueJson).value | Should Be 'Test' (ConvertFrom-TestJson -Json $domainFqdnValues[0].valueJson).value | Should Be 'contoso.local' (ConvertFrom-TestJson -Json $configDbNameValues[0].valueJson).expression | Should Match 'joinNotEmpty' $landscapeValues[0].scopeType | Should Be 'TemplateRevision' [string]$landscapeValues[0].scopeId | Should Be $script:environmentRevisionId $configDbNameValues[0].valueSourceType | Should Be 'Expression' } It 'marks sensitive parameter values as secret references' { $setupCredential = Get-TestDefinition -Definitions $script:sharePointDefinitions -RevisionId $script:sharePointRevisionId -Kind 'Parameter' -Name 'SetupCredential' $setupCredentialValues = @(Invoke-TestMergeApiJson -Method GET -Path "configuration-values?definitionId=$($setupCredential.id)") $setupValue = ConvertFrom-TestJson -Json $setupCredentialValues[0].valueJson @($setupCredentialValues).Count | Should Be 1 $setupCredentialValues[0].valueSourceType | Should Be 'SecretReference' $setupValue.value.Provider | Should Be 'SecretManagement' $setupValue.value.Name | Should Be 'Windows/SharePoint/SetupAccount' } }