Tests/GitEasy.WorkArea.Tests.ps1
|
BeforeAll { $ProjectRoot = Split-Path -Parent $PSScriptRoot $ModulePath = Join-Path $ProjectRoot 'GitEasy.psd1' function Invoke-TestGit { <# .DESCRIPTION Test helper. Runs git with the given arguments and returns exit code and output. Steps: 1. Run git capturing combined output. 2. Throw on non-zero exit unless -AllowFailure is set. 3. Return a result object with the exit code and output array. #> param( [Parameter(Mandatory)] [string[]]$ArgumentList, [switch]$AllowFailure ) $oldPreference = $ErrorActionPreference try { $ErrorActionPreference = 'Continue' $output = & git @ArgumentList 2>&1 $exitCode = $LASTEXITCODE } finally { $ErrorActionPreference = $oldPreference } if (($exitCode -ne 0) -and (-not $AllowFailure)) { throw "Git failed: git $($ArgumentList -join ' ')`n$($output -join [Environment]::NewLine)" } return [PSCustomObject]@{ ExitCode = $exitCode Output = @($output) } } function New-TestRepositoryWithCommit { <# .DESCRIPTION Test helper. Creates a git repository with one saved point for test isolation. Steps: 1. Create the target directory. 2. Initialize a new repository and set test-safe user name and email. 3. Write a README file, stage it, and create the first saved point. #> param([Parameter(Mandatory)] [string]$Path) New-Item -Path $Path -ItemType Directory -Force | Out-Null Push-Location -LiteralPath $Path try { Invoke-TestGit -ArgumentList @('init') | Out-Null Invoke-TestGit -ArgumentList @('config', 'user.name', 'GitEasy Pester') | Out-Null Invoke-TestGit -ArgumentList @('config', 'user.email', 'giteasy-pester@example.invalid') | Out-Null Set-Content -LiteralPath (Join-Path $Path 'README.md') -Value 'workarea baseline' -Encoding UTF8 Invoke-TestGit -ArgumentList @('add', '-A') | Out-Null Invoke-TestGit -ArgumentList @('commit', '-m', 'workarea baseline') | Out-Null } finally { Pop-Location } } function Get-TestCurrentBranch { <# .DESCRIPTION Test helper. Returns the active working area name for the given repository. Steps: 1. Query the symbolic reference for the current working area. 2. Return the first line of output. #> param([Parameter(Mandatory)][string]$Path) Push-Location -LiteralPath $Path try { $r = Invoke-TestGit -ArgumentList @('symbolic-ref', '--short', 'HEAD') -AllowFailure return ($r.Output | Select-Object -First 1) } finally { Pop-Location } } } Describe 'New-WorkBranch' { BeforeAll { Remove-Module GitEasy -Force -ErrorAction SilentlyContinue Import-Module $ModulePath -Force } BeforeEach { $script:Stem = [guid]::NewGuid().ToString('N').Substring(0, 8) $script:TempRepo = Join-Path ([System.IO.Path]::GetTempPath()) ("GitEasy_NWB_$script:Stem") $script:TempLogs = Join-Path ([System.IO.Path]::GetTempPath()) ("GitEasy_NWB_$($script:Stem)_logs") New-TestRepositoryWithCommit -Path $script:TempRepo New-Item -Path $script:TempLogs -ItemType Directory -Force | Out-Null $env:GITEASY_LOG_PATH = $script:TempLogs Push-Location -LiteralPath $script:TempRepo } AfterEach { Pop-Location Remove-Item Env:\GITEASY_LOG_PATH -ErrorAction SilentlyContinue Remove-Item -LiteralPath $script:TempRepo -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $script:TempLogs -Recurse -Force -ErrorAction SilentlyContinue } It 'creates a new working area and switches to it' { New-WorkBranch -Name 'feature/test-one' $current = Get-TestCurrentBranch -Path $script:TempRepo $current | Should -Be 'feature/test-one' } It 'returns a structured object describing the new working area' { $result = New-WorkBranch -Name 'feature/test-result' $result | Should -Not -BeNullOrEmpty $result.Branch | Should -Be 'feature/test-result' } It 'refuses to create a working area that already exists' { New-WorkBranch -Name 'feature/already-here' $caught = $null try { New-WorkBranch -Name 'feature/already-here' } catch { $caught = $_ } $caught | Should -Not -BeNullOrEmpty } It 'rejects invalid working-area names' { $caught = $null try { New-WorkBranch -Name 'has spaces' } catch { $caught = $_ } $caught | Should -Not -BeNullOrEmpty } It 'every invocation writes a log file with SUCCESS outcome' { New-WorkBranch -Name 'feature/log-success' $logs = @(Get-ChildItem -LiteralPath $script:TempLogs -Filter 'New-WorkBranch-*.log' -File) $logs.Count -gt 0 | Should -Be $true $body = Get-Content -LiteralPath ($logs | Sort-Object LastWriteTime | Select-Object -Last 1).FullName -Raw $body | Should -Match 'Outcome: SUCCESS' } It 'plain-English failure surfaces the log path with no raw git word' { New-WorkBranch -Name 'feature/already-fail' $thrown = $null try { New-WorkBranch -Name 'feature/already-fail' } catch { $thrown = $_ } $thrown | Should -Not -BeNullOrEmpty $thrown.Exception.Message | Should -Match '(?i)Details:' $userMessage = $thrown.Exception.Message -replace '(?ms)Details:.*$','' $userMessage | Should -Not -Match '(?i)\bgit\b' } It 'refuses to create a working area while there are unfinished conflicts' { Set-Content -LiteralPath (Join-Path $script:TempRepo 'shared.txt') -Value "version A`n" -Encoding UTF8 Save-Work 'baseline' -NoPush $baseBranch = Get-TestCurrentBranch -Path $script:TempRepo Invoke-TestGit -ArgumentList @('checkout', '-b', 'conflict-feature') | Out-Null Set-Content -LiteralPath (Join-Path $script:TempRepo 'shared.txt') -Value "version B`n" -Encoding UTF8 Save-Work 'feature change' -NoPush Invoke-TestGit -ArgumentList @('checkout', $baseBranch) | Out-Null Set-Content -LiteralPath (Join-Path $script:TempRepo 'shared.txt') -Value "version C`n" -Encoding UTF8 Save-Work 'main change' -NoPush Invoke-TestGit -ArgumentList @('merge', 'conflict-feature') -AllowFailure | Out-Null $caught = $null try { New-WorkBranch -Name 'feature/should-fail-during-conflict' } catch { $caught = $_ } $caught | Should -Not -BeNullOrEmpty } } Describe 'Switch-Work' { BeforeAll { Remove-Module GitEasy -Force -ErrorAction SilentlyContinue Import-Module $ModulePath -Force } BeforeEach { $script:Stem = [guid]::NewGuid().ToString('N').Substring(0, 8) $script:TempRepo = Join-Path ([System.IO.Path]::GetTempPath()) ("GitEasy_SW2_$script:Stem") $script:TempLogs = Join-Path ([System.IO.Path]::GetTempPath()) ("GitEasy_SW2_$($script:Stem)_logs") New-TestRepositoryWithCommit -Path $script:TempRepo New-Item -Path $script:TempLogs -ItemType Directory -Force | Out-Null $env:GITEASY_LOG_PATH = $script:TempLogs Push-Location -LiteralPath $script:TempRepo $script:BaseBranch = Get-TestCurrentBranch -Path $script:TempRepo Invoke-TestGit -ArgumentList @('branch', 'feature/already') | Out-Null } AfterEach { Pop-Location Remove-Item Env:\GITEASY_LOG_PATH -ErrorAction SilentlyContinue Remove-Item -LiteralPath $script:TempRepo -Recurse -Force -ErrorAction SilentlyContinue Remove-Item -LiteralPath $script:TempLogs -Recurse -Force -ErrorAction SilentlyContinue } It 'switches to an existing working area' { Switch-Work -Name 'feature/already' $current = Get-TestCurrentBranch -Path $script:TempRepo $current | Should -Be 'feature/already' } It 'returns a structured object describing the switch' { $result = Switch-Work -Name 'feature/already' $result | Should -Not -BeNullOrEmpty $result.Branch | Should -Be 'feature/already' } It 'refuses to switch to a working area that does not exist' { $caught = $null try { Switch-Work -Name 'feature/does-not-exist' } catch { $caught = $_ } $caught | Should -Not -BeNullOrEmpty } It 'refuses to switch when there are unsaved changes' { Set-Content -LiteralPath (Join-Path $script:TempRepo 'pending.txt') -Value 'unsaved' -Encoding UTF8 $caught = $null try { Switch-Work -Name 'feature/already' } catch { $caught = $_ } $caught | Should -Not -BeNullOrEmpty $current = Get-TestCurrentBranch -Path $script:TempRepo $current | Should -Be $script:BaseBranch } It 'every invocation writes a log file with SUCCESS outcome' { Switch-Work -Name 'feature/already' $logs = @(Get-ChildItem -LiteralPath $script:TempLogs -Filter 'Switch-Work-*.log' -File) $logs.Count -gt 0 | Should -Be $true $body = Get-Content -LiteralPath ($logs | Sort-Object LastWriteTime | Select-Object -Last 1).FullName -Raw $body | Should -Match 'Outcome: SUCCESS' } It 'plain-English failure surfaces the log path with no raw git word' { $thrown = $null try { Switch-Work -Name 'feature/does-not-exist' } catch { $thrown = $_ } $thrown | Should -Not -BeNullOrEmpty $thrown.Exception.Message | Should -Match '(?i)Details:' $userMessage = $thrown.Exception.Message -replace '(?ms)Details:.*$','' $userMessage | Should -Not -Match '(?i)\bgit\b' } It 'refuses to switch while there are unfinished conflicts' { Set-Content -LiteralPath (Join-Path $script:TempRepo 'shared.txt') -Value "version A`n" -Encoding UTF8 Save-Work 'baseline' -NoPush $baseBranch = Get-TestCurrentBranch -Path $script:TempRepo Invoke-TestGit -ArgumentList @('checkout', '-b', 'conflict-feature-sw') | Out-Null Set-Content -LiteralPath (Join-Path $script:TempRepo 'shared.txt') -Value "version B`n" -Encoding UTF8 Save-Work 'feature change' -NoPush Invoke-TestGit -ArgumentList @('checkout', $baseBranch) | Out-Null Set-Content -LiteralPath (Join-Path $script:TempRepo 'shared.txt') -Value "version C`n" -Encoding UTF8 Save-Work 'main change' -NoPush Invoke-TestGit -ArgumentList @('merge', 'conflict-feature-sw') -AllowFailure | Out-Null $caught = $null try { Switch-Work -Name 'feature/already' } catch { $caught = $_ } $caught | Should -Not -BeNullOrEmpty } } |