Private/TestCaseManagement/Resolve-TcmTestCaseSyncStatus.ps1
function Resolve-TcmTestCaseSyncStatus { <# .SYNOPSIS Determines the sync status of a test case by comparing local and remote states. .DESCRIPTION Uses a hash cache (.tcm-hashes.json) to implement 3-way merge logic: - Compares current local hash with cached local hash (detects local changes) - Compares current remote hash with cached remote hash (detects remote changes) - Determines sync status based on change patterns: * synced: No changes detected * local-changes: Only local changed since last sync * remote-changes: Only remote changed since last sync * conflict: Both local and remote changed since last sync * new-local: Test case exists only locally (non-numeric ID or no cache entry) * new-remote: Test case exists only remotely (no local file) .PARAMETER InputObject Extended test case object from Get-TcmTestCase containing Id, LocalData, RemoteData, LocalDataHash, RemoteDataHash, etc. .PARAMETER Config Configuration object from Get-TcmTestCaseConfig. .OUTPUTS The input object with SyncStatus property set. #> [CmdletBinding()] [OutputType('PSTypeNames.AzureDevOpsApi.TcmTestCaseExtended')] param( [Parameter(Mandatory, ValueFromPipeline)] [PSTypeName('PSTypeNames.AzureDevOpsApi.TcmTestCaseExtended')] [object] $InputObject, [Parameter(Mandatory)] [hashtable] $Config ) try { $Id = $InputObject.Id # If ID is not numeric, it's a new local test case (not synced yet) if (-not ($Id -match '^\d+$')) { $InputObject.SyncStatus = "new-local" return $InputObject } # Load hash cache $hashCache = Get-TcmHashCache -TestCasesRoot $Config.TestCasesRoot $cachedEntry = $hashCache["$Id"] # Convert to string for hashtable lookup # Check what data exists (already loaded by Get-TcmTestCase) $localExists = $null -ne $InputObject.LocalData $remoteExists = $null -ne $InputObject.RemoteData Write-Verbose "Resolve-TcmTestCaseSyncStatus for '$Id': localExists=$localExists, remoteExists=$remoteExists, cachedEntry=$($null -ne $cachedEntry)" # If local file doesn't exist but cache entry does, invalidate cache (file was deleted) if (-not $localExists -and $cachedEntry) { Write-Verbose "Cache entry exists for test case '$Id' but local file is missing - invalidating cache entry" $cachedEntry = $null } # Case 1: No local and no remote = new-local (shouldn't happen with numeric ID) if (-not $localExists -and -not $remoteExists) { $InputObject.SyncStatus = "new-local" return $InputObject } # Case 2: No local but remote exists = new-remote if (-not $localExists -and $remoteExists) { $InputObject.SyncStatus = "new-remote" return $InputObject } # Case 3: Local exists but no remote = new-local or local-changes if ($localExists -and -not $remoteExists) { if (-not $cachedEntry) { # No cache entry = never synced = new-local $InputObject.SyncStatus = "new-local" } else { # Had cache entry but remote gone = local-changes (assume we want to re-create) $InputObject.SyncStatus = "local-changes" } return $InputObject } # Case 4: Both local and remote exist - use pre-calculated hashes $localHash = $InputObject.LocalDataHash $remoteHash = $InputObject.RemoteDataHash Write-Verbose "Local hash for test case '$Id': $localHash" Write-Verbose "Remote hash for test case '$Id': $remoteHash" # If no cache entry exists, determine initial sync status if (-not $cachedEntry) { Write-Verbose "No cache entry found for test case '$Id' - determining initial status" if ($localHash -eq $remoteHash) { # Already in sync, create cache entry $InputObject.SyncStatus = "synced" } else { # Different but never synced - assume local-changes (local is source of truth) $InputObject.SyncStatus = "local-changes" } return $InputObject } # Compare with cached hashes to detect changes $cachedHash = $cachedEntry.hash $localChanged = ($localHash -ne $cachedHash) $remoteChanged = ($remoteHash -ne $cachedHash) Write-Verbose "Cache comparison for test case '$Id': localChanged=$localChanged, remoteChanged=$remoteChanged" Write-Verbose " Current local: $localHash" Write-Verbose " Current remote: $remoteHash" Write-Verbose " Cached (last sync): $cachedHash" # Determine sync status based on 3-way merge logic if (-not $localChanged -and -not $remoteChanged) { # No changes since last sync $InputObject.SyncStatus = "synced" } elseif ($localChanged -and -not $remoteChanged) { # Only local changed since last sync $InputObject.SyncStatus = "local-changes" } elseif (-not $localChanged -and $remoteChanged) { # Only remote changed since last sync $InputObject.SyncStatus = "remote-changes" } elseif ($localHash -eq $remoteHash) { # Both changed to the same value - in sync $InputObject.SyncStatus = "synced" } else { # Both changed to different values = conflict $InputObject.SyncStatus = "conflict" } return $InputObject } catch { throw "Failed to determine sync status for test case '$Id': $($_.Exception.Message)" } } |