Private/TestCaseManagement/Sync-TcmTestCaseFromRemote.ps1

function Sync-TcmTestCaseFromRemote {
    <#
        .SYNOPSIS
            Syncs remote test case data to local YAML files.

        .DESCRIPTION
            Takes a resolved test case object with remote work item data and syncs it to a local YAML file.
            InputObject must contain RemoteData with the work item information.

        .PARAMETER InputObject
            The resolved test case object with RemoteData containing the work item information.

        .PARAMETER OutputPath
            Relative path where to create the test case file (used when pulling a
            Work Item as a new test case file).

        .PARAMETER TestCasesRoot
            Root directory for test cases. If not specified, uses the default TestCases directory.

        .PARAMETER Force
            Force pull even if there are local changes (overwrite local).

        .EXAMPLE
            # Sync remote work item data to local file
            $resolvedObject | Sync-TcmTestCaseFromRemote
    #>


    [CmdletBinding(SupportsShouldProcess)]
    param(
        [Parameter(ValueFromPipeline, ValueFromPipelineByPropertyName)]
        [ValidateScript({ $_.PSTypeNames -contains $global:PSTypeNames.AzureDevOpsApi.TcmTestCaseExtended })]
        $InputObject,

        # Optional output path (used when pulling a Work Item as a new test case file)
        [string] $OutputPath,

        [string] $TestCasesRoot,

        [switch] $Force,

        [string] $Message,

        [string] $MessageColor = 'Cyan',

        [string] $ShouldProcessOperation = "Pull from Azure DevOps"
    )

    begin {
        # Get configuration
        $config = Get-TcmTestCaseConfig -TestCasesRoot $TestCasesRoot

        # Get credentials and project info
        $collectionUri = $config.azureDevOps.collectionUri
        $project = $config.azureDevOps.project

        if (-not $collectionUri -or -not $project) {
            throw "Azure DevOps collectionUri and project must be configured in config.yaml"
        }

        $processedCount = 0
        $hasErrors = $false
    }

    process {
        # Validate input
        if (-not $InputObject -or -not $InputObject.Id -or -not $InputObject.RemoteData) {
            throw "Invalid input: InputObject must have Id and RemoteData properties"
        }

        try {
            $workItemId = $InputObject.Id
            $workItem = $InputObject.RemoteWorkItem
            $testCaseData = $InputObject.RemoteData

            # Display message if provided
            if ($Message) {
                Write-Host "← $Message" -ForegroundColor $MessageColor
            }

            # Only process if Id looks like a Work Item ID (numeric)
            if ($workItemId -notmatch '^\d+$') {
                Write-Warning "Skipping '$workItemId': Not a numeric Work Item ID"
                return
            }

            $workItemId = [int]$workItemId

            if ($workItem -and $workItem.fields.'System.WorkItemType' -ne 'Test Case') {
                throw "Work item $workItemId is not a Test Case"
            }

            # Determine output path
            $outputPath = $InputObject.FilePath
            if (-not $outputPath) {
                if (-not $OutputPath) {
                    $sanitizedTitle = $workItem.fields.'System.Title' -replace '[^\w\s-]', '' -replace '\s+', '-'
                    $fileName = "$workItemId-$sanitizedTitle.yaml".ToLower()
                    $OutputPath = $fileName
                }
                $outputPath = Join-Path $config.TestCasesRoot $OutputPath
            }

            # Create test case data structure
            # Note: $testCaseData already has id and title set from ConvertFrom-TcmWorkItemToTestCase
            $fullTestCase = [ordered]@{
                testCase = $testCaseData
            }

            if ($PSCmdlet.ShouldProcess("Test case '$workItemId'", $ShouldProcessOperation)) {
                $actualFilePath = Save-TcmTestCaseYaml -FilePath $outputPath -Data $fullTestCase -TestCasesRoot $config.TestCasesRoot

                Write-Host "Synced test case from work item $workItemId to: $actualFilePath" -ForegroundColor Green
                $processedCount++

                # Update cache: after pull, local and remote should match
                # Re-read from disk to get the actual file content hash
                $savedTestCase = Get-TcmTestCaseFromFile -FilePath $actualFilePath
                $savedHash = Get-TcmStringHash -InputObject $savedTestCase
                Update-TcmHashCacheEntry `
                    -TestCasesRoot $config.TestCasesRoot `
                    -TestCaseId $workItemId `
                    -Hash $savedHash
            }
        } catch {
            $hasErrors = $true
            Write-Error "Failed to sync test case: $($_.Exception.Message)"
            throw
        }

    }

    end {
        if ($processedCount -gt 0 -and -not $hasErrors) {
            Write-Host "Pulled $processedCount test case(s) successfully." -ForegroundColor Green
        }
    }
}