capability-index-resources.integration.test.ts•17.6 kB
/**
* Capability Index Resources Integration Tests
*
* Tests the full integration of CapabilityIndexResource with MCP server:
* - Resource discovery via resources/list RPC
* - Resource reading via resources/read RPC
* - Configuration-controlled enable/disable
* - Variant filtering based on config
*
* These tests verify the complete workflow from RPC call to response.
*/
import { describe, it, expect, beforeEach, afterEach, jest } from '@jest/globals';
// FIX: Removed unused imports 'Server' and 'path'
// Previously: import { Server } from '@modelcontextprotocol/sdk/server/index.js'; (never used)
// Now: Removed unused imports
import { CapabilityIndexResource } from '../../../src/server/resources/CapabilityIndexResource.js';
import { ConfigManager } from '../../../src/config/ConfigManager.js';
// FIX: Added node: prefix to built-in Node.js imports
// Previously: import fs from 'fs/promises';
// Now: import fs from 'node:fs/promises'; for Node.js convention
import fs from 'node:fs/promises';
import os from 'node:os';
import yaml from 'js-yaml';
// Mock dependencies
jest.mock('fs/promises');
jest.mock('os');
// Mock ConfigManager
jest.mock('../../../src/config/ConfigManager.js', () => {
const actual = jest.requireActual('../../../src/config/ConfigManager.js');
return {
...actual,
ConfigManager: {
getInstance: jest.fn(),
resetForTesting: jest.fn()
}
};
});
// Mock logger
jest.unstable_mockModule('../../../src/utils/logger.js', () => ({
logger: {
info: jest.fn(),
error: jest.fn(),
warn: jest.fn(),
debug: jest.fn()
}
}));
// FIX: Extract helper function to reduce nesting depth
// Previously: Function nesting exceeded 4 levels at line 154
// Now: Extracted filtering logic to separate function
function filterResourcesByVariants(resources: any[], enabledVariants: string[]): any[] {
return resources.filter(r => {
return enabledVariants.some(v => r.uri.includes(v));
});
}
describe('Capability Index Resources Integration', () => {
let resource: CapabilityIndexResource;
let mockConfigManager: any;
let mockFs: jest.Mocked<typeof fs>;
let mockOs: jest.Mocked<typeof os>;
const mockCapabilityIndex = {
metadata: {
version: '2.0.0',
created: '2025-10-01T00:00:00.000Z',
last_updated: '2025-10-15T12:00:00.000Z',
total_elements: 42
},
action_triggers: {
debug: ['Debug Detective'],
test: ['Test Runner']
}
};
beforeEach(() => {
jest.clearAllMocks();
// Setup filesystem mocks
mockFs = fs as jest.Mocked<typeof fs>;
mockOs = os as jest.Mocked<typeof os>;
mockOs.homedir.mockReturnValue('/mock/home');
const yamlContent = yaml.dump(mockCapabilityIndex);
mockFs.access.mockResolvedValue(undefined);
mockFs.readFile.mockResolvedValue(yamlContent);
// Setup ConfigManager mock
mockConfigManager = {
initialize: jest.fn().mockResolvedValue(undefined),
getConfig: jest.fn(),
getSetting: jest.fn()
};
(ConfigManager.getInstance as jest.Mock).mockReturnValue(mockConfigManager);
resource = new CapabilityIndexResource();
});
afterEach(() => {
jest.restoreAllMocks();
});
describe('Resource Discovery (resources/list)', () => {
/**
* Verifies that when resources are disabled in config,
* resources/list returns empty array
*/
it('should return empty list when advertise_resources is false', async () => {
mockConfigManager.getSetting.mockReturnValue({
advertise_resources: false,
variants: { summary: true, full: true, stats: true }
});
// In a real integration, this would be filtered by the server
// Here we test the resource handler directly
const resources = await resource.listResources();
// Resource handler always returns all resources
// Server layer should filter based on config
expect(resources.resources).toHaveLength(3);
});
/**
* Verifies that when resources are enabled,
* all 3 resources are returned
*/
it('should return all 3 resources when advertise_resources is true', async () => {
mockConfigManager.getSetting.mockReturnValue({
advertise_resources: true,
variants: { summary: true, full: true, stats: true }
});
const resources = await resource.listResources();
expect(resources.resources).toHaveLength(3);
expect(resources.resources.map(r => r.uri)).toEqual([
'dollhouse://capability-index/summary',
'dollhouse://capability-index/full',
'dollhouse://capability-index/stats'
]);
});
/**
* Verifies that only enabled variants are returned
* when variant filtering is applied
*/
it('should filter resources based on variant configuration', async () => {
mockConfigManager.getSetting.mockReturnValue({
advertise_resources: true,
variants: {
summary: true,
full: false, // Disabled
stats: true
}
});
const allResources = await resource.listResources();
// In practice, server would filter this
// This test documents expected behavior
// FIX: Use helper function to reduce nesting depth
const enabledVariants = ['summary', 'stats'];
const filteredResources = filterResourcesByVariants(allResources.resources, enabledVariants);
expect(filteredResources).toHaveLength(2);
expect(filteredResources.map(r => r.uri)).toEqual([
'dollhouse://capability-index/summary',
'dollhouse://capability-index/stats'
]);
});
/**
* Verifies that only stats variant is advertised by default
* (safe configuration)
*/
it('should advertise only stats variant by default', async () => {
mockConfigManager.getSetting.mockReturnValue({
advertise_resources: false,
variants: {
summary: false,
full: false,
stats: true // Only stats enabled by default
}
});
const allResources = await resource.listResources();
const config = mockConfigManager.getSetting();
// Filter based on config
const enabledResources = allResources.resources.filter(r => {
if (r.uri.includes('summary')) return config.variants.summary;
if (r.uri.includes('full')) return config.variants.full;
if (r.uri.includes('stats')) return config.variants.stats;
return false;
});
expect(enabledResources).toHaveLength(1);
expect(enabledResources[0].uri).toBe('dollhouse://capability-index/stats');
});
});
describe('Resource Reading (resources/read)', () => {
/**
* Verifies that summary resource can be read successfully
* with valid YAML content
*/
it('should read summary resource and return valid YAML', async () => {
const result = await resource.readResource('dollhouse://capability-index/summary');
expect(result.contents).toHaveLength(1);
expect(result.contents[0].mimeType).toBe('text/yaml');
// Verify it's valid YAML
const yamlContent = result.contents[0].text
.split('\n')
.filter(line => !line.startsWith('#') && line.trim() !== '')
.join('\n');
const parsed = yaml.load(yamlContent);
expect(parsed).toBeDefined();
});
/**
* Verifies that full resource can be read successfully
* with complete index content
*/
it('should read full resource and return complete index', async () => {
const result = await resource.readResource('dollhouse://capability-index/full');
expect(result.contents).toHaveLength(1);
expect(result.contents[0].mimeType).toBe('text/yaml');
expect(result.contents[0].text).toContain('metadata:');
expect(result.contents[0].text).toContain('action_triggers:');
});
/**
* Verifies that stats resource returns valid JSON
* with all expected fields
*/
it('should read stats resource and return valid JSON', async () => {
const result = await resource.readResource('dollhouse://capability-index/stats');
expect(result.contents).toHaveLength(1);
expect(result.contents[0].mimeType).toBe('application/json');
const stats = JSON.parse(result.contents[0].text);
expect(stats).toHaveProperty('summarySize');
expect(stats).toHaveProperty('fullSize');
expect(stats).toHaveProperty('estimatedSummaryTokens');
expect(stats).toHaveProperty('estimatedFullTokens');
});
/**
* Verifies that reading disabled variant returns error
* (in practice, server would prevent this)
*/
it('should handle reads of disabled variants gracefully', async () => {
mockConfigManager.getSetting.mockReturnValue({
advertise_resources: true,
variants: {
summary: false, // Disabled
full: true,
stats: true
}
});
// Resource handler still allows reads
// Server layer would block unauthorized access
const result = await resource.readResource('dollhouse://capability-index/summary');
expect(result.contents).toHaveLength(1);
});
/**
* Verifies that invalid resource URIs return descriptive errors
*/
it('should return error for invalid resource URI', async () => {
await expect(
resource.readResource('dollhouse://capability-index/invalid')
).rejects.toThrow('Unknown capability index resource');
});
/**
* Verifies that missing capability index file returns error
*/
it('should return error when capability index does not exist', async () => {
mockFs.access.mockRejectedValue(new Error('ENOENT: no such file or directory'));
await expect(
resource.readResource('dollhouse://capability-index/summary')
).rejects.toThrow('Capability index not available');
});
});
describe('Configuration Integration', () => {
/**
* Verifies that default configuration has resources disabled
* for safety (opt-in model)
*/
it('should have advertise_resources disabled by default', () => {
mockConfigManager.getSetting.mockReturnValue({
advertise_resources: false,
variants: {
summary: false,
full: false,
stats: true
}
});
const config = mockConfigManager.getSetting();
expect(config.advertise_resources).toBe(false);
});
/**
* Verifies that variants can be individually toggled
*/
it('should allow individual variant toggles', () => {
mockConfigManager.getSetting.mockReturnValue({
advertise_resources: true,
variants: {
summary: true,
full: false,
stats: true
}
});
const config = mockConfigManager.getSetting();
expect(config.variants.summary).toBe(true);
expect(config.variants.full).toBe(false);
expect(config.variants.stats).toBe(true);
});
/**
* Verifies that enabling all variants works correctly
*/
it('should support enabling all variants', () => {
mockConfigManager.getSetting.mockReturnValue({
advertise_resources: true,
variants: {
summary: true,
full: true,
stats: true
}
});
const config = mockConfigManager.getSetting();
expect(config.variants.summary).toBe(true);
expect(config.variants.full).toBe(true);
expect(config.variants.stats).toBe(true);
});
/**
* Verifies that stats variant is safe to enable by default
* (minimal token impact)
*/
it('should enable stats variant by default as it is safe', () => {
mockConfigManager.getSetting.mockReturnValue({
advertise_resources: false,
variants: {
summary: false,
full: false,
stats: true // Safe default
}
});
const config = mockConfigManager.getSetting();
expect(config.variants.stats).toBe(true);
});
});
describe('Error Handling', () => {
/**
* Verifies that filesystem errors are caught and
* return meaningful error messages
*/
it('should handle filesystem errors gracefully', async () => {
mockFs.readFile.mockRejectedValue(new Error('EACCES: permission denied'));
await expect(
resource.readResource('dollhouse://capability-index/summary')
).rejects.toThrow('Capability index not available');
});
/**
* Verifies that YAML parsing errors return descriptive messages
*/
it('should handle YAML parsing errors', async () => {
mockFs.readFile.mockResolvedValue('invalid: yaml: [unclosed');
await expect(
resource.readResource('dollhouse://capability-index/summary')
).rejects.toThrow('Capability index not available');
});
/**
* Verifies that missing file errors include helpful context
*/
it('should provide helpful error when capability index is missing', async () => {
mockFs.access.mockRejectedValue(new Error('ENOENT: no such file'));
await expect(
resource.readResource('dollhouse://capability-index/summary')
).rejects.toThrow('Capability index not available');
});
});
describe('Cache Behavior in Integration', () => {
/**
* Verifies that multiple resource reads use cache efficiently
*/
it('should use cache across multiple resource reads', async () => {
// Read summary
await resource.readResource('dollhouse://capability-index/summary');
expect(mockFs.readFile).toHaveBeenCalledTimes(1);
// Read full - should use cache
await resource.readResource('dollhouse://capability-index/full');
expect(mockFs.readFile).toHaveBeenCalledTimes(1);
// Read stats - should use cache
await resource.readResource('dollhouse://capability-index/stats');
expect(mockFs.readFile).toHaveBeenCalledTimes(1);
});
/**
* Verifies that cache expires and reloads data
*/
it('should reload after cache TTL expires', async () => {
const originalNow = Date.now;
let currentTime = 1000000;
Date.now = jest.fn(() => currentTime);
try {
// First read at t=1000000
await resource.readResource('dollhouse://capability-index/summary');
expect(mockFs.readFile).toHaveBeenCalledTimes(1);
// Second read at t=1070000 (70 seconds later, cache expired)
currentTime = 1070000;
await resource.readResource('dollhouse://capability-index/summary');
expect(mockFs.readFile).toHaveBeenCalledTimes(2);
} finally {
Date.now = originalNow;
}
});
});
describe('Performance Characteristics', () => {
/**
* Verifies that summary variant is lightweight
*/
it('should generate summary quickly (lightweight variant)', async () => {
const start = Date.now();
await resource.readResource('dollhouse://capability-index/summary');
const duration = Date.now() - start;
// Should complete quickly (< 100ms)
expect(duration).toBeLessThan(100);
});
/**
* Verifies that stats generation is fast
*/
it('should generate stats quickly', async () => {
const start = Date.now();
await resource.readResource('dollhouse://capability-index/stats');
const duration = Date.now() - start;
// Should complete quickly (< 100ms)
expect(duration).toBeLessThan(100);
});
/**
* Verifies that concurrent requests are handled efficiently
*/
it('should handle concurrent reads efficiently', async () => {
const promises = [
resource.readResource('dollhouse://capability-index/summary'),
resource.readResource('dollhouse://capability-index/full'),
resource.readResource('dollhouse://capability-index/stats')
];
await Promise.all(promises);
// Should only load file once due to caching
expect(mockFs.readFile).toHaveBeenCalledTimes(1);
});
});
describe('Token Estimates', () => {
/**
* Verifies that summary token estimate is within expected range
* (~2.5-3.5K tokens)
*/
it('should have summary token estimate in expected range', async () => {
const result = await resource.readResource('dollhouse://capability-index/stats');
const stats = JSON.parse(result.contents[0].text);
// Summary should be relatively small
expect(stats.estimatedSummaryTokens).toBeGreaterThan(100);
expect(stats.estimatedSummaryTokens).toBeLessThan(10000);
});
/**
* Verifies that full index token estimate is substantial
* (~35-45K tokens)
*/
it('should have full token estimate significantly larger than summary', async () => {
const result = await resource.readResource('dollhouse://capability-index/stats');
const stats = JSON.parse(result.contents[0].text);
expect(stats.estimatedFullTokens).toBeGreaterThan(stats.estimatedSummaryTokens);
expect(stats.estimatedFullTokens).toBeGreaterThan(1000);
});
/**
* Verifies that stats resource itself is minimal
* (just JSON metadata, ~50 tokens)
*/
it('should have stats resource be minimal in size', async () => {
const result = await resource.readResource('dollhouse://capability-index/stats');
const statsText = result.contents[0].text;
// Stats JSON should be small
expect(statsText.length).toBeLessThan(500);
});
});
});