lib/rules/releases/ReleaseRulesHelper.Tests.ps1

#############################################################################
# Tests for ReleaseRulesHelper.ps1 - Shared helper functions for release rules
#############################################################################

BeforeAll {
    . "$PSScriptRoot/../../StateModel.ps1"
    . "$PSScriptRoot/ReleaseRulesHelper.ps1"
}

Describe "Test-ShouldBeLatestRelease" {
    Context "Invalid version formats" {
        It "should return false for non-patch versions (major only)" {
            $state = [RepositoryState]::new()
            $state.Releases = @()
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1"
            
            $result | Should -Be $false
        }
        
        It "should return false for non-patch versions (minor only)" {
            $state = [RepositoryState]::new()
            $state.Releases = @()
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0"
            
            $result | Should -Be $false
        }
        
        It "should return false for 'latest' version" {
            $state = [RepositoryState]::new()
            $state.Releases = @()
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "latest"
            
            $result | Should -Be $false
        }
        
        It "should return false for versions with suffixes" {
            $state = [RepositoryState]::new()
            $state.Releases = @()
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0.0-beta"
            
            $result | Should -Be $false
        }
    }
    
    Context "No existing releases" {
        It "should return true when no releases exist" {
            $state = [RepositoryState]::new()
            $state.Releases = @()
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0.0"
            
            $result | Should -Be $true
        }
        
        It "should return true when only draft releases exist" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $true  # Draft - not eligible
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "def456"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($releaseData))
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0.0"
            
            $result | Should -Be $true
        }
        
        It "should return true when only prerelease releases exist" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $false
                prerelease = $true  # Prerelease - not eligible
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "def456"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($releaseData))
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0.0"
            
            $result | Should -Be $true
        }
        
        It "should return true when only ignored releases exist" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "def456"
                immutable = $false
            }
            $release = [ReleaseInfo]::new($releaseData)
            $release.IsIgnored = $true  # Ignored - not eligible
            $state.Releases = @($release)
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0.0"
            
            $result | Should -Be $true
        }
    }
    
    Context "Version comparison - higher version should be latest" {
        It "should return true when target is higher major version" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "abc123"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($releaseData))
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v2.0.0"
            
            $result | Should -Be $true
        }
        
        It "should return true when target is higher minor version" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "abc123"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($releaseData))
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.1.0"
            
            $result | Should -Be $true
        }
        
        It "should return true when target is higher patch version" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "abc123"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($releaseData))
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0.1"
            
            $result | Should -Be $true
        }
        
        It "should handle numeric sorting correctly (v10 > v9)" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v9.0.0"
                id = 900
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v9.0.0"
                target_commitish = "sha900"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($releaseData))
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v10.0.0"
            
            $result | Should -Be $true
        }
    }
    
    Context "Version comparison - lower version should NOT be latest" {
        It "should return false when target is lower major version" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "def456"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($releaseData))
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0.0"
            
            $result | Should -Be $false
        }
        
        It "should return false when target is lower minor version" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v1.2.0"
                id = 120
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.2.0"
                target_commitish = "sha120"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($releaseData))
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.1.0"
            
            $result | Should -Be $false
        }
        
        It "should return false when target is lower patch version" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v1.0.2"
                id = 102
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.2"
                target_commitish = "sha102"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($releaseData))
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0.1"
            
            $result | Should -Be $false
        }
        
        It "should return false when target equals existing version" {
            $state = [RepositoryState]::new()
            $releaseData = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "abc123"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($releaseData))
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0.0"
            
            $result | Should -Be $false
        }
    }
    
    Context "Prerelease handling via ReleaseInfo parameter" {
        It "should return false when ReleaseInfo indicates prerelease" {
            $state = [RepositoryState]::new()
            $state.Releases = @()  # No existing releases
            
            # The release being created/published is marked as prerelease
            $prereleaseData = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $false
                prerelease = $true  # This is a prerelease
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "def456"
                immutable = $false
            }
            $prereleaseInfo = [ReleaseInfo]::new($prereleaseData)
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v2.0.0" -ReleaseInfo $prereleaseInfo
            
            $result | Should -Be $false
        }
        
        It "should return true when ReleaseInfo indicates non-prerelease and version is highest" {
            $state = [RepositoryState]::new()
            $existingRelease = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "abc123"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($existingRelease))
            
            # The release being created/published is NOT a prerelease
            $newReleaseData = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $true
                prerelease = $false  # Not a prerelease
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "def456"
                immutable = $false
            }
            $newReleaseInfo = [ReleaseInfo]::new($newReleaseData)
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v2.0.0" -ReleaseInfo $newReleaseInfo
            
            $result | Should -Be $true
        }
    }
    
    Context "Multiple existing releases" {
        It "should correctly identify highest among multiple releases" {
            $state = [RepositoryState]::new()
            
            $release1 = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "sha100"
                immutable = $false
            }
            $release2 = [PSCustomObject]@{
                tag_name = "v1.5.0"
                id = 150
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.5.0"
                target_commitish = "sha150"
                immutable = $false
            }
            $release3 = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "sha200"
                immutable = $false
            }
            
            $state.Releases = @(
                [ReleaseInfo]::new($release1),
                [ReleaseInfo]::new($release2),
                [ReleaseInfo]::new($release3)
            )
            
            # v2.1.0 should be latest (higher than v2.0.0)
            $result = Test-ShouldBeLatestRelease -State $state -Version "v2.1.0"
            $result | Should -Be $true
            
            # v1.6.0 should NOT be latest (lower than v2.0.0)
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.6.0"
            $result | Should -Be $false
        }
        
        It "should ignore prerelease releases when finding highest" {
            $state = [RepositoryState]::new()
            
            $stableRelease = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false
                prerelease = $false  # Stable
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "sha100"
                immutable = $false
            }
            $prereleaseRelease = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $false
                prerelease = $true  # Prerelease - should be ignored
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "sha200"
                immutable = $false
            }
            
            $state.Releases = @(
                [ReleaseInfo]::new($stableRelease),
                [ReleaseInfo]::new($prereleaseRelease)
            )
            
            # v1.1.0 should be latest because v2.0.0 is a prerelease
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.1.0"
            
            $result | Should -Be $true
        }
    }
    
    Context "Real-world scenarios" {
        It "should handle backporting older version (should NOT become latest)" {
            $state = [RepositoryState]::new()
            
            # v2.0.0 is the current latest
            $release = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "sha200"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($release))
            
            # Backporting a security fix to v1.x line
            $result = Test-ShouldBeLatestRelease -State $state -Version "v1.0.1"
            
            $result | Should -Be $false
        }
        
        It "should handle new major version (should become latest)" {
            $state = [RepositoryState]::new()
            
            # v1.5.3 is the current latest
            $release = [PSCustomObject]@{
                tag_name = "v1.5.3"
                id = 153
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.5.3"
                target_commitish = "sha153"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($release))
            
            # Releasing v2.0.0
            $result = Test-ShouldBeLatestRelease -State $state -Version "v2.0.0"
            
            $result | Should -Be $true
        }
        
        It "should handle prerelease for next version (should NOT become latest)" {
            $state = [RepositoryState]::new()
            
            # v1.0.0 is the current stable latest
            $release = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "sha100"
                immutable = $false
            }
            $state.Releases = @([ReleaseInfo]::new($release))
            
            # Creating v2.0.0 as a prerelease (via ReleaseInfo)
            $prereleaseData = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $false
                prerelease = $true  # Prerelease
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "sha200"
                immutable = $false
            }
            $prereleaseInfo = [ReleaseInfo]::new($prereleaseData)
            
            $result = Test-ShouldBeLatestRelease -State $state -Version "v2.0.0" -ReleaseInfo $prereleaseInfo
            
            $result | Should -Be $false
        }
    }
}

