diff --git a/Actions/.Modules/ReadSettings.psm1 b/Actions/.Modules/ReadSettings.psm1 index 8109baf299..a5707dc73c 100644 --- a/Actions/.Modules/ReadSettings.psm1 +++ b/Actions/.Modules/ReadSettings.psm1 @@ -16,13 +16,19 @@ $CustomTemplateProjectSettingsFile = Join-Path '.github' $CustomTemplateProjectS function MergeCustomObjectIntoOrderedDictionary { Param( [System.Collections.Specialized.OrderedDictionary] $dst, - [PSCustomObject] $src + [PSCustomObject] $src, + [string[]] $srcImportantSettings = @(), + [string[]] $dstImportantSettings = @() ) # If the src object contains property 'overwriteSettings' (list of settings), remove these settings from the dst object, so that they can be re-added with the new value later on if ($src.PSObject.Properties.Name -contains "overwriteSettings") { $src.overwriteSettings | ForEach-Object { $prop = $_ + if ($dstImportantSettings -contains $prop -and $srcImportantSettings -notcontains $prop) { + OutputDebug "Ignoring overwriteSettings for '$prop' because it is important in higher priority settings and not marked important in source" + return + } if ($dst.Contains($prop) -and $src.PSObject.Properties.Name -contains $prop) { # Remove the property from the destination object only if it also exists in the source object. The property will be re-added with the new value later on. OutputDebug "Overwriting setting $prop" @@ -37,7 +43,7 @@ function MergeCustomObjectIntoOrderedDictionary { $src.PSObject.Properties.GetEnumerator() | ForEach-Object { $prop = $_.Name - # Skip overwriteSettings property as it's only used to remove settings from the destination object and is specific to the source object + # Skip overwriteSettings property as it's only used for configuration, not actual settings if ($prop -eq "overwriteSettings") { return } @@ -62,7 +68,7 @@ function MergeCustomObjectIntoOrderedDictionary { # If the property exists in the source object, but is of a different type, throw an error # If the property exists in the source object: # If the property is an Object, call this function recursively to merge values - # If the property is an Object[], merge the arrays + # If the property is an Object[], merge the arrays (even if important - arrays always merge) # If the property is a simple type, replace the value in the destination object with the value from the source object @($dst.Keys) | ForEach-Object { $prop = $_ @@ -71,6 +77,13 @@ function MergeCustomObjectIntoOrderedDictionary { $srcProp = $src."$prop" $dstPropType = $dstProp.GetType().Name $srcPropType = $srcProp.GetType().Name + + # For non-array properties: skip if this setting is marked as important from higher priority source, + # unless the lower-priority source also marks this property as important. + if ($dstImportantSettings -contains $prop -and $srcPropType -ne "Object[]" -and $srcImportantSettings -notcontains $prop) { + OutputDebug "Skipping important setting '$prop' marked from higher priority source (non-array type)" + return + } if ($srcPropType -eq "PSCustomObject" -and $dstPropType -eq "OrderedDictionary") { MergeCustomObjectIntoOrderedDictionary -dst $dst."$prop" -src $srcProp } @@ -262,7 +275,8 @@ function GetDefaultSettings "filesToInclude" = @() "filesToExclude" = @() } - "postponeProjectInBuildOrder" = $false + "postponeProjectInBuildOrder" = $false + "importantSettings" = @() } } @@ -483,11 +497,19 @@ function ReadSettings { } } - foreach($settingsObject in $settingsObjects) { + $currentImportantSettings = @() + foreach ($settingsObject in $settingsObjects) { $settingsJson = $settingsObject.Settings if ($settingsJson) { OutputDebug "Applying settings from $($settingsObject.Source) ($($settingsObject.Type))" - MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson + $srcImportantSettings = @() + if ($settingsJson.PSObject.Properties.Name -contains "importantSettings") { + $srcImportantSettings = @($settingsJson.importantSettings) + } + MergeCustomObjectIntoOrderedDictionary -dst $settings -src $settingsJson -srcImportantSettings $srcImportantSettings -dstImportantSettings $currentImportantSettings + if ($settingsJson.PSObject.Properties.Name -contains "importantSettings") { + $currentImportantSettings = @($currentImportantSettings + $srcImportantSettings | Select-Object -Unique) + } if ($settingsJson.PSObject.Properties.Name -eq "ConditionalSettings") { foreach($conditionalSetting in $settingsJson.ConditionalSettings) { if ("$conditionalSetting" -ne "") { @@ -509,7 +531,14 @@ function ReadSettings { } if ($conditionMet) { OutputDebug "Applying conditional settings for $($conditions -join ", ")" - MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings + $srcImportantSettings = @() + if ($conditionalSetting.settings.PSObject.Properties.Name -contains "importantSettings") { + $srcImportantSettings = @($conditionalSetting.settings.importantSettings) + } + MergeCustomObjectIntoOrderedDictionary -dst $settings -src $conditionalSetting.settings -srcImportantSettings $srcImportantSettings -dstImportantSettings $currentImportantSettings + if ($conditionalSetting.settings.PSObject.Properties.Name -contains "importantSettings") { + $currentImportantSettings = @($currentImportantSettings + $srcImportantSettings | Select-Object -Unique) + } } } } diff --git a/Actions/.Modules/settings.schema.json b/Actions/.Modules/settings.schema.json index 4a1cd415d3..7fe826f293 100644 --- a/Actions/.Modules/settings.schema.json +++ b/Actions/.Modules/settings.schema.json @@ -399,7 +399,7 @@ "type": "string", "enum": ["includeDependencies"] }, - "description": "An array of settings to be overwritten by the current deliverToAppSource setting. See https://aka.ms/ALGoSettings#overwriteSettings" + "description": "An array of settings to be overwritten by the current deliverToAppSource setting. If a setting is marked as important in higher-priority settings, overwrite is ignored unless the current source also marks that same setting as important. See https://aka.ms/ALGoSettings#overwriteSettings" } }, "required": [ @@ -654,7 +654,7 @@ "type": "string", "enum": ["includeProjects", "excludeProjects"] }, - "description": "An array of settings to be overwritten by the current alDoc setting. See https://aka.ms/ALGoSettings#overwriteSettings" + "description": "An array of settings to be overwritten by the current alDoc setting. If a setting is marked as important in higher-priority settings, overwrite is ignored unless the current source also marks that same setting as important. See https://aka.ms/ALGoSettings#overwriteSettings" } }, "commitOptions": { @@ -684,7 +684,7 @@ "type": "string", "enum": ["pullRequestLabels"] }, - "description": "An array of settings to be overwritten by the current commitOptions setting. See https://aka.ms/ALGoSettings#overwriteSettings" + "description": "An array of settings to be overwritten by the current commitOptions setting. If a setting is marked as important in higher-priority settings, overwrite is ignored unless the current source also marks that same setting as important. See https://aka.ms/ALGoSettings#overwriteSettings" } }, "required": [ @@ -732,7 +732,15 @@ "type": "string", "enum": ["unusedALGoSystemFiles", "projects", "additionalCountries", "appDependencies", "appFolders", "testDependencies", "testFolders", "bcptTestFolders", "pageScriptingTests", "restoreDatabases", "installApps", "installTestApps", "customCodeCops", "configPackages", "appSourceCopMandatoryAffixes", "deliverToAppSource", "appDependencyProbingPaths", "incrementalBuilds", "environments", "buildModes", "bcptThresholds", "fullBuildPatterns", "excludeEnvironments", "alDoc", "commitOptions", "trustedSigning"] }, - "description": "An array of settings to be overwritten by the current settings. See https://aka.ms/ALGoSettings#overwriteSettings" + "description": "An array of settings to be overwritten by the current settings. If a setting is marked as important in higher-priority settings, overwrite is ignored unless the current source also marks that same setting as important. See https://aka.ms/ALGoSettings#overwriteSettings" + }, + "importantSettings": { + "type": "array", + "items": { + "type": "string" + }, + "default": [], + "description": "An array of top-level setting names that are protected from non-important lower-priority overrides. If the same setting is marked as important in both source and destination, the source value is allowed to override. See https://aka.ms/ALGoSettings#importantSettings" }, "reportSuppressedDiagnostics": { "type": "boolean", diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 75e04d37ef..951305189c 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,3 +1,37 @@ +### Important settings protection + +A new `importantSettings` setting allows you to protect specific settings from being overridden by lower-priority sources in the settings hierarchy. When a setting is marked as important at a higher priority level, it cannot be overridden by non-important settings from lower priority sources. + +```json +{ + "importantSettings": ["country", "keyVaultName"], + "country": "de", + "keyVaultName": "orgVault" +} +``` + +**Behavior:** +- Settings marked as important in organization or repository settings cannot be overridden by non-important values from project, workflow, user, or environment settings +- If a lower-priority source also marks the same setting as important, the lower-priority value is allowed to override +- Important arrays are still merged by default +- The `overwriteSettings` mechanism can replace an important setting only when the source also marks that same setting as important +- `ConditionalSettings` respect importantSettings markings, allowing you to enforce conditional important settings based on buildMode, branch, trigger, or user + +**Example with ConditionalSettings:** +```json +{ + "ConditionalSettings": [ + { + "buildModes": ["Validate"], + "settings": { + "importantSettings": ["country"], + "country": "us" + } + } + ] +} +``` + ### New `doNotPerformUpgrade` setting AL-Go now supports a new `doNotPerformUpgrade` setting that is passed through to `Run-AlPipeline`. Use it to skip the upgrade phase while still running the rest of the pipeline. @@ -22,6 +56,7 @@ Workspace compilation now finds altool both in the platform-specific subfolder ( ## v9.1 + ### Resilient Pull Request Status Check for large builds The Pull Request Status Check action no longer fails on builds with more than one page of jobs (more than 100 jobs). The jobs API call now uses `--slurp` so multi-page responses are parsed as a single JSON array (previously `gh api --paginate | ConvertFrom-Json` failed with "Invalid JSON primitive" when more than one page was returned). The call is also retried, and requests a smaller page size, to tolerate the intermittent HTTP 502 responses that the GitHub jobs endpoint returns for large builds. diff --git a/Scenarios/settings.md b/Scenarios/settings.md index c0118f92d4..94e4442a69 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -317,6 +317,83 @@ then, after merging, the result settings object will contain the following value > _**Note**_: `overwriteSettings` isn't a setting on its own and it isn't available in the output of `ReadSetting` action, for example. It's merely used to control the settings merging mechanism and allow overwriting complex settings types. The value of `overwriteSettings` should only contain settings of types _array_ or _object_ and all the settings in `overwriteSettings` should be present with the new value. +## Important settings + +By default, AL-Go follows a standard settings hierarchy where settings from higher priority levels (closer to deployment) override settings from lower priority levels. However, you can mark specific settings as **important** to protect them from being overridden by lower priority settings using the `importantSettings` array. + +When a setting is marked as important at a higher level in the hierarchy, it cannot be overridden by non-important values from lower priority levels. If a lower-priority source also marks the same setting as important, then the lower-priority value is allowed to override. + +_Example_: +Say, `ALGoOrgSettings` (organization level) contains the following values: + +```json +{ + "importantSettings": ["country", "keyVaultName"], + "country": "de", + "keyVaultName": "OrgVault" +} +``` + +and `.AL-Go\settings.json` (project level, lower priority) contains the following values: + +```json +{ + "country": "us", + "keyVaultName": "ProjectVault" +} +``` + +then, after merging, the result settings object will contain the following values: + +```json +{ + "importantSettings": ["country", "keyVaultName"], + "country": "de", + "keyVaultName": "OrgVault" +} +``` + +The `country` and `keyVaultName` settings from the organization level are protected and cannot be overridden by the project level settings. + +_Example with ConditionalSettings_: +Say, `ALGoOrgSettings` (organization level) contains conditional settings that set country based on buildMode: + +```json +{ + "ConditionalSettings": [ + { + "buildModes": ["ValidateUS"], + "settings": { + "importantSettings": ["country"], + "country": "us" + } + } + ] +} +``` + +and `.AL-Go\settings.json` (project level) contains: + +```json +{ + "country": "w1", + "buildModes": ["Default", "ValidateUS"] +} +``` + +When reading settings for buildMode `ValidateUS`, the conditional setting from the organization level will apply. The result will be: + +```json +{ + "country": "us", + "buildModes": ["Default", "ValidateUS"] +} +``` + +Even though the project specifies `country: "w1"`, the conditional setting from the organization level marked the country as important for the `ValidateUS` buildMode and the project value is not marked important, so the conditional value takes precedence. + +> _**Note**_: `importantSettings` is an array of setting names that should be protected from non-important overrides from lower priority settings. If the same setting is marked as important at both levels, the source (lower-priority) value is allowed to override the destination value. Only top-level setting names can be marked as important; nested properties within complex objects cannot be individually marked as important. Array settings marked as important are still merged with lower-priority arrays. `overwriteSettings` can force replacement for important settings only when the source also marks that same setting as important. + ## Custom Delivery diff --git a/Tests/ReadSettings.Test.ps1 b/Tests/ReadSettings.Test.ps1 index 7e11477adf..f0675e90a7 100644 --- a/Tests/ReadSettings.Test.ps1 +++ b/Tests/ReadSettings.Test.ps1 @@ -541,6 +541,533 @@ InModuleScope ReadSettings { # Allows testing of private functions Remove-Item -Path $tempName -Recurse -Force } + It 'importantSettings from higher priority source prevents overwrite from lower priority' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Repo settings: important settings includes "country", set country = "de" + @{ "importantSettings" = @("country"); "country" = "de" } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: try to override country = "ch" + @{ "country" = "ch" } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # Important setting from repo should prevent project from overwriting + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.country | Should -Be 'de' # Repo important value wins + $settings.importantSettings | Should -Contain 'country' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'Multiple importantSettings are respected' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Repo settings: mark both country and keyVaultName as important + @{ + "importantSettings" = @("country", "keyVaultName") + "country" = "de" + "keyVaultName" = "orgVault" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: try to override both + @{ + "country" = "ch" + "keyVaultName" = "projectVault" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.country | Should -Be 'de' + $settings.keyVaultName | Should -Be 'orgVault' + $settings.importantSettings | Should -Contain 'country' + $settings.importantSettings | Should -Contain 'keyVaultName' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'Lower-priority important settings can override higher-priority important settings' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings (via variable): mark country as important and set to "de" + $ENV:ALGoOrgSettings = @{ + "importantSettings" = @("country") + "country" = "de" + } | ConvertTo-Json -Depth 99 + + # Repo settings: try to override country with normal (non-important) setting = "us" + @{ "country" = "us" } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: try to override with important setting = "ch" + @{ + "importantSettings" = @("country") + "country" = "ch" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + # Project setting is also marked as important and should be allowed to override + # an important setting from a higher-priority source. + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.country | Should -Be 'ch' # Source important overrides destination important + + $ENV:ALGoOrgSettings = '' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'importantSettings marked arrays are still merged with lower priority arrays' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Repo settings: mark additionalCountries as important with specific values + @{ + "importantSettings" = @("additionalCountries") + "additionalCountries" = @("de", "at") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: try to add more countries + @{ + "additionalCountries" = @("ch", "be") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # Important array settings are still merged with lower priority arrays (exception: if overwriteSettings is used) + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.additionalCountries | Should -Be @("de", "at", "ch", "be") # Org + Project values merged + $settings.importantSettings | Should -Contain "additionalCountries" + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'importantSettings are not overridden by overwriteSettings unless source also marks them important' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Repo settings: mark additionalCountries as important + @{ + "importantSettings" = @("additionalCountries") + "additionalCountries" = @("de", "at") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: use overwriteSettings to override additionalCountries (force replacement instead of merge) + @{ + "overwriteSettings" = @("additionalCountries") + "additionalCountries" = @("ch", "be") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # overwriteSettings should be ignored because source does not mark the setting as important. + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.additionalCountries | Should -Be @("de", "at", "ch", "be") + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'importantSettings can be overridden with overwriteSettings when source also marks them important' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + @{ + "importantSettings" = @("additionalCountries") + "additionalCountries" = @("de", "at") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + @{ + "importantSettings" = @("additionalCountries") + "overwriteSettings" = @("additionalCountries") + "additionalCountries" = @("ch", "be") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.additionalCountries | Should -Be @("ch", "be") + + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'Non-important array settings are merged normally (baseline)' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Repo settings: additionalCountries WITHOUT marking as important + @{ + "additionalCountries" = @("de", "at") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: add more countries + @{ + "additionalCountries" = @("ch", "be") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # Without important marking, arrays should be merged + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.additionalCountries | Should -Be @("de", "at", "ch", "be") # All values merged + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'Empty importantSettings has no effect (backward compatibility)' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Repo settings: importantSettings is empty + @{ + "importantSettings" = @() + "country" = "us" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: override country + @{ + "country" = "ch" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # Without important marking, normal hierarchy applies + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.country | Should -Be 'ch' # Project wins (normal behavior) + $settings.importantSettings | Should -Be @() # Empty array preserved + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'ConditionalSetting with importantSettings at repo level overrides project setting for specific buildMode' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Repo settings: ConditionalSetting for buildMode "ValidateUS" with important country marking + @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("ValidateUS") + "settings" = @{ + "importantSettings" = @("country") + "country" = "us" + } + } + ) + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $githubFolder "AL-Go-Settings.json") -Encoding utf8 -Force + + # Project settings: country = "w1", buildModes include "ValidateUS" + @{ + "country" = "w1" + "buildModes" = @("Default", "ValidateUS") + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = '' + + # When reading for buildMode "Default", project country "w1" should be used + $settingsDefault = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -buildMode 'Default' -userName '' + $settingsDefault.country | Should -Be 'w1' # No org conditional applies for "Default" + + # When reading for buildMode "ValidateUS", repo conditional with important marking should override project + $settingsValidateUS = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -buildMode 'ValidateUS' -userName '' + $settingsValidateUS.country | Should -Be 'us' # Repo conditional important setting wins + $settingsValidateUS.buildModes | Should -Contain 'ValidateUS' + $settingsValidateUS.importantSettings | Should -Contain 'country' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + + It 'importantSettings are merged correctly' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: importantSettings is filled + $ENV:ALGoOrgSettings = @{ + "importantSettings" = @("country") + "country" = "us" + } | ConvertTo-Json -Depth 99 + + # Repo settings: add another important setting + $ENV:ALGoRepoSettings = @{ + "importantSettings" = @("companyName") + "country" = "de" + "companyName" = "MyCompany" + } | ConvertTo-Json -Depth 99 + + # Project settings: add another important setting + @{ + "importantSettings" = @("keyVaultName") + "country" = "ch" + "keyVaultName" = "mykv" + "companyName" = "AnotherCompany" + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + # Without important marking, normal hierarchy applies + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' + $settings.importantSettings | Should -Contain 'country' # from repo settings + $settings.importantSettings | Should -Contain 'companyName' # from repo settings + $settings.importantSettings | Should -Contain 'keyVaultName' # from project settings + + $settings.country | Should -Be 'us' # from org settings + $settings.companyName | Should -Be 'MyCompany' # from repo settings + $settings.keyVaultName | Should -Be 'mykv' # from project settings + + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + It 'conditional importantSettings are merged correctly' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: importantSettings is filled + $ENV:ALGoOrgSettings = @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("CustomBuildMode") + "settings" = @{ + "importantSettings" = @("country") + "country" = "us" + } + }) + } | ConvertTo-Json -Depth 99 + + # Repo settings: add another important setting + $ENV:ALGoRepoSettings = @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("CustomBuildMode") + "settings" = @{ "importantSettings" = @("companyName") + "country" = "de" + "companyName" = "MyCompany" + } + }) + } | ConvertTo-Json -Depth 99 + + # Project settings: add another important setting + @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("CustomBuildMode") + "settings" = @{ "importantSettings" = @("keyVaultName") + "country" = "ch" + "keyVaultName" = "mykv" + "companyName" = "AnotherCompany" + } + }) + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + # Without important marking, normal hierarchy applies + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' -buildMode 'CustomBuildMode' + $settings.importantSettings | Should -Contain 'country' # from repo settings + $settings.importantSettings | Should -Contain 'companyName' # from repo settings + $settings.importantSettings | Should -Contain 'keyVaultName' # from project settings + + $settings.country | Should -Be 'us' # from org settings + $settings.companyName | Should -Be 'MyCompany' # from repo settings + $settings.keyVaultName | Should -Be 'mykv' # from project settings + + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + It 'mixed importantSettings are merged correctly' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + $projectALGoFolder = Join-Path $tempName "Project/$ALGoFolderName" + + New-Item $githubFolder -ItemType Directory | Out-Null + New-Item $projectALGoFolder -ItemType Directory | Out-Null + + # Org settings: importantSettings is filled + $ENV:ALGoOrgSettings = @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("CustomBuildMode") + "settings" = @{ + "importantSettings" = @("country") + "country" = "us" + } + }) + } | ConvertTo-Json -Depth 99 + + # Repo settings: add another important setting + $ENV:ALGoRepoSettings = @{ + "importantSettings" = @("companyName") + "country" = "de" + "companyName" = "MyCompany" + + } | ConvertTo-Json -Depth 99 + + # Project settings: add another important setting + @{ + "ConditionalSettings" = @( + @{ + "buildModes" = @("CustomBuildMode") + "settings" = @{ "importantSettings" = @("keyVaultName") + "country" = "ch" + "keyVaultName" = "mykv" + "companyName" = "AnotherCompany" + } + }) + } | ConvertTo-Json -Depth 99 | + Set-Content -Path (Join-Path $projectALGoFolder "settings.json") -Encoding utf8 -Force + + # Without important marking, normal hierarchy applies + $settings = ReadSettings -baseFolder $tempName -project 'Project' -repoName 'repo' -workflowName '' -branchName '' -userName '' -buildMode 'CustomBuildMode' + $settings.importantSettings | Should -Contain 'country' # from repo settings + $settings.importantSettings | Should -Contain 'companyName' # from repo settings + $settings.importantSettings | Should -Contain 'keyVaultName' # from project settings + + $settings.country | Should -Be 'us' # from org settings + $settings.companyName | Should -Be 'MyCompany' # from repo settings + $settings.keyVaultName | Should -Be 'mykv' # from project settings + + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } + It 'ValidateSettings skips validation entirely on PS versions less than 7 without warning' { Mock OutputWarning { } Mock ConvertTo-Json { '{}' }