/**
* Integration Tests: Resources and Prompts
*
* Tests resource and prompt discovery, loading, and execution through MCP.
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { setupTestMCPServer, type TestMCPServer } from '../helpers/test-server.js';
import * as schemaCacheModule from '../../src/lib/schema-cache.js';
describe('Resources and Prompts', () => {
let testServer: TestMCPServer;
const mockSchema = {
generated_at: '2024-01-01T00:00:00Z',
version: '1.0.0',
objects: {
companies: {
api_slug: 'companies',
title: 'Companies',
description: 'Company records',
type: 'object' as const,
attributes: [
{
slug: 'name',
title: 'Name',
type: 'text',
required: true,
},
],
},
},
};
beforeEach(async () => {
// Mock schema cache to return valid data
vi.spyOn(schemaCacheModule, 'getWorkspaceSchema').mockResolvedValue(mockSchema);
testServer = await setupTestMCPServer();
});
afterEach(async () => {
await testServer.close();
vi.restoreAllMocks();
});
describe('Resource discovery', () => {
it('should list all available resources', async () => {
const resources = await testServer.listResources();
expect(resources.length).toBeGreaterThan(0);
expect(resources[0]).toHaveProperty('uri');
expect(resources[0]).toHaveProperty('name');
expect(resources[0]).toHaveProperty('mimeType');
});
it('should include workspace schema resource', async () => {
const resources = await testServer.listResources();
const schemaResource = resources.find((r) =>
r.uri.includes('attio-workspace-schema')
);
expect(schemaResource).toBeDefined();
expect(schemaResource?.name).toContain('Workspace Schema');
expect(schemaResource?.mimeType).toBe('application/json');
});
});
describe('Resource loading', () => {
it('should read workspace schema resource', async () => {
const resources = await testServer.listResources();
const schemaResource = resources.find((r) =>
r.uri.includes('attio-workspace-schema')
);
expect(schemaResource).toBeDefined();
const result = await testServer.readResource(schemaResource!.uri);
expect(result.contents).toBeDefined();
expect(Array.isArray(result.contents)).toBe(true);
expect(result.contents[0].uri).toBe(schemaResource!.uri);
expect(result.contents[0].mimeType).toBe('application/json');
});
it('should return valid JSON in schema resource', async () => {
const resources = await testServer.listResources();
const schemaResource = resources.find((r) =>
r.uri.includes('attio-workspace-schema')
);
const result = await testServer.readResource(schemaResource!.uri);
const content = result.contents[0];
// Should be parseable JSON
expect(() => JSON.parse(content.text as string)).not.toThrow();
const schema = JSON.parse(content.text as string);
expect(schema).toHaveProperty('generated_at');
expect(schema).toHaveProperty('version');
expect(schema).toHaveProperty('objects');
});
it('should include companies object in schema', async () => {
const resources = await testServer.listResources();
const schemaResource = resources.find((r) =>
r.uri.includes('attio-workspace-schema')
);
const result = await testServer.readResource(schemaResource!.uri);
const schema = JSON.parse(result.contents[0].text as string);
expect(schema.objects).toHaveProperty('companies');
expect(schema.objects.companies).toHaveProperty('api_slug');
expect(schema.objects.companies).toHaveProperty('attributes');
expect(Array.isArray(schema.objects.companies.attributes)).toBe(true);
});
});
describe('Resource caching', () => {
it('should cache schema resource data', async () => {
const resources = await testServer.listResources();
const schemaResource = resources.find((r) =>
r.uri.includes('attio-workspace-schema')
);
// Read twice
const result1 = await testServer.readResource(schemaResource!.uri);
const result2 = await testServer.readResource(schemaResource!.uri);
const schema1 = JSON.parse(result1.contents[0].text as string);
const schema2 = JSON.parse(result2.contents[0].text as string);
// Should return same cached data (same timestamp)
expect(schema1.generated_at).toBe(schema2.generated_at);
});
});
describe('Error handling', () => {
it('should throw error for non-existent resource', async () => {
await expect(
testServer.readResource('file:///nonexistent/resource.json')
).rejects.toThrow();
});
it('should throw error for non-existent prompt', async () => {
await expect(
testServer.getPrompt('nonexistent_prompt', {})
).rejects.toThrow();
});
});
describe('Resource and tool integration', () => {
it('should use workspace schema in tool execution', async () => {
// The get_workspace_schema tool uses the same schema resource
const toolResult = await testServer.callTool('get_workspace_schema', {
scope: 'summary',
});
const toolData = JSON.parse(toolResult.content[0].text as string);
const resources = await testServer.listResources();
const schemaResource = resources.find((r) =>
r.uri.includes('attio-workspace-schema')
);
const resourceResult = await testServer.readResource(schemaResource!.uri);
const resourceData = JSON.parse(resourceResult.contents[0].text as string);
// Both should reference the same underlying schema cache
expect(toolData.data.version).toBe(resourceData.version);
});
});
});