Skip to main content
Glama
shortcutClient.test.ts11.8 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('ShortcutClient Integration Tests', () => { let client: ShortcutClient let testStoryId: number | null = null let testProjectId: number | null = null let testEpicId: number | null = null let testWorkflowStateId: number | null = null 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('getWorkflows', () => { it('should fetch workflows with states', async () => { const workflows = await client.getWorkflows() expect(workflows).toBeDefined() expect(Array.isArray(workflows)).toBe(true) expect(workflows.length).toBeGreaterThan(0) const workflow = workflows[0]! expect(workflow).toHaveProperty('id') expect(workflow).toHaveProperty('name') expect(workflow).toHaveProperty('states') expect(Array.isArray(workflow.states)).toBe(true) expect(workflow.states.length).toBeGreaterThan(0) const state = workflow.states[0]! expect(state).toHaveProperty('id') expect(state).toHaveProperty('name') expect(state).toHaveProperty('type') expect(['unstarted', 'started', 'done']).toContain(state.type) expect(state).toHaveProperty('position') // Save a workflow state ID for later tests testWorkflowStateId = workflow.states[0]!.id }) }) describe('getProjects', () => { it('should fetch all projects', async () => { const projects = await client.getProjects() expect(projects).toBeDefined() expect(Array.isArray(projects)).toBe(true) if (projects.length > 0) { const project = projects[0]! expect(project).toHaveProperty('id') expect(project).toHaveProperty('name') expect(project).toHaveProperty('color') expect(project).toHaveProperty('team_id') // Save a project ID for later tests testProjectId = project.id } }) }) describe('getEpics', () => { it('should fetch all epics', async () => { const epics = await client.getEpics() expect(epics).toBeDefined() expect(Array.isArray(epics)).toBe(true) if (epics.length > 0) { const epic = epics[0]! expect(epic).toHaveProperty('id') expect(epic).toHaveProperty('name') expect(epic).toHaveProperty('state') expect(['to do', 'in progress', 'done']).toContain(epic.state) // Save an epic ID for later tests testEpicId = epic.id } }) }) describe('getMembers', () => { it('should fetch all members', async () => { const members = await client.getMembers() expect(members).toBeDefined() expect(Array.isArray(members)).toBe(true) expect(members.length).toBeGreaterThan(0) const member = members[0]! expect(member).toHaveProperty('id') // Members might have different structures depending on role/type // Let's just verify we got a valid member object with an ID expect(typeof member.id).toBe('string') }) }) describe('getCurrentUser', () => { it('should fetch the current user', async () => { const user = await client.getCurrentUser() expect(user).toBeDefined() expect(user).toHaveProperty('id') // The current user endpoint might return a more complete member object expect(typeof user.id).toBe('string') }) }) describe('searchStories', () => { it('should search for stories', async () => { const results = await client.searchStories('is:story') expect(results).toBeDefined() expect(results).toHaveProperty('stories') expect(results.stories).toHaveProperty('data') expect(results.stories).toHaveProperty('total') expect(Array.isArray(results.stories.data)).toBe(true) if (results.stories.data.length > 0) { const story = results.stories.data[0]! expect(story).toHaveProperty('id') expect(story).toHaveProperty('name') expect(story).toHaveProperty('story_type') expect(['feature', 'bug', 'chore']).toContain(story.story_type) // Save a story ID for later tests testStoryId = story.id } }) it('should handle search with specific query', async () => { const results = await client.searchStories('state:unstarted') expect(results).toBeDefined() expect(results.stories).toBeDefined() expect(Array.isArray(results.stories.data)).toBe(true) }) }) describe('getStoriesInProject', () => { it('should fetch stories in a project', async () => { if (!testProjectId) { console.warn('No test project ID available, skipping test') return } const stories = await client.getStoriesInProject(testProjectId) expect(stories).toBeDefined() expect(Array.isArray(stories)).toBe(true) if (stories.length > 0) { const story = stories[0]! expect(story.project_id).toBe(testProjectId) } }) }) describe('getStoriesInEpic', () => { it('should fetch stories in an epic', async () => { if (!testEpicId) { console.warn('No test epic ID available, skipping test') return } const stories = await client.getStoriesInEpic(testEpicId) expect(stories).toBeDefined() expect(Array.isArray(stories)).toBe(true) if (stories.length > 0) { const story = stories[0]! expect(story.epic_id).toBe(testEpicId) } }) }) describe('getStory', () => { it('should fetch a specific story by ID', async () => { if (!testStoryId) { console.warn('No test story ID available, skipping test') return } const story = await client.getStory(testStoryId) expect(story).toBeDefined() expect(story.id).toBe(testStoryId) expect(story).toHaveProperty('name') expect(story).toHaveProperty('description') expect(story).toHaveProperty('story_type') expect(story).toHaveProperty('workflow_state_id') expect(story).toHaveProperty('created_at') expect(story).toHaveProperty('updated_at') }) it('should handle non-existent story ID', async () => { await expect(client.getStory(999999999)).rejects.toThrow() }) }) describe('getComments', () => { it('should fetch comments for a story', async () => { if (!testStoryId) { console.warn('No test story ID available, skipping test') return } const comments = await client.getComments(testStoryId) expect(comments).toBeDefined() expect(Array.isArray(comments)).toBe(true) if (comments.length > 0) { const comment = comments[0]! expect(comment).toHaveProperty('id') expect(comment).toHaveProperty('text') expect(comment).toHaveProperty('author_id') expect(comment).toHaveProperty('created_at') expect(comment).toHaveProperty('story_id') expect(comment.story_id).toBe(testStoryId) } }) }) describe('Story modifications', () => { it.skip('should add a comment to a story', async () => { if (!testStoryId) { console.warn('No test story ID available, skipping test') return } const commentText = `Integration test comment - ${new Date().toISOString()}` const comment = await client.addComment(testStoryId, { text: commentText, }) expect(comment).toBeDefined() expect(comment).toHaveProperty('id') expect(comment.text).toBe(commentText) expect(comment.story_id).toBe(testStoryId) }) it.skip('should update a story', async () => { if (!testStoryId || !testWorkflowStateId) { console.warn('No test story ID or workflow state ID available, skipping test') return } const originalStory = await client.getStory(testStoryId) const updateDescription = `Updated by integration test - ${new Date().toISOString()}` const updatedStory = await client.updateStory(testStoryId, { description: updateDescription, }) expect(updatedStory).toBeDefined() expect(updatedStory.id).toBe(testStoryId) expect(updatedStory.description).toBe(updateDescription) // Restore original description if (originalStory.description !== undefined) { await client.updateStory(testStoryId, { description: originalStory.description, }) } }) it.skip('should update story with labels', async () => { if (!testStoryId) { console.warn('No test story ID available, skipping test') return } const updatedStory = await client.updateStory(testStoryId, { labels: [{ name: 'test-label' }], }) expect(updatedStory).toBeDefined() expect(updatedStory.labels).toBeDefined() expect(Array.isArray(updatedStory.labels)).toBe(true) }) }) describe('Error handling', () => { it('should handle API errors gracefully', async () => { const invalidClient = new ShortcutClient({ apiToken: 'invalid-token', baseUrl: 'https://api.app.shortcut.com/api/v3', }) await expect(invalidClient.getProjects()).rejects.toThrow('Shortcut API error') }) it('should include error details in the error object', async () => { const invalidClient = new ShortcutClient({ apiToken: 'invalid-token', baseUrl: 'https://api.app.shortcut.com/api/v3', }) try { await invalidClient.getProjects() 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() } }) }) })

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