Describe "Get-DuplicateReleaseId" {
    Context "No duplicates" {
        It "should return empty array when no releases" {
            $state = [RepositoryState]::new()
            $state.Releases = @()
            
            $result = Get-DuplicateReleaseId -State $state
            
            $result.Count | Should -Be 0
        }
        
        It "should return empty array when no duplicates exist" {
            $state = [RepositoryState]::new()
            
            $release1 = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "sha100"
                immutable = $false
            }
            $release2 = [PSCustomObject]@{
                tag_name = "v2.0.0"
                id = 200
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v2.0.0"
                target_commitish = "sha200"
                immutable = $false
            }
            
            $state.Releases = @(
                [ReleaseInfo]::new($release1),
                [ReleaseInfo]::new($release2)
            )
            
            $result = Get-DuplicateReleaseId -State $state
            
            $result.Count | Should -Be 0
        }
    }
    
    Context "With duplicates" {
        It "should return duplicate IDs keeping published over draft" {
            $state = [RepositoryState]::new()
            
            $publishedRelease = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false  # Published - should be kept
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "sha100"
                immutable = $false
            }
            $draftRelease = [PSCustomObject]@{
                tag_name = "v1.0.0"  # Same tag - duplicate
                id = 101
                draft = $true  # Draft - should be marked as duplicate
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "sha100"
                immutable = $false
            }
            
            $state.Releases = @(
                [ReleaseInfo]::new($publishedRelease),
                [ReleaseInfo]::new($draftRelease)
            )
            
            $result = Get-DuplicateReleaseId -State $state
            
            $result.Count | Should -Be 1
            $result | Should -Contain 101
        }
        
        It "should ignore non-patch versions" {
            $state = [RepositoryState]::new()
            
            # Two releases with major version tag (not patch format)
            $release1 = [PSCustomObject]@{
                tag_name = "v1"  # Not a patch version
                id = 100
                draft = $false
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1"
                target_commitish = "sha100"
                immutable = $false
            }
            $release2 = [PSCustomObject]@{
                tag_name = "v1"  # Not a patch version - duplicate
                id = 101
                draft = $true
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1"
                target_commitish = "sha100"
                immutable = $false
            }
            
            $state.Releases = @(
                [ReleaseInfo]::new($release1),
                [ReleaseInfo]::new($release2)
            )
            
            $result = Get-DuplicateReleaseId -State $state
            
            # Should not find duplicates since v1 is not a patch version
            $result.Count | Should -Be 0
        }
    }
}

