- Added ApiClientModel and ApiTokenModel for managing API clients and tokens. - Introduced ConfigurationDefinitionModel and ConfigurationValueModel for configuration management. - Created CredentialSecretModel for storing credential secrets. - Developed DeploymentArtifactModel and DeploymentBatchModel for deployment management. - Enhanced DeploymentTargetModel and DeploymentTemplateSelectionModel to support template revisions. - Added TemplateRevisionModel and TemplateVersionModel for versioning templates. - Implemented ApiClientSecretHasher for secure secret hashing. - Created ApiTokenService for generating and validating JWT tokens. - Updated QueueJobService to handle deployment requests with artifacts. - Configured authentication settings in appsettings.json for JWT and Negotiate authentication.
311 lines
15 KiB
PowerShell
311 lines
15 KiB
PowerShell
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 = '<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 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'
|
|
}
|
|
}
|