Skip to main content
Glama
response-parsing.test.ts8.23 kB
import { describe, it, expect, beforeAll } from 'vitest' import { ShortcutClient } from '../../src/shortcutClient' // Types are used implicitly for type checking in tests // @ts-ignore import type { Story, StoryComment, Workflow, Project, Epic, Member, SearchResults, } from '../../src/shortcut-types' describe('Shortcut API Response Parsing', () => { let client: ShortcutClient beforeAll(() => { const apiToken = process.env.SHORTCUT_TOKEN if (!apiToken) { throw new Error('SHORTCUT_TOKEN not found in environment') } client = new ShortcutClient({ apiToken, baseUrl: 'https://api.app.shortcut.com/api/v3', }) }) describe('Type validation', () => { it('should parse workflow response correctly', async () => { const workflows = await client.getWorkflows() workflows.forEach((workflow) => { expect(workflow).toMatchObject({ id: expect.any(Number), name: expect.any(String), states: expect.any(Array), }) workflow.states.forEach((state) => { expect(state).toMatchObject({ id: expect.any(Number), name: expect.any(String), type: expect.stringMatching(/^(unstarted|started|done)$/), position: expect.any(Number), }) }) }) }) it('should parse project response correctly', async () => { const projects = await client.getProjects() projects.forEach((project) => { expect(project).toMatchObject({ id: expect.any(Number), name: expect.any(String), color: expect.stringMatching(/^#[0-9a-fA-F]{6}$/), team_id: expect.any(Number), }) if (project.description !== undefined) { expect(typeof project.description).toBe('string') } }) }) it('should parse epic response correctly', async () => { const epics = await client.getEpics() epics.forEach((epic) => { expect(epic).toMatchObject({ id: expect.any(Number), name: expect.any(String), state: expect.stringMatching(/^(to do|in progress|done)$/), }) if (epic.description !== undefined) { expect(typeof epic.description).toBe('string') } if (epic.milestone_id !== undefined && epic.milestone_id !== null) { expect(typeof epic.milestone_id).toBe('number') } }) }) it('should parse story response correctly', async () => { const results = await client.searchStories('is:story') if (results.stories.data.length > 0) { const story = results.stories.data[0]! expect(story).toMatchObject({ id: expect.any(Number), name: expect.any(String), story_type: expect.stringMatching(/^(feature|bug|chore)$/), workflow_state_id: expect.any(Number), created_at: expect.any(String), updated_at: expect.any(String), started: expect.any(Boolean), completed: expect.any(Boolean), }) // Optional fields if (story.description !== undefined) { expect(typeof story.description).toBe('string') } if (story.estimate !== undefined && story.estimate !== null) { expect(typeof story.estimate).toBe('number') } if (story.project_id !== undefined && story.project_id !== null) { expect(typeof story.project_id).toBe('number') } if (story.epic_id !== undefined && story.epic_id !== null) { expect(typeof story.epic_id).toBe('number') } if (story.labels !== undefined) { expect(Array.isArray(story.labels)).toBe(true) story.labels.forEach((label) => { expect(label).toMatchObject({ id: expect.any(Number), name: expect.any(String), }) }) } if (story.owner_ids !== undefined) { expect(Array.isArray(story.owner_ids)).toBe(true) story.owner_ids.forEach((id: string) => { expect(typeof id).toBe('string') }) } } }) it('should parse search results correctly', async () => { const results = await client.searchStories('is:story') expect(results).toMatchObject({ stories: { data: expect.any(Array), total: expect.any(Number), }, epics: { data: expect.any(Array), total: expect.any(Number), }, }) expect(results.stories.total).toBeGreaterThanOrEqual(results.stories.data.length) expect(results.epics.total).toBeGreaterThanOrEqual(results.epics.data.length) }) it('should parse comment response correctly', async () => { // First, find a story with comments const stories = await client.searchStories('is:story has:comment') if (stories.stories.data.length > 0) { const storyId = stories.stories.data[0]!.id const comments = await client.getComments(storyId) if (comments.length > 0) { comments.forEach((comment) => { expect(comment).toMatchObject({ id: expect.any(Number), text: expect.any(String), author_id: expect.any(String), created_at: expect.any(String), updated_at: expect.any(String), story_id: expect.any(Number), }) }) } } }) it('should handle empty arrays correctly', async () => { // Search for something unlikely to return results const results = await client.searchStories('is:story label:"nonexistent-label-xyz-123"') expect(results.stories.data).toEqual([]) expect(results.stories.total).toBe(0) }) it('should handle special characters in responses', async () => { // Test searching with special characters const results = await client.searchStories('is:story "test & development"') // Should not throw and should return valid structure expect(results).toHaveProperty('stories') expect(results.stories).toHaveProperty('data') expect(results.stories).toHaveProperty('total') }) }) describe('Error response parsing', () => { it('should parse error responses with proper structure', async () => { try { await client.getStory(999999999) expect.fail('Should have thrown an error') } catch (error: any) { expect(error).toBeInstanceOf(Error) expect(error.message).toContain('Shortcut API error') expect(error.response).toBeDefined() expect(error.response.status).toBeDefined() expect(typeof error.response.status).toBe('number') if (error.response.data) { expect(typeof error.response.data).toBe('object') } } }) }) })

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/currentspace/shortcut_mcp'

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