microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
docs/659-role-based-docs-lifecycle-guides

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/tests/plugins/Validate-Collections.Tests.ps1

617lines · modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4
5BeforeAll {
6 . $PSScriptRoot/../../plugins/Validate-Collections.ps1
7}
8
9Describe 'Test-KindSuffix' {
10 It 'Returns empty for valid agent path' {
11 $result = Test-KindSuffix -Kind 'agent' -ItemPath '.github/agents/rpi-agent.agent.md' -RepoRoot $TestDrive
12 $result | Should -BeNullOrEmpty
13 }
14
15 It 'Returns empty for valid prompt path' {
16 $result = Test-KindSuffix -Kind 'prompt' -ItemPath '.github/prompts/gen-plan.prompt.md' -RepoRoot $TestDrive
17 $result | Should -BeNullOrEmpty
18 }
19
20 It 'Returns empty for valid instruction path' {
21 $result = Test-KindSuffix -Kind 'instruction' -ItemPath '.github/instructions/csharp.instructions.md' -RepoRoot $TestDrive
22 $result | Should -BeNullOrEmpty
23 }
24
25 It 'Returns empty for valid skill path with SKILL.md' {
26 $skillDir = Join-Path $TestDrive '.github/skills/video-to-gif'
27 New-Item -ItemType Directory -Path $skillDir -Force | Out-Null
28 Set-Content -Path (Join-Path $skillDir 'SKILL.md') -Value '# Skill'
29
30 $result = Test-KindSuffix -Kind 'skill' -ItemPath '.github/skills/video-to-gif' -RepoRoot $TestDrive
31 $result | Should -BeNullOrEmpty
32 }
33
34 It 'Returns error for invalid agent suffix' {
35 $result = Test-KindSuffix -Kind 'agent' -ItemPath '.github/agents/bad.prompt.md' -RepoRoot $TestDrive
36 $result | Should -Match "kind 'agent' expects"
37 }
38
39 It 'Returns error for invalid prompt suffix' {
40 $result = Test-KindSuffix -Kind 'prompt' -ItemPath '.github/prompts/bad.agent.md' -RepoRoot $TestDrive
41 $result | Should -Match "kind 'prompt' expects"
42 }
43
44 It 'Returns error when SKILL.md missing for skill kind' {
45 $emptySkillDir = Join-Path $TestDrive '.github/skills/no-skill'
46 New-Item -ItemType Directory -Path $emptySkillDir -Force | Out-Null
47
48 $result = Test-KindSuffix -Kind 'skill' -ItemPath '.github/skills/no-skill' -RepoRoot $TestDrive
49 $result | Should -Match "kind 'skill' expects SKILL.md"
50 }
51}
52
53Describe 'Resolve-ItemMaturity' {
54 It 'Returns stable for null maturity' {
55 $result = Resolve-ItemMaturity -Maturity $null
56 $result | Should -Be 'stable'
57 }
58
59 It 'Returns stable for empty string' {
60 $result = Resolve-ItemMaturity -Maturity ''
61 $result | Should -Be 'stable'
62 }
63
64 It 'Returns stable for whitespace' {
65 $result = Resolve-ItemMaturity -Maturity ' '
66 $result | Should -Be 'stable'
67 }
68
69 It 'Passes through explicit value' {
70 $result = Resolve-ItemMaturity -Maturity 'preview'
71 $result | Should -Be 'preview'
72 }
73
74 It 'Passes through experimental value' {
75 $result = Resolve-ItemMaturity -Maturity 'experimental'
76 $result | Should -Be 'experimental'
77 }
78}
79
80Describe 'Get-CollectionItemKey' {
81 It 'Builds correct composite key' {
82 $result = Get-CollectionItemKey -Kind 'agent' -ItemPath '.github/agents/rpi-agent.agent.md'
83 $result | Should -Be 'agent|.github/agents/rpi-agent.agent.md'
84 }
85
86 It 'Builds key for instruction kind' {
87 $result = Get-CollectionItemKey -Kind 'instruction' -ItemPath '.github/instructions/csharp.instructions.md'
88 $result | Should -Be 'instruction|.github/instructions/csharp.instructions.md'
89 }
90}
91
92Describe 'Invoke-CollectionValidation - repo-specific path rejection' {
93 BeforeAll {
94 Import-Module PowerShell-Yaml -ErrorAction Stop
95
96 $script:repoRoot = Join-Path $TestDrive 'repo'
97 $script:collectionsDir = Join-Path $script:repoRoot 'collections'
98
99 # Create artifact directories and files
100 $instrDir = Join-Path $script:repoRoot '.github/instructions'
101 $agentsDir = Join-Path $script:repoRoot '.github/agents'
102 $sharedDir = Join-Path $instrDir 'shared'
103 $hveCoreAgentsDir = Join-Path $agentsDir 'hve-core'
104
105 New-Item -ItemType Directory -Path $instrDir -Force | Out-Null
106 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
107 New-Item -ItemType Directory -Path $sharedDir -Force | Out-Null
108 New-Item -ItemType Directory -Path $hveCoreAgentsDir -Force | Out-Null
109
110 # Root-level (repo-specific) files
111 Set-Content -Path (Join-Path $instrDir 'workflows.instructions.md') -Value '---\ndescription: repo-specific\n---'
112 Set-Content -Path (Join-Path $agentsDir 'internal.agent.md') -Value '---\ndescription: repo-specific agent\n---'
113
114 # Subdirectory (collection-scoped) files
115 Set-Content -Path (Join-Path $sharedDir 'hve-core-location.instructions.md') -Value '---\ndescription: shared\n---'
116 Set-Content -Path (Join-Path $hveCoreAgentsDir 'rpi-agent.agent.md') -Value '---\ndescription: distributable agent\n---'
117 }
118
119 BeforeEach {
120 # Clear collection files between tests to prevent cross-contamination
121 if (Test-Path $script:collectionsDir) {
122 Remove-Item -Path $script:collectionsDir -Recurse -Force
123 }
124 New-Item -ItemType Directory -Path $script:collectionsDir -Force | Out-Null
125 }
126
127 It 'Fails validation for root-level instruction' {
128 $manifest = [ordered]@{
129 id = 'test-reject-instr'
130 name = 'Test Reject Instruction'
131 description = 'Tests repo-specific instruction rejection'
132 items = @(
133 [ordered]@{
134 path = '.github/instructions/workflows.instructions.md'
135 kind = 'instruction'
136 }
137 )
138 }
139 $yaml = ConvertTo-Yaml -Data $manifest
140 Set-Content -Path (Join-Path $script:collectionsDir 'test-reject-instr.collection.yml') -Value $yaml
141
142 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
143 $result.Success | Should -BeFalse
144 $result.ErrorCount | Should -BeGreaterOrEqual 1
145 }
146
147 It 'Passes validation for instruction in subdirectory' {
148 $manifest = [ordered]@{
149 id = 'test-allow-location'
150 name = 'Test Allow Location'
151 description = 'Tests that subdirectory instructions are allowed'
152 items = @(
153 [ordered]@{
154 path = '.github/instructions/shared/hve-core-location.instructions.md'
155 kind = 'instruction'
156 }
157 )
158 }
159 $yaml = ConvertTo-Yaml -Data $manifest
160 Set-Content -Path (Join-Path $script:collectionsDir 'test-allow-location.collection.yml') -Value $yaml
161
162 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
163 $result.Success | Should -BeTrue
164 }
165
166 It 'Fails validation for root-level agent' {
167 $manifest = [ordered]@{
168 id = 'test-reject-agent'
169 name = 'Test Reject Agent'
170 description = 'Tests repo-specific agent rejection'
171 items = @(
172 [ordered]@{
173 path = '.github/agents/internal.agent.md'
174 kind = 'agent'
175 }
176 )
177 }
178 $yaml = ConvertTo-Yaml -Data $manifest
179 Set-Content -Path (Join-Path $script:collectionsDir 'test-reject-agent.collection.yml') -Value $yaml
180
181 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
182 $result.Success | Should -BeFalse
183 $result.ErrorCount | Should -BeGreaterOrEqual 1
184 }
185
186 It 'Passes validation for agent in subdirectory' {
187 $manifest = [ordered]@{
188 id = 'test-allow-agent'
189 name = 'Test Allow Agent'
190 description = 'Tests that subdirectory agents pass'
191 items = @(
192 [ordered]@{
193 path = '.github/agents/hve-core/rpi-agent.agent.md'
194 kind = 'agent'
195 }
196 )
197 }
198 $yaml = ConvertTo-Yaml -Data $manifest
199 Set-Content -Path (Join-Path $script:collectionsDir 'test-allow-agent.collection.yml') -Value $yaml
200
201 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
202 $result.Success | Should -BeTrue
203 }
204}
205
206Describe 'Invoke-CollectionValidation - collection-level maturity' {
207 BeforeAll {
208 Import-Module PowerShell-Yaml -ErrorAction Stop
209
210 $script:repoRoot = Join-Path $TestDrive 'maturity-repo'
211 $script:collectionsDir = Join-Path $script:repoRoot 'collections'
212
213 # Create a valid artifact for items to reference
214 $agentsDir = Join-Path $script:repoRoot '.github/agents/test'
215 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
216 Set-Content -Path (Join-Path $agentsDir 'test.agent.md') -Value '---\ndescription: test agent\n---'
217 }
218
219 BeforeEach {
220 if (Test-Path $script:collectionsDir) {
221 Remove-Item -Path $script:collectionsDir -Recurse -Force
222 }
223 New-Item -ItemType Directory -Path $script:collectionsDir -Force | Out-Null
224 }
225
226 It 'Passes validation for collection with maturity: experimental' {
227 $manifest = [ordered]@{
228 id = 'test-maturity-experimental'
229 name = 'Test'
230 description = 'Tests experimental maturity'
231 maturity = 'experimental'
232 items = @(
233 [ordered]@{
234 path = '.github/agents/test/test.agent.md'
235 kind = 'agent'
236 }
237 )
238 }
239 $yaml = ConvertTo-Yaml -Data $manifest
240 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-experimental.collection.yml') -Value $yaml
241
242 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
243 $result.Success | Should -BeTrue
244 }
245
246 It 'Passes validation for collection with maturity: stable' {
247 $manifest = [ordered]@{
248 id = 'test-maturity-stable'
249 name = 'Test'
250 description = 'Tests stable maturity'
251 maturity = 'stable'
252 items = @(
253 [ordered]@{
254 path = '.github/agents/test/test.agent.md'
255 kind = 'agent'
256 }
257 )
258 }
259 $yaml = ConvertTo-Yaml -Data $manifest
260 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-stable.collection.yml') -Value $yaml
261
262 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
263 $result.Success | Should -BeTrue
264 }
265
266 It 'Passes validation for collection with maturity: preview' {
267 $manifest = [ordered]@{
268 id = 'test-maturity-preview'
269 name = 'Test'
270 description = 'Tests preview maturity'
271 maturity = 'preview'
272 items = @(
273 [ordered]@{
274 path = '.github/agents/test/test.agent.md'
275 kind = 'agent'
276 }
277 )
278 }
279 $yaml = ConvertTo-Yaml -Data $manifest
280 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-preview.collection.yml') -Value $yaml
281
282 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
283 $result.Success | Should -BeTrue
284 }
285
286 It 'Passes validation for collection with maturity: deprecated' {
287 $manifest = [ordered]@{
288 id = 'test-maturity-deprecated'
289 name = 'Test'
290 description = 'Tests deprecated maturity'
291 maturity = 'deprecated'
292 items = @(
293 [ordered]@{
294 path = '.github/agents/test/test.agent.md'
295 kind = 'agent'
296 }
297 )
298 }
299 $yaml = ConvertTo-Yaml -Data $manifest
300 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-deprecated.collection.yml') -Value $yaml
301
302 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
303 $result.Success | Should -BeTrue
304 }
305
306 It 'Fails validation for collection with invalid maturity: beta' {
307 $manifest = [ordered]@{
308 id = 'test-maturity-beta'
309 name = 'Test'
310 description = 'Tests invalid maturity'
311 maturity = 'beta'
312 items = @(
313 [ordered]@{
314 path = '.github/agents/test/test.agent.md'
315 kind = 'agent'
316 }
317 )
318 }
319 $yaml = ConvertTo-Yaml -Data $manifest
320 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-beta.collection.yml') -Value $yaml
321
322 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
323 $result.Success | Should -BeFalse
324 $result.ErrorCount | Should -BeGreaterOrEqual 1
325 }
326
327 It 'Passes validation for collection with omitted maturity' {
328 $manifest = [ordered]@{
329 id = 'test-maturity-omitted'
330 name = 'Test'
331 description = 'Tests omitted maturity'
332 items = @(
333 [ordered]@{
334 path = '.github/agents/test/test.agent.md'
335 kind = 'agent'
336 }
337 )
338 }
339 $yaml = ConvertTo-Yaml -Data $manifest
340 Set-Content -Path (Join-Path $script:collectionsDir 'test-maturity-omitted.collection.yml') -Value $yaml
341
342 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
343 $result.Success | Should -BeTrue
344 }
345}
346
347Describe 'Invoke-CollectionValidation - error paths' {
348 BeforeAll {
349 Import-Module PowerShell-Yaml -ErrorAction Stop
350
351 $script:repoRoot = Join-Path $TestDrive 'error-repo'
352 $script:collectionsDir = Join-Path $script:repoRoot 'collections'
353
354 # Create valid artifacts for reference
355 $agentsDir = Join-Path $script:repoRoot '.github/agents/test'
356 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
357 Set-Content -Path (Join-Path $agentsDir 'a.agent.md') -Value '---\ndescription: agent a\n---'
358 Set-Content -Path (Join-Path $agentsDir 'b.agent.md') -Value '---\ndescription: agent b\n---'
359
360 $instrDir = Join-Path $script:repoRoot '.github/instructions/test'
361 New-Item -ItemType Directory -Path $instrDir -Force | Out-Null
362 Set-Content -Path (Join-Path $instrDir 'test.instructions.md') -Value '---\ndescription: test\n---'
363 }
364
365 BeforeEach {
366 if (Test-Path $script:collectionsDir) {
367 Remove-Item -Path $script:collectionsDir -Recurse -Force
368 }
369 New-Item -ItemType Directory -Path $script:collectionsDir -Force | Out-Null
370 }
371
372 It 'Returns success with zero collections when directory is empty' {
373 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
374 $result.Success | Should -BeTrue
375 $result.CollectionCount | Should -Be 0
376 }
377
378 It 'Fails when required field is missing' {
379 $yaml = @"
380name: No ID Collection
381description: Missing id field
382items:
383 - path: .github/agents/test/a.agent.md
384 kind: agent
385"@
386 Set-Content -Path (Join-Path $script:collectionsDir 'no-id.collection.yml') -Value $yaml
387
388 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
389 $result.Success | Should -BeFalse
390 }
391
392 It 'Fails for invalid id format' {
393 $manifest = [ordered]@{
394 id = 'INVALID_ID!'
395 name = 'Bad ID'
396 description = 'Invalid id format'
397 items = @(
398 [ordered]@{
399 path = '.github/agents/test/a.agent.md'
400 kind = 'agent'
401 }
402 )
403 }
404 $yaml = ConvertTo-Yaml -Data $manifest
405 Set-Content -Path (Join-Path $script:collectionsDir 'bad-id.collection.yml') -Value $yaml
406
407 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
408 $result.Success | Should -BeFalse
409 }
410
411 It 'Fails for duplicate ids across collections' {
412 $manifest = [ordered]@{
413 id = 'dup-id'
414 name = 'First'
415 description = 'First collection'
416 items = @(
417 [ordered]@{
418 path = '.github/agents/test/a.agent.md'
419 kind = 'agent'
420 }
421 )
422 }
423 $yaml = ConvertTo-Yaml -Data $manifest
424 Set-Content -Path (Join-Path $script:collectionsDir 'dup1.collection.yml') -Value $yaml
425 Set-Content -Path (Join-Path $script:collectionsDir 'dup2.collection.yml') -Value $yaml
426
427 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
428 $result.Success | Should -BeFalse
429 }
430
431 It 'Fails when item path does not exist' {
432 $manifest = [ordered]@{
433 id = 'missing-path'
434 name = 'Missing'
435 description = 'Item path missing'
436 items = @(
437 [ordered]@{
438 path = '.github/agents/test/nonexistent.agent.md'
439 kind = 'agent'
440 }
441 )
442 }
443 $yaml = ConvertTo-Yaml -Data $manifest
444 Set-Content -Path (Join-Path $script:collectionsDir 'missing-path.collection.yml') -Value $yaml
445
446 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
447 $result.Success | Should -BeFalse
448 }
449
450 It 'Fails when item has no kind' {
451 $yaml = @"
452id: no-kind
453name: No Kind
454description: Item missing kind
455items:
456 - path: .github/agents/test/a.agent.md
457"@
458 Set-Content -Path (Join-Path $script:collectionsDir 'no-kind.collection.yml') -Value $yaml
459
460 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
461 $result.Success | Should -BeFalse
462 }
463
464 It 'Fails for invalid item maturity' {
465 $manifest = [ordered]@{
466 id = 'bad-item-mat'
467 name = 'Bad Item Maturity'
468 description = 'Item with invalid maturity'
469 items = @(
470 [ordered]@{
471 path = '.github/agents/test/a.agent.md'
472 kind = 'agent'
473 maturity = 'alpha'
474 }
475 )
476 }
477 $yaml = ConvertTo-Yaml -Data $manifest
478 Set-Content -Path (Join-Path $script:collectionsDir 'bad-item-mat.collection.yml') -Value $yaml
479
480 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
481 $result.Success | Should -BeFalse
482 }
483
484 It 'Fails for kind-suffix mismatch' {
485 $manifest = [ordered]@{
486 id = 'suffix-mismatch'
487 name = 'Suffix Mismatch'
488 description = 'Agent path with wrong suffix'
489 items = @(
490 [ordered]@{
491 path = '.github/instructions/test/test.instructions.md'
492 kind = 'agent'
493 }
494 )
495 }
496 $yaml = ConvertTo-Yaml -Data $manifest
497 Set-Content -Path (Join-Path $script:collectionsDir 'suffix-mismatch.collection.yml') -Value $yaml
498
499 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
500 $result.Success | Should -BeFalse
501 }
502
503 It 'Fails for instruction kind with wrong suffix' {
504 $manifest = [ordered]@{
505 id = 'instr-suffix'
506 name = 'Instruction Suffix'
507 description = 'Instruction item with agent suffix'
508 items = @(
509 [ordered]@{
510 path = '.github/agents/test/a.agent.md'
511 kind = 'instruction'
512 }
513 )
514 }
515 $yaml = ConvertTo-Yaml -Data $manifest
516 Set-Content -Path (Join-Path $script:collectionsDir 'instr-suffix.collection.yml') -Value $yaml
517
518 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
519 $result.Success | Should -BeFalse
520 }
521
522 It 'Detects duplicate artifact keys at distinct paths' {
523 # Two agents at different paths that resolve to the same artifact key
524 $agentsDir2 = Join-Path $script:repoRoot '.github/agents/other'
525 New-Item -ItemType Directory -Path $agentsDir2 -Force | Out-Null
526 Set-Content -Path (Join-Path $agentsDir2 'a.agent.md') -Value '---\ndescription: same name\n---'
527
528 $manifest = [ordered]@{
529 id = 'dup-artifact'
530 name = 'Dup Artifact'
531 description = 'Same artifact key from different paths'
532 items = @(
533 [ordered]@{
534 path = '.github/agents/test/a.agent.md'
535 kind = 'agent'
536 },
537 [ordered]@{
538 path = '.github/agents/other/a.agent.md'
539 kind = 'agent'
540 }
541 )
542 }
543 $yaml = ConvertTo-Yaml -Data $manifest
544 Set-Content -Path (Join-Path $script:collectionsDir 'dup-artifact.collection.yml') -Value $yaml
545
546 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
547 $result.Success | Should -BeFalse
548 }
549
550 It 'Detects shared item missing canonical entry' {
551 # Two collections share the same item but neither is hve-core-all
552 $manifest1 = [ordered]@{
553 id = 'share-one'
554 name = 'Share One'
555 description = 'First sharer'
556 items = @(
557 [ordered]@{
558 path = '.github/agents/test/a.agent.md'
559 kind = 'agent'
560 }
561 )
562 }
563 $manifest2 = [ordered]@{
564 id = 'share-two'
565 name = 'Share Two'
566 description = 'Second sharer'
567 items = @(
568 [ordered]@{
569 path = '.github/agents/test/a.agent.md'
570 kind = 'agent'
571 }
572 )
573 }
574 $yaml1 = ConvertTo-Yaml -Data $manifest1
575 $yaml2 = ConvertTo-Yaml -Data $manifest2
576 Set-Content -Path (Join-Path $script:collectionsDir 'share-one.collection.yml') -Value $yaml1
577 Set-Content -Path (Join-Path $script:collectionsDir 'share-two.collection.yml') -Value $yaml2
578
579 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
580 $result.Success | Should -BeFalse
581 }
582
583 It 'Detects maturity conflict with canonical collection' {
584 # hve-core-all has the item as stable, another collection has it as experimental
585 $canonical = [ordered]@{
586 id = 'hve-core-all'
587 name = 'All'
588 description = 'Canonical collection'
589 items = @(
590 [ordered]@{
591 path = '.github/agents/test/a.agent.md'
592 kind = 'agent'
593 maturity = 'stable'
594 }
595 )
596 }
597 $other = [ordered]@{
598 id = 'conflict-col'
599 name = 'Conflict'
600 description = 'Conflicting maturity'
601 items = @(
602 [ordered]@{
603 path = '.github/agents/test/a.agent.md'
604 kind = 'agent'
605 maturity = 'experimental'
606 }
607 )
608 }
609 $yaml1 = ConvertTo-Yaml -Data $canonical
610 $yaml2 = ConvertTo-Yaml -Data $other
611 Set-Content -Path (Join-Path $script:collectionsDir 'hve-core-all.collection.yml') -Value $yaml1
612 Set-Content -Path (Join-Path $script:collectionsDir 'conflict-col.collection.yml') -Value $yaml2
613
614 $result = Invoke-CollectionValidation -RepoRoot $script:repoRoot
615 $result.Success | Should -BeFalse
616 }
617}