microsoft/hve-core

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
7d63c6d0506185bee9717955d0fc5ed35861d8a7

Branches

Tags

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

Clone

HTTPS

Download ZIP

scripts/tests/plugins/PluginHelpers.Tests.ps1

365lines · 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 'New-PluginReadmeContent - maturity notice' {
10 It 'Includes experimental notice when maturity is experimental' {
11 $collection = @{
12 id = 'test-exp'
13 name = 'Test Experimental'
14 description = 'An experimental collection'
15 }
16 $items = @(@{ Name = 'test-agent'; Description = 'desc'; Kind = 'agent' })
17 $result = New-PluginReadmeContent -Collection $collection -Items $items -Maturity 'experimental'
18 $result | Should -Match '\u26A0' # warning sign emoji
19 }
20
21 It 'Has no notice when maturity is stable' {
22 $collection = @{
23 id = 'test-stable'
24 name = 'Test Stable'
25 description = 'A stable collection'
26 }
27 $items = @(@{ Name = 'test-agent'; Description = 'desc'; Kind = 'agent' })
28 $result = New-PluginReadmeContent -Collection $collection -Items $items -Maturity 'stable'
29 $result | Should -Not -Match '\u26A0'
30 }
31
32 It 'Has no notice when maturity is omitted' {
33 $collection = @{
34 id = 'test-default'
35 name = 'Test Default'
36 description = 'A default collection'
37 }
38 $items = @(@{ Name = 'test-agent'; Description = 'desc'; Kind = 'agent' })
39 $result = New-PluginReadmeContent -Collection $collection -Items $items
40 $result | Should -Not -Match '\u26A0'
41 }
42
43 It 'Has no notice when maturity is null' {
44 $collection = @{
45 id = 'test-null'
46 name = 'Test Null'
47 description = 'A null maturity collection'
48 }
49 $items = @(@{ Name = 'test-agent'; Description = 'desc'; Kind = 'agent' })
50 $result = New-PluginReadmeContent -Collection $collection -Items $items -Maturity $null
51 $result | Should -Not -Match '\u26A0'
52 }
53}
54
55Describe 'Get-PluginItemName' {
56 It 'Strips .agent.md suffix' {
57 $result = Get-PluginItemName -FileName 'task-researcher.agent.md' -Kind 'agent'
58 $result | Should -Be 'task-researcher.md'
59 }
60
61 It 'Strips .prompt.md suffix' {
62 $result = Get-PluginItemName -FileName 'gen-plan.prompt.md' -Kind 'prompt'
63 $result | Should -Be 'gen-plan.md'
64 }
65
66 It 'Strips .instructions.md suffix' {
67 $result = Get-PluginItemName -FileName 'csharp.instructions.md' -Kind 'instruction'
68 $result | Should -Be 'csharp.md'
69 }
70
71 It 'Returns skill directory name unchanged' {
72 $result = Get-PluginItemName -FileName 'video-to-gif' -Kind 'skill'
73 $result | Should -Be 'video-to-gif'
74 }
75}
76
77Describe 'Get-PluginSubdirectory' {
78 It 'Maps agent to agents' {
79 $result = Get-PluginSubdirectory -Kind 'agent'
80 $result | Should -Be 'agents'
81 }
82
83 It 'Maps prompt to commands' {
84 $result = Get-PluginSubdirectory -Kind 'prompt'
85 $result | Should -Be 'commands'
86 }
87
88 It 'Maps instruction to instructions' {
89 $result = Get-PluginSubdirectory -Kind 'instruction'
90 $result | Should -Be 'instructions'
91 }
92
93 It 'Maps skill to skills' {
94 $result = Get-PluginSubdirectory -Kind 'skill'
95 $result | Should -Be 'skills'
96 }
97}
98
99Describe 'Write-PluginDirectory - DryRun mode' {
100 BeforeAll {
101 $script:repoRoot = Join-Path $TestDrive 'wpd-repo'
102 $script:pluginsDir = Join-Path $TestDrive 'wpd-plugins'
103 New-Item -ItemType Directory -Path $script:repoRoot -Force | Out-Null
104 New-Item -ItemType Directory -Path $script:pluginsDir -Force | Out-Null
105
106 # Create a valid agent file with frontmatter
107 $agentDir = Join-Path $script:repoRoot '.github/agents/test'
108 New-Item -ItemType Directory -Path $agentDir -Force | Out-Null
109 Set-Content -Path (Join-Path $agentDir 'example.agent.md') -Value "---`ndescription: An example agent`n---`nAgent body"
110
111 # Create a valid skill directory with SKILL.md
112 $skillDir = Join-Path $script:repoRoot '.github/skills/test/my-skill'
113 New-Item -ItemType Directory -Path $skillDir -Force | Out-Null
114 Set-Content -Path (Join-Path $skillDir 'SKILL.md') -Value "---`ndescription: A skill`n---`nSkill body"
115
116 # Create shared dirs
117 New-Item -ItemType Directory -Path (Join-Path $script:repoRoot 'docs/templates') -Force | Out-Null
118 New-Item -ItemType Directory -Path (Join-Path $script:repoRoot 'scripts/lib') -Force | Out-Null
119 }
120
121 It 'Completes DryRun without creating files for agents' {
122 $collection = @{
123 id = 'dryrun-test'
124 name = 'DryRun Test'
125 description = 'Testing DryRun mode'
126 items = @(
127 @{
128 path = '.github/agents/test/example.agent.md'
129 kind = 'agent'
130 }
131 )
132 }
133
134 $result = Write-PluginDirectory -Collection $collection -PluginsDir $script:pluginsDir `
135 -RepoRoot $script:repoRoot -Version '1.0.0' -DryRun
136
137 $result.Success | Should -BeTrue
138 $result.AgentCount | Should -Be 1
139
140 # Verify no actual files were created
141 $pluginDir = Join-Path $script:pluginsDir 'dryrun-test'
142 Test-Path -Path $pluginDir | Should -BeFalse
143 }
144
145 It 'Completes DryRun with skill items' {
146 $collection = @{
147 id = 'dryrun-skill'
148 name = 'DryRun Skill'
149 description = 'Testing DryRun with skills'
150 items = @(
151 @{
152 path = '.github/skills/test/my-skill'
153 kind = 'skill'
154 }
155 )
156 }
157
158 $result = Write-PluginDirectory -Collection $collection -PluginsDir $script:pluginsDir `
159 -RepoRoot $script:repoRoot -Version '1.0.0' -DryRun
160
161 $result.Success | Should -BeTrue
162 $result.SkillCount | Should -Be 1
163 }
164
165 It 'Handles source file not found for non-skill items' {
166 $collection = @{
167 id = 'missing-source'
168 name = 'Missing Source'
169 description = 'Non-existent source file'
170 items = @(
171 @{
172 path = '.github/agents/test/nonexistent.agent.md'
173 kind = 'agent'
174 }
175 )
176 }
177
178 $result = Write-PluginDirectory -Collection $collection -PluginsDir $script:pluginsDir `
179 -RepoRoot $script:repoRoot -Version '1.0.0' -DryRun
180
181 $result.Success | Should -BeTrue
182 $result.AgentCount | Should -Be 1
183 }
184
185 It 'Warns when shared directory is missing' {
186 $emptyRepo = Join-Path $TestDrive 'empty-repo'
187 New-Item -ItemType Directory -Path $emptyRepo -Force | Out-Null
188
189 # Create agent file but no shared directories
190 $agentDir = Join-Path $emptyRepo '.github/agents/test'
191 New-Item -ItemType Directory -Path $agentDir -Force | Out-Null
192 Set-Content -Path (Join-Path $agentDir 'a.agent.md') -Value "---`ndescription: test`n---"
193
194 $collection = @{
195 id = 'no-shared'
196 name = 'No Shared'
197 description = 'Missing shared dirs'
198 items = @(
199 @{
200 path = '.github/agents/test/a.agent.md'
201 kind = 'agent'
202 }
203 )
204 }
205
206 $result = Write-PluginDirectory -Collection $collection -PluginsDir $script:pluginsDir `
207 -RepoRoot $emptyRepo -Version '1.0.0' -DryRun
208
209 $result.Success | Should -BeTrue
210 }
211}
212
213Describe 'Test-SymlinkCapability' {
214 It 'Returns a boolean' {
215 $result = Test-SymlinkCapability
216 $result | Should -BeOfType [bool]
217 }
218
219 It 'Cleans up probe directory' {
220 $probeDirPattern = Join-Path ([System.IO.Path]::GetTempPath()) "hve-symlink-probe-$PID"
221 Test-SymlinkCapability | Out-Null
222 Test-Path $probeDirPattern | Should -BeFalse
223 }
224}
225
226Describe 'New-PluginLink' {
227 BeforeAll {
228 $script:linkRoot = Join-Path $TestDrive 'link-test'
229 New-Item -ItemType Directory -Path $script:linkRoot -Force | Out-Null
230 }
231
232 It 'Writes text stub when SymlinkCapable is false' {
233 $src = Join-Path $script:linkRoot 'src-stub.txt'
234 Set-Content -Path $src -Value 'content' -NoNewline
235 $dest = Join-Path $script:linkRoot 'dest-stub.txt'
236
237 New-PluginLink -SourcePath $src -DestinationPath $dest
238
239 Test-Path $dest | Should -BeTrue
240 $stubContent = [System.IO.File]::ReadAllText($dest)
241 $expectedPath = [System.IO.Path]::GetRelativePath((Split-Path -Parent $dest), $src) -replace '\\', '/'
242 $stubContent | Should -Be $expectedPath
243 }
244
245 It 'Creates parent directory when destination parent does not exist' {
246 $src = Join-Path $script:linkRoot 'src-parent.txt'
247 Set-Content -Path $src -Value 'data' -NoNewline
248 $dest = Join-Path $script:linkRoot 'nested/deep/dest-parent.txt'
249
250 New-PluginLink -SourcePath $src -DestinationPath $dest
251
252 Test-Path $dest | Should -BeTrue
253 }
254}
255
256Describe 'Repair-PluginSymlinkIndex' {
257 Context 'When PluginsDir does not exist' {
258 It 'Returns 0' {
259 $result = Repair-PluginSymlinkIndex `
260 -PluginsDir (Join-Path $TestDrive 'nonexistent') `
261 -RepoRoot $TestDrive
262 $result | Should -Be 0
263 }
264 }
265
266 Context 'In a git repository with text stubs' {
267 BeforeAll {
268 $script:repoRoot = Join-Path $TestDrive 'symlink-repo'
269 New-Item -ItemType Directory -Path $script:repoRoot -Force | Out-Null
270
271 Push-Location $script:repoRoot
272 try {
273 git init --quiet 2>$null
274 git config user.email 'test@test.com'
275 git config user.name 'Test'
276
277 $script:pluginsDir = Join-Path $script:repoRoot 'plugins'
278 New-Item -ItemType Directory -Path $script:pluginsDir -Force | Out-Null
279
280 # Valid text stub: small, starts with ../, no newlines
281 [System.IO.File]::WriteAllText(
282 (Join-Path $script:pluginsDir 'valid-stub.md'),
283 '../some/source.md'
284 )
285
286 # Large file (>500 bytes) — skipped by size filter
287 [System.IO.File]::WriteAllText(
288 (Join-Path $script:pluginsDir 'large-file.md'),
289 ('x' * 501)
290 )
291
292 # Non-stub content — skipped by pattern filter
293 [System.IO.File]::WriteAllText(
294 (Join-Path $script:pluginsDir 'non-stub.md'),
295 '# Regular markdown'
296 )
297
298 # Stub with newline — skipped by newline filter
299 [System.IO.File]::WriteAllText(
300 (Join-Path $script:pluginsDir 'newline-stub.md'),
301 "../path/file.md`n"
302 )
303
304 git add -- plugins/ 2>$null
305 git commit -m 'initial' --quiet 2>$null
306 } finally {
307 Pop-Location
308 }
309 }
310
311 It 'Counts only valid stubs in DryRun mode' {
312 Push-Location $script:repoRoot
313 try {
314 $result = Repair-PluginSymlinkIndex `
315 -PluginsDir $script:pluginsDir `
316 -RepoRoot $script:repoRoot -DryRun
317 $result | Should -Be 1
318 } finally {
319 Pop-Location
320 }
321 }
322
323 It 'Does not modify index in DryRun mode' {
324 Push-Location $script:repoRoot
325 try {
326 $before = git ls-files --stage -- plugins/valid-stub.md 2>$null
327 Repair-PluginSymlinkIndex `
328 -PluginsDir $script:pluginsDir `
329 -RepoRoot $script:repoRoot -DryRun | Out-Null
330 $after = git ls-files --stage -- plugins/valid-stub.md 2>$null
331 $before | Should -Be $after
332 } finally {
333 Pop-Location
334 }
335 }
336
337 It 'Re-indexes tracked text stub as mode 120000' {
338 Push-Location $script:repoRoot
339 try {
340 $result = Repair-PluginSymlinkIndex `
341 -PluginsDir $script:pluginsDir `
342 -RepoRoot $script:repoRoot
343 $result | Should -Be 1
344
345 $lsOutput = git ls-files --stage -- plugins/valid-stub.md 2>$null
346 $lsOutput | Should -Match '^120000'
347 } finally {
348 Pop-Location
349 }
350 }
351
352 It 'Skips entries already at mode 120000' {
353 Push-Location $script:repoRoot
354 try {
355 # Previous test fixed the stub; second run finds nothing new
356 $result = Repair-PluginSymlinkIndex `
357 -PluginsDir $script:pluginsDir `
358 -RepoRoot $script:repoRoot
359 $result | Should -Be 0
360 } finally {
361 Pop-Location
362 }
363 }
364 }
365}
366