@skillsmith/mcp-server 0.4.2 → 0.4.3
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.
- package/README.md +11 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/__tests__/LocalIndexer.test.js +158 -0
- package/dist/src/__tests__/LocalIndexer.test.js.map +1 -1
- package/dist/src/__tests__/compare.test.d.ts +8 -0
- package/dist/src/__tests__/compare.test.d.ts.map +1 -0
- package/dist/src/__tests__/compare.test.js +162 -0
- package/dist/src/__tests__/compare.test.js.map +1 -0
- package/dist/src/__tests__/context.async.test.d.ts +8 -0
- package/dist/src/__tests__/context.async.test.d.ts.map +1 -0
- package/dist/src/__tests__/context.async.test.js +223 -0
- package/dist/src/__tests__/context.async.test.js.map +1 -0
- package/dist/src/__tests__/middleware/errorFormatter.builders.test.d.ts +10 -0
- package/dist/src/__tests__/middleware/errorFormatter.builders.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/errorFormatter.builders.test.js +93 -0
- package/dist/src/__tests__/middleware/errorFormatter.builders.test.js.map +1 -0
- package/dist/src/__tests__/middleware/license-renewal.test.d.ts +10 -0
- package/dist/src/__tests__/middleware/license-renewal.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/license-renewal.test.js +152 -0
- package/dist/src/__tests__/middleware/license-renewal.test.js.map +1 -0
- package/dist/src/__tests__/middleware/quota-helpers.test.d.ts +9 -0
- package/dist/src/__tests__/middleware/quota-helpers.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/quota-helpers.test.js +105 -0
- package/dist/src/__tests__/middleware/quota-helpers.test.js.map +1 -0
- package/dist/src/__tests__/middleware/quota.test.d.ts +12 -0
- package/dist/src/__tests__/middleware/quota.test.d.ts.map +1 -0
- package/dist/src/__tests__/middleware/quota.test.js +189 -0
- package/dist/src/__tests__/middleware/quota.test.js.map +1 -0
- package/dist/src/__tests__/recommend-online-path.test.d.ts +10 -0
- package/dist/src/__tests__/recommend-online-path.test.d.ts.map +1 -0
- package/dist/src/__tests__/recommend-online-path.test.js +225 -0
- package/dist/src/__tests__/recommend-online-path.test.js.map +1 -0
- package/dist/src/__tests__/recommend.test.d.ts +2 -0
- package/dist/src/__tests__/recommend.test.d.ts.map +1 -1
- package/dist/src/__tests__/recommend.test.js +14 -2
- package/dist/src/__tests__/recommend.test.js.map +1 -1
- package/dist/src/__tests__/search-online-path.test.d.ts +10 -0
- package/dist/src/__tests__/search-online-path.test.d.ts.map +1 -0
- package/dist/src/__tests__/search-online-path.test.js +140 -0
- package/dist/src/__tests__/search-online-path.test.js.map +1 -0
- package/dist/src/__tests__/search.test.js +153 -5
- package/dist/src/__tests__/search.test.js.map +1 -1
- package/dist/src/context/project-detector.d.ts.map +1 -1
- package/dist/src/context/project-detector.js +1 -0
- package/dist/src/context/project-detector.js.map +1 -1
- package/dist/src/context.async.d.ts +48 -0
- package/dist/src/context.async.d.ts.map +1 -0
- package/dist/src/context.async.js +215 -0
- package/dist/src/context.async.js.map +1 -0
- package/dist/src/context.d.ts +5 -145
- package/dist/src/context.d.ts.map +1 -1
- package/dist/src/context.helpers.d.ts +25 -0
- package/dist/src/context.helpers.d.ts.map +1 -0
- package/dist/src/context.helpers.js +49 -0
- package/dist/src/context.helpers.js.map +1 -0
- package/dist/src/context.js +11 -228
- package/dist/src/context.js.map +1 -1
- package/dist/src/context.types.d.ts +110 -0
- package/dist/src/context.types.d.ts.map +1 -0
- package/dist/src/context.types.js +10 -0
- package/dist/src/context.types.js.map +1 -0
- package/dist/src/health/readinessCheck.d.ts +1 -1
- package/dist/src/health/readinessCheck.d.ts.map +1 -1
- package/dist/src/index.js +21 -152
- package/dist/src/index.js.map +1 -1
- package/dist/src/indexer/FrontmatterParser.d.ts +6 -0
- package/dist/src/indexer/FrontmatterParser.d.ts.map +1 -1
- package/dist/src/indexer/FrontmatterParser.js +15 -0
- package/dist/src/indexer/FrontmatterParser.js.map +1 -1
- package/dist/src/indexer/LocalIndexer.d.ts +4 -0
- package/dist/src/indexer/LocalIndexer.d.ts.map +1 -1
- package/dist/src/indexer/LocalIndexer.js +3 -0
- package/dist/src/indexer/LocalIndexer.js.map +1 -1
- package/dist/src/middleware/degradation.d.ts.map +1 -1
- package/dist/src/middleware/degradation.js +8 -0
- package/dist/src/middleware/degradation.js.map +1 -1
- package/dist/src/middleware/errorFormatter.builders.d.ts +49 -0
- package/dist/src/middleware/errorFormatter.builders.d.ts.map +1 -0
- package/dist/src/middleware/errorFormatter.builders.js +237 -0
- package/dist/src/middleware/errorFormatter.builders.js.map +1 -0
- package/dist/src/middleware/errorFormatter.d.ts +5 -100
- package/dist/src/middleware/errorFormatter.d.ts.map +1 -1
- package/dist/src/middleware/errorFormatter.js +16 -238
- package/dist/src/middleware/errorFormatter.js.map +1 -1
- package/dist/src/middleware/errorFormatter.types.d.ts +81 -0
- package/dist/src/middleware/errorFormatter.types.d.ts.map +1 -0
- package/dist/src/middleware/errorFormatter.types.js +34 -0
- package/dist/src/middleware/errorFormatter.types.js.map +1 -0
- package/dist/src/middleware/toolFeatureMapping.d.ts +1 -1
- package/dist/src/middleware/toolFeatureMapping.d.ts.map +1 -1
- package/dist/src/middleware/toolFeatureMapping.js +8 -0
- package/dist/src/middleware/toolFeatureMapping.js.map +1 -1
- package/dist/src/tool-dispatch.d.ts +27 -0
- package/dist/src/tool-dispatch.d.ts.map +1 -0
- package/dist/src/tool-dispatch.js +127 -0
- package/dist/src/tool-dispatch.js.map +1 -0
- package/dist/src/tools/LocalSkillSearch.d.ts.map +1 -1
- package/dist/src/tools/LocalSkillSearch.js +4 -0
- package/dist/src/tools/LocalSkillSearch.js.map +1 -1
- package/dist/src/tools/get-skill.d.ts.map +1 -1
- package/dist/src/tools/get-skill.js +14 -0
- package/dist/src/tools/get-skill.js.map +1 -1
- package/dist/src/tools/index.d.ts +6 -0
- package/dist/src/tools/index.d.ts.map +1 -1
- package/dist/src/tools/index.js +6 -0
- package/dist/src/tools/index.js.map +1 -1
- package/dist/src/tools/install.d.ts +3 -35
- package/dist/src/tools/install.d.ts.map +1 -1
- package/dist/src/tools/install.js +22 -74
- package/dist/src/tools/install.js.map +1 -1
- package/dist/src/tools/install.optimize.d.ts +46 -0
- package/dist/src/tools/install.optimize.d.ts.map +1 -0
- package/dist/src/tools/install.optimize.js +67 -0
- package/dist/src/tools/install.optimize.js.map +1 -0
- package/dist/src/tools/install.tool.d.ts +44 -0
- package/dist/src/tools/install.tool.d.ts.map +1 -0
- package/dist/src/tools/install.tool.js +44 -0
- package/dist/src/tools/install.tool.js.map +1 -0
- package/dist/src/tools/install.types.d.ts +7 -1
- package/dist/src/tools/install.types.d.ts.map +1 -1
- package/dist/src/tools/recommend.d.ts +2 -4
- package/dist/src/tools/recommend.d.ts.map +1 -1
- package/dist/src/tools/recommend.format.d.ts +28 -0
- package/dist/src/tools/recommend.format.d.ts.map +1 -0
- package/dist/src/tools/recommend.format.js +111 -0
- package/dist/src/tools/recommend.format.js.map +1 -0
- package/dist/src/tools/recommend.js +6 -97
- package/dist/src/tools/recommend.js.map +1 -1
- package/dist/src/tools/recommend.types.d.ts +1 -1
- package/dist/src/tools/search.d.ts +24 -21
- package/dist/src/tools/search.d.ts.map +1 -1
- package/dist/src/tools/search.formatter.d.ts +30 -0
- package/dist/src/tools/search.formatter.d.ts.map +1 -0
- package/dist/src/tools/search.formatter.js +64 -0
- package/dist/src/tools/search.formatter.js.map +1 -0
- package/dist/src/tools/search.js +55 -54
- package/dist/src/tools/search.js.map +1 -1
- package/dist/src/tools/skill-audit.d.ts +98 -0
- package/dist/src/tools/skill-audit.d.ts.map +1 -0
- package/dist/src/tools/skill-audit.js +105 -0
- package/dist/src/tools/skill-audit.js.map +1 -0
- package/dist/src/tools/skill-audit.test.d.ts +6 -0
- package/dist/src/tools/skill-audit.test.d.ts.map +1 -0
- package/dist/src/tools/skill-audit.test.js +121 -0
- package/dist/src/tools/skill-audit.test.js.map +1 -0
- package/dist/src/tools/skill-diff.d.ts +107 -0
- package/dist/src/tools/skill-diff.d.ts.map +1 -0
- package/dist/src/tools/skill-diff.js +268 -0
- package/dist/src/tools/skill-diff.js.map +1 -0
- package/dist/src/tools/skill-diff.test.d.ts +6 -0
- package/dist/src/tools/skill-diff.test.d.ts.map +1 -0
- package/dist/src/tools/skill-diff.test.js +260 -0
- package/dist/src/tools/skill-diff.test.js.map +1 -0
- package/dist/src/tools/skill-updates.d.ts +1 -1
- package/dist/src/tools/skill-updates.d.ts.map +1 -1
- package/dist/src/tools/suggest.d.ts +4 -4
- package/dist/src/tools/uninstall.d.ts +1 -1
- package/dist/src/tools/validate.helpers.d.ts.map +1 -1
- package/dist/src/tools/validate.helpers.js +31 -0
- package/dist/src/tools/validate.helpers.js.map +1 -1
- package/dist/src/utils/validation.d.ts +13 -0
- package/dist/src/utils/validation.d.ts.map +1 -1
- package/dist/src/utils/validation.js +27 -0
- package/dist/src/utils/validation.js.map +1 -1
- package/dist/tests/health.test.js +4 -4
- package/dist/tests/health.test.js.map +1 -1
- package/dist/tests/integration/recommend.integration.test.js +2 -0
- package/dist/tests/integration/recommend.integration.test.js.map +1 -1
- package/dist/tests/integration/setup.d.ts +3 -1
- package/dist/tests/integration/setup.d.ts.map +1 -1
- package/dist/tests/integration/setup.js +4 -1
- package/dist/tests/integration/setup.js.map +1 -1
- package/dist/tests/recommend.test.js +2 -0
- package/dist/tests/recommend.test.js.map +1 -1
- package/dist/tests/unit/validate-helpers.test.js +54 -0
- package/dist/tests/unit/validate-helpers.test.js.map +1 -1
- package/package.json +2 -2
- package/server.json +2 -2
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2755 Wave 2: Quota helpers pure-function unit tests
|
|
3
|
+
*
|
|
4
|
+
* Tests for InMemoryQuotaStorage, getWarningMessage, and getWarningLevel
|
|
5
|
+
* from quota-helpers.ts. No mocking needed — all pure functions or
|
|
6
|
+
* simple in-memory operations.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect } from 'vitest';
|
|
9
|
+
import { InMemoryQuotaStorage, getWarningMessage, getWarningLevel, } from '../../middleware/quota-helpers.js';
|
|
10
|
+
// ============================================================================
|
|
11
|
+
// InMemoryQuotaStorage
|
|
12
|
+
// ============================================================================
|
|
13
|
+
describe('InMemoryQuotaStorage', () => {
|
|
14
|
+
it('starts with zero usage for a new customer', async () => {
|
|
15
|
+
const storage = new InMemoryQuotaStorage();
|
|
16
|
+
const usage = await storage.getUsage('customer-1');
|
|
17
|
+
expect(usage.used).toBe(0);
|
|
18
|
+
expect(usage.periodStart).toBeInstanceOf(Date);
|
|
19
|
+
expect(usage.periodEnd).toBeInstanceOf(Date);
|
|
20
|
+
expect(usage.periodEnd.getTime()).toBeGreaterThan(usage.periodStart.getTime());
|
|
21
|
+
});
|
|
22
|
+
it('accumulates usage correctly across multiple incrementUsage calls', async () => {
|
|
23
|
+
const storage = new InMemoryQuotaStorage();
|
|
24
|
+
await storage.incrementUsage('customer-a', 1);
|
|
25
|
+
await storage.incrementUsage('customer-a', 1);
|
|
26
|
+
await storage.incrementUsage('customer-a', 3);
|
|
27
|
+
const usage = await storage.getUsage('customer-a');
|
|
28
|
+
expect(usage.used).toBe(5);
|
|
29
|
+
});
|
|
30
|
+
it('tracks usage independently per customer', async () => {
|
|
31
|
+
const storage = new InMemoryQuotaStorage();
|
|
32
|
+
await storage.incrementUsage('cust-x', 10);
|
|
33
|
+
await storage.incrementUsage('cust-y', 5);
|
|
34
|
+
const usageX = await storage.getUsage('cust-x');
|
|
35
|
+
const usageY = await storage.getUsage('cust-y');
|
|
36
|
+
expect(usageX.used).toBe(10);
|
|
37
|
+
expect(usageY.used).toBe(5);
|
|
38
|
+
});
|
|
39
|
+
it('resets usage after initializePeriod', async () => {
|
|
40
|
+
const storage = new InMemoryQuotaStorage();
|
|
41
|
+
await storage.incrementUsage('cust-z', 50);
|
|
42
|
+
await storage.initializePeriod('cust-z', 1000);
|
|
43
|
+
const usage = await storage.getUsage('cust-z');
|
|
44
|
+
expect(usage.used).toBe(0);
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
// ============================================================================
|
|
48
|
+
// getWarningMessage()
|
|
49
|
+
// ============================================================================
|
|
50
|
+
describe('getWarningMessage()', () => {
|
|
51
|
+
it('returns undefined for warning level 0 (no warning)', () => {
|
|
52
|
+
const msg = getWarningMessage(0, 100, 1000, 'community');
|
|
53
|
+
expect(msg).toBeUndefined();
|
|
54
|
+
});
|
|
55
|
+
it('returns 80% notice string for warning level 80', () => {
|
|
56
|
+
const msg = getWarningMessage(80, 800, 1000, 'community');
|
|
57
|
+
expect(msg).toBeDefined();
|
|
58
|
+
expect(msg).toContain('80%');
|
|
59
|
+
expect(msg).toContain('200'); // 200 remaining
|
|
60
|
+
});
|
|
61
|
+
it('returns 90% warning string for warning level 90', () => {
|
|
62
|
+
const msg = getWarningMessage(90, 900, 1000, 'community');
|
|
63
|
+
expect(msg).toBeDefined();
|
|
64
|
+
expect(msg).toContain('90%');
|
|
65
|
+
expect(msg).toContain('100'); // 100 remaining
|
|
66
|
+
expect(msg).toContain('upgrading');
|
|
67
|
+
});
|
|
68
|
+
it('returns exceeded string for warning level 100', () => {
|
|
69
|
+
const msg = getWarningMessage(100, 1000, 1000, 'community');
|
|
70
|
+
expect(msg).toBeDefined();
|
|
71
|
+
expect(msg).toContain('exceeded');
|
|
72
|
+
expect(msg).toContain('Upgrade');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
// ============================================================================
|
|
76
|
+
// getWarningLevel()
|
|
77
|
+
// ============================================================================
|
|
78
|
+
describe('getWarningLevel()', () => {
|
|
79
|
+
it('returns 0 for usage below 80%', () => {
|
|
80
|
+
expect(getWarningLevel(0)).toBe(0);
|
|
81
|
+
expect(getWarningLevel(50)).toBe(0);
|
|
82
|
+
expect(getWarningLevel(79.9)).toBe(0);
|
|
83
|
+
});
|
|
84
|
+
it('returns 80 for usage at exactly 80%', () => {
|
|
85
|
+
expect(getWarningLevel(80)).toBe(80);
|
|
86
|
+
});
|
|
87
|
+
it('returns 80 for usage between 80% and 90%', () => {
|
|
88
|
+
expect(getWarningLevel(85)).toBe(80);
|
|
89
|
+
expect(getWarningLevel(89.9)).toBe(80);
|
|
90
|
+
});
|
|
91
|
+
it('returns 90 for usage at exactly 90%', () => {
|
|
92
|
+
expect(getWarningLevel(90)).toBe(90);
|
|
93
|
+
});
|
|
94
|
+
it('returns 90 for usage between 90% and 100%', () => {
|
|
95
|
+
expect(getWarningLevel(95)).toBe(90);
|
|
96
|
+
expect(getWarningLevel(99.9)).toBe(90);
|
|
97
|
+
});
|
|
98
|
+
it('returns 100 for usage at exactly 100%', () => {
|
|
99
|
+
expect(getWarningLevel(100)).toBe(100);
|
|
100
|
+
});
|
|
101
|
+
it('returns 100 for usage above 100% (over-quota)', () => {
|
|
102
|
+
expect(getWarningLevel(110)).toBe(100);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
//# sourceMappingURL=quota-helpers.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota-helpers.test.js","sourceRoot":"","sources":["../../../../src/__tests__/middleware/quota-helpers.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2755 Wave 2: Quota middleware unit tests
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: This file uses ONLY beforeEach/afterEach-scoped spies.
|
|
5
|
+
* There are NO module-level vi.mock() calls here to avoid conflicts
|
|
6
|
+
* with the existing quota-wiring.test.ts file that lives in the same
|
|
7
|
+
* directory and uses the same modules.
|
|
8
|
+
*
|
|
9
|
+
* @see packages/mcp-server/src/__tests__/middleware/quota-wiring.test.ts
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=quota.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota.test.d.ts","sourceRoot":"","sources":["../../../../src/__tests__/middleware/quota.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2755 Wave 2: Quota middleware unit tests
|
|
3
|
+
*
|
|
4
|
+
* CRITICAL: This file uses ONLY beforeEach/afterEach-scoped spies.
|
|
5
|
+
* There are NO module-level vi.mock() calls here to avoid conflicts
|
|
6
|
+
* with the existing quota-wiring.test.ts file that lives in the same
|
|
7
|
+
* directory and uses the same modules.
|
|
8
|
+
*
|
|
9
|
+
* @see packages/mcp-server/src/__tests__/middleware/quota-wiring.test.ts
|
|
10
|
+
*/
|
|
11
|
+
import { describe, it, expect, afterEach, vi } from 'vitest';
|
|
12
|
+
import { createQuotaMiddleware, withQuotaEnforcement, isUnlimitedTier, getQuotaLimit, formatQuotaRemaining, } from '../../middleware/quota.js';
|
|
13
|
+
import { createLicenseMiddleware } from '../../middleware/license.js';
|
|
14
|
+
// ============================================================================
|
|
15
|
+
// Shared test helpers
|
|
16
|
+
// ============================================================================
|
|
17
|
+
function makeStorage(used) {
|
|
18
|
+
return {
|
|
19
|
+
getUsage: vi.fn().mockResolvedValue({
|
|
20
|
+
used,
|
|
21
|
+
periodStart: new Date(),
|
|
22
|
+
periodEnd: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
|
|
23
|
+
}),
|
|
24
|
+
incrementUsage: vi.fn().mockResolvedValue(undefined),
|
|
25
|
+
initializePeriod: vi.fn().mockResolvedValue(undefined),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
function makeLicenseInfo(tier) {
|
|
29
|
+
return {
|
|
30
|
+
valid: true,
|
|
31
|
+
tier,
|
|
32
|
+
features: [],
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// getStatus()
|
|
37
|
+
// ============================================================================
|
|
38
|
+
describe('createQuotaMiddleware - getStatus()', () => {
|
|
39
|
+
afterEach(() => {
|
|
40
|
+
vi.restoreAllMocks();
|
|
41
|
+
});
|
|
42
|
+
it('returns unlimited result for enterprise tier', async () => {
|
|
43
|
+
const quota = createQuotaMiddleware();
|
|
44
|
+
const licenseInfo = makeLicenseInfo('enterprise');
|
|
45
|
+
const status = await quota.getStatus(licenseInfo);
|
|
46
|
+
expect(status.allowed).toBe(true);
|
|
47
|
+
expect(status.remaining).toBe(-1);
|
|
48
|
+
expect(status.limit).toBe(-1);
|
|
49
|
+
expect(status.percentUsed).toBe(0);
|
|
50
|
+
expect(status.warningLevel).toBe(0);
|
|
51
|
+
});
|
|
52
|
+
it('returns 0% usage for community tier at zero calls', async () => {
|
|
53
|
+
const storage = makeStorage(0);
|
|
54
|
+
const quota = createQuotaMiddleware({ storage });
|
|
55
|
+
const licenseInfo = makeLicenseInfo('community');
|
|
56
|
+
const status = await quota.getStatus(licenseInfo);
|
|
57
|
+
expect(status.allowed).toBe(true);
|
|
58
|
+
expect(status.limit).toBe(1_000);
|
|
59
|
+
expect(status.remaining).toBe(1_000);
|
|
60
|
+
expect(status.percentUsed).toBe(0);
|
|
61
|
+
expect(status.warningLevel).toBe(0);
|
|
62
|
+
expect(status.message).toBeUndefined();
|
|
63
|
+
});
|
|
64
|
+
it('returns 80% warning for community tier at 800/1000 calls', async () => {
|
|
65
|
+
const storage = makeStorage(800);
|
|
66
|
+
const quota = createQuotaMiddleware({ storage });
|
|
67
|
+
const licenseInfo = makeLicenseInfo('community');
|
|
68
|
+
const status = await quota.getStatus(licenseInfo);
|
|
69
|
+
expect(status.allowed).toBe(true);
|
|
70
|
+
expect(status.percentUsed).toBe(80);
|
|
71
|
+
expect(status.warningLevel).toBe(80);
|
|
72
|
+
expect(status.message).toContain('80%');
|
|
73
|
+
});
|
|
74
|
+
it('returns 90% warning for community tier at 900/1000 calls', async () => {
|
|
75
|
+
const storage = makeStorage(900);
|
|
76
|
+
const quota = createQuotaMiddleware({ storage });
|
|
77
|
+
const licenseInfo = makeLicenseInfo('community');
|
|
78
|
+
const status = await quota.getStatus(licenseInfo);
|
|
79
|
+
expect(status.allowed).toBe(true);
|
|
80
|
+
expect(status.percentUsed).toBe(90);
|
|
81
|
+
expect(status.warningLevel).toBe(90);
|
|
82
|
+
expect(status.message).toContain('90%');
|
|
83
|
+
expect(status.upgradeUrl).toBeDefined();
|
|
84
|
+
});
|
|
85
|
+
it('returns not-allowed for community tier at 1000/1000 calls (quota exhausted)', async () => {
|
|
86
|
+
const storage = makeStorage(1000);
|
|
87
|
+
const quota = createQuotaMiddleware({ storage });
|
|
88
|
+
const licenseInfo = makeLicenseInfo('community');
|
|
89
|
+
const status = await quota.getStatus(licenseInfo);
|
|
90
|
+
expect(status.allowed).toBe(false);
|
|
91
|
+
expect(status.remaining).toBe(0);
|
|
92
|
+
expect(status.percentUsed).toBe(100);
|
|
93
|
+
});
|
|
94
|
+
it('works with null licenseInfo (defaults to community tier)', async () => {
|
|
95
|
+
const storage = makeStorage(0);
|
|
96
|
+
const quota = createQuotaMiddleware({ storage });
|
|
97
|
+
const status = await quota.getStatus(null);
|
|
98
|
+
expect(status.limit).toBe(1_000);
|
|
99
|
+
expect(status.allowed).toBe(true);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// withQuotaEnforcement()
|
|
104
|
+
// ============================================================================
|
|
105
|
+
describe('withQuotaEnforcement()', () => {
|
|
106
|
+
afterEach(() => {
|
|
107
|
+
vi.restoreAllMocks();
|
|
108
|
+
});
|
|
109
|
+
it('delegates to inner handler when quota allows', async () => {
|
|
110
|
+
const storage = makeStorage(0);
|
|
111
|
+
const quota = createQuotaMiddleware({ storage });
|
|
112
|
+
const license = createLicenseMiddleware();
|
|
113
|
+
const innerHandler = vi.fn().mockResolvedValue({ result: 'success' });
|
|
114
|
+
const wrapped = withQuotaEnforcement(innerHandler, license, quota);
|
|
115
|
+
const result = await wrapped('skill_search', { query: 'commit' });
|
|
116
|
+
expect(innerHandler).toHaveBeenCalledTimes(1);
|
|
117
|
+
expect(innerHandler).toHaveBeenCalledWith({ query: 'commit' });
|
|
118
|
+
expect(result).toEqual({ result: 'success' });
|
|
119
|
+
});
|
|
120
|
+
it('returns quota-exceeded error response when quota is exceeded', async () => {
|
|
121
|
+
const exhaustedStorage = makeStorage(1000); // community limit = 1000
|
|
122
|
+
const quota = createQuotaMiddleware({ storage: exhaustedStorage });
|
|
123
|
+
const license = createLicenseMiddleware();
|
|
124
|
+
const innerHandler = vi.fn().mockResolvedValue({ result: 'should not reach here' });
|
|
125
|
+
const wrapped = withQuotaEnforcement(innerHandler, license, quota);
|
|
126
|
+
const result = await wrapped('skill_search', { query: 'commit' });
|
|
127
|
+
// Inner handler should NOT have been called
|
|
128
|
+
expect(innerHandler).not.toHaveBeenCalled();
|
|
129
|
+
// Result must be MCP error response shape
|
|
130
|
+
expect(result).toMatchObject({ isError: true });
|
|
131
|
+
const errorResult = result;
|
|
132
|
+
expect(errorResult.isError).toBe(true);
|
|
133
|
+
expect(Array.isArray(errorResult.content)).toBe(true);
|
|
134
|
+
expect(errorResult.content[0].type).toBe('text');
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
// ============================================================================
|
|
138
|
+
// isUnlimitedTier()
|
|
139
|
+
// ============================================================================
|
|
140
|
+
describe('isUnlimitedTier()', () => {
|
|
141
|
+
it('returns true for enterprise tier', () => {
|
|
142
|
+
expect(isUnlimitedTier('enterprise')).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
it('returns false for community tier', () => {
|
|
145
|
+
expect(isUnlimitedTier('community')).toBe(false);
|
|
146
|
+
});
|
|
147
|
+
it('returns false for individual tier', () => {
|
|
148
|
+
expect(isUnlimitedTier('individual')).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
it('returns false for team tier', () => {
|
|
151
|
+
expect(isUnlimitedTier('team')).toBe(false);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
// ============================================================================
|
|
155
|
+
// getQuotaLimit()
|
|
156
|
+
// ============================================================================
|
|
157
|
+
describe('getQuotaLimit()', () => {
|
|
158
|
+
it('returns 1000 for community tier', () => {
|
|
159
|
+
expect(getQuotaLimit('community')).toBe(1_000);
|
|
160
|
+
});
|
|
161
|
+
it('returns 10000 for individual tier', () => {
|
|
162
|
+
expect(getQuotaLimit('individual')).toBe(10_000);
|
|
163
|
+
});
|
|
164
|
+
it('returns 100000 for team tier', () => {
|
|
165
|
+
expect(getQuotaLimit('team')).toBe(100_000);
|
|
166
|
+
});
|
|
167
|
+
it('returns -1 for enterprise tier (unlimited)', () => {
|
|
168
|
+
expect(getQuotaLimit('enterprise')).toBe(-1);
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
// ============================================================================
|
|
172
|
+
// formatQuotaRemaining()
|
|
173
|
+
// ============================================================================
|
|
174
|
+
describe('formatQuotaRemaining()', () => {
|
|
175
|
+
it('returns "Unlimited" when limit is -1', () => {
|
|
176
|
+
expect(formatQuotaRemaining(-1, -1)).toBe('Unlimited');
|
|
177
|
+
});
|
|
178
|
+
it('formats remaining / limit for finite limits', () => {
|
|
179
|
+
const result = formatQuotaRemaining(750, 1000);
|
|
180
|
+
expect(result).toContain('750');
|
|
181
|
+
expect(result).toContain('1,000');
|
|
182
|
+
});
|
|
183
|
+
it('formats zero remaining correctly', () => {
|
|
184
|
+
const result = formatQuotaRemaining(0, 1000);
|
|
185
|
+
expect(result).toContain('0');
|
|
186
|
+
expect(result).toContain('1,000');
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
//# sourceMappingURL=quota.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"quota.test.js","sourceRoot":"","sources":["../../../../src/__tests__/middleware/quota.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2755 Wave 2: Online API path tests for executeRecommend
|
|
3
|
+
*
|
|
4
|
+
* Tests the branch where context.apiClient.isOffline() returns false,
|
|
5
|
+
* exercising the Promise.allSettled(API + local) merge path.
|
|
6
|
+
*
|
|
7
|
+
* Split from recommend.test.ts to keep each file under 500 lines.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=recommend-online-path.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommend-online-path.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/recommend-online-path.test.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG"}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-2755 Wave 2: Online API path tests for executeRecommend
|
|
3
|
+
*
|
|
4
|
+
* Tests the branch where context.apiClient.isOffline() returns false,
|
|
5
|
+
* exercising the Promise.allSettled(API + local) merge path.
|
|
6
|
+
*
|
|
7
|
+
* Split from recommend.test.ts to keep each file under 500 lines.
|
|
8
|
+
*/
|
|
9
|
+
import { describe, it, expect, beforeAll, afterAll, vi, beforeEach, afterEach } from 'vitest';
|
|
10
|
+
import { executeRecommend } from '../tools/recommend.js';
|
|
11
|
+
import { createTestContext } from './test-utils.js';
|
|
12
|
+
import * as LocalSkillSearchModule from '../tools/LocalSkillSearch.js';
|
|
13
|
+
import * as CoreModule from '@skillsmith/core';
|
|
14
|
+
/**
|
|
15
|
+
* SMI-2755 Wave 2: Online API path tests for executeRecommend
|
|
16
|
+
*
|
|
17
|
+
* Tests the branch where context.apiClient.isOffline() returns false,
|
|
18
|
+
* exercising the Promise.allSettled(API + local) merge path.
|
|
19
|
+
*/
|
|
20
|
+
describe('Recommend Tool - Online API Path (SMI-2755)', () => {
|
|
21
|
+
let onlineContext;
|
|
22
|
+
const mockLocalSkills = [
|
|
23
|
+
{
|
|
24
|
+
id: 'local/my-tool',
|
|
25
|
+
name: 'my-tool',
|
|
26
|
+
description: 'A local tool',
|
|
27
|
+
author: 'local',
|
|
28
|
+
tags: ['productivity'],
|
|
29
|
+
qualityScore: 70,
|
|
30
|
+
trustTier: 'local',
|
|
31
|
+
source: 'local',
|
|
32
|
+
path: '/home/user/.claude/skills/my-tool',
|
|
33
|
+
hasSkillMd: true,
|
|
34
|
+
lastModified: new Date().toISOString(),
|
|
35
|
+
repository: null,
|
|
36
|
+
},
|
|
37
|
+
];
|
|
38
|
+
beforeAll(() => {
|
|
39
|
+
onlineContext = createTestContext();
|
|
40
|
+
});
|
|
41
|
+
afterAll(() => {
|
|
42
|
+
onlineContext.db.close();
|
|
43
|
+
});
|
|
44
|
+
beforeEach(() => {
|
|
45
|
+
// Mock local indexer returning a single skill
|
|
46
|
+
// Partial mock — only methods called by executeRecommend are implemented
|
|
47
|
+
vi.spyOn(LocalSkillSearchModule, 'getLocalIndexer').mockReturnValue({
|
|
48
|
+
index: vi.fn().mockResolvedValue(mockLocalSkills),
|
|
49
|
+
indexSync: vi.fn().mockReturnValue(mockLocalSkills),
|
|
50
|
+
search: vi.fn().mockReturnValue(mockLocalSkills),
|
|
51
|
+
clearCache: vi.fn(),
|
|
52
|
+
getSkillsDir: vi.fn().mockReturnValue('/home/user/.claude/skills'),
|
|
53
|
+
calculateQualityScore: vi.fn().mockReturnValue(70),
|
|
54
|
+
indexSkillDir: vi.fn(),
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
afterEach(() => {
|
|
58
|
+
vi.restoreAllMocks();
|
|
59
|
+
});
|
|
60
|
+
it('takes the online path when isOffline() returns false', async () => {
|
|
61
|
+
// Spy on isOffline to return false and getRecommendations to return controlled data
|
|
62
|
+
vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
|
|
63
|
+
vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
|
|
64
|
+
data: [
|
|
65
|
+
{
|
|
66
|
+
id: 'community/jest-helper',
|
|
67
|
+
name: 'jest-helper',
|
|
68
|
+
description: 'Jest test helper',
|
|
69
|
+
author: 'community',
|
|
70
|
+
tags: ['testing'],
|
|
71
|
+
trust_tier: 'community',
|
|
72
|
+
quality_score: 0.87,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
meta: { total: 1 },
|
|
76
|
+
});
|
|
77
|
+
const result = await executeRecommend({ project_context: 'testing', limit: 5 }, onlineContext);
|
|
78
|
+
expect(result.recommendations).toBeDefined();
|
|
79
|
+
expect(Array.isArray(result.recommendations)).toBe(true);
|
|
80
|
+
expect(onlineContext.apiClient.getRecommendations).toHaveBeenCalledTimes(1);
|
|
81
|
+
});
|
|
82
|
+
it('merges API results with local results and deduplicates by skill_id', async () => {
|
|
83
|
+
vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
|
|
84
|
+
vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
|
|
85
|
+
data: [
|
|
86
|
+
{
|
|
87
|
+
id: 'community/jest-helper',
|
|
88
|
+
name: 'jest-helper',
|
|
89
|
+
description: 'Jest helper',
|
|
90
|
+
author: 'community',
|
|
91
|
+
tags: ['testing'],
|
|
92
|
+
trust_tier: 'community',
|
|
93
|
+
quality_score: 0.87,
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'anthropic/commit',
|
|
97
|
+
name: 'commit',
|
|
98
|
+
description: 'Commit helper',
|
|
99
|
+
author: 'anthropic',
|
|
100
|
+
tags: ['git'],
|
|
101
|
+
trust_tier: 'verified',
|
|
102
|
+
quality_score: 0.95,
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
meta: { total: 2 },
|
|
106
|
+
});
|
|
107
|
+
const result = await executeRecommend({ project_context: 'git workflow', limit: 10 }, onlineContext);
|
|
108
|
+
// No duplicate skill IDs
|
|
109
|
+
const ids = result.recommendations.map((r) => r.skill_id);
|
|
110
|
+
const uniqueIds = new Set(ids);
|
|
111
|
+
expect(ids.length).toBe(uniqueIds.size);
|
|
112
|
+
});
|
|
113
|
+
it('falls back to local-only results when API call fails', async () => {
|
|
114
|
+
vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
|
|
115
|
+
vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockRejectedValue(new Error('Network error'));
|
|
116
|
+
// Should not throw — falls back gracefully
|
|
117
|
+
const result = await executeRecommend({ project_context: 'testing', limit: 5 }, onlineContext);
|
|
118
|
+
expect(result.recommendations).toBeDefined();
|
|
119
|
+
expect(Array.isArray(result.recommendations)).toBe(true);
|
|
120
|
+
});
|
|
121
|
+
it('applies role filter and +30 score boost for matched roles in online path', async () => {
|
|
122
|
+
expect.hasAssertions();
|
|
123
|
+
vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
|
|
124
|
+
vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
|
|
125
|
+
data: [
|
|
126
|
+
{
|
|
127
|
+
id: 'community/jest-helper',
|
|
128
|
+
name: 'jest-helper',
|
|
129
|
+
description: 'Jest helper',
|
|
130
|
+
author: 'community',
|
|
131
|
+
tags: ['testing'],
|
|
132
|
+
trust_tier: 'community',
|
|
133
|
+
quality_score: 0.5,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
meta: { total: 1 },
|
|
137
|
+
});
|
|
138
|
+
const result = await executeRecommend({ project_context: 'testing', role: 'testing', limit: 5 }, onlineContext);
|
|
139
|
+
expect(result.context.role_filter).toBe('testing');
|
|
140
|
+
// If any recommendation survived the role filter, its score should be boosted
|
|
141
|
+
result.recommendations.forEach((rec) => {
|
|
142
|
+
if (rec.roles?.includes('testing')) {
|
|
143
|
+
expect(rec.quality_score).toBeGreaterThanOrEqual(50 + 30);
|
|
144
|
+
expect(rec.reason).toContain('role: testing');
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
it('calls trackEvent when context.distinctId is set', async () => {
|
|
149
|
+
const trackEventSpy = vi.spyOn(CoreModule, 'trackEvent').mockImplementation(() => { });
|
|
150
|
+
vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
|
|
151
|
+
vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
|
|
152
|
+
data: [],
|
|
153
|
+
meta: { total: 0 },
|
|
154
|
+
});
|
|
155
|
+
const contextWithId = { ...onlineContext, distinctId: 'test-user-123' };
|
|
156
|
+
await executeRecommend({ project_context: 'testing', limit: 5 }, contextWithId);
|
|
157
|
+
expect(trackEventSpy).toHaveBeenCalledWith('test-user-123', 'skill_recommend', expect.objectContaining({ source: 'mcp' }));
|
|
158
|
+
});
|
|
159
|
+
it('does not call trackEvent when context.distinctId is absent', async () => {
|
|
160
|
+
const trackEventSpy = vi.spyOn(CoreModule, 'trackEvent').mockImplementation(() => { });
|
|
161
|
+
vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
|
|
162
|
+
vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
|
|
163
|
+
data: [],
|
|
164
|
+
meta: { total: 0 },
|
|
165
|
+
});
|
|
166
|
+
// onlineContext has no distinctId (createTestContext doesn't set one)
|
|
167
|
+
await executeRecommend({ project_context: 'testing', limit: 5 }, onlineContext);
|
|
168
|
+
expect(trackEventSpy).not.toHaveBeenCalled();
|
|
169
|
+
});
|
|
170
|
+
it('includes local skill results in candidates_considered count', async () => {
|
|
171
|
+
vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
|
|
172
|
+
vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
|
|
173
|
+
data: [
|
|
174
|
+
{
|
|
175
|
+
id: 'community/docker-compose',
|
|
176
|
+
name: 'docker-compose',
|
|
177
|
+
description: 'Docker helper',
|
|
178
|
+
author: 'community',
|
|
179
|
+
tags: ['devops'],
|
|
180
|
+
trust_tier: 'community',
|
|
181
|
+
quality_score: 0.84,
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
meta: { total: 1 },
|
|
185
|
+
});
|
|
186
|
+
const result = await executeRecommend({ project_context: 'devops', limit: 5 }, onlineContext);
|
|
187
|
+
// candidates_considered = API results + local results
|
|
188
|
+
// Local indexer returns 1 skill (mockLocalSkills has 1 item)
|
|
189
|
+
expect(result.candidates_considered).toBeGreaterThanOrEqual(1);
|
|
190
|
+
});
|
|
191
|
+
it('handles API returning empty data gracefully', async () => {
|
|
192
|
+
vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
|
|
193
|
+
vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
|
|
194
|
+
data: [],
|
|
195
|
+
meta: { total: 0 },
|
|
196
|
+
});
|
|
197
|
+
const result = await executeRecommend({ project_context: 'anything', limit: 5 }, onlineContext);
|
|
198
|
+
expect(result.recommendations).toBeDefined();
|
|
199
|
+
// Local skill (my-tool) may appear in results
|
|
200
|
+
expect(Array.isArray(result.recommendations)).toBe(true);
|
|
201
|
+
});
|
|
202
|
+
it('combines online + role filter + tracking simultaneously', async () => {
|
|
203
|
+
const trackEventSpy = vi.spyOn(CoreModule, 'trackEvent').mockImplementation(() => { });
|
|
204
|
+
vi.spyOn(onlineContext.apiClient, 'isOffline').mockReturnValue(false);
|
|
205
|
+
vi.spyOn(onlineContext.apiClient, 'getRecommendations').mockResolvedValue({
|
|
206
|
+
data: [
|
|
207
|
+
{
|
|
208
|
+
id: 'community/jest-helper',
|
|
209
|
+
name: 'jest-helper',
|
|
210
|
+
description: 'Jest test helper',
|
|
211
|
+
author: 'community',
|
|
212
|
+
tags: ['testing'],
|
|
213
|
+
trust_tier: 'community',
|
|
214
|
+
quality_score: 0.6,
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
meta: { total: 1 },
|
|
218
|
+
});
|
|
219
|
+
const contextWithId = { ...onlineContext, distinctId: 'combined-test-user' };
|
|
220
|
+
const result = await executeRecommend({ project_context: 'testing', role: 'testing', limit: 5 }, contextWithId);
|
|
221
|
+
expect(result.context.role_filter).toBe('testing');
|
|
222
|
+
expect(trackEventSpy).toHaveBeenCalledWith('combined-test-user', 'skill_recommend', expect.objectContaining({ source: 'mcp' }));
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
//# sourceMappingURL=recommend-online-path.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recommend-online-path.test.js","sourceRoot":"","sources":["../../../src/__tests__/recommend-online-path.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
* Tests for SMI-1837: Include Local Skills in Recommendations
|
|
3
3
|
* Verifies that local skills are searched in parallel with the API,
|
|
4
4
|
* not just as a fallback.
|
|
5
|
+
*
|
|
6
|
+
* SMI-2755: Online API path tests split to recommend-online-path.test.ts.
|
|
5
7
|
*/
|
|
6
8
|
export {};
|
|
7
9
|
//# sourceMappingURL=recommend.test.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recommend.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/recommend.test.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"recommend.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/recommend.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|