@blocks-network/mcp-server 0.1.62 → 0.1.64
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.
- package/LICENSE +26 -0
- package/README.md +32 -20
- package/dist/agent-status.d.ts +49 -0
- package/dist/agent-status.js +40 -0
- package/dist/billing.d.ts +50 -0
- package/dist/billing.js +72 -0
- package/dist/index.js +50 -4
- package/dist/protocol-headers.d.ts +11 -0
- package/dist/protocol-headers.js +11 -0
- package/dist/registry-list.d.ts +2 -4
- package/dist/registry-list.js +3 -4
- package/dist/tools.d.ts +47 -2
- package/dist/tools.js +169 -3
- package/package.json +1 -1
- package/src/agent-status.ts +89 -0
- package/src/billing.ts +122 -0
- package/src/index.ts +102 -3
- package/src/protocol-headers.ts +12 -0
- package/src/registry-list.ts +7 -5
- package/src/tools.ts +248 -5
- package/tests/billing.test.ts +203 -0
- package/tests/download-artifact.test.ts +142 -0
- package/tests/get-agent-status.test.ts +108 -0
- package/tests/helpers.ts +75 -13
- package/tests/list-agents.test.ts +6 -6
- package/tests/registry-list.test.ts +5 -5
- package/tests/task-lifecycle.test.ts +64 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { getAgentStatus } from '../src/tools.js';
|
|
3
|
+
import {
|
|
4
|
+
fetchAgentStatus,
|
|
5
|
+
type AgentStatusResponse,
|
|
6
|
+
} from '../src/agent-status.js';
|
|
7
|
+
import {
|
|
8
|
+
PROTOCOL_VERSION_HEADER,
|
|
9
|
+
CURRENT_PROTOCOL_VERSION,
|
|
10
|
+
} from '../src/protocol-headers.js';
|
|
11
|
+
import { makeFakeDeps } from './helpers.js';
|
|
12
|
+
|
|
13
|
+
function mockResponse(body: unknown, ok = true, status = 200): Response {
|
|
14
|
+
return { ok, status, json: async () => body } as Response;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe('get_agent_status (handler)', () => {
|
|
18
|
+
it('forwards baseUrl, apiKey and agentNames to fetchAgentStatus and emits JSON', async () => {
|
|
19
|
+
const result: AgentStatusResponse = {
|
|
20
|
+
agents: {
|
|
21
|
+
alice: {
|
|
22
|
+
agentName: 'alice',
|
|
23
|
+
instances: [],
|
|
24
|
+
onlineCount: 2,
|
|
25
|
+
totalActiveTasks: 1,
|
|
26
|
+
taskCount: 1,
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
const { deps, mocks } = makeFakeDeps({
|
|
31
|
+
apiKey: 'bk_test',
|
|
32
|
+
agentStatusResult: result,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
const res = await getAgentStatus({ agentNames: ['alice'] }, deps);
|
|
36
|
+
|
|
37
|
+
expect(mocks.fetchAgentStatus).toHaveBeenCalledWith({
|
|
38
|
+
baseUrl: 'http://api.test',
|
|
39
|
+
apiKey: 'bk_test',
|
|
40
|
+
agentNames: ['alice'],
|
|
41
|
+
});
|
|
42
|
+
const parsed = JSON.parse(res.content[0].text);
|
|
43
|
+
expect(parsed.agents.alice.onlineCount).toBe(2);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('fetchAgentStatus (HTTP helper)', () => {
|
|
48
|
+
it('builds /api/v1/agent-status URL with agentNames csv and protocol header', async () => {
|
|
49
|
+
const fetchImpl = vi
|
|
50
|
+
.fn()
|
|
51
|
+
.mockResolvedValue(mockResponse({ agents: {} }));
|
|
52
|
+
|
|
53
|
+
await fetchAgentStatus({
|
|
54
|
+
baseUrl: 'http://api.test/',
|
|
55
|
+
agentNames: ['alice', 'bob'],
|
|
56
|
+
apiKey: 'bk_test',
|
|
57
|
+
fetchImpl: fetchImpl as unknown as typeof fetch,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const [url, init] = fetchImpl.mock.calls[0];
|
|
61
|
+
const parsed = new URL(String(url));
|
|
62
|
+
expect(parsed.pathname).toBe('/api/v1/agent-status');
|
|
63
|
+
expect(parsed.searchParams.get('agentNames')).toBe('alice,bob');
|
|
64
|
+
const headers = init?.headers as Record<string, string>;
|
|
65
|
+
expect(headers[PROTOCOL_VERSION_HEADER]).toBe(CURRENT_PROTOCOL_VERSION);
|
|
66
|
+
expect(headers['Authorization']).toBe('Bearer bk_test');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('rejects empty input', async () => {
|
|
70
|
+
await expect(
|
|
71
|
+
fetchAgentStatus({
|
|
72
|
+
baseUrl: 'http://api.test',
|
|
73
|
+
agentNames: [],
|
|
74
|
+
}),
|
|
75
|
+
).rejects.toThrow(/at least one/);
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('rejects more than 50 names', async () => {
|
|
79
|
+
const names = Array.from({ length: 51 }, (_, i) => `a${i}`);
|
|
80
|
+
await expect(
|
|
81
|
+
fetchAgentStatus({
|
|
82
|
+
baseUrl: 'http://api.test',
|
|
83
|
+
agentNames: names,
|
|
84
|
+
}),
|
|
85
|
+
).rejects.toThrow(/at most 50/);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('rejects names with invalid characters', async () => {
|
|
89
|
+
await expect(
|
|
90
|
+
fetchAgentStatus({
|
|
91
|
+
baseUrl: 'http://api.test',
|
|
92
|
+
agentNames: ['has-a-hyphen'],
|
|
93
|
+
}),
|
|
94
|
+
).rejects.toThrow(/Invalid agent name/);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it('throws on non-OK responses', async () => {
|
|
98
|
+
const fetchImpl = vi.fn().mockResolvedValue(mockResponse({}, false, 500));
|
|
99
|
+
|
|
100
|
+
await expect(
|
|
101
|
+
fetchAgentStatus({
|
|
102
|
+
baseUrl: 'http://api.test',
|
|
103
|
+
agentNames: ['alice'],
|
|
104
|
+
fetchImpl: fetchImpl as unknown as typeof fetch,
|
|
105
|
+
}),
|
|
106
|
+
).rejects.toThrow('HTTP 500');
|
|
107
|
+
});
|
|
108
|
+
});
|
package/tests/helpers.ts
CHANGED
|
@@ -89,6 +89,9 @@ export interface FakeClientConfig {
|
|
|
89
89
|
export function makeFakeClient(cfg: FakeClientConfig = {}): TaskClientLike & {
|
|
90
90
|
sendMessageMock: ReturnType<typeof vi.fn>;
|
|
91
91
|
cancelTaskMock: ReturnType<typeof vi.fn>;
|
|
92
|
+
pauseTaskMock: ReturnType<typeof vi.fn>;
|
|
93
|
+
resumeTaskMock: ReturnType<typeof vi.fn>;
|
|
94
|
+
retryTaskMock: ReturnType<typeof vi.fn>;
|
|
92
95
|
connectMock: ReturnType<typeof vi.fn>;
|
|
93
96
|
listTasksMock: ReturnType<typeof vi.fn>;
|
|
94
97
|
getTaskMock: ReturnType<typeof vi.fn>;
|
|
@@ -97,6 +100,9 @@ export function makeFakeClient(cfg: FakeClientConfig = {}): TaskClientLike & {
|
|
|
97
100
|
const sendMessageMock = vi.fn().mockResolvedValue(session);
|
|
98
101
|
const connectMock = vi.fn().mockResolvedValue(session);
|
|
99
102
|
const cancelTaskMock = vi.fn().mockResolvedValue(undefined);
|
|
103
|
+
const pauseTaskMock = vi.fn().mockResolvedValue(undefined);
|
|
104
|
+
const resumeTaskMock = vi.fn().mockResolvedValue(undefined);
|
|
105
|
+
const retryTaskMock = vi.fn().mockResolvedValue(undefined);
|
|
100
106
|
const listTasksMock = vi.fn().mockResolvedValue(
|
|
101
107
|
cfg.listResult ?? { tasks: [], totalCount: 0 },
|
|
102
108
|
);
|
|
@@ -105,11 +111,17 @@ export function makeFakeClient(cfg: FakeClientConfig = {}): TaskClientLike & {
|
|
|
105
111
|
return {
|
|
106
112
|
sendMessage: sendMessageMock,
|
|
107
113
|
cancelTask: cancelTaskMock,
|
|
114
|
+
pauseTask: pauseTaskMock,
|
|
115
|
+
resumeTask: resumeTaskMock,
|
|
116
|
+
retryTask: retryTaskMock,
|
|
108
117
|
connect: connectMock,
|
|
109
118
|
listTasks: listTasksMock,
|
|
110
119
|
getTask: getTaskMock,
|
|
111
120
|
sendMessageMock,
|
|
112
121
|
cancelTaskMock,
|
|
122
|
+
pauseTaskMock,
|
|
123
|
+
resumeTaskMock,
|
|
124
|
+
retryTaskMock,
|
|
113
125
|
connectMock,
|
|
114
126
|
listTasksMock,
|
|
115
127
|
getTaskMock,
|
|
@@ -119,9 +131,13 @@ export function makeFakeClient(cfg: FakeClientConfig = {}): TaskClientLike & {
|
|
|
119
131
|
export interface FakeDepsOverrides {
|
|
120
132
|
baseUrl?: string;
|
|
121
133
|
apiKey?: string;
|
|
134
|
+
orgId?: string;
|
|
122
135
|
client?: TaskClientLike;
|
|
123
136
|
agentEntry?: AgentEntryLike | null;
|
|
124
137
|
listAgentsResult?: ListAgentsResult;
|
|
138
|
+
agentStatusResult?: { agents: Record<string, unknown> };
|
|
139
|
+
balance?: Record<string, unknown>;
|
|
140
|
+
topUpSession?: Record<string, unknown>;
|
|
125
141
|
maxUploadBytes?: number;
|
|
126
142
|
fileSize?: number;
|
|
127
143
|
}
|
|
@@ -130,36 +146,76 @@ export function makeFakeDeps(overrides: FakeDepsOverrides = {}) {
|
|
|
130
146
|
const client = overrides.client ?? makeFakeClient();
|
|
131
147
|
const getBaseUrl = vi.fn().mockResolvedValue(overrides.baseUrl ?? 'http://api.test');
|
|
132
148
|
const getApiKey = vi.fn().mockReturnValue(overrides.apiKey);
|
|
149
|
+
const getOrgId = vi.fn().mockReturnValue(overrides.orgId);
|
|
133
150
|
const getTaskClient = vi.fn().mockResolvedValue(client);
|
|
134
151
|
const getAgentByName = vi.fn().mockResolvedValue(overrides.agentEntry ?? null);
|
|
135
152
|
const listAgents = vi
|
|
136
153
|
.fn()
|
|
137
154
|
.mockResolvedValue(overrides.listAgentsResult ?? { agents: [], totalCount: 0 });
|
|
155
|
+
const fetchAgentStatus = vi
|
|
156
|
+
.fn<ToolDeps['fetchAgentStatus']>()
|
|
157
|
+
.mockResolvedValue(
|
|
158
|
+
(overrides.agentStatusResult ?? { agents: {} }) as Awaited<
|
|
159
|
+
ReturnType<ToolDeps['fetchAgentStatus']>
|
|
160
|
+
>,
|
|
161
|
+
);
|
|
162
|
+
const getConsumerBalance = vi
|
|
163
|
+
.fn<ToolDeps['getConsumerBalance']>()
|
|
164
|
+
.mockResolvedValue(
|
|
165
|
+
(overrides.balance ?? {
|
|
166
|
+
balance: '0',
|
|
167
|
+
reservedBalance: '0',
|
|
168
|
+
availableBalance: '0',
|
|
169
|
+
updatedAt: '2026-05-28T00:00:00Z',
|
|
170
|
+
}) as Awaited<ReturnType<ToolDeps['getConsumerBalance']>>,
|
|
171
|
+
);
|
|
172
|
+
const createConsumerTopUp = vi
|
|
173
|
+
.fn<ToolDeps['createConsumerTopUp']>()
|
|
174
|
+
.mockResolvedValue(
|
|
175
|
+
(overrides.topUpSession ?? {
|
|
176
|
+
checkoutUrl: 'https://stripe.test/checkout/abc',
|
|
177
|
+
sessionId: 'cs_test_default',
|
|
178
|
+
}) as Awaited<ReturnType<ToolDeps['createConsumerTopUp']>>,
|
|
179
|
+
);
|
|
138
180
|
const validateFilePath = vi.fn((p: string) => p);
|
|
181
|
+
const resolveSavePath = vi.fn((p: string) => p);
|
|
182
|
+
const writeFile = vi.fn();
|
|
139
183
|
const fileSize = vi.fn().mockReturnValue(overrides.fileSize ?? 100);
|
|
140
|
-
const filePartFromPath = vi.fn(
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
184
|
+
const filePartFromPath = vi.fn<ToolDeps['filePartFromPath']>(
|
|
185
|
+
async (path, opts) =>
|
|
186
|
+
({
|
|
187
|
+
type: 'file' as const,
|
|
188
|
+
partId: opts?.partId ?? 'file',
|
|
189
|
+
file: { id: 'file-id', mimeType: opts?.contentType ?? 'application/octet-stream' },
|
|
190
|
+
_path: path,
|
|
191
|
+
}) as Awaited<ReturnType<ToolDeps['filePartFromPath']>>,
|
|
192
|
+
);
|
|
193
|
+
const textPart = vi.fn<ToolDeps['textPart']>(
|
|
194
|
+
(text, partId) =>
|
|
195
|
+
({
|
|
196
|
+
type: 'text' as const,
|
|
197
|
+
partId,
|
|
198
|
+
text,
|
|
199
|
+
}) as ReturnType<ToolDeps['textPart']>,
|
|
200
|
+
);
|
|
151
201
|
|
|
152
202
|
const deps: ToolDeps = {
|
|
153
203
|
getBaseUrl,
|
|
154
204
|
getApiKey,
|
|
205
|
+
getOrgId,
|
|
155
206
|
getTaskClient,
|
|
156
207
|
getAgentByName,
|
|
157
208
|
listAgents,
|
|
209
|
+
fetchAgentStatus,
|
|
210
|
+
getConsumerBalance,
|
|
211
|
+
createConsumerTopUp,
|
|
158
212
|
validateFilePath,
|
|
213
|
+
resolveSavePath,
|
|
214
|
+
writeFile,
|
|
159
215
|
fileSize,
|
|
160
216
|
maxUploadBytes: overrides.maxUploadBytes ?? 25 * 1024 * 1024,
|
|
161
|
-
filePartFromPath
|
|
162
|
-
textPart
|
|
217
|
+
filePartFromPath,
|
|
218
|
+
textPart,
|
|
163
219
|
};
|
|
164
220
|
|
|
165
221
|
return {
|
|
@@ -168,10 +224,16 @@ export function makeFakeDeps(overrides: FakeDepsOverrides = {}) {
|
|
|
168
224
|
mocks: {
|
|
169
225
|
getBaseUrl,
|
|
170
226
|
getApiKey,
|
|
227
|
+
getOrgId,
|
|
171
228
|
getTaskClient,
|
|
172
229
|
getAgentByName,
|
|
173
230
|
listAgents,
|
|
231
|
+
fetchAgentStatus,
|
|
232
|
+
getConsumerBalance,
|
|
233
|
+
createConsumerTopUp,
|
|
174
234
|
validateFilePath,
|
|
235
|
+
resolveSavePath,
|
|
236
|
+
writeFile,
|
|
175
237
|
fileSize,
|
|
176
238
|
filePartFromPath,
|
|
177
239
|
textPart,
|
|
@@ -3,7 +3,7 @@ import { listAgents } from '../src/tools.js';
|
|
|
3
3
|
import { makeFakeDeps } from './helpers.js';
|
|
4
4
|
|
|
5
5
|
describe('list_agents', () => {
|
|
6
|
-
it('renders one row per agent with
|
|
6
|
+
it('renders one row per agent with tag names joined by commas', async () => {
|
|
7
7
|
const { deps } = makeFakeDeps({
|
|
8
8
|
listAgentsResult: {
|
|
9
9
|
agents: [
|
|
@@ -11,7 +11,7 @@ describe('list_agents', () => {
|
|
|
11
11
|
agentName: 'alice',
|
|
12
12
|
name: 'Alice',
|
|
13
13
|
listing: 'public',
|
|
14
|
-
|
|
14
|
+
tags: [
|
|
15
15
|
{ id: 'translate', name: 'Translate' },
|
|
16
16
|
{ id: 'summarize', name: 'Summarize' },
|
|
17
17
|
],
|
|
@@ -31,21 +31,21 @@ describe('list_agents', () => {
|
|
|
31
31
|
]);
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
-
it('forwards baseUrl, apiKey,
|
|
34
|
+
it('forwards baseUrl, apiKey, tag, listing, limit to the registry helper', async () => {
|
|
35
35
|
const { deps, mocks } = makeFakeDeps({
|
|
36
36
|
apiKey: 'sk-test',
|
|
37
37
|
baseUrl: 'http://api.test',
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
await listAgents(
|
|
41
|
-
{
|
|
41
|
+
{ tag: 'translate', listing: 'private', limit: 25 },
|
|
42
42
|
deps,
|
|
43
43
|
);
|
|
44
44
|
|
|
45
45
|
expect(mocks.listAgents).toHaveBeenCalledWith({
|
|
46
46
|
baseUrl: 'http://api.test',
|
|
47
47
|
apiKey: 'sk-test',
|
|
48
|
-
|
|
48
|
+
tag: 'translate',
|
|
49
49
|
listing: 'private',
|
|
50
50
|
limit: 25,
|
|
51
51
|
});
|
|
@@ -54,7 +54,7 @@ describe('list_agents', () => {
|
|
|
54
54
|
it('defaults to "public" for missing listing and uses agentName when name is absent', async () => {
|
|
55
55
|
const { deps } = makeFakeDeps({
|
|
56
56
|
listAgentsResult: {
|
|
57
|
-
agents: [{ agentName: 'naked',
|
|
57
|
+
agents: [{ agentName: 'naked', tags: [] }],
|
|
58
58
|
totalCount: 1,
|
|
59
59
|
},
|
|
60
60
|
});
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { listAgentsAuthenticated } from '../src/registry-list.js';
|
|
2
3
|
import {
|
|
3
|
-
listAgentsAuthenticated,
|
|
4
4
|
PROTOCOL_VERSION_HEADER,
|
|
5
5
|
CURRENT_PROTOCOL_VERSION,
|
|
6
|
-
} from '../src/
|
|
6
|
+
} from '../src/protocol-headers.js';
|
|
7
7
|
|
|
8
8
|
function mockResponse(body: unknown, ok = true, status = 200): Response {
|
|
9
9
|
return {
|
|
@@ -57,7 +57,7 @@ describe('listAgentsAuthenticated', () => {
|
|
|
57
57
|
await listAgentsAuthenticated({
|
|
58
58
|
baseUrl: 'http://api.test/',
|
|
59
59
|
listing: 'private',
|
|
60
|
-
|
|
60
|
+
tag: 'translate',
|
|
61
61
|
limit: 50,
|
|
62
62
|
fetchImpl: fetchImpl as unknown as typeof fetch,
|
|
63
63
|
});
|
|
@@ -68,7 +68,7 @@ describe('listAgentsAuthenticated', () => {
|
|
|
68
68
|
expect(parsed.searchParams.get('include')).toBe('full');
|
|
69
69
|
expect(parsed.searchParams.get('listing')).toBe('private');
|
|
70
70
|
expect(parsed.searchParams.get('scope')).toBe('owned');
|
|
71
|
-
expect(parsed.searchParams.get('
|
|
71
|
+
expect(parsed.searchParams.get('tag')).toBe('translate');
|
|
72
72
|
expect(parsed.searchParams.get('limit')).toBe('50');
|
|
73
73
|
});
|
|
74
74
|
|
|
@@ -104,7 +104,7 @@ describe('listAgentsAuthenticated', () => {
|
|
|
104
104
|
|
|
105
105
|
it('returns parsed JSON body on success', async () => {
|
|
106
106
|
const body = {
|
|
107
|
-
agents: [{ agentName: 'alice',
|
|
107
|
+
agents: [{ agentName: 'alice', tags: [] }],
|
|
108
108
|
totalCount: 1,
|
|
109
109
|
};
|
|
110
110
|
const fetchImpl = vi
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { pauseTask, resumeTask, retryTask } from '../src/tools.js';
|
|
3
|
+
import { makeFakeDeps, makeFakeClient } from './helpers.js';
|
|
4
|
+
|
|
5
|
+
describe('pause_task', () => {
|
|
6
|
+
it('forwards taskId to client.pauseTask and confirms', async () => {
|
|
7
|
+
const client = makeFakeClient();
|
|
8
|
+
const { deps } = makeFakeDeps({ client });
|
|
9
|
+
|
|
10
|
+
const res = await pauseTask({ taskId: 't_1' }, deps);
|
|
11
|
+
|
|
12
|
+
expect(client.pauseTaskMock).toHaveBeenCalledWith('t_1');
|
|
13
|
+
expect(res.content[0].text).toBe('Task t_1 paused.');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('propagates errors from pauseTask', async () => {
|
|
17
|
+
const client = makeFakeClient();
|
|
18
|
+
client.pauseTaskMock.mockRejectedValueOnce(new Error('already terminal'));
|
|
19
|
+
const { deps } = makeFakeDeps({ client });
|
|
20
|
+
|
|
21
|
+
await expect(pauseTask({ taskId: 't_err' }, deps)).rejects.toThrow('already terminal');
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe('resume_task', () => {
|
|
26
|
+
it('forwards taskId to client.resumeTask and confirms', async () => {
|
|
27
|
+
const client = makeFakeClient();
|
|
28
|
+
const { deps } = makeFakeDeps({ client });
|
|
29
|
+
|
|
30
|
+
const res = await resumeTask({ taskId: 't_2' }, deps);
|
|
31
|
+
|
|
32
|
+
expect(client.resumeTaskMock).toHaveBeenCalledWith('t_2');
|
|
33
|
+
expect(res.content[0].text).toBe('Task t_2 resumed.');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('propagates errors from resumeTask', async () => {
|
|
37
|
+
const client = makeFakeClient();
|
|
38
|
+
client.resumeTaskMock.mockRejectedValueOnce(new Error('not paused'));
|
|
39
|
+
const { deps } = makeFakeDeps({ client });
|
|
40
|
+
|
|
41
|
+
await expect(resumeTask({ taskId: 't_err' }, deps)).rejects.toThrow('not paused');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
describe('retry_task', () => {
|
|
46
|
+
it('forwards taskId to client.retryTask and confirms', async () => {
|
|
47
|
+
const client = makeFakeClient();
|
|
48
|
+
const { deps } = makeFakeDeps({ client });
|
|
49
|
+
|
|
50
|
+
const res = await retryTask({ taskId: 't_3' }, deps);
|
|
51
|
+
|
|
52
|
+
expect(client.retryTaskMock).toHaveBeenCalledWith('t_3');
|
|
53
|
+
expect(res.content[0].text).toBe('Task t_3 retry requested.');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('propagates errors from retryTask', async () => {
|
|
57
|
+
const client = makeFakeClient();
|
|
58
|
+
client.retryTaskMock.mockRejectedValueOnce(new Error('not failed'));
|
|
59
|
+
const { deps } = makeFakeDeps({ client });
|
|
60
|
+
|
|
61
|
+
await expect(retryTask({ taskId: 't_err' }, deps)).rejects.toThrow('not failed');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|