microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
feat/987-enable-json-log-lint-version-consistency

Branches

Tags

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

Clone

HTTPS

Download ZIP

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

475lines · modecode

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