/**
* Tests for get_workspace_schema tool
*/
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
import { handleGetWorkspaceSchema } from '../../../src/tools/get-workspace-schema.js';
import * as schemaCacheModule from '../../../src/lib/schema-cache.js';
// Mock the schema-cache module
vi.mock('../../../src/lib/schema-cache.js', () => ({
getWorkspaceSchema: vi.fn(),
filterToKeyAttributes: vi.fn(),
}));
describe('get_workspace_schema', () => {
let originalApiKey: string | undefined;
const mockFullSchema = {
objects: {
companies: {
id: { object_slug: 'companies' },
api_slug: 'companies',
singular: 'Company',
plural: 'Companies',
attributes: [
{
id: { attribute_slug: 'name' },
api_slug: 'name',
title: 'Name',
type: 'text',
is_required: true,
},
{
id: { attribute_slug: 'description' },
api_slug: 'description',
title: 'Description',
type: 'text',
is_required: false,
},
{
id: { attribute_slug: 'domains' },
api_slug: 'domains',
title: 'Domains',
type: 'domain',
is_required: false,
},
],
},
people: {
id: { object_slug: 'people' },
api_slug: 'people',
singular: 'Person',
plural: 'People',
attributes: [
{
id: { attribute_slug: 'name' },
api_slug: 'name',
title: 'Name',
type: 'person-name',
is_required: true,
},
{
id: { attribute_slug: 'email_addresses' },
api_slug: 'email_addresses',
title: 'Email addresses',
type: 'email-address',
is_required: false,
},
],
},
},
lists: {
'my-list': {
id: { list_slug: 'my-list' },
api_slug: 'my-list',
name: 'My List',
attributes: [
{
id: { attribute_slug: 'custom_field' },
api_slug: 'custom_field',
title: 'Custom Field',
type: 'text',
},
],
},
},
};
beforeEach(() => {
originalApiKey = process.env.ATTIO_API_KEY;
process.env.ATTIO_API_KEY = 'test-api-key';
vi.clearAllMocks();
});
afterEach(() => {
if (originalApiKey) {
process.env.ATTIO_API_KEY = originalApiKey;
} else {
delete process.env.ATTIO_API_KEY;
}
});
describe('Summary scope (default)', () => {
it('should return summary with key attributes only', async () => {
const mockSummary = {
objects: {
companies: {
api_slug: 'companies',
singular: 'Company',
plural: 'Companies',
key_attributes: ['name', 'domains'],
},
people: {
api_slug: 'people',
singular: 'Person',
plural: 'People',
key_attributes: ['name', 'email_addresses'],
},
},
};
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
vi.mocked(schemaCacheModule.filterToKeyAttributes).mockReturnValue(
mockSummary as any
);
const result = await handleGetWorkspaceSchema({});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(parsed.data).toEqual(mockSummary);
expect(schemaCacheModule.getWorkspaceSchema).toHaveBeenCalledWith(
'test-api-key',
false
);
expect(schemaCacheModule.filterToKeyAttributes).toHaveBeenCalledWith(
mockFullSchema
);
});
it('should use scope="summary" explicitly', async () => {
const mockSummary = { objects: {} };
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
vi.mocked(schemaCacheModule.filterToKeyAttributes).mockReturnValue(
mockSummary as any
);
const result = await handleGetWorkspaceSchema({ scope: 'summary' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(schemaCacheModule.filterToKeyAttributes).toHaveBeenCalled();
});
});
describe('Full scope', () => {
it('should return full schema with all attributes', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({ scope: 'full' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(parsed.data.view).toBe('full');
expect(parsed.data.objects.companies.attributes).toHaveLength(3);
expect(parsed.data.objects.companies.total_attributes).toBe(3);
expect(parsed.data.objects.companies.showing).toBe('all');
expect(parsed.data.objects.people.total_attributes).toBe(2);
expect(parsed.data.objects.people.showing).toBe('all');
});
});
describe('Object scope', () => {
it('should return specific object with all attributes', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({
scope: 'object',
object_slug: 'companies',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(parsed.data.api_slug).toBe('companies');
expect(parsed.data.attributes).toHaveLength(3);
expect(parsed.data.total_attributes).toBe(3);
expect(parsed.data.showing).toBe('all');
});
it('should fail if object_slug is not provided', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({ scope: 'object' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.type).toBe('validation_error');
expect(parsed.error.field).toBe('object_slug');
expect(parsed.error.message).toContain('object_slug is required');
});
it('should fail if object does not exist', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({
scope: 'object',
object_slug: 'nonexistent',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.type).toBe('validation_error');
expect(parsed.error.field).toBe('object_slug');
expect(parsed.error.message).toContain('Object "nonexistent" not found');
expect(parsed.error.message).toContain('companies, people');
});
});
describe('List scope', () => {
it('should return specific list with all attributes', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({
scope: 'list',
list_slug: 'my-list',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(true);
expect(parsed.data.api_slug).toBe('my-list');
expect(parsed.data.name).toBe('My List');
expect(parsed.data.attributes).toHaveLength(1);
expect(parsed.data.total_attributes).toBe(1);
expect(parsed.data.showing).toBe('all');
});
it('should fail if list_slug is not provided', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({ scope: 'list' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.type).toBe('validation_error');
expect(parsed.error.field).toBe('list_slug');
expect(parsed.error.message).toContain('list_slug is required');
});
it('should fail if list does not exist', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({
scope: 'list',
list_slug: 'nonexistent-list',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.type).toBe('validation_error');
expect(parsed.error.field).toBe('list_slug');
expect(parsed.error.message).toContain('List "nonexistent-list" not found');
});
it('should handle workspace with no lists', async () => {
const schemaWithoutLists = {
...mockFullSchema,
lists: undefined,
};
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
schemaWithoutLists as any
);
const result = await handleGetWorkspaceSchema({
scope: 'list',
list_slug: 'any-list',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.message).toContain(
'No lists are currently defined in the workspace'
);
});
});
describe('Force reload', () => {
it('should pass force_reload=true to getWorkspaceSchema', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
vi.mocked(schemaCacheModule.filterToKeyAttributes).mockReturnValue(
{} as any
);
await handleGetWorkspaceSchema({ force_reload: true });
expect(schemaCacheModule.getWorkspaceSchema).toHaveBeenCalledWith(
'test-api-key',
true
);
});
it('should default to force_reload=false', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
vi.mocked(schemaCacheModule.filterToKeyAttributes).mockReturnValue(
{} as any
);
await handleGetWorkspaceSchema({});
expect(schemaCacheModule.getWorkspaceSchema).toHaveBeenCalledWith(
'test-api-key',
false
);
});
});
describe('Validation errors', () => {
it('should fail if ATTIO_API_KEY is not configured', async () => {
delete process.env.ATTIO_API_KEY;
const result = await handleGetWorkspaceSchema({});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.type).toBe('validation_error');
expect(parsed.error.message).toContain('ATTIO_API_KEY');
});
it('should fail for invalid scope', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({
scope: 'invalid' as any,
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.success).toBe(false);
expect(parsed.error.type).toBe('validation_error');
expect(parsed.error.field).toBe('scope');
expect(parsed.error.message).toContain('Invalid scope');
expect(parsed.error.message).toContain('summary, full, object, list');
});
});
describe('Helper notes', () => {
it('should include mapping note for summary scope', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
vi.mocked(schemaCacheModule.filterToKeyAttributes).mockReturnValue(
{} as any
);
const result = await handleGetWorkspaceSchema({ scope: 'summary' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.data._note).toContain('company_id maps to "company"');
expect(parsed.data._note).toContain('fund_id maps to "fund_5"');
});
it('should include mapping note for full scope', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({ scope: 'full' });
const parsed = JSON.parse(result.content[0].text);
expect(parsed.data._note).toBeDefined();
expect(parsed.data._note).toContain('Tool parameter names may differ');
});
it('should include mapping note for object scope', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({
scope: 'object',
object_slug: 'companies',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.data._note).toBeDefined();
});
it('should not include mapping note for list scope', async () => {
vi.mocked(schemaCacheModule.getWorkspaceSchema).mockResolvedValue(
mockFullSchema as any
);
const result = await handleGetWorkspaceSchema({
scope: 'list',
list_slug: 'my-list',
});
const parsed = JSON.parse(result.content[0].text);
expect(parsed.data._note).toBeUndefined();
});
});
});