Skip to main content
Glama
iceener

Linear Streamable MCP Server

by iceener
list-my-issues.test.ts9.33 kB
/** * Tests for list_my_issues tool. * Verifies: filtering current user's issues, state filters, output shape. */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { listMyIssuesTool } from '../../src/shared/tools/linear/list-my-issues.js'; import { createMockLinearClient, resetMockCalls, type MockLinearClient, } from '../mocks/linear-client.js'; import type { ToolContext } from '../../src/shared/tools/types.js'; // ───────────────────────────────────────────────────────────────────────────── // Test Setup // ───────────────────────────────────────────────────────────────────────────── let mockClient: MockLinearClient; const baseContext: ToolContext = { sessionId: 'test-session', providerToken: 'test-token', authStrategy: 'bearer', }; vi.mock('../../src/services/linear/client.js', () => ({ getLinearClient: vi.fn(() => Promise.resolve(mockClient)), })); beforeEach(() => { mockClient = createMockLinearClient(); resetMockCalls(mockClient); }); // ───────────────────────────────────────────────────────────────────────────── // Tool Metadata Tests // ───────────────────────────────────────────────────────────────────────────── describe('list_my_issues tool metadata', () => { it('has correct name and title', () => { expect(listMyIssuesTool.name).toBe('list_my_issues'); expect(listMyIssuesTool.title).toBe('List My Issues'); }); it('has readOnlyHint annotation', () => { expect(listMyIssuesTool.annotations?.readOnlyHint).toBe(true); expect(listMyIssuesTool.annotations?.destructiveHint).toBe(false); }); it('has description mentioning current user filter', () => { expect(listMyIssuesTool.description).toContain('assigned to you'); }); }); // ───────────────────────────────────────────────────────────────────────────── // Handler Behavior Tests // ───────────────────────────────────────────────────────────────────────────── describe('list_my_issues handler', () => { it('returns issues assigned to current viewer', async () => { const result = await listMyIssuesTool.handler({}, baseContext); expect(result.isError).toBeFalsy(); expect(result.structuredContent).toBeDefined(); const structured = result.structuredContent as Record<string, unknown>; expect(structured.items).toBeDefined(); expect(Array.isArray(structured.items)).toBe(true); }); it('uses viewer.assignedIssues query (implicit assignee filter)', async () => { const result = await listMyIssuesTool.handler({}, baseContext); expect(result.isError).toBeFalsy(); // Verify the query uses assignedIssues (not issues with assignee filter) const call = mockClient._calls.rawRequest[0]; expect(call.query).toContain('assignedIssues'); }); it('passes custom filters to assignedIssues query', async () => { const result = await listMyIssuesTool.handler( { filter: { state: { type: { eq: 'started' } } } }, baseContext, ); expect(result.isError).toBeFalsy(); const call = mockClient._calls.rawRequest[0]; const filter = call.variables?.filter as Record<string, unknown>; // assignedIssues already filters by viewer, so only state filter is added expect(filter.state).toEqual({ type: { eq: 'started' } }); }); it('supports filtering active issues only', async () => { const result = await listMyIssuesTool.handler( { filter: { state: { type: { neq: 'completed' } } } }, baseContext, ); expect(result.isError).toBeFalsy(); const call = mockClient._calls.rawRequest[0]; const filter = call.variables?.filter as Record<string, unknown>; expect(filter.state).toEqual({ type: { neq: 'completed' } }); }); it('supports keyword search', async () => { const result = await listMyIssuesTool.handler({ q: 'authentication' }, baseContext); expect(result.isError).toBeFalsy(); const call = mockClient._calls.rawRequest[0]; const filter = call.variables?.filter as Record<string, unknown>; expect(filter.or).toBeDefined(); }); it('respects limit parameter', async () => { const result = await listMyIssuesTool.handler({ limit: 5 }, baseContext); expect(result.isError).toBeFalsy(); const call = mockClient._calls.rawRequest[0]; expect(call.variables?.first).toBe(5); }); it('supports pagination with cursor', async () => { const result = await listMyIssuesTool.handler({ cursor: 'test-cursor' }, baseContext); expect(result.isError).toBeFalsy(); const call = mockClient._calls.rawRequest[0]; expect(call.variables?.after).toBe('test-cursor'); }); it('supports ordering by updatedAt', async () => { const result = await listMyIssuesTool.handler({ orderBy: 'updatedAt' }, baseContext); expect(result.isError).toBeFalsy(); const call = mockClient._calls.rawRequest[0]; expect(call.variables?.orderBy).toBe('updatedAt'); }); it('supports ordering by priority', async () => { const result = await listMyIssuesTool.handler({ orderBy: 'priority' }, baseContext); expect(result.isError).toBeFalsy(); const call = mockClient._calls.rawRequest[0]; expect(call.variables?.orderBy).toBe('priority'); }); }); // ───────────────────────────────────────────────────────────────────────────── // Output Shape Tests // ───────────────────────────────────────────────────────────────────────────── describe('list_my_issues output shape', () => { it('matches ListIssuesOutputSchema', async () => { const result = await listMyIssuesTool.handler({}, baseContext); const structured = result.structuredContent as Record<string, unknown>; expect(structured.items).toBeDefined(); expect(structured.limit).toBeDefined(); const items = structured.items as Array<Record<string, unknown>>; for (const item of items) { expect(item.id).toBeDefined(); expect(item.title).toBeDefined(); expect(item.stateId).toBeDefined(); } }); it('includes pagination info', async () => { const result = await listMyIssuesTool.handler({}, baseContext); const structured = result.structuredContent as Record<string, unknown>; expect('nextCursor' in structured || 'cursor' in structured).toBe(true); }); }); // ───────────────────────────────────────────────────────────────────────────── // Common Workflow Tests // ───────────────────────────────────────────────────────────────────────────── describe('list_my_issues common workflows', () => { it('shows my active tasks (most common query)', async () => { const result = await listMyIssuesTool.handler( { filter: { state: { type: { neq: 'completed' } } } }, baseContext, ); expect(result.isError).toBeFalsy(); // Uses assignedIssues query const call = mockClient._calls.rawRequest[0]; expect(call.query).toContain('assignedIssues'); }); it('shows my in-progress tasks', async () => { const result = await listMyIssuesTool.handler( { filter: { state: { type: { eq: 'started' } } } }, baseContext, ); expect(result.isError).toBeFalsy(); // Verify filter was passed to query const call = mockClient._calls.rawRequest[0]; const filter = call.variables?.filter as Record<string, unknown>; expect(filter.state).toEqual({ type: { eq: 'started' } }); }); it('shows my completed tasks', async () => { const result = await listMyIssuesTool.handler( { filter: { state: { type: { eq: 'completed' } } } }, baseContext, ); expect(result.isError).toBeFalsy(); // Verify filter was passed to query const call = mockClient._calls.rawRequest[0]; const filter = call.variables?.filter as Record<string, unknown>; expect(filter.state).toEqual({ type: { eq: 'completed' } }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/iceener/linear-streamable-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server