microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
hve-core-v3.0.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/tests/plugins/PluginHelpers.Tests.ps1

673lines · modecode

1#Requires -Modules Pester
2# Copyright (c) Microsoft Corporation.
3# SPDX-License-Identifier: MIT
4
5BeforeAll {
6 Import-Module $PSScriptRoot/../../plugins/Modules/PluginHelpers.psm1 -Force
7}
8
9Describe 'Get-ArtifactFiles - repo-specific path exclusion' {
10 BeforeAll {
11 $script:repoRoot = Join-Path $TestDrive 'repo'
12 $ghDir = Join-Path $script:repoRoot '.github'
13
14 # Create root-level repo-specific agent (should be excluded)
15 $agentsDir = Join-Path $ghDir 'agents'
16 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
17 Set-Content -Path (Join-Path $agentsDir 'internal.agent.md') -Value '---\ndescription: repo-specific\n---'
18
19 # Create collection-scoped agent in subdirectory (should be included)
20 $hveCoreAgentsDir = Join-Path $agentsDir 'hve-core'
21 New-Item -ItemType Directory -Path $hveCoreAgentsDir -Force | Out-Null
22 Set-Content -Path (Join-Path $hveCoreAgentsDir 'rpi-agent.agent.md') -Value '---\ndescription: distributable\n---'
23
24 # Create root-level repo-specific instruction (should be excluded)
25 $instrDir = Join-Path $ghDir 'instructions'
26 New-Item -ItemType Directory -Path $instrDir -Force | Out-Null
27 Set-Content -Path (Join-Path $instrDir 'workflows.instructions.md') -Value '---\ndescription: repo-specific\n---'
28
29 # Create collection-scoped instruction in subdirectory (should be included)
30 $sharedInstrDir = Join-Path $instrDir 'shared'
31 New-Item -ItemType Directory -Path $sharedInstrDir -Force | Out-Null
32 Set-Content -Path (Join-Path $sharedInstrDir 'hve-core-location.instructions.md') -Value '---\ndescription: shared\n---'
33
34 # Create root-level repo-specific prompt (should be excluded)
35 $promptsDir = Join-Path $ghDir 'prompts'
36 New-Item -ItemType Directory -Path $promptsDir -Force | Out-Null
37 Set-Content -Path (Join-Path $promptsDir 'internal.prompt.md') -Value '---\ndescription: repo-specific prompt\n---'
38
39 # Create collection-scoped prompt in subdirectory (should be included)
40 $hveCorePromptsDir = Join-Path $promptsDir 'hve-core'
41 New-Item -ItemType Directory -Path $hveCorePromptsDir -Force | Out-Null
42 Set-Content -Path (Join-Path $hveCorePromptsDir 'task-plan.prompt.md') -Value '---\ndescription: distributable prompt\n---'
43 }
44
45 It 'Excludes root-level repo-specific instructions' {
46 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
47 $paths = $items | ForEach-Object { $_.path }
48 $paths | Should -Not -Contain '.github/instructions/workflows.instructions.md'
49 }
50
51 It 'Excludes root-level repo-specific agents' {
52 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
53 $paths = $items | ForEach-Object { $_.path }
54 $paths | Should -Not -Contain '.github/agents/internal.agent.md'
55 }
56
57 It 'Excludes root-level repo-specific prompts' {
58 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
59 $paths = $items | ForEach-Object { $_.path }
60 $paths | Should -Not -Contain '.github/prompts/internal.prompt.md'
61 }
62
63 It 'Includes collection-scoped agents in subdirectories' {
64 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
65 $paths = $items | ForEach-Object { $_.path }
66 $paths | Should -Contain '.github/agents/hve-core/rpi-agent.agent.md'
67 }
68
69 It 'Includes collection-scoped instructions in subdirectories' {
70 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
71 $paths = $items | ForEach-Object { $_.path }
72 $paths | Should -Contain '.github/instructions/shared/hve-core-location.instructions.md'
73 }
74
75 It 'Includes collection-scoped prompts in subdirectories' {
76 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
77 $paths = $items | ForEach-Object { $_.path }
78 $paths | Should -Contain '.github/prompts/hve-core/task-plan.prompt.md'
79 }
80}
81
82Describe 'Get-ArtifactFiles - deprecated path exclusion' {
83 BeforeAll {
84 $script:repoRoot = Join-Path $TestDrive 'repo-deprecated'
85 $ghDir = Join-Path $script:repoRoot '.github'
86
87 # Create non-deprecated artifacts
88 $agentsDir = Join-Path $ghDir 'agents/rpi'
89 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
90 Set-Content -Path (Join-Path $agentsDir 'active.agent.md') -Value '---\ndescription: active\n---'
91
92 $promptsDir = Join-Path $ghDir 'prompts/rpi'
93 New-Item -ItemType Directory -Path $promptsDir -Force | Out-Null
94 Set-Content -Path (Join-Path $promptsDir 'active.prompt.md') -Value '---\ndescription: active\n---'
95
96 # Create deprecated artifacts
97 $deprecatedAgentsDir = Join-Path $ghDir 'deprecated/agents'
98 New-Item -ItemType Directory -Path $deprecatedAgentsDir -Force | Out-Null
99 Set-Content -Path (Join-Path $deprecatedAgentsDir 'old.agent.md') -Value '---\ndescription: deprecated\n---'
100
101 $deprecatedPromptsDir = Join-Path $ghDir 'deprecated/prompts'
102 New-Item -ItemType Directory -Path $deprecatedPromptsDir -Force | Out-Null
103 Set-Content -Path (Join-Path $deprecatedPromptsDir 'old.prompt.md') -Value '---\ndescription: deprecated\n---'
104
105 $deprecatedInstrDir = Join-Path $ghDir 'deprecated/instructions'
106 New-Item -ItemType Directory -Path $deprecatedInstrDir -Force | Out-Null
107 Set-Content -Path (Join-Path $deprecatedInstrDir 'old.instructions.md') -Value '---\ndescription: deprecated\n---'
108
109 # Create deprecated skill
110 $deprecatedSkillDir = Join-Path $ghDir 'deprecated/skills/old-skill'
111 New-Item -ItemType Directory -Path $deprecatedSkillDir -Force | Out-Null
112 Set-Content -Path (Join-Path $deprecatedSkillDir 'SKILL.md') -Value '---\nname: old-skill\ndescription: deprecated\n---'
113
114 # Create non-deprecated skill (under .github/skills/)
115 $skillDir = Join-Path $ghDir 'skills/experimental/good-skill'
116 New-Item -ItemType Directory -Path $skillDir -Force | Out-Null
117 Set-Content -Path (Join-Path $skillDir 'SKILL.md') -Value '---\nname: good-skill\ndescription: active\n---'
118 }
119
120 It 'Excludes deprecated agent files' {
121 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
122 $paths = $items | ForEach-Object { $_.path }
123 $paths | Should -Not -Contain '.github/deprecated/agents/old.agent.md'
124 }
125
126 It 'Excludes deprecated prompt files' {
127 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
128 $paths = $items | ForEach-Object { $_.path }
129 $paths | Should -Not -Contain '.github/deprecated/prompts/old.prompt.md'
130 }
131
132 It 'Excludes deprecated instruction files' {
133 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
134 $paths = $items | ForEach-Object { $_.path }
135 $paths | Should -Not -Contain '.github/deprecated/instructions/old.instructions.md'
136 }
137
138 It 'Excludes deprecated skill directories' {
139 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
140 $paths = $items | ForEach-Object { $_.path }
141 $paths | Should -Not -Contain '.github/deprecated/skills/old-skill'
142 }
143
144 It 'Includes non-deprecated artifacts' {
145 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
146 $paths = $items | ForEach-Object { $_.path }
147 $paths | Should -Contain '.github/agents/rpi/active.agent.md'
148 $paths | Should -Contain '.github/prompts/rpi/active.prompt.md'
149 }
150
151 It 'Includes non-deprecated skills' {
152 $items = Get-ArtifactFiles -RepoRoot $script:repoRoot
153 $paths = $items | ForEach-Object { $_.path }
154 $paths | Should -Contain '.github/skills/experimental/good-skill'
155 }
156}
157
158Describe 'Test-DeprecatedPath' {
159 It 'Returns true for path containing /deprecated/ segment' {
160 Test-DeprecatedPath -Path '.github/deprecated/agents/old.agent.md' | Should -BeTrue
161 }
162
163 It 'Returns true for path with backslash deprecated segment' {
164 Test-DeprecatedPath -Path '.github\deprecated\agents\old.agent.md' | Should -BeTrue
165 }
166
167 It 'Returns false for path without deprecated segment' {
168 Test-DeprecatedPath -Path '.github/agents/rpi/active.agent.md' | Should -BeFalse
169 }
170
171 It 'Returns false when deprecated appears in filename only' {
172 Test-DeprecatedPath -Path '.github/agents/deprecated-notes.agent.md' | Should -BeFalse
173 }
174
175 It 'Returns true for mid-path deprecated directory' {
176 Test-DeprecatedPath -Path 'skills/deprecated/old-skill/SKILL.md' | Should -BeTrue
177 }
178}
179
180Describe 'Test-HveCoreRepoSpecificPath' {
181 It 'Returns true for root-level file (no subdirectory)' {
182 Test-HveCoreRepoSpecificPath -RelativePath 'workflows.instructions.md' | Should -BeTrue
183 }
184
185 It 'Returns false for file in a subdirectory' {
186 Test-HveCoreRepoSpecificPath -RelativePath 'hve-core/markdown.instructions.md' | Should -BeFalse
187 }
188
189 It 'Returns false for file in nested subdirectory' {
190 Test-HveCoreRepoSpecificPath -RelativePath 'coding-standards/csharp/style.instructions.md' | Should -BeFalse
191 }
192
193 It 'Returns false for shared subdirectory path' {
194 Test-HveCoreRepoSpecificPath -RelativePath 'shared/hve-core-location.instructions.md' | Should -BeFalse
195 }
196}
197
198Describe 'Test-HveCoreRepoRelativePath' {
199 It 'Returns true for root-level agent' {
200 Test-HveCoreRepoRelativePath -Path '.github/agents/internal.agent.md' | Should -BeTrue
201 }
202
203 It 'Returns true for root-level instruction' {
204 Test-HveCoreRepoRelativePath -Path '.github/instructions/workflows.instructions.md' | Should -BeTrue
205 }
206
207 It 'Returns true for root-level prompt' {
208 Test-HveCoreRepoRelativePath -Path '.github/prompts/internal.prompt.md' | Should -BeTrue
209 }
210
211 It 'Returns false for non-.github path' {
212 Test-HveCoreRepoRelativePath -Path 'scripts/plugins/foo.ps1' | Should -BeFalse
213 }
214
215 It 'Returns false for collection-scoped path in subdirectory' {
216 Test-HveCoreRepoRelativePath -Path '.github/agents/hve-core/rpi-agent.agent.md' | Should -BeFalse
217 }
218
219 It 'Returns false for shared instruction in subdirectory' {
220 Test-HveCoreRepoRelativePath -Path '.github/instructions/shared/hve-core-location.instructions.md' | Should -BeFalse
221 }
222
223 It 'Returns false for path directly under .github (wrong nesting level)' {
224 Test-HveCoreRepoRelativePath -Path '.github/foo.md' | Should -BeFalse
225 }
226}
227
228Describe 'Resolve-CollectionItemMaturity' {
229 It 'Returns stable for null' {
230 $result = Resolve-CollectionItemMaturity -Maturity $null
231 $result | Should -Be 'stable'
232 }
233
234 It 'Returns stable for empty string' {
235 $result = Resolve-CollectionItemMaturity -Maturity ''
236 $result | Should -Be 'stable'
237 }
238
239 It 'Returns stable for whitespace' {
240 $result = Resolve-CollectionItemMaturity -Maturity ' '
241 $result | Should -Be 'stable'
242 }
243
244 It 'Passes through preview' {
245 $result = Resolve-CollectionItemMaturity -Maturity 'preview'
246 $result | Should -Be 'preview'
247 }
248
249 It 'Passes through experimental' {
250 $result = Resolve-CollectionItemMaturity -Maturity 'experimental'
251 $result | Should -Be 'experimental'
252 }
253}
254
255Describe 'Test-ArtifactDeprecated' {
256 It 'Returns true for deprecated' {
257 $result = Test-ArtifactDeprecated -Maturity 'deprecated'
258 $result | Should -BeTrue
259 }
260
261 It 'Returns false for stable' {
262 $result = Test-ArtifactDeprecated -Maturity 'stable'
263 $result | Should -BeFalse
264 }
265
266 It 'Returns false for preview' {
267 $result = Test-ArtifactDeprecated -Maturity 'preview'
268 $result | Should -BeFalse
269 }
270
271 It 'Returns false for experimental' {
272 $result = Test-ArtifactDeprecated -Maturity 'experimental'
273 $result | Should -BeFalse
274 }
275
276 It 'Returns false for null (defaults to stable)' {
277 $result = Test-ArtifactDeprecated -Maturity $null
278 $result | Should -BeFalse
279 }
280}
281
282Describe 'New-PluginReadmeContent - maturity notice' {
283 It 'Includes experimental notice when maturity is experimental' {
284 $collection = @{
285 id = 'test-exp'
286 name = 'Test Experimental'
287 description = 'An experimental collection'
288 }
289 $items = @(@{ Name = 'test-agent'; Description = 'desc'; Kind = 'agent' })
290 $result = New-PluginReadmeContent -Collection $collection -Items $items -Maturity 'experimental'
291 $result | Should -Match '\u26A0' # warning sign emoji
292 }
293
294 It 'Has no notice when maturity is stable' {
295 $collection = @{
296 id = 'test-stable'
297 name = 'Test Stable'
298 description = 'A stable collection'
299 }
300 $items = @(@{ Name = 'test-agent'; Description = 'desc'; Kind = 'agent' })
301 $result = New-PluginReadmeContent -Collection $collection -Items $items -Maturity 'stable'
302 $result | Should -Not -Match '\u26A0'
303 }
304
305 It 'Has no notice when maturity is omitted' {
306 $collection = @{
307 id = 'test-default'
308 name = 'Test Default'
309 description = 'A default collection'
310 }
311 $items = @(@{ Name = 'test-agent'; Description = 'desc'; Kind = 'agent' })
312 $result = New-PluginReadmeContent -Collection $collection -Items $items
313 $result | Should -Not -Match '\u26A0'
314 }
315
316 It 'Has no notice when maturity is null' {
317 $collection = @{
318 id = 'test-null'
319 name = 'Test Null'
320 description = 'A null maturity collection'
321 }
322 $items = @(@{ Name = 'test-agent'; Description = 'desc'; Kind = 'agent' })
323 $result = New-PluginReadmeContent -Collection $collection -Items $items -Maturity $null
324 $result | Should -Not -Match '\u26A0'
325 }
326}
327
328Describe 'Get-PluginItemName' {
329 It 'Strips .agent.md suffix' {
330 $result = Get-PluginItemName -FileName 'task-researcher.agent.md' -Kind 'agent'
331 $result | Should -Be 'task-researcher.md'
332 }
333
334 It 'Strips .prompt.md suffix' {
335 $result = Get-PluginItemName -FileName 'gen-plan.prompt.md' -Kind 'prompt'
336 $result | Should -Be 'gen-plan.md'
337 }
338
339 It 'Strips .instructions.md suffix' {
340 $result = Get-PluginItemName -FileName 'csharp.instructions.md' -Kind 'instruction'
341 $result | Should -Be 'csharp.md'
342 }
343
344 It 'Returns skill directory name unchanged' {
345 $result = Get-PluginItemName -FileName 'video-to-gif' -Kind 'skill'
346 $result | Should -Be 'video-to-gif'
347 }
348}
349
350Describe 'Get-PluginSubdirectory' {
351 It 'Maps agent to agents' {
352 $result = Get-PluginSubdirectory -Kind 'agent'
353 $result | Should -Be 'agents'
354 }
355
356 It 'Maps prompt to commands' {
357 $result = Get-PluginSubdirectory -Kind 'prompt'
358 $result | Should -Be 'commands'
359 }
360
361 It 'Maps instruction to instructions' {
362 $result = Get-PluginSubdirectory -Kind 'instruction'
363 $result | Should -Be 'instructions'
364 }
365
366 It 'Maps skill to skills' {
367 $result = Get-PluginSubdirectory -Kind 'skill'
368 $result | Should -Be 'skills'
369 }
370}
371
372Describe 'Get-ArtifactFrontmatter - YAML parse failure' {
373 It 'Returns fallback when YAML frontmatter is malformed' {
374 $testFile = Join-Path $TestDrive 'bad-yaml.agent.md'
375 # Invalid YAML: tab characters and broken mapping
376 Set-Content -Path $testFile -Value "---`n`t: [invalid: yaml`n---`nBody"
377 $result = Get-ArtifactFrontmatter -FilePath $testFile -FallbackDescription 'fallback-desc'
378 $result.description | Should -Be 'fallback-desc'
379 }
380}
381
382Describe 'Write-PluginDirectory - DryRun mode' {
383 BeforeAll {
384 $script:repoRoot = Join-Path $TestDrive 'wpd-repo'
385 $script:pluginsDir = Join-Path $TestDrive 'wpd-plugins'
386 New-Item -ItemType Directory -Path $script:repoRoot -Force | Out-Null
387 New-Item -ItemType Directory -Path $script:pluginsDir -Force | Out-Null
388
389 # Create a valid agent file with frontmatter
390 $agentDir = Join-Path $script:repoRoot '.github/agents/test'
391 New-Item -ItemType Directory -Path $agentDir -Force | Out-Null
392 Set-Content -Path (Join-Path $agentDir 'example.agent.md') -Value "---`ndescription: An example agent`n---`nAgent body"
393
394 # Create a valid skill directory with SKILL.md
395 $skillDir = Join-Path $script:repoRoot '.github/skills/test/my-skill'
396 New-Item -ItemType Directory -Path $skillDir -Force | Out-Null
397 Set-Content -Path (Join-Path $skillDir 'SKILL.md') -Value "---`ndescription: A skill`n---`nSkill body"
398
399 # Create shared dirs
400 New-Item -ItemType Directory -Path (Join-Path $script:repoRoot 'docs/templates') -Force | Out-Null
401 New-Item -ItemType Directory -Path (Join-Path $script:repoRoot 'scripts/lib') -Force | Out-Null
402 }
403
404 It 'Completes DryRun without creating files for agents' {
405 $collection = @{
406 id = 'dryrun-test'
407 name = 'DryRun Test'
408 description = 'Testing DryRun mode'
409 items = @(
410 @{
411 path = '.github/agents/test/example.agent.md'
412 kind = 'agent'
413 }
414 )
415 }
416
417 $result = Write-PluginDirectory -Collection $collection -PluginsDir $script:pluginsDir `
418 -RepoRoot $script:repoRoot -Version '1.0.0' -DryRun
419
420 $result.Success | Should -BeTrue
421 $result.AgentCount | Should -Be 1
422
423 # Verify no actual files were created
424 $pluginDir = Join-Path $script:pluginsDir 'dryrun-test'
425 Test-Path -Path $pluginDir | Should -BeFalse
426 }
427
428 It 'Completes DryRun with skill items' {
429 $collection = @{
430 id = 'dryrun-skill'
431 name = 'DryRun Skill'
432 description = 'Testing DryRun with skills'
433 items = @(
434 @{
435 path = '.github/skills/test/my-skill'
436 kind = 'skill'
437 }
438 )
439 }
440
441 $result = Write-PluginDirectory -Collection $collection -PluginsDir $script:pluginsDir `
442 -RepoRoot $script:repoRoot -Version '1.0.0' -DryRun
443
444 $result.Success | Should -BeTrue
445 $result.SkillCount | Should -Be 1
446 }
447
448 It 'Handles source file not found for non-skill items' {
449 $collection = @{
450 id = 'missing-source'
451 name = 'Missing Source'
452 description = 'Non-existent source file'
453 items = @(
454 @{
455 path = '.github/agents/test/nonexistent.agent.md'
456 kind = 'agent'
457 }
458 )
459 }
460
461 $result = Write-PluginDirectory -Collection $collection -PluginsDir $script:pluginsDir `
462 -RepoRoot $script:repoRoot -Version '1.0.0' -DryRun
463
464 $result.Success | Should -BeTrue
465 $result.AgentCount | Should -Be 1
466 }
467
468 It 'Warns when shared directory is missing' {
469 $emptyRepo = Join-Path $TestDrive 'empty-repo'
470 New-Item -ItemType Directory -Path $emptyRepo -Force | Out-Null
471
472 # Create agent file but no shared directories
473 $agentDir = Join-Path $emptyRepo '.github/agents/test'
474 New-Item -ItemType Directory -Path $agentDir -Force | Out-Null
475 Set-Content -Path (Join-Path $agentDir 'a.agent.md') -Value "---`ndescription: test`n---"
476
477 $collection = @{
478 id = 'no-shared'
479 name = 'No Shared'
480 description = 'Missing shared dirs'
481 items = @(
482 @{
483 path = '.github/agents/test/a.agent.md'
484 kind = 'agent'
485 }
486 )
487 }
488
489 $result = Write-PluginDirectory -Collection $collection -PluginsDir $script:pluginsDir `
490 -RepoRoot $emptyRepo -Version '1.0.0' -DryRun
491
492 $result.Success | Should -BeTrue
493 }
494}
495
496Describe 'Test-SymlinkCapability' {
497 It 'Returns a boolean' {
498 $result = Test-SymlinkCapability
499 $result | Should -BeOfType [bool]
500 }
501
502 It 'Cleans up probe directory' {
503 $probeDirPattern = Join-Path ([System.IO.Path]::GetTempPath()) "hve-symlink-probe-$PID"
504 Test-SymlinkCapability | Out-Null
505 Test-Path $probeDirPattern | Should -BeFalse
506 }
507}
508
509Describe 'Update-HveCoreAllCollection - display key ordering' {
510 BeforeAll {
511 $script:repoRoot = Join-Path $TestDrive 'repo-display-order'
512 $ghDir = Join-Path $script:repoRoot '.github'
513
514 # Create a minimal artifact so discovery finds at least one item
515 $agentsDir = Join-Path $ghDir 'agents/test-collection'
516 New-Item -ItemType Directory -Path $agentsDir -Force | Out-Null
517 Set-Content -Path (Join-Path $agentsDir 'sample.agent.md') -Value "---`ndescription: sample agent`n---`nBody"
518 }
519
520 It 'Preserves featured-then-ordering key order when both keys exist' {
521 $collectionsDir = Join-Path $script:repoRoot 'collections'
522 New-Item -ItemType Directory -Path $collectionsDir -Force | Out-Null
523
524 # Write manifest with ordering BEFORE featured (reversed)
525 $yaml = @"
526id: hve-core-all
527name: HVE Core All
528description: All artifacts
529tags: []
530items:
531- path: .github/agents/test-collection/sample.agent.md
532 kind: agent
533display:
534 ordering: alpha
535 featured:
536 - sample.agent.md
537"@
538 Set-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml') -Value $yaml -Encoding utf8 -NoNewline
539
540 Update-HveCoreAllCollection -RepoRoot $script:repoRoot | Out-Null
541
542 $output = Get-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml') -Raw
543 # featured must appear before ordering in the output
544 $featuredIndex = $output.IndexOf('featured:')
545 $orderingIndex = $output.IndexOf('ordering:')
546 $featuredIndex | Should -BeLessThan $orderingIndex -Because 'featured key should precede ordering key in display section'
547 }
548
549 It 'Handles display with only ordering key' {
550 $collectionsDir = Join-Path $script:repoRoot 'collections'
551 New-Item -ItemType Directory -Path $collectionsDir -Force | Out-Null
552
553 $yaml = @"
554id: hve-core-all
555name: HVE Core All
556description: All artifacts
557tags: []
558items:
559- path: .github/agents/test-collection/sample.agent.md
560 kind: agent
561display:
562 ordering: alpha
563"@
564 Set-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml') -Value $yaml -Encoding utf8 -NoNewline
565
566 Update-HveCoreAllCollection -RepoRoot $script:repoRoot | Out-Null
567
568 $output = Get-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml') -Raw
569 $output | Should -Match 'ordering: alpha'
570 $output | Should -Not -Match 'featured:'
571 }
572
573 It 'Handles display with only featured key' {
574 $collectionsDir = Join-Path $script:repoRoot 'collections'
575 New-Item -ItemType Directory -Path $collectionsDir -Force | Out-Null
576
577 $yaml = @"
578id: hve-core-all
579name: HVE Core All
580description: All artifacts
581tags: []
582items:
583- path: .github/agents/test-collection/sample.agent.md
584 kind: agent
585display:
586 featured:
587 - sample.agent.md
588"@
589 Set-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml') -Value $yaml -Encoding utf8 -NoNewline
590
591 Update-HveCoreAllCollection -RepoRoot $script:repoRoot | Out-Null
592
593 $output = Get-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml') -Raw
594 $output | Should -Match 'featured:'
595 $output | Should -Not -Match 'ordering:'
596 }
597
598 It 'Returns expected result hashtable' {
599 $collectionsDir = Join-Path $script:repoRoot 'collections'
600 New-Item -ItemType Directory -Path $collectionsDir -Force | Out-Null
601
602 $yaml = @"
603id: hve-core-all
604name: HVE Core All
605description: All artifacts
606tags: []
607items:
608- path: .github/agents/test-collection/sample.agent.md
609 kind: agent
610display:
611 ordering: alpha
612"@
613 Set-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml') -Value $yaml -Encoding utf8 -NoNewline
614
615 $result = Update-HveCoreAllCollection -RepoRoot $script:repoRoot
616
617 $result.ItemCount | Should -BeGreaterOrEqual 1
618 $result.Keys | Should -Contain 'AddedCount'
619 $result.Keys | Should -Contain 'RemovedCount'
620 $result.Keys | Should -Contain 'DeprecatedCount'
621 }
622
623 It 'Does not write to disk in DryRun mode' {
624 $collectionsDir = Join-Path $script:repoRoot 'collections'
625 New-Item -ItemType Directory -Path $collectionsDir -Force | Out-Null
626
627 $yaml = @"
628id: hve-core-all
629name: HVE Core All
630description: All artifacts
631tags: []
632items: []
633display:
634 ordering: alpha
635"@
636 Set-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml') -Value $yaml -Encoding utf8 -NoNewline
637
638 Update-HveCoreAllCollection -RepoRoot $script:repoRoot -DryRun | Out-Null
639
640 $output = Get-Content -Path (Join-Path $collectionsDir 'hve-core-all.collection.yml') -Raw
641 $output | Should -Match 'items: \[\]' -Because 'DryRun should not modify the file'
642 }
643}
644
645Describe 'New-PluginLink' {
646 BeforeAll {
647 $script:linkRoot = Join-Path $TestDrive 'link-test'
648 New-Item -ItemType Directory -Path $script:linkRoot -Force | Out-Null
649 }
650
651 It 'Writes text stub when SymlinkCapable is false' {
652 $src = Join-Path $script:linkRoot 'src-stub.txt'
653 Set-Content -Path $src -Value 'content' -NoNewline
654 $dest = Join-Path $script:linkRoot 'dest-stub.txt'
655
656 New-PluginLink -SourcePath $src -DestinationPath $dest
657
658 Test-Path $dest | Should -BeTrue
659 $stubContent = [System.IO.File]::ReadAllText($dest)
660 $expectedPath = [System.IO.Path]::GetRelativePath((Split-Path -Parent $dest), $src) -replace '\\', '/'
661 $stubContent | Should -Be $expectedPath
662 }
663
664 It 'Creates parent directory when destination parent does not exist' {
665 $src = Join-Path $script:linkRoot 'src-parent.txt'
666 Set-Content -Path $src -Value 'data' -NoNewline
667 $dest = Join-Path $script:linkRoot 'nested/deep/dest-parent.txt'
668
669 New-PluginLink -SourcePath $src -DestinationPath $dest
670
671 Test-Path $dest | Should -BeTrue
672 }
673}
674