microsoft/hve-core

Public

mirrored from https://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/research-single-dynamic-rewrite

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

scripts/tests/extension/Package-Extension.Tests.ps1

1059lines · modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4
5BeforeAll {
6 . $PSScriptRoot/../../extension/Package-Extension.ps1
7 Import-Module "$PSScriptRoot/../Mocks/GitMocks.psm1" -Force
8 Import-Module "$PSScriptRoot/../../lib/Modules/CIHelpers.psm1" -Force
9}
10
11Describe 'Test-VsceAvailable' {
12 It 'Returns hashtable with IsAvailable property' {
13 $result = Test-VsceAvailable
14 $result | Should -BeOfType [hashtable]
15 $result.Keys | Should -Contain 'IsAvailable'
16 }
17
18 It 'Returns CommandType when available' {
19 $result = Test-VsceAvailable
20 if ($result.IsAvailable) {
21 $result.CommandType | Should -BeIn @('npx', 'vsce')
22 $result.Command | Should -Not -BeNullOrEmpty
23 }
24 }
25
26 It 'Returns vsce when vsce command is found' {
27 Mock Get-Command {
28 param($Name, $ErrorAction)
29 $null = $ErrorAction # Suppress PSScriptAnalyzer warning
30 if ($Name -eq 'vsce') {
31 return [PSCustomObject]@{ Source = 'C:\bin\vsce.cmd' }
32 }
33 return $null
34 }
35 $result = Test-VsceAvailable
36 $result.IsAvailable | Should -BeTrue
37 $result.CommandType | Should -Be 'vsce'
38 $result.Command | Should -Be 'C:\bin\vsce.cmd'
39 }
40
41 It 'Returns npx when only npx command is found' {
42 Mock Get-Command {
43 param($Name, $ErrorAction)
44 $null = $ErrorAction # Suppress PSScriptAnalyzer warning
45 if ($Name -eq 'npx') {
46 return [PSCustomObject]@{ Source = 'C:\bin\npx.cmd' }
47 }
48 return $null
49 }
50 $result = Test-VsceAvailable
51 $result.IsAvailable | Should -BeTrue
52 $result.CommandType | Should -Be 'npx'
53 $result.Command | Should -Be 'C:\bin\npx.cmd'
54 }
55
56 It 'Returns not available when neither vsce nor npx exist' {
57 Mock Get-Command { return $null }
58 $result = Test-VsceAvailable
59 $result.IsAvailable | Should -BeFalse
60 $result.CommandType | Should -BeNullOrEmpty
61 $result.Command | Should -BeNullOrEmpty
62 }
63}
64
65Describe 'Get-ExtensionOutputPath' {
66 BeforeAll {
67 $script:testDir = [System.IO.Path]::GetTempPath().TrimEnd([System.IO.Path]::DirectorySeparatorChar)
68 }
69
70 It 'Constructs correct output path' {
71 $result = Get-ExtensionOutputPath -ExtensionDirectory $script:testDir -ExtensionName 'my-extension' -PackageVersion '1.0.0'
72 $expected = [System.IO.Path]::Combine($script:testDir, 'my-extension-1.0.0.vsix')
73 $result | Should -Be $expected
74 }
75
76 It 'Handles pre-release version numbers' {
77 $result = Get-ExtensionOutputPath -ExtensionDirectory $script:testDir -ExtensionName 'ext' -PackageVersion '2.1.0-preview.1'
78 $expected = [System.IO.Path]::Combine($script:testDir, 'ext-2.1.0-preview.1.vsix')
79 $result | Should -Be $expected
80 }
81
82 It 'Uses collection ID in filename when specified' {
83 $result = Get-ExtensionOutputPath -ExtensionDirectory $script:testDir -ExtensionName 'my-extension' -PackageVersion '1.0.0' -CollectionId 'developer'
84 $expected = [System.IO.Path]::Combine($script:testDir, 'developer-1.0.0.vsix')
85 $result | Should -Be $expected
86 }
87
88 It 'Uses extension name when no collection ID' {
89 $result = Get-ExtensionOutputPath -ExtensionDirectory $script:testDir -ExtensionName 'my-extension' -PackageVersion '1.0.0' -CollectionId ''
90 $expected = [System.IO.Path]::Combine($script:testDir, 'my-extension-1.0.0.vsix')
91 $result | Should -Be $expected
92 }
93}
94
95Describe 'Test-ExtensionManifestValid' {
96 It 'Returns valid result for proper manifest' {
97 $manifest = [PSCustomObject]@{
98 name = 'my-extension'
99 version = '1.0.0'
100 publisher = 'my-publisher'
101 engines = [PSCustomObject]@{ vscode = '^1.80.0' }
102 }
103 $result = Test-ExtensionManifestValid -ManifestContent $manifest
104 $result.IsValid | Should -BeTrue
105 $result.Errors | Should -BeNullOrEmpty
106 }
107
108 It 'Returns invalid when name missing' {
109 $manifest = @{
110 version = '1.0.0'
111 publisher = 'pub'
112 engines = @{ vscode = '^1.80.0' }
113 }
114 $result = Test-ExtensionManifestValid -ManifestContent $manifest
115 $result.IsValid | Should -BeFalse
116 $result.Errors | Should -Contain "Missing required 'name' field"
117 }
118
119 It 'Returns invalid when version missing' {
120 $manifest = @{
121 name = 'ext'
122 publisher = 'pub'
123 engines = @{ vscode = '^1.80.0' }
124 }
125 $result = Test-ExtensionManifestValid -ManifestContent $manifest
126 $result.IsValid | Should -BeFalse
127 $result.Errors | Should -Contain "Missing required 'version' field"
128 }
129
130 It 'Returns invalid when publisher missing' {
131 $manifest = @{
132 name = 'ext'
133 version = '1.0.0'
134 engines = @{ vscode = '^1.80.0' }
135 }
136 $result = Test-ExtensionManifestValid -ManifestContent $manifest
137 $result.IsValid | Should -BeFalse
138 $result.Errors | Should -Contain "Missing required 'publisher' field"
139 }
140
141 It 'Returns invalid when engines.vscode missing' {
142 $manifest = @{
143 name = 'ext'
144 version = '1.0.0'
145 publisher = 'pub'
146 }
147 $result = Test-ExtensionManifestValid -ManifestContent $manifest
148 $result.IsValid | Should -BeFalse
149 $result.Errors | Should -Contain "Missing required 'engines' field"
150 }
151
152 It 'Returns invalid when engines exists but vscode key missing' {
153 $manifest = [PSCustomObject]@{
154 name = 'ext'
155 version = '1.0.0'
156 publisher = 'pub'
157 engines = [PSCustomObject]@{ node = '>=16' }
158 }
159 $result = Test-ExtensionManifestValid -ManifestContent $manifest
160 $result.IsValid | Should -BeFalse
161 $result.Errors | Should -Contain "Missing required 'engines.vscode' field"
162 }
163
164 It 'Returns invalid when version format is incorrect' {
165 $manifest = [PSCustomObject]@{
166 name = 'ext'
167 version = 'invalid-version'
168 publisher = 'pub'
169 engines = [PSCustomObject]@{ vscode = '^1.80.0' }
170 }
171 $result = Test-ExtensionManifestValid -ManifestContent $manifest
172 $result.IsValid | Should -BeFalse
173 $result.Errors | Should -Match 'Invalid version format'
174 }
175
176 It 'Collects multiple errors' {
177 $manifest = @{}
178 $result = Test-ExtensionManifestValid -ManifestContent $manifest
179 $result.IsValid | Should -BeFalse
180 $result.Errors.Count | Should -BeGreaterThan 1
181 }
182}
183
184Describe 'Get-VscePackageCommand' {
185 It 'Returns npx command structure for npx type' {
186 $result = Get-VscePackageCommand -CommandType 'npx'
187 $result.Executable | Should -Be 'npx'
188 $result.Arguments | Should -Contain '@vscode/vsce'
189 $result.Arguments | Should -Contain 'package'
190 }
191
192 It 'Returns vsce command for vsce type' {
193 $result = Get-VscePackageCommand -CommandType 'vsce'
194 $result.Executable | Should -Be 'vsce'
195 $result.Arguments | Should -Contain 'package'
196 }
197
198 It 'Includes --pre-release flag when specified' {
199 $result = Get-VscePackageCommand -CommandType 'npx' -PreRelease
200 $result.Arguments | Should -Contain '--pre-release'
201 }
202
203 It 'Excludes --pre-release flag when not specified' {
204 $result = Get-VscePackageCommand -CommandType 'npx'
205 $result.Arguments | Should -Not -Contain '--pre-release'
206 }
207}
208
209Describe 'New-PackagingResult' {
210 BeforeAll {
211 $script:testVsixPath = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath().TrimEnd([System.IO.Path]::DirectorySeparatorChar), 'ext.vsix')
212 }
213
214 It 'Creates success result with all properties' {
215 $result = New-PackagingResult -Success $true -OutputPath $script:testVsixPath -Version '1.0.0' -ErrorMessage $null
216 $result.Success | Should -BeTrue
217 $result.OutputPath | Should -Be $script:testVsixPath
218 $result.Version | Should -Be '1.0.0'
219 $result.ErrorMessage | Should -BeNullOrEmpty
220 }
221
222 It 'Creates failure result with error message' {
223 $result = New-PackagingResult -Success $false -OutputPath $null -Version $null -ErrorMessage 'Packaging failed'
224 $result.Success | Should -BeFalse
225 $result.ErrorMessage | Should -Be 'Packaging failed'
226 }
227
228 It 'Creates result with default empty strings for optional parameters' {
229 $result = New-PackagingResult -Success $true
230 $result.Success | Should -BeTrue
231 $result.OutputPath | Should -Be ''
232 $result.Version | Should -Be ''
233 $result.ErrorMessage | Should -Be ''
234 }
235
236 It 'Creates failure result with only error message specified' {
237 $result = New-PackagingResult -Success $false -ErrorMessage 'Something went wrong'
238 $result.Success | Should -BeFalse
239 $result.ErrorMessage | Should -Be 'Something went wrong'
240 $result.OutputPath | Should -Be ''
241 $result.Version | Should -Be ''
242 }
243}
244
245Describe 'Get-ResolvedPackageVersion' {
246 It 'Returns specified version when provided' {
247 $result = Get-ResolvedPackageVersion -SpecifiedVersion '2.0.0' -ManifestVersion '1.0.0' -DevPatchNumber ''
248 $result.IsValid | Should -BeTrue
249 $result.PackageVersion | Should -Be '2.0.0'
250 }
251
252 It 'Returns manifest version when no specified version' {
253 $result = Get-ResolvedPackageVersion -SpecifiedVersion '' -ManifestVersion '1.5.0' -DevPatchNumber ''
254 $result.IsValid | Should -BeTrue
255 $result.PackageVersion | Should -Be '1.5.0'
256 }
257
258 It 'Applies dev patch number when provided' {
259 $result = Get-ResolvedPackageVersion -SpecifiedVersion '' -ManifestVersion '1.0.0' -DevPatchNumber '42'
260 $result.IsValid | Should -BeTrue
261 $result.PackageVersion | Should -Be '1.0.0-dev.42'
262 }
263
264 It 'Specified version with dev patch appends dev suffix' {
265 $result = Get-ResolvedPackageVersion -SpecifiedVersion '3.0.0' -ManifestVersion '1.0.0' -DevPatchNumber '99'
266 $result.IsValid | Should -BeTrue
267 $result.PackageVersion | Should -Be '3.0.0-dev.99'
268 }
269
270 It 'Returns invalid for malformed specified version' {
271 $result = Get-ResolvedPackageVersion -SpecifiedVersion 'not-a-version' -ManifestVersion '1.0.0' -DevPatchNumber ''
272 $result.IsValid | Should -BeFalse
273 $result.ErrorMessage | Should -Match 'Invalid version format specified'
274 }
275
276 It 'Returns invalid for malformed manifest version when no specified version' {
277 $result = Get-ResolvedPackageVersion -SpecifiedVersion '' -ManifestVersion 'bad-version' -DevPatchNumber ''
278 $result.IsValid | Should -BeFalse
279 $result.ErrorMessage | Should -Match 'Invalid version format in package.json'
280 }
281
282 It 'Extracts base version from manifest with prerelease suffix' {
283 $result = Get-ResolvedPackageVersion -SpecifiedVersion '' -ManifestVersion '1.2.3-beta.1' -DevPatchNumber ''
284 $result.IsValid | Should -BeTrue
285 $result.BaseVersion | Should -Be '1.2.3'
286 $result.PackageVersion | Should -Be '1.2.3'
287 }
288
289 It 'Returns BaseVersion correctly when specified version provided' {
290 $result = Get-ResolvedPackageVersion -SpecifiedVersion '4.5.6' -ManifestVersion '1.0.0' -DevPatchNumber ''
291 $result.IsValid | Should -BeTrue
292 $result.BaseVersion | Should -Be '4.5.6'
293 }
294}
295
296Describe 'Invoke-PackageExtension' {
297 BeforeAll {
298 $script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "pkg-ext-test-$([guid]::NewGuid().ToString('N').Substring(0,8))"
299 $script:extDir = Join-Path $script:testRoot 'extension'
300 $script:repoRoot = Join-Path $script:testRoot 'repo'
301 }
302
303 BeforeEach {
304 # Create fresh test directories for each test
305 New-Item -Path $script:extDir -ItemType Directory -Force | Out-Null
306 New-Item -Path $script:repoRoot -ItemType Directory -Force | Out-Null
307 New-Item -Path (Join-Path $script:repoRoot '.github') -ItemType Directory -Force | Out-Null
308 New-Item -Path (Join-Path $script:repoRoot '.github/skills') -ItemType Directory -Force | Out-Null
309 New-Item -Path (Join-Path $script:repoRoot 'scripts/dev-tools') -ItemType Directory -Force | Out-Null
310 New-Item -Path (Join-Path $script:repoRoot 'scripts/lib/Modules') -ItemType Directory -Force | Out-Null
311 Set-Content -Path (Join-Path $script:repoRoot 'scripts/lib/Modules/CIHelpers.psm1') -Value '# Mock module'
312 New-Item -Path (Join-Path $script:repoRoot 'docs/templates') -ItemType Directory -Force | Out-Null
313 }
314
315 AfterEach {
316 if (Test-Path $script:testRoot) {
317 Remove-Item -Path $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
318 }
319 }
320
321 It 'Returns failure when extension directory does not exist' {
322 $nonexistentPath = Join-Path ([System.IO.Path]::GetTempPath()) "nonexistent-ext-$([guid]::NewGuid().ToString('N').Substring(0,8))"
323 $result = Invoke-PackageExtension -ExtensionDirectory $nonexistentPath -RepoRoot $script:repoRoot
324 $result.Success | Should -BeFalse
325 $result.ErrorMessage | Should -Match 'Extension directory not found'
326 }
327
328 It 'Returns failure when package.json missing' {
329 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
330 $result.Success | Should -BeFalse
331 $result.ErrorMessage | Should -Match 'package.json not found'
332 }
333
334 It 'Returns failure when .github directory missing' {
335 # Create package.json but remove .github
336 $manifest = @{
337 name = 'test-ext'
338 version = '1.0.0'
339 publisher = 'test'
340 engines = @{ vscode = '^1.80.0' }
341 }
342 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
343 Remove-Item -Path (Join-Path $script:repoRoot '.github') -Recurse -Force
344
345 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
346 $result.Success | Should -BeFalse
347 $result.ErrorMessage | Should -Match '.github directory not found'
348 }
349
350 It 'Returns failure for invalid JSON in package.json' {
351 Set-Content -Path (Join-Path $script:extDir 'package.json') -Value '{ invalid json }'
352
353 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
354 $result.Success | Should -BeFalse
355 $result.ErrorMessage | Should -Match 'Failed to parse package.json'
356 }
357
358 It 'Returns failure for invalid manifest missing required fields' {
359 $manifest = @{ name = 'only-name' } # Missing version, publisher, engines
360 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
361
362 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
363 $result.Success | Should -BeFalse
364 $result.ErrorMessage | Should -Match 'Invalid package.json'
365 }
366
367 It 'Returns failure for invalid specified version format' {
368 $manifest = @{
369 name = 'test-ext'
370 version = '1.0.0'
371 publisher = 'test'
372 engines = @{ vscode = '^1.80.0' }
373 }
374 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
375
376 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot -Version 'invalid-version'
377 $result.Success | Should -BeFalse
378 $result.ErrorMessage | Should -Match 'Invalid version format'
379 }
380
381 It 'Returns structured result hashtable with expected keys' {
382 Mock Test-VsceAvailable { return @{ IsAvailable = $false; CommandType = ''; Command = '' } }
383
384 $manifest = @{
385 name = 'test-ext'
386 version = '1.0.0'
387 publisher = 'test'
388 engines = @{ vscode = '^1.80.0' }
389 }
390 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
391
392 # Will fail at vsce availability check, validates structure
393 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
394
395 $result | Should -BeOfType [hashtable]
396 $result.Keys | Should -Contain 'Success'
397 $result.Keys | Should -Contain 'ErrorMessage'
398 }
399
400 It 'Applies DevPatchNumber to version correctly' {
401 Mock Test-VsceAvailable { return @{ IsAvailable = $false; CommandType = ''; Command = '' } }
402
403 $manifest = @{
404 name = 'test-ext'
405 version = '2.0.0'
406 publisher = 'test'
407 engines = @{ vscode = '^1.80.0' }
408 }
409 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
410
411 # Will fail at vsce availability check, validates version resolution path
412 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot -DevPatchNumber '123'
413
414 # Even on failure, the result indicates version was processed
415 $result | Should -BeOfType [hashtable]
416 }
417
418 It 'Copies changelog when valid path provided' {
419 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
420 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
421
422 $manifest = @{
423 name = 'test-ext'
424 version = '1.0.0'
425 publisher = 'test'
426 engines = @{ vscode = '^1.80.0' }
427 }
428 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
429
430 # Create a changelog file
431 $changelogPath = Join-Path $script:repoRoot 'CHANGELOG.md'
432 Set-Content -Path $changelogPath -Value '# Changelog'
433
434 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot -ChangelogPath $changelogPath
435
436 # Changelog should be copied to extension directory
437 $destChangelog = Join-Path $script:extDir 'CHANGELOG.md'
438 Test-Path $destChangelog | Should -BeTrue
439 $result | Should -Not -BeNullOrEmpty
440 }
441
442 It 'Warns when changelog path does not exist' {
443 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
444 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
445 Mock Write-Warning { }
446
447 $manifest = @{
448 name = 'test-ext'
449 version = '1.0.0'
450 publisher = 'test'
451 engines = @{ vscode = '^1.80.0' }
452 }
453 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
454
455 $nonexistentChangelog = Join-Path ([System.IO.Path]::GetTempPath()) "changelog-$([guid]::NewGuid().ToString('N').Substring(0,8)).md"
456 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot -ChangelogPath $nonexistentChangelog
457
458 Should -Invoke Write-Warning -Times 1
459 $result | Should -Not -BeNullOrEmpty
460 }
461
462 It 'Returns failure when vsce command fails with non-zero exit code' {
463 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
464 Mock Get-VscePackageCommand { return @{ Executable = 'pwsh'; Arguments = @('-Command', 'exit 1') } }
465
466 $manifest = @{
467 name = 'test-ext'
468 version = '1.0.0'
469 publisher = 'test'
470 engines = @{ vscode = '^1.80.0' }
471 }
472 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
473
474 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
475
476 $result.Success | Should -BeFalse
477 $result.ErrorMessage | Should -Match 'vsce package command failed|The term'
478 }
479
480 It 'Returns failure when CIHelpers.psm1 missing' {
481 # Create package.json and .github, but remove CIHelpers.psm1
482 $manifest = @{
483 name = 'test-ext'
484 version = '1.0.0'
485 publisher = 'test'
486 engines = @{ vscode = '^1.80.0' }
487 }
488 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
489 Remove-Item -Path (Join-Path $script:repoRoot 'scripts/lib/Modules/CIHelpers.psm1') -Force
490
491 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
492 $result.Success | Should -BeFalse
493 $result.ErrorMessage | Should -Match 'CIHelpers.psm1 not found'
494 }
495
496 Context 'Package.json backup restore' {
497 It 'Does not create backup when no collection specified' {
498 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
499 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
500
501 $manifest = @{
502 name = 'test-ext'
503 version = '1.0.0'
504 publisher = 'test'
505 engines = @{ vscode = '^1.80.0' }
506 }
507 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
508
509 # Create fake vsix so packaging succeeds
510 $vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
511 Set-Content -Path $vsixPath -Value 'fake-vsix'
512
513 $null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
514
515 Test-Path (Join-Path $script:extDir 'package.json.bak') | Should -BeFalse
516 }
517
518 It 'Restores package.json from backup after packaging' {
519 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
520 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
521
522 # Original package.json content (will be overwritten by template)
523 $originalManifest = @{
524 name = 'hve-core'
525 version = '1.0.0'
526 publisher = 'test'
527 engines = @{ vscode = '^1.80.0' }
528 }
529
530 # Simulate post-template state: template content in package.json, original backed up
531 $templateManifest = @{
532 name = 'hve-developer'
533 version = '1.0.0'
534 publisher = 'test'
535 engines = @{ vscode = '^1.80.0' }
536 }
537 $templateManifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
538 $originalManifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json.bak')
539
540 # Create fake vsix so packaging succeeds
541 $vsixPath = Join-Path $script:extDir 'hve-developer-1.0.0.vsix'
542 Set-Content -Path $vsixPath -Value 'fake-vsix'
543
544 $null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
545
546 # Verify the original manifest was restored
547 $restored = Get-Content -Path (Join-Path $script:extDir 'package.json') -Raw | ConvertFrom-Json
548 $restored.name | Should -Be 'hve-core'
549 }
550
551 It 'Removes backup file after restore' {
552 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
553 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
554
555 $manifest = @{
556 name = 'test-ext'
557 version = '1.0.0'
558 publisher = 'test'
559 engines = @{ vscode = '^1.80.0' }
560 }
561 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
562
563 # Create a backup file manually to simulate Invoke-PrepareExtension behavior
564 $backupManifest = @{
565 name = 'original-ext'
566 version = '1.0.0'
567 publisher = 'test'
568 engines = @{ vscode = '^1.80.0' }
569 }
570 $backupManifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json.bak')
571
572 # Create fake vsix so packaging succeeds
573 $vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
574 Set-Content -Path $vsixPath -Value 'fake-vsix'
575
576 $null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
577
578 Test-Path (Join-Path $script:extDir 'package.json.bak') | Should -BeFalse
579 }
580
581 It 'Restored package.json contains original metadata' {
582 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
583 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
584
585 # Original manifest backed up before template was applied
586 $originalManifest = @{
587 name = 'hve-core-original'
588 version = '2.5.0'
589 publisher = 'original-pub'
590 description = 'Original description'
591 engines = @{ vscode = '^1.80.0' }
592 }
593
594 # Template manifest currently in package.json
595 $templateManifest = @{
596 name = 'hve-persona'
597 version = '2.5.0'
598 publisher = 'persona-pub'
599 description = 'Persona description'
600 engines = @{ vscode = '^1.80.0' }
601 }
602 $templateManifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
603 $originalManifest | ConvertTo-Json -Depth 10 | Set-Content (Join-Path $script:extDir 'package.json.bak')
604
605 # Create fake vsix matching the template name
606 $vsixPath = Join-Path $script:extDir 'hve-persona-2.5.0.vsix'
607 Set-Content -Path $vsixPath -Value 'fake-vsix'
608
609 $null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
610
611 $restored = Get-Content -Path (Join-Path $script:extDir 'package.json') -Raw | ConvertFrom-Json
612 $restored.name | Should -Be 'hve-core-original'
613 $restored.publisher | Should -Be 'original-pub'
614 $restored.description | Should -Be 'Original description'
615 }
616 }
617}
618
619Describe 'Test-PackagingInputsValid' {
620 BeforeAll {
621 $script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "pkg-inputs-test-$([guid]::NewGuid().ToString('N').Substring(0,8))"
622 $script:extDir = Join-Path $script:testRoot 'extension'
623 $script:repoRoot = Join-Path $script:testRoot 'repo'
624 }
625
626 BeforeEach {
627 New-Item -Path $script:extDir -ItemType Directory -Force | Out-Null
628 New-Item -Path $script:repoRoot -ItemType Directory -Force | Out-Null
629 New-Item -Path (Join-Path $script:repoRoot '.github') -ItemType Directory -Force | Out-Null
630 New-Item -Path (Join-Path $script:repoRoot '.github/skills') -ItemType Directory -Force | Out-Null
631 New-Item -Path (Join-Path $script:repoRoot 'scripts/lib/Modules') -ItemType Directory -Force | Out-Null
632 Set-Content -Path (Join-Path $script:repoRoot 'scripts/lib/Modules/CIHelpers.psm1') -Value '# Mock'
633 Set-Content -Path (Join-Path $script:extDir 'package.json') -Value '{}'
634 }
635
636 AfterEach {
637 if (Test-Path $script:testRoot) {
638 Remove-Item -Path $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
639 }
640 }
641
642 It 'Returns valid when all paths exist' {
643 $result = Test-PackagingInputsValid -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
644 $result.IsValid | Should -BeTrue
645 $result.Errors | Should -BeNullOrEmpty
646 }
647
648 It 'Returns resolved paths in result' {
649 $result = Test-PackagingInputsValid -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
650 $result.PackageJsonPath | Should -BeLike '*package.json'
651 $result.GitHubDir | Should -BeLike '*.github'
652 $result.CIHelpersPath | Should -BeLike '*CIHelpers.psm1'
653 }
654
655 It 'Returns error when extension directory not found' {
656 $nonexistent = Join-Path ([System.IO.Path]::GetTempPath()) "nonexistent-$([guid]::NewGuid().ToString('N').Substring(0,8))"
657 $result = Test-PackagingInputsValid -ExtensionDirectory $nonexistent -RepoRoot $script:repoRoot
658 $result.IsValid | Should -BeFalse
659 # Function accumulates multiple errors; extension dir missing cascades to package.json missing
660 $result.Errors | Should -Match 'Extension directory not found|package.json not found'
661 }
662
663 It 'Returns error when package.json not found' {
664 Remove-Item -Path (Join-Path $script:extDir 'package.json') -Force
665 $result = Test-PackagingInputsValid -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
666 $result.IsValid | Should -BeFalse
667 $result.Errors | Should -Match 'package.json not found'
668 }
669
670 It 'Returns error when .github directory not found' {
671 Remove-Item -Path (Join-Path $script:repoRoot '.github') -Recurse -Force
672 $result = Test-PackagingInputsValid -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
673 $result.IsValid | Should -BeFalse
674 $result.Errors | Should -Match '.github directory not found'
675 }
676
677 It 'Returns error when CIHelpers.psm1 not found' {
678 Remove-Item -Path (Join-Path $script:repoRoot 'scripts/lib/Modules/CIHelpers.psm1') -Force
679 $result = Test-PackagingInputsValid -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
680 $result.IsValid | Should -BeFalse
681 $result.Errors | Should -Match 'CIHelpers.psm1 not found'
682 }
683
684 It 'Collects multiple errors' {
685 Remove-Item -Path (Join-Path $script:extDir 'package.json') -Force
686 Remove-Item -Path (Join-Path $script:repoRoot '.github') -Recurse -Force
687 $result = Test-PackagingInputsValid -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
688 $result.IsValid | Should -BeFalse
689 $result.Errors.Count | Should -BeGreaterOrEqual 2
690 }
691}
692
693Describe 'Get-PackagingDirectorySpec' {
694 BeforeAll {
695 # Use platform-agnostic temp paths for cross-platform CI compatibility
696 $script:repoRoot = Join-Path ([System.IO.Path]::GetTempPath()) 'spec-repo'
697 $script:extDir = Join-Path ([System.IO.Path]::GetTempPath()) 'spec-ext'
698 }
699
700 It 'Returns array of 5 directory specifications' {
701 $result = Get-PackagingDirectorySpec -RepoRoot $script:repoRoot -ExtensionDirectory $script:extDir
702 $result.Count | Should -Be 5
703 }
704
705 It 'Includes .github directory specification' {
706 $result = Get-PackagingDirectorySpec -RepoRoot $script:repoRoot -ExtensionDirectory $script:extDir
707 $githubSpec = $result | Where-Object { $_.Source -like '*.github' }
708 $githubSpec | Should -Not -BeNullOrEmpty
709 $githubSpec.Destination | Should -BeLike '*.github'
710 $githubSpec.IsFile | Should -BeFalse
711 }
712
713 It 'Includes dev-tools directory specification' {
714 $result = Get-PackagingDirectorySpec -RepoRoot $script:repoRoot -ExtensionDirectory $script:extDir
715 $devToolsSpec = $result | Where-Object { $_.Source -like '*dev-tools' }
716 $devToolsSpec | Should -Not -BeNullOrEmpty
717 $devToolsSpec.IsFile | Should -BeFalse
718 }
719
720 It 'Includes CIHelpers.psm1 file specification' {
721 $result = Get-PackagingDirectorySpec -RepoRoot $script:repoRoot -ExtensionDirectory $script:extDir
722 $ciHelpersSpec = $result | Where-Object { $_.Source -like '*CIHelpers.psm1' }
723 $ciHelpersSpec | Should -Not -BeNullOrEmpty
724 $ciHelpersSpec.IsFile | Should -BeTrue
725 }
726
727 It 'Includes docs/templates directory specification' {
728 $result = Get-PackagingDirectorySpec -RepoRoot $script:repoRoot -ExtensionDirectory $script:extDir
729 $templatesSpec = $result | Where-Object { $_.Source -like '*templates' }
730 $templatesSpec | Should -Not -BeNullOrEmpty
731 $templatesSpec.IsFile | Should -BeFalse
732 }
733
734 It 'Includes skills directory specification' {
735 $result = Get-PackagingDirectorySpec -RepoRoot $script:repoRoot -ExtensionDirectory $script:extDir
736 $skillsSpec = $result | Where-Object { $_.Source -like '*skills' }
737 $skillsSpec | Should -Not -BeNullOrEmpty
738 $skillsSpec.Destination | Should -BeLike '*skills'
739 $skillsSpec.IsFile | Should -BeFalse
740 }
741
742 It 'Uses correct path joining for source and destination' {
743 $result = Get-PackagingDirectorySpec -RepoRoot $script:repoRoot -ExtensionDirectory $script:extDir
744 foreach ($spec in $result) {
745 $spec.Source | Should -Not -BeNullOrEmpty
746 $spec.Destination | Should -Not -BeNullOrEmpty
747 }
748 }
749}
750
751Describe 'Invoke-VsceCommand' {
752 BeforeAll {
753 $script:testDir = Join-Path ([System.IO.Path]::GetTempPath()) "vsce-cmd-test-$([guid]::NewGuid().ToString('N').Substring(0,8))"
754 }
755
756 BeforeEach {
757 New-Item -Path $script:testDir -ItemType Directory -Force | Out-Null
758 }
759
760 AfterEach {
761 if (Test-Path $script:testDir) {
762 Remove-Item -Path $script:testDir -Recurse -Force -ErrorAction SilentlyContinue
763 }
764 }
765
766 It 'Returns hashtable with Success and ExitCode' {
767 $result = Invoke-VsceCommand -Executable 'pwsh' -Arguments @('-Command', 'exit 0') -WorkingDirectory $script:testDir
768 $result | Should -BeOfType [hashtable]
769 $result.Keys | Should -Contain 'Success'
770 $result.Keys | Should -Contain 'ExitCode'
771 }
772
773 It 'Returns Success true for zero exit code' {
774 $result = Invoke-VsceCommand -Executable 'pwsh' -Arguments @('-Command', 'exit 0') -WorkingDirectory $script:testDir
775 $result.Success | Should -BeTrue
776 $result.ExitCode | Should -Be 0
777 }
778
779 It 'Returns Success false for non-zero exit code' {
780 $result = Invoke-VsceCommand -Executable 'pwsh' -Arguments @('-Command', 'exit 42') -WorkingDirectory $script:testDir
781 $result.Success | Should -BeFalse
782 $result.ExitCode | Should -Be 42
783 }
784
785 It 'Restores working directory after execution' {
786 $originalDir = Get-Location
787 $null = Invoke-VsceCommand -Executable 'pwsh' -Arguments @('-Command', 'exit 0') -WorkingDirectory $script:testDir
788 (Get-Location).Path | Should -Be $originalDir.Path
789 }
790
791 It 'Uses cmd wrapper when UseWindowsWrapper specified with npx' -Skip:(-not $IsWindows) {
792 # Test that cmd wrapper path executes without error
793 # npx --help outputs text to the pipeline alongside the hashtable return value
794 $output = Invoke-VsceCommand -Executable 'npx' -Arguments @('--help') -WorkingDirectory $script:testDir -UseWindowsWrapper
795 # Filter for the hashtable return (command output also flows through pipeline)
796 $result = $output | Where-Object { $_ -is [hashtable] }
797 $result | Should -Not -BeNullOrEmpty
798 $result.Keys | Should -Contain 'Success'
799 }
800}
801
802Describe 'Remove-PackagingArtifacts' {
803 BeforeAll {
804 $script:testDir = Join-Path ([System.IO.Path]::GetTempPath()) "rm-artifacts-test-$([guid]::NewGuid().ToString('N').Substring(0,8))"
805 }
806
807 BeforeEach {
808 New-Item -Path $script:testDir -ItemType Directory -Force | Out-Null
809 }
810
811 AfterEach {
812 if (Test-Path $script:testDir) {
813 Remove-Item -Path $script:testDir -Recurse -Force -ErrorAction SilentlyContinue
814 }
815 }
816
817 It 'Removes existing directories' {
818 New-Item -Path (Join-Path $script:testDir '.github') -ItemType Directory -Force | Out-Null
819 New-Item -Path (Join-Path $script:testDir 'scripts') -ItemType Directory -Force | Out-Null
820
821 Remove-PackagingArtifacts -ExtensionDirectory $script:testDir
822
823 Test-Path (Join-Path $script:testDir '.github') | Should -BeFalse
824 Test-Path (Join-Path $script:testDir 'scripts') | Should -BeFalse
825 }
826
827 It 'Silently skips non-existent directories' {
828 { Remove-PackagingArtifacts -ExtensionDirectory $script:testDir } | Should -Not -Throw
829 }
830
831 It 'Uses custom directory names when specified' {
832 New-Item -Path (Join-Path $script:testDir 'custom1') -ItemType Directory -Force | Out-Null
833 New-Item -Path (Join-Path $script:testDir 'custom2') -ItemType Directory -Force | Out-Null
834
835 Remove-PackagingArtifacts -ExtensionDirectory $script:testDir -DirectoryNames @('custom1', 'custom2')
836
837 Test-Path (Join-Path $script:testDir 'custom1') | Should -BeFalse
838 Test-Path (Join-Path $script:testDir 'custom2') | Should -BeFalse
839 }
840
841 It 'Removes nested contents recursively' {
842 $nestedDir = Join-Path $script:testDir '.github/nested/deep'
843 New-Item -Path $nestedDir -ItemType Directory -Force | Out-Null
844 Set-Content -Path (Join-Path $nestedDir 'file.txt') -Value 'content'
845
846 Remove-PackagingArtifacts -ExtensionDirectory $script:testDir -DirectoryNames @('.github')
847
848 Test-Path (Join-Path $script:testDir '.github') | Should -BeFalse
849 }
850}
851
852Describe 'Restore-PackageJsonVersion' {
853 BeforeAll {
854 $script:testDir = Join-Path ([System.IO.Path]::GetTempPath()) "restore-ver-test-$([guid]::NewGuid().ToString('N').Substring(0,8))"
855 }
856
857 BeforeEach {
858 New-Item -Path $script:testDir -ItemType Directory -Force | Out-Null
859 }
860
861 AfterEach {
862 if (Test-Path $script:testDir) {
863 Remove-Item -Path $script:testDir -Recurse -Force -ErrorAction SilentlyContinue
864 }
865 }
866
867 It 'Restores original version to package.json' {
868 $packageJsonPath = Join-Path $script:testDir 'package.json'
869 $packageJson = @{ name = 'test'; version = '2.0.0' }
870 $packageJson | ConvertTo-Json | Set-Content -Path $packageJsonPath
871
872 $obj = Get-Content $packageJsonPath | ConvertFrom-Json
873 Restore-PackageJsonVersion -PackageJsonPath $packageJsonPath -PackageJson $obj -OriginalVersion '1.0.0'
874
875 $updated = Get-Content $packageJsonPath | ConvertFrom-Json
876 $updated.version | Should -Be '1.0.0'
877 }
878
879 It 'Returns early when OriginalVersion is null' {
880 $packageJsonPath = Join-Path $script:testDir 'package.json'
881 $packageJson = @{ name = 'test'; version = '2.0.0' }
882 $packageJson | ConvertTo-Json | Set-Content -Path $packageJsonPath
883
884 $obj = Get-Content $packageJsonPath | ConvertFrom-Json
885 { Restore-PackageJsonVersion -PackageJsonPath $packageJsonPath -PackageJson $obj -OriginalVersion $null } | Should -Not -Throw
886
887 $unchanged = Get-Content $packageJsonPath | ConvertFrom-Json
888 $unchanged.version | Should -Be '2.0.0'
889 }
890
891 It 'Returns early when PackageJson is null' {
892 $packageJsonPath = Join-Path $script:testDir 'package.json'
893 Set-Content -Path $packageJsonPath -Value '{"version": "2.0.0"}'
894
895 { Restore-PackageJsonVersion -PackageJsonPath $packageJsonPath -PackageJson $null -OriginalVersion '1.0.0' } | Should -Not -Throw
896
897 $unchanged = Get-Content $packageJsonPath | ConvertFrom-Json
898 $unchanged.version | Should -Be '2.0.0'
899 }
900
901 It 'Returns early when PackageJsonPath is null' {
902 $packageJson = @{ name = 'test'; version = '2.0.0' }
903 { Restore-PackageJsonVersion -PackageJsonPath $null -PackageJson $packageJson -OriginalVersion '1.0.0' } | Should -Not -Throw
904 }
905
906 It 'Handles write failure gracefully' {
907 Mock Write-Warning {}
908 $invalidPath = Join-Path $script:testDir 'nonexistent/package.json'
909 $packageJson = [PSCustomObject]@{ name = 'test'; version = '2.0.0' }
910
911 { Restore-PackageJsonVersion -PackageJsonPath $invalidPath -PackageJson $packageJson -OriginalVersion '1.0.0' } | Should -Not -Throw
912 }
913}
914
915Describe 'CI Integration - Package-Extension' {
916 BeforeAll {
917 $script:testRoot = Join-Path ([System.IO.Path]::GetTempPath()) "ci-int-test-$([guid]::NewGuid().ToString('N').Substring(0,8))"
918 $script:extDir = Join-Path $script:testRoot 'extension'
919 $script:repoRoot = Join-Path $script:testRoot 'repo'
920 }
921
922 AfterAll {
923 if (Test-Path $script:testRoot) {
924 Remove-Item -Path $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
925 }
926 }
927
928 Context 'GitHub Actions environment' {
929 BeforeEach {
930 Initialize-MockCIEnvironment
931 New-Item -Path $script:extDir -ItemType Directory -Force | Out-Null
932 New-Item -Path $script:repoRoot -ItemType Directory -Force | Out-Null
933 New-Item -Path (Join-Path $script:repoRoot '.github') -ItemType Directory -Force | Out-Null
934 New-Item -Path (Join-Path $script:repoRoot '.github/skills') -ItemType Directory -Force | Out-Null
935 New-Item -Path (Join-Path $script:repoRoot 'scripts/dev-tools') -ItemType Directory -Force | Out-Null
936 New-Item -Path (Join-Path $script:repoRoot 'scripts/lib/Modules') -ItemType Directory -Force | Out-Null
937 Set-Content -Path (Join-Path $script:repoRoot 'scripts/lib/Modules/CIHelpers.psm1') -Value '# Mock module'
938 New-Item -Path (Join-Path $script:repoRoot 'docs/templates') -ItemType Directory -Force | Out-Null
939
940 $manifest = @{
941 name = 'test-ext'
942 version = '1.0.0'
943 publisher = 'test'
944 engines = @{ vscode = '^1.80.0' }
945 }
946 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
947 }
948
949 AfterEach {
950 Clear-MockCIEnvironment
951 if (Test-Path $script:testRoot) {
952 Remove-Item -Path $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
953 }
954 }
955
956 It 'Sets version output variable on successful package' {
957 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
958 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
959
960 $vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
961 Set-Content -Path $vsixPath -Value 'fake-vsix'
962
963 $null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
964
965 $outputContent = Get-Content $env:GITHUB_OUTPUT -Raw
966 $outputContent | Should -Match 'version=1\.0\.0'
967 }
968
969 It 'Sets vsix-file output variable on successful package' {
970 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
971 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
972
973 $vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
974 Set-Content -Path $vsixPath -Value 'fake-vsix'
975
976 $null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
977
978 $outputContent = Get-Content $env:GITHUB_OUTPUT -Raw
979 $outputContent | Should -Match 'vsix-file=test-ext-1\.0\.0\.vsix'
980 }
981
982 It 'Sets pre-release output variable when PreRelease specified' {
983 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
984 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
985
986 $vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
987 Set-Content -Path $vsixPath -Value 'fake-vsix'
988
989 $null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot -PreRelease
990
991 $outputContent = Get-Content $env:GITHUB_OUTPUT -Raw
992 $outputContent | Should -Match 'pre-release=True'
993 }
994
995 It 'Sets pre-release output variable to false when PreRelease not specified' {
996 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
997 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
998
999 $vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
1000 Set-Content -Path $vsixPath -Value 'fake-vsix'
1001
1002 $null = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
1003
1004 $outputContent = Get-Content $env:GITHUB_OUTPUT -Raw
1005 $outputContent | Should -Match 'pre-release=False'
1006 }
1007
1008 It 'Returns failure result when vsce command fails' {
1009 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
1010 Mock Get-VscePackageCommand { return @{ Executable = 'pwsh'; Arguments = @('-Command', 'exit 1') } }
1011
1012 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
1013
1014 $result.Success | Should -BeFalse
1015 $result.ErrorMessage | Should -Match 'vsce package command failed'
1016 }
1017 }
1018
1019 Context 'Local environment' {
1020 BeforeEach {
1021 Clear-MockCIEnvironment
1022
1023 New-Item -Path $script:extDir -ItemType Directory -Force | Out-Null
1024 New-Item -Path $script:repoRoot -ItemType Directory -Force | Out-Null
1025 New-Item -Path (Join-Path $script:repoRoot '.github') -ItemType Directory -Force | Out-Null
1026 New-Item -Path (Join-Path $script:repoRoot '.github/skills') -ItemType Directory -Force | Out-Null
1027 New-Item -Path (Join-Path $script:repoRoot 'scripts/dev-tools') -ItemType Directory -Force | Out-Null
1028 New-Item -Path (Join-Path $script:repoRoot 'scripts/lib/Modules') -ItemType Directory -Force | Out-Null
1029 Set-Content -Path (Join-Path $script:repoRoot 'scripts/lib/Modules/CIHelpers.psm1') -Value '# Mock module'
1030 New-Item -Path (Join-Path $script:repoRoot 'docs/templates') -ItemType Directory -Force | Out-Null
1031
1032 $manifest = @{
1033 name = 'test-ext'
1034 version = '1.0.0'
1035 publisher = 'test'
1036 engines = @{ vscode = '^1.80.0' }
1037 }
1038 $manifest | ConvertTo-Json | Set-Content (Join-Path $script:extDir 'package.json')
1039 }
1040
1041 AfterEach {
1042 if (Test-Path $script:testRoot) {
1043 Remove-Item -Path $script:testRoot -Recurse -Force -ErrorAction SilentlyContinue
1044 }
1045 }
1046
1047 It 'Completes without error when not in CI environment' {
1048 Mock Test-VsceAvailable { return @{ IsAvailable = $true; CommandType = 'vsce'; Command = 'vsce' } }
1049 Mock Get-VscePackageCommand { return @{ Executable = 'echo'; Arguments = @('mocked') } }
1050
1051 $vsixPath = Join-Path $script:extDir 'test-ext-1.0.0.vsix'
1052 Set-Content -Path $vsixPath -Value 'fake-vsix'
1053
1054 $result = Invoke-PackageExtension -ExtensionDirectory $script:extDir -RepoRoot $script:repoRoot
1055
1056 $result.Success | Should -BeTrue
1057 }
1058 }
1059}
1060