Private/TestCaseManagement/Get-TcmTestCaseFromFile.ps1

function Get-TcmTestCaseFromFile {
    <#
        .SYNOPSIS
            Loads a test case from a YAML file.

        .PARAMETER FilePath
            Full path to the YAML file.

        .PARAMETER IncludeMetadata
            Include the legacy metadata block in the output when present.
    #>


    [CmdletBinding()]
    [OutputType('PSTypeNames.AzureDevOpsApi.TcmTestCase')]
    param(
        [Parameter(Mandatory)]
        [string] $FilePath,

        [switch] $IncludeMetadata
    )

    if (-not (Test-Path $FilePath)) {
        throw "Test case file not found: $FilePath"
    }

    try {
        $yamlContent = Get-Content -Path $FilePath -Raw
        $testCase = ConvertFrom-Yaml $yamlContent

        # Migrate legacy metadata fields into new layout if present
        if ($testCase.metadata) {
            # If metadata.id exists, rename to localId
            if ($testCase.metadata.PSObject.Properties.Name -contains 'id' -and -not ($testCase.metadata.PSObject.Properties.Name -contains 'localId')) {
                $testCase.metadata.localId = $testCase.metadata.id
                $testCase.metadata.PSObject.Properties.Remove('id') | Out-Null
            }

            # Move title and azureDevOpsId into testCase if they exist in metadata
            if ($testCase.metadata.PSObject.Properties.Name -contains 'title') {
                if (-not $testCase.testCase) { $testCase.testCase = @{} }
                if (-not $testCase.testCase.PSObject.Properties.Name -contains 'title') {
                    $testCase.testCase.title = $testCase.metadata.title
                }
                $testCase.metadata.PSObject.Properties.Remove('title') | Out-Null
            }

            if ($testCase.metadata.PSObject.Properties.Name -contains 'azureDevOpsId') {
                if (-not $testCase.testCase) { $testCase.testCase = @{} }
                if (-not $testCase.testCase.PSObject.Properties.Name -contains 'id') {
                    $testCase.testCase.id = $testCase.metadata.azureDevOpsId
                }
                $testCase.metadata.PSObject.Properties.Remove('azureDevOpsId') | Out-Null
            }
        }

        # Ensure testCase object exists and has title/id keys
        if (-not $testCase.testCase) { $testCase.testCase = @{} }
        if (-not $testCase.testCase.PSObject.Properties.Name -contains 'title') { $testCase.testCase.title = '' }
        if (-not $testCase.testCase.PSObject.Properties.Name -contains 'id') { $testCase.testCase.id = $null }

        # Calculate content hash for sync purposes (before removing metadata)
        $contentForHash = $testCase.testCase
        $hash = Get-TcmStringHash -InputObject $contentForHash

        # Remove metadata if not requested
        if (-not $IncludeMetadata) {
            $testCase.PSObject.Properties.Remove('metadata') | Out-Null
            $testCase.PSObject.Properties.Remove('history') | Out-Null
        }

        # Convert to PSCustomObject first
        $result = [PSCustomObject]$testCase
        $result.PSTypeNames.Insert(0, $global:PSTypeNames.AzureDevOpsApi.TcmTestCase)

        # Add computed properties AFTER conversion to PSCustomObject
        # (NoteProperties added to hashtables are lost during [PSCustomObject] conversion)
        $result | Add-Member -NotePropertyName 'FilePath' -NotePropertyValue $FilePath -Force
        $result | Add-Member -NotePropertyName 'FileName' -NotePropertyValue (Split-Path -Leaf $FilePath) -Force
        $result | Add-Member -NotePropertyName 'RelativePath' -NotePropertyValue (Get-TcmRelativeTestCasePath -FilePath $FilePath) -Force
        $result | Add-Member -NotePropertyName 'ContentHash' -NotePropertyValue $hash -Force

        return $result
    }
    catch {
        throw "Failed to parse test case file '$FilePath': $($_.Exception.Message)"
    }
}