microsoft/hve-core

Public

mirrored fromhttps://github.com/microsoft/hve-coreAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
copilot/update-workflow-file-and-script

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/tests/plugins/Generate-Plugins.Tests.ps1

472lines · modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4
5BeforeAll {
6 . $PSScriptRoot/../../plugins/Generate-Plugins.ps1
7}
8
9Describe 'Get-AllowedCollectionMaturities' {
10 It 'Returns only stable for Stable channel' {
11 $result = Get-AllowedCollectionMaturities -Channel 'Stable'
12 $result | Should -Be @('stable')
13 }
14
15 It 'Returns stable, preview, and experimental for PreRelease channel' {
16 $result = Get-AllowedCollectionMaturities -Channel 'PreRelease'
17 $result | Should -Contain 'stable'
18 $result | Should -Contain 'preview'
19 $result | Should -Contain 'experimental'
20 }
21
22 It 'Does not include deprecated for either channel' {
23 $stable = Get-AllowedCollectionMaturities -Channel 'Stable'
24 $preRelease = Get-AllowedCollectionMaturities -Channel 'PreRelease'
25 $stable | Should -Not -Contain 'deprecated'
26 $preRelease | Should -Not -Contain 'deprecated'
27 }
28}
29
30Describe 'Select-CollectionItemsByChannel' {
31 It 'Includes stable items on Stable channel' {
32 $collection = @{
33 id = 'test'
34 items = @(
35 @{ kind = 'agent'; path = '.github/agents/a.agent.md'; maturity = 'stable' }
36 )
37 }
38 $result = Select-CollectionItemsByChannel -Collection $collection -Channel 'Stable'
39 $result.items.Count | Should -Be 1
40 }
41
42 It 'Excludes preview items on Stable channel' {
43 $collection = @{
44 id = 'test'
45 items = @(
46 @{ kind = 'agent'; path = '.github/agents/a.agent.md'; maturity = 'stable' },
47 @{ kind = 'agent'; path = '.github/agents/b.agent.md'; maturity = 'preview' }
48 )
49 }
50 $result = Select-CollectionItemsByChannel -Collection $collection -Channel 'Stable'
51 $result.items.Count | Should -Be 1
52 }
53
54 It 'Includes preview and experimental items on PreRelease channel' {
55 $collection = @{
56 id = 'test'
57 items = @(
58 @{ kind = 'agent'; path = '.github/agents/a.agent.md'; maturity = 'stable' },
59 @{ kind = 'prompt'; path = '.github/prompts/b.prompt.md'; maturity = 'preview' },
60 @{ kind = 'instruction'; path = '.github/instructions/c.instructions.md'; maturity = 'experimental' }
61 )
62 }
63 $result = Select-CollectionItemsByChannel -Collection $collection -Channel 'PreRelease'
64 $result.items.Count | Should -Be 3
65 }
66
67 It 'Excludes deprecated items on PreRelease channel' {
68 $collection = @{
69 id = 'test'
70 items = @(
71 @{ kind = 'agent'; path = '.github/agents/a.agent.md'; maturity = 'stable' },
72 @{ kind = 'agent'; path = '.github/agents/old.agent.md'; maturity = 'deprecated' }
73 )
74 }
75 $result = Select-CollectionItemsByChannel -Collection $collection -Channel 'PreRelease'
76 $result.items.Count | Should -Be 1
77 }
78
79 It 'Defaults to stable when maturity is null' {
80 $collection = @{
81 id = 'test'
82 items = @(
83 @{ kind = 'agent'; path = '.github/agents/a.agent.md'; maturity = $null }
84 )
85 }
86 $result = Select-CollectionItemsByChannel -Collection $collection -Channel 'Stable'
87 $result.items.Count | Should -Be 1
88 }
89
90 It 'Preserves non-items keys from collection' {
91 $collection = @{
92 id = 'test'
93 name = 'Test Collection'
94 description = 'desc'
95 items = @(
96 @{ kind = 'agent'; path = '.github/agents/a.agent.md'; maturity = 'stable' }
97 )
98 }
99 $result = Select-CollectionItemsByChannel -Collection $collection -Channel 'Stable'
100 $result.id | Should -Be 'test'
101 $result.name | Should -Be 'Test Collection'
102 $result.description | Should -Be 'desc'
103 }
104}
105
106Describe 'Invoke-PluginGeneration - collection-level maturity' {
107 BeforeAll {
108 $script:maturityDir = Join-Path $TestDrive ([System.Guid]::NewGuid().ToString())
109 New-Item -ItemType Directory -Path $script:maturityDir -Force | Out-Null
110
111 # Create package.json
112 @{
113 name = 'hve-core'
114 version = '1.0.0'
115 description = 'test'
116 author = 'test-author'
117 } | ConvertTo-Json | Set-Content -Path (Join-Path $script:maturityDir 'package.json')
118
119 # Create collections directory
120 $collectionsDir = Join-Path $script:maturityDir 'collections'
121 New-Item -ItemType Directory -Path $collectionsDir -Force | Out-Null
122
123 # Create .github structure with a test artifact
124 $ghDir = Join-Path $script:maturityDir '.github'
125 $agentsDir = Join-Path $ghDir 'agents/col'
126 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
127 @'
128---
129description: "Test agent"
130---
131'@ | Set-Content -Path (Join-Path $agentsDir 'test.agent.md')
132
133 # Create shared directories for symlinks
134 New-Item -ItemType Directory -Path (Join-Path $script:maturityDir 'docs/templates') -Force | Out-Null
135 New-Item -ItemType Directory -Path (Join-Path $script:maturityDir 'scripts/lib') -Force | Out-Null
136
137 # Create plugins directory
138 New-Item -ItemType Directory -Path (Join-Path $script:maturityDir 'plugins') -Force | Out-Null
139
140 # Create .github/plugin directory
141 New-Item -ItemType Directory -Path (Join-Path $script:maturityDir '.github/plugin') -Force | Out-Null
142
143 # hve-core-all collection (required by Update-HveCoreAllCollection)
144 @"
145id: hve-core-all
146name: hve-core
147description: All artifacts
148tags: []
149items:
150 - path: .github/agents/col/test.agent.md
151 kind: agent
152display: {}
153"@ | Set-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml')
154
155 # Deprecated collection
156 @"
157id: deprecated-col
158name: Deprecated Collection
159description: A deprecated collection
160maturity: deprecated
161items:
162 - path: .github/agents/col/test.agent.md
163 kind: agent
164"@ | Set-Content -Path (Join-Path $collectionsDir 'deprecated-col.collection.yml')
165
166 # Experimental collection
167 @"
168id: experimental-col
169name: Experimental Collection
170description: An experimental collection
171maturity: experimental
172items:
173 - path: .github/agents/col/test.agent.md
174 kind: agent
175"@ | Set-Content -Path (Join-Path $collectionsDir 'experimental-col.collection.yml')
176 }
177
178 AfterAll {
179 Remove-Item -Path $script:maturityDir -Recurse -Force -ErrorAction SilentlyContinue
180 }
181
182 It 'Skips deprecated collection during generation' {
183 Invoke-PluginGeneration -RepoRoot $script:maturityDir -CollectionIds @('deprecated-col') -Refresh -Channel 'PreRelease' | Out-Null
184 $pluginDir = Join-Path $script:maturityDir 'plugins/deprecated-col'
185 Test-Path $pluginDir | Should -BeFalse
186 }
187
188 It 'Generates experimental collection on PreRelease channel' {
189 Invoke-PluginGeneration -RepoRoot $script:maturityDir -CollectionIds @('experimental-col') -Refresh -Channel 'PreRelease' | Out-Null
190 $pluginDir = Join-Path $script:maturityDir 'plugins/experimental-col'
191 Test-Path $pluginDir | Should -BeTrue
192 }
193}
194
195Describe 'Invoke-PluginGeneration' {
196 BeforeAll {
197 $script:tempDir = Join-Path $TestDrive ([System.Guid]::NewGuid().ToString())
198 New-Item -ItemType Directory -Path $script:tempDir -Force | Out-Null
199
200 # Create package.json
201 @{
202 name = 'hve-core'
203 version = '1.0.0'
204 description = 'test'
205 author = 'test-author'
206 } | ConvertTo-Json | Set-Content -Path (Join-Path $script:tempDir 'package.json')
207
208 # Create collections directory with manifests
209 $collectionsDir = Join-Path $script:tempDir 'collections'
210 New-Item -ItemType Directory -Path $collectionsDir -Force | Out-Null
211
212 # Create .github structure with artifacts
213 $ghDir = Join-Path $script:tempDir '.github'
214 $agentsDir = Join-Path $ghDir 'agents'
215 $promptsDir = Join-Path $ghDir 'prompts'
216 $instrDir = Join-Path $ghDir 'instructions'
217 $skillsDir = Join-Path $ghDir 'skills/test-skill'
218 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
219 New-Item -ItemType Directory -Path $promptsDir -Force | Out-Null
220 New-Item -ItemType Directory -Path $instrDir -Force | Out-Null
221 New-Item -ItemType Directory -Path $skillsDir -Force | Out-Null
222
223 @'
224---
225description: "Test agent"
226---
227'@ | Set-Content -Path (Join-Path $agentsDir 'test.agent.md')
228
229 @'
230---
231description: "Test prompt"
232---
233'@ | Set-Content -Path (Join-Path $promptsDir 'test.prompt.md')
234
235 @'
236---
237description: "Test instruction"
238applyTo: "**/*.ps1"
239---
240'@ | Set-Content -Path (Join-Path $instrDir 'test.instructions.md')
241
242 @'
243---
244name: test-skill
245description: "Test skill"
246---
247'@ | Set-Content -Path (Join-Path $skillsDir 'SKILL.md')
248
249 # Create docs/templates and scripts directories for shared symlinking
250 New-Item -ItemType Directory -Path (Join-Path $script:tempDir 'docs/templates') -Force | Out-Null
251 New-Item -ItemType Directory -Path (Join-Path $script:tempDir 'scripts/lib') -Force | Out-Null
252
253 # Create plugins directory
254 New-Item -ItemType Directory -Path (Join-Path $script:tempDir 'plugins') -Force | Out-Null
255
256 # Create .github/plugin directory for marketplace manifest
257 New-Item -ItemType Directory -Path (Join-Path $script:tempDir '.github/plugin') -Force | Out-Null
258
259 # hve-core-all collection
260 @"
261id: hve-core-all
262name: hve-core
263description: All artifacts
264tags:
265 - copilot
266items:
267 - path: .github/agents/test.agent.md
268 kind: agent
269 - path: .github/prompts/test.prompt.md
270 kind: prompt
271 - path: .github/instructions/test.instructions.md
272 kind: instruction
273 - path: .github/skills/test-skill
274 kind: skill
275display:
276 color: blue
277"@ | Set-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml')
278 }
279
280 AfterAll {
281 Remove-Item -Path $script:tempDir -Recurse -Force -ErrorAction SilentlyContinue
282 }
283
284 It 'Generates plugins successfully' {
285 $result = Invoke-PluginGeneration -RepoRoot $script:tempDir -Refresh -Channel 'PreRelease'
286 $result.Success | Should -BeTrue
287 $result.PluginCount | Should -BeGreaterOrEqual 1
288 }
289
290 It 'Creates plugin directory' {
291 $pluginDir = Join-Path $script:tempDir 'plugins/hve-core-all'
292 Test-Path $pluginDir | Should -BeTrue
293 }
294
295 It 'Generates plugin.json manifest' {
296 $manifestPath = Join-Path $script:tempDir 'plugins/hve-core-all/.github/plugin/plugin.json'
297 Test-Path $manifestPath | Should -BeTrue
298 $manifest = Get-Content -Path $manifestPath -Raw | ConvertFrom-Json
299 $manifest.name | Should -Be 'hve-core-all'
300 }
301
302 It 'Generates README.md' {
303 $readmePath = Join-Path $script:tempDir 'plugins/hve-core-all/README.md'
304 Test-Path $readmePath | Should -BeTrue
305 }
306
307 It 'Filters to specific collection IDs when provided' {
308 $result = Invoke-PluginGeneration -RepoRoot $script:tempDir -CollectionIds @('hve-core-all') -Refresh -Channel 'PreRelease'
309 $result.PluginCount | Should -Be 1
310 }
311
312 It 'Warns for non-existent collection IDs' {
313 $result = Invoke-PluginGeneration -RepoRoot $script:tempDir -CollectionIds @('nonexistent') -Refresh -Channel 'PreRelease' 3>&1
314 $warnings = @($result | Where-Object { $_ -is [System.Management.Automation.WarningRecord] })
315 $warnings.Count | Should -BeGreaterOrEqual 1
316 }
317
318 It 'Supports DryRun mode' {
319 $result = Invoke-PluginGeneration -RepoRoot $script:tempDir -CollectionIds @('hve-core-all') -DryRun -Channel 'PreRelease'
320 $result.Success | Should -BeTrue
321 }
322
323 It 'Returns zero plugins when no collections found' {
324 $emptyRoot = Join-Path $TestDrive ([System.Guid]::NewGuid().ToString())
325 New-Item -ItemType Directory -Path (Join-Path $emptyRoot 'collections') -Force | Out-Null
326 New-Item -ItemType Directory -Path (Join-Path $emptyRoot 'plugins') -Force | Out-Null
327 @{ name = 'test'; version = '1.0.0'; description = 'test'; author = 'test' } |
328 ConvertTo-Json | Set-Content -Path (Join-Path $emptyRoot 'package.json')
329
330 # Create minimal .github structure for auto-update
331 New-Item -ItemType Directory -Path (Join-Path $emptyRoot '.github/agents') -Force | Out-Null
332 @"
333id: hve-core-all
334name: hve-core
335description: test
336tags: []
337items: []
338display: {}
339"@ | Set-Content -Path (Join-Path $emptyRoot 'collections/hve-core-all.collection.yml')
340
341 $result = Invoke-PluginGeneration -RepoRoot $emptyRoot -CollectionIds @('missing-id') -Channel 'PreRelease' 3>&1
342 $hashtableResult = $result | Where-Object { $_ -is [hashtable] }
343 if ($hashtableResult) {
344 $hashtableResult.PluginCount | Should -Be 0
345 }
346 }
347
348 It 'Applies channel filtering to items' {
349 # Add a collection with mixed maturities
350 $mixedPath = Join-Path (Join-Path $script:tempDir 'collections') 'mixed.collection.yml'
351 @"
352id: mixed
353name: Mixed Collection
354description: Mixed maturity test
355items:
356 - path: .github/agents/test.agent.md
357 kind: agent
358 maturity: stable
359 - path: .github/prompts/test.prompt.md
360 kind: prompt
361 maturity: experimental
362"@ | Set-Content -Path $mixedPath
363
364 $result = Invoke-PluginGeneration -RepoRoot $script:tempDir -CollectionIds @('mixed') -Refresh -Channel 'Stable'
365 $result.Success | Should -BeTrue
366 }
367
368 It 'Removes existing plugin directory on Refresh' {
369 # Create a stale file in plugin dir
370 $staleDir = Join-Path $script:tempDir 'plugins/hve-core-all/stale'
371 New-Item -ItemType Directory -Path $staleDir -Force | Out-Null
372 'stale' | Set-Content -Path (Join-Path $staleDir 'file.txt')
373
374 $result = Invoke-PluginGeneration -RepoRoot $script:tempDir -CollectionIds @('hve-core-all') -Refresh -Channel 'PreRelease'
375 $result.Success | Should -BeTrue
376 Test-Path $staleDir | Should -BeFalse
377 }
378
379 It 'Logs DryRun message when refreshing existing plugin' {
380 # Ensure plugin directory exists
381 $pluginDir = Join-Path $script:tempDir 'plugins/hve-core-all'
382 New-Item -ItemType Directory -Path $pluginDir -Force | Out-Null
383
384 $output = Invoke-PluginGeneration -RepoRoot $script:tempDir `
385 -CollectionIds @('hve-core-all') `
386 -Refresh -DryRun -Channel 'PreRelease' 6>&1
387
388 $dryRunMessages = @($output | Where-Object { "$_" -match 'DRY RUN.*Would remove' })
389 $dryRunMessages.Count | Should -BeGreaterOrEqual 1
390 }
391
392 It 'Warns when collections directory has no matching YAML files' {
393 $emptyRoot = Join-Path $TestDrive ([System.Guid]::NewGuid().ToString())
394 $emptyCollDir = Join-Path $emptyRoot 'collections'
395 New-Item -ItemType Directory -Path $emptyCollDir -Force | Out-Null
396 New-Item -ItemType Directory -Path (Join-Path $emptyRoot 'plugins') -Force | Out-Null
397 New-Item -ItemType Directory -Path (Join-Path $emptyRoot '.github/agents') -Force | Out-Null
398 @{ name = 'test'; version = '1.0.0'; description = 'test'; author = 'test' } |
399 ConvertTo-Json | Set-Content -Path (Join-Path $emptyRoot 'package.json')
400
401 # Mock Update-HveCoreAllCollection to avoid file-not-found errors
402 Mock Update-HveCoreAllCollection { return @{ ItemCount = 0; AddedCount = 0; RemovedCount = 0 } }
403
404 $result = Invoke-PluginGeneration -RepoRoot $emptyRoot -Channel 'PreRelease' 3>&1
405 $warnings = @($result | Where-Object { $_ -is [System.Management.Automation.WarningRecord] })
406 $warnings.Count | Should -BeGreaterOrEqual 1
407 $warnings[0].Message | Should -Match 'No collection manifests found'
408 }
409
410 It 'Outputs verbose symlink capability detection' {
411 $output = Invoke-PluginGeneration -RepoRoot $script:tempDir `
412 -CollectionIds @('hve-core-all') `
413 -Channel 'PreRelease' -Verbose 4>&1
414
415 $capMsg = @($output | Where-Object { "$_" -match 'Symlink capability' })
416 $capMsg.Count | Should -BeGreaterOrEqual 1
417 }
418}
419
420Describe 'Start-PluginGeneration' {
421 It 'Returns 0 on successful generation' {
422 Mock Invoke-PluginGeneration { return @{ Success = $true; PluginCount = 2 } }
423 Mock Get-Module { return @{ Name = 'PowerShell-Yaml' } } -ParameterFilter { $ListAvailable -and $Name -eq 'PowerShell-Yaml' }
424 Mock Import-Module {}
425
426 $scriptPath = "$PSScriptRoot/../../plugins/Generate-Plugins.ps1"
427 $exitCode = Start-PluginGeneration -ScriptPath $scriptPath -Channel 'PreRelease'
428 $exitCode | Should -Be 0
429 }
430
431 It 'Returns 1 when Invoke-PluginGeneration reports failure' {
432 Mock Invoke-PluginGeneration { return @{ Success = $false; PluginCount = 0; ErrorMessage = 'Generation failed' } }
433 Mock Get-Module { return @{ Name = 'PowerShell-Yaml' } } -ParameterFilter { $ListAvailable -and $Name -eq 'PowerShell-Yaml' }
434 Mock Import-Module {}
435
436 $scriptPath = "$PSScriptRoot/../../plugins/Generate-Plugins.ps1"
437 $output = Start-PluginGeneration -ScriptPath $scriptPath -Channel 'PreRelease' -ErrorAction SilentlyContinue
438 $exitCode = @($output) | Where-Object { $_ -is [int] } | Select-Object -Last 1
439 $exitCode | Should -Be 1
440 }
441
442 It 'Returns 1 when PowerShell-Yaml module is missing' {
443 Mock Get-Module { return $null } -ParameterFilter { $ListAvailable -and $Name -eq 'PowerShell-Yaml' }
444
445 $scriptPath = "$PSScriptRoot/../../plugins/Generate-Plugins.ps1"
446 $output = Start-PluginGeneration -ScriptPath $scriptPath -Channel 'PreRelease' -ErrorAction SilentlyContinue
447 $exitCode = @($output) | Where-Object { $_ -is [int] } | Select-Object -Last 1
448 $exitCode | Should -Be 1
449 }
450
451 It 'Defaults to refresh when no CollectionIds, Refresh, or DryRun provided' {
452 Mock Get-Module { return @{ Name = 'PowerShell-Yaml' } } -ParameterFilter { $ListAvailable -and $Name -eq 'PowerShell-Yaml' }
453 Mock Import-Module {}
454 Mock Invoke-PluginGeneration { return @{ Success = $true; PluginCount = 1 } }
455
456 $scriptPath = "$PSScriptRoot/../../plugins/Generate-Plugins.ps1"
457 Start-PluginGeneration -ScriptPath $scriptPath -Channel 'PreRelease' | Out-Null
458
459 Should -Invoke Invoke-PluginGeneration -Times 1 -ParameterFilter { $Refresh -eq $true }
460 }
461
462 It 'Does not force refresh when CollectionIds are provided' {
463 Mock Get-Module { return @{ Name = 'PowerShell-Yaml' } } -ParameterFilter { $ListAvailable -and $Name -eq 'PowerShell-Yaml' }
464 Mock Import-Module {}
465 Mock Invoke-PluginGeneration { return @{ Success = $true; PluginCount = 1 } }
466
467 $scriptPath = "$PSScriptRoot/../../plugins/Generate-Plugins.ps1"
468 Start-PluginGeneration -ScriptPath $scriptPath -CollectionIds @('test') -Channel 'PreRelease' | Out-Null
469
470 Should -Invoke Invoke-PluginGeneration -Times 1 -ParameterFilter { $Refresh -eq $false }
471 }
472}
473