Describe "Get-DuplicateDraftRelease" {
    Context "No duplicates" {
        It "should return empty array when no releases" {
            $state = [RepositoryState]::new()
            $state.Releases = @()
            
            $result = Get-DuplicateDraftRelease -State $state
            
            $result.Count | Should -Be 0
        }
    }
    
    Context "With duplicates" {
        It "should only return draft duplicates (not published)" {
            $state = [RepositoryState]::new()
            
            $publishedRelease = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false  # Published - should be kept
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "sha100"
                immutable = $false
            }
            $draftRelease = [PSCustomObject]@{
                tag_name = "v1.0.0"  # Same tag - duplicate
                id = 101
                draft = $true  # Draft - can be deleted
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "sha100"
                immutable = $false
            }
            
            $state.Releases = @(
                [ReleaseInfo]::new($publishedRelease),
                [ReleaseInfo]::new($draftRelease)
            )
            
            $result = Get-DuplicateDraftRelease -State $state
            
            $result.Count | Should -Be 1
            $result[0].Id | Should -Be 101
            $result[0].IsDraft | Should -Be $true
        }
        
        It "should not return published duplicates even if they would be deleted" {
            $state = [RepositoryState]::new()
            
            # Two published releases - published releases cannot be deleted
            $release1 = [PSCustomObject]@{
                tag_name = "v1.0.0"
                id = 100
                draft = $false  # Published
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "sha100"
                immutable = $true
            }
            $release2 = [PSCustomObject]@{
                tag_name = "v1.0.0"  # Same tag - duplicate
                id = 101
                draft = $false  # Also published - cannot be deleted
                prerelease = $false
                html_url = "https://github.com/repo/releases/tag/v1.0.0"
                target_commitish = "sha100"
                immutable = $false
            }
            
            $state.Releases = @(
                [ReleaseInfo]::new($release1),
                [ReleaseInfo]::new($release2)
            )
            
            $result = Get-DuplicateDraftRelease -State $state
            
            # No drafts to delete
            $result.Count | Should -Be 0
        }
    }
}