@skillsmith/mcp-server 0.3.2 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/README.md +5 -3
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/src/__tests__/context.test.d.ts +10 -0
  4. package/dist/src/__tests__/context.test.d.ts.map +1 -0
  5. package/dist/src/__tests__/context.test.js +345 -0
  6. package/dist/src/__tests__/context.test.js.map +1 -0
  7. package/dist/src/__tests__/get-skill.test.d.ts +1 -0
  8. package/dist/src/__tests__/get-skill.test.d.ts.map +1 -1
  9. package/dist/src/__tests__/get-skill.test.js +84 -0
  10. package/dist/src/__tests__/get-skill.test.js.map +1 -1
  11. package/dist/src/__tests__/middleware/license.test.js +180 -78
  12. package/dist/src/__tests__/middleware/license.test.js.map +1 -1
  13. package/dist/src/__tests__/search.test.js +2 -1
  14. package/dist/src/__tests__/search.test.js.map +1 -1
  15. package/dist/src/__tests__/utils/validation.test.d.ts +7 -0
  16. package/dist/src/__tests__/utils/validation.test.d.ts.map +1 -0
  17. package/dist/src/__tests__/utils/validation.test.js +82 -0
  18. package/dist/src/__tests__/utils/validation.test.js.map +1 -0
  19. package/dist/src/context.d.ts +16 -0
  20. package/dist/src/context.d.ts.map +1 -1
  21. package/dist/src/context.js +57 -9
  22. package/dist/src/context.js.map +1 -1
  23. package/dist/src/index.js +1 -1
  24. package/dist/src/index.js.map +1 -1
  25. package/dist/src/llm/failover.d.ts +200 -0
  26. package/dist/src/llm/failover.d.ts.map +1 -0
  27. package/dist/src/llm/failover.js +329 -0
  28. package/dist/src/llm/failover.js.map +1 -0
  29. package/dist/src/middleware/license.d.ts +5 -0
  30. package/dist/src/middleware/license.d.ts.map +1 -1
  31. package/dist/src/middleware/license.js +2 -1
  32. package/dist/src/middleware/license.js.map +1 -1
  33. package/dist/src/tools/compare.d.ts +3 -102
  34. package/dist/src/tools/compare.d.ts.map +1 -1
  35. package/dist/src/tools/compare.helpers.d.ts +36 -0
  36. package/dist/src/tools/compare.helpers.d.ts.map +1 -0
  37. package/dist/src/tools/compare.helpers.js +252 -0
  38. package/dist/src/tools/compare.helpers.js.map +1 -0
  39. package/dist/src/tools/compare.js +6 -288
  40. package/dist/src/tools/compare.js.map +1 -1
  41. package/dist/src/tools/compare.types.d.ts +133 -0
  42. package/dist/src/tools/compare.types.d.ts.map +1 -0
  43. package/dist/src/tools/compare.types.js +45 -0
  44. package/dist/src/tools/compare.types.js.map +1 -0
  45. package/dist/src/tools/get-skill.d.ts.map +1 -1
  46. package/dist/src/tools/get-skill.js +35 -2
  47. package/dist/src/tools/get-skill.js.map +1 -1
  48. package/dist/src/tools/install.d.ts +6 -60
  49. package/dist/src/tools/install.d.ts.map +1 -1
  50. package/dist/src/tools/install.helpers.d.ts +82 -0
  51. package/dist/src/tools/install.helpers.d.ts.map +1 -0
  52. package/dist/src/tools/install.helpers.js +324 -0
  53. package/dist/src/tools/install.helpers.js.map +1 -0
  54. package/dist/src/tools/install.js +176 -168
  55. package/dist/src/tools/install.js.map +1 -1
  56. package/dist/src/tools/install.types.d.ts +114 -0
  57. package/dist/src/tools/install.types.d.ts.map +1 -0
  58. package/dist/src/tools/install.types.js +91 -0
  59. package/dist/src/tools/install.types.js.map +1 -0
  60. package/dist/src/tools/recommend.d.ts +2 -148
  61. package/dist/src/tools/recommend.d.ts.map +1 -1
  62. package/dist/src/tools/recommend.helpers.d.ts +42 -0
  63. package/dist/src/tools/recommend.helpers.d.ts.map +1 -0
  64. package/dist/src/tools/recommend.helpers.js +155 -0
  65. package/dist/src/tools/recommend.helpers.js.map +1 -0
  66. package/dist/src/tools/recommend.js +75 -140
  67. package/dist/src/tools/recommend.js.map +1 -1
  68. package/dist/src/tools/recommend.types.d.ts +164 -0
  69. package/dist/src/tools/recommend.types.d.ts.map +1 -0
  70. package/dist/src/tools/recommend.types.js +87 -0
  71. package/dist/src/tools/recommend.types.js.map +1 -0
  72. package/dist/src/tools/search.d.ts +18 -4
  73. package/dist/src/tools/search.d.ts.map +1 -1
  74. package/dist/src/tools/search.js +64 -12
  75. package/dist/src/tools/search.js.map +1 -1
  76. package/dist/src/tools/validate.d.ts +3 -70
  77. package/dist/src/tools/validate.d.ts.map +1 -1
  78. package/dist/src/tools/validate.helpers.d.ts +22 -0
  79. package/dist/src/tools/validate.helpers.d.ts.map +1 -0
  80. package/dist/src/tools/validate.helpers.js +276 -0
  81. package/dist/src/tools/validate.helpers.js.map +1 -0
  82. package/dist/src/tools/validate.js +4 -337
  83. package/dist/src/tools/validate.js.map +1 -1
  84. package/dist/src/tools/validate.types.d.ts +96 -0
  85. package/dist/src/tools/validate.types.d.ts.map +1 -0
  86. package/dist/src/tools/validate.types.js +71 -0
  87. package/dist/src/tools/validate.types.js.map +1 -0
  88. package/dist/src/webhooks/index.d.ts +1 -0
  89. package/dist/src/webhooks/index.d.ts.map +1 -1
  90. package/dist/src/webhooks/index.js +2 -0
  91. package/dist/src/webhooks/index.js.map +1 -1
  92. package/dist/src/webhooks/stripe-webhook-endpoint.d.ts +68 -0
  93. package/dist/src/webhooks/stripe-webhook-endpoint.d.ts.map +1 -0
  94. package/dist/src/webhooks/stripe-webhook-endpoint.js +213 -0
  95. package/dist/src/webhooks/stripe-webhook-endpoint.js.map +1 -0
  96. package/dist/tests/integration/install.integration.test.js +273 -1
  97. package/dist/tests/integration/install.integration.test.js.map +1 -1
  98. package/dist/tests/integration/recommend.integration.test.js +2 -1
  99. package/dist/tests/integration/recommend.integration.test.js.map +1 -1
  100. package/dist/tests/llm/failover.test.d.ts +13 -0
  101. package/dist/tests/llm/failover.test.d.ts.map +1 -0
  102. package/dist/tests/llm/failover.test.js +250 -0
  103. package/dist/tests/llm/failover.test.js.map +1 -0
  104. package/dist/tests/recommend.test.js +133 -1
  105. package/dist/tests/recommend.test.js.map +1 -1
  106. package/dist/tests/tools.test.d.ts +1 -0
  107. package/dist/tests/tools.test.d.ts.map +1 -1
  108. package/dist/tests/tools.test.js +59 -1
  109. package/dist/tests/tools.test.js.map +1 -1
  110. package/dist/tests/unit/compare-helpers.test.d.ts +8 -0
  111. package/dist/tests/unit/compare-helpers.test.d.ts.map +1 -0
  112. package/dist/tests/unit/compare-helpers.test.js +224 -0
  113. package/dist/tests/unit/compare-helpers.test.js.map +1 -0
  114. package/dist/tests/unit/install-helpers.test.d.ts +8 -0
  115. package/dist/tests/unit/install-helpers.test.d.ts.map +1 -0
  116. package/dist/tests/unit/install-helpers.test.js +460 -0
  117. package/dist/tests/unit/install-helpers.test.js.map +1 -0
  118. package/dist/tests/unit/recommend-helpers.test.d.ts +8 -0
  119. package/dist/tests/unit/recommend-helpers.test.d.ts.map +1 -0
  120. package/dist/tests/unit/recommend-helpers.test.js +117 -0
  121. package/dist/tests/unit/recommend-helpers.test.js.map +1 -0
  122. package/dist/tests/unit/validate-helpers.test.d.ts +8 -0
  123. package/dist/tests/unit/validate-helpers.test.d.ts.map +1 -0
  124. package/dist/tests/unit/validate-helpers.test.js +243 -0
  125. package/dist/tests/unit/validate-helpers.test.js.map +1 -0
  126. package/package.json +1 -1
  127. package/src/assets/docs/USER_GUIDE.md +0 -192
  128. package/src/assets/skills/skillsmith/docs/QUOTAS.md +0 -182
  129. package/src/assets/skills/skillsmith/docs/SECURITY.md +0 -174
  130. package/src/assets/skills/skillsmith/docs/TRUST_TIERS.md +0 -142
