@cube8021/taskforge-mcp 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/dist/__tests__/api.test.d.ts +12 -0
  2. package/dist/__tests__/api.test.d.ts.map +1 -0
  3. package/dist/__tests__/api.test.js +157 -0
  4. package/dist/__tests__/api.test.js.map +1 -0
  5. package/dist/__tests__/e2e/mcp-protocol.test.d.ts +8 -0
  6. package/dist/__tests__/e2e/mcp-protocol.test.d.ts.map +1 -0
  7. package/dist/__tests__/e2e/mcp-protocol.test.js +201 -0
  8. package/dist/__tests__/e2e/mcp-protocol.test.js.map +1 -0
  9. package/dist/__tests__/setup.d.ts +8 -0
  10. package/dist/__tests__/setup.d.ts.map +1 -0
  11. package/dist/__tests__/setup.js +24 -0
  12. package/dist/__tests__/setup.js.map +1 -0
  13. package/dist/__tests__/tools/features.test.d.ts +5 -0
  14. package/dist/__tests__/tools/features.test.d.ts.map +1 -0
  15. package/dist/__tests__/tools/features.test.js +119 -0
  16. package/dist/__tests__/tools/features.test.js.map +1 -0
  17. package/dist/__tests__/tools/projects.test.d.ts +7 -0
  18. package/dist/__tests__/tools/projects.test.d.ts.map +1 -0
  19. package/dist/__tests__/tools/projects.test.js +153 -0
  20. package/dist/__tests__/tools/projects.test.js.map +1 -0
  21. package/dist/__tests__/tools/sprints.test.d.ts +5 -0
  22. package/dist/__tests__/tools/sprints.test.d.ts.map +1 -0
  23. package/dist/__tests__/tools/sprints.test.js +125 -0
  24. package/dist/__tests__/tools/sprints.test.js.map +1 -0
  25. package/dist/__tests__/tools/tasks.test.d.ts +7 -0
  26. package/dist/__tests__/tools/tasks.test.d.ts.map +1 -0
  27. package/dist/__tests__/tools/tasks.test.js +214 -0
  28. package/dist/__tests__/tools/tasks.test.js.map +1 -0
  29. package/dist/index.d.ts +16 -1
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +115 -85
  32. package/dist/index.js.map +1 -1
  33. package/package.json +11 -3
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Unit tests for the apiRequest function
3
+ *
4
+ * Tests cover:
5
+ * - Successful GET/POST/PUT/DELETE requests
6
+ * - Error handling (401, 404, 500)
7
+ * - Network errors
8
+ * - Query parameter handling
9
+ * - Request body serialization
10
+ */
11
+ export {};
12
+ //# sourceMappingURL=api.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/api.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG"}
@@ -0,0 +1,157 @@
1
+ /**
2
+ * Unit tests for the apiRequest function
3
+ *
4
+ * Tests cover:
5
+ * - Successful GET/POST/PUT/DELETE requests
6
+ * - Error handling (401, 404, 500)
7
+ * - Network errors
8
+ * - Query parameter handling
9
+ * - Request body serialization
10
+ */
11
+ import { describe, it, expect, beforeEach } from 'vitest';
12
+ import { fetchMocker } from './setup.js';
13
+ import { apiRequest, mcpResponse, getConfig } from '../index.js';
14
+ describe('apiRequest', () => {
15
+ beforeEach(() => {
16
+ fetchMocker.resetMocks();
17
+ });
18
+ describe('GET requests', () => {
19
+ it('should make a GET request and return parsed JSON', async () => {
20
+ const mockData = { data: { id: 1, name: 'Test Project' } };
21
+ fetchMocker.mockResponseOnce(JSON.stringify(mockData));
22
+ const result = await apiRequest('GET', '/api/v1/projects/1');
23
+ expect(result).toEqual(mockData);
24
+ expect(fetchMocker).toHaveBeenCalledTimes(1);
25
+ const [url, options] = fetchMocker.mock.calls[0];
26
+ expect(url).toBe('https://test-api.taskforge.local/api/v1/projects/1');
27
+ expect(options?.method).toBe('GET');
28
+ expect(options?.headers).toEqual({
29
+ 'Authorization': 'Bearer test-token-12345',
30
+ 'Content-Type': 'application/json',
31
+ });
32
+ });
33
+ it('should append query parameters to GET requests', async () => {
34
+ const mockData = { data: [] };
35
+ fetchMocker.mockResponseOnce(JSON.stringify(mockData));
36
+ await apiRequest('GET', '/api/v1/tasks', undefined, {
37
+ project_id: 1,
38
+ status: 'todo',
39
+ limit: 10,
40
+ });
41
+ const [url] = fetchMocker.mock.calls[0];
42
+ expect(url).toContain('project_id=1');
43
+ expect(url).toContain('status=todo');
44
+ expect(url).toContain('limit=10');
45
+ });
46
+ it('should ignore undefined query parameters', async () => {
47
+ fetchMocker.mockResponseOnce(JSON.stringify({ data: [] }));
48
+ await apiRequest('GET', '/api/v1/tasks', undefined, {
49
+ project_id: 1,
50
+ status: undefined,
51
+ feature_id: undefined,
52
+ });
53
+ const [url] = fetchMocker.mock.calls[0];
54
+ expect(url).toContain('project_id=1');
55
+ expect(url).not.toContain('status=');
56
+ expect(url).not.toContain('feature_id=');
57
+ });
58
+ });
59
+ describe('POST requests', () => {
60
+ it('should make a POST request with JSON body', async () => {
61
+ const mockData = { data: { id: 1, name: 'New Project' } };
62
+ fetchMocker.mockResponseOnce(JSON.stringify(mockData));
63
+ const body = { name: 'New Project', description: 'Test' };
64
+ const result = await apiRequest('POST', '/api/v1/projects', body);
65
+ expect(result).toEqual(mockData);
66
+ const [, options] = fetchMocker.mock.calls[0];
67
+ expect(options?.method).toBe('POST');
68
+ expect(options?.body).toBe(JSON.stringify(body));
69
+ });
70
+ it('should not include body for GET requests even if provided', async () => {
71
+ fetchMocker.mockResponseOnce(JSON.stringify({ data: {} }));
72
+ await apiRequest('GET', '/api/v1/projects/1', { ignored: 'body' });
73
+ const [, options] = fetchMocker.mock.calls[0];
74
+ expect(options?.body).toBeUndefined();
75
+ });
76
+ });
77
+ describe('PUT requests', () => {
78
+ it('should make a PUT request with JSON body', async () => {
79
+ const mockData = { data: { id: 1, name: 'Updated Project' } };
80
+ fetchMocker.mockResponseOnce(JSON.stringify(mockData));
81
+ const body = { name: 'Updated Project' };
82
+ await apiRequest('PUT', '/api/v1/projects/1', body);
83
+ const [, options] = fetchMocker.mock.calls[0];
84
+ expect(options?.method).toBe('PUT');
85
+ expect(options?.body).toBe(JSON.stringify(body));
86
+ });
87
+ });
88
+ describe('DELETE requests', () => {
89
+ it('should make a DELETE request', async () => {
90
+ const mockData = { message: 'Deleted successfully' };
91
+ fetchMocker.mockResponseOnce(JSON.stringify(mockData));
92
+ await apiRequest('DELETE', '/api/v1/projects/1');
93
+ const [, options] = fetchMocker.mock.calls[0];
94
+ expect(options?.method).toBe('DELETE');
95
+ });
96
+ });
97
+ describe('error handling', () => {
98
+ it('should throw error with message from API response on 401', async () => {
99
+ fetchMocker.mockResponseOnce(JSON.stringify({ error: 'Invalid or expired token' }), { status: 401 });
100
+ await expect(apiRequest('GET', '/api/v1/projects')).rejects.toThrow('Invalid or expired token');
101
+ });
102
+ it('should throw error with message from API response on 404', async () => {
103
+ fetchMocker.mockResponseOnce(JSON.stringify({ message: 'Project not found' }), { status: 404 });
104
+ await expect(apiRequest('GET', '/api/v1/projects/999')).rejects.toThrow('Project not found');
105
+ });
106
+ it('should throw error with status code if no error message', async () => {
107
+ fetchMocker.mockResponseOnce(JSON.stringify({}), { status: 500 });
108
+ await expect(apiRequest('GET', '/api/v1/projects')).rejects.toThrow('API error: 500');
109
+ });
110
+ it('should throw error on invalid JSON response', async () => {
111
+ fetchMocker.mockResponseOnce('not valid json');
112
+ await expect(apiRequest('GET', '/api/v1/projects')).rejects.toThrow('Invalid JSON response');
113
+ });
114
+ it('should throw on network failure', async () => {
115
+ fetchMocker.mockRejectOnce(new Error('Network error'));
116
+ await expect(apiRequest('GET', '/api/v1/projects')).rejects.toThrow('Network error');
117
+ });
118
+ });
119
+ });
120
+ describe('mcpResponse', () => {
121
+ it('should format data as MCP text content', () => {
122
+ const data = { id: 1, name: 'Test' };
123
+ const result = mcpResponse(data);
124
+ expect(result).toEqual({
125
+ content: [
126
+ {
127
+ type: 'text',
128
+ text: JSON.stringify(data, null, 2),
129
+ },
130
+ ],
131
+ });
132
+ });
133
+ it('should handle arrays', () => {
134
+ const data = [{ id: 1 }, { id: 2 }];
135
+ const result = mcpResponse(data);
136
+ expect(result.content[0].text).toBe(JSON.stringify(data, null, 2));
137
+ });
138
+ it('should handle null', () => {
139
+ const result = mcpResponse(null);
140
+ expect(result.content[0].text).toBe('null');
141
+ });
142
+ });
143
+ describe('getConfig', () => {
144
+ it('should return current config from environment', () => {
145
+ const config = getConfig();
146
+ expect(config.API_URL).toBe('https://test-api.taskforge.local');
147
+ expect(config.API_TOKEN).toBe('test-token-12345');
148
+ });
149
+ it('should use default API_URL if not set', () => {
150
+ const originalUrl = process.env.API_URL;
151
+ delete process.env.API_URL;
152
+ const config = getConfig();
153
+ expect(config.API_URL).toBe('https://taskforge.support.tools');
154
+ process.env.API_URL = originalUrl;
155
+ });
156
+ });
157
+ //# sourceMappingURL=api.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"api.test.js","sourceRoot":"","sources":["../../src/__tests__/api.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * E2E tests for the MCP protocol
3
+ *
4
+ * These tests spawn the actual MCP server and communicate with it
5
+ * over stdio using JSON-RPC 2.0.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=mcp-protocol.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-protocol.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/e2e/mcp-protocol.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
@@ -0,0 +1,201 @@
1
+ /**
2
+ * E2E tests for the MCP protocol
3
+ *
4
+ * These tests spawn the actual MCP server and communicate with it
5
+ * over stdio using JSON-RPC 2.0.
6
+ */
7
+ import { describe, it, expect, beforeAll, afterAll } from 'vitest';
8
+ import { spawn } from 'child_process';
9
+ import { join } from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import { dirname } from 'path';
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = dirname(__filename);
14
+ // Path to the built index.js
15
+ const SERVER_PATH = join(__dirname, '../../../dist/index.js');
16
+ /**
17
+ * Helper class to communicate with MCP server over stdio
18
+ */
19
+ class McpClient {
20
+ process = null;
21
+ messageId = 0;
22
+ pendingRequests = new Map();
23
+ buffer = '';
24
+ async start() {
25
+ return new Promise((resolve, reject) => {
26
+ this.process = spawn('node', [SERVER_PATH], {
27
+ env: {
28
+ ...process.env,
29
+ API_URL: 'https://test-api.taskforge.local',
30
+ TASKFORGE_API_TOKEN: 'test-token-12345',
31
+ },
32
+ stdio: ['pipe', 'pipe', 'pipe'],
33
+ });
34
+ this.process.stdout?.on('data', (data) => {
35
+ this.handleData(data.toString());
36
+ });
37
+ this.process.stderr?.on('data', (data) => {
38
+ // Server logs to stderr, which is normal
39
+ const message = data.toString();
40
+ if (message.includes('TaskForge MCP server started')) {
41
+ resolve();
42
+ }
43
+ });
44
+ this.process.on('error', (error) => {
45
+ reject(error);
46
+ });
47
+ // Give server time to start
48
+ setTimeout(() => resolve(), 500);
49
+ });
50
+ }
51
+ handleData(data) {
52
+ this.buffer += data;
53
+ // MCP messages are newline-delimited JSON
54
+ const lines = this.buffer.split('\n');
55
+ this.buffer = lines.pop() || '';
56
+ for (const line of lines) {
57
+ if (line.trim()) {
58
+ try {
59
+ const response = JSON.parse(line);
60
+ const pending = this.pendingRequests.get(response.id);
61
+ if (pending) {
62
+ pending.resolve(response);
63
+ this.pendingRequests.delete(response.id);
64
+ }
65
+ }
66
+ catch {
67
+ // Ignore non-JSON lines (like server logs)
68
+ }
69
+ }
70
+ }
71
+ }
72
+ async send(method, params) {
73
+ if (!this.process?.stdin) {
74
+ throw new Error('Server not started');
75
+ }
76
+ const id = ++this.messageId;
77
+ const request = {
78
+ jsonrpc: '2.0',
79
+ id,
80
+ method,
81
+ params,
82
+ };
83
+ return new Promise((resolve, reject) => {
84
+ this.pendingRequests.set(id, { resolve, reject });
85
+ const message = JSON.stringify(request) + '\n';
86
+ this.process.stdin.write(message);
87
+ // Timeout after 5 seconds
88
+ setTimeout(() => {
89
+ if (this.pendingRequests.has(id)) {
90
+ this.pendingRequests.delete(id);
91
+ reject(new Error(`Request ${id} timed out`));
92
+ }
93
+ }, 5000);
94
+ });
95
+ }
96
+ stop() {
97
+ if (this.process) {
98
+ this.process.kill();
99
+ this.process = null;
100
+ }
101
+ }
102
+ }
103
+ describe('MCP Protocol E2E', () => {
104
+ let client;
105
+ beforeAll(async () => {
106
+ client = new McpClient();
107
+ await client.start();
108
+ });
109
+ afterAll(() => {
110
+ client.stop();
111
+ });
112
+ describe('initialization', () => {
113
+ it('should respond to initialize request', async () => {
114
+ const response = await client.send('initialize', {
115
+ protocolVersion: '2024-11-05',
116
+ capabilities: {},
117
+ clientInfo: {
118
+ name: 'test-client',
119
+ version: '1.0.0',
120
+ },
121
+ });
122
+ expect(response.error).toBeUndefined();
123
+ expect(response.result).toBeDefined();
124
+ const result = response.result;
125
+ expect(result.protocolVersion).toBeDefined();
126
+ expect(result.capabilities).toBeDefined();
127
+ expect(result.serverInfo.name).toBe('taskforge-mcp');
128
+ });
129
+ });
130
+ describe('tools/list', () => {
131
+ it('should list all 59 tools', async () => {
132
+ // First initialize
133
+ await client.send('initialize', {
134
+ protocolVersion: '2024-11-05',
135
+ capabilities: {},
136
+ clientInfo: { name: 'test', version: '1.0.0' },
137
+ });
138
+ const response = await client.send('tools/list', {});
139
+ expect(response.error).toBeUndefined();
140
+ expect(response.result).toBeDefined();
141
+ const result = response.result;
142
+ expect(result.tools).toBeInstanceOf(Array);
143
+ expect(result.tools.length).toBe(59);
144
+ // Verify some expected tools exist
145
+ const toolNames = result.tools.map((t) => t.name);
146
+ expect(toolNames).toContain('createProject');
147
+ expect(toolNames).toContain('getTasks');
148
+ expect(toolNames).toContain('createFeature');
149
+ expect(toolNames).toContain('createSprint');
150
+ expect(toolNames).toContain('getExecutionOrder');
151
+ });
152
+ it('should include tool descriptions', async () => {
153
+ const response = await client.send('tools/list', {});
154
+ const result = response.result;
155
+ const createProjectTool = result.tools.find((t) => t.name === 'createProject');
156
+ expect(createProjectTool).toBeDefined();
157
+ expect(createProjectTool?.description).toBeTruthy();
158
+ });
159
+ });
160
+ });
161
+ describe('MCP Server startup', () => {
162
+ it('should fail to start without API token', async () => {
163
+ return new Promise((resolve) => {
164
+ let foundErrorMessage = false;
165
+ let hasResolved = false;
166
+ const proc = spawn('node', [SERVER_PATH], {
167
+ env: {
168
+ ...process.env,
169
+ API_URL: 'https://test-api.taskforge.local',
170
+ TASKFORGE_API_TOKEN: '', // Empty token
171
+ },
172
+ stdio: ['pipe', 'pipe', 'pipe'],
173
+ });
174
+ proc.stderr?.on('data', (data) => {
175
+ const message = data.toString();
176
+ if (message.includes('TASKFORGE_API_TOKEN environment variable is required')) {
177
+ foundErrorMessage = true;
178
+ }
179
+ });
180
+ proc.on('exit', (code) => {
181
+ if (hasResolved)
182
+ return;
183
+ hasResolved = true;
184
+ // The process should either exit with code 1 OR we should have seen the error message
185
+ expect(foundErrorMessage || code === 1).toBe(true);
186
+ resolve();
187
+ });
188
+ // Timeout - if process doesn't exit naturally
189
+ setTimeout(() => {
190
+ if (hasResolved)
191
+ return;
192
+ hasResolved = true;
193
+ // Should have at least seen the error message
194
+ expect(foundErrorMessage).toBe(true);
195
+ proc.kill();
196
+ resolve();
197
+ }, 2000);
198
+ });
199
+ });
200
+ });
201
+ //# sourceMappingURL=mcp-protocol.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-protocol.test.js","sourceRoot":"","sources":["../../../src/__tests__/e2e/mcp-protocol.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Test setup file for Vitest
3
+ *
4
+ * This file runs before all tests and sets up global mocks.
5
+ */
6
+ declare const fetchMocker: import("vitest-fetch-mock").FetchMock;
7
+ export { fetchMocker };
8
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,QAAA,MAAM,WAAW,uCAAsB,CAAC;AAkBxC,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Test setup file for Vitest
3
+ *
4
+ * This file runs before all tests and sets up global mocks.
5
+ */
6
+ import createFetchMock from 'vitest-fetch-mock';
7
+ import { vi, beforeEach, afterEach } from 'vitest';
8
+ // Create fetch mock
9
+ const fetchMocker = createFetchMock(vi);
10
+ // Enable fetch mocking globally
11
+ fetchMocker.enableMocks();
12
+ // Reset mocks before each test
13
+ beforeEach(() => {
14
+ fetchMocker.resetMocks();
15
+ // Set default env vars for testing
16
+ process.env.API_URL = 'https://test-api.taskforge.local';
17
+ process.env.TASKFORGE_API_TOKEN = 'test-token-12345';
18
+ });
19
+ // Clean up after each test
20
+ afterEach(() => {
21
+ vi.restoreAllMocks();
22
+ });
23
+ export { fetchMocker };
24
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/__tests__/setup.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,eAAe,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,EAAE,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AAEnD,oBAAoB;AACpB,MAAM,WAAW,GAAG,eAAe,CAAC,EAAE,CAAC,CAAC;AAExC,gCAAgC;AAChC,WAAW,CAAC,WAAW,EAAE,CAAC;AAE1B,+BAA+B;AAC/B,UAAU,CAAC,GAAG,EAAE;IACd,WAAW,CAAC,UAAU,EAAE,CAAC;IACzB,mCAAmC;IACnC,OAAO,CAAC,GAAG,CAAC,OAAO,GAAG,kCAAkC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,mBAAmB,GAAG,kBAAkB,CAAC;AACvD,CAAC,CAAC,CAAC;AAEH,2BAA2B;AAC3B,SAAS,CAAC,GAAG,EAAE;IACb,EAAE,CAAC,eAAe,EAAE,CAAC;AACvB,CAAC,CAAC,CAAC;AAEH,OAAO,EAAE,WAAW,EAAE,CAAC"}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Unit tests for Feature-related API requests
3
+ */
4
+ export {};
5
+ //# sourceMappingURL=features.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/tools/features.test.ts"],"names":[],"mappings":"AAAA;;GAEG"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Unit tests for Feature-related API requests
3
+ */
4
+ import { describe, it, expect, beforeEach } from 'vitest';
5
+ import { fetchMocker } from '../setup.js';
6
+ import { apiRequest } from '../../index.js';
7
+ describe('Feature API Requests', () => {
8
+ beforeEach(() => {
9
+ fetchMocker.resetMocks();
10
+ });
11
+ describe('createFeature', () => {
12
+ it('should POST to /api/v1/features', async () => {
13
+ const mockResponse = {
14
+ data: { id: 1, name: 'Feature', project_id: 1 },
15
+ };
16
+ fetchMocker.mockResponseOnce(JSON.stringify(mockResponse));
17
+ await apiRequest('POST', '/api/v1/features', {
18
+ project_id: 1,
19
+ name: 'Feature',
20
+ description: 'A feature',
21
+ });
22
+ const [url, options] = fetchMocker.mock.calls[0];
23
+ expect(url).toBe('https://test-api.taskforge.local/api/v1/features');
24
+ expect(options?.method).toBe('POST');
25
+ const body = JSON.parse(options?.body);
26
+ expect(body.project_id).toBe(1);
27
+ expect(body.name).toBe('Feature');
28
+ });
29
+ });
30
+ describe('listFeatures', () => {
31
+ it('should GET /api/v1/features with project filter', async () => {
32
+ fetchMocker.mockResponseOnce(JSON.stringify({ data: [], total: 0 }));
33
+ await apiRequest('GET', '/api/v1/features', undefined, {
34
+ project_id: 1,
35
+ limit: 10,
36
+ offset: 0,
37
+ });
38
+ const url = fetchMocker.mock.calls[0][0];
39
+ expect(url).toContain('project_id=1');
40
+ expect(url).toContain('limit=10');
41
+ });
42
+ });
43
+ describe('getFeature', () => {
44
+ it('should GET /api/v1/features/:id', async () => {
45
+ const mockFeature = { data: { id: 1, name: 'Feature' } };
46
+ fetchMocker.mockResponseOnce(JSON.stringify(mockFeature));
47
+ const result = await apiRequest('GET', '/api/v1/features/1');
48
+ expect(result).toEqual(mockFeature);
49
+ expect(fetchMocker.mock.calls[0][0]).toContain('/api/v1/features/1');
50
+ });
51
+ });
52
+ describe('updateFeature', () => {
53
+ it('should PUT to /api/v1/features/:id', async () => {
54
+ fetchMocker.mockResponseOnce(JSON.stringify({ data: { id: 1, name: 'Updated' } }));
55
+ await apiRequest('PUT', '/api/v1/features/1', {
56
+ name: 'Updated',
57
+ description: 'New description',
58
+ });
59
+ const [url, options] = fetchMocker.mock.calls[0];
60
+ expect(url).toBe('https://test-api.taskforge.local/api/v1/features/1');
61
+ expect(options?.method).toBe('PUT');
62
+ });
63
+ });
64
+ describe('deleteFeature', () => {
65
+ it('should DELETE /api/v1/features/:id', async () => {
66
+ fetchMocker.mockResponseOnce(JSON.stringify({ message: 'Deleted' }));
67
+ await apiRequest('DELETE', '/api/v1/features/1');
68
+ const [url, options] = fetchMocker.mock.calls[0];
69
+ expect(url).toBe('https://test-api.taskforge.local/api/v1/features/1');
70
+ expect(options?.method).toBe('DELETE');
71
+ });
72
+ });
73
+ describe('createSubFeature', () => {
74
+ it('should POST to /api/v1/features/:parentId/subfeatures', async () => {
75
+ fetchMocker.mockResponseOnce(JSON.stringify({ data: { id: 2, parent_id: 1 } }));
76
+ await apiRequest('POST', '/api/v1/features/1/subfeatures', {
77
+ name: 'Sub Feature',
78
+ description: 'A sub-feature',
79
+ });
80
+ const [url] = fetchMocker.mock.calls[0];
81
+ expect(url).toBe('https://test-api.taskforge.local/api/v1/features/1/subfeatures');
82
+ });
83
+ });
84
+ describe('moveFeature', () => {
85
+ it('should PUT to /api/v1/features/:id/move', async () => {
86
+ fetchMocker.mockResponseOnce(JSON.stringify({ data: { id: 1, parent_id: 2 } }));
87
+ await apiRequest('PUT', '/api/v1/features/1/move', {
88
+ new_parent_id: 2,
89
+ });
90
+ const [url, options] = fetchMocker.mock.calls[0];
91
+ expect(url).toBe('https://test-api.taskforge.local/api/v1/features/1/move');
92
+ expect(options?.method).toBe('PUT');
93
+ });
94
+ it('should allow moving to root (null parent)', async () => {
95
+ fetchMocker.mockResponseOnce(JSON.stringify({ data: { id: 1, parent_id: null } }));
96
+ await apiRequest('PUT', '/api/v1/features/1/move', {
97
+ new_parent_id: null,
98
+ });
99
+ const body = JSON.parse(fetchMocker.mock.calls[0][1]?.body);
100
+ expect(body.new_parent_id).toBeNull();
101
+ });
102
+ });
103
+ describe('getFeatureTree', () => {
104
+ it('should GET /api/v1/features/:id/tree', async () => {
105
+ const mockTree = {
106
+ data: {
107
+ id: 1,
108
+ name: 'Parent',
109
+ children: [{ id: 2, name: 'Child' }],
110
+ },
111
+ };
112
+ fetchMocker.mockResponseOnce(JSON.stringify(mockTree));
113
+ const result = await apiRequest('GET', '/api/v1/features/1/tree');
114
+ expect(result).toEqual(mockTree);
115
+ expect(fetchMocker.mock.calls[0][0]).toContain('/api/v1/features/1/tree');
116
+ });
117
+ });
118
+ });
119
+ //# sourceMappingURL=features.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"features.test.js","sourceRoot":"","sources":["../../../src/__tests__/tools/features.test.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC1D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE5C,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,UAAU,CAAC,GAAG,EAAE;QACd,WAAW,CAAC,UAAU,EAAE,CAAC;IAC3B,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;YAC/C,MAAM,YAAY,GAAG;gBACnB,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC,EAAE;aAChD,CAAC;YACF,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC,CAAC;YAE3D,MAAM,UAAU,CAAC,MAAM,EAAE,kBAAkB,EAAE;gBAC3C,UAAU,EAAE,CAAC;gBACb,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,WAAW;aACzB,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,kDAAkD,CAAC,CAAC;YACrE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACrC,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,IAAc,CAAC,CAAC;YACjD,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAChC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,cAAc,EAAE,GAAG,EAAE;QAC5B,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;YAC/D,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAErE,MAAM,UAAU,CAAC,KAAK,EAAE,kBAAkB,EAAE,SAAS,EAAE;gBACrD,UAAU,EAAE,CAAC;gBACb,KAAK,EAAE,EAAE;gBACT,MAAM,EAAE,CAAC;aACV,CAAC,CAAC;YAEH,MAAM,GAAG,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;YACzC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,cAAc,CAAC,CAAC;YACtC,MAAM,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;QACpC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;QAC1B,EAAE,CAAC,iCAAiC,EAAE,KAAK,IAAI,EAAE;YAC/C,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;YACzD,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,CAAC,CAAC,CAAC;YAE1D,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,oBAAoB,CAAC,CAAC;YAE7D,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,CAAC;YACpC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,oBAAoB,CAAC,CAAC;QACvE,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;YAClD,WAAW,CAAC,gBAAgB,CAC1B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC,CACrD,CAAC;YAEF,MAAM,UAAU,CAAC,KAAK,EAAE,oBAAoB,EAAE;gBAC5C,IAAI,EAAE,SAAS;gBACf,WAAW,EAAE,iBAAiB;aAC/B,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;YACvE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;QAC7B,EAAE,CAAC,oCAAoC,EAAE,KAAK,IAAI,EAAE;YAClD,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC,CAAC;YAErE,MAAM,UAAU,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;YAEjD,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;YACvE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,kBAAkB,EAAE,GAAG,EAAE;QAChC,EAAE,CAAC,uDAAuD,EAAE,KAAK,IAAI,EAAE;YACrE,WAAW,CAAC,gBAAgB,CAC1B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAClD,CAAC;YAEF,MAAM,UAAU,CAAC,MAAM,EAAE,gCAAgC,EAAE;gBACzD,IAAI,EAAE,aAAa;gBACnB,WAAW,EAAE,eAAe;aAC7B,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACxC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CACd,gEAAgE,CACjE,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;QAC3B,EAAE,CAAC,yCAAyC,EAAE,KAAK,IAAI,EAAE;YACvD,WAAW,CAAC,gBAAgB,CAC1B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,EAAE,CAAC,CAClD,CAAC;YAEF,MAAM,UAAU,CAAC,KAAK,EAAE,yBAAyB,EAAE;gBACjD,aAAa,EAAE,CAAC;aACjB,CAAC,CAAC;YAEH,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YACjD,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CACd,yDAAyD,CAC1D,CAAC;YACF,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtC,CAAC,CAAC,CAAC;QAEH,EAAE,CAAC,2CAA2C,EAAE,KAAK,IAAI,EAAE;YACzD,WAAW,CAAC,gBAAgB,CAC1B,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,CACrD,CAAC;YAEF,MAAM,UAAU,CAAC,KAAK,EAAE,yBAAyB,EAAE;gBACjD,aAAa,EAAE,IAAI;aACpB,CAAC,CAAC;YAEH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,IAAc,CAAC,CAAC;YACtE,MAAM,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,QAAQ,EAAE,CAAC;QACxC,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,QAAQ,CAAC,gBAAgB,EAAE,GAAG,EAAE;QAC9B,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;YACpD,MAAM,QAAQ,GAAG;gBACf,IAAI,EAAE;oBACJ,EAAE,EAAE,CAAC;oBACL,IAAI,EAAE,QAAQ;oBACd,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;iBACrC;aACF,CAAC;YACF,WAAW,CAAC,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC;YAEvD,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,KAAK,EAAE,yBAAyB,CAAC,CAAC;YAElE,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YACjC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,yBAAyB,CAAC,CAAC;QAC5E,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Unit tests for Project-related API requests
3
+ *
4
+ * Tests the API request patterns used by project tools
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=projects.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"projects.test.d.ts","sourceRoot":"","sources":["../../../src/__tests__/tools/projects.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}