diff --git a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 index a6366ea3ef..3482ce5d0c 100644 --- a/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 +++ b/Actions/CheckForUpdates/CheckForUpdates.HelperFunctions.ps1 @@ -330,146 +330,180 @@ function ApplyWorkflowDefaultInputs { return } + # Get workflow_call inputs + $workflowCallInputs = $yaml.Get('on:/workflow_call:/inputs:/') + # Apply defaults to matching inputs - foreach ($default in $repoSettings.workflowDefaultInputs) { - $inputName = $default.name - $defaultValue = $default.value - - # Check if this input exists in the workflow - $inputSection = $inputs.Get("$($inputName):/") - if (-not $inputSection) { - # Input is not present in the workflow - continue + $workflowDispatchInputsModified = $false + $workflowCallInputsModified = $false + + foreach ($defaultInput in $repoSettings.workflowDefaultInputs) { + # Apply to workflow_dispatch inputs and only update workflow_call if dispatch was modified + if (ApplyWorkflowDefaultInput -workflowName $workflowName -inputs $inputs -defaultInput $defaultInput) { + $workflowDispatchInputsModified = $true + + # Only apply to workflow_call inputs if the workflow_dispatch input was modified + if ($workflowCallInputs) { + if (ApplyWorkflowDefaultInput -workflowName $workflowName -inputs $workflowCallInputs -defaultInput $defaultInput) { + $workflowCallInputsModified = $true + } + } } + } - # 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() - } + # Update the workflow_dispatch section if modified + if ($workflowDispatchInputsModified) { + $workflowDispatch.Replace('inputs:/', $inputs.content) + $yaml.Replace('on:/workflow_dispatch:/', $workflowDispatch.content) + } + + # Update workflow_call inputs if modified + if ($workflowCallInputsModified) { + $yaml.Replace('on:/workflow_call:/inputs:/', $workflowCallInputs.content) + } +} + +function ApplyWorkflowDefaultInput { + Param( + [string] $workflowName, + [Yaml] $inputs, + [hashtable] $defaultInput + ) + + $inputName = $defaultInput.name + $defaultValue = $defaultInput.value + + # Check if this input exists in the inputs collection + $inputSection = $inputs.Get("$($inputName):/") + if (-not $inputSection) { + # Input is not present in the workflow + return $false + } + + # 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") + # 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 } - 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('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 + } + 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 workflow_dispatch section with modified inputs - $workflowDispatch.Replace('inputs:/', $inputs.content) + # Update the inputs collection with the modified input section + $inputs.Replace("$($inputName):/", $inputSection.content) - # Update the on: section with modified workflow_dispatch - $yaml.Replace('on:/workflow_dispatch:/', $workflowDispatch.content) + return $true } function GetWorkflowContentWithChangesFromSettings { diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 3c7cd00000..b4268dd8c0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -2,6 +2,14 @@ - Issue 2055 When using versioningStrategy 3+16, you get an error when building - AL-Go repositories with large amounts of projects may run into issues with too large environment variables +- Discussion 1855 Add trigger 'workflow_call' to workflow 'Update AL-Go System Files' for reusability + +### Set default values for workflow inputs + +The `workflowDefaultInputs` setting now also applies to `workflow_call` inputs when an input with the same name exists for `workflow_dispatch`. +This ensures consistent default values across both manual workflow runs and reusable workflow calls. + +Read more at [workflowDefaultInputs](https://aka.ms/algosettings#workflowDefaultInputs). ### AL-Go Telemetry updates diff --git a/Scenarios/settings.md b/Scenarios/settings.md index c650107348..d22b2f77a8 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.
**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" }`
`]` | +| 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. If a workflow also has matching `workflow_call` inputs, those will receive the same default values to ensure consistency in reusable workflows. 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 matching `workflow_dispatch` inputs. If a workflow also defines `workflow_call` inputs with the same names, those inputs will receive the same defaults to ensure consistency between manual and programmatic workflow invocations.
**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" }`
`]` | diff --git a/Templates/AppSource App/.github/workflows/UpdateGitHubGoSystemFiles.yaml b/Templates/AppSource App/.github/workflows/UpdateGitHubGoSystemFiles.yaml index 81f4bfea61..30308f8d44 100644 --- a/Templates/AppSource App/.github/workflows/UpdateGitHubGoSystemFiles.yaml +++ b/Templates/AppSource App/.github/workflows/UpdateGitHubGoSystemFiles.yaml @@ -19,6 +19,26 @@ on: description: Specify a comma-separated list of branches to update. Wildcards are supported. The AL-Go settings will be read for every branch. Leave empty to update the current branch only. required: false default: '' + workflow_call: + inputs: + templateUrl: + description: Template Repository URL (current is {TEMPLATEURL}) + type: string + required: false + default: '' + downloadLatest: + description: Download latest from template repository + type: boolean + default: true + directCommit: + description: Direct Commit? + type: boolean + default: false + includeBranches: + description: Specify a comma-separated list of branches to update. Wildcards are supported. The AL-Go settings will be read for every branch. Leave empty to update the current branch only. + type: string + required: false + default: '' permissions: actions: read @@ -117,8 +137,8 @@ jobs: downloadLatest: '${{ github.event.inputs.downloadLatest }}' run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - if('${{ github.event_name }}' -eq 'workflow_dispatch') { - Write-Host "Using inputs from workflow_dispatch event" + if('${{ github.event_name }}' -in 'workflow_dispatch', 'workflow_call') { + Write-Host "Using inputs from ${{ github.event_name }} event" $directCommit = $env:directCommit $downloadLatest = $env:downloadLatest } diff --git a/Templates/Per Tenant Extension/.github/workflows/UpdateGitHubGoSystemFiles.yaml b/Templates/Per Tenant Extension/.github/workflows/UpdateGitHubGoSystemFiles.yaml index 81f4bfea61..30308f8d44 100644 --- a/Templates/Per Tenant Extension/.github/workflows/UpdateGitHubGoSystemFiles.yaml +++ b/Templates/Per Tenant Extension/.github/workflows/UpdateGitHubGoSystemFiles.yaml @@ -19,6 +19,26 @@ on: description: Specify a comma-separated list of branches to update. Wildcards are supported. The AL-Go settings will be read for every branch. Leave empty to update the current branch only. required: false default: '' + workflow_call: + inputs: + templateUrl: + description: Template Repository URL (current is {TEMPLATEURL}) + type: string + required: false + default: '' + downloadLatest: + description: Download latest from template repository + type: boolean + default: true + directCommit: + description: Direct Commit? + type: boolean + default: false + includeBranches: + description: Specify a comma-separated list of branches to update. Wildcards are supported. The AL-Go settings will be read for every branch. Leave empty to update the current branch only. + type: string + required: false + default: '' permissions: actions: read @@ -117,8 +137,8 @@ jobs: downloadLatest: '${{ github.event.inputs.downloadLatest }}' run: | $errorActionPreference = "Stop"; $ProgressPreference = "SilentlyContinue"; Set-StrictMode -Version 2.0 - if('${{ github.event_name }}' -eq 'workflow_dispatch') { - Write-Host "Using inputs from workflow_dispatch event" + if('${{ github.event_name }}' -in 'workflow_dispatch', 'workflow_call') { + Write-Host "Using inputs from ${{ github.event_name }} event" $directCommit = $env:directCommit $downloadLatest = $env:downloadLatest } diff --git a/Tests/CheckForUpdates.Action.Test.ps1 b/Tests/CheckForUpdates.Action.Test.ps1 index e776247755..be6cfa9b98 100644 --- a/Tests/CheckForUpdates.Action.Test.ps1 +++ b/Tests/CheckForUpdates.Action.Test.ps1 @@ -268,11 +268,19 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $modifiedContent."srcSetting" | Should -Be "value1" $modifiedContent."`$schema" | Should -Be "someSchema" } +} + +Describe "CheckForUpdates Action: ApplyWorkflowDefaultInputs Tests" { + BeforeAll { + $actionName = "CheckForUpdates" + $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve + . (Join-Path -Path $scriptRoot -ChildPath "CheckForUpdates.HelperFunctions.ps1") + } It 'ApplyWorkflowDefaultInputs applies default values to workflow inputs' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML with workflow_dispatch inputs + # Create a test workflow YAML with both workflow_dispatch and workflow_call inputs $yamlContent = @( "name: 'Test Workflow'", "on:", @@ -290,6 +298,20 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " description: Version number", " required: false", " default: ''", + " workflow_call:", + " 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", @@ -311,10 +333,15 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Apply the defaults ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - # Verify the defaults were applied + # Verify the defaults were applied to workflow_dispatch $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'" + + # Verify the defaults were also applied to workflow_call + $yaml.Get('on:/workflow_call:/inputs:/directCommit:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_call:/inputs:/useGhTokenWorkflow:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_call:/inputs:/updateVersionNumber:/default:').content -join '' | Should -Be "default: '+0.1'" } It 'ApplyWorkflowDefaultInputs handles empty workflowDefaultInputs array' { @@ -410,7 +437,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowDefaultInputs applies multiple defaults to same workflow' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML with multiple inputs + # Create a test workflow YAML with multiple inputs in both workflow_dispatch and workflow_call $yamlContent = @( "name: 'Test Workflow'", "on:", @@ -431,6 +458,23 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " - optionA", " - optionB", " default: optionA", + " workflow_call:", + " 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" @@ -451,89 +495,32 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Apply the defaults ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" - # Verify all defaults were applied + # Verify all defaults were applied to workflow_dispatch $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'" + # Verify all defaults were also applied to workflow_call + $yaml.Get('on:/workflow_call:/inputs:/input1:/default:').content -join '' | Should -Be 'default: true' + $yaml.Get('on:/workflow_call:/inputs:/input2:/default:').content -join '' | Should -Be 'default: 5' + $yaml.Get('on:/workflow_call:/inputs:/input3:/default:').content -join '' | Should -Be "default: 'test-value'" + $yaml.Get('on:/workflow_call:/inputs:/input4:/default:').content -join '' | Should -Be "default: 'optionB'" } - It 'ApplyWorkflowDefaultInputs is case-insensitive for input names' { + It 'ApplyWorkflowDefaultInputs ignores defaults for non-existent inputs' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML with specific casing + # Create a test workflow YAML with both workflow_dispatch and workflow_call $yamlContent = @( "name: 'Test Workflow'", "on:", " workflow_dispatch:", " inputs:", - " MyInput:", + " existingInput:", " 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:", + " workflow_call:", " inputs:", " existingInput:", " type: boolean", @@ -562,7 +549,7 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { It 'ApplyWorkflowDefaultInputs applies only existing inputs when mixed with non-existent inputs' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML + # Create a test workflow YAML with both workflow_dispatch and workflow_call $yamlContent = @( "name: 'Test Workflow'", "on:", @@ -571,6 +558,11 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " existingInput:", " type: boolean", " default: false", + " workflow_call:", + " inputs:", + " existingInput:", + " type: boolean", + " default: false", "jobs:", " test:", " runs-on: ubuntu-latest" @@ -590,14 +582,17 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { # Apply the defaults - should not throw { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Not -Throw - # Verify only the existing input was modified + # Verify only the existing input was modified in workflow_dispatch $yaml.Get('on:/workflow_dispatch:/inputs:/existingInput:/default:').content -join '' | Should -Be 'default: true' + + # Verify only the existing input was also modified in workflow_call + $yaml.Get('on:/workflow_call:/inputs:/existingInput:/default:').content -join '' | Should -Be 'default: true' } - It 'ApplyWorkflowDefaultInputs handles special YAML characters in string values' { + It 'ApplyWorkflowDefaultInputs applies last value when multiple entries have same input name' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML + # Create a test workflow YAML with both workflow_dispatch and workflow_call $yamlContent = @( "name: 'Test Workflow'", "on:", @@ -607,11 +602,16 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { " type: string", " default: ''", " input2:", + " type: boolean", + " default: false", + " workflow_call:", + " inputs:", + " input1:", " type: string", " default: ''", - " input3:", - " type: string", - " default: ''", + " input2:", + " type: boolean", + " default: false", "jobs:", " test:", " runs-on: ubuntu-latest" @@ -619,37 +619,50 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $yaml = [Yaml]::new($yamlContent) - # Create settings with special YAML characters + # 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" = "value: with colon" }, - @{ "name" = "input2"; "value" = "value # with comment" }, - @{ "name" = "input3"; "value" = "value with 'quotes' inside" } + @{ "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 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'" + # Verify "last wins" - the final value for input1 should be applied to workflow_dispatch + $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" also applies to workflow_call + $yaml.Get('on:/workflow_call:/inputs:/input1:/default:').content -join '' | Should -Be "default: 'final-value'" + $yaml.Get('on:/workflow_call:/inputs:/input2:/default:').content -join '' | Should -Be 'default: false' } - It 'ApplyWorkflowDefaultInputs handles environment input type' { + It 'ApplyWorkflowDefaultInputs updates workflow_call inputs only when matching workflow_dispatch input exists' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML with environment type + # Create a workflow where workflow_call has an input that also exists in workflow_dispatch + # and another input that does NOT exist in workflow_dispatch $yamlContent = @( "name: 'Test Workflow'", "on:", " workflow_dispatch:", " inputs:", - " environmentName:", - " description: Environment to deploy to", - " type: environment", - " default: ''", + " sharedInput:", + " type: string", + " default: 'dispatch-default'", + " workflow_call:", + " inputs:", + " sharedInput:", + " type: string", + " default: 'call-default'", + " callOnlyInput:", + " type: boolean", + " default: false", "jobs:", " test:", " runs-on: ubuntu-latest" @@ -657,66 +670,80 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $yaml = [Yaml]::new($yamlContent) - # Create settings with environment value (should be treated as string) + # Provide defaults for both inputs $repoSettings = @{ "workflowDefaultInputs" = @( - @{ "name" = "environmentName"; "value" = "production" } + @{ "name" = "sharedInput"; "value" = "new-value" }, + @{ "name" = "callOnlyInput"; "value" = $true } ) } # 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'" + # Verify workflow_dispatch: sharedInput should be updated + $yaml.Get('on:/workflow_dispatch:/inputs:/sharedInput:/default:').content -join '' | Should -Be "default: 'new-value'" + + # Verify workflow_call: Only sharedInput should be updated (exists in workflow_dispatch) + $yaml.Get('on:/workflow_call:/inputs:/sharedInput:/default:').content -join '' | Should -Be "default: 'new-value'" + + # Verify workflow_call: callOnlyInput should NOT be updated (doesn't exist in workflow_dispatch) + $yaml.Get('on:/workflow_call:/inputs:/callOnlyInput:/default:').content -join '' | Should -Be 'default: false' } - It 'ApplyWorkflowDefaultInputs validates invalid choice value not in options' { + It 'ApplyWorkflowDefaultInputs handles workflow with only workflow_call inputs' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML with choice input + # Create a workflow that only has workflow_call (no workflow_dispatch) + # Per the rule: workflow_call inputs are only updated when matching workflow_dispatch input exists + # Since there's no workflow_dispatch, no updates should be applied $yamlContent = @( - "name: 'Test Workflow'", + "name: 'Reusable Workflow'", "on:", - " workflow_dispatch:", + " workflow_call:", " inputs:", - " deploymentType:", - " type: choice", - " options:", - " - Development", - " - Staging", - " - Production", - " default: Development", + " input1:", + " type: string", + " default: ''", + " input2:", + " type: boolean", + " default: false", "jobs:", " test:", " runs-on: ubuntu-latest" ) $yaml = [Yaml]::new($yamlContent) + $originalContent = $yaml.content -join "`n" - # Create settings with invalid choice value + # Provide defaults $repoSettings = @{ "workflowDefaultInputs" = @( - @{ "name" = "deploymentType"; "value" = "Testing" } + @{ "name" = "input1"; "value" = "reusable-value" }, + @{ "name" = "input2"; "value" = $true } ) } - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*not a valid choice*" + # Apply the defaults + ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + + # Verify YAML was not modified + $yaml.content -join "`n" | Should -Be $originalContent } - It 'ApplyWorkflowDefaultInputs handles inputs without existing default' { + It 'ApplyWorkflowDefaultInputs handles workflow_call without inputs section' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML with input without default + # Create a workflow with workflow_call that has an inputs section but no actual inputs $yamlContent = @( "name: 'Test Workflow'", "on:", " workflow_dispatch:", " inputs:", - " myInput:", - " description: My Input", - " required: false", + " dispatchInput:", + " type: string", + " default: ''", + " workflow_call:", "jobs:", " test:", " runs-on: ubuntu-latest" @@ -724,380 +751,468 @@ Describe "CheckForUpdates Action: CheckForUpdates.HelperFunctions.ps1" { $yaml = [Yaml]::new($yamlContent) - # Create settings with workflow input defaults + # Provide defaults $repoSettings = @{ "workflowDefaultInputs" = @( - @{ "name" = "myInput"; "value" = "test-value" } + @{ "name" = "dispatchInput"; "value" = "test-value" }, + @{ "name" = "nonExistentInput"; "value" = "ignored" } ) } # 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'" + # Verify workflow_dispatch input was updated + $yaml.Get('on:/workflow_dispatch:/inputs:/dispatchInput:/default:').content -join '' | Should -Be "default: 'test-value'" + + # Verify workflow_call remains unchanged (no inputs to update) + $yaml.Get('on:/workflow_call:').content -join '' | Should -Be 'workflow_call:' + } +} + +Describe "CheckForUpdates Action: ApplyWorkflowDefaultInput Tests" { + BeforeAll { + $actionName = "CheckForUpdates" + $scriptRoot = Join-Path $PSScriptRoot "..\Actions\$actionName" -Resolve + . (Join-Path -Path $scriptRoot -ChildPath "CheckForUpdates.HelperFunctions.ps1") } - It 'ApplyWorkflowDefaultInputs handles different value types' { + It 'ApplyWorkflowDefaultInput ignores default when input with same name does not exist' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML + # Create workflow with input $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" + "inputs:", + " myInput:", + " description: 'My input'", + " type: boolean", + " default: false" ) $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') - # Create settings with different value types - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "boolInput"; "value" = $true }, - @{ "name" = "stringInput"; "value" = "test" }, - @{ "name" = "numberInput"; "value" = 42 } - ) - } + $originalContent = $yaml.content -join "`n" - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + # Apply default value + $defaultInput = @{ 'name' = 'otherInput'; 'value' = $true } + $result = ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput - # 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' + # Verify no changes were made and function returned false + $result | Should -Be $false + $yaml.content -join "`n" | Should -Be $originalContent } - It 'ApplyWorkflowDefaultInputs validates boolean type mismatch' { + It 'ApplyWorkflowDefaultInput updates only input with matching name' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML with boolean input + # Create workflow with input $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " boolInput:", - " type: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" + "inputs:", + " input1:", + " description: 'Input 1'", + " type: boolean", + " default: false", + " input2:", + " description: 'Input 2'", + " type: boolean", + " default: false" ) $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') - # Create settings with wrong type (string instead of boolean) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "boolInput"; "value" = "not a boolean" } - ) - } + # Apply default value + $defaultInput = @{ 'name' = 'input1'; 'value' = $true } + $result = ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected boolean value*" + # Verify changes were made and function returned true + $result | Should -Be $true + $inputs.Get('input1:/default:').content -join '' | Should -Be "default: true" + $inputs.Get('input2:/default:').content -join '' | Should -Be "default: false" } - It 'ApplyWorkflowDefaultInputs validates number type mismatch' { + It 'ApplyWorkflowDefaultInput is case-insensitive for input names' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML with number input + # Create workflow with specific casing $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " numberInput:", - " type: number", - " default: 0", - "jobs:", - " test:", - " runs-on: ubuntu-latest" + "inputs:", + " MyInput:", + " description: 'My input'", + " type: boolean", + " default: false" ) $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') - # Create settings with wrong type (string instead of number) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "numberInput"; "value" = "not a number" } - ) - } + # Apply default value with different casing + $defaultInput = @{ 'name' = 'myInput'; 'value' = $true } + $result = ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected number value*" + # Verify default was applied despite case difference (case-insensitive matching) + $result | Should -Be $true + $inputs.Get('MyInput:/default:').content -join '' | Should -Be 'default: true' } - It 'ApplyWorkflowDefaultInputs validates string type mismatch' { + It 'ApplyWorkflowDefaultInput inserts default line when missing' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML with string input + # Create workflow with input without default line $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " stringInput:", - " type: string", - " default: ''", - "jobs:", - " test:", - " runs-on: ubuntu-latest" + "inputs:", + " myInput:", + " description: 'My input without default'", + " type: string" ) $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') - # Create settings with wrong type (boolean instead of string) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "stringInput"; "value" = $true } - ) - } + # Apply default value + $defaultInput = @{ 'name' = 'myInput'; 'value' = 'new-default' } + $result = ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*Expected string value*" + # Verify default line was inserted and function returned true + $result | Should -Be $true + $inputSection = $inputs.Get('myInput:/') + $inputSection.content -join '' | Should -BeLike "*default: 'new-default'*" } - It 'ApplyWorkflowDefaultInputs validates choice type' { + It 'ApplyWorkflowDefaultInput handles special YAML characters in string values' { . (Join-Path $scriptRoot "yamlclass.ps1") - # Create a test workflow YAML with choice input + # Test colon $yamlContent = @( - "name: 'Test Workflow'", - "on:", - " workflow_dispatch:", - " inputs:", - " choiceInput:", - " type: choice", - " options:", - " - option1", - " - option2", - " default: option1", - "jobs:", - " test:", - " runs-on: ubuntu-latest" + "inputs:", + " input1:", + " type: string", + " default: ''" ) + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + $defaultInput = @{ 'name' = 'input1'; 'value' = 'value: with colon' } + ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput + $inputSection = $inputs.Get('input1:/') + $inputSection.Get('default:').content -join '' | Should -Be "default: 'value: with colon'" + # Test hash/comment + $yamlContent = @( + "inputs:", + " input2:", + " type: string", + " default: ''" + ) $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + $defaultInput = @{ 'name' = 'input2'; 'value' = 'value # with comment' } + ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput + $inputSection = $inputs.Get('input2:/') + $inputSection.Get('default:').content -join '' | Should -Be "default: 'value # with comment'" - # Create settings with correct type (string for choice) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "choiceInput"; "value" = "option2" } - ) - } + # Test quotes + $yamlContent = @( + "inputs:", + " input3:", + " type: string", + " default: ''" + ) + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + $defaultInput = @{ 'name' = 'input3'; 'value' = "value with 'quotes' inside" } + ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput + $inputSection = $inputs.Get('input3:/') + $inputSection.Get('default:').content -join '' | Should -Be "default: 'value with ''quotes'' inside'" + } - # 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 'ApplyWorkflowDefaultInput handles environment input type' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $yamlContent = @( + "inputs:", + " environmentName:", + " description: Environment to deploy to", + " type: environment", + " default: ''" + ) + + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + + # Apply environment value (should be treated as string) + $defaultInput = @{ 'name' = 'environmentName'; 'value' = 'production' } + ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput + + # Verify environment value is set as string + $inputSection = $inputs.Get('environmentName:/') + $inputSection.Get('default:').content -join '' | Should -Be "default: 'production'" } - It 'ApplyWorkflowDefaultInputs validates choice value is in available options' { + It 'ApplyWorkflowDefaultInput 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:", - " choiceInput:", - " type: choice", - " options:", - " - option1", - " - option2", - " - option3", - " default: option1", - "jobs:", - " test:", - " runs-on: ubuntu-latest" + "inputs:", + " deploymentType:", + " type: choice", + " options:", + " - Development", + " - Staging", + " - Production", + " default: Development" ) $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') - # Create settings with invalid choice value - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "choiceInput"; "value" = "invalidOption" } - ) - } + # Apply invalid choice value - should throw + $defaultInput = @{ 'name' = 'deploymentType'; 'value' = 'Testing' } + { ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput } | Should -Throw "*not a valid choice*" + } + + It 'ApplyWorkflowDefaultInput handles different value types' { + . (Join-Path $scriptRoot "yamlclass.ps1") - # Apply the defaults - should throw validation error - { ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" } | Should -Throw "*not a valid choice*" + # Test boolean + $yamlContent = @( + "inputs:", + " boolInput:", + " type: boolean", + " default: false" + ) + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + $defaultInput = @{ 'name' = 'boolInput'; 'value' = $true } + ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput + $inputSection = $inputs.Get('boolInput:/') + $inputSection.Get('default:').content -join '' | Should -Be 'default: true' + + # Test string + $yamlContent = @( + "inputs:", + " stringInput:", + " type: string", + " default: ''" + ) + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + $defaultInput = @{ 'name' = 'stringInput'; 'value' = 'test' } + ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput + $inputSection = $inputs.Get('stringInput:/') + $inputSection.Get('default:').content -join '' | Should -Be "default: 'test'" + + # Test number + $yamlContent = @( + "inputs:", + " numberInput:", + " type: number", + " default: 0" + ) + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + $defaultInput = @{ 'name' = 'numberInput'; 'value' = 42 } + ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput + $inputSection = $inputs.Get('numberInput:/') + $inputSection.Get('default:').content -join '' | Should -Be 'default: 42' } - It 'ApplyWorkflowDefaultInputs validates choice value with case-sensitive matching' { + It 'ApplyWorkflowDefaultInput validates boolean type mismatch' { . (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" + "inputs:", + " boolInput:", + " type: boolean", + " default: false" ) $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') - # Test 1: Exact case match should succeed - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "releaseTypeInput"; "value" = "Prerelease" } - ) - } + # Apply wrong type - should throw + $defaultInput = @{ 'name' = 'boolInput'; 'value' = 'not a boolean' } + { ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput } | Should -Throw "*Expected boolean value*" + } - { 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'" + It 'ApplyWorkflowDefaultInput validates number type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") - # Test 2: Wrong case should fail with case-sensitive error message - $yaml2 = [Yaml]::new($yamlContent) - $repoSettings2 = @{ - "workflowDefaultInputs" = @( - @{ "name" = "releaseTypeInput"; "value" = "prerelease" } - ) - } + $yamlContent = @( + "inputs:", + " numberInput:", + " type: number", + " default: 0" + ) - { ApplyWorkflowDefaultInputs -yaml $yaml2 -repoSettings $repoSettings2 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') - # Test 3: Uppercase version should also fail - $yaml3 = [Yaml]::new($yamlContent) - $repoSettings3 = @{ - "workflowDefaultInputs" = @( - @{ "name" = "releaseTypeInput"; "value" = "PRERELEASE" } - ) - } + # Apply wrong type - should throw + $defaultInput = @{ 'name' = 'numberInput'; 'value' = 'not a number' } + { ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput } | Should -Throw "*Expected number value*" + } - { ApplyWorkflowDefaultInputs -yaml $yaml3 -repoSettings $repoSettings3 -workflowName "Test Workflow" } | Should -Throw "*case-sensitive match required*" + It 'ApplyWorkflowDefaultInput validates string type mismatch' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $yamlContent = @( + "inputs:", + " stringInput:", + " type: string", + " default: ''" + ) + + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + + # Apply wrong type - should throw + $defaultInput = @{ 'name' = 'stringInput'; 'value' = $true } + { ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput } | Should -Throw "*Expected string value*" } - It 'ApplyWorkflowDefaultInputs handles inputs without type specification' { + It 'ApplyWorkflowDefaultInput validates choice type accepts valid option' { . (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" + "inputs:", + " choiceInput:", + " type: choice", + " options:", + " - option1", + " - option2", + " default: option1" ) $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') - # Create settings with string value (should work without warning) - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "noTypeInput"; "value" = "string value" } - ) - } + # Apply valid choice - should succeed + $defaultInput = @{ 'name' = 'choiceInput'; 'value' = 'option2' } + { ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput } | Should -Not -Throw + $inputSection = $inputs.Get('choiceInput:/') + $inputSection.Get('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:/noTypeInput:/default:').content -join '' | Should -Be "default: 'string value'" + It 'ApplyWorkflowDefaultInput validates choice value is in available options' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $yamlContent = @( + "inputs:", + " choiceInput:", + " type: choice", + " options:", + " - option1", + " - option2", + " - option3", + " default: option1" + ) + + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + + # Apply invalid choice - should throw + $defaultInput = @{ 'name' = 'choiceInput'; 'value' = 'invalidOption' } + { ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput } | Should -Throw "*not a valid choice*" } - It 'ApplyWorkflowDefaultInputs escapes single quotes in string values' { + It 'ApplyWorkflowDefaultInput validates choice value with case-sensitive matching' { . (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" + "inputs:", + " releaseTypeInput:", + " type: choice", + " options:", + " - Release", + " - Prerelease", + " - Draft", + " default: Release" ) + # Test 1: Exact case match should succeed $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + $defaultInput = @{ 'name' = 'releaseTypeInput'; 'value' = 'Prerelease' } + { ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput } | Should -Not -Throw + $inputSection = $inputs.Get('releaseTypeInput:/') + $inputSection.Get('default:').content -join '' | Should -Be "default: 'Prerelease'" - # Create settings with string value containing single quote - $repoSettings = @{ - "workflowDefaultInputs" = @( - @{ "name" = "nameInput"; "value" = "O'Brien" } - ) - } + # Test 2: Wrong case should fail + $yaml2 = [Yaml]::new($yamlContent) + $inputs2 = $yaml2.Get('inputs:/') + $defaultInput2 = @{ 'name' = 'releaseTypeInput'; 'value' = 'prerelease' } + { ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs2 -defaultInput $defaultInput2 } | Should -Throw "*case-sensitive match required*" - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + # Test 3: Uppercase should also fail + $yaml3 = [Yaml]::new($yamlContent) + $inputs3 = $yaml3.Get('inputs:/') + $defaultInput3 = @{ 'name' = 'releaseTypeInput'; 'value' = 'PRERELEASE' } + { ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs3 -defaultInput $defaultInput3 } | Should -Throw "*case-sensitive match required*" + } - # 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 'ApplyWorkflowDefaultInput handles inputs without type specification' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $yamlContent = @( + "inputs:", + " noTypeInput:", + " description: Input without type", + " default: ''" + ) + + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + + # Apply string value (should work as type defaults to string) + $defaultInput = @{ 'name' = 'noTypeInput'; 'value' = 'string value' } + { ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput } | Should -Not -Throw + $inputSection = $inputs.Get('noTypeInput:/') + $inputSection.Get('default:').content -join '' | Should -Be "default: 'string value'" } - It 'ApplyWorkflowDefaultInputs applies last value when multiple entries have same input name' { + It 'ApplyWorkflowDefaultInput escapes single quotes 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: boolean", - " default: false", - "jobs:", - " test:", - " runs-on: ubuntu-latest" + "inputs:", + " nameInput:", + " type: string", + " default: ''" ) $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') - # 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 value with single quote + $defaultInput = @{ 'name' = 'nameInput'; 'value' = "O'Brien" } + ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput - # Apply the defaults - ApplyWorkflowDefaultInputs -yaml $yaml -repoSettings $repoSettings -workflowName "Test Workflow" + # Verify single quote is escaped per YAML spec (doubled) + $inputSection = $inputs.Get('nameInput:/') + $inputSection.Get('default:').content -join '' | Should -Be "default: 'O''Brien'" + } - # 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' + It 'ApplyWorkflowDefaultInput replaces existing default value' { + . (Join-Path $scriptRoot "yamlclass.ps1") + + $yamlContent = @( + "inputs:", + " myInput:", + " type: string", + " default: 'old-value'" + ) + + $yaml = [Yaml]::new($yamlContent) + $inputs = $yaml.Get('inputs:/') + + # Apply new value + $defaultInput = @{ 'name' = 'myInput'; 'value' = 'new-value' } + ApplyWorkflowDefaultInput -workflowName 'TestWorkflow' -inputs $inputs -defaultInput $defaultInput + + # Verify default was replaced + $inputSection = $inputs.Get('myInput:/') + $inputSection.Get('default:').content -join '' | Should -Be "default: 'new-value'" } }