@@ -0,0 +1,460 @@
1
+ /**
2
+ * @fileoverview Tests for install.helpers.ts functions
3
+ * @module @skillsmith/mcp-server/tests/unit/install-helpers
4
+ *
5
+ * SMI-1721: Comprehensive tests to improve coverage from 36% to 80%+
6
+ */
7
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
8
+ import * as fs from 'fs/promises';
9
+ import { parseSkillId, parseRepoUrl, validateSkillMd, generateTips, acquireManifestLock, releaseManifestLock, loadManifest, saveManifest, updateManifestSafely, lookupSkillFromRegistry, fetchFromGitHub, } from '../../src/tools/install.helpers.js';
10
+ // Mock fs module
11
+ vi.mock('fs/promises');
12
+ // Mock global fetch
13
+ const mockFetch = vi.fn();
14
+ global.fetch = mockFetch;
15
+ describe('install.helpers', () => {
16
+ describe('parseSkillId', () => {
17
+ it('parses full GitHub URL', () => {
18
+ const result = parseSkillId('https://github.com/owner/repo');
19
+ expect(result).toEqual({
20
+ owner: 'owner',
21
+ repo: 'repo',
22
+ path: '',
23
+ isRegistryId: false,
24
+ });
25
+ });
26
+ it('parses GitHub URL with path', () => {
27
+ const result = parseSkillId('https://github.com/owner/repo/tree/main/skills/my-skill');
28
+ expect(result).toEqual({
29
+ owner: 'owner',
30
+ repo: 'repo',
31
+ path: 'tree/main/skills/my-skill',
32
+ isRegistryId: false,
33
+ });
34
+ });
35
+ it('parses 2-part registry ID', () => {
36
+ const result = parseSkillId('author/skill-name');
37
+ expect(result).toEqual({
38
+ owner: 'author',
39
+ repo: 'skill-name',
40
+ path: '',
41
+ isRegistryId: true,
42
+ });
43
+ });
44
+ it('parses 3-part direct reference', () => {
45
+ const result = parseSkillId('owner/repo/skills/my-skill');
46
+ expect(result).toEqual({
47
+ owner: 'owner',
48
+ repo: 'repo',
49
+ path: 'skills/my-skill',
50
+ isRegistryId: false,
51
+ });
52
+ });
53
+ it('throws for invalid format', () => {
54
+ expect(() => parseSkillId('invalid')).toThrow('Invalid skill ID format');
55
+ });
56
+ });
57
+ describe('parseRepoUrl', () => {
58
+ it('parses simple GitHub URL', () => {
59
+ const result = parseRepoUrl('https://github.com/owner/repo');
60
+ expect(result).toEqual({
61
+ owner: 'owner',
62
+ repo: 'repo',
63
+ path: '',
64
+ branch: 'main',
65
+ });
66
+ });
67
+ it('parses GitHub URL with tree path', () => {
68
+ const result = parseRepoUrl('https://github.com/owner/repo/tree/develop/src/skills');
69
+ expect(result).toEqual({
70
+ owner: 'owner',
71
+ repo: 'repo',
72
+ path: 'src/skills',
73
+ branch: 'develop',
74
+ });
75
+ });
76
+ it('parses GitHub URL with blob path', () => {
77
+ const result = parseRepoUrl('https://github.com/owner/repo/blob/feature/path/file.md');
78
+ expect(result).toEqual({
79
+ owner: 'owner',
80
+ repo: 'repo',
81
+ path: 'path/file.md',
82
+ branch: 'feature',
83
+ });
84
+ });
85
+ it('rejects non-GitHub hosts', () => {
86
+ expect(() => parseRepoUrl('https://gitlab.com/owner/repo')).toThrow('Invalid repository host');
87
+ });
88
+ it('rejects malicious hosts', () => {
89
+ expect(() => parseRepoUrl('https://evil.com/owner/repo')).toThrow('Invalid repository host');
90
+ });
91
+ it('accepts www.github.com', () => {
92
+ const result = parseRepoUrl('https://www.github.com/owner/repo');
93
+ expect(result.owner).toBe('owner');
94
+ expect(result.repo).toBe('repo');
95
+ });
96
+ });
97
+ describe('validateSkillMd', () => {
98
+ it('validates valid SKILL.md', () => {
99
+ const content = `# My Skill
100
+
101
+ This is a skill that does something useful. It has enough content to pass validation.
102
+
103
+ ## Usage
104
+ Use this skill to do things.
105
+ `;
106
+ const result = validateSkillMd(content);
107
+ expect(result.valid).toBe(true);
108
+ expect(result.errors).toEqual([]);
109
+ });
110
+ it('rejects missing title', () => {
111
+ const content = 'This is content without a heading. It is long enough but has no title marker.';
112
+ const result = validateSkillMd(content);
113
+ expect(result.valid).toBe(false);
114
+ expect(result.errors).toContain('Missing title (# heading)');
115
+ });
116
+ it('rejects too short content', () => {
117
+ const content = '# Title\n\nToo short.';
118
+ const result = validateSkillMd(content);
119
+ expect(result.valid).toBe(false);
120
+ expect(result.errors).toContain('SKILL.md is too short (minimum 100 characters)');
121
+ });
122
+ it('collects multiple errors', () => {
123
+ const content = 'No title, too short';
124
+ const result = validateSkillMd(content);
125
+ expect(result.valid).toBe(false);
126
+ expect(result.errors.length).toBe(2);
127
+ });
128
+ });
129
+ describe('generateTips', () => {
130
+ it('generates tips with skill name', () => {
131
+ const tips = generateTips('my-skill');
132
+ expect(tips).toHaveLength(4);
133
+ expect(tips[0]).toContain('my-skill');
134
+ expect(tips[0]).toContain('installed successfully');
135
+ });
136
+ it('includes usage instructions', () => {
137
+ const tips = generateTips('test-skill');
138
+ expect(tips.some((t) => t.includes('Use the test-skill skill'))).toBe(true);
139
+ });
140
+ it('includes ls command', () => {
141
+ const tips = generateTips('any-skill');
142
+ expect(tips.some((t) => t.includes('ls ~/.claude/skills/'))).toBe(true);
143
+ });
144
+ it('includes uninstall hint', () => {
145
+ const tips = generateTips('any-skill');
146
+ expect(tips.some((t) => t.includes('uninstall_skill'))).toBe(true);
147
+ });
148
+ });
149
+ // ============================================================================
150
+ // SMI-1721: New tests for async/file system functions
151
+ // ============================================================================
152
+ describe('acquireManifestLock', () => {
153
+ const mockWriteFile = vi.mocked(fs.writeFile);
154
+ const mockStat = vi.mocked(fs.stat);
155
+ const mockUnlink = vi.mocked(fs.unlink);
156
+ beforeEach(() => {
157
+ vi.clearAllMocks();
158
+ });
159
+ it('acquires lock successfully on first try', async () => {
160
+ mockWriteFile.mockResolvedValueOnce(undefined);
161
+ await expect(acquireManifestLock()).resolves.toBeUndefined();
162
+ expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining('manifest.json.lock'), expect.any(String), { flag: 'wx' });
163
+ });
164
+ it('retries when lock exists and eventually succeeds', async () => {
165
+ // First call fails with EEXIST, second succeeds
166
+ const existsError = new Error('EEXIST');
167
+ existsError.code = 'EEXIST';
168
+ mockWriteFile.mockRejectedValueOnce(existsError).mockResolvedValueOnce(undefined);
169
+ // Mock stat to show lock is NOT stale (recent)
170
+ mockStat.mockResolvedValueOnce({
171
+ mtimeMs: Date.now() - 1000, // 1 second old
172
+ });
173
+ await expect(acquireManifestLock()).resolves.toBeUndefined();
174
+ expect(mockWriteFile).toHaveBeenCalledTimes(2);
175
+ });
176
+ it('removes stale lock and acquires', async () => {
177
+ const existsError = new Error('EEXIST');
178
+ existsError.code = 'EEXIST';
179
+ mockWriteFile.mockRejectedValueOnce(existsError).mockResolvedValueOnce(undefined);
180
+ // Mock stat to show lock is stale (old)
181
+ mockStat.mockResolvedValueOnce({
182
+ mtimeMs: Date.now() - 60000, // 60 seconds old (stale)
183
+ });
184
+ mockUnlink.mockResolvedValueOnce(undefined);
185
+ await expect(acquireManifestLock()).resolves.toBeUndefined();
186
+ expect(mockUnlink).toHaveBeenCalled();
187
+ });
188
+ it('throws non-EEXIST errors immediately', async () => {
189
+ const permError = new Error('EACCES');
190
+ permError.code = 'EACCES';
191
+ mockWriteFile.mockRejectedValueOnce(permError);
192
+ await expect(acquireManifestLock()).rejects.toThrow('EACCES');
193
+ });
194
+ });
195
+ describe('releaseManifestLock', () => {
196
+ const mockUnlink = vi.mocked(fs.unlink);
197
+ beforeEach(() => {
198
+ vi.clearAllMocks();
199
+ });
200
+ it('releases lock successfully', async () => {
201
+ mockUnlink.mockResolvedValueOnce(undefined);
202
+ await expect(releaseManifestLock()).resolves.toBeUndefined();
203
+ expect(mockUnlink).toHaveBeenCalledWith(expect.stringContaining('manifest.json.lock'));
204
+ });
205
+ it('ignores errors when lock already released', async () => {
206
+ mockUnlink.mockRejectedValueOnce(new Error('ENOENT'));
207
+ // Should not throw
208
+ await expect(releaseManifestLock()).resolves.toBeUndefined();
209
+ });
210
+ });
211
+ describe('loadManifest', () => {
212
+ const mockReadFile = vi.mocked(fs.readFile);
213
+ beforeEach(() => {
214
+ vi.clearAllMocks();
215
+ });
216
+ it('loads existing manifest', async () => {
217
+ const manifest = {
218
+ version: '1.0.0',
219
+ installedSkills: {
220
+ 'test/skill': {
221
+ id: 'test/skill',
222
+ name: 'test-skill',
223
+ version: '1.0.0',
224
+ source: 'github',
225
+ installPath: '/path/to/skill',
226
+ installedAt: '2024-01-01',
227
+ lastUpdated: '2024-01-01',
228
+ },
229
+ },
230
+ };
231
+ mockReadFile.mockResolvedValueOnce(JSON.stringify(manifest));
232
+ const result = await loadManifest();
233
+ expect(result).toEqual(manifest);
234
+ });
235
+ it('returns empty manifest when file does not exist', async () => {
236
+ mockReadFile.mockRejectedValueOnce(new Error('ENOENT'));
237
+ const result = await loadManifest();
238
+ expect(result).toEqual({
239
+ version: '1.0.0',
240
+ installedSkills: {},
241
+ });
242
+ });
243
+ it('returns empty manifest on parse error', async () => {
244
+ mockReadFile.mockResolvedValueOnce('invalid json {{{');
245
+ const result = await loadManifest();
246
+ expect(result).toEqual({
247
+ version: '1.0.0',
248
+ installedSkills: {},
249
+ });
250
+ });
251
+ });
252
+ describe('saveManifest', () => {
253
+ const mockMkdir = vi.mocked(fs.mkdir);
254
+ const mockWriteFile = vi.mocked(fs.writeFile);
255
+ const mockRename = vi.mocked(fs.rename);
256
+ beforeEach(() => {
257
+ vi.clearAllMocks();
258
+ });
259
+ it('saves manifest with atomic write', async () => {
260
+ mockMkdir.mockResolvedValueOnce(undefined);
261
+ mockWriteFile.mockResolvedValueOnce(undefined);
262
+ mockRename.mockResolvedValueOnce(undefined);
263
+ const manifest = {
264
+ version: '1.0.0',
265
+ installedSkills: {},
266
+ };
267
+ await saveManifest(manifest);
268
+ expect(mockMkdir).toHaveBeenCalledWith(expect.any(String), { recursive: true });
269
+ expect(mockWriteFile).toHaveBeenCalledWith(expect.stringContaining('.tmp.'), JSON.stringify(manifest, null, 2));
270
+ expect(mockRename).toHaveBeenCalled();
271
+ });
272
+ });
273
+ describe('updateManifestSafely', () => {
274
+ const mockWriteFile = vi.mocked(fs.writeFile);
275
+ const mockReadFile = vi.mocked(fs.readFile);
276
+ const mockMkdir = vi.mocked(fs.mkdir);
277
+ const mockRename = vi.mocked(fs.rename);
278
+ const mockUnlink = vi.mocked(fs.unlink);
279
+ beforeEach(() => {
280
+ vi.clearAllMocks();
281
+ });
282
+ it('acquires lock, updates, and releases lock', async () => {
283
+ // Mock lock acquisition
284
+ mockWriteFile.mockResolvedValue(undefined);
285
+ // Mock load
286
+ mockReadFile.mockResolvedValueOnce(JSON.stringify({
287
+ version: '1.0.0',
288
+ installedSkills: {},
289
+ }));
290
+ // Mock save
291
+ mockMkdir.mockResolvedValueOnce(undefined);
292
+ mockRename.mockResolvedValueOnce(undefined);
293
+ // Mock release
294
+ mockUnlink.mockResolvedValueOnce(undefined);
295
+ const updateFn = vi.fn((m) => ({
296
+ ...m,
297
+ installedSkills: { 'new/skill': { id: 'new/skill' } },
298
+ }));
299
+ await updateManifestSafely(updateFn);
300
+ expect(updateFn).toHaveBeenCalled();
301
+ expect(mockUnlink).toHaveBeenCalled(); // Lock released
302
+ });
303
+ it('releases lock even on error', async () => {
304
+ // Mock lock acquisition
305
+ mockWriteFile.mockResolvedValue(undefined);
306
+ // Mock load - throw error
307
+ mockReadFile.mockRejectedValueOnce(new Error('Read error'));
308
+ // Mock release
309
+ mockUnlink.mockResolvedValueOnce(undefined);
310
+ const updateFn = vi.fn();
311
+ // loadManifest catches errors and returns empty manifest
312
+ // so this should still succeed
313
+ await expect(updateManifestSafely(updateFn)).resolves.toBeUndefined();
314
+ });
315
+ });
316
+ describe('lookupSkillFromRegistry', () => {
317
+ it('returns skill info from API when online', async () => {
318
+ const mockContext = {
319
+ apiClient: {
320
+ isOffline: () => false,
321
+ getSkill: vi.fn().mockResolvedValue({
322
+ data: {
323
+ name: 'test-skill',
324
+ repo_url: 'https://github.com/owner/repo',
325
+ trust_tier: 'community',
326
+ },
327
+ }),
328
+ },
329
+ skillRepository: {
330
+ findById: vi.fn(),
331
+ },
332
+ };
333
+ const result = await lookupSkillFromRegistry('test/skill', mockContext);
334
+ expect(result).toEqual({
335
+ repoUrl: 'https://github.com/owner/repo',
336
+ name: 'test-skill',
337
+ trustTier: 'community',
338
+ });
339
+ expect(mockContext.apiClient.getSkill).toHaveBeenCalledWith('test/skill');
340
+ });
341
+ it('falls back to local DB when API offline', async () => {
342
+ const mockContext = {
343
+ apiClient: {
344
+ isOffline: () => true,
345
+ },
346
+ skillRepository: {
347
+ findById: vi.fn().mockReturnValue({
348
+ name: 'local-skill',
349
+ repoUrl: 'https://github.com/local/repo',
350
+ trustTier: 'experimental',
351
+ }),
352
+ },
353
+ };
354
+ const result = await lookupSkillFromRegistry('local/skill', mockContext);
355
+ expect(result).toEqual({
356
+ repoUrl: 'https://github.com/local/repo',
357
+ name: 'local-skill',
358
+ trustTier: 'experimental',
359
+ });
360
+ });
361
+ it('falls back to local DB when API fails', async () => {
362
+ const mockContext = {
363
+ apiClient: {
364
+ isOffline: () => false,
365
+ getSkill: vi.fn().mockRejectedValue(new Error('API error')),
366
+ },
367
+ skillRepository: {
368
+ findById: vi.fn().mockReturnValue({
369
+ name: 'fallback-skill',
370
+ repoUrl: 'https://github.com/fallback/repo',
371
+ trustTier: 'community',
372
+ }),
373
+ },
374
+ };
375
+ const result = await lookupSkillFromRegistry('fallback/skill', mockContext);
376
+ expect(result).toEqual({
377
+ repoUrl: 'https://github.com/fallback/repo',
378
+ name: 'fallback-skill',
379
+ trustTier: 'community',
380
+ });
381
+ });
382
+ it('returns null when skill not found anywhere', async () => {
383
+ const mockContext = {
384
+ apiClient: {
385
+ isOffline: () => true,
386
+ },
387
+ skillRepository: {
388
+ findById: vi.fn().mockReturnValue(null),
389
+ },
390
+ };
391
+ const result = await lookupSkillFromRegistry('nonexistent/skill', mockContext);
392
+ expect(result).toBeNull();
393
+ });
394
+ it('returns null when API returns skill without repo_url', async () => {
395
+ const mockContext = {
396
+ apiClient: {
397
+ isOffline: () => false,
398
+ getSkill: vi.fn().mockResolvedValue({
399
+ data: {
400
+ name: 'seed-skill',
401
+ repo_url: null, // No repo URL (seed data)
402
+ },
403
+ }),
404
+ },
405
+ skillRepository: {
406
+ findById: vi.fn().mockReturnValue(null),
407
+ },
408
+ };
409
+ const result = await lookupSkillFromRegistry('seed/skill', mockContext);
410
+ expect(result).toBeNull();
411
+ });
412
+ });
413
+ describe('fetchFromGitHub', () => {
414
+ beforeEach(() => {
415
+ vi.clearAllMocks();
416
+ mockFetch.mockReset();
417
+ });
418
+ it('fetches file from main branch', async () => {
419
+ mockFetch.mockResolvedValueOnce({
420
+ ok: true,
421
+ text: () => Promise.resolve('# SKILL.md content'),
422
+ });
423
+ const result = await fetchFromGitHub('owner', 'repo', 'SKILL.md');
424
+ expect(result).toBe('# SKILL.md content');
425
+ expect(mockFetch).toHaveBeenCalledWith('https://raw.githubusercontent.com/owner/repo/main/SKILL.md');
426
+ });
427
+ it('fetches from specified branch', async () => {
428
+ mockFetch.mockResolvedValueOnce({
429
+ ok: true,
430
+ text: () => Promise.resolve('content from develop'),
431
+ });
432
+ const result = await fetchFromGitHub('owner', 'repo', 'file.md', 'develop');
433
+ expect(result).toBe('content from develop');
434
+ expect(mockFetch).toHaveBeenCalledWith('https://raw.githubusercontent.com/owner/repo/develop/file.md');
435
+ });
436
+ it('falls back to master when main fails', async () => {
437
+ mockFetch.mockResolvedValueOnce({ ok: false, status: 404 }).mockResolvedValueOnce({
438
+ ok: true,
439
+ text: () => Promise.resolve('content from master'),
440
+ });
441
+ const result = await fetchFromGitHub('owner', 'repo', 'SKILL.md');
442
+ expect(result).toBe('content from master');
443
+ expect(mockFetch).toHaveBeenCalledTimes(2);
444
+ expect(mockFetch).toHaveBeenLastCalledWith('https://raw.githubusercontent.com/owner/repo/master/SKILL.md');
445
+ });
446
+ it('throws when both main and master fail', async () => {
447
+ mockFetch
448
+ .mockResolvedValueOnce({ ok: false, status: 404 })
449
+ .mockResolvedValueOnce({ ok: false, status: 404 });
450
+ await expect(fetchFromGitHub('owner', 'repo', 'SKILL.md')).rejects.toThrow('Failed to fetch SKILL.md: 404');
451
+ });
452
+ it('does not try master fallback for non-main branches', async () => {
453
+ mockFetch.mockResolvedValueOnce({ ok: false, status: 404 });
454
+ await expect(fetchFromGitHub('owner', 'repo', 'SKILL.md', 'develop')).rejects.toThrow('Failed to fetch SKILL.md: 404');
455
+ // Should only call once, no master fallback
456
+ expect(mockFetch).toHaveBeenCalledTimes(1);
457
+ });
458
+ });
459
+ });
460
+ //# sourceMappingURL=install-helpers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"install-helpers.test.js","sourceRoot":"","sources":["../../../tests/unit/install-helpers.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @fileoverview Tests for recommend.helpers.ts
3
+ * @module @skillsmith/mcp-server/tests/unit/recommend-helpers
4
+ *
5
+ * SMI-1719: Unit tests for extracted helper functions from Wave 3 refactor
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=recommend-helpers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommend-helpers.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/recommend-helpers.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @fileoverview Tests for recommend.helpers.ts
3
+ * @module @skillsmith/mcp-server/tests/unit/recommend-helpers
4
+ *
5
+ * SMI-1719: Unit tests for extracted helper functions from Wave 3 refactor
6
+ */
7
+ import { describe, it, expect } from 'vitest';
8
+ import { inferRolesFromTags, isSkillCollection, COLLECTION_PATTERNS, } from '../../src/tools/recommend.helpers.js';
9
+ describe('recommend.helpers', () => {
10
+ describe('inferRolesFromTags', () => {
11
+ it('returns empty array for empty tags', () => {
12
+ expect(inferRolesFromTags([])).toEqual([]);
13
+ });
14
+ // SMI-1725: Defensive null/undefined handling
15
+ it('handles null input gracefully', () => {
16
+ expect(inferRolesFromTags(null)).toEqual([]);
17
+ });
18
+ it('handles undefined input gracefully', () => {
19
+ expect(inferRolesFromTags(undefined)).toEqual([]);
20
+ });
21
+ it('infers code-quality role from lint tags', () => {
22
+ const result = inferRolesFromTags(['lint', 'eslint']);
23
+ expect(result).toContain('code-quality');
24
+ });
25
+ it('infers testing role from test tags', () => {
26
+ const result = inferRolesFromTags(['testing', 'jest', 'vitest']);
27
+ expect(result).toContain('testing');
28
+ });
29
+ it('infers documentation role from docs tags', () => {
30
+ const result = inferRolesFromTags(['documentation', 'readme', 'jsdoc']);
31
+ expect(result).toContain('documentation');
32
+ });
33
+ it('infers workflow role from git/ci tags', () => {
34
+ const result = inferRolesFromTags(['git', 'commit', 'ci-cd']);
35
+ expect(result).toContain('workflow');
36
+ });
37
+ it('infers security role from security tags', () => {
38
+ const result = inferRolesFromTags(['security', 'audit', 'vulnerability']);
39
+ expect(result).toContain('security');
40
+ });
41
+ it('infers development-partner role from ai tags', () => {
42
+ const result = inferRolesFromTags(['ai', 'assistant', 'copilot']);
43
+ expect(result).toContain('development-partner');
44
+ });
45
+ it('handles multiple roles from mixed tags', () => {
46
+ const result = inferRolesFromTags(['eslint', 'jest', 'git']);
47
+ expect(result).toContain('code-quality');
48
+ expect(result).toContain('testing');
49
+ expect(result).toContain('workflow');
50
+ });
51
+ it('normalizes tags with hyphens and underscores', () => {
52
+ const result = inferRolesFromTags(['code-review', 'pull_request']);
53
+ expect(result).toContain('code-quality');
54
+ expect(result).toContain('workflow');
55
+ });
56
+ it('handles case insensitivity', () => {
57
+ const result = inferRolesFromTags(['ESLINT', 'Jest', 'GIT']);
58
+ expect(result).toContain('code-quality');
59
+ expect(result).toContain('testing');
60
+ expect(result).toContain('workflow');
61
+ });
62
+ it('does not duplicate roles', () => {
63
+ const result = inferRolesFromTags(['lint', 'linting', 'eslint', 'prettier']);
64
+ const codeQualityCount = result.filter((r) => r === 'code-quality').length;
65
+ expect(codeQualityCount).toBe(1);
66
+ });
67
+ it('returns empty for unrecognized tags', () => {
68
+ const result = inferRolesFromTags(['random', 'unknown', 'custom']);
69
+ expect(result).toEqual([]);
70
+ });
71
+ });
72
+ describe('isSkillCollection', () => {
73
+ it('returns true for skill names with -skills suffix', () => {
74
+ expect(isSkillCollection('my-skills', '')).toBe(true);
75
+ });
76
+ it('returns true for skill names with -collection suffix', () => {
77
+ expect(isSkillCollection('utils-collection', '')).toBe(true);
78
+ });
79
+ it('returns true for skill names with -pack suffix', () => {
80
+ expect(isSkillCollection('dev-pack', '')).toBe(true);
81
+ });
82
+ it('returns true for skill names with skill-collection', () => {
83
+ expect(isSkillCollection('my-skill-collection', '')).toBe(true);
84
+ });
85
+ it('returns true for skill names with skills-repo', () => {
86
+ expect(isSkillCollection('company-skills-repo', '')).toBe(true);
87
+ });
88
+ it('returns true when description contains collection of skills', () => {
89
+ expect(isSkillCollection('my-tool', 'A collection of useful skills for development')).toBe(true);
90
+ });
91
+ it('returns false for regular skill names', () => {
92
+ expect(isSkillCollection('commit-helper', '')).toBe(false);
93
+ });
94
+ it('returns false for skills with similar but not matching patterns', () => {
95
+ expect(isSkillCollection('skillful-coder', '')).toBe(false);
96
+ });
97
+ it('returns false when description mentions collection but not skill', () => {
98
+ expect(isSkillCollection('my-tool', 'A collection of utilities')).toBe(false);
99
+ });
100
+ it('handles empty inputs', () => {
101
+ expect(isSkillCollection('', '')).toBe(false);
102
+ });
103
+ });
104
+ describe('COLLECTION_PATTERNS', () => {
105
+ it('exports collection patterns constant', () => {
106
+ expect(COLLECTION_PATTERNS).toBeDefined();
107
+ expect(Array.isArray(COLLECTION_PATTERNS)).toBe(true);
108
+ expect(COLLECTION_PATTERNS.length).toBeGreaterThan(0);
109
+ });
110
+ it('contains expected patterns', () => {
111
+ expect(COLLECTION_PATTERNS).toContain('-skills');
112
+ expect(COLLECTION_PATTERNS).toContain('-collection');
113
+ expect(COLLECTION_PATTERNS).toContain('-pack');
114
+ });
115
+ });
116
+ });
117
+ //# sourceMappingURL=recommend-helpers.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"recommend-helpers.test.js","sourceRoot":"","sources":["../../../tests/unit/recommend-helpers.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @fileoverview Tests for validate.helpers.ts
3
+ * @module @skillsmith/mcp-server/tests/unit/validate-helpers
4
+ *
5
+ * SMI-1719: Unit tests for extracted helper functions from Wave 3 refactor
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=validate-helpers.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validate-helpers.test.d.ts","sourceRoot":"","sources":["../../../tests/unit/validate-helpers.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}