Skip to content

Commit c6d9d60

Browse files
committed
Extract tests for GitAliases.Extra from dotfiles repo
1 parent ec77836 commit c6d9d60

File tree

2 files changed

+376
-1
lines changed

2 files changed

+376
-1
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ To publish from CI:
7878
## What CI checks
7979

8080
- `PSScriptAnalyzer` linting with `PSScriptAnalyzerSettings.psd1`
81-
- `Pester` tests in `tests\`
81+
- `Pester` tests in `tests\` (module + integration)
8282
- GitHub Actions matrix on:
8383
- Windows PowerShell
8484
- PowerShell 7
Lines changed: 375 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,375 @@
1+
$ErrorActionPreference = 'Stop'
2+
Set-StrictMode -Version Latest
3+
$script:HasGcoAlias = [bool](Get-Command gco -ErrorAction SilentlyContinue)
4+
5+
BeforeAll {
6+
function Script:Invoke-GitCommand {
7+
[CmdletBinding()]
8+
param(
9+
[Parameter(Mandatory = $true)]
10+
[string]$RepoPath,
11+
[Parameter(Mandatory = $true)]
12+
[string[]]$Arguments,
13+
[switch]$AllowFail
14+
)
15+
16+
$previousPreference = $ErrorActionPreference
17+
$ErrorActionPreference = 'Continue'
18+
try {
19+
$output = & git -C $RepoPath @Arguments 2>&1
20+
} finally {
21+
$ErrorActionPreference = $previousPreference
22+
}
23+
$exitCode = $LASTEXITCODE
24+
$text = ($output | Out-String).Trim()
25+
26+
if (-not $AllowFail -and $exitCode -ne 0) {
27+
throw "git $($Arguments -join ' ') failed in '$RepoPath' (exit=$exitCode): $text"
28+
}
29+
30+
[pscustomobject]@{
31+
ExitCode = $exitCode
32+
Output = $text
33+
}
34+
}
35+
36+
function Script:Find-BlobPrefixCollision {
37+
[CmdletBinding()]
38+
param(
39+
[string]$Prefix = '8695',
40+
[int]$MaxAttempts = 2000000
41+
)
42+
43+
$sha1 = [System.Security.Cryptography.SHA1]::Create()
44+
try {
45+
for ($i = 0; $i -lt $MaxAttempts; $i++) {
46+
$content = "collision-$i"
47+
$contentBytes = [Text.Encoding]::ASCII.GetBytes($content)
48+
$headerBytes = [Text.Encoding]::ASCII.GetBytes("blob $($contentBytes.Length)`0")
49+
50+
$payload = New-Object byte[] ($headerBytes.Length + $contentBytes.Length)
51+
[Buffer]::BlockCopy($headerBytes, 0, $payload, 0, $headerBytes.Length)
52+
[Buffer]::BlockCopy($contentBytes, 0, $payload, $headerBytes.Length, $contentBytes.Length)
53+
54+
$hashBytes = $sha1.ComputeHash($payload)
55+
$hash = ([BitConverter]::ToString($hashBytes) -replace '-', '').ToLowerInvariant()
56+
if ($hash.StartsWith($Prefix)) {
57+
return [pscustomobject]@{
58+
Content = $content
59+
Hash = $hash
60+
}
61+
}
62+
}
63+
} finally {
64+
$sha1.Dispose()
65+
}
66+
67+
throw "Could not find blob hash prefix '$Prefix' within $MaxAttempts attempts."
68+
}
69+
70+
[string]$script:RepoRoot = Resolve-Path -LiteralPath (Join-Path $PSScriptRoot '..') |
71+
Select-Object -ExpandProperty Path -First 1
72+
$script:ModuleManifest = Join-Path $script:RepoRoot 'GitAliases.Extras.psd1'
73+
74+
if (Get-Module -ListAvailable -Name git-aliases) {
75+
Import-Module git-aliases -DisableNameChecking -ErrorAction SilentlyContinue
76+
}
77+
78+
Import-Module $script:ModuleManifest -Force
79+
$script:HasGcoAlias = [bool](Get-Command gco -ErrorAction SilentlyContinue)
80+
}
81+
82+
AfterAll {
83+
Remove-Module GitAliases.Extras -Force -ErrorAction SilentlyContinue
84+
}
85+
86+
Describe 'GitAliases.Extras module' {
87+
It 'imports successfully' {
88+
Get-Module GitAliases.Extras | Should -Not -BeNullOrEmpty
89+
}
90+
91+
It 'exports expected commands' {
92+
Get-Command Test-InGitRepo -ErrorAction Stop | Should -Not -BeNullOrEmpty
93+
Get-Command UpMerge -ErrorAction Stop | Should -Not -BeNullOrEmpty
94+
Get-Command UpRebase -ErrorAction Stop | Should -Not -BeNullOrEmpty
95+
Get-Command gfp -ErrorAction Stop | Should -Not -BeNullOrEmpty
96+
Get-Command gsw -ErrorAction Stop | Should -Not -BeNullOrEmpty
97+
}
98+
99+
It 'Test-InGitRepo returns a boolean value outside or inside a repository' {
100+
(Test-InGitRepo).GetType().Name | Should -Be 'Boolean'
101+
}
102+
}
103+
104+
Describe 'gfp integration' {
105+
It 'creates series.mbox using default base branch resolution' -Skip:(-not (Get-Command git -ErrorAction SilentlyContinue)) {
106+
$tempRoot = Join-Path ([IO.Path]::GetTempPath()) ("gfp-default-" + [guid]::NewGuid().Guid)
107+
$remotePath = Join-Path $tempRoot 'remote.git'
108+
$workPath = Join-Path $tempRoot 'work'
109+
110+
New-Item -ItemType Directory -Path $workPath -Force | Out-Null
111+
try {
112+
Invoke-GitCommand -RepoPath $tempRoot -Arguments @('init', '--bare', $remotePath) | Out-Null
113+
Invoke-GitCommand -RepoPath $tempRoot -Arguments @('init', $workPath) | Out-Null
114+
115+
Invoke-GitCommand -RepoPath $workPath -Arguments @('config', 'user.email', 'test@example.com') | Out-Null
116+
Invoke-GitCommand -RepoPath $workPath -Arguments @('config', 'user.name', 'Test User') | Out-Null
117+
Invoke-GitCommand -RepoPath $workPath -Arguments @('config', 'commit.gpgsign', 'false') | Out-Null
118+
119+
Set-Content -Path (Join-Path $workPath 'README.md') -Value 'root' -NoNewline -Encoding ascii
120+
Invoke-GitCommand -RepoPath $workPath -Arguments @('add', 'README.md') | Out-Null
121+
Invoke-GitCommand -RepoPath $workPath -Arguments @('commit', '-m', 'init main') | Out-Null
122+
Invoke-GitCommand -RepoPath $workPath -Arguments @('branch', '-M', 'main') | Out-Null
123+
Invoke-GitCommand -RepoPath $workPath -Arguments @('remote', 'add', 'origin', $remotePath) | Out-Null
124+
Invoke-GitCommand -RepoPath $workPath -Arguments @('push', '-u', 'origin', 'main') | Out-Null
125+
126+
Add-Content -Path (Join-Path $workPath 'README.md') -Value "`nfeature-from-main"
127+
Invoke-GitCommand -RepoPath $workPath -Arguments @('add', 'README.md') | Out-Null
128+
Invoke-GitCommand -RepoPath $workPath -Arguments @('commit', '-m', 'feature main patch') | Out-Null
129+
130+
Push-Location $workPath
131+
try {
132+
$mboxPath = gfp
133+
} finally {
134+
Pop-Location
135+
}
136+
137+
$expectedPath = Join-Path $workPath 'series.mbox'
138+
$mboxPath | Should -Be $expectedPath
139+
(Test-Path -LiteralPath $expectedPath) | Should -BeTrue
140+
141+
$mboxContent = Get-Content -LiteralPath $expectedPath -Raw
142+
$mboxContent | Should -Match 'Subject: \[PATCH'
143+
$mboxContent | Should -Match 'feature main patch'
144+
} finally {
145+
if (Test-Path $tempRoot) {
146+
Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
147+
}
148+
}
149+
}
150+
151+
It 'supports explicit target branch and custom output mbox path' -Skip:(-not (Get-Command git -ErrorAction SilentlyContinue)) {
152+
$tempRoot = Join-Path ([IO.Path]::GetTempPath()) ("gfp-custom-" + [guid]::NewGuid().Guid)
153+
$remotePath = Join-Path $tempRoot 'remote.git'
154+
$workPath = Join-Path $tempRoot 'work'
155+
156+
New-Item -ItemType Directory -Path $workPath -Force | Out-Null
157+
try {
158+
Invoke-GitCommand -RepoPath $tempRoot -Arguments @('init', '--bare', $remotePath) | Out-Null
159+
Invoke-GitCommand -RepoPath $tempRoot -Arguments @('init', $workPath) | Out-Null
160+
161+
Invoke-GitCommand -RepoPath $workPath -Arguments @('config', 'user.email', 'test@example.com') | Out-Null
162+
Invoke-GitCommand -RepoPath $workPath -Arguments @('config', 'user.name', 'Test User') | Out-Null
163+
Invoke-GitCommand -RepoPath $workPath -Arguments @('config', 'commit.gpgsign', 'false') | Out-Null
164+
165+
Set-Content -Path (Join-Path $workPath 'README.md') -Value 'root' -NoNewline -Encoding ascii
166+
Invoke-GitCommand -RepoPath $workPath -Arguments @('add', 'README.md') | Out-Null
167+
Invoke-GitCommand -RepoPath $workPath -Arguments @('commit', '-m', 'init master') | Out-Null
168+
Invoke-GitCommand -RepoPath $workPath -Arguments @('branch', '-M', 'master') | Out-Null
169+
Invoke-GitCommand -RepoPath $workPath -Arguments @('remote', 'add', 'origin', $remotePath) | Out-Null
170+
Invoke-GitCommand -RepoPath $workPath -Arguments @('push', '-u', 'origin', 'master') | Out-Null
171+
172+
Add-Content -Path (Join-Path $workPath 'README.md') -Value "`nfeature-custom"
173+
Invoke-GitCommand -RepoPath $workPath -Arguments @('add', 'README.md') | Out-Null
174+
Invoke-GitCommand -RepoPath $workPath -Arguments @('commit', '-m', 'custom output patch') | Out-Null
175+
176+
Push-Location $workPath
177+
try {
178+
$mboxPath = gfp master 'artifacts\custom-series.mbox'
179+
} finally {
180+
Pop-Location
181+
}
182+
183+
$expectedPath = Join-Path $workPath 'artifacts\custom-series.mbox'
184+
$mboxPath | Should -Be $expectedPath
185+
(Test-Path -LiteralPath $expectedPath) | Should -BeTrue
186+
187+
$mboxContent = Get-Content -LiteralPath $expectedPath -Raw
188+
$mboxContent | Should -Match 'Subject: \[PATCH'
189+
$mboxContent | Should -Match 'custom output patch'
190+
} finally {
191+
if (Test-Path $tempRoot) {
192+
Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
193+
}
194+
}
195+
}
196+
}
197+
198+
Describe 'gsw integration' {
199+
It 'completes branch names for gsw when command is followed by a space' -Skip:(-not (Get-Command git -ErrorAction SilentlyContinue)) {
200+
$tempRoot = Join-Path ([IO.Path]::GetTempPath()) ("gsw-space-complete-" + [guid]::NewGuid().Guid)
201+
$repoPath = Join-Path $tempRoot 'repo'
202+
203+
New-Item -ItemType Directory -Path $repoPath -Force | Out-Null
204+
try {
205+
Invoke-GitCommand -RepoPath $tempRoot -Arguments @('init', $repoPath) | Out-Null
206+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('config', 'user.email', 'test@example.com') | Out-Null
207+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('config', 'user.name', 'Test User') | Out-Null
208+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('config', 'commit.gpgsign', 'false') | Out-Null
209+
210+
Set-Content -Path (Join-Path $repoPath 'README.md') -Value 'root' -NoNewline -Encoding ascii
211+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('add', 'README.md') | Out-Null
212+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('commit', '-m', 'init') | Out-Null
213+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('branch', '-M', 'main') | Out-Null
214+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('branch', 'feature/tab-complete') | Out-Null
215+
216+
Push-Location $repoPath
217+
try {
218+
$line = 'gsw '
219+
$result = TabExpansion2 -inputScript $line -cursorColumn $line.Length
220+
} finally {
221+
Pop-Location
222+
}
223+
224+
$result.CompletionMatches.Count | Should -BeGreaterThan 0
225+
$completionTexts = @($result.CompletionMatches | Select-Object -ExpandProperty CompletionText)
226+
$completionTexts | Should -Contain 'main'
227+
$completionTexts | Should -Contain 'feature/tab-complete'
228+
$completionTexts | Should -Not -Contain 'switch'
229+
} finally {
230+
if (Test-Path $tempRoot) {
231+
Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
232+
}
233+
}
234+
}
235+
236+
It 'completes long options for gsw alias' -Skip:(-not (Get-Command git -ErrorAction SilentlyContinue)) {
237+
Push-Location $script:RepoRoot
238+
try {
239+
$line = 'gsw --'
240+
$result = TabExpansion2 -inputScript $line -cursorColumn $line.Length
241+
} finally {
242+
Pop-Location
243+
}
244+
245+
$result.CompletionMatches.Count | Should -BeGreaterThan 0
246+
$completionTexts = @($result.CompletionMatches | Select-Object -ExpandProperty CompletionText)
247+
$completionTexts | Should -Contain '--track'
248+
}
249+
250+
It 'completes long options for gco alias from git-aliases module' -Skip:(-not (Get-Command git -ErrorAction SilentlyContinue) -or -not $script:HasGcoAlias) {
251+
Push-Location $script:RepoRoot
252+
try {
253+
$line = 'gco --'
254+
$result = TabExpansion2 -inputScript $line -cursorColumn $line.Length
255+
} finally {
256+
Pop-Location
257+
}
258+
259+
$result.CompletionMatches.Count | Should -BeGreaterThan 0
260+
$completionTexts = @($result.CompletionMatches | Select-Object -ExpandProperty CompletionText)
261+
$completionTexts | Should -Contain '--detach'
262+
}
263+
264+
It 'returns PowerShell-safe completion text for branches starting with # when escaped prefix is used' -Skip:(-not (Get-Command git -ErrorAction SilentlyContinue)) {
265+
$tempRoot = Join-Path ([IO.Path]::GetTempPath()) ("gsw-complete-" + [guid]::NewGuid().Guid)
266+
$repoPath = Join-Path $tempRoot 'repo'
267+
268+
New-Item -ItemType Directory -Path $repoPath -Force | Out-Null
269+
try {
270+
Invoke-GitCommand -RepoPath $tempRoot -Arguments @('init', $repoPath) | Out-Null
271+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('config', 'user.email', 'test@example.com') | Out-Null
272+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('config', 'user.name', 'Test User') | Out-Null
273+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('config', 'commit.gpgsign', 'false') | Out-Null
274+
275+
Set-Content -Path (Join-Path $repoPath 'README.md') -Value 'root' -NoNewline -Encoding ascii
276+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('add', 'README.md') | Out-Null
277+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('commit', '-m', 'init') | Out-Null
278+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('branch', '-M', 'main') | Out-Null
279+
Invoke-GitCommand -RepoPath $repoPath -Arguments @('branch', '#8698') | Out-Null
280+
281+
Push-Location $repoPath
282+
try {
283+
$line = 'gsw `#'
284+
$result = TabExpansion2 -inputScript $line -cursorColumn $line.Length
285+
} finally {
286+
Pop-Location
287+
}
288+
289+
$result.CompletionMatches.Count | Should -BeGreaterThan 0
290+
$completionTexts = @($result.CompletionMatches | Select-Object -ExpandProperty CompletionText)
291+
$completionTexts | Should -Contain "'#8698'"
292+
} finally {
293+
if (Test-Path $tempRoot) {
294+
Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
295+
}
296+
}
297+
}
298+
299+
It 'handles remote-only numeric branch when native switch is ambiguous' -Skip:(-not (Get-Command git -ErrorAction SilentlyContinue)) {
300+
$tempRoot = Join-Path ([IO.Path]::GetTempPath()) ("gsw-integration-" + [guid]::NewGuid().Guid)
301+
$remotePath = Join-Path $tempRoot 'remote.git'
302+
$seedPath = Join-Path $tempRoot 'seed'
303+
$clonePath = Join-Path $tempRoot 'clone'
304+
305+
New-Item -ItemType Directory -Path $seedPath -Force | Out-Null
306+
try {
307+
Invoke-GitCommand -RepoPath $tempRoot -Arguments @('init', '--bare', $remotePath) | Out-Null
308+
Invoke-GitCommand -RepoPath $tempRoot -Arguments @('init', $seedPath) | Out-Null
309+
310+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('config', 'user.email', 'test@example.com') | Out-Null
311+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('config', 'user.name', 'Test User') | Out-Null
312+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('config', 'commit.gpgsign', 'false') | Out-Null
313+
314+
Set-Content -Path (Join-Path $seedPath 'README.md') -Value 'root' -NoNewline -Encoding ascii
315+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('add', 'README.md') | Out-Null
316+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('commit', '-m', 'init') | Out-Null
317+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('branch', '-M', 'main') | Out-Null
318+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('remote', 'add', 'origin', $remotePath) | Out-Null
319+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('push', '-u', 'origin', 'main') | Out-Null
320+
321+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('switch', '-c', '8695') | Out-Null
322+
Set-Content -Path (Join-Path $seedPath 'feature.txt') -Value 'feature' -NoNewline -Encoding ascii
323+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('add', 'feature.txt') | Out-Null
324+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('commit', '-m', 'feature') | Out-Null
325+
Invoke-GitCommand -RepoPath $seedPath -Arguments @('push', '-u', 'origin', '8695') | Out-Null
326+
327+
Invoke-GitCommand -RepoPath $tempRoot -Arguments @('clone', $remotePath, $clonePath) | Out-Null
328+
Invoke-GitCommand -RepoPath $clonePath -Arguments @('config', 'commit.gpgsign', 'false') | Out-Null
329+
330+
$collision = Find-BlobPrefixCollision -Prefix '8695'
331+
$collisionFile = Join-Path $clonePath 'collision.txt'
332+
Set-Content -Path $collisionFile -Value $collision.Content -NoNewline -Encoding ascii
333+
334+
$written = (Invoke-GitCommand -RepoPath $clonePath -Arguments @('hash-object', '-w', $collisionFile)).Output
335+
$written | Should -Be $collision.Hash
336+
337+
(Invoke-GitCommand -RepoPath $clonePath -Arguments @('cat-file', '-t', '8695')).Output | Should -Be 'blob'
338+
339+
Push-Location $clonePath
340+
try {
341+
$previousPreference = $ErrorActionPreference
342+
$ErrorActionPreference = 'Continue'
343+
try {
344+
$nativeSwitchOutput = & git switch 8695 2>&1 | Out-String
345+
$nativeSwitchExitCode = $LASTEXITCODE
346+
} finally {
347+
$ErrorActionPreference = $previousPreference
348+
}
349+
350+
$nativeSwitchExitCode | Should -Not -Be 0
351+
$nativeSwitchOutput.Trim() | Should -Match 'unable to read tree|non-commit|invalid reference'
352+
353+
$previousPreference = $ErrorActionPreference
354+
$ErrorActionPreference = 'Continue'
355+
try {
356+
gsw 8695 | Out-Null
357+
$gswExitCode = $LASTEXITCODE
358+
} finally {
359+
$ErrorActionPreference = $previousPreference
360+
}
361+
362+
$gswExitCode | Should -Be 0
363+
} finally {
364+
Pop-Location
365+
}
366+
367+
(Invoke-GitCommand -RepoPath $clonePath -Arguments @('rev-parse', '--abbrev-ref', 'HEAD')).Output | Should -Be '8695'
368+
(Invoke-GitCommand -RepoPath $clonePath -Arguments @('rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}')).Output | Should -Be 'origin/8695'
369+
} finally {
370+
if (Test-Path $tempRoot) {
371+
Remove-Item -Path $tempRoot -Recurse -Force -ErrorAction SilentlyContinue
372+
}
373+
}
374+
}
375+
}

0 commit comments

Comments
 (0)