From fcff18b266724d53b6e4f69c11b55b97b2ef57b5 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Thu, 16 Oct 2025 23:54:38 +0200 Subject: [PATCH 01/30] Set default values on workflow_dispatch input #1952 --- Actions/.Modules/ReadSettings.psm1 | 2 + Actions/.Modules/settings.schema.json | 30 ++ .../CheckForUpdates.HelperFunctions.ps1 | 185 ++++++++ RELEASENOTES.md | 34 ++ Scenarios/settings.md | 1 + Tests/CheckForUpdates.Action.Test.ps1 | 439 ++++++++++++++++++ 6 files changed, 691 insertions(+) diff --git a/Actions/.Modules/ReadSettings.psm1 b/Actions/.Modules/ReadSettings.psm1 index 941491bb9..03eef0122 100644 --- a/Actions/.Modules/ReadSettings.psm1 +++ b/Actions/.Modules/ReadSettings.psm1 @@ -241,6 +241,7 @@ function GetDefaultSettings "gitSubmodulesTokenSecretName" = "gitSubmodulesToken" "shortLivedArtifactsRetentionDays" = 1 # 0 means use GitHub default "reportSuppressedDiagnostics" = $false + "workflowInputDefaults" = @() } } @@ -590,4 +591,5 @@ function SanitizeWorkflowName { } Export-ModuleMember -Function ReadSettings +Export-ModuleMember -Function SanitizeWorkflowName Export-ModuleMember -Variable ALGoFolderName, ALGoSettingsFile, RepoSettingsFile, CustomTemplateRepoSettingsFile, CustomTemplateProjectSettingsFile diff --git a/Actions/.Modules/settings.schema.json b/Actions/.Modules/settings.schema.json index b498eb32b..34870eb5d 100644 --- a/Actions/.Modules/settings.schema.json +++ b/Actions/.Modules/settings.schema.json @@ -693,6 +693,36 @@ "reportSuppressedDiagnostics": { "type": "boolean", "description": "Report suppressed diagnostics. See https://aka.ms/ALGoSettings#reportsuppresseddiagnostics" + }, + "workflowInputDefaults": { + "type": "array", + "items": { + "type": "object", + "properties": { + "workflow": { + "type": "string", + "description": "The workflow name to apply default values to (must match exactly)" + }, + "defaults": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the workflow input" + }, + "value": { + "description": "The default value for the workflow input (can be string, boolean, or number)" + } + }, + "required": ["name", "value"] + } + } + }, + "required": ["workflow", "defaults"] + }, + "description": "An array of workflow input default values. See https://aka.ms/ALGoSettings#workflowInputDefaults" } } } diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index 83e79150a..26ecd8384 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -301,6 +301,186 @@ function ModifyUpdateALGoSystemFiles { $yaml.Replace('jobs:/UpdateALGoSystemFiles:/', $updateALGoSystemFilesJob.content) } +function ApplyWorkflowInputDefaults { + Param( + [Yaml] $yaml, + [hashtable] $repoSettings, + [string] $workflowName + ) + + # Check if workflow_dispatch inputs exist + $workflowDispatch = $yaml.Get('on:/workflow_dispatch:/') + if (-not $workflowDispatch) { + # No workflow_dispatch section, nothing to do + return + } + + $inputs = $workflowDispatch.Get('inputs:/') + if (-not $inputs) { + # No inputs section, nothing to do + return + } + + # Sanitize the workflow name for matching + $sanitizedWorkflowName = SanitizeWorkflowName -workflowName $workflowName + + # Find matching workflow input defaults + $matchingDefaults = @() + foreach ($workflowInputDefault in $repoSettings.workflowInputDefaults) { + if ($workflowInputDefault.workflow) { + # Sanitize the workflow name in the setting for comparison + $settingWorkflowName = SanitizeWorkflowName -workflowName $workflowInputDefault.workflow + if ($sanitizedWorkflowName -eq $settingWorkflowName) { + $matchingDefaults += $workflowInputDefault + } + } + } + + if ($matchingDefaults.Count -eq 0) { + # No matching defaults for this workflow + return + } + + # Apply defaults to matching inputs + foreach ($workflowInputDefault in $matchingDefaults) { + foreach ($default in $workflowInputDefault.defaults) { + $inputName = $default.name + $defaultValue = $default.value + + # Check if this input exists in the workflow + $inputSection = $inputs.Get("$($inputName):/") + if ($inputSection) { + # Get the input type from the YAML if specified + $inputType = $null + $typeStart = 0 + $typeCount = 0 + if ($inputSection.Find('type:', [ref] $typeStart, [ref] $typeCount)) { + $typeLine = $inputSection.content[$typeStart].Trim() + if ($typeLine -match 'type:\s*(.+)') { + $inputType = $matches[1].Trim() + } + } + + # Validate that the value type matches the input type + $validationError = $null + if ($inputType) { + switch ($inputType) { + 'boolean' { + if ($defaultValue -isnot [bool]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected boolean value, but got $($defaultValue.GetType().Name). Please use `$true or `$false." + } + } + 'number' { + if ($defaultValue -isnot [int] -and $defaultValue -isnot [long] -and $defaultValue -isnot [double]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected number value, but got $($defaultValue.GetType().Name)." + } + } + 'string' { + if ($defaultValue -isnot [string]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected string value, but got $($defaultValue.GetType().Name)." + } + } + 'choice' { + # Choice inputs accept strings and must match one of the available options + if ($defaultValue -isnot [string]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected string value for choice input, but got $($defaultValue.GetType().Name)." + } + else { + # Validate that the value is one of the available options + $optionsStart = 0 + $optionsCount = 0 + if ($inputSection.Find('options:', [ref] $optionsStart, [ref] $optionsCount)) { + $availableOptions = @() + # Parse the options from the YAML (they are indented list items starting with "- ") + for ($i = $optionsStart + 1; $i -lt ($optionsStart + $optionsCount); $i++) { + $optionLine = $inputSection.content[$i].Trim() + if ($optionLine -match '^-\s*(.+)$') { + $availableOptions += $matches[1].Trim() + } + } + + if ($availableOptions.Count -gt 0 -and $availableOptions -notcontains $defaultValue) { + $validationError = "Workflow '$workflowName', input '$inputName': Value '$defaultValue' is not a valid choice. Available options: $($availableOptions -join ', ')." + } + } + } + } + } + } + else { + # If no type is specified in the workflow, it defaults to string + if ($defaultValue -isnot [string]) { + OutputWarning "Workflow '$workflowName', input '$inputName': No type specified in workflow (defaults to string), but configured value is $($defaultValue.GetType().Name). This may cause issues." + } + } + + if ($validationError) { + throw $validationError + } + + # Convert the default value to the appropriate YAML format + $yamlValue = $defaultValue + if ($defaultValue -is [bool]) { + $yamlValue = $defaultValue.ToString().ToLower() + } + elseif ($defaultValue -is [string]) { + # Quote strings + $yamlValue = "'$defaultValue'" + } + + # Find and replace the default: line in the input section + $start = 0 + $count = 0 + if ($inputSection.Find('default:', [ref] $start, [ref] $count)) { + # Replace existing default value + $inputSection.Replace('default:', "default: $yamlValue") + } + else { + # Add default value - find the best place to insert it + # Insert after type, required, or description (whichever comes last) + $insertAfter = -1 + $typeLine = 0 + $typeCount = 0 + $requiredLine = 0 + $requiredCount = 0 + $descLine = 0 + $descCount = 0 + + if ($inputSection.Find('type:', [ref] $typeLine, [ref] $typeCount)) { + $insertAfter = $typeLine + $typeCount + } + if ($inputSection.Find('required:', [ref] $requiredLine, [ref] $requiredCount)) { + if (($requiredLine + $requiredCount) -gt $insertAfter) { + $insertAfter = $requiredLine + $requiredCount + } + } + if ($inputSection.Find('description:', [ref] $descLine, [ref] $descCount)) { + if (($descLine + $descCount) -gt $insertAfter) { + $insertAfter = $descLine + $descCount + } + } + + if ($insertAfter -eq -1) { + # No other properties, insert at position 1 (after the input name) + $insertAfter = 1 + } + + $inputSection.Insert($insertAfter, "default: $yamlValue") + } + + # Update the inputs section with the modified input + $inputs.Replace("$($inputName):/", $inputSection.content) + } + } + } + + # Update the workflow_dispatch section with modified inputs + $workflowDispatch.Replace('inputs:/', $inputs.content) + + # Update the on: section with modified workflow_dispatch + $yaml.Replace('on:/workflow_dispatch:/', $workflowDispatch.content) +} + function GetWorkflowContentWithChangesFromSettings { Param( [string] $srcFile, @@ -394,6 +574,11 @@ function GetWorkflowContentWithChangesFromSettings { ModifyUpdateALGoSystemFiles -yaml $yaml -repoSettings $repoSettings } + # Apply workflow input defaults from settings + if ($repoSettings.Keys -contains 'workflowInputDefaults') { + ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName $workflowName + } + # combine all the yaml file lines into a single string with LF line endings $yaml.content -join "`n" } diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 0d245251b..e92ee89cf 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,40 @@ + +### Set default values for workflow inputs + +A new setting `workflowInputDefaults` allows you to configure default values for workflow_dispatch inputs. This makes it easier to run workflows manually with consistent settings across your team. + +When you add this setting to your AL-Go settings file and run the "Update AL-Go System Files" workflow, the default values will be automatically applied to the workflow YAML files in your repository. +The default values must match the input types (boolean, number, string, or choice) defined in the workflow YAML files. + +Example configuration: + +```json +{ + "workflowInputDefaults": [ + { + "workflow": "Create Release", + "defaults": [ + { "name": "directCommit", "value": true }, + { "name": "useGhTokenWorkflow", "value": true }, + { "name": "updateVersionNumber", "value": "+0.1" } + ] + }, + { + "workflow": "Update AL-Go System Files", + "defaults": [ + { "name": "directCommit", "value": true } + ] + } + ] +} +``` + +Read more at [workflowInputDefaults](https://aka.ms/algosettings#workflowInputDefaults). + ### Issues - Discussion 1911 Add support for reportSuppressedDiagnostics +- Discussion 1952 Set default values on workflow_dispatch input ## v8.0 diff --git a/Scenarios/settings.md b/Scenarios/settings.md index 48ea6ab0c..0d62b30d9 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -133,6 +133,7 @@ The repository settings are only read from the repository settings file (.github | trustedSigning | Structure defining the properties needed for enabling trusted Signing. Please read [this](https://learn.microsoft.com/en-us/azure/trusted-signing/) to setup your Azure Trusted Signing Account and Certificate Profile and then provide these properties in this setting:
**Account** must be the name of your trusted signing account.
**Endpoint** must point to the endpoint of your trusted signing account (ex. https://weu.codesigning.azure.net).
**CertificateProfile** must be the CertificateProfile in your trusted signing account you want to use for signing.
Please note that your Azure_Credentials secret (Microsoft Entra ID App or Managed identity) needs to provide access to your azure subscription and be assigned the `Trusted Signing Certificate Profile Signer` role in the Trusted Signing Account. | | shortLivedArtifactsRetentionDays | Number of days to keep short lived build artifacts (f.ex build artifacts from pull request builds, next minor or next major builds). 0 means use GitHub default. | 1 | | updateALGoSystemFilesEnvironment | If specified, this is the name of the environment, which holds the GhTokenWorkflow secret. With this, you can ensure that the Update AL-Go System Files can be guarded by an approval workflow by setting this on the environment used. You need to run the Update AL-Go System Files (with the GhTokenWorkflow in place globally) this setting to take effect. | | +| workflowInputDefaults | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). The workflow name must match exactly (after removing invalid filename characters).
**defaults** = An array of default values, where each entry has:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice). Type validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowInputDefaults": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | [ ] | ## Workflow specific settings diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index ee498dd2a..c18521339 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -210,6 +210,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $actionName = "CheckForUpdates" $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve Import-Module (Join-Path $scriptRoot "..\Github-Helper.psm1") -DisableNameChecking -Force + Import-Module (Join-Path $scriptRoot "..\.Modules\ReadSettings.psm1") -DisableNameChecking -Force . (Join-Path -Path $scriptRoot -ChildPath "CheckForUpdates.HelperFunctions.ps1") [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] $tmpSrcFile = Join-Path $PSScriptRoot "tempSrcFile.json" @@ -267,4 +268,442 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $modifiedContent."srcSetting" | Should -Be "value1" $modifiedContent."`$schema" | Should -Be "someSchema" } + + It 'ApplyWorkflowInputDefaults applies default values to workflow inputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with workflow_dispatch inputs + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " directCommit:", + " description: Direct Commit?", + " type: boolean", + " default: false", + " useGhTokenWorkflow:", + " description: Use GhTokenWorkflow?", + " type: boolean", + " default: false", + " updateVersionNumber:", + " description: Version number", + " required: false", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - run: echo test" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "directCommit"; "value" = $true }, + @{ "name" = "useGhTokenWorkflow"; "value" = $true }, + @{ "name" = "updateVersionNumber"; "value" = "+0.1" } + ) + } + ) + } + + # Apply the defaults + ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the defaults were applied + $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/useGhTokenWorkflow:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/updateVersionNumber:/default:').content -join '' | Should -Be "default: '+0.1'" + } + + It 'ApplyWorkflowInputDefaults handles workflows without workflow_dispatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML without workflow_dispatch + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " push:", + " branches: [ main ]", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - run: echo test" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "directCommit"; "value" = $true } + ) + } + ) + } + + # Apply the defaults - should not throw + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + } + + It 'ApplyWorkflowInputDefaults handles non-matching workflow names' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " directCommit:", + " description: Direct Commit?", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with workflow input defaults for a different workflow + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Other Workflow" + "defaults" = @( + @{ "name" = "directCommit"; "value" = $true } + ) + } + ) + } + + # Apply the defaults + ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the defaults were NOT applied (original value preserved) + $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: false' + } + + It 'ApplyWorkflowInputDefaults handles inputs without existing default' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with input without default + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " description: My Input", + " required: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "myInput"; "value" = "test-value" } + ) + } + ) + } + + # Apply the defaults + ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the default was added + $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') + $defaultLine | Should -Not -BeNullOrEmpty + $defaultLine.content -join '' | Should -Be "default: 'test-value'" + } + + It 'ApplyWorkflowInputDefaults handles different value types' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " boolInput:", + " type: boolean", + " default: false", + " stringInput:", + " type: string", + " default: ''", + " numberInput:", + " type: number", + " default: 0", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with different value types + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "boolInput"; "value" = $true }, + @{ "name" = "stringInput"; "value" = "test" }, + @{ "name" = "numberInput"; "value" = 42 } + ) + } + ) + } + + # Apply the defaults + ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the defaults were applied with correct types + $yaml.Get('on:/workflow_dispatch:/inputs:/boolInput:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/stringInput:/default:').content -join '' | Should -Be "default: 'test'" + $yaml.Get('on:/workflow_dispatch:/inputs:/numberInput:/default:').content -join '' | Should -Be 'default: 42' + } + + It 'ApplyWorkflowInputDefaults validates boolean type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with boolean input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " boolInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with wrong type (string instead of boolean) + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "boolInput"; "value" = "not a boolean" } + ) + } + ) + } + + # Apply the defaults - should throw validation error + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + Should -Throw "*Expected boolean value*" + } + + It 'ApplyWorkflowInputDefaults validates number type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with number input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " numberInput:", + " type: number", + " default: 0", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with wrong type (string instead of number) + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "numberInput"; "value" = "not a number" } + ) + } + ) + } + + # Apply the defaults - should throw validation error + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + Should -Throw "*Expected number value*" + } + + It 'ApplyWorkflowInputDefaults validates string type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with string input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " stringInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with wrong type (boolean instead of string) + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "stringInput"; "value" = $true } + ) + } + ) + } + + # Apply the defaults - should throw validation error + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + Should -Throw "*Expected string value*" + } + + It 'ApplyWorkflowInputDefaults validates choice type' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " choiceInput:", + " type: choice", + " options:", + " - option1", + " - option2", + " default: option1", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with correct type (string for choice) + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "choiceInput"; "value" = "option2" } + ) + } + ) + } + + # Apply the defaults - should succeed + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/choiceInput:/default:').content -join '' | Should -Be "default: 'option2'" + } + + It 'ApplyWorkflowInputDefaults validates choice value is in available options' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " choiceInput:", + " type: choice", + " options:", + " - option1", + " - option2", + " - option3", + " default: option1", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with invalid choice value + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "choiceInput"; "value" = "invalidOption" } + ) + } + ) + } + + # Apply the defaults - should throw validation error + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + Should -Throw "*not a valid choice*" + } + + It 'ApplyWorkflowInputDefaults handles inputs without type specification' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML without type (defaults to string) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " noTypeInput:", + " description: Input without type", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with string value (should work without warning) + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "noTypeInput"; "value" = "string value" } + ) + } + ) + } + + # Apply the defaults - should succeed + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" + } } + From b68121aa5e25d29d8a563b4a87c766552be38fee Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Fri, 17 Oct 2025 00:27:57 +0200 Subject: [PATCH 02/30] Move workflowInputDefaults setting documentation --- Scenarios/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scenarios/settings.md b/Scenarios/settings.md index 0d62b30d9..c0c991d5d 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -83,6 +83,7 @@ The repository settings are only read from the repository settings file (.github | useGitSubmodules | If your repository is using Git Submodules, you can set the `useGitSubmodules` setting to `"true"` or `"recursive"` in order to use these submodules during build workflows. If `useGitSubmodules` is not set, git submodules are not initialized. If the submodules reside in private repositories, you need to define a `gitSubmodulesToken` secret. Read [this](https://aka.ms/algosecrets#gitSubmodulesToken) for more information. | | commitOptions | If you want more control over how AL-Go creates pull requests or commits changes to the repository you can define `commitOptions`. It is a structure defining how you want AL-Go to handle automated commits or pull requests coming from AL-Go (e.g. for Update AL-Go System Files). The structure contains the following properties:
**messageSuffix** = A string you want to append to the end of commits/pull requests created by AL-Go. This can be useful if you are using the Azure Boards integration (or similar integration) to link commits to work items.
`createPullRequest` : A boolean defining whether AL-Go should create a pull request or attempt to push directly in the branch.
**pullRequestAutoMerge** = A boolean defining whether you want AL-Go pull requests to be set to auto-complete. This will auto-complete the pull requests once all checks are green and all required reviewers have approved.
**pullRequestMergeMethod** = A string defining which merge method to use when auto-merging pull requests. Valid values are "merge" and "squash". Default is "squash".
**pullRequestLabels** = A list of labels to add to the pull request. The labels need to be created in the repository before they can be applied.
If you want different behavior in different AL-Go workflows you can add the `commitOptions` setting to your [workflow-specific settings files](https://github.com/microsoft/AL-Go/blob/main/Scenarios/settings.md#where-are-the-settings-located). | | incrementalBuilds | A structure defining how you want AL-Go to handle incremental builds. When using incremental builds for a build, AL-Go will look for the latest successful CI/CD build, newer than the defined `retentionDays` and only rebuild projects or apps (based on `mode`) which needs to be rebuilt. The structure supports the following properties:
**onPush** = Determines whether incremental builds is enabled in CI/CD triggered by a merge/push event. Default is **false**.
**onPull_Request** = Determines whether incremental builds is enabled in Pull Requests. Default is **true**.
**onSchedule** = Determines whether incremental builds is enabled in CI/CD when running on a schedule. Default is **false**.
**retentionDays** = Number of days a successful build is good (and can be used for incremental builds). Default is **30**.
**mode** = Specifies the mode for incremental builds. Currently, two values are supported. Use **modifiedProjects** when you want to rebuild all apps in all modified projects and depending projects or **modifiedApps** if you want to rebuild modified apps and all apps with dependencies to this app.
**NOTE:** when running incremental builds, it is recommended to also set `workflowConcurrency` for the CI/CD workflow, as defined [here](https://aka.ms/algosettings#workflowConcurrency). | +| workflowInputDefaults | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). The workflow name must match exactly (after removing invalid filename characters).
**defaults** = An array of default values, where each entry has:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice). Type validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowInputDefaults": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | @@ -133,7 +134,6 @@ The repository settings are only read from the repository settings file (.github | trustedSigning | Structure defining the properties needed for enabling trusted Signing. Please read [this](https://learn.microsoft.com/en-us/azure/trusted-signing/) to setup your Azure Trusted Signing Account and Certificate Profile and then provide these properties in this setting:
**Account** must be the name of your trusted signing account.
**Endpoint** must point to the endpoint of your trusted signing account (ex. https://weu.codesigning.azure.net).
**CertificateProfile** must be the CertificateProfile in your trusted signing account you want to use for signing.
Please note that your Azure_Credentials secret (Microsoft Entra ID App or Managed identity) needs to provide access to your azure subscription and be assigned the `Trusted Signing Certificate Profile Signer` role in the Trusted Signing Account. | | shortLivedArtifactsRetentionDays | Number of days to keep short lived build artifacts (f.ex build artifacts from pull request builds, next minor or next major builds). 0 means use GitHub default. | 1 | | updateALGoSystemFilesEnvironment | If specified, this is the name of the environment, which holds the GhTokenWorkflow secret. With this, you can ensure that the Update AL-Go System Files can be guarded by an approval workflow by setting this on the environment used. You need to run the Update AL-Go System Files (with the GhTokenWorkflow in place globally) this setting to take effect. | | -| workflowInputDefaults | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). The workflow name must match exactly (after removing invalid filename characters).
**defaults** = An array of default values, where each entry has:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice). Type validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowInputDefaults": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | [ ] | ## Workflow specific settings From 93b49b790b59a69248cc49eee13dcfcb9c9c35cb Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Fri, 17 Oct 2025 09:49:05 +0200 Subject: [PATCH 03/30] Hide workflow_dispatch input #1952 --- Actions/.Modules/settings.schema.json | 4 + .../CheckForUpdates.HelperFunctions.ps1 | 101 ++++ Actions/CheckForUpdates/yamlclass.ps1 | 12 +- RELEASENOTES.md | 4 +- Scenarios/settings.md | 2 +- Tests/CheckForUpdates.Action.Test.ps1 | 561 ++++++++++++++++++ 6 files changed, 680 insertions(+), 4 deletions(-) diff --git a/Actions/.Modules/settings.schema.json b/Actions/.Modules/settings.schema.json index 34870eb5d..c2eaeb654 100644 --- a/Actions/.Modules/settings.schema.json +++ b/Actions/.Modules/settings.schema.json @@ -714,6 +714,10 @@ }, "value": { "description": "The default value for the workflow input (can be string, boolean, or number)" + }, + "hide": { + "type": "boolean", + "description": "If true, the input is removed from the workflow and the configured value is used directly" } }, "required": ["name", "value"] diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index 26ecd8384..30b22e2fe 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -341,11 +341,37 @@ function ApplyWorkflowInputDefaults { return } + # Helper to convert values to GitHub expression literals + $convertToExpressionLiteral = { + param($value) + + if ($value -is [bool]) { + return $value.ToString().ToLower() + } + + if ($value -is [int] -or $value -is [long] -or $value -is [short] -or $value -is [byte] -or $value -is [sbyte] -or $value -is [uint16] -or $value -is [uint32] -or $value -is [uint64]) { + return $value.ToString() + } + + if ($value -is [double] -or $value -is [single] -or $value -is [decimal]) { + return $value.ToString([System.Globalization.CultureInfo]::InvariantCulture) + } + + $escapedValue = ($value -as [string]) -replace "'", "''" + return "'$escapedValue'" + } + + $inputsToHide = @{} + # Apply defaults to matching inputs foreach ($workflowInputDefault in $matchingDefaults) { foreach ($default in $workflowInputDefault.defaults) { $inputName = $default.name $defaultValue = $default.value + $hideInput = $false + if ($default['hide']) { + $hideInput = [bool]$default.hide + } # Check if this input exists in the workflow $inputSection = $inputs.Get("$($inputName):/") @@ -418,6 +444,15 @@ function ApplyWorkflowInputDefaults { throw $validationError } + if ($hideInput) { + # Store this input to hide it later + $inputsToHide[$inputName] = [pscustomobject]@{ + value = $defaultValue + expression = & $convertToExpressionLiteral $defaultValue + } + continue + } + # Convert the default value to the appropriate YAML format $yamlValue = $defaultValue if ($defaultValue -is [bool]) { @@ -474,11 +509,77 @@ function ApplyWorkflowInputDefaults { } } + # Remove hidden inputs from the inputs section + if ($inputsToHide.Count -gt 0) { + # Collect all inputs to remove with their positions + $inputsToRemove = @() + foreach ($entry in $inputsToHide.GetEnumerator()) { + $name = $entry.Key + $start = 0 + $count = 0 + # Find the input including all its content (type, default, etc.) + # Use the name with colon to match the full input section + if ($inputs.Find("$($name):", [ref] $start, [ref] $count)) { + $inputsToRemove += [pscustomobject]@{ + Name = $name + Start = $start + Count = $count + } + } + else { + OutputWarning "Workflow '$workflowName': Unable to hide input '$name' because it was not found in the workflow file." + } + } + + # Remove inputs in reverse order to maintain correct indices + foreach ($item in $inputsToRemove | Sort-Object -Property Start -Descending) { + $inputs.Remove($item.Start, $item.Count) + } + } + # Update the workflow_dispatch section with modified inputs $workflowDispatch.Replace('inputs:/', $inputs.content) # Update the on: section with modified workflow_dispatch $yaml.Replace('on:/workflow_dispatch:/', $workflowDispatch.content) + + # Replace all references to hidden inputs with their literal values + if ($inputsToHide.Count -gt 0) { + foreach ($entry in $inputsToHide.GetEnumerator()) { + $inputName = $entry.Key + $expressionValue = $entry.Value.expression + $rawValue = $entry.Value.value + + # Replace references in expressions: ${{ github.event.inputs.name }} and ${{ inputs.name }} + # These use the expression literal format (true/false for boolean, unquoted number, 'quoted' string) + $yaml.ReplaceAll("`${{ github.event.inputs.$inputName }}", $expressionValue) + $yaml.ReplaceAll("`${{ inputs.$inputName }}", $expressionValue) + + # Replace references in if conditions: github.event.inputs.name and inputs.name (without ${{ }}) + # In if conditions, bare references are always treated as strings, so we need to use string literal format + # Convert the value to a string literal (always quoted) for comparisons + # For booleans, use lowercase to match GitHub Actions convention (case-sensitive) + if ($rawValue -is [bool]) { + $stringValue = $rawValue.ToString().ToLower() + } + else { + $stringValue = $rawValue.ToString().Replace("'", "''") + } + $stringLiteral = "'$stringValue'" + + # Replace github.event.inputs.NAME (safe because it's a specific prefix) + $yaml.ReplaceAll("github.event.inputs.$inputName", $stringLiteral) + + # Replace inputs.NAME but be careful not to match patterns like: + # - needs.inputs.outputs.NAME (where a job is named "inputs") + # - steps.CreateInputs.outputs.NAME (where "inputs" is part of a word) + # Use negative lookbehind (?` or `inputs.` in the workflow file are replaced with the configured value when the "Update AL-Go System Files" workflow runs. Example configuration: @@ -15,7 +15,7 @@ Example configuration: "workflow": "Create Release", "defaults": [ { "name": "directCommit", "value": true }, - { "name": "useGhTokenWorkflow", "value": true }, + { "name": "useGhTokenWorkflow", "value": true, "hide": true }, { "name": "updateVersionNumber", "value": "+0.1" } ] }, diff --git a/Scenarios/settings.md b/Scenarios/settings.md index c0c991d5d..8bb9e5ef3 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -83,7 +83,7 @@ The repository settings are only read from the repository settings file (.github | useGitSubmodules | If your repository is using Git Submodules, you can set the `useGitSubmodules` setting to `"true"` or `"recursive"` in order to use these submodules during build workflows. If `useGitSubmodules` is not set, git submodules are not initialized. If the submodules reside in private repositories, you need to define a `gitSubmodulesToken` secret. Read [this](https://aka.ms/algosecrets#gitSubmodulesToken) for more information. | | commitOptions | If you want more control over how AL-Go creates pull requests or commits changes to the repository you can define `commitOptions`. It is a structure defining how you want AL-Go to handle automated commits or pull requests coming from AL-Go (e.g. for Update AL-Go System Files). The structure contains the following properties:
**messageSuffix** = A string you want to append to the end of commits/pull requests created by AL-Go. This can be useful if you are using the Azure Boards integration (or similar integration) to link commits to work items.
`createPullRequest` : A boolean defining whether AL-Go should create a pull request or attempt to push directly in the branch.
**pullRequestAutoMerge** = A boolean defining whether you want AL-Go pull requests to be set to auto-complete. This will auto-complete the pull requests once all checks are green and all required reviewers have approved.
**pullRequestMergeMethod** = A string defining which merge method to use when auto-merging pull requests. Valid values are "merge" and "squash". Default is "squash".
**pullRequestLabels** = A list of labels to add to the pull request. The labels need to be created in the repository before they can be applied.
If you want different behavior in different AL-Go workflows you can add the `commitOptions` setting to your [workflow-specific settings files](https://github.com/microsoft/AL-Go/blob/main/Scenarios/settings.md#where-are-the-settings-located). | | incrementalBuilds | A structure defining how you want AL-Go to handle incremental builds. When using incremental builds for a build, AL-Go will look for the latest successful CI/CD build, newer than the defined `retentionDays` and only rebuild projects or apps (based on `mode`) which needs to be rebuilt. The structure supports the following properties:
**onPush** = Determines whether incremental builds is enabled in CI/CD triggered by a merge/push event. Default is **false**.
**onPull_Request** = Determines whether incremental builds is enabled in Pull Requests. Default is **true**.
**onSchedule** = Determines whether incremental builds is enabled in CI/CD when running on a schedule. Default is **false**.
**retentionDays** = Number of days a successful build is good (and can be used for incremental builds). Default is **30**.
**mode** = Specifies the mode for incremental builds. Currently, two values are supported. Use **modifiedProjects** when you want to rebuild all apps in all modified projects and depending projects or **modifiedApps** if you want to rebuild modified apps and all apps with dependencies to this app.
**NOTE:** when running incremental builds, it is recommended to also set `workflowConcurrency` for the CI/CD workflow, as defined [here](https://aka.ms/algosettings#workflowConcurrency). | -| workflowInputDefaults | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). The workflow name must match exactly (after removing invalid filename characters).
**defaults** = An array of default values, where each entry has:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice). Type validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowInputDefaults": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | +| workflowInputDefaults | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). The workflow name must match exactly (after removing invalid filename characters).
**defaults** = An array of default values, where each entry has:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
  **hide** (optional) = Set to `true` to remove the input from the workflow UI and use the configured value automatically. When hiding an input, all usages of `github.event.inputs.` or `inputs.` are replaced with the configured value when the "Update AL-Go System Files" workflow runs.
The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice). Type validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
Example:
`"workflowInputDefaults": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true, "hide": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index c18521339..fcc733a06 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -203,6 +203,105 @@ Describe('YamlClass Tests') { $srcContent | Should -Be ($resultContent -join "`n") } + + It 'Test YamlClass ReplaceAll with regex - negative lookbehind and word boundaries' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $yamlContent = @( + "needs: [ inputs ]", + "myOutput: `${{ needs.inputs.outputs.output }}", + "myInput: `${{ inputs.output }}", + "if: inputs.runTests == 'true'", + "step: CreateInputs" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Replace "inputs.output" where "inputs" is NOT preceded by a dot (negative lookbehind) + # and "output" is a complete word (word boundary) + # This matches the actual pattern used in the hide feature: (?> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ Inputs ]", + " steps:", + " - name: Deploy", + " env:", + " branch: `${{ needs.Inputs.outputs.branch }}", + " run: echo Deploying to `$branch" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag for the branch input + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "branch"; "value" = "production"; "hide" = $true } + ) + } + ) + } + + # Apply the defaults + ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('branch:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.Inputs.outputs.branch is NOT replaced + # This is a job output reference, not a workflow input reference + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.Inputs\.outputs\.branch" + + # Verify that direct input references would be replaced if they existed + # (but they don't exist in this workflow, so we just verify the job reference remains) + $content | Should -Not -Match "github\.event\.inputs\.branch" + $content | Should -Not -Match "`\$\{\{ inputs\.branch \}\}" + } + + It 'ApplyWorkflowInputDefaults does not replace parts of job output references with input names like output' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Edge case: input named "output" should not replace "inputs.outputs.output" or "outputs.output" + # Using lowercase "inputs" as job name to test the problematic case + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " output:", + " type: string", + " default: 'default'", + "jobs:", + " inputs:", + " runs-on: ubuntu-latest", + " outputs:", + " output: `${{ steps.CreateInputs.outputs.output }}", + " steps:", + " - name: Create inputs", + " id: CreateInputs", + " run: echo 'output=test' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ inputs ]", + " steps:", + " - name: Deploy", + " env:", + " myOutput: `${{ needs.inputs.outputs.output }}", + " run: echo Using `$myOutput" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag for the "output" input + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "output"; "value" = "hidden"; "hide" = $true } + ) + } + ) + } + + # Apply the defaults + ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('output:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.inputs.outputs.output is NOT replaced + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.inputs\.outputs\.output" + $content | Should -Match "steps\.CreateInputs\.outputs\.output" + + # Verify that direct input references would be replaced if they existed + $content | Should -Not -Match "github\.event\.inputs\.output" + $content | Should -Not -Match "`\$\{\{ inputs\.output \}\}" + } + + It 'ApplyWorkflowInputDefaults does not replace job output references when input is named outputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Edge case: input named "outputs" should not replace "inputs.outputs" in job output contexts + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " outputs:", + " type: string", + " default: 'default'", + "jobs:", + " Initialization:", + " runs-on: ubuntu-latest", + " outputs:", + " telemetryScopeJson: `${{ steps.init.outputs.telemetryScopeJson }}", + " steps:", + " - name: Initialize", + " id: init", + " run: echo 'telemetryScopeJson={}' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ Initialization ]", + " steps:", + " - name: Deploy", + " env:", + " telemetryScope: `${{ needs.Initialization.outputs.telemetryScopeJson }}", + " run: echo Using `$telemetryScope", + " - name: Use input", + " run: echo `${{ inputs.outputs }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag for the "outputs" input + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "outputs"; "value" = "hidden-value"; "hide" = $true } + ) + } + ) + } + + # Apply the defaults + ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.Initialization.outputs.telemetryScopeJson is NOT replaced + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.Initialization\.outputs\.telemetryScopeJson" + $content | Should -Match "steps\.init\.outputs\.telemetryScopeJson" + + # Verify that the actual input reference WAS replaced + $content | Should -Match "echo 'hidden-value'" + $content | Should -Not -Match "github\.event\.inputs\.outputs" + $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" + } + + It 'ApplyWorkflowInputDefaults does not replace job outputs when job is named inputs and input is named outputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # This is the real edge case: job named "inputs" (lowercase) with outputs, and an input also named "outputs" + # needs.inputs.outputs.something should NOT be replaced (it's a job output reference) + # but ${{ inputs.outputs }} should be replaced (it's a workflow input reference) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " outputs:", + " type: string", + " default: 'default'", + "jobs:", + " inputs:", + " runs-on: ubuntu-latest", + " outputs:", + " myValue: `${{ steps.init.outputs.myValue }}", + " steps:", + " - name: Initialize", + " id: init", + " run: echo 'myValue=test' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ inputs ]", + " steps:", + " - name: Use job output", + " env:", + " value: `${{ needs.inputs.outputs.myValue }}", + " run: echo Job output is `$value", + " - name: Use input", + " run: echo Input is `${{ inputs.outputs }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag for the "outputs" input + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "outputs"; "value" = "hidden-input-value"; "hide" = $true } + ) + } + ) + } + + # Apply the defaults + ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the hidden input was removed + $inputsSection = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputsSection.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.inputs.outputs.myValue is NOT replaced (job output reference) + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.inputs\.outputs\.myValue" + + # Verify that steps.init.outputs.myValue is NOT replaced (step output reference) + $content | Should -Match "steps\.init\.outputs\.myValue" + + # Verify that inputs.outputs WAS replaced with the hidden value + $content | Should -Match "echo Input is 'hidden-input-value'" + $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" + } } + + + From 993474938d2b5e6f14ab6643793f703bd77d2da3 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Fri, 17 Oct 2025 10:18:47 +0200 Subject: [PATCH 04/30] Fixes Copilot comments --- .../CheckForUpdates.HelperFunctions.ps1 | 5 ++- Scenarios/settings.md | 8 +++- Tests/CheckForUpdates.Action.Test.ps1 | 38 +++++++++++++++++++ 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index 26ecd8384..bb99a353e 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -424,8 +424,9 @@ function ApplyWorkflowInputDefaults { $yamlValue = $defaultValue.ToString().ToLower() } elseif ($defaultValue -is [string]) { - # Quote strings - $yamlValue = "'$defaultValue'" + # Quote strings and escape single quotes per YAML spec + $escapedValue = $defaultValue.Replace("'", "''") + $yamlValue = "'$escapedValue'" } # Find and replace the default: line in the input section diff --git a/Scenarios/settings.md b/Scenarios/settings.md index c0c991d5d..c8c28bc1f 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -83,7 +83,7 @@ The repository settings are only read from the repository settings file (.github | useGitSubmodules | If your repository is using Git Submodules, you can set the `useGitSubmodules` setting to `"true"` or `"recursive"` in order to use these submodules during build workflows. If `useGitSubmodules` is not set, git submodules are not initialized. If the submodules reside in private repositories, you need to define a `gitSubmodulesToken` secret. Read [this](https://aka.ms/algosecrets#gitSubmodulesToken) for more information. | | commitOptions | If you want more control over how AL-Go creates pull requests or commits changes to the repository you can define `commitOptions`. It is a structure defining how you want AL-Go to handle automated commits or pull requests coming from AL-Go (e.g. for Update AL-Go System Files). The structure contains the following properties:
**messageSuffix** = A string you want to append to the end of commits/pull requests created by AL-Go. This can be useful if you are using the Azure Boards integration (or similar integration) to link commits to work items.
`createPullRequest` : A boolean defining whether AL-Go should create a pull request or attempt to push directly in the branch.
**pullRequestAutoMerge** = A boolean defining whether you want AL-Go pull requests to be set to auto-complete. This will auto-complete the pull requests once all checks are green and all required reviewers have approved.
**pullRequestMergeMethod** = A string defining which merge method to use when auto-merging pull requests. Valid values are "merge" and "squash". Default is "squash".
**pullRequestLabels** = A list of labels to add to the pull request. The labels need to be created in the repository before they can be applied.
If you want different behavior in different AL-Go workflows you can add the `commitOptions` setting to your [workflow-specific settings files](https://github.com/microsoft/AL-Go/blob/main/Scenarios/settings.md#where-are-the-settings-located). | | incrementalBuilds | A structure defining how you want AL-Go to handle incremental builds. When using incremental builds for a build, AL-Go will look for the latest successful CI/CD build, newer than the defined `retentionDays` and only rebuild projects or apps (based on `mode`) which needs to be rebuilt. The structure supports the following properties:
**onPush** = Determines whether incremental builds is enabled in CI/CD triggered by a merge/push event. Default is **false**.
**onPull_Request** = Determines whether incremental builds is enabled in Pull Requests. Default is **true**.
**onSchedule** = Determines whether incremental builds is enabled in CI/CD when running on a schedule. Default is **false**.
**retentionDays** = Number of days a successful build is good (and can be used for incremental builds). Default is **30**.
**mode** = Specifies the mode for incremental builds. Currently, two values are supported. Use **modifiedProjects** when you want to rebuild all apps in all modified projects and depending projects or **modifiedApps** if you want to rebuild modified apps and all apps with dependencies to this app.
**NOTE:** when running incremental builds, it is recommended to also set `workflowConcurrency` for the CI/CD workflow, as defined [here](https://aka.ms/algosettings#workflowConcurrency). | -| workflowInputDefaults | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). The workflow name must match exactly (after removing invalid filename characters).
**defaults** = An array of default values, where each entry has:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice). Type validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowInputDefaults": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | +| workflowInputDefaults | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). Workflow names are matched using [sanitized names](#workflow-name-sanitization).
**defaults** = An array of default values, where each entry has:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  â€¢ The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  â€¢ For choice inputs, the value must be one of the options declared in the workflow
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowInputDefaults": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | @@ -182,6 +182,12 @@ to your [project settings file](#where-are-the-settings-located) will ensure tha - **workflows** settings will be applied to workflows matching the patterns - **users** settings will be applied for users matching the patterns + + +### Workflow Name Sanitization + +When matching workflow names (for conditional settings and workflowInputDefaults), AL-Go sanitizes the actual workflow name before comparison. Sanitization removes invalid filename characters such as leading spaces, quotes, colons, slashes, and other special characters. For example, a workflow named `" CI/CD"` would be sanitized to `"CICD"` for matching purposes. + You could imagine that you could have and organizational settings variable containing: ```json diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index c18521339..daeb8dd83 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -705,5 +705,43 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw $yaml.Get('on:/workflow_dispatch:/inputs:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" } + + It 'ApplyWorkflowInputDefaults escapes single quotes in string values' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with string input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " nameInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with string value containing single quote + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "nameInput"; "value" = "O'Brien" } + ) + } + ) + } + + # Apply the defaults + ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify single quote is escaped per YAML spec (doubled) + $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" + } } From aec01d350bb0deea0ab68aa27eb64422789a0f77 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Fri, 17 Oct 2025 12:33:15 +0200 Subject: [PATCH 05/30] Data type checking fix for PowerShell 5 --- Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index ffbc16a74..91b4bd166 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -349,7 +349,7 @@ function ApplyWorkflowInputDefaults { return $value.ToString().ToLower() } - if ($value -is [int] -or $value -is [long] -or $value -is [short] -or $value -is [byte] -or $value -is [sbyte] -or $value -is [uint16] -or $value -is [uint32] -or $value -is [uint64]) { + if ($value -is [int] -or $value -is [long] -or $value -is [System.Int16] -or $value -is [byte] -or $value -is [System.SByte] -or $value -is [System.UInt16] -or $value -is [System.UInt32] -or $value -is [System.UInt64]) { return $value.ToString() } From 018b008574c911351fb919480be179a4fc632d12 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Fri, 17 Oct 2025 13:25:52 +0200 Subject: [PATCH 06/30] Enhance ApplyWorkflowInputDefaults to validate choice input with case-sensitive matching and update related test cases --- .../CheckForUpdates.HelperFunctions.ps1 | 6 +- Tests/CheckForUpdates.Action.Test.ps1 | 71 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index bb99a353e..cc3137817 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -381,7 +381,7 @@ function ApplyWorkflowInputDefaults { } } 'choice' { - # Choice inputs accept strings and must match one of the available options + # Choice inputs accept strings and must match one of the available options (case-sensitive) if ($defaultValue -isnot [string]) { $validationError = "Workflow '$workflowName', input '$inputName': Expected string value for choice input, but got $($defaultValue.GetType().Name)." } @@ -399,8 +399,8 @@ function ApplyWorkflowInputDefaults { } } - if ($availableOptions.Count -gt 0 -and $availableOptions -notcontains $defaultValue) { - $validationError = "Workflow '$workflowName', input '$inputName': Value '$defaultValue' is not a valid choice. Available options: $($availableOptions -join ', ')." + if ($availableOptions.Count -gt 0 -and $availableOptions -cnotcontains $defaultValue) { + $validationError = "Workflow '$workflowName', input '$inputName': Value '$defaultValue' is not a valid choice (case-sensitive match required). Available options: $($availableOptions -join ', ')." } } } diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index daeb8dd83..98ce6cf25 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -670,6 +670,77 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { Should -Throw "*not a valid choice*" } + It 'ApplyWorkflowInputDefaults validates choice value with case-sensitive matching' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input using mixed case options + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " releaseTypeInput:", + " type: choice", + " options:", + " - Release", + " - Prerelease", + " - Draft", + " default: Release", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Test 1: Exact case match should succeed + $repoSettings = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "releaseTypeInput"; "value" = "Prerelease" } + ) + } + ) + } + + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/releaseTypeInput:/default:').content -join '' | Should -Be "default: 'Prerelease'" + + # Test 2: Wrong case should fail with case-sensitive error message + $yaml2 = [Yaml]::new($yamlContent) + $repoSettings2 = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "releaseTypeInput"; "value" = "prerelease" } + ) + } + ) + } + + { ApplyWorkflowInputDefaults -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | + Should -Throw "*case-sensitive match required*" + + # Test 3: Uppercase version should also fail + $yaml3 = [Yaml]::new($yamlContent) + $repoSettings3 = @{ + "workflowInputDefaults" = @( + @{ + "workflow" = "Test Workflow" + "defaults" = @( + @{ "name" = "releaseTypeInput"; "value" = "PRERELEASE" } + ) + } + ) + } + + { ApplyWorkflowInputDefaults -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | + Should -Throw "*case-sensitive match required*" + } + It 'ApplyWorkflowInputDefaults handles inputs without type specification' { . (Join-Path $scriptRoot "yamlclass.ps1") From 28f569712e94a36d72cc5bbef9eefd253f0431c6 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Fri, 17 Oct 2025 14:23:19 +0200 Subject: [PATCH 07/30] Remove unnecessary blank lines in ApplyWorkflowInputDefaults function and related tests --- .../CheckForUpdates.HelperFunctions.ps1 | 20 +- Tests/CheckForUpdates.Action.Test.ps1 | 244 +++++++++--------- 2 files changed, 132 insertions(+), 132 deletions(-) diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index 8074c9c9a..11f53429f 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -424,7 +424,7 @@ function ApplyWorkflowInputDefaults { $availableOptions += $matches[1].Trim() } } - + if ($availableOptions.Count -gt 0 -and $availableOptions -cnotcontains $defaultValue) { $validationError = "Workflow '$workflowName', input '$inputName': Value '$defaultValue' is not a valid choice (case-sensitive match required). Available options: $($availableOptions -join ', ')." } @@ -463,7 +463,7 @@ function ApplyWorkflowInputDefaults { $escapedValue = $defaultValue.Replace("'", "''") $yamlValue = "'$escapedValue'" } - + # Find and replace the default: line in the input section $start = 0 $count = 0 @@ -481,7 +481,7 @@ function ApplyWorkflowInputDefaults { $requiredCount = 0 $descLine = 0 $descCount = 0 - + if ($inputSection.Find('type:', [ref] $typeLine, [ref] $typeCount)) { $insertAfter = $typeLine + $typeCount } @@ -495,15 +495,15 @@ function ApplyWorkflowInputDefaults { $insertAfter = $descLine + $descCount } } - + if ($insertAfter -eq -1) { # No other properties, insert at position 1 (after the input name) $insertAfter = 1 } - + $inputSection.Insert($insertAfter, "default: $yamlValue") } - + # Update the inputs section with the modified input $inputs.Replace("$($inputName):/", $inputSection.content) } @@ -540,7 +540,7 @@ function ApplyWorkflowInputDefaults { # Update the workflow_dispatch section with modified inputs $workflowDispatch.Replace('inputs:/', $inputs.content) - + # Update the on: section with modified workflow_dispatch $yaml.Replace('on:/workflow_dispatch:/', $workflowDispatch.content) @@ -555,7 +555,7 @@ function ApplyWorkflowInputDefaults { # These use the expression literal format (true/false for boolean, unquoted number, 'quoted' string) $yaml.ReplaceAll("`${{ github.event.inputs.$inputName }}", $expressionValue) $yaml.ReplaceAll("`${{ inputs.$inputName }}", $expressionValue) - + # Replace references in if conditions: github.event.inputs.name and inputs.name (without ${{ }}) # In if conditions, bare references are always treated as strings, so we need to use string literal format # Convert the value to a string literal (always quoted) for comparisons @@ -567,10 +567,10 @@ function ApplyWorkflowInputDefaults { $stringValue = $rawValue.ToString().Replace("'", "''") } $stringLiteral = "'$stringValue'" - + # Replace github.event.inputs.NAME (safe because it's a specific prefix) $yaml.ReplaceAll("github.event.inputs.$inputName", $stringLiteral) - + # Replace inputs.NAME but be careful not to match patterns like: # - needs.inputs.outputs.NAME (where a job is named "inputs") # - steps.CreateInputs.outputs.NAME (where "inputs" is part of a word) diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index a21d89c15..3b2375b26 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -1,4 +1,4 @@ -Get-Module TestActionsHelper | Remove-Module -Force +Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') Import-Module (Join-Path $PSScriptRoot "..\Actions\TelemetryHelper.psm1") $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 @@ -370,7 +370,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults applies default values to workflow inputs' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with workflow_dispatch inputs $yamlContent = @( "name: 'Test Workflow'", @@ -395,9 +395,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " steps:", " - run: echo test" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with workflow input defaults $repoSettings = @{ "workflowInputDefaults" = @( @@ -411,10 +411,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the defaults were applied $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: true' $yaml.Get('on:/workflow_dispatch:/inputs:/useGhTokenWorkflow:/default:').content -join '' | Should -Be 'default: true' @@ -423,7 +423,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults handles workflows without workflow_dispatch' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML without workflow_dispatch $yamlContent = @( "name: 'Test Workflow'", @@ -436,9 +436,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " steps:", " - run: echo test" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with workflow input defaults $repoSettings = @{ "workflowInputDefaults" = @( @@ -450,14 +450,14 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should not throw { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw } It 'ApplyWorkflowInputDefaults handles non-matching workflow names' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML $yamlContent = @( "name: 'Test Workflow'", @@ -472,9 +472,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with workflow input defaults for a different workflow $repoSettings = @{ "workflowInputDefaults" = @( @@ -486,17 +486,17 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the defaults were NOT applied (original value preserved) $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: false' } It 'ApplyWorkflowInputDefaults handles inputs without existing default' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with input without default $yamlContent = @( "name: 'Test Workflow'", @@ -510,9 +510,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with workflow input defaults $repoSettings = @{ "workflowInputDefaults" = @( @@ -524,10 +524,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the default was added $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') $defaultLine | Should -Not -BeNullOrEmpty @@ -536,7 +536,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults handles different value types' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML $yamlContent = @( "name: 'Test Workflow'", @@ -556,9 +556,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with different value types $repoSettings = @{ "workflowInputDefaults" = @( @@ -572,10 +572,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the defaults were applied with correct types $yaml.Get('on:/workflow_dispatch:/inputs:/boolInput:/default:').content -join '' | Should -Be 'default: true' $yaml.Get('on:/workflow_dispatch:/inputs:/stringInput:/default:').content -join '' | Should -Be "default: 'test'" @@ -584,7 +584,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults validates boolean type mismatch' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with boolean input $yamlContent = @( "name: 'Test Workflow'", @@ -598,9 +598,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with wrong type (string instead of boolean) $repoSettings = @{ "workflowInputDefaults" = @( @@ -612,15 +612,15 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected boolean value*" } It 'ApplyWorkflowInputDefaults validates number type mismatch' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with number input $yamlContent = @( "name: 'Test Workflow'", @@ -634,9 +634,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with wrong type (string instead of number) $repoSettings = @{ "workflowInputDefaults" = @( @@ -648,15 +648,15 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected number value*" } It 'ApplyWorkflowInputDefaults validates string type mismatch' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with string input $yamlContent = @( "name: 'Test Workflow'", @@ -670,9 +670,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with wrong type (boolean instead of string) $repoSettings = @{ "workflowInputDefaults" = @( @@ -684,15 +684,15 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected string value*" } It 'ApplyWorkflowInputDefaults validates choice type' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with choice input $yamlContent = @( "name: 'Test Workflow'", @@ -709,9 +709,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with correct type (string for choice) $repoSettings = @{ "workflowInputDefaults" = @( @@ -723,7 +723,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should succeed { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw $yaml.Get('on:/workflow_dispatch:/inputs:/choiceInput:/default:').content -join '' | Should -Be "default: 'option2'" @@ -731,7 +731,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults validates choice value is in available options' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with choice input $yamlContent = @( "name: 'Test Workflow'", @@ -749,9 +749,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with invalid choice value $repoSettings = @{ "workflowInputDefaults" = @( @@ -763,15 +763,15 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*not a valid choice*" } It 'ApplyWorkflowInputDefaults validates choice value with case-sensitive matching' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with choice input using mixed case options $yamlContent = @( "name: 'Test Workflow'", @@ -789,9 +789,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Test 1: Exact case match should succeed $repoSettings = @{ "workflowInputDefaults" = @( @@ -803,10 +803,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw $yaml.Get('on:/workflow_dispatch:/inputs:/releaseTypeInput:/default:').content -join '' | Should -Be "default: 'Prerelease'" - + # Test 2: Wrong case should fail with case-sensitive error message $yaml2 = [Yaml]::new($yamlContent) $repoSettings2 = @{ @@ -819,10 +819,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - - { ApplyWorkflowInputDefaults -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | + + { ApplyWorkflowInputDefaults -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" - + # Test 3: Uppercase version should also fail $yaml3 = [Yaml]::new($yamlContent) $repoSettings3 = @{ @@ -835,14 +835,14 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - - { ApplyWorkflowInputDefaults -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | + + { ApplyWorkflowInputDefaults -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" } It 'ApplyWorkflowInputDefaults handles inputs without type specification' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML without type (defaults to string) $yamlContent = @( "name: 'Test Workflow'", @@ -856,9 +856,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with string value (should work without warning) $repoSettings = @{ "workflowInputDefaults" = @( @@ -870,7 +870,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should succeed { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw $yaml.Get('on:/workflow_dispatch:/inputs:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" @@ -878,7 +878,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults escapes single quotes in string values' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with string input $yamlContent = @( "name: 'Test Workflow'", @@ -892,9 +892,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with string value containing single quote $repoSettings = @{ "workflowInputDefaults" = @( @@ -906,17 +906,17 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify single quote is escaped per YAML spec (doubled) $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" } It 'ApplyWorkflowInputDefaults hides boolean inputs when hide is true' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with boolean input $yamlContent = @( "name: 'Test Workflow'", @@ -936,9 +936,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " - name: Use input", " run: echo `${{ github.event.inputs.hiddenInput }}" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with hide flag $repoSettings = @{ "workflowInputDefaults" = @( @@ -951,18 +951,18 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the hidden input was removed from inputs $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') $inputs.Find('hiddenInput:', [ref] $null, [ref] $null) | Should -Be $false - + # Verify the visible input still exists with updated default $inputs.Find('visibleInput:', [ref] $null, [ref] $null) | Should -Be $true $yaml.Get('on:/workflow_dispatch:/inputs:/visibleInput:/default:').content -join '' | Should -Be "default: 'test'" - + # Verify the reference was replaced with literal value $yaml.content -join "`n" | Should -Match "echo true" $yaml.content -join "`n" | Should -Not -Match "github\.event\.inputs\.hiddenInput" @@ -970,7 +970,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults hides string inputs and replaces references correctly' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with string input $yamlContent = @( "name: 'Test Workflow'", @@ -989,9 +989,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " - name: Use version again", " run: echo `${{ github.event.inputs.versionInput }}" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with hide flag $repoSettings = @{ "workflowInputDefaults" = @( @@ -1003,14 +1003,14 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') $inputs.Find('versionInput:', [ref] $null, [ref] $null) | Should -Be $false - + # Verify both references were replaced with quoted string $content = $yaml.content -join "`n" $content | Should -Match "echo '\+0\.1'" @@ -1020,7 +1020,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults hides number inputs and replaces references correctly' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with number input $yamlContent = @( "name: 'Test Workflow'", @@ -1037,9 +1037,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " - name: Use count", " run: echo `${{ inputs.countInput }}" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with hide flag $repoSettings = @{ "workflowInputDefaults" = @( @@ -1051,14 +1051,14 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') $inputs.Find('countInput:', [ref] $null, [ref] $null) | Should -Be $false - + # Verify the reference was replaced with number (unquoted) $yaml.content -join "`n" | Should -Match "echo 10" $yaml.content -join "`n" | Should -Not -Match "inputs\.countInput" @@ -1066,7 +1066,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults replaces hidden input references in if conditions' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with input used in if condition (without ${{ }}) $yamlContent = @( "name: 'Test Workflow'", @@ -1084,9 +1084,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " - name: Run", " run: echo Running" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with hide flag $repoSettings = @{ "workflowInputDefaults" = @( @@ -1098,14 +1098,14 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') $inputs.Find('runTests:', [ref] $null, [ref] $null) | Should -Be $false - + # Verify the reference in if condition was replaced with string literal # Bare references in if conditions are always treated as strings in GitHub Actions # GitHub Actions comparisons are case-sensitive, so we use lowercase 'true' @@ -1115,7 +1115,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults does not replace job output references' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with needs.JobName.outputs.inputName pattern # This should NOT be replaced even if an input with the same name exists and is hidden $yamlContent = @( @@ -1144,9 +1144,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " branch: `${{ needs.Inputs.outputs.branch }}", " run: echo Deploying to `$branch" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with hide flag for the branch input $repoSettings = @{ "workflowInputDefaults" = @( @@ -1158,19 +1158,19 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') $inputs.Find('branch:', [ref] $null, [ref] $null) | Should -Be $false - + # Verify that needs.Inputs.outputs.branch is NOT replaced # This is a job output reference, not a workflow input reference $content = $yaml.content -join "`n" $content | Should -Match "needs\.Inputs\.outputs\.branch" - + # Verify that direct input references would be replaced if they existed # (but they don't exist in this workflow, so we just verify the job reference remains) $content | Should -Not -Match "github\.event\.inputs\.branch" @@ -1179,7 +1179,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults does not replace parts of job output references with input names like output' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Edge case: input named "output" should not replace "inputs.outputs.output" or "outputs.output" # Using lowercase "inputs" as job name to test the problematic case $yamlContent = @( @@ -1208,9 +1208,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " myOutput: `${{ needs.inputs.outputs.output }}", " run: echo Using `$myOutput" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with hide flag for the "output" input $repoSettings = @{ "workflowInputDefaults" = @( @@ -1222,19 +1222,19 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') $inputs.Find('output:', [ref] $null, [ref] $null) | Should -Be $false - + # Verify that needs.inputs.outputs.output is NOT replaced $content = $yaml.content -join "`n" $content | Should -Match "needs\.inputs\.outputs\.output" $content | Should -Match "steps\.CreateInputs\.outputs\.output" - + # Verify that direct input references would be replaced if they existed $content | Should -Not -Match "github\.event\.inputs\.output" $content | Should -Not -Match "`\$\{\{ inputs\.output \}\}" @@ -1242,7 +1242,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults does not replace job output references when input is named outputs' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Edge case: input named "outputs" should not replace "inputs.outputs" in job output contexts $yamlContent = @( "name: 'Test Workflow'", @@ -1272,9 +1272,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " - name: Use input", " run: echo `${{ inputs.outputs }}" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with hide flag for the "outputs" input $repoSettings = @{ "workflowInputDefaults" = @( @@ -1286,19 +1286,19 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') $inputs.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false - + # Verify that needs.Initialization.outputs.telemetryScopeJson is NOT replaced $content = $yaml.content -join "`n" $content | Should -Match "needs\.Initialization\.outputs\.telemetryScopeJson" $content | Should -Match "steps\.init\.outputs\.telemetryScopeJson" - + # Verify that the actual input reference WAS replaced $content | Should -Match "echo 'hidden-value'" $content | Should -Not -Match "github\.event\.inputs\.outputs" @@ -1307,7 +1307,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults does not replace job outputs when job is named inputs and input is named outputs' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # This is the real edge case: job named "inputs" (lowercase) with outputs, and an input also named "outputs" # needs.inputs.outputs.something should NOT be replaced (it's a job output reference) # but ${{ inputs.outputs }} should be replaced (it's a workflow input reference) @@ -1339,9 +1339,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " - name: Use input", " run: echo Input is `${{ inputs.outputs }}" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with hide flag for the "outputs" input $repoSettings = @{ "workflowInputDefaults" = @( @@ -1353,21 +1353,21 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the hidden input was removed $inputsSection = $yaml.Get('on:/workflow_dispatch:/inputs:/') $inputsSection.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false - + # Verify that needs.inputs.outputs.myValue is NOT replaced (job output reference) $content = $yaml.content -join "`n" $content | Should -Match "needs\.inputs\.outputs\.myValue" - + # Verify that steps.init.outputs.myValue is NOT replaced (step output reference) $content | Should -Match "steps\.init\.outputs\.myValue" - + # Verify that inputs.outputs WAS replaced with the hidden value $content | Should -Match "echo Input is 'hidden-input-value'" $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" From de397ec523756feef321c4accf75453e8129fc3b Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Fri, 17 Oct 2025 14:37:20 +0200 Subject: [PATCH 08/30] Remove unnecessary blank lines in ApplyWorkflowInputDefaults function and related tests --- .../CheckForUpdates.HelperFunctions.ps1 | 14 +- Tests/CheckForUpdates.Action.Test.ps1 | 136 +++++++++--------- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index cc3137817..307e5e85b 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -398,7 +398,7 @@ function ApplyWorkflowInputDefaults { $availableOptions += $matches[1].Trim() } } - + if ($availableOptions.Count -gt 0 -and $availableOptions -cnotcontains $defaultValue) { $validationError = "Workflow '$workflowName', input '$inputName': Value '$defaultValue' is not a valid choice (case-sensitive match required). Available options: $($availableOptions -join ', ')." } @@ -428,7 +428,7 @@ function ApplyWorkflowInputDefaults { $escapedValue = $defaultValue.Replace("'", "''") $yamlValue = "'$escapedValue'" } - + # Find and replace the default: line in the input section $start = 0 $count = 0 @@ -446,7 +446,7 @@ function ApplyWorkflowInputDefaults { $requiredCount = 0 $descLine = 0 $descCount = 0 - + if ($inputSection.Find('type:', [ref] $typeLine, [ref] $typeCount)) { $insertAfter = $typeLine + $typeCount } @@ -460,15 +460,15 @@ function ApplyWorkflowInputDefaults { $insertAfter = $descLine + $descCount } } - + if ($insertAfter -eq -1) { # No other properties, insert at position 1 (after the input name) $insertAfter = 1 } - + $inputSection.Insert($insertAfter, "default: $yamlValue") } - + # Update the inputs section with the modified input $inputs.Replace("$($inputName):/", $inputSection.content) } @@ -477,7 +477,7 @@ function ApplyWorkflowInputDefaults { # Update the workflow_dispatch section with modified inputs $workflowDispatch.Replace('inputs:/', $inputs.content) - + # Update the on: section with modified workflow_dispatch $yaml.Replace('on:/workflow_dispatch:/', $workflowDispatch.content) } diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index 98ce6cf25..0a49becc1 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -1,4 +1,4 @@ -Get-Module TestActionsHelper | Remove-Module -Force +Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') Import-Module (Join-Path $PSScriptRoot "..\Actions\TelemetryHelper.psm1") $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 @@ -271,7 +271,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults applies default values to workflow inputs' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with workflow_dispatch inputs $yamlContent = @( "name: 'Test Workflow'", @@ -296,9 +296,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " steps:", " - run: echo test" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with workflow input defaults $repoSettings = @{ "workflowInputDefaults" = @( @@ -312,10 +312,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the defaults were applied $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: true' $yaml.Get('on:/workflow_dispatch:/inputs:/useGhTokenWorkflow:/default:').content -join '' | Should -Be 'default: true' @@ -324,7 +324,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults handles workflows without workflow_dispatch' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML without workflow_dispatch $yamlContent = @( "name: 'Test Workflow'", @@ -337,9 +337,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " steps:", " - run: echo test" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with workflow input defaults $repoSettings = @{ "workflowInputDefaults" = @( @@ -351,14 +351,14 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should not throw { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw } It 'ApplyWorkflowInputDefaults handles non-matching workflow names' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML $yamlContent = @( "name: 'Test Workflow'", @@ -373,9 +373,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with workflow input defaults for a different workflow $repoSettings = @{ "workflowInputDefaults" = @( @@ -387,17 +387,17 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the defaults were NOT applied (original value preserved) $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: false' } It 'ApplyWorkflowInputDefaults handles inputs without existing default' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with input without default $yamlContent = @( "name: 'Test Workflow'", @@ -411,9 +411,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with workflow input defaults $repoSettings = @{ "workflowInputDefaults" = @( @@ -425,10 +425,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the default was added $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') $defaultLine | Should -Not -BeNullOrEmpty @@ -437,7 +437,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults handles different value types' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML $yamlContent = @( "name: 'Test Workflow'", @@ -457,9 +457,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with different value types $repoSettings = @{ "workflowInputDefaults" = @( @@ -473,10 +473,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify the defaults were applied with correct types $yaml.Get('on:/workflow_dispatch:/inputs:/boolInput:/default:').content -join '' | Should -Be 'default: true' $yaml.Get('on:/workflow_dispatch:/inputs:/stringInput:/default:').content -join '' | Should -Be "default: 'test'" @@ -485,7 +485,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults validates boolean type mismatch' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with boolean input $yamlContent = @( "name: 'Test Workflow'", @@ -499,9 +499,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with wrong type (string instead of boolean) $repoSettings = @{ "workflowInputDefaults" = @( @@ -513,15 +513,15 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected boolean value*" } It 'ApplyWorkflowInputDefaults validates number type mismatch' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with number input $yamlContent = @( "name: 'Test Workflow'", @@ -535,9 +535,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with wrong type (string instead of number) $repoSettings = @{ "workflowInputDefaults" = @( @@ -549,15 +549,15 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected number value*" } It 'ApplyWorkflowInputDefaults validates string type mismatch' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with string input $yamlContent = @( "name: 'Test Workflow'", @@ -571,9 +571,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with wrong type (boolean instead of string) $repoSettings = @{ "workflowInputDefaults" = @( @@ -585,15 +585,15 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected string value*" } It 'ApplyWorkflowInputDefaults validates choice type' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with choice input $yamlContent = @( "name: 'Test Workflow'", @@ -610,9 +610,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with correct type (string for choice) $repoSettings = @{ "workflowInputDefaults" = @( @@ -624,7 +624,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should succeed { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw $yaml.Get('on:/workflow_dispatch:/inputs:/choiceInput:/default:').content -join '' | Should -Be "default: 'option2'" @@ -632,7 +632,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults validates choice value is in available options' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with choice input $yamlContent = @( "name: 'Test Workflow'", @@ -650,9 +650,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with invalid choice value $repoSettings = @{ "workflowInputDefaults" = @( @@ -664,15 +664,15 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*not a valid choice*" } It 'ApplyWorkflowInputDefaults validates choice value with case-sensitive matching' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with choice input using mixed case options $yamlContent = @( "name: 'Test Workflow'", @@ -690,9 +690,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Test 1: Exact case match should succeed $repoSettings = @{ "workflowInputDefaults" = @( @@ -704,10 +704,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw $yaml.Get('on:/workflow_dispatch:/inputs:/releaseTypeInput:/default:').content -join '' | Should -Be "default: 'Prerelease'" - + # Test 2: Wrong case should fail with case-sensitive error message $yaml2 = [Yaml]::new($yamlContent) $repoSettings2 = @{ @@ -720,10 +720,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - - { ApplyWorkflowInputDefaults -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | + + { ApplyWorkflowInputDefaults -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" - + # Test 3: Uppercase version should also fail $yaml3 = [Yaml]::new($yamlContent) $repoSettings3 = @{ @@ -736,14 +736,14 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - - { ApplyWorkflowInputDefaults -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | + + { ApplyWorkflowInputDefaults -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" } It 'ApplyWorkflowInputDefaults handles inputs without type specification' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML without type (defaults to string) $yamlContent = @( "name: 'Test Workflow'", @@ -757,9 +757,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with string value (should work without warning) $repoSettings = @{ "workflowInputDefaults" = @( @@ -771,7 +771,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults - should succeed { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw $yaml.Get('on:/workflow_dispatch:/inputs:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" @@ -779,7 +779,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowInputDefaults escapes single quotes in string values' { . (Join-Path $scriptRoot "yamlclass.ps1") - + # Create a test workflow YAML with string input $yamlContent = @( "name: 'Test Workflow'", @@ -793,9 +793,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " test:", " runs-on: ubuntu-latest" ) - + $yaml = [Yaml]::new($yamlContent) - + # Create settings with string value containing single quote $repoSettings = @{ "workflowInputDefaults" = @( @@ -807,10 +807,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } ) } - + # Apply the defaults ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - + # Verify single quote is escaped per YAML spec (doubled) $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" } From 8e6d97ae68add8e496887c3ac8af60b2c202fb99 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Fri, 17 Oct 2025 15:16:05 +0200 Subject: [PATCH 09/30] Remove unnecessary blank line in RELEASENOTES.md --- RELEASENOTES.md | 1 - Scenarios/settings.md | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index a58e132ae..862e12476 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,3 @@ - ### Set default values for workflow inputs A new setting `workflowInputDefaults` allows you to configure default values for workflow_dispatch inputs. This makes it easier to run workflows manually with consistent settings across your team. diff --git a/Scenarios/settings.md b/Scenarios/settings.md index c8c28bc1f..66d6d70fd 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -83,7 +83,7 @@ The repository settings are only read from the repository settings file (.github | useGitSubmodules | If your repository is using Git Submodules, you can set the `useGitSubmodules` setting to `"true"` or `"recursive"` in order to use these submodules during build workflows. If `useGitSubmodules` is not set, git submodules are not initialized. If the submodules reside in private repositories, you need to define a `gitSubmodulesToken` secret. Read [this](https://aka.ms/algosecrets#gitSubmodulesToken) for more information. | | commitOptions | If you want more control over how AL-Go creates pull requests or commits changes to the repository you can define `commitOptions`. It is a structure defining how you want AL-Go to handle automated commits or pull requests coming from AL-Go (e.g. for Update AL-Go System Files). The structure contains the following properties:
**messageSuffix** = A string you want to append to the end of commits/pull requests created by AL-Go. This can be useful if you are using the Azure Boards integration (or similar integration) to link commits to work items.
`createPullRequest` : A boolean defining whether AL-Go should create a pull request or attempt to push directly in the branch.
**pullRequestAutoMerge** = A boolean defining whether you want AL-Go pull requests to be set to auto-complete. This will auto-complete the pull requests once all checks are green and all required reviewers have approved.
**pullRequestMergeMethod** = A string defining which merge method to use when auto-merging pull requests. Valid values are "merge" and "squash". Default is "squash".
**pullRequestLabels** = A list of labels to add to the pull request. The labels need to be created in the repository before they can be applied.
If you want different behavior in different AL-Go workflows you can add the `commitOptions` setting to your [workflow-specific settings files](https://github.com/microsoft/AL-Go/blob/main/Scenarios/settings.md#where-are-the-settings-located). | | incrementalBuilds | A structure defining how you want AL-Go to handle incremental builds. When using incremental builds for a build, AL-Go will look for the latest successful CI/CD build, newer than the defined `retentionDays` and only rebuild projects or apps (based on `mode`) which needs to be rebuilt. The structure supports the following properties:
**onPush** = Determines whether incremental builds is enabled in CI/CD triggered by a merge/push event. Default is **false**.
**onPull_Request** = Determines whether incremental builds is enabled in Pull Requests. Default is **true**.
**onSchedule** = Determines whether incremental builds is enabled in CI/CD when running on a schedule. Default is **false**.
**retentionDays** = Number of days a successful build is good (and can be used for incremental builds). Default is **30**.
**mode** = Specifies the mode for incremental builds. Currently, two values are supported. Use **modifiedProjects** when you want to rebuild all apps in all modified projects and depending projects or **modifiedApps** if you want to rebuild modified apps and all apps with dependencies to this app.
**NOTE:** when running incremental builds, it is recommended to also set `workflowConcurrency` for the CI/CD workflow, as defined [here](https://aka.ms/algosettings#workflowConcurrency). | -| workflowInputDefaults | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). Workflow names are matched using [sanitized names](#workflow-name-sanitization).
**defaults** = An array of default values, where each entry has:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  â€¢ The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  â€¢ For choice inputs, the value must be one of the options declared in the workflow
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowInputDefaults": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | +| workflowInputDefaults | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). Workflow names are matched using [sanitized names](#workflow-name-sanitization).
**defaults** = An array of default values, where each entry has:
**name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  • The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  • For choice inputs, the value must be one of the options declared in the workflow
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowInputDefaults": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | From e2fe4f1c0ee142676de63406c0e0a70ea84f865e Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Fri, 17 Oct 2025 15:17:10 +0200 Subject: [PATCH 10/30] Remove unnecessary blank line in CheckForUpdates.Action.Test.ps1 --- Tests/CheckForUpdates.Action.Test.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index 0a49becc1..9dadac0e7 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -815,4 +815,3 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" } } - From 6b826f299cb93a13eab3ca595ec25c4e5c36f46c Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Mon, 20 Oct 2025 12:08:18 +0200 Subject: [PATCH 11/30] Consolidate Export-ModuleMember for ReadSettings and SanitizeWorkflowName functions --- Actions/.Modules/ReadSettings.psm1 | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Actions/.Modules/ReadSettings.psm1 b/Actions/.Modules/ReadSettings.psm1 index 2d2c20aa6..6ff302ddf 100644 --- a/Actions/.Modules/ReadSettings.psm1 +++ b/Actions/.Modules/ReadSettings.psm1 @@ -609,6 +609,5 @@ function SanitizeWorkflowName { return $workflowName.Trim().Split([System.IO.Path]::getInvalidFileNameChars()) -join "" } -Export-ModuleMember -Function ReadSettings -Export-ModuleMember -Function SanitizeWorkflowName +Export-ModuleMember -Function ReadSettings, SanitizeWorkflowName Export-ModuleMember -Variable ALGoFolderName, ALGoSettingsFile, RepoSettingsFile, CustomTemplateRepoSettingsFile, CustomTemplateProjectSettingsFile From 84f6b53b038ae58cb992246f86b9a71f31d04d7d Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Mon, 20 Oct 2025 13:41:54 +0200 Subject: [PATCH 12/30] Move module import to global scope --- Tests/CheckForUpdates.Action.Test.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index 9dadac0e7..e5d81363d 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -1,6 +1,7 @@ Get-Module TestActionsHelper | Remove-Module -Force Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') -Import-Module (Join-Path $PSScriptRoot "..\Actions\TelemetryHelper.psm1") +Import-Module (Join-Path $PSScriptRoot "../Actions/TelemetryHelper.psm1") +Import-Module (Join-Path $PSScriptRoot '../Actions/.Modules/ReadSettings.psm1') -Force $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 Describe "CheckForUpdates Action Tests" { @@ -210,7 +211,6 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $actionName = "CheckForUpdates" $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve Import-Module (Join-Path $scriptRoot "..\Github-Helper.psm1") -DisableNameChecking -Force - Import-Module (Join-Path $scriptRoot "..\.Modules\ReadSettings.psm1") -DisableNameChecking -Force . (Join-Path -Path $scriptRoot -ChildPath "CheckForUpdates.HelperFunctions.ps1") [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] $tmpSrcFile = Join-Path $PSScriptRoot "tempSrcFile.json" From df27829c0a729ee2ad0bafea4549b69625e9b7ba Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Thu, 13 Nov 2025 22:05:06 +0100 Subject: [PATCH 13/30] Rename workflowInputDefaults to workflowDefaultInputs in settings and related references --- Actions/.Modules/ReadSettings.psm1 | 2 +- Actions/.Modules/settings.schema.json | 4 +- .../CheckForUpdates.HelperFunctions.ps1 | 8 +- RELEASENOTES.md | 6 +- Scenarios/settings.md | 4 +- Tests/CheckForUpdates.Action.Test.ps1 | 86 +++++++++---------- 6 files changed, 55 insertions(+), 55 deletions(-) diff --git a/Actions/.Modules/ReadSettings.psm1 b/Actions/.Modules/ReadSettings.psm1 index af841aa7c..4144a57ad 100644 --- a/Actions/.Modules/ReadSettings.psm1 +++ b/Actions/.Modules/ReadSettings.psm1 @@ -241,7 +241,7 @@ function GetDefaultSettings "gitSubmodulesTokenSecretName" = "gitSubmodulesToken" "shortLivedArtifactsRetentionDays" = 1 # 0 means use GitHub default "reportSuppressedDiagnostics" = $false - "workflowInputDefaults" = @() + "workflowDefaultInputs" = @() } } diff --git a/Actions/.Modules/settings.schema.json b/Actions/.Modules/settings.schema.json index 34870eb5d..e3bb08801 100644 --- a/Actions/.Modules/settings.schema.json +++ b/Actions/.Modules/settings.schema.json @@ -694,7 +694,7 @@ "type": "boolean", "description": "Report suppressed diagnostics. See https://aka.ms/ALGoSettings#reportsuppresseddiagnostics" }, - "workflowInputDefaults": { + "workflowDefaultInputs": { "type": "array", "items": { "type": "object", @@ -722,7 +722,7 @@ }, "required": ["workflow", "defaults"] }, - "description": "An array of workflow input default values. See https://aka.ms/ALGoSettings#workflowInputDefaults" + "description": "An array of workflow input default values. See https://aka.ms/ALGoSettings#workflowDefaultInputs" } } } diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index 307e5e85b..baa15a3f2 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -301,7 +301,7 @@ function ModifyUpdateALGoSystemFiles { $yaml.Replace('jobs:/UpdateALGoSystemFiles:/', $updateALGoSystemFilesJob.content) } -function ApplyWorkflowInputDefaults { +function ApplyWorkflowDefaultInputs { Param( [Yaml] $yaml, [hashtable] $repoSettings, @@ -326,7 +326,7 @@ function ApplyWorkflowInputDefaults { # Find matching workflow input defaults $matchingDefaults = @() - foreach ($workflowInputDefault in $repoSettings.workflowInputDefaults) { + foreach ($workflowInputDefault in $repoSettings.workflowDefaultInputs) { if ($workflowInputDefault.workflow) { # Sanitize the workflow name in the setting for comparison $settingWorkflowName = SanitizeWorkflowName -workflowName $workflowInputDefault.workflow @@ -576,8 +576,8 @@ function GetWorkflowContentWithChangesFromSettings { } # Apply workflow input defaults from settings - if ($repoSettings.Keys -contains 'workflowInputDefaults') { - ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName $workflowName + if ($repoSettings.Keys -contains 'workflowDefaultInputs') { + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName $workflowName } # combine all the yaml file lines into a single string with LF line endings diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 158b59dbc..55f7671d2 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,6 +1,6 @@ ### Set default values for workflow inputs -A new setting `workflowInputDefaults` allows you to configure default values for workflow_dispatch inputs. This makes it easier to run workflows manually with consistent settings across your team. +A new setting `workflowDefaultInputs` allows you to configure default values for workflow_dispatch inputs. This makes it easier to run workflows manually with consistent settings across your team. When you add this setting to your AL-Go settings file and run the "Update AL-Go System Files" workflow, the default values will be automatically applied to the workflow YAML files in your repository. The default values must match the input types (boolean, number, string, or choice) defined in the workflow YAML files. @@ -9,7 +9,7 @@ Example configuration: ```json { - "workflowInputDefaults": [ + "workflowDefaultInputs": [ { "workflow": "Create Release", "defaults": [ @@ -28,7 +28,7 @@ Example configuration: } ``` -Read more at [workflowInputDefaults](https://aka.ms/algosettings#workflowInputDefaults). +Read more at [workflowDefaultInputs](https://aka.ms/algosettings#workflowDefaultInputs). ### Issues diff --git a/Scenarios/settings.md b/Scenarios/settings.md index 66d6d70fd..d9b79a42c 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -83,7 +83,7 @@ The repository settings are only read from the repository settings file (.github | useGitSubmodules | If your repository is using Git Submodules, you can set the `useGitSubmodules` setting to `"true"` or `"recursive"` in order to use these submodules during build workflows. If `useGitSubmodules` is not set, git submodules are not initialized. If the submodules reside in private repositories, you need to define a `gitSubmodulesToken` secret. Read [this](https://aka.ms/algosecrets#gitSubmodulesToken) for more information. | | commitOptions | If you want more control over how AL-Go creates pull requests or commits changes to the repository you can define `commitOptions`. It is a structure defining how you want AL-Go to handle automated commits or pull requests coming from AL-Go (e.g. for Update AL-Go System Files). The structure contains the following properties:
**messageSuffix** = A string you want to append to the end of commits/pull requests created by AL-Go. This can be useful if you are using the Azure Boards integration (or similar integration) to link commits to work items.
`createPullRequest` : A boolean defining whether AL-Go should create a pull request or attempt to push directly in the branch.
**pullRequestAutoMerge** = A boolean defining whether you want AL-Go pull requests to be set to auto-complete. This will auto-complete the pull requests once all checks are green and all required reviewers have approved.
**pullRequestMergeMethod** = A string defining which merge method to use when auto-merging pull requests. Valid values are "merge" and "squash". Default is "squash".
**pullRequestLabels** = A list of labels to add to the pull request. The labels need to be created in the repository before they can be applied.
If you want different behavior in different AL-Go workflows you can add the `commitOptions` setting to your [workflow-specific settings files](https://github.com/microsoft/AL-Go/blob/main/Scenarios/settings.md#where-are-the-settings-located). | | incrementalBuilds | A structure defining how you want AL-Go to handle incremental builds. When using incremental builds for a build, AL-Go will look for the latest successful CI/CD build, newer than the defined `retentionDays` and only rebuild projects or apps (based on `mode`) which needs to be rebuilt. The structure supports the following properties:
**onPush** = Determines whether incremental builds is enabled in CI/CD triggered by a merge/push event. Default is **false**.
**onPull_Request** = Determines whether incremental builds is enabled in Pull Requests. Default is **true**.
**onSchedule** = Determines whether incremental builds is enabled in CI/CD when running on a schedule. Default is **false**.
**retentionDays** = Number of days a successful build is good (and can be used for incremental builds). Default is **30**.
**mode** = Specifies the mode for incremental builds. Currently, two values are supported. Use **modifiedProjects** when you want to rebuild all apps in all modified projects and depending projects or **modifiedApps** if you want to rebuild modified apps and all apps with dependencies to this app.
**NOTE:** when running incremental builds, it is recommended to also set `workflowConcurrency` for the CI/CD workflow, as defined [here](https://aka.ms/algosettings#workflowConcurrency). | -| workflowInputDefaults | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). Workflow names are matched using [sanitized names](#workflow-name-sanitization).
**defaults** = An array of default values, where each entry has:
**name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  • The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  • For choice inputs, the value must be one of the options declared in the workflow
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowInputDefaults": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | +| workflowDefaultInputs | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). Workflow names are matched using [sanitized names](#workflow-name-sanitization).
**defaults** = An array of default values, where each entry has:
**name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  • The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  • For choice inputs, the value must be one of the options declared in the workflow
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowDefaultInputs": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | @@ -186,7 +186,7 @@ to your [project settings file](#where-are-the-settings-located) will ensure tha ### Workflow Name Sanitization -When matching workflow names (for conditional settings and workflowInputDefaults), AL-Go sanitizes the actual workflow name before comparison. Sanitization removes invalid filename characters such as leading spaces, quotes, colons, slashes, and other special characters. For example, a workflow named `" CI/CD"` would be sanitized to `"CICD"` for matching purposes. +When matching workflow names (for conditional settings and workflowDefaultInputs), AL-Go sanitizes the actual workflow name before comparison. Sanitization removes invalid filename characters such as leading spaces, quotes, colons, slashes, and other special characters. For example, a workflow named `" CI/CD"` would be sanitized to `"CICD"` for matching purposes. You could imagine that you could have and organizational settings variable containing: diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index e5d81363d..463e7037c 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -269,7 +269,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $modifiedContent."`$schema" | Should -Be "someSchema" } - It 'ApplyWorkflowInputDefaults applies default values to workflow inputs' { + It 'ApplyWorkflowDefaultInputs applies default values to workflow inputs' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML with workflow_dispatch inputs @@ -301,7 +301,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with workflow input defaults $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -314,7 +314,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" # Verify the defaults were applied $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: true' @@ -322,7 +322,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $yaml.Get('on:/workflow_dispatch:/inputs:/updateVersionNumber:/default:').content -join '' | Should -Be "default: '+0.1'" } - It 'ApplyWorkflowInputDefaults handles workflows without workflow_dispatch' { + It 'ApplyWorkflowDefaultInputs handles workflows without workflow_dispatch' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML without workflow_dispatch @@ -342,7 +342,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with workflow input defaults $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -353,10 +353,10 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - should not throw - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw } - It 'ApplyWorkflowInputDefaults handles non-matching workflow names' { + It 'ApplyWorkflowDefaultInputs handles non-matching workflow names' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML @@ -378,7 +378,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with workflow input defaults for a different workflow $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Other Workflow" "defaults" = @( @@ -389,13 +389,13 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" # Verify the defaults were NOT applied (original value preserved) $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: false' } - It 'ApplyWorkflowInputDefaults handles inputs without existing default' { + It 'ApplyWorkflowDefaultInputs handles inputs without existing default' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML with input without default @@ -416,7 +416,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with workflow input defaults $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -427,7 +427,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" # Verify the default was added $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') @@ -435,7 +435,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $defaultLine.content -join '' | Should -Be "default: 'test-value'" } - It 'ApplyWorkflowInputDefaults handles different value types' { + It 'ApplyWorkflowDefaultInputs handles different value types' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML @@ -462,7 +462,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with different value types $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -475,7 +475,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" # Verify the defaults were applied with correct types $yaml.Get('on:/workflow_dispatch:/inputs:/boolInput:/default:').content -join '' | Should -Be 'default: true' @@ -483,7 +483,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $yaml.Get('on:/workflow_dispatch:/inputs:/numberInput:/default:').content -join '' | Should -Be 'default: 42' } - It 'ApplyWorkflowInputDefaults validates boolean type mismatch' { + It 'ApplyWorkflowDefaultInputs validates boolean type mismatch' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML with boolean input @@ -504,7 +504,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with wrong type (string instead of boolean) $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -515,11 +515,11 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected boolean value*" } - It 'ApplyWorkflowInputDefaults validates number type mismatch' { + It 'ApplyWorkflowDefaultInputs validates number type mismatch' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML with number input @@ -540,7 +540,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with wrong type (string instead of number) $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -551,11 +551,11 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected number value*" } - It 'ApplyWorkflowInputDefaults validates string type mismatch' { + It 'ApplyWorkflowDefaultInputs validates string type mismatch' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML with string input @@ -576,7 +576,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with wrong type (boolean instead of string) $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -587,11 +587,11 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected string value*" } - It 'ApplyWorkflowInputDefaults validates choice type' { + It 'ApplyWorkflowDefaultInputs validates choice type' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML with choice input @@ -615,7 +615,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with correct type (string for choice) $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -626,11 +626,11 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - should succeed - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw $yaml.Get('on:/workflow_dispatch:/inputs:/choiceInput:/default:').content -join '' | Should -Be "default: 'option2'" } - It 'ApplyWorkflowInputDefaults validates choice value is in available options' { + It 'ApplyWorkflowDefaultInputs validates choice value is in available options' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML with choice input @@ -655,7 +655,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with invalid choice value $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -666,11 +666,11 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - should throw validation error - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*not a valid choice*" } - It 'ApplyWorkflowInputDefaults validates choice value with case-sensitive matching' { + It 'ApplyWorkflowDefaultInputs validates choice value with case-sensitive matching' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML with choice input using mixed case options @@ -695,7 +695,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Test 1: Exact case match should succeed $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -705,13 +705,13 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { ) } - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw $yaml.Get('on:/workflow_dispatch:/inputs:/releaseTypeInput:/default:').content -join '' | Should -Be "default: 'Prerelease'" # Test 2: Wrong case should fail with case-sensitive error message $yaml2 = [Yaml]::new($yamlContent) $repoSettings2 = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -721,13 +721,13 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { ) } - { ApplyWorkflowInputDefaults -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | + { ApplyWorkflowDefaultInputs -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" # Test 3: Uppercase version should also fail $yaml3 = [Yaml]::new($yamlContent) $repoSettings3 = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -737,11 +737,11 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { ) } - { ApplyWorkflowInputDefaults -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | + { ApplyWorkflowDefaultInputs -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" } - It 'ApplyWorkflowInputDefaults handles inputs without type specification' { + It 'ApplyWorkflowDefaultInputs handles inputs without type specification' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML without type (defaults to string) @@ -762,7 +762,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with string value (should work without warning) $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -773,11 +773,11 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - should succeed - { ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw $yaml.Get('on:/workflow_dispatch:/inputs:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" } - It 'ApplyWorkflowInputDefaults escapes single quotes in string values' { + It 'ApplyWorkflowDefaultInputs escapes single quotes in string values' { . (Join-Path $scriptRoot "yamlclass.ps1") # Create a test workflow YAML with string input @@ -798,7 +798,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with string value containing single quote $repoSettings = @{ - "workflowInputDefaults" = @( + "workflowDefaultInputs" = @( @{ "workflow" = "Test Workflow" "defaults" = @( @@ -809,7 +809,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { } # Apply the defaults - ApplyWorkflowInputDefaults -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" # Verify single quote is escaped per YAML spec (doubled) $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" From 3f02f9d80c0496ff71f004e2b6d22854cb951045 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Mon, 17 Nov 2025 22:51:53 +0100 Subject: [PATCH 14/30] Simplified workflowDefaultInputs setting --- Actions/.Modules/ReadSettings.psm1 | 2 +- Actions/.Modules/settings.schema.json | 23 +- .../CheckForUpdates.HelperFunctions.ps1 | 231 +-- Scenarios/settings.md | 6 +- Tests/CheckForUpdates.Action.Test.ps1 | 1846 +++++++++-------- 5 files changed, 1146 insertions(+), 962 deletions(-) diff --git a/Actions/.Modules/ReadSettings.psm1 b/Actions/.Modules/ReadSettings.psm1 index 4144a57ad..8c80cbb36 100644 --- a/Actions/.Modules/ReadSettings.psm1 +++ b/Actions/.Modules/ReadSettings.psm1 @@ -609,5 +609,5 @@ function SanitizeWorkflowName { return $workflowName.Trim().Split([System.IO.Path]::getInvalidFileNameChars()) -join "" } -Export-ModuleMember -Function ReadSettings, SanitizeWorkflowName +Export-ModuleMember -Function ReadSettings Export-ModuleMember -Variable ALGoFolderName, ALGoSettingsFile, RepoSettingsFile, CustomTemplateRepoSettingsFile, CustomTemplateProjectSettingsFile diff --git a/Actions/.Modules/settings.schema.json b/Actions/.Modules/settings.schema.json index e3bb08801..4e36caa21 100644 --- a/Actions/.Modules/settings.schema.json +++ b/Actions/.Modules/settings.schema.json @@ -699,28 +699,15 @@ "items": { "type": "object", "properties": { - "workflow": { + "name": { "type": "string", - "description": "The workflow name to apply default values to (must match exactly)" + "description": "The name of the workflow input" }, - "defaults": { - "type": "array", - "items": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The name of the workflow input" - }, - "value": { - "description": "The default value for the workflow input (can be string, boolean, or number)" - } - }, - "required": ["name", "value"] - } + "value": { + "description": "The default value for the workflow input (can be string, boolean, or number)" } }, - "required": ["workflow", "defaults"] + "required": ["name", "value"] }, "description": "An array of workflow input default values. See https://aka.ms/ALGoSettings#workflowDefaultInputs" } diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index baa15a3f2..32c2c7ece 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -321,157 +321,140 @@ function ApplyWorkflowDefaultInputs { return } - # Sanitize the workflow name for matching - $sanitizedWorkflowName = SanitizeWorkflowName -workflowName $workflowName - - # Find matching workflow input defaults - $matchingDefaults = @() - foreach ($workflowInputDefault in $repoSettings.workflowDefaultInputs) { - if ($workflowInputDefault.workflow) { - # Sanitize the workflow name in the setting for comparison - $settingWorkflowName = SanitizeWorkflowName -workflowName $workflowInputDefault.workflow - if ($sanitizedWorkflowName -eq $settingWorkflowName) { - $matchingDefaults += $workflowInputDefault - } - } - } - - if ($matchingDefaults.Count -eq 0) { - # No matching defaults for this workflow + if ($repoSettings.workflowDefaultInputs.Count -eq 0) { + # No defaults for this workflow return } # Apply defaults to matching inputs - foreach ($workflowInputDefault in $matchingDefaults) { - foreach ($default in $workflowInputDefault.defaults) { - $inputName = $default.name - $defaultValue = $default.value - - # Check if this input exists in the workflow - $inputSection = $inputs.Get("$($inputName):/") - if ($inputSection) { - # Get the input type from the YAML if specified - $inputType = $null - $typeStart = 0 - $typeCount = 0 - if ($inputSection.Find('type:', [ref] $typeStart, [ref] $typeCount)) { - $typeLine = $inputSection.content[$typeStart].Trim() - if ($typeLine -match 'type:\s*(.+)') { - $inputType = $matches[1].Trim() - } + foreach ($default in $repoSettings.workflowDefaultInputs) { + $inputName = $default.name + $defaultValue = $default.value + + # Check if this input exists in the workflow + $inputSection = $inputs.Get("$($inputName):/") + if ($inputSection) { + # Get the input type from the YAML if specified + $inputType = $null + $typeStart = 0 + $typeCount = 0 + if ($inputSection.Find('type:', [ref] $typeStart, [ref] $typeCount)) { + $typeLine = $inputSection.content[$typeStart].Trim() + if ($typeLine -match 'type:\s*(.+)') { + $inputType = $matches[1].Trim() } + } - # Validate that the value type matches the input type - $validationError = $null - if ($inputType) { - switch ($inputType) { - 'boolean' { - if ($defaultValue -isnot [bool]) { - $validationError = "Workflow '$workflowName', input '$inputName': Expected boolean value, but got $($defaultValue.GetType().Name). Please use `$true or `$false." - } + # Validate that the value type matches the input type + $validationError = $null + if ($inputType) { + switch ($inputType) { + 'boolean' { + if ($defaultValue -isnot [bool]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected boolean value, but got $($defaultValue.GetType().Name). Please use `$true or `$false." } - 'number' { - if ($defaultValue -isnot [int] -and $defaultValue -isnot [long] -and $defaultValue -isnot [double]) { - $validationError = "Workflow '$workflowName', input '$inputName': Expected number value, but got $($defaultValue.GetType().Name)." - } + } + 'number' { + if ($defaultValue -isnot [int] -and $defaultValue -isnot [long] -and $defaultValue -isnot [double]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected number value, but got $($defaultValue.GetType().Name)." } - 'string' { - if ($defaultValue -isnot [string]) { - $validationError = "Workflow '$workflowName', input '$inputName': Expected string value, but got $($defaultValue.GetType().Name)." - } + } + 'string' { + if ($defaultValue -isnot [string]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected string value, but got $($defaultValue.GetType().Name)." } - 'choice' { - # Choice inputs accept strings and must match one of the available options (case-sensitive) - if ($defaultValue -isnot [string]) { - $validationError = "Workflow '$workflowName', input '$inputName': Expected string value for choice input, but got $($defaultValue.GetType().Name)." - } - else { - # Validate that the value is one of the available options - $optionsStart = 0 - $optionsCount = 0 - if ($inputSection.Find('options:', [ref] $optionsStart, [ref] $optionsCount)) { - $availableOptions = @() - # Parse the options from the YAML (they are indented list items starting with "- ") - for ($i = $optionsStart + 1; $i -lt ($optionsStart + $optionsCount); $i++) { - $optionLine = $inputSection.content[$i].Trim() - if ($optionLine -match '^-\s*(.+)$') { - $availableOptions += $matches[1].Trim() - } + } + 'choice' { + # Choice inputs accept strings and must match one of the available options (case-sensitive) + if ($defaultValue -isnot [string]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected string value for choice input, but got $($defaultValue.GetType().Name)." + } + else { + # Validate that the value is one of the available options + $optionsStart = 0 + $optionsCount = 0 + if ($inputSection.Find('options:', [ref] $optionsStart, [ref] $optionsCount)) { + $availableOptions = @() + # Parse the options from the YAML (they are indented list items starting with "- ") + for ($i = $optionsStart + 1; $i -lt ($optionsStart + $optionsCount); $i++) { + $optionLine = $inputSection.content[$i].Trim() + if ($optionLine -match '^-\s*(.+)$') { + $availableOptions += $matches[1].Trim() } + } - if ($availableOptions.Count -gt 0 -and $availableOptions -cnotcontains $defaultValue) { - $validationError = "Workflow '$workflowName', input '$inputName': Value '$defaultValue' is not a valid choice (case-sensitive match required). Available options: $($availableOptions -join ', ')." - } + if ($availableOptions.Count -gt 0 -and $availableOptions -cnotcontains $defaultValue) { + $validationError = "Workflow '$workflowName', input '$inputName': Value '$defaultValue' is not a valid choice (case-sensitive match required). Available options: $($availableOptions -join ', ')." } } } } } - else { - # If no type is specified in the workflow, it defaults to string - if ($defaultValue -isnot [string]) { - OutputWarning "Workflow '$workflowName', input '$inputName': No type specified in workflow (defaults to string), but configured value is $($defaultValue.GetType().Name). This may cause issues." - } + } + else { + # If no type is specified in the workflow, it defaults to string + if ($defaultValue -isnot [string]) { + OutputWarning "Workflow '$workflowName', input '$inputName': No type specified in workflow (defaults to string), but configured value is $($defaultValue.GetType().Name). This may cause issues." } + } - if ($validationError) { - throw $validationError - } + if ($validationError) { + throw $validationError + } - # Convert the default value to the appropriate YAML format - $yamlValue = $defaultValue - if ($defaultValue -is [bool]) { - $yamlValue = $defaultValue.ToString().ToLower() - } - elseif ($defaultValue -is [string]) { - # Quote strings and escape single quotes per YAML spec - $escapedValue = $defaultValue.Replace("'", "''") - $yamlValue = "'$escapedValue'" - } + # Convert the default value to the appropriate YAML format + $yamlValue = $defaultValue + if ($defaultValue -is [bool]) { + $yamlValue = $defaultValue.ToString().ToLower() + } + elseif ($defaultValue -is [string]) { + # Quote strings and escape single quotes per YAML spec + $escapedValue = $defaultValue.Replace("'", "''") + $yamlValue = "'$escapedValue'" + } + + # Find and replace the default: line in the input section + $start = 0 + $count = 0 + if ($inputSection.Find('default:', [ref] $start, [ref] $count)) { + # Replace existing default value + $inputSection.Replace('default:', "default: $yamlValue") + } + else { + # Add default value - find the best place to insert it + # Insert after type, required, or description (whichever comes last) + $insertAfter = -1 + $typeLine = 0 + $typeCount = 0 + $requiredLine = 0 + $requiredCount = 0 + $descLine = 0 + $descCount = 0 - # Find and replace the default: line in the input section - $start = 0 - $count = 0 - if ($inputSection.Find('default:', [ref] $start, [ref] $count)) { - # Replace existing default value - $inputSection.Replace('default:', "default: $yamlValue") + if ($inputSection.Find('type:', [ref] $typeLine, [ref] $typeCount)) { + $insertAfter = $typeLine + $typeCount } - else { - # Add default value - find the best place to insert it - # Insert after type, required, or description (whichever comes last) - $insertAfter = -1 - $typeLine = 0 - $typeCount = 0 - $requiredLine = 0 - $requiredCount = 0 - $descLine = 0 - $descCount = 0 - - if ($inputSection.Find('type:', [ref] $typeLine, [ref] $typeCount)) { - $insertAfter = $typeLine + $typeCount - } - if ($inputSection.Find('required:', [ref] $requiredLine, [ref] $requiredCount)) { - if (($requiredLine + $requiredCount) -gt $insertAfter) { - $insertAfter = $requiredLine + $requiredCount - } - } - if ($inputSection.Find('description:', [ref] $descLine, [ref] $descCount)) { - if (($descLine + $descCount) -gt $insertAfter) { - $insertAfter = $descLine + $descCount - } + if ($inputSection.Find('required:', [ref] $requiredLine, [ref] $requiredCount)) { + if (($requiredLine + $requiredCount) -gt $insertAfter) { + $insertAfter = $requiredLine + $requiredCount } - - if ($insertAfter -eq -1) { - # No other properties, insert at position 1 (after the input name) - $insertAfter = 1 + } + if ($inputSection.Find('description:', [ref] $descLine, [ref] $descCount)) { + if (($descLine + $descCount) -gt $insertAfter) { + $insertAfter = $descLine + $descCount } + } - $inputSection.Insert($insertAfter, "default: $yamlValue") + if ($insertAfter -eq -1) { + # No other properties, insert at position 1 (after the input name) + $insertAfter = 1 } - # Update the inputs section with the modified input - $inputs.Replace("$($inputName):/", $inputSection.content) + $inputSection.Insert($insertAfter, "default: $yamlValue") } + + # Update the inputs section with the modified input + $inputs.Replace("$($inputName):/", $inputSection.content) } } diff --git a/Scenarios/settings.md b/Scenarios/settings.md index d9b79a42c..fa71ecf0a 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -83,7 +83,7 @@ The repository settings are only read from the repository settings file (.github | useGitSubmodules | If your repository is using Git Submodules, you can set the `useGitSubmodules` setting to `"true"` or `"recursive"` in order to use these submodules during build workflows. If `useGitSubmodules` is not set, git submodules are not initialized. If the submodules reside in private repositories, you need to define a `gitSubmodulesToken` secret. Read [this](https://aka.ms/algosecrets#gitSubmodulesToken) for more information. | | commitOptions | If you want more control over how AL-Go creates pull requests or commits changes to the repository you can define `commitOptions`. It is a structure defining how you want AL-Go to handle automated commits or pull requests coming from AL-Go (e.g. for Update AL-Go System Files). The structure contains the following properties:
**messageSuffix** = A string you want to append to the end of commits/pull requests created by AL-Go. This can be useful if you are using the Azure Boards integration (or similar integration) to link commits to work items.
`createPullRequest` : A boolean defining whether AL-Go should create a pull request or attempt to push directly in the branch.
**pullRequestAutoMerge** = A boolean defining whether you want AL-Go pull requests to be set to auto-complete. This will auto-complete the pull requests once all checks are green and all required reviewers have approved.
**pullRequestMergeMethod** = A string defining which merge method to use when auto-merging pull requests. Valid values are "merge" and "squash". Default is "squash".
**pullRequestLabels** = A list of labels to add to the pull request. The labels need to be created in the repository before they can be applied.
If you want different behavior in different AL-Go workflows you can add the `commitOptions` setting to your [workflow-specific settings files](https://github.com/microsoft/AL-Go/blob/main/Scenarios/settings.md#where-are-the-settings-located). | | incrementalBuilds | A structure defining how you want AL-Go to handle incremental builds. When using incremental builds for a build, AL-Go will look for the latest successful CI/CD build, newer than the defined `retentionDays` and only rebuild projects or apps (based on `mode`) which needs to be rebuilt. The structure supports the following properties:
**onPush** = Determines whether incremental builds is enabled in CI/CD triggered by a merge/push event. Default is **false**.
**onPull_Request** = Determines whether incremental builds is enabled in Pull Requests. Default is **true**.
**onSchedule** = Determines whether incremental builds is enabled in CI/CD when running on a schedule. Default is **false**.
**retentionDays** = Number of days a successful build is good (and can be used for incremental builds). Default is **30**.
**mode** = Specifies the mode for incremental builds. Currently, two values are supported. Use **modifiedProjects** when you want to rebuild all apps in all modified projects and depending projects or **modifiedApps** if you want to rebuild modified apps and all apps with dependencies to this app.
**NOTE:** when running incremental builds, it is recommended to also set `workflowConcurrency` for the CI/CD workflow, as defined [here](https://aka.ms/algosettings#workflowConcurrency). | -| workflowDefaultInputs | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
**workflow** = The name of the workflow (e.g., "Create Release"). Workflow names are matched using [sanitized names](#workflow-name-sanitization).
**defaults** = An array of default values, where each entry has:
**name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  • The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  • For choice inputs, the value must be one of the options declared in the workflow
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to the workflow YAML files.
Example:
`"workflowDefaultInputs": [`
` {`
` "workflow": "Create Release",`
` "defaults": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
` ]`
` }`
`]` | +| workflowDefaultInputs | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  • The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  • For choice inputs, the value must be one of the options declared in the workflow
  • Choice validation is case-sensitive
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to all workflows that have matching input names.
**Usage:** This setting can be used on its own in repository settings to apply defaults to all workflows with matching input names. Alternatively, you can use it within [conditional settings](#conditional-settings) to apply defaults only to specific workflows, branches, or other conditions.
Example:
`"workflowDefaultInputs": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
`]` | @@ -182,11 +182,13 @@ to your [project settings file](#where-are-the-settings-located) will ensure tha - **workflows** settings will be applied to workflows matching the patterns - **users** settings will be applied for users matching the patterns +**Note:** You can use `workflowDefaultInputs` within conditional settings to apply workflow input defaults only when certain conditions are met. For example, you could set different default values for specific workflows or branches. + ### Workflow Name Sanitization -When matching workflow names (for conditional settings and workflowDefaultInputs), AL-Go sanitizes the actual workflow name before comparison. Sanitization removes invalid filename characters such as leading spaces, quotes, colons, slashes, and other special characters. For example, a workflow named `" CI/CD"` would be sanitized to `"CICD"` for matching purposes. +When matching workflow names for conditional settings, AL-Go sanitizes the actual workflow name before comparison. Sanitization removes invalid filename characters such as leading spaces, quotes, colons, slashes, and other special characters. For example, a workflow named `" CI/CD"` would be sanitized to `"CICD"` for matching purposes. You could imagine that you could have and organizational settings variable containing: diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index 463e7037c..e1306b211 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -1,817 +1,1029 @@ -Get-Module TestActionsHelper | Remove-Module -Force -Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') -Import-Module (Join-Path $PSScriptRoot "../Actions/TelemetryHelper.psm1") -Import-Module (Join-Path $PSScriptRoot '../Actions/.Modules/ReadSettings.psm1') -Force -$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - -Describe "CheckForUpdates Action Tests" { - BeforeAll { - $actionName = "CheckForUpdates" - $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] - $actionScript = GetActionScript -scriptRoot $scriptRoot -scriptName "$actionName.ps1" - } - - It 'Compile Action' { - Invoke-Expression $actionScript - } - - It 'Test action.yaml matches script' { - $outputs = [ordered]@{ - } - YamlTest -scriptRoot $scriptRoot -actionName $actionName -actionScript $actionScript -outputs $outputs - } - - It 'Test that Update AL-Go System Files uses fixes runs-on' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - $updateYamlFile = Join-Path $scriptRoot "..\..\Templates\Per Tenant Extension\.github\workflows\UpdateGitHubGoSystemFiles.yaml" - $updateYaml = [Yaml]::Load($updateYamlFile) - $updateYaml.content | Where-Object { $_ -like '*runs-on:*' } | ForEach-Object { - $_.Trim() | Should -Be 'runs-on: windows-latest' -Because "Expected 'runs-on: windows-latest', in order to hardcode runner to windows-latest, but got $_" - } - } -} - -Describe('YamlClass Tests') { - BeforeAll { - $actionName = "CheckForUpdates" - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'scriptRoot', Justification = 'False positive.')] - $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve - - Mock Trace-Information {} - } - - It 'Test YamlClass' { - . (Join-Path $scriptRoot "yamlclass.ps1") - $yaml = [Yaml]::load((Join-Path $PSScriptRoot 'YamlSnippet.txt')) - - # Yaml file should have 77 entries - $yaml.content.Count | Should -be 74 - - $start = 0; $count = 0 - # Locate lines for permissions section (including permissions: line) - $yaml.Find('permissions:', [ref] $start, [ref] $count) | Should -be $true - $start | Should -be 17 - $count | Should -be 5 - - # Locate lines for permissions section (excluding permissions: line) - $yaml.Find('permissions:/', [ref] $start, [ref] $count) | Should -be $true - $start | Should -be 18 - $count | Should -be 4 - - # Get Yaml class for permissions section (excluding permissions: line) - $yaml.Get('permissions:/').content | ForEach-Object { $_ | Should -not -belike ' *' } - - # Locate section called permissionos (should return false) - $yaml.Find('permissionos:', [ref] $start, [ref] $count) | Should -Not -be $true - - # Check checkout step - ($yaml.Get('jobs:/Initialization:/steps:/- name: Checkout').content -join '') | Should -be "- name: Checkout uses: actions/checkout@v4 with: lfs: true" - - # Get Shell line in read Settings step - ($yaml.Get('jobs:/Initialization:/steps:/- name: Read settings/with:/shell:').content -join '') | Should -be "shell: powershell" - - # Get Jobs section (without the jobs: line) - $jobsYaml = $yaml.Get('jobs:/') - - # Locate CheckForUpdates - $jobsYaml.Find('CheckForUpdates:', [ref] $start, [ref] $count) | Should -be $true - $start | Should -be 24 - $count | Should -be 19 - - # Replace all occurances of 'shell: powershell' with 'shell: pwsh' - $yaml.ReplaceAll('shell: powershell','shell: pwsh') - $yaml.content[46].Trim() | Should -be 'shell: pwsh' - - # Replace Permissions - $yaml.Replace('Permissions:/',@('contents: write','actions: read')) - $yaml.content[44].Trim() | Should -be 'shell: pwsh' - $yaml.content.Count | Should -be 72 - - # Get Jobs section (without the jobs: line) - $jobsYaml = $yaml.Get('jobs:/') - ($jobsYaml.Get('Initialization:/steps:/- name: Read settings/with:/shell:').content -join '') | Should -be "shell: pwsh" - } - - It 'Test YamlClass Remove' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - $yamlSnippet = @( - "permissions:", - " contents: read", - " actions: read", - " pull-requests: write", - " checks: write" - ) - - $permissionsYaml = [Yaml]::new($yamlSnippet) - - $permissionsContent = $permissionsYaml.Get('permissions:/') - $permissionsContent.content.Count | Should -be 4 - $permissionsContent.Remove(1, 0) # Remove nothing - $permissionsContent.content.Count | Should -be 4 - $permissionsContent.content[0].Trim() | Should -be 'contents: read' - $permissionsContent.content[1].Trim() | Should -be 'actions: read' - $permissionsContent.content[2].Trim() | Should -be 'pull-requests: write' - $permissionsContent.content[3].Trim() | Should -be 'checks: write' - - $permissionsContent = $permissionsYaml.Get('permissions:/') - $permissionsContent.content.Count | Should -be 4 - $permissionsContent.Remove(0, 3) # Remove first 3 lines - $permissionsContent.content.Count | Should -be 1 - $permissionsContent.content[0].Trim() | Should -be 'checks: write' - - $permissionsContent = $permissionsYaml.Get('permissions:/') - $permissionsContent.content.Count | Should -be 4 - $permissionsContent.Remove(2, 1) # Remove only the 3rd line - $permissionsContent.content.Count | Should -be 3 - $permissionsContent.content[0].Trim() | Should -be 'contents: read' - $permissionsContent.content[1].Trim() | Should -be 'actions: read' - $permissionsContent.content[2].Trim() | Should -be 'checks: write' - - $permissionsContent = $permissionsYaml.Get('permissions:/') - $permissionsContent.content.Count | Should -be 4 - $permissionsContent.Remove(2, 4) # Remove more than the number of lines - $permissionsContent.content.Count | Should -be 2 # Only the first two lines should remain - $permissionsContent.content[0].Trim() | Should -be 'contents: read' - $permissionsContent.content[1].Trim() | Should -be 'actions: read' - } - - It 'Test YamlClass GetCustomJobsFromYaml' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - $customizedYaml = [Yaml]::load((Join-Path $PSScriptRoot 'CustomizedYamlSnippet-All.txt')) - $nonCustomizedYaml = [Yaml]::load((Join-Path $PSScriptRoot 'YamlSnippet.txt')) - - # Get Custom jobs from yaml - $customJobs = $customizedYaml.GetCustomJobsFromYaml('CustomJob*') - $customJobs | Should -Not -BeNullOrEmpty - $customJobs.Count | Should -be 2 - - $customJobs[0].Name | Should -Be 'CustomJob-MyFinalJob' - $customJobs[0].Origin | Should -Be 'FinalRepository' - - $customJobs[1].Name | Should -Be 'CustomJob-MyCustomTemplateJob' - $customJobs[1].Origin | Should -Be 'TemplateRepository' - - $emptyCustomJobs = $nonCustomizedYaml.GetCustomJobsFromYaml('CustomJob*') - $emptyCustomJobs | Should -BeNullOrEmpty - } - - It 'Test YamlClass AddCustomJobsToYaml' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - $customTemplateYaml = [Yaml]::load((Join-Path $PSScriptRoot 'CustomizedYamlSnippet-TemplateRepository.txt')) - $finalRepositoryYaml = [Yaml]::load((Join-Path $PSScriptRoot 'CustomizedYamlSnippet-FinalRepository.txt')) - $nonCustomizedYaml = [Yaml]::load((Join-Path $PSScriptRoot 'YamlSnippet.txt')) - - $customTemplateJobs = $customTemplateYaml.GetCustomJobsFromYaml('CustomJob*') - $customTemplateJobs | Should -Not -BeNullOrEmpty - $customTemplateJobs.Count | Should -be 1 - $customTemplateJobs[0].Name | Should -Be 'CustomJob-MyCustomTemplateJob' - $customTemplateJobs[0].Origin | Should -Be 'FinalRepository' # Custom template job has FinalRepository as origin when in the template itself - - # Add the custom job to the non-customized yaml - $nonCustomizedYaml.AddCustomJobsToYaml($customTemplateJobs, [CustomizationOrigin]::TemplateRepository) - - $nonCustomizedYaml.content -join "`r`n" | Should -Be ($finalRepositoryYaml.content -join "`r`n") - - # Adding the jobs again doesn't have an effect - $nonCustomizedYaml.AddCustomJobsToYaml($customTemplateJobs, [CustomizationOrigin]::TemplateRepository) - - $nonCustomizedYaml.content -join "`r`n" | Should -Be ($finalRepositoryYaml.content -join "`r`n") - } - - It('Test YamlClass ApplyTemplateCustomizations') { - . (Join-Path $scriptRoot "yamlclass.ps1") - - $srcContent = Get-Content (Join-Path $PSScriptRoot 'YamlSnippet.txt') - $resultContent = Get-Content (Join-Path $PSScriptRoot 'CustomizedYamlSnippet-FinalRepository.txt') - - [Yaml]::ApplyTemplateCustomizations([ref] $srcContent, (Join-Path $PSScriptRoot 'CustomizedYamlSnippet-TemplateRepository.txt')) - - $srcContent | Should -Be ($resultContent -join "`n") - } - - It('Test YamlClass ApplyFinalCustomizations') { - . (Join-Path $scriptRoot "yamlclass.ps1") - - $srcContent = Get-Content (Join-Path $PSScriptRoot 'YamlSnippet.txt') - $resultContent = Get-Content (Join-Path $PSScriptRoot 'CustomizedYamlSnippet-TemplateRepository.txt') - - [Yaml]::ApplyFinalCustomizations([ref] $srcContent, (Join-Path $PSScriptRoot 'CustomizedYamlSnippet-TemplateRepository.txt')) # Threat the template repo as a final repo - - $srcContent | Should -Be ($resultContent -join "`n") - } -} - -Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { - BeforeAll { - $actionName = "CheckForUpdates" - $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve - Import-Module (Join-Path $scriptRoot "..\Github-Helper.psm1") -DisableNameChecking -Force - . (Join-Path -Path $scriptRoot -ChildPath "CheckForUpdates.HelperFunctions.ps1") - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] - $tmpSrcFile = Join-Path $PSScriptRoot "tempSrcFile.json" - [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] - $tmpDstFile = Join-Path $PSScriptRoot "tempDestFile.json" - } - - AfterEach { - # Clean up temporary files - if (Test-Path $tmpSrcFile) { - Remove-Item -Path $tmpSrcFile -Force - } - if (Test-Path $tmpDstFile) { - Remove-Item -Path $tmpDstFile -Force - } - } - - It 'GetModifiedSettingsContent returns correct content when destination file is not empty' { - # Create settings files with the content - @{ "`$schema" = "someSchema"; "srcSetting" = "value1" } | ConvertTo-Json -Depth 10 | Out-File -FilePath $tmpSrcFile -Force - @{ "setting1" = "value2" } | ConvertTo-Json -Depth 10 | Out-File -FilePath $tmpDstFile -Force - - $modifiedContentJson = GetModifiedSettingsContent -srcSettingsFile $tmpSrcFile -dstSettingsFile $tmpDstFile - - $modifiedContent = $modifiedContentJson | ConvertFrom-Json - $modifiedContent | Should -Not -BeNullOrEmpty - $modifiedContent.PSObject.Properties.Name.Count | Should -Be 2 # setting1 and $schema - $modifiedContent."setting1" | Should -Be "value2" - $modifiedContent."`$schema" | Should -Be "someSchema" - } - - It 'GetModifiedSettingsContent returns correct content when destination file is empty' { - # Create only the source file - @{ "`$schema" = "someSchema"; "srcSetting" = "value1" } | ConvertTo-Json -Depth 10 | Out-File -FilePath $tmpSrcFile -Force - '' | Out-File -FilePath $tmpDstFile -Force - $modifiedContentJson = GetModifiedSettingsContent -srcSettingsFile $tmpSrcFile -dstSettingsFile $tmpDstFile - - $modifiedContent = $modifiedContentJson | ConvertFrom-Json - $modifiedContent | Should -Not -BeNullOrEmpty - @($modifiedContent.PSObject.Properties.Name).Count | Should -Be 2 # srcSetting and $schema - $modifiedContent."`$schema" | Should -Be "someSchema" - $modifiedContent."srcSetting" | Should -Be "value1" - } - - It 'GetModifiedSettingsContent returns correct content when destination file does not exist' { - # Create only the source file - @{ "`$schema" = "someSchema"; "srcSetting" = "value1" } | ConvertTo-Json -Depth 10 | Out-File -FilePath $tmpSrcFile -Force - - Test-Path $tmpDstFile | Should -Be $false - $modifiedContentJson = GetModifiedSettingsContent -srcSettingsFile $tmpSrcFile -dstSettingsFile $tmpDstFile - - $modifiedContent = $modifiedContentJson | ConvertFrom-Json - $modifiedContent | Should -Not -BeNullOrEmpty - $modifiedContent.PSObject.Properties.Name.Count | Should -Be 2 # srcSetting and $schema - $modifiedContent."srcSetting" | Should -Be "value1" - $modifiedContent."`$schema" | Should -Be "someSchema" - } - - It 'ApplyWorkflowDefaultInputs applies default values to workflow inputs' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with workflow_dispatch inputs - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " directCommit:", - " description: Direct Commit?", - " type: boolean", - " default: false", - " useGhTokenWorkflow:", - " description: Use GhTokenWorkflow?", - " type: boolean", - " default: false", - " updateVersionNumber:", - " description: Version number", - " required: false", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest", - " steps:", - " - run: echo test" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with workflow input defaults - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "directCommit"; "value" = $true }, - @{ "name" = "useGhTokenWorkflow"; "value" = $true }, - @{ "name" = "updateVersionNumber"; "value" = "+0.1" } - ) - } - ) - } - - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - - # Verify the defaults were applied - $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: true' - $yaml.Get('on:/workflow_dispatch:/inputs:/useGhTokenWorkflow:/default:').content -join '' | Should -Be 'default: true' - $yaml.Get('on:/workflow_dispatch:/inputs:/updateVersionNumber:/default:').content -join '' | Should -Be "default: '+0.1'" - } - - It 'ApplyWorkflowDefaultInputs handles workflows without workflow_dispatch' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML without workflow_dispatch - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " push:", - " branches: [ main ]", - "jobs:", - " test:", - " runs-on: ubuntu-latest", - " steps:", - " - run: echo test" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with workflow input defaults - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "directCommit"; "value" = $true } - ) - } - ) - } - - # Apply the defaults - should not throw - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - } - - It 'ApplyWorkflowDefaultInputs handles non-matching workflow names' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " directCommit:", - " description: Direct Commit?", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with workflow input defaults for a different workflow - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Other Workflow" - "defaults" = @( - @{ "name" = "directCommit"; "value" = $true } - ) - } - ) - } - - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - - # Verify the defaults were NOT applied (original value preserved) - $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: false' - } - - It 'ApplyWorkflowDefaultInputs handles inputs without existing default' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with input without default - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " myInput:", - " description: My Input", - " required: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with workflow input defaults - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "myInput"; "value" = "test-value" } - ) - } - ) - } - - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - - # Verify the default was added - $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') - $defaultLine | Should -Not -BeNullOrEmpty - $defaultLine.content -join '' | Should -Be "default: 'test-value'" - } - - It 'ApplyWorkflowDefaultInputs handles different value types' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " boolInput:", - " type: boolean", - " default: false", - " stringInput:", - " type: string", - " default: ''", - " numberInput:", - " type: number", - " default: 0", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with different value types - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "boolInput"; "value" = $true }, - @{ "name" = "stringInput"; "value" = "test" }, - @{ "name" = "numberInput"; "value" = 42 } - ) - } - ) - } - - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - - # Verify the defaults were applied with correct types - $yaml.Get('on:/workflow_dispatch:/inputs:/boolInput:/default:').content -join '' | Should -Be 'default: true' - $yaml.Get('on:/workflow_dispatch:/inputs:/stringInput:/default:').content -join '' | Should -Be "default: 'test'" - $yaml.Get('on:/workflow_dispatch:/inputs:/numberInput:/default:').content -join '' | Should -Be 'default: 42' - } - - It 'ApplyWorkflowDefaultInputs validates boolean type mismatch' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with boolean input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " boolInput:", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with wrong type (string instead of boolean) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "boolInput"; "value" = "not a boolean" } - ) - } - ) - } - - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | - Should -Throw "*Expected boolean value*" - } - - It 'ApplyWorkflowDefaultInputs validates number type mismatch' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with number input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " numberInput:", - " type: number", - " default: 0", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with wrong type (string instead of number) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "numberInput"; "value" = "not a number" } - ) - } - ) - } - - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | - Should -Throw "*Expected number value*" - } - - It 'ApplyWorkflowDefaultInputs validates string type mismatch' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with string input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " stringInput:", - " type: string", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with wrong type (boolean instead of string) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "stringInput"; "value" = $true } - ) - } - ) - } - - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | - Should -Throw "*Expected string value*" - } - - It 'ApplyWorkflowDefaultInputs validates choice type' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with choice input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " choiceInput:", - " type: choice", - " options:", - " - option1", - " - option2", - " default: option1", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with correct type (string for choice) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "choiceInput"; "value" = "option2" } - ) - } - ) - } - - # Apply the defaults - should succeed - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - $yaml.Get('on:/workflow_dispatch:/inputs:/choiceInput:/default:').content -join '' | Should -Be "default: 'option2'" - } - - It 'ApplyWorkflowDefaultInputs validates choice value is in available options' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with choice input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " choiceInput:", - " type: choice", - " options:", - " - option1", - " - option2", - " - option3", - " default: option1", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with invalid choice value - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "choiceInput"; "value" = "invalidOption" } - ) - } - ) - } - - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | - Should -Throw "*not a valid choice*" - } - - It 'ApplyWorkflowDefaultInputs validates choice value with case-sensitive matching' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with choice input using mixed case options - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " releaseTypeInput:", - " type: choice", - " options:", - " - Release", - " - Prerelease", - " - Draft", - " default: Release", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Test 1: Exact case match should succeed - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "releaseTypeInput"; "value" = "Prerelease" } - ) - } - ) - } - - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - $yaml.Get('on:/workflow_dispatch:/inputs:/releaseTypeInput:/default:').content -join '' | Should -Be "default: 'Prerelease'" - - # Test 2: Wrong case should fail with case-sensitive error message - $yaml2 = [Yaml]::new($yamlContent) - $repoSettings2 = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "releaseTypeInput"; "value" = "prerelease" } - ) - } - ) - } - - { ApplyWorkflowDefaultInputs -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | - Should -Throw "*case-sensitive match required*" - - # Test 3: Uppercase version should also fail - $yaml3 = [Yaml]::new($yamlContent) - $repoSettings3 = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "releaseTypeInput"; "value" = "PRERELEASE" } - ) - } - ) - } - - { ApplyWorkflowDefaultInputs -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | - Should -Throw "*case-sensitive match required*" - } - - It 'ApplyWorkflowDefaultInputs handles inputs without type specification' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML without type (defaults to string) - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " noTypeInput:", - " description: Input without type", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with string value (should work without warning) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "noTypeInput"; "value" = "string value" } - ) - } - ) - } - - # Apply the defaults - should succeed - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - $yaml.Get('on:/workflow_dispatch:/inputs:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" - } - - It 'ApplyWorkflowDefaultInputs escapes single quotes in string values' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with string input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " nameInput:", - " type: string", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with string value containing single quote - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "nameInput"; "value" = "O'Brien" } - ) - } - ) - } - - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - - # Verify single quote is escaped per YAML spec (doubled) - $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" - } -} +Get-Module TestActionsHelper | Remove-Module -Force +Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') +Import-Module (Join-Path $PSScriptRoot "../Actions/TelemetryHelper.psm1") +Import-Module (Join-Path $PSScriptRoot '../Actions/.Modules/ReadSettings.psm1') -Force +$errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 + +Describe "CheckForUpdates Action Tests" { + BeforeAll { + $actionName = "CheckForUpdates" + $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] + $actionScript = GetActionScript -scriptRoot $scriptRoot -scriptName "$actionName.ps1" + } + + It 'Compile Action' { + Invoke-Expression $actionScript + } + + It 'Test action.yaml matches script' { + $outputs = [ordered]@{ + } + YamlTest -scriptRoot $scriptRoot -actionName $actionName -actionScript $actionScript -outputs $outputs + } + + It 'Test that Update AL-Go System Files uses fixes runs-on' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $updateYamlFile = Join-Path $scriptRoot "..\..\Templates\Per Tenant Extension\.github\workflows\UpdateGitHubGoSystemFiles.yaml" + $updateYaml = [Yaml]::Load($updateYamlFile) + $updateYaml.content | Where-Object { $_ -like '*runs-on:*' } | ForEach-Object { + $_.Trim() | Should -Be 'runs-on: windows-latest' -Because "Expected 'runs-on: windows-latest', in order to hardcode runner to windows-latest, but got $_" + } + } +} + +Describe('YamlClass Tests') { + BeforeAll { + $actionName = "CheckForUpdates" + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'scriptRoot', Justification = 'False positive.')] + $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve + + Mock Trace-Information {} + } + + It 'Test YamlClass' { + . (Join-Path $scriptRoot "yamlclass.ps1") + $yaml = [Yaml]::load((Join-Path $PSScriptRoot 'YamlSnippet.txt')) + + # Yaml file should have 77 entries + $yaml.content.Count | Should -be 74 + + $start = 0; $count = 0 + # Locate lines for permissions section (including permissions: line) + $yaml.Find('permissions:', [ref] $start, [ref] $count) | Should -be $true + $start | Should -be 17 + $count | Should -be 5 + + # Locate lines for permissions section (excluding permissions: line) + $yaml.Find('permissions:/', [ref] $start, [ref] $count) | Should -be $true + $start | Should -be 18 + $count | Should -be 4 + + # Get Yaml class for permissions section (excluding permissions: line) + $yaml.Get('permissions:/').content | ForEach-Object { $_ | Should -not -belike ' *' } + + # Locate section called permissionos (should return false) + $yaml.Find('permissionos:', [ref] $start, [ref] $count) | Should -Not -be $true + + # Check checkout step + ($yaml.Get('jobs:/Initialization:/steps:/- name: Checkout').content -join '') | Should -be "- name: Checkout uses: actions/checkout@v4 with: lfs: true" + + # Get Shell line in read Settings step + ($yaml.Get('jobs:/Initialization:/steps:/- name: Read settings/with:/shell:').content -join '') | Should -be "shell: powershell" + + # Get Jobs section (without the jobs: line) + $jobsYaml = $yaml.Get('jobs:/') + + # Locate CheckForUpdates + $jobsYaml.Find('CheckForUpdates:', [ref] $start, [ref] $count) | Should -be $true + $start | Should -be 24 + $count | Should -be 19 + + # Replace all occurances of 'shell: powershell' with 'shell: pwsh' + $yaml.ReplaceAll('shell: powershell','shell: pwsh') + $yaml.content[46].Trim() | Should -be 'shell: pwsh' + + # Replace Permissions + $yaml.Replace('Permissions:/',@('contents: write','actions: read')) + $yaml.content[44].Trim() | Should -be 'shell: pwsh' + $yaml.content.Count | Should -be 72 + + # Get Jobs section (without the jobs: line) + $jobsYaml = $yaml.Get('jobs:/') + ($jobsYaml.Get('Initialization:/steps:/- name: Read settings/with:/shell:').content -join '') | Should -be "shell: pwsh" + } + + It 'Test YamlClass Remove' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $yamlSnippet = @( + "permissions:", + " contents: read", + " actions: read", + " pull-requests: write", + " checks: write" + ) + + $permissionsYaml = [Yaml]::new($yamlSnippet) + + $permissionsContent = $permissionsYaml.Get('permissions:/') + $permissionsContent.content.Count | Should -be 4 + $permissionsContent.Remove(1, 0) # Remove nothing + $permissionsContent.content.Count | Should -be 4 + $permissionsContent.content[0].Trim() | Should -be 'contents: read' + $permissionsContent.content[1].Trim() | Should -be 'actions: read' + $permissionsContent.content[2].Trim() | Should -be 'pull-requests: write' + $permissionsContent.content[3].Trim() | Should -be 'checks: write' + + $permissionsContent = $permissionsYaml.Get('permissions:/') + $permissionsContent.content.Count | Should -be 4 + $permissionsContent.Remove(0, 3) # Remove first 3 lines + $permissionsContent.content.Count | Should -be 1 + $permissionsContent.content[0].Trim() | Should -be 'checks: write' + + $permissionsContent = $permissionsYaml.Get('permissions:/') + $permissionsContent.content.Count | Should -be 4 + $permissionsContent.Remove(2, 1) # Remove only the 3rd line + $permissionsContent.content.Count | Should -be 3 + $permissionsContent.content[0].Trim() | Should -be 'contents: read' + $permissionsContent.content[1].Trim() | Should -be 'actions: read' + $permissionsContent.content[2].Trim() | Should -be 'checks: write' + + $permissionsContent = $permissionsYaml.Get('permissions:/') + $permissionsContent.content.Count | Should -be 4 + $permissionsContent.Remove(2, 4) # Remove more than the number of lines + $permissionsContent.content.Count | Should -be 2 # Only the first two lines should remain + $permissionsContent.content[0].Trim() | Should -be 'contents: read' + $permissionsContent.content[1].Trim() | Should -be 'actions: read' + } + + It 'Test YamlClass GetCustomJobsFromYaml' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $customizedYaml = [Yaml]::load((Join-Path $PSScriptRoot 'CustomizedYamlSnippet-All.txt')) + $nonCustomizedYaml = [Yaml]::load((Join-Path $PSScriptRoot 'YamlSnippet.txt')) + + # Get Custom jobs from yaml + $customJobs = $customizedYaml.GetCustomJobsFromYaml('CustomJob*') + $customJobs | Should -Not -BeNullOrEmpty + $customJobs.Count | Should -be 2 + + $customJobs[0].Name | Should -Be 'CustomJob-MyFinalJob' + $customJobs[0].Origin | Should -Be 'FinalRepository' + + $customJobs[1].Name | Should -Be 'CustomJob-MyCustomTemplateJob' + $customJobs[1].Origin | Should -Be 'TemplateRepository' + + $emptyCustomJobs = $nonCustomizedYaml.GetCustomJobsFromYaml('CustomJob*') + $emptyCustomJobs | Should -BeNullOrEmpty + } + + It 'Test YamlClass AddCustomJobsToYaml' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $customTemplateYaml = [Yaml]::load((Join-Path $PSScriptRoot 'CustomizedYamlSnippet-TemplateRepository.txt')) + $finalRepositoryYaml = [Yaml]::load((Join-Path $PSScriptRoot 'CustomizedYamlSnippet-FinalRepository.txt')) + $nonCustomizedYaml = [Yaml]::load((Join-Path $PSScriptRoot 'YamlSnippet.txt')) + + $customTemplateJobs = $customTemplateYaml.GetCustomJobsFromYaml('CustomJob*') + $customTemplateJobs | Should -Not -BeNullOrEmpty + $customTemplateJobs.Count | Should -be 1 + $customTemplateJobs[0].Name | Should -Be 'CustomJob-MyCustomTemplateJob' + $customTemplateJobs[0].Origin | Should -Be 'FinalRepository' # Custom template job has FinalRepository as origin when in the template itself + + # Add the custom job to the non-customized yaml + $nonCustomizedYaml.AddCustomJobsToYaml($customTemplateJobs, [CustomizationOrigin]::TemplateRepository) + + $nonCustomizedYaml.content -join "`r`n" | Should -Be ($finalRepositoryYaml.content -join "`r`n") + + # Adding the jobs again doesn't have an effect + $nonCustomizedYaml.AddCustomJobsToYaml($customTemplateJobs, [CustomizationOrigin]::TemplateRepository) + + $nonCustomizedYaml.content -join "`r`n" | Should -Be ($finalRepositoryYaml.content -join "`r`n") + } + + It('Test YamlClass ApplyTemplateCustomizations') { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $srcContent = Get-Content (Join-Path $PSScriptRoot 'YamlSnippet.txt') + $resultContent = Get-Content (Join-Path $PSScriptRoot 'CustomizedYamlSnippet-FinalRepository.txt') + + [Yaml]::ApplyTemplateCustomizations([ref] $srcContent, (Join-Path $PSScriptRoot 'CustomizedYamlSnippet-TemplateRepository.txt')) + + $srcContent | Should -Be ($resultContent -join "`n") + } + + It('Test YamlClass ApplyFinalCustomizations') { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $srcContent = Get-Content (Join-Path $PSScriptRoot 'YamlSnippet.txt') + $resultContent = Get-Content (Join-Path $PSScriptRoot 'CustomizedYamlSnippet-TemplateRepository.txt') + + [Yaml]::ApplyFinalCustomizations([ref] $srcContent, (Join-Path $PSScriptRoot 'CustomizedYamlSnippet-TemplateRepository.txt')) # Threat the template repo as a final repo + + $srcContent | Should -Be ($resultContent -join "`n") + } +} + +Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { + BeforeAll { + $actionName = "CheckForUpdates" + $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve + Import-Module (Join-Path $scriptRoot "..\Github-Helper.psm1") -DisableNameChecking -Force + . (Join-Path -Path $scriptRoot -ChildPath "CheckForUpdates.HelperFunctions.ps1") + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] + $tmpSrcFile = Join-Path $PSScriptRoot "tempSrcFile.json" + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', 'actionScript', Justification = 'False positive.')] + $tmpDstFile = Join-Path $PSScriptRoot "tempDestFile.json" + } + + AfterEach { + # Clean up temporary files + if (Test-Path $tmpSrcFile) { + Remove-Item -Path $tmpSrcFile -Force + } + if (Test-Path $tmpDstFile) { + Remove-Item -Path $tmpDstFile -Force + } + } + + It 'GetModifiedSettingsContent returns correct content when destination file is not empty' { + # Create settings files with the content + @{ "`$schema" = "someSchema"; "srcSetting" = "value1" } | ConvertTo-Json -Depth 10 | Out-File -FilePath $tmpSrcFile -Force + @{ "setting1" = "value2" } | ConvertTo-Json -Depth 10 | Out-File -FilePath $tmpDstFile -Force + + $modifiedContentJson = GetModifiedSettingsContent -srcSettingsFile $tmpSrcFile -dstSettingsFile $tmpDstFile + + $modifiedContent = $modifiedContentJson | ConvertFrom-Json + $modifiedContent | Should -Not -BeNullOrEmpty + $modifiedContent.PSObject.Properties.Name.Count | Should -Be 2 # setting1 and $schema + $modifiedContent."setting1" | Should -Be "value2" + $modifiedContent."`$schema" | Should -Be "someSchema" + } + + It 'GetModifiedSettingsContent returns correct content when destination file is empty' { + # Create only the source file + @{ "`$schema" = "someSchema"; "srcSetting" = "value1" } | ConvertTo-Json -Depth 10 | Out-File -FilePath $tmpSrcFile -Force + '' | Out-File -FilePath $tmpDstFile -Force + $modifiedContentJson = GetModifiedSettingsContent -srcSettingsFile $tmpSrcFile -dstSettingsFile $tmpDstFile + + $modifiedContent = $modifiedContentJson | ConvertFrom-Json + $modifiedContent | Should -Not -BeNullOrEmpty + @($modifiedContent.PSObject.Properties.Name).Count | Should -Be 2 # srcSetting and $schema + $modifiedContent."`$schema" | Should -Be "someSchema" + $modifiedContent."srcSetting" | Should -Be "value1" + } + + It 'GetModifiedSettingsContent returns correct content when destination file does not exist' { + # Create only the source file + @{ "`$schema" = "someSchema"; "srcSetting" = "value1" } | ConvertTo-Json -Depth 10 | Out-File -FilePath $tmpSrcFile -Force + + Test-Path $tmpDstFile | Should -Be $false + $modifiedContentJson = GetModifiedSettingsContent -srcSettingsFile $tmpSrcFile -dstSettingsFile $tmpDstFile + + $modifiedContent = $modifiedContentJson | ConvertFrom-Json + $modifiedContent | Should -Not -BeNullOrEmpty + $modifiedContent.PSObject.Properties.Name.Count | Should -Be 2 # srcSetting and $schema + $modifiedContent."srcSetting" | Should -Be "value1" + $modifiedContent."`$schema" | Should -Be "someSchema" + } + + It 'ApplyWorkflowDefaultInputs applies default values to workflow inputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with workflow_dispatch inputs + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " directCommit:", + " description: Direct Commit?", + " type: boolean", + " default: false", + " useGhTokenWorkflow:", + " description: Use GhTokenWorkflow?", + " type: boolean", + " default: false", + " updateVersionNumber:", + " description: Version number", + " required: false", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - run: echo test" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "directCommit"; "value" = $true }, + @{ "name" = "useGhTokenWorkflow"; "value" = $true }, + @{ "name" = "updateVersionNumber"; "value" = "+0.1" } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the defaults were applied + $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/useGhTokenWorkflow:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/updateVersionNumber:/default:').content -join '' | Should -Be "default: '+0.1'" + } + + It 'ApplyWorkflowDefaultInputs handles empty workflowDefaultInputs array' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with empty workflowDefaultInputs array + $repoSettings = @{ + "workflowDefaultInputs" = @() + } + + # Apply the defaults - should not throw and should not modify workflow + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:').content -join '' | Should -Be 'default: false' + } + + It 'ApplyWorkflowDefaultInputs handles workflows without workflow_dispatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML without workflow_dispatch + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " push:", + " branches: [ main ]", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - run: echo test" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "directCommit"; "value" = $true } + ) + } + + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + } + + It 'ApplyWorkflowDefaultInputs handles workflow_dispatch without inputs section' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with workflow_dispatch but no inputs + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "someInput"; "value" = $true } + ) + } + + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + } + + It 'ApplyWorkflowDefaultInputs applies multiple defaults to same workflow' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with multiple inputs + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " input1:", + " type: boolean", + " default: false", + " input2:", + " type: number", + " default: 0", + " input3:", + " type: string", + " default: ''", + " input4:", + " type: choice", + " options:", + " - optionA", + " - optionB", + " default: optionA", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with multiple defaults + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = $true }, + @{ "name" = "input2"; "value" = 5 }, + @{ "name" = "input3"; "value" = "test-value" }, + @{ "name" = "input4"; "value" = "optionB" } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify all defaults were applied + $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be 'default: 5' + $yaml.Get('on:/workflow_dispatch:/inputs:/input3:/default:').content -join '' | Should -Be "default: 'test-value'" + $yaml.Get('on:/workflow_dispatch:/inputs:/input4:/default:').content -join '' | Should -Be "default: 'optionB'" + } + + It 'ApplyWorkflowDefaultInputs inserts default line when missing' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with input without default line (only description) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " description: 'My input without default'", + " type: string", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with default value + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = "new-default" } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify default line was inserted + $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') + $defaultLine | Should -Not -BeNullOrEmpty + $defaultLine.content -join '' | Should -Be "default: 'new-default'" + } + + It 'ApplyWorkflowDefaultInputs is case-insensitive for input names' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with specific casing + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " MyInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with different casing + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify default WAS applied despite case difference (case-insensitive matching) + $yaml.Get('on:/workflow_dispatch:/inputs:/MyInput:/default:').content -join '' | Should -Be 'default: true' + } + + It 'ApplyWorkflowDefaultInputs ignores defaults for non-existent inputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " existingInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with both existing and non-existent input names + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "existingInput"; "value" = $true }, + @{ "name" = "nonExistentInput"; "value" = "ignored" }, + @{ "name" = "anotherMissingInput"; "value" = 42 } + ) + } + + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + + # Verify only the existing input was modified + $yaml.Get('on:/workflow_dispatch:/inputs:/existingInput:/default:').content -join '' | Should -Be 'default: true' + } + + It 'ApplyWorkflowDefaultInputs handles special YAML characters in string values' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " input1:", + " type: string", + " default: ''", + " input2:", + " type: string", + " default: ''", + " input3:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with special YAML characters + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = "value: with colon" }, + @{ "name" = "input2"; "value" = "value # with comment" }, + @{ "name" = "input3"; "value" = "value with 'quotes' inside" } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify values are properly quoted and escaped + $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be "default: 'value: with colon'" + $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be "default: 'value # with comment'" + $yaml.Get('on:/workflow_dispatch:/inputs:/input3:/default:').content -join '' | Should -Be "default: 'value with ''quotes'' inside'" + } + + It 'ApplyWorkflowDefaultInputs handles environment input type' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with environment type + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " environmentName:", + " description: Environment to deploy to", + " type: environment", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with environment value (should be treated as string) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "environmentName"; "value" = "production" } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify environment value is set as string + $yaml.Get('on:/workflow_dispatch:/inputs:/environmentName:/default:').content -join '' | Should -Be "default: 'production'" + } + + It 'ApplyWorkflowDefaultInputs validates invalid choice value not in options' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " deploymentType:", + " type: choice", + " options:", + " - Development", + " - Staging", + " - Production", + " default: Development", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with invalid choice value + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "deploymentType"; "value" = "Testing" } + ) + } + + # Apply the defaults - should throw validation error + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + Should -Throw "*not a valid choice*" + } + + It 'ApplyWorkflowDefaultInputs handles inputs without existing default' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with input without default + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " description: My Input", + " required: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = "test-value" } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the default was added + $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') + $defaultLine | Should -Not -BeNullOrEmpty + $defaultLine.content -join '' | Should -Be "default: 'test-value'" + } + + It 'ApplyWorkflowDefaultInputs handles different value types' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " boolInput:", + " type: boolean", + " default: false", + " stringInput:", + " type: string", + " default: ''", + " numberInput:", + " type: number", + " default: 0", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with different value types + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "boolInput"; "value" = $true }, + @{ "name" = "stringInput"; "value" = "test" }, + @{ "name" = "numberInput"; "value" = 42 } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify the defaults were applied with correct types + $yaml.Get('on:/workflow_dispatch:/inputs:/boolInput:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/stringInput:/default:').content -join '' | Should -Be "default: 'test'" + $yaml.Get('on:/workflow_dispatch:/inputs:/numberInput:/default:').content -join '' | Should -Be 'default: 42' + } + + It 'ApplyWorkflowDefaultInputs validates boolean type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with boolean input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " boolInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with wrong type (string instead of boolean) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "boolInput"; "value" = "not a boolean" } + ) + } + + # Apply the defaults - should throw validation error + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + Should -Throw "*Expected boolean value*" + } + + It 'ApplyWorkflowDefaultInputs validates number type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with number input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " numberInput:", + " type: number", + " default: 0", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with wrong type (string instead of number) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "numberInput"; "value" = "not a number" } + ) + } + + # Apply the defaults - should throw validation error + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + Should -Throw "*Expected number value*" + } + + It 'ApplyWorkflowDefaultInputs validates string type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with string input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " stringInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with wrong type (boolean instead of string) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "stringInput"; "value" = $true } + ) + } + + # Apply the defaults - should throw validation error + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + Should -Throw "*Expected string value*" + } + + It 'ApplyWorkflowDefaultInputs validates choice type' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " choiceInput:", + " type: choice", + " options:", + " - option1", + " - option2", + " default: option1", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with correct type (string for choice) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "choiceInput"; "value" = "option2" } + ) + } + + # Apply the defaults - should succeed + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/choiceInput:/default:').content -join '' | Should -Be "default: 'option2'" + } + + It 'ApplyWorkflowDefaultInputs validates choice value is in available options' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " choiceInput:", + " type: choice", + " options:", + " - option1", + " - option2", + " - option3", + " default: option1", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with invalid choice value + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "choiceInput"; "value" = "invalidOption" } + ) + } + + # Apply the defaults - should throw validation error + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + Should -Throw "*not a valid choice*" + } + + It 'ApplyWorkflowDefaultInputs validates choice value with case-sensitive matching' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input using mixed case options + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " releaseTypeInput:", + " type: choice", + " options:", + " - Release", + " - Prerelease", + " - Draft", + " default: Release", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Test 1: Exact case match should succeed + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "releaseTypeInput"; "value" = "Prerelease" } + ) + } + + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/releaseTypeInput:/default:').content -join '' | Should -Be "default: 'Prerelease'" + + # Test 2: Wrong case should fail with case-sensitive error message + $yaml2 = [Yaml]::new($yamlContent) + $repoSettings2 = @{ + "workflowDefaultInputs" = @( + @{ "name" = "releaseTypeInput"; "value" = "prerelease" } + ) + } + + { ApplyWorkflowDefaultInputs -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | + Should -Throw "*case-sensitive match required*" + + # Test 3: Uppercase version should also fail + $yaml3 = [Yaml]::new($yamlContent) + $repoSettings3 = @{ + "workflowDefaultInputs" = @( + @{ "name" = "releaseTypeInput"; "value" = "PRERELEASE" } + ) + } + + { ApplyWorkflowDefaultInputs -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | + Should -Throw "*case-sensitive match required*" + } + + It 'ApplyWorkflowDefaultInputs handles inputs without type specification' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML without type (defaults to string) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " noTypeInput:", + " description: Input without type", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with string value (should work without warning) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "noTypeInput"; "value" = "string value" } + ) + } + + # Apply the defaults - should succeed + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" + } + + It 'ApplyWorkflowDefaultInputs escapes single quotes in string values' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with string input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " nameInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with string value containing single quote + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "nameInput"; "value" = "O'Brien" } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify single quote is escaped per YAML spec (doubled) + $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" + } +} From 29b1cd51ed39b2f5cb41c76b6dec5a0e872ec2bf Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Mon, 17 Nov 2025 23:02:18 +0100 Subject: [PATCH 15/30] Add tests for merging workflowDefaultInputs with duplicate entries --- Tests/CheckForUpdates.Action.Test.ps1 | 41 ++++++++++++++++++ Tests/ReadSettings.Test.ps1 | 60 +++++++++++++++++++++++++++ 2 files changed, 101 insertions(+) diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index e1306b211..87862c1e6 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -1026,4 +1026,45 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Verify single quote is escaped per YAML spec (doubled) $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" } + + It 'ApplyWorkflowDefaultInputs applies last value when multiple entries have same input name' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " input1:", + " type: string", + " default: ''", + " input2:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with duplicate entries for input1 - simulating merged conditional settings + # This can happen when multiple conditionalSettings blocks both match and both define the same input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = "first-value" }, + @{ "name" = "input2"; "value" = $false }, + @{ "name" = "input1"; "value" = "second-value" }, # Duplicate input1 + @{ "name" = "input1"; "value" = "final-value" } # Another duplicate input1 + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify "last wins" - the final value for input1 should be applied + $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be "default: 'final-value'" + $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be 'default: false' + } } diff --git a/Tests/ReadSettings.Test.ps1 b/Tests/ReadSettings.Test.ps1 index 18dc52beb..8443a5fb1 100644 --- a/Tests/ReadSettings.Test.ps1 +++ b/Tests/ReadSettings.Test.ps1 @@ -459,5 +459,65 @@ InModuleScope ReadSettings { # Allows testing of private functions # overwriteSettings should never be added to the destination object $dst.PSObject.Properties.Name | Should -Not -Contain 'overwriteSettings' } + + It 'Multiple conditionalSettings with same array setting are merged (all entries kept)' { + Mock Write-Host { } + Mock Out-Host { } + + Push-Location + $tempName = Join-Path ([System.IO.Path]::GetTempPath()) ([Guid]::NewGuid().ToString()) + $githubFolder = Join-Path $tempName ".github" + New-Item $githubFolder -ItemType Directory | Out-Null + + # Create conditional settings with two blocks that both match and both have workflowDefaultInputs + $conditionalSettings = [ordered]@{ + "conditionalSettings" = @( + @{ + "branches" = @( 'main' ) + "settings" = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = "value1" } + ) + } + } + @{ + "branches" = @( 'main' ) + "settings" = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = "value2" }, + @{ "name" = "input2"; "value" = "value3" } + ) + } + } + ) + } + $ENV:ALGoOrgSettings = '' + $ENV:ALGoRepoSettings = $conditionalSettings | ConvertTo-Json -Depth 99 + + # Both conditional blocks match branch 'main', so both should be applied + $settings = ReadSettings -baseFolder $tempName -project '' -repoName 'repo' -workflowName 'Workflow' -branchName 'main' -userName 'user' + + # Verify array was merged - should have 3 entries total + $settings.workflowDefaultInputs | Should -Not -BeNullOrEmpty + $settings.workflowDefaultInputs.Count | Should -Be 3 + + # First entry from first conditional block + $settings.workflowDefaultInputs[0].name | Should -Be 'input1' + $settings.workflowDefaultInputs[0].value | Should -Be 'value1' + + # Second entry from second conditional block + $settings.workflowDefaultInputs[1].name | Should -Be 'input1' + $settings.workflowDefaultInputs[1].value | Should -Be 'value2' + + # Third entry from second conditional block + $settings.workflowDefaultInputs[2].name | Should -Be 'input2' + $settings.workflowDefaultInputs[2].value | Should -Be 'value3' + + $ENV:ALGoRepoSettings = '' + + # Clean up + Pop-Location + Remove-Item -Path $tempName -Recurse -Force + } } } From f1cee93750ed3c2d3aa7b541d975419764515464 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Mon, 17 Nov 2025 23:15:42 +0100 Subject: [PATCH 16/30] Reorganize and clarify Workflow Name Sanitization section in settings.md --- Scenarios/settings.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Scenarios/settings.md b/Scenarios/settings.md index fa71ecf0a..99a6cca55 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -184,13 +184,7 @@ to your [project settings file](#where-are-the-settings-located) will ensure tha **Note:** You can use `workflowDefaultInputs` within conditional settings to apply workflow input defaults only when certain conditions are met. For example, you could set different default values for specific workflows or branches. - - -### Workflow Name Sanitization - -When matching workflow names for conditional settings, AL-Go sanitizes the actual workflow name before comparison. Sanitization removes invalid filename characters such as leading spaces, quotes, colons, slashes, and other special characters. For example, a workflow named `" CI/CD"` would be sanitized to `"CICD"` for matching purposes. - -You could imagine that you could have and organizational settings variable containing: +You could imagine that you could have an organizational settings variable containing: ```json "ConditionalSettings": [ @@ -213,6 +207,12 @@ Which will ensure that for all repositories named `bcsamples-*` in this organiza > [!NOTE] > You can have conditional settings on any level and all conditional settings which has all conditions met will be applied in the order of settings file + appearance. + + +### Workflow Name Sanitization + +When matching workflow names for conditional settings, AL-Go sanitizes the actual workflow name before comparison. Sanitization removes invalid filename characters such as leading spaces, quotes, colons, slashes, and other special characters. For example, a workflow named `" CI/CD"` would be sanitized to `"CICD"` for matching purposes. + # Expert level From 2511b24e536e7bbab24c44792edd1812ef8dbc91 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Tue, 18 Nov 2025 00:08:04 +0100 Subject: [PATCH 17/30] Set default values for workflow inputs in RELEASENOTES.md and update usage instructions in settings.md --- RELEASENOTES.md | 34 ++++++++++++++++++++++------------ Scenarios/settings.md | 2 +- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 67a36e1cc..d04659eb9 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -10,24 +10,34 @@ Example configuration: ```json { "workflowDefaultInputs": [ + { "name": "directCommit", "value": true }, + { "name": "useGhTokenWorkflow", "value": true } + ] +} +``` + +This setting can be used on its own in repository settings to apply defaults to all workflows with matching input names. Alternatively, you can use it within [conditional settings](https://aka.ms/algosettings#conditional-settings) to apply defaults only to specific workflows, branches, or other conditions. + +Example using conditional settings to target specific workflows: + +```json +{ + "conditionalSettings": [ { - "workflow": "Create Release", - "defaults": [ - { "name": "directCommit", "value": true }, - { "name": "useGhTokenWorkflow", "value": true }, - { "name": "updateVersionNumber", "value": "+0.1" } - ] - }, - { - "workflow": "Update AL-Go System Files", - "defaults": [ - { "name": "directCommit", "value": true } - ] + "workflows": ["Create Release"], + "settings": { + "workflowDefaultInputs": [ + { "name": "directCommit", "value": true }, + { "name": "releaseType", "value": "Prerelease" } + ] + } } ] } ``` +**Important:** When multiple conditional settings blocks match and both define `workflowDefaultInputs`, the arrays are merged (all entries are kept). When the defaults are applied to workflows, the last matching entry for each input name wins. + Read more at [workflowDefaultInputs](https://aka.ms/algosettings#workflowDefaultInputs). ### Issues diff --git a/Scenarios/settings.md b/Scenarios/settings.md index 99a6cca55..5fe3bba5b 100644 --- a/Scenarios/settings.md +++ b/Scenarios/settings.md @@ -83,7 +83,7 @@ The repository settings are only read from the repository settings file (.github | useGitSubmodules | If your repository is using Git Submodules, you can set the `useGitSubmodules` setting to `"true"` or `"recursive"` in order to use these submodules during build workflows. If `useGitSubmodules` is not set, git submodules are not initialized. If the submodules reside in private repositories, you need to define a `gitSubmodulesToken` secret. Read [this](https://aka.ms/algosecrets#gitSubmodulesToken) for more information. | | commitOptions | If you want more control over how AL-Go creates pull requests or commits changes to the repository you can define `commitOptions`. It is a structure defining how you want AL-Go to handle automated commits or pull requests coming from AL-Go (e.g. for Update AL-Go System Files). The structure contains the following properties:
**messageSuffix** = A string you want to append to the end of commits/pull requests created by AL-Go. This can be useful if you are using the Azure Boards integration (or similar integration) to link commits to work items.
`createPullRequest` : A boolean defining whether AL-Go should create a pull request or attempt to push directly in the branch.
**pullRequestAutoMerge** = A boolean defining whether you want AL-Go pull requests to be set to auto-complete. This will auto-complete the pull requests once all checks are green and all required reviewers have approved.
**pullRequestMergeMethod** = A string defining which merge method to use when auto-merging pull requests. Valid values are "merge" and "squash". Default is "squash".
**pullRequestLabels** = A list of labels to add to the pull request. The labels need to be created in the repository before they can be applied.
If you want different behavior in different AL-Go workflows you can add the `commitOptions` setting to your [workflow-specific settings files](https://github.com/microsoft/AL-Go/blob/main/Scenarios/settings.md#where-are-the-settings-located). | | incrementalBuilds | A structure defining how you want AL-Go to handle incremental builds. When using incremental builds for a build, AL-Go will look for the latest successful CI/CD build, newer than the defined `retentionDays` and only rebuild projects or apps (based on `mode`) which needs to be rebuilt. The structure supports the following properties:
**onPush** = Determines whether incremental builds is enabled in CI/CD triggered by a merge/push event. Default is **false**.
**onPull_Request** = Determines whether incremental builds is enabled in Pull Requests. Default is **true**.
**onSchedule** = Determines whether incremental builds is enabled in CI/CD when running on a schedule. Default is **false**.
**retentionDays** = Number of days a successful build is good (and can be used for incremental builds). Default is **30**.
**mode** = Specifies the mode for incremental builds. Currently, two values are supported. Use **modifiedProjects** when you want to rebuild all apps in all modified projects and depending projects or **modifiedApps** if you want to rebuild modified apps and all apps with dependencies to this app.
**NOTE:** when running incremental builds, it is recommended to also set `workflowConcurrency` for the CI/CD workflow, as defined [here](https://aka.ms/algosettings#workflowConcurrency). | -| workflowDefaultInputs | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  • The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  • For choice inputs, the value must be one of the options declared in the workflow
  • Choice validation is case-sensitive
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to all workflows that have matching input names.
**Usage:** This setting can be used on its own in repository settings to apply defaults to all workflows with matching input names. Alternatively, you can use it within [conditional settings](#conditional-settings) to apply defaults only to specific workflows, branches, or other conditions.
Example:
`"workflowDefaultInputs": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
`]` | +| workflowDefaultInputs | An array of workflow input default values. This setting allows you to configure default values for workflow_dispatch inputs, making it easier to run workflows manually with consistent settings. Each entry should contain:
  **name** = The name of the workflow input
  **value** = The default value (can be string, boolean, or number)
**Important validation rules:**
  • The value type must match the input type defined in the workflow YAML file (boolean, number, string, or choice)
  • For choice inputs, the value must be one of the options declared in the workflow
  • Choice validation is case-sensitive
Type and choice validation is performed when running the "Update AL-Go System Files" workflow to prevent configuration errors.
When you run the "Update AL-Go System Files" workflow, these default values will be applied to all workflows that have matching input names.
**Usage:** This setting can be used on its own in repository settings to apply defaults to all workflows with matching input names. Alternatively, you can use it within [conditional settings](#conditional-settings) to apply defaults only to specific workflows, branches, or other conditions.
**Important:** When multiple conditional settings blocks match and both define `workflowDefaultInputs`, the arrays are merged (all entries are kept). When the defaults are applied to workflows, the last matching entry for each input name wins.
**Example:**
`"workflowDefaultInputs": [`
` { "name": "directCommit", "value": true },`
` { "name": "useGhTokenWorkflow", "value": true },`
` { "name": "updateVersionNumber", "value": "+0.1" }`
`]` | From 474a6fdabbae59bf160940758ed8b25582fe86de Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Tue, 18 Nov 2025 10:00:59 +0100 Subject: [PATCH 18/30] Remove duplicate inputs handling from ApplyWorkflowDefaultInputs function (bad merge) --- .../CheckForUpdates.HelperFunctions.ps1 | 28 ------------------- 1 file changed, 28 deletions(-) diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index 17907bb2e..c47afab2e 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -521,34 +521,6 @@ function ApplyWorkflowDefaultInputs { } } - # Remove hidden inputs from the inputs section - if ($inputsToHide.Count -gt 0) { - # Collect all inputs to remove with their positions - $inputsToRemove = @() - foreach ($entry in $inputsToHide.GetEnumerator()) { - $name = $entry.Key - $start = 0 - $count = 0 - # Find the input including all its content (type, default, etc.) - # Use the name with colon to match the full input section - if ($inputs.Find("$($name):", [ref] $start, [ref] $count)) { - $inputsToRemove += [pscustomobject]@{ - Name = $name - Start = $start - Count = $count - } - } - else { - OutputWarning "Workflow '$workflowName': Unable to hide input '$name' because it was not found in the workflow file." - } - } - - # Remove inputs in reverse order to maintain correct indices - foreach ($item in $inputsToRemove | Sort-Object -Property Start -Descending) { - $inputs.Remove($item.Start, $item.Count) - } - } - # Update the workflow_dispatch section with modified inputs $workflowDispatch.Replace('inputs:/', $inputs.content) From 627c802bd99f298f0841cc88a44c1b95720c9471 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Tue, 18 Nov 2025 10:01:13 +0100 Subject: [PATCH 19/30] Hide default values from workflow_dispatch inputs in ApplyWorkflowDefaultInputs tests --- Tests/CheckForUpdates.Action.Test.ps1 | 123 ++++++++++++++------------ 1 file changed, 66 insertions(+), 57 deletions(-) diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index 6f9a70798..341881f61 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -1195,18 +1195,13 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with hide flag $repoSettings = @{ "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "visibleInput"; "value" = "test" }, - @{ "name" = "hiddenInput"; "value" = $true; "hide" = $true } - ) - } + @{ "name" = "visibleInput"; "value" = "test" }, + @{ "name" = "hiddenInput"; "value" = $true; "hide" = $true } ) } # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Boolean" # Verify the hidden input was removed from inputs $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') @@ -1248,17 +1243,12 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with hide flag $repoSettings = @{ "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "versionInput"; "value" = "+0.1"; "hide" = $true } - ) - } + @{ "name" = "versionInput"; "value" = "+0.1"; "hide" = $true } ) } # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide String" # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') @@ -1296,17 +1286,12 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with hide flag $repoSettings = @{ "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "countInput"; "value" = 10; "hide" = $true } - ) - } + @{ "name" = "countInput"; "value" = 10; "hide" = $true } ) } # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Number" # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') @@ -1343,17 +1328,12 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with hide flag $repoSettings = @{ "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "runTests"; "value" = $true; "hide" = $true } - ) - } + @{ "name" = "runTests"; "value" = $true; "hide" = $true } ) } # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide If Condition" # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') @@ -1403,17 +1383,12 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with hide flag for the branch input $repoSettings = @{ "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "branch"; "value" = "production"; "hide" = $true } - ) - } + @{ "name" = "branch"; "value" = "production"; "hide" = $true } ) } # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Job Output" # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') @@ -1467,17 +1442,12 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with hide flag for the "output" input $repoSettings = @{ "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "output"; "value" = "hidden"; "hide" = $true } - ) - } + @{ "name" = "output"; "value" = "hidden"; "hide" = $true } ) } # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Input Named Output" # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') @@ -1531,17 +1501,12 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with hide flag for the "outputs" input $repoSettings = @{ "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "outputs"; "value" = "hidden-value"; "hide" = $true } - ) - } + @{ "name" = "outputs"; "value" = "hidden-value"; "hide" = $true } ) } # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Input Named Outputs" # Verify the hidden input was removed $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') @@ -1598,17 +1563,12 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Create settings with hide flag for the "outputs" input $repoSettings = @{ "workflowDefaultInputs" = @( - @{ - "workflow" = "Test Workflow" - "defaults" = @( - @{ "name" = "outputs"; "value" = "hidden-input-value"; "hide" = $true } - ) - } + @{ "name" = "outputs"; "value" = "hidden-input-value"; "hide" = $true } ) } # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Job Named Inputs" # Verify the hidden input was removed $inputsSection = $yaml.Get('on:/workflow_dispatch:/inputs:/') @@ -1625,4 +1585,53 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $content | Should -Match "echo Input is 'hidden-input-value'" $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" } + + It 'ApplyWorkflowDefaultInputs silently skips hiding non-existent input' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with only one input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " existingInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.existingInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings trying to hide an input that doesn't exist + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "existingInput"; "value" = "test-value" }, + @{ "name" = "nonExistentInput"; "value" = "hidden-value"; "hide" = $true } + ) + } + + # Mock OutputWarning to verify no warning is issued + Mock OutputWarning { } + + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Non-Existent" } | Should -Not -Throw + + # Verify no warning was issued + Assert-MockCalled OutputWarning -Times 0 + + # Verify the existing input was updated but not hidden + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('existingInput:', [ref] $null, [ref] $null) | Should -Be $true + $yaml.Get('on:/workflow_dispatch:/inputs:/existingInput:/default:').content -join '' | Should -Be "default: 'test-value'" + + # Verify the workflow content was not affected by the non-existent input + $content = $yaml.content -join "`n" + $content | Should -Match "`\$\{\{ inputs\.existingInput \}\}" + } } From a5c92b501203baeac3aa7d632992d3a818ff80e0 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Tue, 18 Nov 2025 10:21:04 +0100 Subject: [PATCH 20/30] Add Context grouping to tests --- Tests/CheckForUpdates.Action.Test.ps1 | 2158 +++++++++++++------------ 1 file changed, 1083 insertions(+), 1075 deletions(-) diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index 341881f61..125a6e496 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -368,1270 +368,1278 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $modifiedContent."`$schema" | Should -Be "someSchema" } - It 'ApplyWorkflowDefaultInputs applies default values to workflow inputs' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with workflow_dispatch inputs - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " directCommit:", - " description: Direct Commit?", - " type: boolean", - " default: false", - " useGhTokenWorkflow:", - " description: Use GhTokenWorkflow?", - " type: boolean", - " default: false", - " updateVersionNumber:", - " description: Version number", - " required: false", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest", - " steps:", - " - run: echo test" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with workflow input defaults - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "directCommit"; "value" = $true }, - @{ "name" = "useGhTokenWorkflow"; "value" = $true }, - @{ "name" = "updateVersionNumber"; "value" = "+0.1" } + Context 'ApplyWorkflowDefaultInputs' { + + It 'applies default values to workflow inputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with workflow_dispatch inputs + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " directCommit:", + " description: Direct Commit?", + " type: boolean", + " default: false", + " useGhTokenWorkflow:", + " description: Use GhTokenWorkflow?", + " type: boolean", + " default: false", + " updateVersionNumber:", + " description: Version number", + " required: false", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - run: echo test" ) - } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + $yaml = [Yaml]::new($yamlContent) - # Verify the defaults were applied - $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: true' - $yaml.Get('on:/workflow_dispatch:/inputs:/useGhTokenWorkflow:/default:').content -join '' | Should -Be 'default: true' - $yaml.Get('on:/workflow_dispatch:/inputs:/updateVersionNumber:/default:').content -join '' | Should -Be "default: '+0.1'" - } + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "directCommit"; "value" = $true }, + @{ "name" = "useGhTokenWorkflow"; "value" = $true }, + @{ "name" = "updateVersionNumber"; "value" = "+0.1" } + ) + } - It 'ApplyWorkflowDefaultInputs handles empty workflowDefaultInputs array' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " myInput:", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - $yaml = [Yaml]::new($yamlContent) - - # Create settings with empty workflowDefaultInputs array - $repoSettings = @{ - "workflowDefaultInputs" = @() + # Verify the defaults were applied + $yaml.Get('on:/workflow_dispatch:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/useGhTokenWorkflow:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/updateVersionNumber:/default:').content -join '' | Should -Be "default: '+0.1'" } - # Apply the defaults - should not throw and should not modify workflow - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:').content -join '' | Should -Be 'default: false' - } - - It 'ApplyWorkflowDefaultInputs handles workflows without workflow_dispatch' { - . (Join-Path $scriptRoot "yamlclass.ps1") + It 'handles empty workflowDefaultInputs array' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - # Create a test workflow YAML without workflow_dispatch - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " push:", - " branches: [ main ]", - "jobs:", - " test:", - " runs-on: ubuntu-latest", - " steps:", - " - run: echo test" - ) + $yaml = [Yaml]::new($yamlContent) - $yaml = [Yaml]::new($yamlContent) + # Create settings with empty workflowDefaultInputs array + $repoSettings = @{ + "workflowDefaultInputs" = @() + } - # Create settings with workflow input defaults - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "directCommit"; "value" = $true } - ) + # Apply the defaults - should not throw and should not modify workflow + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:').content -join '' | Should -Be 'default: false' } - # Apply the defaults - should not throw - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - } + It 'handles workflows without workflow_dispatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML without workflow_dispatch + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " push:", + " branches: [ main ]", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - run: echo test" + ) - It 'ApplyWorkflowDefaultInputs handles workflow_dispatch without inputs section' { - . (Join-Path $scriptRoot "yamlclass.ps1") + $yaml = [Yaml]::new($yamlContent) - # Create a test workflow YAML with workflow_dispatch but no inputs - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "directCommit"; "value" = $true } + ) + } - $yaml = [Yaml]::new($yamlContent) + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + } - # Create settings with workflow input defaults - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "someInput"; "value" = $true } + It 'handles workflow_dispatch without inputs section' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with workflow_dispatch but no inputs + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + "jobs:", + " test:", + " runs-on: ubuntu-latest" ) - } - # Apply the defaults - should not throw - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs applies multiple defaults to same workflow' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "someInput"; "value" = $true } + ) + } - # Create a test workflow YAML with multiple inputs - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " input1:", - " type: boolean", - " default: false", - " input2:", - " type: number", - " default: 0", - " input3:", - " type: string", - " default: ''", - " input4:", - " type: choice", - " options:", - " - optionA", - " - optionB", - " default: optionA", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + } - # Create settings with multiple defaults - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "input1"; "value" = $true }, - @{ "name" = "input2"; "value" = 5 }, - @{ "name" = "input3"; "value" = "test-value" }, - @{ "name" = "input4"; "value" = "optionB" } + It 'applies multiple defaults to same workflow' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with multiple inputs + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " input1:", + " type: boolean", + " default: false", + " input2:", + " type: number", + " default: 0", + " input3:", + " type: string", + " default: ''", + " input4:", + " type: choice", + " options:", + " - optionA", + " - optionB", + " default: optionA", + "jobs:", + " test:", + " runs-on: ubuntu-latest" ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with multiple defaults + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = $true }, + @{ "name" = "input2"; "value" = 5 }, + @{ "name" = "input3"; "value" = "test-value" }, + @{ "name" = "input4"; "value" = "optionB" } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify all defaults were applied + $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be 'default: 5' + $yaml.Get('on:/workflow_dispatch:/inputs:/input3:/default:').content -join '' | Should -Be "default: 'test-value'" + $yaml.Get('on:/workflow_dispatch:/inputs:/input4:/default:').content -join '' | Should -Be "default: 'optionB'" } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + It 'inserts default line when missing' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with input without default line (only description) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " description: 'My input without default'", + " type: string", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - # Verify all defaults were applied - $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be 'default: true' - $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be 'default: 5' - $yaml.Get('on:/workflow_dispatch:/inputs:/input3:/default:').content -join '' | Should -Be "default: 'test-value'" - $yaml.Get('on:/workflow_dispatch:/inputs:/input4:/default:').content -join '' | Should -Be "default: 'optionB'" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs inserts default line when missing' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Create settings with default value + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = "new-default" } + ) + } - # Create a test workflow YAML with input without default line (only description) - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " myInput:", - " description: 'My input without default'", - " type: string", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - $yaml = [Yaml]::new($yamlContent) + # Verify default line was inserted + $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') + $defaultLine | Should -Not -BeNullOrEmpty + $defaultLine.content -join '' | Should -Be "default: 'new-default'" + } - # Create settings with default value - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "myInput"; "value" = "new-default" } + It 'is case-insensitive for input names' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with specific casing + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " MyInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" ) - } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + $yaml = [Yaml]::new($yamlContent) - # Verify default line was inserted - $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') - $defaultLine | Should -Not -BeNullOrEmpty - $defaultLine.content -join '' | Should -Be "default: 'new-default'" - } + # Create settings with different casing + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = $true } + ) + } - It 'ApplyWorkflowDefaultInputs is case-insensitive for input names' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - # Create a test workflow YAML with specific casing - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " MyInput:", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with different casing - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "myInput"; "value" = $true } - ) + # Verify default WAS applied despite case difference (case-insensitive matching) + $yaml.Get('on:/workflow_dispatch:/inputs:/MyInput:/default:').content -join '' | Should -Be 'default: true' } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - - # Verify default WAS applied despite case difference (case-insensitive matching) - $yaml.Get('on:/workflow_dispatch:/inputs:/MyInput:/default:').content -join '' | Should -Be 'default: true' - } + It 'ignores defaults for non-existent inputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " existingInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - It 'ApplyWorkflowDefaultInputs ignores defaults for non-existent inputs' { - . (Join-Path $scriptRoot "yamlclass.ps1") + $yaml = [Yaml]::new($yamlContent) - # Create a test workflow YAML - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " existingInput:", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Create settings with both existing and non-existent input names + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "existingInput"; "value" = $true }, + @{ "name" = "nonExistentInput"; "value" = "ignored" }, + @{ "name" = "anotherMissingInput"; "value" = 42 } + ) + } - $yaml = [Yaml]::new($yamlContent) + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - # Create settings with both existing and non-existent input names - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "existingInput"; "value" = $true }, - @{ "name" = "nonExistentInput"; "value" = "ignored" }, - @{ "name" = "anotherMissingInput"; "value" = 42 } - ) + # Verify only the existing input was modified + $yaml.Get('on:/workflow_dispatch:/inputs:/existingInput:/default:').content -join '' | Should -Be 'default: true' } - # Apply the defaults - should not throw - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - - # Verify only the existing input was modified - $yaml.Get('on:/workflow_dispatch:/inputs:/existingInput:/default:').content -join '' | Should -Be 'default: true' - } + It 'handles special YAML characters in string values' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " input1:", + " type: string", + " default: ''", + " input2:", + " type: string", + " default: ''", + " input3:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - It 'ApplyWorkflowDefaultInputs handles special YAML characters in string values' { - . (Join-Path $scriptRoot "yamlclass.ps1") + $yaml = [Yaml]::new($yamlContent) - # Create a test workflow YAML - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " input1:", - " type: string", - " default: ''", - " input2:", - " type: string", - " default: ''", - " input3:", - " type: string", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Create settings with special YAML characters + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = "value: with colon" }, + @{ "name" = "input2"; "value" = "value # with comment" }, + @{ "name" = "input3"; "value" = "value with 'quotes' inside" } + ) + } - $yaml = [Yaml]::new($yamlContent) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - # Create settings with special YAML characters - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "input1"; "value" = "value: with colon" }, - @{ "name" = "input2"; "value" = "value # with comment" }, - @{ "name" = "input3"; "value" = "value with 'quotes' inside" } - ) + # Verify values are properly quoted and escaped + $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be "default: 'value: with colon'" + $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be "default: 'value # with comment'" + $yaml.Get('on:/workflow_dispatch:/inputs:/input3:/default:').content -join '' | Should -Be "default: 'value with ''quotes'' inside'" } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + It 'handles environment input type' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with environment type + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " environmentName:", + " description: Environment to deploy to", + " type: environment", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - # Verify values are properly quoted and escaped - $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be "default: 'value: with colon'" - $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be "default: 'value # with comment'" - $yaml.Get('on:/workflow_dispatch:/inputs:/input3:/default:').content -join '' | Should -Be "default: 'value with ''quotes'' inside'" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs handles environment input type' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Create settings with environment value (should be treated as string) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "environmentName"; "value" = "production" } + ) + } - # Create a test workflow YAML with environment type - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " environmentName:", - " description: Environment to deploy to", - " type: environment", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - $yaml = [Yaml]::new($yamlContent) - - # Create settings with environment value (should be treated as string) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "environmentName"; "value" = "production" } - ) + # Verify environment value is set as string + $yaml.Get('on:/workflow_dispatch:/inputs:/environmentName:/default:').content -join '' | Should -Be "default: 'production'" } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - - # Verify environment value is set as string - $yaml.Get('on:/workflow_dispatch:/inputs:/environmentName:/default:').content -join '' | Should -Be "default: 'production'" - } + It 'validates invalid choice value not in options' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " deploymentType:", + " type: choice", + " options:", + " - Development", + " - Staging", + " - Production", + " default: Development", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - It 'ApplyWorkflowDefaultInputs validates invalid choice value not in options' { - . (Join-Path $scriptRoot "yamlclass.ps1") + $yaml = [Yaml]::new($yamlContent) - # Create a test workflow YAML with choice input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " deploymentType:", - " type: choice", - " options:", - " - Development", - " - Staging", - " - Production", - " default: Development", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Create settings with invalid choice value + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "deploymentType"; "value" = "Testing" } + ) + } - $yaml = [Yaml]::new($yamlContent) + # Apply the defaults - should throw validation error + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + Should -Throw "*not a valid choice*" + } - # Create settings with invalid choice value - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "deploymentType"; "value" = "Testing" } + It 'handles inputs without existing default' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with input without default + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " description: My Input", + " required: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" ) - } - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | - Should -Throw "*not a valid choice*" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs handles inputs without existing default' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Create settings with workflow input defaults + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = "test-value" } + ) + } - # Create a test workflow YAML with input without default - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " myInput:", - " description: My Input", - " required: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - $yaml = [Yaml]::new($yamlContent) + # Verify the default was added + $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') + $defaultLine | Should -Not -BeNullOrEmpty + $defaultLine.content -join '' | Should -Be "default: 'test-value'" + } - # Create settings with workflow input defaults - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "myInput"; "value" = "test-value" } + It 'handles different value types' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " boolInput:", + " type: boolean", + " default: false", + " stringInput:", + " type: string", + " default: ''", + " numberInput:", + " type: number", + " default: 0", + "jobs:", + " test:", + " runs-on: ubuntu-latest" ) - } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + $yaml = [Yaml]::new($yamlContent) - # Verify the default was added - $defaultLine = $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:') - $defaultLine | Should -Not -BeNullOrEmpty - $defaultLine.content -join '' | Should -Be "default: 'test-value'" - } + # Create settings with different value types + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "boolInput"; "value" = $true }, + @{ "name" = "stringInput"; "value" = "test" }, + @{ "name" = "numberInput"; "value" = 42 } + ) + } - It 'ApplyWorkflowDefaultInputs handles different value types' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - # Create a test workflow YAML - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " boolInput:", - " type: boolean", - " default: false", - " stringInput:", - " type: string", - " default: ''", - " numberInput:", - " type: number", - " default: 0", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) - - $yaml = [Yaml]::new($yamlContent) - - # Create settings with different value types - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "boolInput"; "value" = $true }, - @{ "name" = "stringInput"; "value" = "test" }, - @{ "name" = "numberInput"; "value" = 42 } - ) + # Verify the defaults were applied with correct types + $yaml.Get('on:/workflow_dispatch:/inputs:/boolInput:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_dispatch:/inputs:/stringInput:/default:').content -join '' | Should -Be "default: 'test'" + $yaml.Get('on:/workflow_dispatch:/inputs:/numberInput:/default:').content -join '' | Should -Be 'default: 42' } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - - # Verify the defaults were applied with correct types - $yaml.Get('on:/workflow_dispatch:/inputs:/boolInput:/default:').content -join '' | Should -Be 'default: true' - $yaml.Get('on:/workflow_dispatch:/inputs:/stringInput:/default:').content -join '' | Should -Be "default: 'test'" - $yaml.Get('on:/workflow_dispatch:/inputs:/numberInput:/default:').content -join '' | Should -Be 'default: 42' - } - - It 'ApplyWorkflowDefaultInputs validates boolean type mismatch' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with boolean input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " boolInput:", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + It 'validates boolean type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with boolean input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " boolInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) - # Create settings with wrong type (string instead of boolean) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "boolInput"; "value" = "not a boolean" } - ) - } + # Create settings with wrong type (string instead of boolean) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "boolInput"; "value" = "not a boolean" } + ) + } - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + # Apply the defaults - should throw validation error + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected boolean value*" - } - - It 'ApplyWorkflowDefaultInputs validates number type mismatch' { - . (Join-Path $scriptRoot "yamlclass.ps1") + } - # Create a test workflow YAML with number input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " numberInput:", - " type: number", - " default: 0", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + It 'validates number type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with number input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " numberInput:", + " type: number", + " default: 0", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) - # Create settings with wrong type (string instead of number) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "numberInput"; "value" = "not a number" } - ) - } + # Create settings with wrong type (string instead of number) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "numberInput"; "value" = "not a number" } + ) + } - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + # Apply the defaults - should throw validation error + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected number value*" - } - - It 'ApplyWorkflowDefaultInputs validates string type mismatch' { - . (Join-Path $scriptRoot "yamlclass.ps1") + } - # Create a test workflow YAML with string input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " stringInput:", - " type: string", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + It 'validates string type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with string input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " stringInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) - # Create settings with wrong type (boolean instead of string) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "stringInput"; "value" = $true } - ) - } + # Create settings with wrong type (boolean instead of string) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "stringInput"; "value" = $true } + ) + } - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + # Apply the defaults - should throw validation error + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected string value*" - } + } - It 'ApplyWorkflowDefaultInputs validates choice type' { - . (Join-Path $scriptRoot "yamlclass.ps1") + It 'validates choice type' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " choiceInput:", + " type: choice", + " options:", + " - option1", + " - option2", + " default: option1", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - # Create a test workflow YAML with choice input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " choiceInput:", - " type: choice", - " options:", - " - option1", - " - option2", - " default: option1", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + $yaml = [Yaml]::new($yamlContent) - $yaml = [Yaml]::new($yamlContent) + # Create settings with correct type (string for choice) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "choiceInput"; "value" = "option2" } + ) + } - # Create settings with correct type (string for choice) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "choiceInput"; "value" = "option2" } - ) + # Apply the defaults - should succeed + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/choiceInput:/default:').content -join '' | Should -Be "default: 'option2'" } - # Apply the defaults - should succeed - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - $yaml.Get('on:/workflow_dispatch:/inputs:/choiceInput:/default:').content -join '' | Should -Be "default: 'option2'" - } - - It 'ApplyWorkflowDefaultInputs validates choice value is in available options' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML with choice input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " choiceInput:", - " type: choice", - " options:", - " - option1", - " - option2", - " - option3", - " default: option1", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + It 'validates choice value is in available options' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " choiceInput:", + " type: choice", + " options:", + " - option1", + " - option2", + " - option3", + " default: option1", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) - # Create settings with invalid choice value - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "choiceInput"; "value" = "invalidOption" } - ) - } + # Create settings with invalid choice value + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "choiceInput"; "value" = "invalidOption" } + ) + } - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | + # Apply the defaults - should throw validation error + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*not a valid choice*" - } - - It 'ApplyWorkflowDefaultInputs validates choice value with case-sensitive matching' { - . (Join-Path $scriptRoot "yamlclass.ps1") + } - # Create a test workflow YAML with choice input using mixed case options - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " releaseTypeInput:", - " type: choice", - " options:", - " - Release", - " - Prerelease", - " - Draft", - " default: Release", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + It 'validates choice value with case-sensitive matching' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input using mixed case options + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " releaseTypeInput:", + " type: choice", + " options:", + " - Release", + " - Prerelease", + " - Draft", + " default: Release", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) - # Test 1: Exact case match should succeed - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "releaseTypeInput"; "value" = "Prerelease" } - ) - } + # Test 1: Exact case match should succeed + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "releaseTypeInput"; "value" = "Prerelease" } + ) + } - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - $yaml.Get('on:/workflow_dispatch:/inputs:/releaseTypeInput:/default:').content -join '' | Should -Be "default: 'Prerelease'" + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/releaseTypeInput:/default:').content -join '' | Should -Be "default: 'Prerelease'" - # Test 2: Wrong case should fail with case-sensitive error message - $yaml2 = [Yaml]::new($yamlContent) - $repoSettings2 = @{ - "workflowDefaultInputs" = @( - @{ "name" = "releaseTypeInput"; "value" = "prerelease" } - ) - } + # Test 2: Wrong case should fail with case-sensitive error message + $yaml2 = [Yaml]::new($yamlContent) + $repoSettings2 = @{ + "workflowDefaultInputs" = @( + @{ "name" = "releaseTypeInput"; "value" = "prerelease" } + ) + } - { ApplyWorkflowDefaultInputs -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | + { ApplyWorkflowDefaultInputs -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" - # Test 3: Uppercase version should also fail - $yaml3 = [Yaml]::new($yamlContent) - $repoSettings3 = @{ - "workflowDefaultInputs" = @( - @{ "name" = "releaseTypeInput"; "value" = "PRERELEASE" } - ) - } + # Test 3: Uppercase version should also fail + $yaml3 = [Yaml]::new($yamlContent) + $repoSettings3 = @{ + "workflowDefaultInputs" = @( + @{ "name" = "releaseTypeInput"; "value" = "PRERELEASE" } + ) + } - { ApplyWorkflowDefaultInputs -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | + { ApplyWorkflowDefaultInputs -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" - } + } - It 'ApplyWorkflowDefaultInputs handles inputs without type specification' { - . (Join-Path $scriptRoot "yamlclass.ps1") + It 'handles inputs without type specification' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML without type (defaults to string) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " noTypeInput:", + " description: Input without type", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - # Create a test workflow YAML without type (defaults to string) - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " noTypeInput:", - " description: Input without type", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + $yaml = [Yaml]::new($yamlContent) - $yaml = [Yaml]::new($yamlContent) + # Create settings with string value (should work without warning) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "noTypeInput"; "value" = "string value" } + ) + } - # Create settings with string value (should work without warning) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "noTypeInput"; "value" = "string value" } - ) + # Apply the defaults - should succeed + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.Get('on:/workflow_dispatch:/inputs:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" } - # Apply the defaults - should succeed - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - $yaml.Get('on:/workflow_dispatch:/inputs:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" - } + It 'escapes single quotes in string values' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with string input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " nameInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - It 'ApplyWorkflowDefaultInputs escapes single quotes in string values' { - . (Join-Path $scriptRoot "yamlclass.ps1") + $yaml = [Yaml]::new($yamlContent) - # Create a test workflow YAML with string input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " nameInput:", - " type: string", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + # Create settings with string value containing single quote + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "nameInput"; "value" = "O'Brien" } + ) + } - $yaml = [Yaml]::new($yamlContent) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - # Create settings with string value containing single quote - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "nameInput"; "value" = "O'Brien" } - ) + # Verify single quote is escaped per YAML spec (doubled) + $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - - # Verify single quote is escaped per YAML spec (doubled) - $yaml.Get('on:/workflow_dispatch:/inputs:/nameInput:/default:').content -join '' | Should -Be "default: 'O''Brien'" - } - - It 'ApplyWorkflowDefaultInputs applies last value when multiple entries have same input name' { - . (Join-Path $scriptRoot "yamlclass.ps1") - - # Create a test workflow YAML - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " input1:", - " type: string", - " default: ''", - " input2:", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" - ) + It 'applies last value when multiple entries have same input name' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " input1:", + " type: string", + " default: ''", + " input2:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) - $yaml = [Yaml]::new($yamlContent) + $yaml = [Yaml]::new($yamlContent) + + # Create settings with duplicate entries for input1 - simulating merged conditional settings + # This can happen when multiple conditionalSettings blocks both match and both define the same input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = "first-value" }, + @{ "name" = "input2"; "value" = $false }, + @{ "name" = "input1"; "value" = "second-value" }, # Duplicate input1 + @{ "name" = "input1"; "value" = "final-value" } # Another duplicate input1 + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify "last wins" - the final value for input1 should be applied + $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be "default: 'final-value'" + $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be 'default: false' + } - # Create settings with duplicate entries for input1 - simulating merged conditional settings - # This can happen when multiple conditionalSettings blocks both match and both define the same input - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "input1"; "value" = "first-value" }, - @{ "name" = "input2"; "value" = $false }, - @{ "name" = "input1"; "value" = "second-value" }, # Duplicate input1 - @{ "name" = "input1"; "value" = "final-value" } # Another duplicate input1 + } # End of Context 'ApplyWorkflowDefaultInputs' + + Context 'ApplyWorkflowDefaultInputs - Hide Feature' { + + It 'hides boolean inputs when hide is true' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with boolean input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " visibleInput:", + " type: string", + " default: ''", + " hiddenInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Use input", + " run: echo `${{ github.event.inputs.hiddenInput }}" ) - } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + $yaml = [Yaml]::new($yamlContent) - # Verify "last wins" - the final value for input1 should be applied - $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be "default: 'final-value'" - $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be 'default: false' - } + # Create settings with hide flag + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "visibleInput"; "value" = "test" }, + @{ "name" = "hiddenInput"; "value" = $true; "hide" = $true } + ) + } - It 'ApplyWorkflowDefaultInputs hides boolean inputs when hide is true' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Boolean" - # Create a test workflow YAML with boolean input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " visibleInput:", - " type: string", - " default: ''", - " hiddenInput:", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest", - " steps:", - " - name: Use input", - " run: echo `${{ github.event.inputs.hiddenInput }}" - ) + # Verify the hidden input was removed from inputs + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('hiddenInput:', [ref] $null, [ref] $null) | Should -Be $false - $yaml = [Yaml]::new($yamlContent) + # Verify the visible input still exists with updated default + $inputs.Find('visibleInput:', [ref] $null, [ref] $null) | Should -Be $true + $yaml.Get('on:/workflow_dispatch:/inputs:/visibleInput:/default:').content -join '' | Should -Be "default: 'test'" - # Create settings with hide flag - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "visibleInput"; "value" = "test" }, - @{ "name" = "hiddenInput"; "value" = $true; "hide" = $true } - ) + # Verify the reference was replaced with literal value + $yaml.content -join "`n" | Should -Match "echo true" + $yaml.content -join "`n" | Should -Not -Match "github\.event\.inputs\.hiddenInput" } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Boolean" - - # Verify the hidden input was removed from inputs - $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') - $inputs.Find('hiddenInput:', [ref] $null, [ref] $null) | Should -Be $false + It 'hides string inputs and replaces references correctly' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with string input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " versionInput:", + " type: string", + " default: '+0.0'", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Use version", + " run: echo `${{ inputs.versionInput }}", + " - name: Use version again", + " run: echo `${{ github.event.inputs.versionInput }}" + ) - # Verify the visible input still exists with updated default - $inputs.Find('visibleInput:', [ref] $null, [ref] $null) | Should -Be $true - $yaml.Get('on:/workflow_dispatch:/inputs:/visibleInput:/default:').content -join '' | Should -Be "default: 'test'" + $yaml = [Yaml]::new($yamlContent) - # Verify the reference was replaced with literal value - $yaml.content -join "`n" | Should -Match "echo true" - $yaml.content -join "`n" | Should -Not -Match "github\.event\.inputs\.hiddenInput" - } + # Create settings with hide flag + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "versionInput"; "value" = "+0.1"; "hide" = $true } + ) + } - It 'ApplyWorkflowDefaultInputs hides string inputs and replaces references correctly' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide String" - # Create a test workflow YAML with string input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " versionInput:", - " type: string", - " default: '+0.0'", - "jobs:", - " test:", - " runs-on: ubuntu-latest", - " steps:", - " - name: Use version", - " run: echo `${{ inputs.versionInput }}", - " - name: Use version again", - " run: echo `${{ github.event.inputs.versionInput }}" - ) + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('versionInput:', [ref] $null, [ref] $null) | Should -Be $false - $yaml = [Yaml]::new($yamlContent) - - # Create settings with hide flag - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "versionInput"; "value" = "+0.1"; "hide" = $true } - ) + # Verify both references were replaced with quoted string + $content = $yaml.content -join "`n" + $content | Should -Match "echo '\+0\.1'" + $content | Should -Not -Match "inputs\.versionInput" + $content | Should -Not -Match "github\.event\.inputs\.versionInput" } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide String" - - # Verify the hidden input was removed - $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') - $inputs.Find('versionInput:', [ref] $null, [ref] $null) | Should -Be $false + It 'hides number inputs and replaces references correctly' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with number input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " countInput:", + " type: number", + " default: 5", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Use count", + " run: echo `${{ inputs.countInput }}" + ) - # Verify both references were replaced with quoted string - $content = $yaml.content -join "`n" - $content | Should -Match "echo '\+0\.1'" - $content | Should -Not -Match "inputs\.versionInput" - $content | Should -Not -Match "github\.event\.inputs\.versionInput" - } + $yaml = [Yaml]::new($yamlContent) - It 'ApplyWorkflowDefaultInputs hides number inputs and replaces references correctly' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Create settings with hide flag + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "countInput"; "value" = 10; "hide" = $true } + ) + } - # Create a test workflow YAML with number input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " countInput:", - " type: number", - " default: 5", - "jobs:", - " test:", - " runs-on: ubuntu-latest", - " steps:", - " - name: Use count", - " run: echo `${{ inputs.countInput }}" - ) + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Number" - $yaml = [Yaml]::new($yamlContent) + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('countInput:', [ref] $null, [ref] $null) | Should -Be $false - # Create settings with hide flag - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "countInput"; "value" = 10; "hide" = $true } - ) + # Verify the reference was replaced with number (unquoted) + $yaml.content -join "`n" | Should -Match "echo 10" + $yaml.content -join "`n" | Should -Not -Match "inputs\.countInput" } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Number" + It 'replaces hidden input references in if conditions' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with input used in if condition (without ${{ }}) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " runTests:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: windows-latest", + " if: github.event.inputs.runTests == 'true'", + " steps:", + " - name: Run", + " run: echo Running" + ) - # Verify the hidden input was removed - $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') - $inputs.Find('countInput:', [ref] $null, [ref] $null) | Should -Be $false + $yaml = [Yaml]::new($yamlContent) - # Verify the reference was replaced with number (unquoted) - $yaml.content -join "`n" | Should -Match "echo 10" - $yaml.content -join "`n" | Should -Not -Match "inputs\.countInput" - } + # Create settings with hide flag + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "runTests"; "value" = $true; "hide" = $true } + ) + } - It 'ApplyWorkflowDefaultInputs replaces hidden input references in if conditions' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide If Condition" - # Create a test workflow YAML with input used in if condition (without ${{ }}) - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " runTests:", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: windows-latest", - " if: github.event.inputs.runTests == 'true'", - " steps:", - " - name: Run", - " run: echo Running" - ) + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('runTests:', [ref] $null, [ref] $null) | Should -Be $false - $yaml = [Yaml]::new($yamlContent) + # Verify the reference in if condition was replaced with string literal + # Bare references in if conditions are always treated as strings in GitHub Actions + # GitHub Actions comparisons are case-sensitive, so we use lowercase 'true' + $yaml.content -join "`n" | Should -Match "if: 'true' == 'true'" + $yaml.content -join "`n" | Should -Not -Match "github\.event\.inputs\.runTests" + } - # Create settings with hide flag - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "runTests"; "value" = $true; "hide" = $true } + It 'does not replace job output references' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with needs.JobName.outputs.inputName pattern + # This should NOT be replaced even if an input with the same name exists and is hidden + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " branch:", + " type: string", + " default: 'main'", + "jobs:", + " Inputs:", + " runs-on: ubuntu-latest", + " outputs:", + " branch: `${{ steps.CreateInputs.outputs.branch }}", + " steps:", + " - name: Create inputs", + " id: CreateInputs", + " run: echo 'branch=main' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ Inputs ]", + " steps:", + " - name: Deploy", + " env:", + " branch: `${{ needs.Inputs.outputs.branch }}", + " run: echo Deploying to `$branch" ) - } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide If Condition" + $yaml = [Yaml]::new($yamlContent) - # Verify the hidden input was removed - $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') - $inputs.Find('runTests:', [ref] $null, [ref] $null) | Should -Be $false + # Create settings with hide flag for the branch input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "branch"; "value" = "production"; "hide" = $true } + ) + } - # Verify the reference in if condition was replaced with string literal - # Bare references in if conditions are always treated as strings in GitHub Actions - # GitHub Actions comparisons are case-sensitive, so we use lowercase 'true' - $yaml.content -join "`n" | Should -Match "if: 'true' == 'true'" - $yaml.content -join "`n" | Should -Not -Match "github\.event\.inputs\.runTests" - } + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Job Output" - It 'ApplyWorkflowDefaultInputs does not replace job output references' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('branch:', [ref] $null, [ref] $null) | Should -Be $false - # Create a test workflow YAML with needs.JobName.outputs.inputName pattern - # This should NOT be replaced even if an input with the same name exists and is hidden - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " branch:", - " type: string", - " default: 'main'", - "jobs:", - " Inputs:", - " runs-on: ubuntu-latest", - " outputs:", - " branch: `${{ steps.CreateInputs.outputs.branch }}", - " steps:", - " - name: Create inputs", - " id: CreateInputs", - " run: echo 'branch=main' >> `$GITHUB_OUTPUT", - " Deploy:", - " runs-on: ubuntu-latest", - " needs: [ Inputs ]", - " steps:", - " - name: Deploy", - " env:", - " branch: `${{ needs.Inputs.outputs.branch }}", - " run: echo Deploying to `$branch" - ) + # Verify that needs.Inputs.outputs.branch is NOT replaced + # This is a job output reference, not a workflow input reference + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.Inputs\.outputs\.branch" - $yaml = [Yaml]::new($yamlContent) + # Verify that direct input references would be replaced if they existed + # (but they don't exist in this workflow, so we just verify the job reference remains) + $content | Should -Not -Match "github\.event\.inputs\.branch" + $content | Should -Not -Match "`\$\{\{ inputs\.branch \}\}" + } - # Create settings with hide flag for the branch input - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "branch"; "value" = "production"; "hide" = $true } + It 'does not replace parts of job output references with input names like output' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Edge case: input named "output" should not replace "inputs.outputs.output" or "outputs.output" + # Using lowercase "inputs" as job name to test the problematic case + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " output:", + " type: string", + " default: 'default'", + "jobs:", + " inputs:", + " runs-on: ubuntu-latest", + " outputs:", + " output: `${{ steps.CreateInputs.outputs.output }}", + " steps:", + " - name: Create inputs", + " id: CreateInputs", + " run: echo 'output=test' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ inputs ]", + " steps:", + " - name: Deploy", + " env:", + " myOutput: `${{ needs.inputs.outputs.output }}", + " run: echo Using `$myOutput" ) - } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Job Output" + $yaml = [Yaml]::new($yamlContent) - # Verify the hidden input was removed - $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') - $inputs.Find('branch:', [ref] $null, [ref] $null) | Should -Be $false + # Create settings with hide flag for the "output" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "output"; "value" = "hidden"; "hide" = $true } + ) + } - # Verify that needs.Inputs.outputs.branch is NOT replaced - # This is a job output reference, not a workflow input reference - $content = $yaml.content -join "`n" - $content | Should -Match "needs\.Inputs\.outputs\.branch" + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Input Named Output" - # Verify that direct input references would be replaced if they existed - # (but they don't exist in this workflow, so we just verify the job reference remains) - $content | Should -Not -Match "github\.event\.inputs\.branch" - $content | Should -Not -Match "`\$\{\{ inputs\.branch \}\}" - } + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('output:', [ref] $null, [ref] $null) | Should -Be $false - It 'ApplyWorkflowDefaultInputs does not replace parts of job output references with input names like output' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Verify that needs.inputs.outputs.output is NOT replaced + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.inputs\.outputs\.output" + $content | Should -Match "steps\.CreateInputs\.outputs\.output" - # Edge case: input named "output" should not replace "inputs.outputs.output" or "outputs.output" - # Using lowercase "inputs" as job name to test the problematic case - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " output:", - " type: string", - " default: 'default'", - "jobs:", - " inputs:", - " runs-on: ubuntu-latest", - " outputs:", - " output: `${{ steps.CreateInputs.outputs.output }}", - " steps:", - " - name: Create inputs", - " id: CreateInputs", - " run: echo 'output=test' >> `$GITHUB_OUTPUT", - " Deploy:", - " runs-on: ubuntu-latest", - " needs: [ inputs ]", - " steps:", - " - name: Deploy", - " env:", - " myOutput: `${{ needs.inputs.outputs.output }}", - " run: echo Using `$myOutput" - ) - - $yaml = [Yaml]::new($yamlContent) + # Verify that direct input references would be replaced if they existed + $content | Should -Not -Match "github\.event\.inputs\.output" + $content | Should -Not -Match "`\$\{\{ inputs\.output \}\}" + } - # Create settings with hide flag for the "output" input - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "output"; "value" = "hidden"; "hide" = $true } + It 'does not replace job output references when input is named outputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Edge case: input named "outputs" should not replace "inputs.outputs" in job output contexts + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " outputs:", + " type: string", + " default: 'default'", + "jobs:", + " Initialization:", + " runs-on: ubuntu-latest", + " outputs:", + " telemetryScopeJson: `${{ steps.init.outputs.telemetryScopeJson }}", + " steps:", + " - name: Initialize", + " id: init", + " run: echo 'telemetryScopeJson={}' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ Initialization ]", + " steps:", + " - name: Deploy", + " env:", + " telemetryScope: `${{ needs.Initialization.outputs.telemetryScopeJson }}", + " run: echo Using `$telemetryScope", + " - name: Use input", + " run: echo `${{ inputs.outputs }}" ) - } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Input Named Output" + $yaml = [Yaml]::new($yamlContent) - # Verify the hidden input was removed - $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') - $inputs.Find('output:', [ref] $null, [ref] $null) | Should -Be $false + # Create settings with hide flag for the "outputs" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "outputs"; "value" = "hidden-value"; "hide" = $true } + ) + } - # Verify that needs.inputs.outputs.output is NOT replaced - $content = $yaml.content -join "`n" - $content | Should -Match "needs\.inputs\.outputs\.output" - $content | Should -Match "steps\.CreateInputs\.outputs\.output" + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Input Named Outputs" - # Verify that direct input references would be replaced if they existed - $content | Should -Not -Match "github\.event\.inputs\.output" - $content | Should -Not -Match "`\$\{\{ inputs\.output \}\}" - } + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false - It 'ApplyWorkflowDefaultInputs does not replace job output references when input is named outputs' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Verify that needs.Initialization.outputs.telemetryScopeJson is NOT replaced + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.Initialization\.outputs\.telemetryScopeJson" + $content | Should -Match "steps\.init\.outputs\.telemetryScopeJson" - # Edge case: input named "outputs" should not replace "inputs.outputs" in job output contexts - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " outputs:", - " type: string", - " default: 'default'", - "jobs:", - " Initialization:", - " runs-on: ubuntu-latest", - " outputs:", - " telemetryScopeJson: `${{ steps.init.outputs.telemetryScopeJson }}", - " steps:", - " - name: Initialize", - " id: init", - " run: echo 'telemetryScopeJson={}' >> `$GITHUB_OUTPUT", - " Deploy:", - " runs-on: ubuntu-latest", - " needs: [ Initialization ]", - " steps:", - " - name: Deploy", - " env:", - " telemetryScope: `${{ needs.Initialization.outputs.telemetryScopeJson }}", - " run: echo Using `$telemetryScope", - " - name: Use input", - " run: echo `${{ inputs.outputs }}" - ) - - $yaml = [Yaml]::new($yamlContent) + # Verify that the actual input reference WAS replaced + $content | Should -Match "echo 'hidden-value'" + $content | Should -Not -Match "github\.event\.inputs\.outputs" + $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" + } - # Create settings with hide flag for the "outputs" input - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "outputs"; "value" = "hidden-value"; "hide" = $true } + It 'does not replace job outputs when job is named inputs and input is named outputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # This is the real edge case: job named "inputs" (lowercase) with outputs, and an input also named "outputs" + # needs.inputs.outputs.something should NOT be replaced (it's a job output reference) + # but ${{ inputs.outputs }} should be replaced (it's a workflow input reference) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " outputs:", + " type: string", + " default: 'default'", + "jobs:", + " inputs:", + " runs-on: ubuntu-latest", + " outputs:", + " myValue: `${{ steps.init.outputs.myValue }}", + " steps:", + " - name: Initialize", + " id: init", + " run: echo 'myValue=test' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ inputs ]", + " steps:", + " - name: Use job output", + " env:", + " value: `${{ needs.inputs.outputs.myValue }}", + " run: echo Job output is `$value", + " - name: Use input", + " run: echo Input is `${{ inputs.outputs }}" ) - } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Input Named Outputs" + $yaml = [Yaml]::new($yamlContent) - # Verify the hidden input was removed - $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') - $inputs.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false + # Create settings with hide flag for the "outputs" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "outputs"; "value" = "hidden-input-value"; "hide" = $true } + ) + } - # Verify that needs.Initialization.outputs.telemetryScopeJson is NOT replaced - $content = $yaml.content -join "`n" - $content | Should -Match "needs\.Initialization\.outputs\.telemetryScopeJson" - $content | Should -Match "steps\.init\.outputs\.telemetryScopeJson" + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Job Named Inputs" - # Verify that the actual input reference WAS replaced - $content | Should -Match "echo 'hidden-value'" - $content | Should -Not -Match "github\.event\.inputs\.outputs" - $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" - } + # Verify the hidden input was removed + $inputsSection = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputsSection.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false - It 'ApplyWorkflowDefaultInputs does not replace job outputs when job is named inputs and input is named outputs' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Verify that needs.inputs.outputs.myValue is NOT replaced (job output reference) + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.inputs\.outputs\.myValue" - # This is the real edge case: job named "inputs" (lowercase) with outputs, and an input also named "outputs" - # needs.inputs.outputs.something should NOT be replaced (it's a job output reference) - # but ${{ inputs.outputs }} should be replaced (it's a workflow input reference) - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " outputs:", - " type: string", - " default: 'default'", - "jobs:", - " inputs:", - " runs-on: ubuntu-latest", - " outputs:", - " myValue: `${{ steps.init.outputs.myValue }}", - " steps:", - " - name: Initialize", - " id: init", - " run: echo 'myValue=test' >> `$GITHUB_OUTPUT", - " Deploy:", - " runs-on: ubuntu-latest", - " needs: [ inputs ]", - " steps:", - " - name: Use job output", - " env:", - " value: `${{ needs.inputs.outputs.myValue }}", - " run: echo Job output is `$value", - " - name: Use input", - " run: echo Input is `${{ inputs.outputs }}" - ) + # Verify that steps.init.outputs.myValue is NOT replaced (step output reference) + $content | Should -Match "steps\.init\.outputs\.myValue" - $yaml = [Yaml]::new($yamlContent) - - # Create settings with hide flag for the "outputs" input - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "outputs"; "value" = "hidden-input-value"; "hide" = $true } - ) + # Verify that inputs.outputs WAS replaced with the hidden value + $content | Should -Match "echo Input is 'hidden-input-value'" + $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" } - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Job Named Inputs" - - # Verify the hidden input was removed - $inputsSection = $yaml.Get('on:/workflow_dispatch:/inputs:/') - $inputsSection.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false + It 'silently skips hiding non-existent input' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with only one input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " existingInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.existingInput }}" + ) - # Verify that needs.inputs.outputs.myValue is NOT replaced (job output reference) - $content = $yaml.content -join "`n" - $content | Should -Match "needs\.inputs\.outputs\.myValue" + $yaml = [Yaml]::new($yamlContent) - # Verify that steps.init.outputs.myValue is NOT replaced (step output reference) - $content | Should -Match "steps\.init\.outputs\.myValue" + # Create settings trying to hide an input that doesn't exist + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "existingInput"; "value" = "test-value" }, + @{ "name" = "nonExistentInput"; "value" = "hidden-value"; "hide" = $true } + ) + } - # Verify that inputs.outputs WAS replaced with the hidden value - $content | Should -Match "echo Input is 'hidden-input-value'" - $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" - } + # Mock OutputWarning to verify no warning is issued + Mock OutputWarning { } - It 'ApplyWorkflowDefaultInputs silently skips hiding non-existent input' { - . (Join-Path $scriptRoot "yamlclass.ps1") + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Non-Existent" } | Should -Not -Throw - # Create a test workflow YAML with only one input - $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " existingInput:", - " type: string", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest", - " steps:", - " - name: Test", - " run: echo `${{ inputs.existingInput }}" - ) + # Verify no warning was issued + Assert-MockCalled OutputWarning -Times 0 - $yaml = [Yaml]::new($yamlContent) + # Verify the existing input was updated but not hidden + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('existingInput:', [ref] $null, [ref] $null) | Should -Be $true + $yaml.Get('on:/workflow_dispatch:/inputs:/existingInput:/default:').content -join '' | Should -Be "default: 'test-value'" - # Create settings trying to hide an input that doesn't exist - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "existingInput"; "value" = "test-value" }, - @{ "name" = "nonExistentInput"; "value" = "hidden-value"; "hide" = $true } - ) + # Verify the workflow content was not affected by the non-existent input + $content = $yaml.content -join "`n" + $content | Should -Match "`\$\{\{ inputs\.existingInput \}\}" } - # Mock OutputWarning to verify no warning is issued - Mock OutputWarning { } - - # Apply the defaults - should not throw - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Non-Existent" } | Should -Not -Throw - - # Verify no warning was issued - Assert-MockCalled OutputWarning -Times 0 - - # Verify the existing input was updated but not hidden - $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') - $inputs.Find('existingInput:', [ref] $null, [ref] $null) | Should -Be $true - $yaml.Get('on:/workflow_dispatch:/inputs:/existingInput:/default:').content -join '' | Should -Be "default: 'test-value'" - - # Verify the workflow content was not affected by the non-existent input - $content = $yaml.content -join "`n" - $content | Should -Match "`\$\{\{ inputs\.existingInput \}\}" - } + } # End of Context 'ApplyWorkflowDefaultInputs - Hide Feature' } From d1641582d9b75af17b742f6dad32490d1461e14b Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Tue, 18 Nov 2025 10:33:33 +0100 Subject: [PATCH 21/30] Add exclusion for mdformat hook to prevent formatting in .github/instructions directory --- .pre-commit-config.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index c226d1a8f..10a7048d9 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,6 +7,7 @@ repos: hooks: - id: mdformat args: [--end-of-line=keep] + exclude: '^\.github/instructions/.*\.md$' - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 From dd7870bfa14db0decd384d27cbb01e8f0febce1d Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Tue, 18 Nov 2025 10:33:43 +0100 Subject: [PATCH 22/30] Add testing instructions for AL-Go repository using Pester framework --- .github/instructions/tests.instructions.md | 305 +++++++++++++++++++++ 1 file changed, 305 insertions(+) create mode 100644 .github/instructions/tests.instructions.md diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md new file mode 100644 index 000000000..24f48f0e0 --- /dev/null +++ b/.github/instructions/tests.instructions.md @@ -0,0 +1,305 @@ +--- +applyTo: 'Tests/**' +--- + +# AL-Go Testing Instructions + +## Overview +This document provides guidelines for writing and maintaining tests in the AL-Go repository using the Pester testing framework. + +## Test Framework +- **Framework**: Pester (PowerShell testing framework) +- **Version**: 5.x +- **Test Discovery**: Tests are discovered by `It` blocks within `Describe` blocks + +## Test File Structure + +### Naming Conventions +- Test files should end with `.Test.ps1` +- Test file names should match the component being tested (e.g., `CheckForUpdates.Action.Test.ps1` for `CheckForUpdates.ps1`) + +### Standard Structure +```powershell +Get-Module TestActionsHelper | Remove-Module -Force +Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') + +Describe "Component Name" { + BeforeAll { + # Setup code that runs once before all tests + } + + BeforeEach { + # Setup code that runs before each test + } + + AfterEach { + # Cleanup code that runs after each test + } + + AfterAll { + # Cleanup code that runs once after all tests + } + + It 'Test description' { + # Test implementation + } +} +``` + +## Test Helper Modules + +### TestActionsHelper.psm1 +- Provides common helper functions for testing +- Imports `DebugLogHelper.psm1` globally, making `OutputWarning`, `OutputError`, etc. available for mocking +- Contains functions like `GetActionScript` for loading action scripts +- Provides `GetTemporaryPath()` function for creating temporary file paths (use this instead of `$PSScriptRoot`) + +## Writing Tests + +### Test Naming +- Use descriptive test names that explain what is being tested +- Start with the function/component name being tested +- Use present tense (e.g., "handles", "applies", "validates") +- Examples: + - `'ApplyWorkflowDefaultInputs applies default values to workflow inputs'` + - `'ApplyWorkflowDefaultInputs handles empty workflowDefaultInputs array'` + - `'ApplyWorkflowDefaultInputs validates boolean type mismatch'` + +### Assertions +Use Pester's `Should` assertions: +```powershell +$result | Should -Be $expected +$result | Should -Not -BeNullOrEmpty +$result | Should -Match "pattern" +{ SomeFunction } | Should -Throw "*error message*" +{ SomeFunction } | Should -Not -Throw +``` + +### Mocking Functions + +#### Mocking Output Functions +```powershell +# Mock OutputWarning to verify no warnings +Mock OutputWarning { } +# ... run function ... +Assert-MockCalled OutputWarning -Times 0 + +# Mock OutputWarning to verify specific warning +Mock OutputWarning { } +# ... run function ... +Assert-MockCalled OutputWarning -Exactly 1 -ParameterFilter { + $message -like "*expected warning text*" +} + +# Mock OutputError with counter +$script:errorCount = 0 +Mock OutputError { Param([string] $message) Write-Host "ERROR: $message"; $script:errorCount++ } +# ... run function ... +$script:errorCount | Should -Be 2 +``` + +#### Mock Scope +- Mocks are scoped to the `Describe`, `Context`, or `It` block where they're defined +- `OutputWarning`, `OutputError`, `OutputDebug` are available globally when `TestActionsHelper.psm1` is imported + +### Testing Error Conditions +```powershell +It 'Function throws on invalid input' { + # Use script block with Should -Throw + { FunctionName -InvalidParam } | Should -Throw "*error message*" +} + +It 'Function handles error gracefully' { + # Verify function doesn't throw + { FunctionName -Input $value } | Should -Not -Throw + # Verify error handling occurred + Assert-MockCalled OutputError -Exactly 1 +} +``` + +## Test Organization + +### Group Related Tests +Use `Describe` blocks to group related tests: +```powershell +Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { + # Tests for helper functions +} + +Describe "YamlClass Tests" { + # Tests for YAML class +} +``` + +### Use Context for Sub-grouping +Use `Context` blocks to group related tests within a `Describe` block: +```powershell +Describe "Component" { + Context "Feature A" { + It 'behaves this way' { } + It 'handles edge case' { } + } + + Context "Feature B" { + It 'behaves that way' { } + } +} +``` + +Example from CheckForUpdates tests: +```powershell +Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { + Context "ApplyWorkflowDefaultInputs" { + It 'applies default values to workflow inputs' { } + It 'validates boolean type mismatch' { } + } + + Context "ApplyWorkflowDefaultInputs - Hide Feature" { + It 'hides boolean inputs when hide is true' { } + It 'replaces hidden input references in if conditions' { } + } +} +``` + +## Edge Cases and Coverage + +### Important Edge Cases to Test +1. **Empty/Null inputs**: Test with empty strings, null values, empty arrays +2. **Non-existent resources**: Test with files/settings that don't exist +3. **Type mismatches**: Test with wrong data types +4. **Case sensitivity**: Test with different casing where applicable +5. **Special characters**: Test with YAML special characters, regex patterns +6. **Boundary conditions**: Test minimum/maximum values, empty collections + +### Example Edge Case Tests +```powershell +It 'handles empty input array' { + $result = FunctionName -Input @() + $result | Should -Not -Throw +} + +It 'silently skips non-existent items' { + Mock OutputWarning { } + FunctionName -Items @("existing", "non-existent") + Assert-MockCalled OutputWarning -Times 0 +} +``` + +## Cleanup and Resource Management + +### File Cleanup +```powershell +AfterEach { + if (Test-Path $tempFile) { + Remove-Item -Path $tempFile -Force + } +} +``` + +### Use Temporary Files +Use `GetTemporaryPath()` function instead of `$PSScriptRoot` for temporary files: +```powershell +BeforeAll { + $tmpFile = Join-Path (GetTemporaryPath) "tempFile.json" +} + +AfterEach { + if (Test-Path $tmpFile) { + Remove-Item -Path $tmpFile -Force + } +} +``` + +## Running Tests + +### Run All Tests +```powershell +Invoke-Pester -Path "Tests/" +``` + +### Run Specific Test File +```powershell +Invoke-Pester -Path "Tests/CheckForUpdates.Action.Test.ps1" +``` + +### Run Specific Test +```powershell +Invoke-Pester -Path "Tests/CheckForUpdates.Action.Test.ps1" -FullNameFilter "*test name pattern*" +``` + +### Run with Detailed Output +Use `-Output Detailed` to see individual test results and Context grouping: +```powershell +Invoke-Pester -Path "Tests/CheckForUpdates.Action.Test.ps1" -Output Detailed +``` + +## Best Practices + +1. **Test One Thing**: Each test should verify one specific behavior +2. **Descriptive Names**: Test names should clearly describe what's being tested +3. **Arrange-Act-Assert**: Structure tests with clear setup, execution, and verification +4. **Mock External Dependencies**: Mock file I/O, API calls, external commands +5. **Verify No Side Effects**: Test that functions don't produce warnings/errors when they shouldn't +6. **Clean Up**: Always clean up temporary files and resources +7. **Independent Tests**: Tests should not depend on each other's execution order +8. **Use BeforeAll for Expensive Setup**: Import modules and load data once per Describe block +9. **Test Error Paths**: Don't just test the happy path - test error handling too +10. **Keep Tests Fast**: Mock expensive operations, use minimal test data + +## Common Patterns + +### Testing Functions That Modify Objects +```powershell +It 'modifies object correctly' { + $obj = CreateTestObject + ModifyObject -Object $obj + $obj.Property | Should -Be $expectedValue +} +``` + +### Testing with Settings Objects +```powershell +$repoSettings = @{ + "settingName" = @( + @{ "name" = "value1" }, + @{ "name" = "value2" } + ) +} +FunctionName -Settings $repoSettings +``` + +### Testing Regex Patterns +```powershell +It 'replaces pattern correctly' { + $result = ReplacePattern -Input $testString -Pattern "regex" + $result | Should -Match "expected" + $result | Should -Not -Match "unexpected" +} +``` + +## Debugging Tests + +### View Test Output +```powershell +# Use Write-Host in tests to see output +It 'test name' { + Write-Host "Debug: $variable" + # assertions +} +``` + +### Run Single Test for Debugging +```powershell +# Exact match (no wildcards needed) +Invoke-Pester -Path "file.Test.ps1" -FullNameFilter "exact test name" + +# Partial match (with wildcards) +Invoke-Pester -Path "file.Test.ps1" -FullNameFilter "*pattern*" +``` + +### Check Mock Calls +```powershell +Mock SomeFunction { } -Verifiable +# ... run code ... +Assert-MockCalled SomeFunction -Times 1 -ParameterFilter { $param -eq "value" } +``` From 604c5ce77adb88d0f9e71dade9f84051a791efc5 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Tue, 18 Nov 2025 11:24:53 +0100 Subject: [PATCH 23/30] Add reminder to run tests after changes in testing instructions --- .github/instructions/tests.instructions.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md index 24f48f0e0..0dc827d5f 100644 --- a/.github/instructions/tests.instructions.md +++ b/.github/instructions/tests.instructions.md @@ -245,6 +245,7 @@ Invoke-Pester -Path "Tests/CheckForUpdates.Action.Test.ps1" -Output Detailed 8. **Use BeforeAll for Expensive Setup**: Import modules and load data once per Describe block 9. **Test Error Paths**: Don't just test the happy path - test error handling too 10. **Keep Tests Fast**: Mock expensive operations, use minimal test data +11. **Run Tests After Changes**: Always run tests after creating or modifying them to verify they work correctly ## Common Patterns From 52e65f46866d9b5813615962b61aca3757910133 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Tue, 18 Nov 2025 11:25:11 +0100 Subject: [PATCH 24/30] Refactor ApplyWorkflowDefaultInputs to use regex for matching whitespace variations in input expressions --- .../CheckForUpdates.HelperFunctions.ps1 | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index c47afab2e..afa1a8197 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -536,8 +536,12 @@ function ApplyWorkflowDefaultInputs { # Replace references in expressions: ${{ github.event.inputs.name }} and ${{ inputs.name }} # These use the expression literal format (true/false for boolean, unquoted number, 'quoted' string) - $yaml.ReplaceAll("`${{ github.event.inputs.$inputName }}", $expressionValue) - $yaml.ReplaceAll("`${{ inputs.$inputName }}", $expressionValue) + # Use regex to match any whitespace variations: ${{inputs.name}}, ${{ inputs.name }}, ${{ inputs.name}}, ${{inputs.name }} + # Replace the entire ${{ ... }} expression with just the value + $pattern = "`\$\{\{\s*github\.event\.inputs\.$inputName\s*\}\}" + $yaml.ReplaceAll($pattern, $expressionValue, $true) + $pattern = "`\$\{\{\s*inputs\.$inputName\s*\}\}" + $yaml.ReplaceAll($pattern, $expressionValue, $true) # Replace references in if conditions: github.event.inputs.name and inputs.name (without ${{ }}) # In if conditions, bare references are always treated as strings, so we need to use string literal format @@ -558,8 +562,8 @@ function ApplyWorkflowDefaultInputs { # - needs.inputs.outputs.NAME (where a job is named "inputs") # - steps.CreateInputs.outputs.NAME (where "inputs" is part of a word) # Use negative lookbehind (? Date: Tue, 18 Nov 2025 11:39:31 +0100 Subject: [PATCH 25/30] Implement hiding of default values for workflow inputs in ApplyWorkflowDefaultInputs function --- .../CheckForUpdates.HelperFunctions.ps1 | 18 +- Tests/CheckForUpdates.Action.Test.ps1 | 670 ++++++++++++++++++ 2 files changed, 683 insertions(+), 5 deletions(-) diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index afa1a8197..783d1c12b 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -436,6 +436,13 @@ function ApplyWorkflowDefaultInputs { } continue } + else { + # If hide is false, ensure the input is not in the hide list + # This handles the case where a previous entry had hide=true but a later entry has hide=false + if ($inputsToHide.ContainsKey($inputName)) { + $inputsToHide.Remove($inputName) + } + } # Convert the default value to the appropriate YAML format $yamlValue = $defaultValue @@ -544,9 +551,11 @@ function ApplyWorkflowDefaultInputs { $yaml.ReplaceAll($pattern, $expressionValue, $true) # Replace references in if conditions: github.event.inputs.name and inputs.name (without ${{ }}) - # In if conditions, bare references are always treated as strings, so we need to use string literal format + # github.event.inputs are always strings (user input from UI) + # inputs.* are typed values (boolean/number/string/choice) + + # For github.event.inputs.NAME: always use string literal format # Convert the value to a string literal (always quoted) for comparisons - # For booleans, use lowercase to match GitHub Actions convention (case-sensitive) if ($rawValue -is [bool]) { $stringValue = $rawValue.ToString().ToLower() } @@ -554,10 +563,9 @@ function ApplyWorkflowDefaultInputs { $stringValue = $rawValue.ToString().Replace("'", "''") } $stringLiteral = "'$stringValue'" - - # Replace github.event.inputs.NAME (safe because it's a specific prefix) $yaml.ReplaceAll("github.event.inputs.$inputName", $stringLiteral) + # For inputs.NAME: use expression literal format (typed value) # Replace inputs.NAME but be careful not to match patterns like: # - needs.inputs.outputs.NAME (where a job is named "inputs") # - steps.CreateInputs.outputs.NAME (where "inputs" is part of a word) @@ -565,7 +573,7 @@ function ApplyWorkflowDefaultInputs { # Use word boundary \b after inputName to avoid partial matches # Don't match if followed by a dot (to avoid matching outputs references) $pattern = "(? Date: Tue, 18 Nov 2025 13:12:47 +0100 Subject: [PATCH 26/30] Add discussion entry for hiding workflow_dispatch input --- RELEASENOTES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index b55cf2b84..6017b9c9f 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -50,6 +50,7 @@ Read more at [workflowDefaultInputs](https://aka.ms/algosettings#workflowDefault - Issue 2016 Running Update AL-Go system files with branches wildcard `*` tries to update _origin_ - Issue 1960 Deploy Reference Documentation fails - Discussion 1952 Set default values on workflow_dispatch input +- Discussion 1952 Hide workflow_dispatch input ## v8.0 From 3b944059c351c56f606141be428139c8bc6639a1 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Tue, 18 Nov 2025 17:24:23 +0100 Subject: [PATCH 27/30] Updates after CR --- .../CheckForUpdates.HelperFunctions.ps1 | 203 +++++++++--------- RELEASENOTES.md | 2 +- Tests/CheckForUpdates.Action.Test.ps1 | 43 +++- 3 files changed, 145 insertions(+), 103 deletions(-) diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index 0534eab0a..4d264298c 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -333,129 +333,132 @@ function ApplyWorkflowDefaultInputs { # Check if this input exists in the workflow $inputSection = $inputs.Get("$($inputName):/") - if ($inputSection) { - # Get the input type from the YAML if specified - $inputType = $null - $typeStart = 0 - $typeCount = 0 - if ($inputSection.Find('type:', [ref] $typeStart, [ref] $typeCount)) { - $typeLine = $inputSection.content[$typeStart].Trim() - if ($typeLine -match 'type:\s*(.+)') { - $inputType = $matches[1].Trim() - } + if (-not $inputSection) { + # Input is not present in the workflow + continue + } + + # Get the input type from the YAML if specified + $inputType = $null + $typeStart = 0 + $typeCount = 0 + if ($inputSection.Find('type:', [ref] $typeStart, [ref] $typeCount)) { + $typeLine = $inputSection.content[$typeStart].Trim() + if ($typeLine -match 'type:\s*(.+)') { + $inputType = $matches[1].Trim() } + } - # Validate that the value type matches the input type - $validationError = $null - if ($inputType) { - switch ($inputType) { - 'boolean' { - if ($defaultValue -isnot [bool]) { - $validationError = "Workflow '$workflowName', input '$inputName': Expected boolean value, but got $($defaultValue.GetType().Name). Please use `$true or `$false." - } + # Validate that the value type matches the input type + $validationError = $null + if ($inputType) { + switch ($inputType) { + 'boolean' { + if ($defaultValue -isnot [bool]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected boolean value, but got $($defaultValue.GetType().Name). Please use `$true or `$false." } - 'number' { - if ($defaultValue -isnot [int] -and $defaultValue -isnot [long] -and $defaultValue -isnot [double]) { - $validationError = "Workflow '$workflowName', input '$inputName': Expected number value, but got $($defaultValue.GetType().Name)." - } + } + 'number' { + if ($defaultValue -isnot [int] -and $defaultValue -isnot [long] -and $defaultValue -isnot [double]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected number value, but got $($defaultValue.GetType().Name)." } - 'string' { - if ($defaultValue -isnot [string]) { - $validationError = "Workflow '$workflowName', input '$inputName': Expected string value, but got $($defaultValue.GetType().Name)." - } + } + 'string' { + if ($defaultValue -isnot [string]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected string value, but got $($defaultValue.GetType().Name)." } - 'choice' { - # Choice inputs accept strings and must match one of the available options (case-sensitive) - if ($defaultValue -isnot [string]) { - $validationError = "Workflow '$workflowName', input '$inputName': Expected string value for choice input, but got $($defaultValue.GetType().Name)." - } - else { - # Validate that the value is one of the available options - $optionsStart = 0 - $optionsCount = 0 - if ($inputSection.Find('options:', [ref] $optionsStart, [ref] $optionsCount)) { - $availableOptions = @() - # Parse the options from the YAML (they are indented list items starting with "- ") - for ($i = $optionsStart + 1; $i -lt ($optionsStart + $optionsCount); $i++) { - $optionLine = $inputSection.content[$i].Trim() - if ($optionLine -match '^-\s*(.+)$') { - $availableOptions += $matches[1].Trim() - } + } + 'choice' { + # Choice inputs accept strings and must match one of the available options (case-sensitive) + if ($defaultValue -isnot [string]) { + $validationError = "Workflow '$workflowName', input '$inputName': Expected string value for choice input, but got $($defaultValue.GetType().Name)." + } + else { + # Validate that the value is one of the available options + $optionsStart = 0 + $optionsCount = 0 + if ($inputSection.Find('options:', [ref] $optionsStart, [ref] $optionsCount)) { + $availableOptions = @() + # Parse the options from the YAML (they are indented list items starting with "- ") + for ($i = $optionsStart + 1; $i -lt ($optionsStart + $optionsCount); $i++) { + $optionLine = $inputSection.content[$i].Trim() + if ($optionLine -match '^-\s*(.+)$') { + $availableOptions += $matches[1].Trim() } + } - if ($availableOptions.Count -gt 0 -and $availableOptions -cnotcontains $defaultValue) { - $validationError = "Workflow '$workflowName', input '$inputName': Value '$defaultValue' is not a valid choice (case-sensitive match required). Available options: $($availableOptions -join ', ')." - } + if ($availableOptions.Count -gt 0 -and $availableOptions -cnotcontains $defaultValue) { + $validationError = "Workflow '$workflowName', input '$inputName': Value '$defaultValue' is not a valid choice (case-sensitive match required). Available options: $($availableOptions -join ', ')." } } } } } - else { - # If no type is specified in the workflow, it defaults to string - if ($defaultValue -isnot [string]) { - OutputWarning "Workflow '$workflowName', input '$inputName': No type specified in workflow (defaults to string), but configured value is $($defaultValue.GetType().Name). This may cause issues." - } + } + else { + # If no type is specified in the workflow, it defaults to string + if ($defaultValue -isnot [string]) { + OutputWarning "Workflow '$workflowName', input '$inputName': No type specified in workflow (defaults to string), but configured value is $($defaultValue.GetType().Name). This may cause issues." } + } - if ($validationError) { - throw $validationError - } + if ($validationError) { + throw $validationError + } - # Convert the default value to the appropriate YAML format - $yamlValue = $defaultValue - if ($defaultValue -is [bool]) { - $yamlValue = $defaultValue.ToString().ToLower() - } - elseif ($defaultValue -is [string]) { - # Quote strings and escape single quotes per YAML spec - $escapedValue = $defaultValue.Replace("'", "''") - $yamlValue = "'$escapedValue'" - } + # Convert the default value to the appropriate YAML format + $yamlValue = $defaultValue + if ($defaultValue -is [bool]) { + $yamlValue = $defaultValue.ToString().ToLower() + } + elseif ($defaultValue -is [string]) { + # Quote strings and escape single quotes per YAML spec + $escapedValue = $defaultValue.Replace("'", "''") + $yamlValue = "'$escapedValue'" + } + + # Find and replace the default: line in the input section + $start = 0 + $count = 0 + if ($inputSection.Find('default:', [ref] $start, [ref] $count)) { + # Replace existing default value + $inputSection.Replace('default:', "default: $yamlValue") + } + else { + # Add default value - find the best place to insert it + # Insert after type, required, or description (whichever comes last) + $insertAfter = -1 + $typeLine = 0 + $typeCount = 0 + $requiredLine = 0 + $requiredCount = 0 + $descLine = 0 + $descCount = 0 - # Find and replace the default: line in the input section - $start = 0 - $count = 0 - if ($inputSection.Find('default:', [ref] $start, [ref] $count)) { - # Replace existing default value - $inputSection.Replace('default:', "default: $yamlValue") + if ($inputSection.Find('type:', [ref] $typeLine, [ref] $typeCount)) { + $insertAfter = $typeLine + $typeCount } - else { - # Add default value - find the best place to insert it - # Insert after type, required, or description (whichever comes last) - $insertAfter = -1 - $typeLine = 0 - $typeCount = 0 - $requiredLine = 0 - $requiredCount = 0 - $descLine = 0 - $descCount = 0 - - if ($inputSection.Find('type:', [ref] $typeLine, [ref] $typeCount)) { - $insertAfter = $typeLine + $typeCount - } - if ($inputSection.Find('required:', [ref] $requiredLine, [ref] $requiredCount)) { - if (($requiredLine + $requiredCount) -gt $insertAfter) { - $insertAfter = $requiredLine + $requiredCount - } - } - if ($inputSection.Find('description:', [ref] $descLine, [ref] $descCount)) { - if (($descLine + $descCount) -gt $insertAfter) { - $insertAfter = $descLine + $descCount - } + if ($inputSection.Find('required:', [ref] $requiredLine, [ref] $requiredCount)) { + if (($requiredLine + $requiredCount) -gt $insertAfter) { + $insertAfter = $requiredLine + $requiredCount } - - if ($insertAfter -eq -1) { - # No other properties, insert at position 1 (after the input name) - $insertAfter = 1 + } + if ($inputSection.Find('description:', [ref] $descLine, [ref] $descCount)) { + if (($descLine + $descCount) -gt $insertAfter) { + $insertAfter = $descLine + $descCount } + } - $inputSection.Insert($insertAfter, "default: $yamlValue") + if ($insertAfter -eq -1) { + # No other properties, insert at position 1 (after the input name) + $insertAfter = 1 } - # Update the inputs section with the modified input - $inputs.Replace("$($inputName):/", $inputSection.content) + $inputSection.Insert($insertAfter, "default: $yamlValue") } + + # Update the inputs section with the modified input + $inputs.Replace("$($inputName):/", $inputSection.content) } # Update the workflow_dispatch section with modified inputs diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 013abcfe8..0c5a1f323 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -36,7 +36,7 @@ Example using conditional settings to target specific workflows: } ``` -**Important:** When multiple conditional settings blocks match and both define `workflowDefaultInputs`, the arrays are merged (all entries are kept). When the defaults are applied to workflows, the last matching entry for each input name wins. +**Important:** When multiple conditional settings blocks match and both define `workflowDefaultInputs`, the arrays are merged following AL-Go's standard behavior for complex setting types (all entries are kept). If the same input name appears in multiple entries, the last matching entry takes precedence. Read more at [workflowDefaultInputs](https://aka.ms/algosettings#workflowDefaultInputs). diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index 87862c1e6..8455e9330 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -335,6 +335,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { ) $yaml = [Yaml]::new($yamlContent) + $originalContent = $yaml.content -join "`n" # Create settings with empty workflowDefaultInputs array $repoSettings = @{ @@ -343,6 +344,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Apply the defaults - should not throw and should not modify workflow { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.content -join "`n" | Should -Be $originalContent $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:').content -join '' | Should -Be 'default: false' } @@ -363,6 +365,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { ) $yaml = [Yaml]::new($yamlContent) + $originalContent = $yaml.content -join "`n" # Create settings with workflow input defaults $repoSettings = @{ @@ -371,8 +374,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { ) } - # Apply the defaults - should not throw + # Apply the defaults - should not throw or modify YAML { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.content -join "`n" | Should -Be $originalContent } It 'ApplyWorkflowDefaultInputs handles workflow_dispatch without inputs section' { @@ -389,6 +393,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { ) $yaml = [Yaml]::new($yamlContent) + $originalContent = $yaml.content -join "`n" # Create settings with workflow input defaults $repoSettings = @{ @@ -397,8 +402,9 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { ) } - # Apply the defaults - should not throw + # Apply the defaults - should not throw or modify YAML { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.content -join "`n" | Should -Be $originalContent } It 'ApplyWorkflowDefaultInputs applies multiple defaults to same workflow' { @@ -537,6 +543,39 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " runs-on: ubuntu-latest" ) + $yaml = [Yaml]::new($yamlContent) + $originalContent = $yaml.content -join "`n" + + # Create settings with only non-existent input names + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "nonExistentInput"; "value" = "ignored" }, + @{ "name" = "anotherMissingInput"; "value" = 42 } + ) + } + + # Apply defaults for non-existent inputs - should not throw or modify YAML + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw + $yaml.content -join "`n" | Should -Be $originalContent + } + + It 'ApplyWorkflowDefaultInputs applies only existing inputs when mixed with non-existent inputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " existingInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest" + ) + $yaml = [Yaml]::new($yamlContent) # Create settings with both existing and non-existent input names From 322bfb8653569ac11ef4ab2d19ed4ff641831ef4 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Tue, 18 Nov 2025 17:36:37 +0100 Subject: [PATCH 28/30] Refactor ApplyWorkflowDefaultInputs function to improve input hiding logic and remove unnecessary input removal code. --- .../CheckForUpdates.HelperFunctions.ps1 | 54 +++++-------------- 1 file changed, 13 insertions(+), 41 deletions(-) diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index c73b9fec2..d09bcaa2f 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -432,21 +432,21 @@ function ApplyWorkflowDefaultInputs { throw $validationError } - if ($hideInput) { - # Store this input to hide it later - $inputsToHide[$inputName] = [pscustomobject]@{ - value = $defaultValue - expression = & $convertToExpressionLiteral $defaultValue - } - continue + if ($hideInput) { + # Store this input to hide it later + $inputsToHide[$inputName] = [pscustomobject]@{ + value = $defaultValue + expression = & $convertToExpressionLiteral $defaultValue } - else { - # If hide is false, ensure the input is not in the hide list - # This handles the case where a previous entry had hide=true but a later entry has hide=false - if ($inputsToHide.ContainsKey($inputName)) { - $inputsToHide.Remove($inputName) - } + continue + } + else { + # If hide is false, ensure the input is not in the hide list + # This handles the case where a previous entry had hide=true but a later entry has hide=false + if ($inputsToHide.ContainsKey($inputName)) { + $inputsToHide.Remove($inputName) } + } # Convert the default value to the appropriate YAML format $yamlValue = $defaultValue @@ -531,34 +531,6 @@ function ApplyWorkflowDefaultInputs { } } - # Remove hidden inputs from the inputs section - if ($inputsToHide.Count -gt 0) { - # Collect all inputs to remove with their positions - $inputsToRemove = @() - foreach ($entry in $inputsToHide.GetEnumerator()) { - $name = $entry.Key - $start = 0 - $count = 0 - # Find the input including all its content (type, default, etc.) - # Use the name with colon to match the full input section - if ($inputs.Find("$($name):", [ref] $start, [ref] $count)) { - $inputsToRemove += [pscustomobject]@{ - Name = $name - Start = $start - Count = $count - } - } - else { - OutputWarning "Workflow '$workflowName': Unable to hide input '$name' because it was not found in the workflow file." - } - } - - # Remove inputs in reverse order to maintain correct indices - foreach ($item in $inputsToRemove | Sort-Object -Property Start -Descending) { - $inputs.Remove($item.Start, $item.Count) - } - } - # Update the workflow_dispatch section with modified inputs $workflowDispatch.Replace('inputs:/', $inputs.content) From 46adbd094b77bb7f2017f5d7d2356a5a7b6e5dc5 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Thu, 20 Nov 2025 13:36:02 +0100 Subject: [PATCH 29/30] Remove test instructions for now --- .github/instructions/tests.instructions.md | 306 --------------------- .pre-commit-config.yaml | 1 - 2 files changed, 307 deletions(-) delete mode 100644 .github/instructions/tests.instructions.md diff --git a/.github/instructions/tests.instructions.md b/.github/instructions/tests.instructions.md deleted file mode 100644 index 0dc827d5f..000000000 --- a/.github/instructions/tests.instructions.md +++ /dev/null @@ -1,306 +0,0 @@ ---- -applyTo: 'Tests/**' ---- - -# AL-Go Testing Instructions - -## Overview -This document provides guidelines for writing and maintaining tests in the AL-Go repository using the Pester testing framework. - -## Test Framework -- **Framework**: Pester (PowerShell testing framework) -- **Version**: 5.x -- **Test Discovery**: Tests are discovered by `It` blocks within `Describe` blocks - -## Test File Structure - -### Naming Conventions -- Test files should end with `.Test.ps1` -- Test file names should match the component being tested (e.g., `CheckForUpdates.Action.Test.ps1` for `CheckForUpdates.ps1`) - -### Standard Structure -```powershell -Get-Module TestActionsHelper | Remove-Module -Force -Import-Module (Join-Path $PSScriptRoot 'TestActionsHelper.psm1') - -Describe "Component Name" { - BeforeAll { - # Setup code that runs once before all tests - } - - BeforeEach { - # Setup code that runs before each test - } - - AfterEach { - # Cleanup code that runs after each test - } - - AfterAll { - # Cleanup code that runs once after all tests - } - - It 'Test description' { - # Test implementation - } -} -``` - -## Test Helper Modules - -### TestActionsHelper.psm1 -- Provides common helper functions for testing -- Imports `DebugLogHelper.psm1` globally, making `OutputWarning`, `OutputError`, etc. available for mocking -- Contains functions like `GetActionScript` for loading action scripts -- Provides `GetTemporaryPath()` function for creating temporary file paths (use this instead of `$PSScriptRoot`) - -## Writing Tests - -### Test Naming -- Use descriptive test names that explain what is being tested -- Start with the function/component name being tested -- Use present tense (e.g., "handles", "applies", "validates") -- Examples: - - `'ApplyWorkflowDefaultInputs applies default values to workflow inputs'` - - `'ApplyWorkflowDefaultInputs handles empty workflowDefaultInputs array'` - - `'ApplyWorkflowDefaultInputs validates boolean type mismatch'` - -### Assertions -Use Pester's `Should` assertions: -```powershell -$result | Should -Be $expected -$result | Should -Not -BeNullOrEmpty -$result | Should -Match "pattern" -{ SomeFunction } | Should -Throw "*error message*" -{ SomeFunction } | Should -Not -Throw -``` - -### Mocking Functions - -#### Mocking Output Functions -```powershell -# Mock OutputWarning to verify no warnings -Mock OutputWarning { } -# ... run function ... -Assert-MockCalled OutputWarning -Times 0 - -# Mock OutputWarning to verify specific warning -Mock OutputWarning { } -# ... run function ... -Assert-MockCalled OutputWarning -Exactly 1 -ParameterFilter { - $message -like "*expected warning text*" -} - -# Mock OutputError with counter -$script:errorCount = 0 -Mock OutputError { Param([string] $message) Write-Host "ERROR: $message"; $script:errorCount++ } -# ... run function ... -$script:errorCount | Should -Be 2 -``` - -#### Mock Scope -- Mocks are scoped to the `Describe`, `Context`, or `It` block where they're defined -- `OutputWarning`, `OutputError`, `OutputDebug` are available globally when `TestActionsHelper.psm1` is imported - -### Testing Error Conditions -```powershell -It 'Function throws on invalid input' { - # Use script block with Should -Throw - { FunctionName -InvalidParam } | Should -Throw "*error message*" -} - -It 'Function handles error gracefully' { - # Verify function doesn't throw - { FunctionName -Input $value } | Should -Not -Throw - # Verify error handling occurred - Assert-MockCalled OutputError -Exactly 1 -} -``` - -## Test Organization - -### Group Related Tests -Use `Describe` blocks to group related tests: -```powershell -Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { - # Tests for helper functions -} - -Describe "YamlClass Tests" { - # Tests for YAML class -} -``` - -### Use Context for Sub-grouping -Use `Context` blocks to group related tests within a `Describe` block: -```powershell -Describe "Component" { - Context "Feature A" { - It 'behaves this way' { } - It 'handles edge case' { } - } - - Context "Feature B" { - It 'behaves that way' { } - } -} -``` - -Example from CheckForUpdates tests: -```powershell -Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { - Context "ApplyWorkflowDefaultInputs" { - It 'applies default values to workflow inputs' { } - It 'validates boolean type mismatch' { } - } - - Context "ApplyWorkflowDefaultInputs - Hide Feature" { - It 'hides boolean inputs when hide is true' { } - It 'replaces hidden input references in if conditions' { } - } -} -``` - -## Edge Cases and Coverage - -### Important Edge Cases to Test -1. **Empty/Null inputs**: Test with empty strings, null values, empty arrays -2. **Non-existent resources**: Test with files/settings that don't exist -3. **Type mismatches**: Test with wrong data types -4. **Case sensitivity**: Test with different casing where applicable -5. **Special characters**: Test with YAML special characters, regex patterns -6. **Boundary conditions**: Test minimum/maximum values, empty collections - -### Example Edge Case Tests -```powershell -It 'handles empty input array' { - $result = FunctionName -Input @() - $result | Should -Not -Throw -} - -It 'silently skips non-existent items' { - Mock OutputWarning { } - FunctionName -Items @("existing", "non-existent") - Assert-MockCalled OutputWarning -Times 0 -} -``` - -## Cleanup and Resource Management - -### File Cleanup -```powershell -AfterEach { - if (Test-Path $tempFile) { - Remove-Item -Path $tempFile -Force - } -} -``` - -### Use Temporary Files -Use `GetTemporaryPath()` function instead of `$PSScriptRoot` for temporary files: -```powershell -BeforeAll { - $tmpFile = Join-Path (GetTemporaryPath) "tempFile.json" -} - -AfterEach { - if (Test-Path $tmpFile) { - Remove-Item -Path $tmpFile -Force - } -} -``` - -## Running Tests - -### Run All Tests -```powershell -Invoke-Pester -Path "Tests/" -``` - -### Run Specific Test File -```powershell -Invoke-Pester -Path "Tests/CheckForUpdates.Action.Test.ps1" -``` - -### Run Specific Test -```powershell -Invoke-Pester -Path "Tests/CheckForUpdates.Action.Test.ps1" -FullNameFilter "*test name pattern*" -``` - -### Run with Detailed Output -Use `-Output Detailed` to see individual test results and Context grouping: -```powershell -Invoke-Pester -Path "Tests/CheckForUpdates.Action.Test.ps1" -Output Detailed -``` - -## Best Practices - -1. **Test One Thing**: Each test should verify one specific behavior -2. **Descriptive Names**: Test names should clearly describe what's being tested -3. **Arrange-Act-Assert**: Structure tests with clear setup, execution, and verification -4. **Mock External Dependencies**: Mock file I/O, API calls, external commands -5. **Verify No Side Effects**: Test that functions don't produce warnings/errors when they shouldn't -6. **Clean Up**: Always clean up temporary files and resources -7. **Independent Tests**: Tests should not depend on each other's execution order -8. **Use BeforeAll for Expensive Setup**: Import modules and load data once per Describe block -9. **Test Error Paths**: Don't just test the happy path - test error handling too -10. **Keep Tests Fast**: Mock expensive operations, use minimal test data -11. **Run Tests After Changes**: Always run tests after creating or modifying them to verify they work correctly - -## Common Patterns - -### Testing Functions That Modify Objects -```powershell -It 'modifies object correctly' { - $obj = CreateTestObject - ModifyObject -Object $obj - $obj.Property | Should -Be $expectedValue -} -``` - -### Testing with Settings Objects -```powershell -$repoSettings = @{ - "settingName" = @( - @{ "name" = "value1" }, - @{ "name" = "value2" } - ) -} -FunctionName -Settings $repoSettings -``` - -### Testing Regex Patterns -```powershell -It 'replaces pattern correctly' { - $result = ReplacePattern -Input $testString -Pattern "regex" - $result | Should -Match "expected" - $result | Should -Not -Match "unexpected" -} -``` - -## Debugging Tests - -### View Test Output -```powershell -# Use Write-Host in tests to see output -It 'test name' { - Write-Host "Debug: $variable" - # assertions -} -``` - -### Run Single Test for Debugging -```powershell -# Exact match (no wildcards needed) -Invoke-Pester -Path "file.Test.ps1" -FullNameFilter "exact test name" - -# Partial match (with wildcards) -Invoke-Pester -Path "file.Test.ps1" -FullNameFilter "*pattern*" -``` - -### Check Mock Calls -```powershell -Mock SomeFunction { } -Verifiable -# ... run code ... -Assert-MockCalled SomeFunction -Times 1 -ParameterFilter { $param -eq "value" } -``` diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 10a7048d9..c226d1a8f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -7,7 +7,6 @@ repos: hooks: - id: mdformat args: [--end-of-line=keep] - exclude: '^\.github/instructions/.*\.md$' - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 From 756a85c61804b921dfbbd4458e2dfdf5350cd1c0 Mon Sep 17 00:00:00 2001 From: Johannes Wikman Date: Wed, 3 Dec 2025 18:03:15 +0100 Subject: [PATCH 30/30] Add tests for hiding inputs in CheckForUpdates.Action.Test.ps1 --- Tests/CheckForUpdates.Action.Test.ps1 | 1152 ++++++++++++++++++++++++- 1 file changed, 1148 insertions(+), 4 deletions(-) diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index 2568291e3..20cb29f5b 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -1196,10 +1196,1154 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Apply the defaults ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - # Verify "last wins" - the final value for input1 should be applied - $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be "default: 'final-value'" - $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be 'default: false' - } + # Verify "last wins" - the final value for input1 should be applied + $yaml.Get('on:/workflow_dispatch:/inputs:/input1:/default:').content -join '' | Should -Be "default: 'final-value'" + $yaml.Get('on:/workflow_dispatch:/inputs:/input2:/default:').content -join '' | Should -Be 'default: false' + } + + } # End of Context 'ApplyWorkflowDefaultInputs' + + Context 'ApplyWorkflowDefaultInputs - Hide Feature' { + + It 'hides boolean inputs when hide is true' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with boolean input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " visibleInput:", + " type: string", + " default: ''", + " hiddenInput:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Use input", + " run: echo `${{ github.event.inputs.hiddenInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "visibleInput"; "value" = "test" }, + @{ "name" = "hiddenInput"; "value" = $true; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Boolean" + + # Verify the hidden input was removed from inputs + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('hiddenInput:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the visible input still exists with updated default + $inputs.Find('visibleInput:', [ref] $null, [ref] $null) | Should -Be $true + $yaml.Get('on:/workflow_dispatch:/inputs:/visibleInput:/default:').content -join '' | Should -Be "default: 'test'" + + # Verify the reference was replaced with literal value + $yaml.content -join "`n" | Should -Match "echo true" + $yaml.content -join "`n" | Should -Not -Match "github\.event\.inputs\.hiddenInput" + } + + It 'hides string inputs and replaces references correctly' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with string input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " versionInput:", + " type: string", + " default: '+0.0'", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Use version", + " run: echo `${{ inputs.versionInput }}", + " - name: Use version again", + " run: echo `${{ github.event.inputs.versionInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "versionInput"; "value" = "+0.1"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide String" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('versionInput:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify both references were replaced with quoted string + $content = $yaml.content -join "`n" + $content | Should -Match "echo '\+0\.1'" + $content | Should -Not -Match "inputs\.versionInput" + $content | Should -Not -Match "github\.event\.inputs\.versionInput" + } + + It 'hides number inputs and replaces references correctly' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with number input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " countInput:", + " type: number", + " default: 5", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Use count", + " run: echo `${{ inputs.countInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "countInput"; "value" = 10; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Number" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('countInput:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the reference was replaced with number (unquoted) + $yaml.content -join "`n" | Should -Match "echo 10" + $yaml.content -join "`n" | Should -Not -Match "inputs\.countInput" + } + + It 'replaces hidden input references in if conditions' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with input used in if condition (without ${{ }}) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " runTests:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: windows-latest", + " if: github.event.inputs.runTests == 'true'", + " steps:", + " - name: Run", + " run: echo Running" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "runTests"; "value" = $true; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide If Condition" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('runTests:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the reference in if condition was replaced with string literal + # Bare references in if conditions are always treated as strings in GitHub Actions + # GitHub Actions comparisons are case-sensitive, so we use lowercase 'true' + $yaml.content -join "`n" | Should -Match "if: 'true' == 'true'" + $yaml.content -join "`n" | Should -Not -Match "github\.event\.inputs\.runTests" + } + + It 'does not replace job output references' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with needs.JobName.outputs.inputName pattern + # This should NOT be replaced even if an input with the same name exists and is hidden + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " branch:", + " type: string", + " default: 'main'", + "jobs:", + " Inputs:", + " runs-on: ubuntu-latest", + " outputs:", + " branch: `${{ steps.CreateInputs.outputs.branch }}", + " steps:", + " - name: Create inputs", + " id: CreateInputs", + " run: echo 'branch=main' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ Inputs ]", + " steps:", + " - name: Deploy", + " env:", + " branch: `${{ needs.Inputs.outputs.branch }}", + " run: echo Deploying to `$branch" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag for the branch input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "branch"; "value" = "production"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Job Output" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('branch:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.Inputs.outputs.branch is NOT replaced + # This is a job output reference, not a workflow input reference + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.Inputs\.outputs\.branch" + + # Verify that direct input references would be replaced if they existed + # (but they don't exist in this workflow, so we just verify the job reference remains) + $content | Should -Not -Match "github\.event\.inputs\.branch" + $content | Should -Not -Match "`\$\{\{ inputs\.branch \}\}" + } + + It 'does not replace parts of job output references with input names like output' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Edge case: input named "output" should not replace "inputs.outputs.output" or "outputs.output" + # Using lowercase "inputs" as job name to test the problematic case + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " output:", + " type: string", + " default: 'default'", + "jobs:", + " inputs:", + " runs-on: ubuntu-latest", + " outputs:", + " output: `${{ steps.CreateInputs.outputs.output }}", + " steps:", + " - name: Create inputs", + " id: CreateInputs", + " run: echo 'output=test' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ inputs ]", + " steps:", + " - name: Deploy", + " env:", + " myOutput: `${{ needs.inputs.outputs.output }}", + " run: echo Using `$myOutput" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag for the "output" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "output"; "value" = "hidden"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Input Named Output" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('output:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.inputs.outputs.output is NOT replaced + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.inputs\.outputs\.output" + $content | Should -Match "steps\.CreateInputs\.outputs\.output" + + # Verify that direct input references would be replaced if they existed + $content | Should -Not -Match "github\.event\.inputs\.output" + $content | Should -Not -Match "`\$\{\{ inputs\.output \}\}" + } + + It 'does not replace job output references when input is named outputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Edge case: input named "outputs" should not replace "inputs.outputs" in job output contexts + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " outputs:", + " type: string", + " default: 'default'", + "jobs:", + " Initialization:", + " runs-on: ubuntu-latest", + " outputs:", + " telemetryScopeJson: `${{ steps.init.outputs.telemetryScopeJson }}", + " steps:", + " - name: Initialize", + " id: init", + " run: echo 'telemetryScopeJson={}' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ Initialization ]", + " steps:", + " - name: Deploy", + " env:", + " telemetryScope: `${{ needs.Initialization.outputs.telemetryScopeJson }}", + " run: echo Using `$telemetryScope", + " - name: Use input", + " run: echo `${{ inputs.outputs }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag for the "outputs" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "outputs"; "value" = "hidden-value"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Input Named Outputs" + + # Verify the hidden input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.Initialization.outputs.telemetryScopeJson is NOT replaced + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.Initialization\.outputs\.telemetryScopeJson" + $content | Should -Match "steps\.init\.outputs\.telemetryScopeJson" + + # Verify that the actual input reference WAS replaced + $content | Should -Match "echo 'hidden-value'" + $content | Should -Not -Match "github\.event\.inputs\.outputs" + $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" + } + + It 'does not replace job outputs when job is named inputs and input is named outputs' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # This is the real edge case: job named "inputs" (lowercase) with outputs, and an input also named "outputs" + # needs.inputs.outputs.something should NOT be replaced (it's a job output reference) + # but ${{ inputs.outputs }} should be replaced (it's a workflow input reference) + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " outputs:", + " type: string", + " default: 'default'", + "jobs:", + " inputs:", + " runs-on: ubuntu-latest", + " outputs:", + " myValue: `${{ steps.init.outputs.myValue }}", + " steps:", + " - name: Initialize", + " id: init", + " run: echo 'myValue=test' >> `$GITHUB_OUTPUT", + " Deploy:", + " runs-on: ubuntu-latest", + " needs: [ inputs ]", + " steps:", + " - name: Use job output", + " env:", + " value: `${{ needs.inputs.outputs.myValue }}", + " run: echo Job output is `$value", + " - name: Use input", + " run: echo Input is `${{ inputs.outputs }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flag for the "outputs" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "outputs"; "value" = "hidden-input-value"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Job Named Inputs" + + # Verify the hidden input was removed + $inputsSection = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputsSection.Find('outputs:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify that needs.inputs.outputs.myValue is NOT replaced (job output reference) + $content = $yaml.content -join "`n" + $content | Should -Match "needs\.inputs\.outputs\.myValue" + + # Verify that steps.init.outputs.myValue is NOT replaced (step output reference) + $content | Should -Match "steps\.init\.outputs\.myValue" + + # Verify that inputs.outputs WAS replaced with the hidden value + $content | Should -Match "echo Input is 'hidden-input-value'" + $content | Should -Not -Match "`\$\{\{ inputs\.outputs \}\}" + } + + It 'silently skips hiding non-existent input' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with only one input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " existingInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.existingInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings trying to hide an input that doesn't exist + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "existingInput"; "value" = "test-value" }, + @{ "name" = "nonExistentInput"; "value" = "hidden-value"; "hide" = $true } + ) + } + + # Mock OutputWarning to verify no warning is issued + Mock OutputWarning { } + + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Non-Existent" } | Should -Not -Throw + + # Verify no warning was issued + Assert-MockCalled OutputWarning -Times 0 + + # Verify the existing input was updated but not hidden + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('existingInput:', [ref] $null, [ref] $null) | Should -Be $true + $yaml.Get('on:/workflow_dispatch:/inputs:/existingInput:/default:').content -join '' | Should -Be "default: 'test-value'" + + # Verify the workflow content was not affected by the non-existent input + $content = $yaml.content -join "`n" + $content | Should -Match "`\$\{\{ inputs\.existingInput \}\}" + } + + It 'hides multiple inputs in the same workflow' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with multiple inputs to hide + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " visibleInput:", + " type: string", + " default: ''", + " hiddenInput1:", + " type: boolean", + " default: false", + " hiddenInput2:", + " type: string", + " default: ''", + " hiddenInput3:", + " type: number", + " default: 0", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Use inputs", + " run: echo `${{ inputs.hiddenInput1 }} `${{ inputs.hiddenInput2 }} `${{ inputs.hiddenInput3 }}", + " - name: Use visible", + " run: echo `${{ inputs.visibleInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with multiple hidden inputs + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "visibleInput"; "value" = "visible" }, + @{ "name" = "hiddenInput1"; "value" = $true; "hide" = $true }, + @{ "name" = "hiddenInput2"; "value" = "hidden-string"; "hide" = $true }, + @{ "name" = "hiddenInput3"; "value" = 42; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Multiple Hidden" + + # Verify all hidden inputs were removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('hiddenInput1:', [ref] $null, [ref] $null) | Should -Be $false + $inputs.Find('hiddenInput2:', [ref] $null, [ref] $null) | Should -Be $false + $inputs.Find('hiddenInput3:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify visible input still exists + $inputs.Find('visibleInput:', [ref] $null, [ref] $null) | Should -Be $true + + # Verify all references were replaced + $content = $yaml.content -join "`n" + $content | Should -Match "echo true 'hidden-string' 42" + $content | Should -Match "echo `\$\{\{ inputs\.visibleInput \}\}" + $content | Should -Not -Match "inputs\.hiddenInput1" + $content | Should -Not -Match "inputs\.hiddenInput2" + $content | Should -Not -Match "inputs\.hiddenInput3" + } + + It 'replaces hidden input references in different expression contexts' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with inputs used in various contexts + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " hiddenBool:", + " type: boolean", + " default: false", + " hiddenString:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " if: inputs.hiddenBool == true", + " env:", + " MY_VAR: `${{ inputs.hiddenString }}", + " steps:", + " - name: Checkout", + " uses: actions/checkout@v4", + " with:", + " ref: `${{ inputs.hiddenString }}", + " - name: Step with if", + " if: inputs.hiddenBool", + " run: echo Running", + " - name: Step with env", + " env:", + " BRANCH: `${{ inputs.hiddenString }}", + " run: echo `$BRANCH" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with hide flags + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "hiddenBool"; "value" = $true; "hide" = $true }, + @{ "name" = "hiddenString"; "value" = "main"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Expression Contexts" + + # Verify all references were replaced correctly in different contexts + $content = $yaml.content -join "`n" + $content | Should -Match "if: true == true" # Job-level if + $content | Should -Match "MY_VAR: 'main'" # Job-level env + $content | Should -Match "ref: 'main'" # Action with parameter + $content | Should -Match "if: true" # Step-level if + $content | Should -Match "BRANCH: 'main'" # Step-level env + $content | Should -Not -Match "inputs\.hiddenBool" + $content | Should -Not -Match "inputs\.hiddenString" + } + + It 'does not replace partial input name matches' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with inputs that have overlapping names + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " test:", + " type: string", + " default: ''", + " testInput:", + " type: string", + " default: ''", + " mytest:", + " type: string", + " default: ''", + "jobs:", + " run:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Use inputs", + " run: echo `${{ inputs.test }} `${{ inputs.testInput }} `${{ inputs.mytest }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Hide only the "test" input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "test"; "value" = "hidden-test"; "hide" = $true }, + @{ "name" = "testInput"; "value" = "visible-testInput" }, + @{ "name" = "mytest"; "value" = "visible-mytest" } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Partial Match" + + # Verify only "test" was hidden, not "testInput" or "mytest" + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('test:', [ref] $null, [ref] $null) | Should -Be $false + $inputs.Find('testInput:', [ref] $null, [ref] $null) | Should -Be $true + $inputs.Find('mytest:', [ref] $null, [ref] $null) | Should -Be $true + + # Verify only inputs.test was replaced + $content = $yaml.content -join "`n" + $content | Should -Match "echo 'hidden-test' `\$\{\{ inputs\.testInput \}\} `\$\{\{ inputs\.mytest \}\}" + } + + It 'hides choice type inputs correctly' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with choice input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " releaseType:", + " type: choice", + " options:", + " - Release", + " - Prerelease", + " - Draft", + " default: Release", + "jobs:", + " release:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Create release", + " run: echo Creating `${{ inputs.releaseType }} release" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Hide the choice input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "releaseType"; "value" = "Prerelease"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Choice" + + # Verify the choice input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('releaseType:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the reference was replaced with the choice value + $content = $yaml.content -join "`n" + $content | Should -Match "echo Creating 'Prerelease' release" + $content | Should -Not -Match "inputs\.releaseType" + } + + It 'hides environment type inputs correctly' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with environment input + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " environment:", + " type: environment", + " default: ''", + "jobs:", + " deploy:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Deploy", + " run: echo Deploying to `${{ inputs.environment }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Hide the environment input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "environment"; "value" = "production"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Hide Environment" + + # Verify the environment input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('environment:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the reference was replaced + $content = $yaml.content -join "`n" + $content | Should -Match "echo Deploying to 'production'" + $content | Should -Not -Match "inputs\.environment" + } + + It 'handles input references without whitespace' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with various spacing patterns + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: No spaces", + " run: echo `${{inputs.myInput}}", + " - name: Normal spaces", + " run: echo `${{ inputs.myInput }}", + " - name: Asymmetric left", + " run: echo `${{ inputs.myInput}}", + " - name: Asymmetric right", + " run: echo `${{inputs.myInput }}", + " - name: Multiple on same line", + " run: echo `${{inputs.myInput}} and `${{ inputs.myInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Hide the input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = "test-value"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Spacing" + + # Verify all variations were replaced with the same normalized output + $content = $yaml.content -join "`n" + + # All spacing variations should be replaced with just the value (in quotes for strings) + # Verify the four single-value lines all got replaced the same way + ($content -split "`n" | Where-Object { $_ -match "^\s+run: echo 'test-value'`$" }).Count | Should -Be 4 -Because "Four lines with single value should all be normalized" + + # Verify the multiple-on-same-line case + $content -match "(?m)^\s+run: echo 'test-value' and 'test-value'`$" | Should -Be $true -Because "Multiple on same line should both be replaced" + + # Verify no input references remain + $content | Should -Not -Match "inputs\.myInput" + } + + It 'replaces hidden input references in complex expressions' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with complex expressions + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " enableTests:", + " type: boolean", + " default: false", + " branch:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " if: inputs.enableTests == true && inputs.branch != ''", + " steps:", + " - name: Conditional", + " if: inputs.enableTests && needs.job.outputs.value", + " run: echo Testing", + " - name: Fallback", + " run: echo `${{ inputs.branch || 'main' }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Hide both inputs + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "enableTests"; "value" = $true; "hide" = $true }, + @{ "name" = "branch"; "value" = "develop"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Complex Expressions" + + # Verify complex expressions were handled correctly + $content = $yaml.content -join "`n" + $content | Should -Match "if: true == true && 'develop' != ''" + $content | Should -Match "if: true && needs\.job\.outputs\.value" + $content | Should -Match "echo `\$\{\{ 'develop' \|\| 'main' \}\}" + $content | Should -Not -Match "inputs\.enableTests" + $content | Should -Not -Match "inputs\.branch" + } + + It 'handles case-insensitive input references' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with mixed case references + # Note: GitHub Actions itself is case-sensitive for input references in workflows + # This test verifies our hide feature respects the actual case used + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Lower case", + " run: echo `${{ inputs.myInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Hide the input (case should not matter for input name matching) + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "MYINPUT"; "value" = "test-value"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Case Sensitivity" + + # Verify the input was hidden (case-insensitive matching for input names) + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('myInput:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the reference was replaced + $content = $yaml.content -join "`n" + $content | Should -Match "echo 'test-value'" + } + + It 'handles empty string values when hiding' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " emptyInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.emptyInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Hide with empty string value + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "emptyInput"; "value" = ""; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Empty Value" + + # Verify the input was removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('emptyInput:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the reference was replaced with empty quoted string + $content = $yaml.content -join "`n" + $content | Should -Match "echo ''" + $content | Should -Not -Match "inputs\.emptyInput" + } + + It 'handles special characters in hidden values' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " input1:", + " type: string", + " default: ''", + " input2:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.input1 }} and `${{ inputs.input2 }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Hide with values containing special characters + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = "value with 'quotes'"; "hide" = $true }, + @{ "name" = "input2"; "value" = "+0.1"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Special Chars" + + # Verify values are properly escaped + $content = $yaml.content -join "`n" + $content | Should -Match "echo 'value with ''quotes''' and '\+0\.1'" + $content | Should -Not -Match "inputs\.input1" + $content | Should -Not -Match "inputs\.input2" + } + + It 'handles hidden input with no references in workflow' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML where input is defined but never referenced + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " unusedInput:", + " type: string", + " default: ''", + " usedInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.usedInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Hide the unused input + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "unusedInput"; "value" = "hidden-unused"; "hide" = $true }, + @{ "name" = "usedInput"; "value" = "visible" } + ) + } + + # Apply the defaults - should not throw + { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Unused Input" } | Should -Not -Throw + + # Verify the unused input was still removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('unusedInput:', [ref] $null, [ref] $null) | Should -Be $false + $inputs.Find('usedInput:', [ref] $null, [ref] $null) | Should -Be $true + } + + It 'handles workflow with all inputs hidden' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML with only inputs that will be hidden + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " input1:", + " type: string", + " default: ''", + " input2:", + " type: boolean", + " default: false", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.input1 }} `${{ inputs.input2 }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Hide all inputs + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "input1"; "value" = "hidden1"; "hide" = $true }, + @{ "name" = "input2"; "value" = $true; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - All Hidden" + + # Verify all inputs were removed + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('input1:', [ref] $null, [ref] $null) | Should -Be $false + $inputs.Find('input2:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify references were replaced + $content = $yaml.content -join "`n" + $content | Should -Match "echo 'hidden1' true" + } + + It 'applies last value when duplicate entries have different hide flags' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.myInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with duplicate entries where first has hide=false, last has hide=true + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = "first-value"; "hide" = $false }, + @{ "name" = "myInput"; "value" = "final-value"; "hide" = $true } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Duplicate Hide" + + # Verify last entry wins - input should be hidden + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('myInput:', [ref] $null, [ref] $null) | Should -Be $false + + # Verify the final hidden value was used + $content = $yaml.content -join "`n" + $content | Should -Match "echo 'final-value'" + $content | Should -Not -Match "inputs\.myInput" + } + + It 'applies last value when duplicate entries have hide reversed' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + # Create a test workflow YAML + $yamlContent = @( + "name: 'Test Workflow'", + "on:", + " workflow_dispatch:", + " inputs:", + " myInput:", + " type: string", + " default: ''", + "jobs:", + " test:", + " runs-on: ubuntu-latest", + " steps:", + " - name: Test", + " run: echo `${{ inputs.myInput }}" + ) + + $yaml = [Yaml]::new($yamlContent) + + # Create settings with duplicate entries where first has hide=true, last has hide=false + $repoSettings = @{ + "workflowDefaultInputs" = @( + @{ "name" = "myInput"; "value" = "first-value"; "hide" = $true }, + @{ "name" = "myInput"; "value" = "final-value"; "hide" = $false } + ) + } + + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow - Duplicate Hide Reversed" + + # Verify last entry wins - input should NOT be hidden + $inputs = $yaml.Get('on:/workflow_dispatch:/inputs:/') + $inputs.Find('myInput:', [ref] $null, [ref] $null) | Should -Be $true + + # Verify the final value was applied to the default + $yaml.Get('on:/workflow_dispatch:/inputs:/myInput:/default:').content -join '' | Should -Be "default: 'final-value'" + + # Verify the reference was NOT replaced (input is visible) + $content = $yaml.content -join "`n" + $content | Should -Match "echo `\$\{\{ inputs\.myInput \}\}" + $content | Should -Not -Match "echo 'final-value'" + } + + } # End of Context 'ApplyWorkflowDefaultInputs - Hide Feature' } Describe "ResolveFilePaths" {