Skip to main content
Glama

Task Trellis MCP

getNextAvailableIssue.test.ts12.7 kB
import { TrellisObject, TrellisObjectPriority, TrellisObjectStatus, TrellisObjectType, } from "../../../models"; import { Repository } from "../../../repositories/Repository"; import * as filterUtils from "../../../utils/filterUnavailableObjects"; import * as sortUtils from "../../../utils/sortTrellisObjects"; import { getNextAvailableIssue } from "../getNextAvailableIssue"; // Mock the utility functions jest.mock("../../../utils/filterUnavailableObjects"); jest.mock("../../../utils/sortTrellisObjects"); const mockFilterUnavailableObjects = filterUtils.filterUnavailableObjects as jest.MockedFunction< typeof filterUtils.filterUnavailableObjects >; const mockSortTrellisObjects = sortUtils.sortTrellisObjects as jest.MockedFunction< typeof sortUtils.sortTrellisObjects >; describe("getNextAvailableIssue service function", () => { let mockRepository: jest.Mocked<Repository>; const createMockTask = ( overrides?: Partial<TrellisObject>, ): TrellisObject => ({ id: "T-test-task", type: TrellisObjectType.TASK, title: "Test Task", status: TrellisObjectStatus.OPEN, priority: TrellisObjectPriority.MEDIUM, parent: "F-test-feature", prerequisites: [], affectedFiles: new Map(), log: [], schema: "1.0", childrenIds: [], created: "2025-09-03T00:00:00.000Z", updated: "2025-09-03T00:00:00.000Z", body: "Test task description", ...overrides, }); const createMockFeature = ( overrides?: Partial<TrellisObject>, ): TrellisObject => ({ id: "F-test-feature", type: TrellisObjectType.FEATURE, title: "Test Feature", status: TrellisObjectStatus.OPEN, priority: TrellisObjectPriority.HIGH, parent: "E-test-epic", prerequisites: [], affectedFiles: new Map(), log: [], schema: "1.0", childrenIds: ["T-test-task"], created: "2025-09-03T00:00:00.000Z", updated: "2025-09-03T00:00:00.000Z", body: "Test feature description", ...overrides, }); const _createMockEpic = ( overrides?: Partial<TrellisObject>, ): TrellisObject => ({ id: "E-test-epic", type: TrellisObjectType.EPIC, title: "Test Epic", status: TrellisObjectStatus.OPEN, priority: TrellisObjectPriority.LOW, parent: "P-test-project", prerequisites: [], affectedFiles: new Map(), log: [], schema: "1.0", childrenIds: ["F-test-feature"], created: "2025-09-03T00:00:00.000Z", updated: "2025-09-03T00:00:00.000Z", body: "Test epic description", ...overrides, }); const _createMockProject = ( overrides?: Partial<TrellisObject>, ): TrellisObject => ({ id: "P-test-project", type: TrellisObjectType.PROJECT, title: "Test Project", status: TrellisObjectStatus.OPEN, priority: TrellisObjectPriority.MEDIUM, parent: null, prerequisites: [], affectedFiles: new Map(), log: [], schema: "1.0", childrenIds: ["E-test-epic"], created: "2025-09-03T00:00:00.000Z", updated: "2025-09-03T00:00:00.000Z", body: "Test project description", ...overrides, }); beforeEach(() => { jest.clearAllMocks(); mockRepository = { getObjectById: jest.fn(), getObjects: jest.fn(), saveObject: jest.fn(), deleteObject: jest.fn(), getChildrenOf: jest.fn(), }; }); describe("Basic Functionality", () => { it("should return the highest priority available issue", async () => { const lowPriorityTask = createMockTask({ id: "T-low-priority", priority: TrellisObjectPriority.LOW, }); const highPriorityTask = createMockTask({ id: "T-high-priority", priority: TrellisObjectPriority.HIGH, }); mockRepository.getObjects.mockResolvedValue([ lowPriorityTask, highPriorityTask, ]); mockFilterUnavailableObjects.mockResolvedValue([ lowPriorityTask, highPriorityTask, ]); mockSortTrellisObjects.mockReturnValue([ highPriorityTask, lowPriorityTask, ]); const result = await getNextAvailableIssue(mockRepository); expect(result).toBe(highPriorityTask); expect(mockRepository.getObjects).toHaveBeenCalledWith( false, undefined, undefined, ); expect(mockFilterUnavailableObjects).toHaveBeenCalledWith( [lowPriorityTask, highPriorityTask], mockRepository, ); expect(mockSortTrellisObjects).toHaveBeenCalledWith([ lowPriorityTask, highPriorityTask, ]); }); }); describe("Type Filtering", () => { it("should filter by single issue type", async () => { const task = createMockTask(); mockRepository.getObjects.mockResolvedValue([task]); mockFilterUnavailableObjects.mockResolvedValue([task]); mockSortTrellisObjects.mockReturnValue([task]); const result = await getNextAvailableIssue( mockRepository, undefined, TrellisObjectType.TASK, ); expect(result).toBe(task); expect(mockRepository.getObjects).toHaveBeenCalledWith( false, undefined, TrellisObjectType.TASK, ); }); it("should filter by array of issue types", async () => { const task = createMockTask(); const feature = createMockFeature(); mockRepository.getObjects.mockResolvedValue([task, feature]); mockFilterUnavailableObjects.mockResolvedValue([task, feature]); mockSortTrellisObjects.mockReturnValue([feature, task]); const result = await getNextAvailableIssue(mockRepository, undefined, [ TrellisObjectType.TASK, TrellisObjectType.FEATURE, ]); expect(result).toBe(feature); expect(mockRepository.getObjects).toHaveBeenCalledWith(false, undefined, [ TrellisObjectType.TASK, TrellisObjectType.FEATURE, ]); }); }); describe("Scope Filtering", () => { it("should filter by scope parameter", async () => { const task = createMockTask(); mockRepository.getObjects.mockResolvedValue([task]); mockFilterUnavailableObjects.mockResolvedValue([task]); mockSortTrellisObjects.mockReturnValue([task]); const result = await getNextAvailableIssue( mockRepository, "P-test-project", ); expect(result).toBe(task); expect(mockRepository.getObjects).toHaveBeenCalledWith( false, "P-test-project", undefined, ); }); it("should work with both scope and type filtering", async () => { const task = createMockTask(); mockRepository.getObjects.mockResolvedValue([task]); mockFilterUnavailableObjects.mockResolvedValue([task]); mockSortTrellisObjects.mockReturnValue([task]); const result = await getNextAvailableIssue( mockRepository, "P-test-project", TrellisObjectType.TASK, ); expect(result).toBe(task); expect(mockRepository.getObjects).toHaveBeenCalledWith( false, "P-test-project", TrellisObjectType.TASK, ); }); }); describe("Priority Sorting", () => { it("should return high priority issues before medium and low", async () => { const lowPriorityIssue = createMockTask({ id: "T-low", priority: TrellisObjectPriority.LOW, }); const mediumPriorityIssue = createMockTask({ id: "T-medium", priority: TrellisObjectPriority.MEDIUM, }); const highPriorityIssue = createMockTask({ id: "T-high", priority: TrellisObjectPriority.HIGH, }); mockRepository.getObjects.mockResolvedValue([ lowPriorityIssue, mediumPriorityIssue, highPriorityIssue, ]); mockFilterUnavailableObjects.mockResolvedValue([ lowPriorityIssue, mediumPriorityIssue, highPriorityIssue, ]); mockSortTrellisObjects.mockReturnValue([ highPriorityIssue, mediumPriorityIssue, lowPriorityIssue, ]); const result = await getNextAvailableIssue(mockRepository); expect(result).toBe(highPriorityIssue); expect(mockSortTrellisObjects).toHaveBeenCalledWith([ lowPriorityIssue, mediumPriorityIssue, highPriorityIssue, ]); }); }); describe("Error Scenarios", () => { it("should throw descriptive error when no available issues found", async () => { mockRepository.getObjects.mockResolvedValue([]); mockFilterUnavailableObjects.mockResolvedValue([]); await expect(getNextAvailableIssue(mockRepository)).rejects.toThrow( "No available issues found matching criteria", ); expect(mockRepository.getObjects).toHaveBeenCalledWith( false, undefined, undefined, ); expect(mockFilterUnavailableObjects).toHaveBeenCalledWith( [], mockRepository, ); expect(mockSortTrellisObjects).not.toHaveBeenCalled(); }); it("should throw descriptive error when filtering removes all issues", async () => { const unavailableTask = createMockTask({ status: TrellisObjectStatus.IN_PROGRESS, }); mockRepository.getObjects.mockResolvedValue([unavailableTask]); mockFilterUnavailableObjects.mockResolvedValue([]); await expect(getNextAvailableIssue(mockRepository)).rejects.toThrow( "No available issues found matching criteria", ); expect(mockFilterUnavailableObjects).toHaveBeenCalledWith( [unavailableTask], mockRepository, ); expect(mockSortTrellisObjects).not.toHaveBeenCalled(); }); }); describe("Repository Integration", () => { it("should call repository.getObjects with correct parameters", async () => { const task = createMockTask(); mockRepository.getObjects.mockResolvedValue([task]); mockFilterUnavailableObjects.mockResolvedValue([task]); mockSortTrellisObjects.mockReturnValue([task]); await getNextAvailableIssue( mockRepository, "P-project", TrellisObjectType.FEATURE, ); expect(mockRepository.getObjects).toHaveBeenCalledTimes(1); expect(mockRepository.getObjects).toHaveBeenCalledWith( false, "P-project", TrellisObjectType.FEATURE, ); }); it("should handle repository errors gracefully", async () => { const repositoryError = new Error("Repository connection failed"); mockRepository.getObjects.mockRejectedValue(repositoryError); await expect(getNextAvailableIssue(mockRepository)).rejects.toThrow( "Repository connection failed", ); expect(mockFilterUnavailableObjects).not.toHaveBeenCalled(); expect(mockSortTrellisObjects).not.toHaveBeenCalled(); }); }); describe("Utility Integration", () => { it("should call filterUnavailableObjects with repository and objects", async () => { const task1 = createMockTask({ id: "T-task-1" }); const task2 = createMockTask({ id: "T-task-2" }); const allTasks = [task1, task2]; mockRepository.getObjects.mockResolvedValue(allTasks); mockFilterUnavailableObjects.mockResolvedValue([task1]); mockSortTrellisObjects.mockReturnValue([task1]); await getNextAvailableIssue(mockRepository); expect(mockFilterUnavailableObjects).toHaveBeenCalledTimes(1); expect(mockFilterUnavailableObjects).toHaveBeenCalledWith( allTasks, mockRepository, ); }); it("should call sortTrellisObjects with available objects", async () => { const task1 = createMockTask({ id: "T-task-1" }); const task2 = createMockTask({ id: "T-task-2" }); const availableTasks = [task1, task2]; mockRepository.getObjects.mockResolvedValue([task1, task2]); mockFilterUnavailableObjects.mockResolvedValue(availableTasks); mockSortTrellisObjects.mockReturnValue([task2, task1]); const result = await getNextAvailableIssue(mockRepository); expect(mockSortTrellisObjects).toHaveBeenCalledTimes(1); expect(mockSortTrellisObjects).toHaveBeenCalledWith(availableTasks); expect(result).toBe(task2); }); it("should handle utility function errors gracefully", async () => { const task = createMockTask(); const utilityError = new Error("Filtering failed"); mockRepository.getObjects.mockResolvedValue([task]); mockFilterUnavailableObjects.mockRejectedValue(utilityError); await expect(getNextAvailableIssue(mockRepository)).rejects.toThrow( "Filtering failed", ); expect(mockSortTrellisObjects).not.toHaveBeenCalled(); }); }); });

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/langadventurellc/task-trellis-mcp'

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