@apify/actors-mcp-server 0.14.4-beta.1 → 0.14.4-beta.2
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/dist/apify_client.d.ts.map +1 -1
- package/dist/apify_client.js +3 -2
- package/dist/apify_client.js.map +1 -1
- package/dist/index_internals_test_kit.d.ts +11 -0
- package/dist/index_internals_test_kit.d.ts.map +1 -0
- package/dist/index_internals_test_kit.js +15 -0
- package/dist/index_internals_test_kit.js.map +1 -0
- package/dist/test_kit/cases/actors.cases.d.ts +4 -0
- package/dist/test_kit/cases/actors.cases.d.ts.map +1 -0
- package/dist/test_kit/cases/actors.cases.js +911 -0
- package/dist/test_kit/cases/actors.cases.js.map +1 -0
- package/dist/test_kit/cases/apps.cases.d.ts +4 -0
- package/dist/test_kit/cases/apps.cases.d.ts.map +1 -0
- package/dist/test_kit/cases/apps.cases.js +111 -0
- package/dist/test_kit/cases/apps.cases.js.map +1 -0
- package/dist/test_kit/cases/payments.cases.d.ts +4 -0
- package/dist/test_kit/cases/payments.cases.d.ts.map +1 -0
- package/dist/test_kit/cases/payments.cases.js +207 -0
- package/dist/test_kit/cases/payments.cases.js.map +1 -0
- package/dist/test_kit/cases/registration.cases.d.ts +4 -0
- package/dist/test_kit/cases/registration.cases.d.ts.map +1 -0
- package/dist/test_kit/cases/registration.cases.js +431 -0
- package/dist/test_kit/cases/registration.cases.js.map +1 -0
- package/dist/test_kit/cases/storage.cases.d.ts +4 -0
- package/dist/test_kit/cases/storage.cases.d.ts.map +1 -0
- package/dist/test_kit/cases/storage.cases.js +378 -0
- package/dist/test_kit/cases/storage.cases.js.map +1 -0
- package/dist/test_kit/cases/tasks.cases.d.ts +4 -0
- package/dist/test_kit/cases/tasks.cases.d.ts.map +1 -0
- package/dist/test_kit/cases/tasks.cases.js +320 -0
- package/dist/test_kit/cases/tasks.cases.js.map +1 -0
- package/dist/test_kit/cases/tools.cases.d.ts +4 -0
- package/dist/test_kit/cases/tools.cases.d.ts.map +1 -0
- package/dist/test_kit/cases/tools.cases.js +204 -0
- package/dist/test_kit/cases/tools.cases.js.map +1 -0
- package/dist/test_kit/helpers.d.ts +69 -0
- package/dist/test_kit/helpers.d.ts.map +1 -0
- package/dist/test_kit/helpers.js +223 -0
- package/dist/test_kit/helpers.js.map +1 -0
- package/dist/test_kit/index.d.ts +20 -0
- package/dist/test_kit/index.d.ts.map +1 -0
- package/dist/test_kit/index.js +32 -0
- package/dist/test_kit/index.js.map +1 -0
- package/dist/test_kit/mcp_client.d.ts +31 -0
- package/dist/test_kit/mcp_client.d.ts.map +1 -0
- package/dist/test_kit/mcp_client.js +73 -0
- package/dist/test_kit/mcp_client.js.map +1 -0
- package/dist/test_kit/register.d.ts +9 -0
- package/dist/test_kit/register.d.ts.map +1 -0
- package/dist/test_kit/register.js +39 -0
- package/dist/test_kit/register.js.map +1 -0
- package/dist/test_kit/tsconfig.tsbuildinfo +1 -0
- package/dist/test_kit/types.d.ts +45 -0
- package/dist/test_kit/types.d.ts.map +1 -0
- package/dist/test_kit/types.js +2 -0
- package/dist/test_kit/types.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +18 -3
|
@@ -0,0 +1,911 @@
|
|
|
1
|
+
import { expect } from 'vitest';
|
|
2
|
+
import { actorNameToToolName, getCategoryTools } from '@apify/actors-mcp-server/internals.js';
|
|
3
|
+
import { HELPER_TOOLS, MAX_LIMIT_WITH_INPUT_SCHEMA } from '@apify/actors-mcp-server/internals/test-kit.js';
|
|
4
|
+
import { ACTOR_EXAMPLE_MCP_SERVER, ACTOR_NORMAL_MODE, AUTO_INJECTED_TOOL_NAMES, expectNormalModeTestStructuredContent, expectReadmeInStructuredContent, expectToolNamesToContain, expectUsageCostMeta, findToolByName, getToolNames, validateStructuredOutput, validateStructuredOutputForTool, withClient, } from '../helpers.js';
|
|
5
|
+
// call-actor calls Actors by name — this tool name must never appear in the tool list.
|
|
6
|
+
const NORMAL_MODE_TOOL_NAME = actorNameToToolName(ACTOR_NORMAL_MODE);
|
|
7
|
+
/** Actor tools: search, details, call-actor, get-actor-run. */
|
|
8
|
+
export const actorsCases = [
|
|
9
|
+
{
|
|
10
|
+
name: 'calls an Actor directly via call-actor without a separate add step',
|
|
11
|
+
isDeploymentTest: false,
|
|
12
|
+
run: withClient({ tools: ['call-actor'] }, async (client) => {
|
|
13
|
+
const names = getToolNames(await client.listTools());
|
|
14
|
+
expect(names).toHaveLength(1 + AUTO_INJECTED_TOOL_NAMES.length);
|
|
15
|
+
expect(names).toContain(HELPER_TOOLS.ACTOR_CALL);
|
|
16
|
+
expectToolNamesToContain(names, AUTO_INJECTED_TOOL_NAMES);
|
|
17
|
+
expect(names).not.toContain(NORMAL_MODE_TOOL_NAME);
|
|
18
|
+
// No dynamic "add" step exists — call-actor calls any Actor by name directly.
|
|
19
|
+
const result = await client.callTool({
|
|
20
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
21
|
+
arguments: { actor: ACTOR_NORMAL_MODE, input: { firstNumber: 1, secondNumber: 2 } },
|
|
22
|
+
});
|
|
23
|
+
expectNormalModeTestStructuredContent(result);
|
|
24
|
+
}),
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'should call Actor dynamically via generic call-actor tool without need to add it first',
|
|
28
|
+
isDeploymentTest: false,
|
|
29
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
30
|
+
var _a;
|
|
31
|
+
const names = getToolNames(await client.listTools());
|
|
32
|
+
// actors category (already has call-actor) + auto-injected helpers.
|
|
33
|
+
const numberOfTools = getCategoryTools('default').actors.length + AUTO_INJECTED_TOOL_NAMES.length;
|
|
34
|
+
expect(names).toHaveLength(numberOfTools);
|
|
35
|
+
// get-actor-run should be automatically included when call-actor is present
|
|
36
|
+
expect(names).toContain(HELPER_TOOLS.ACTOR_RUNS_GET);
|
|
37
|
+
expectToolNamesToContain(names, AUTO_INJECTED_TOOL_NAMES);
|
|
38
|
+
// Check that the Actor is not in the tools list
|
|
39
|
+
expect(names).not.toContain(NORMAL_MODE_TOOL_NAME);
|
|
40
|
+
const result = await client.callTool({
|
|
41
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
42
|
+
arguments: { actor: ACTOR_NORMAL_MODE, input: { firstNumber: 1, secondNumber: 2 } },
|
|
43
|
+
});
|
|
44
|
+
const content = result.content;
|
|
45
|
+
// content[0] mirrors structuredContent as JSON; content[1] is "${summary}\n${nextStep}".
|
|
46
|
+
expect((_a = content[0]) === null || _a === void 0 ? void 0 : _a.type).toBe('text');
|
|
47
|
+
const mirrored = JSON.parse(content[0].text);
|
|
48
|
+
expect(mirrored.runId).toBeDefined();
|
|
49
|
+
expect(mirrored.status).toBe('SUCCEEDED');
|
|
50
|
+
// Validate structured output has run-response metadata for the normal-mode-test-actor.
|
|
51
|
+
expectNormalModeTestStructuredContent(result);
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
// isDeploymentTest: full-pipeline smoke (auth + real Actor run).
|
|
56
|
+
name: 'should call Actor directly with required input',
|
|
57
|
+
isDeploymentTest: true,
|
|
58
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
59
|
+
// Should fail without input (AJV validation error)
|
|
60
|
+
await expect(client.callTool({ name: HELPER_TOOLS.ACTOR_CALL, arguments: { actor: ACTOR_NORMAL_MODE } })).rejects.toThrow(/must have required property 'input'/);
|
|
61
|
+
// Should succeed with input
|
|
62
|
+
const callResult = await client.callTool({
|
|
63
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
64
|
+
arguments: { actor: ACTOR_NORMAL_MODE, input: { firstNumber: 1, secondNumber: 2 } },
|
|
65
|
+
});
|
|
66
|
+
expect(callResult.content).toBeDefined();
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'returns terminal RunResponse with usage cost meta when the run completes within waitSecs',
|
|
71
|
+
isDeploymentTest: false,
|
|
72
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
73
|
+
const callResult = await client.callTool({
|
|
74
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
75
|
+
// Max wait (45s) so the test does not flake on a slow run.
|
|
76
|
+
arguments: { actor: ACTOR_NORMAL_MODE, input: { firstNumber: 1, secondNumber: 2 }, waitSecs: 45 },
|
|
77
|
+
});
|
|
78
|
+
validateStructuredOutputForTool(callResult, HELPER_TOOLS.ACTOR_CALL, 'default');
|
|
79
|
+
expectNormalModeTestStructuredContent(callResult);
|
|
80
|
+
const sc = callResult.structuredContent;
|
|
81
|
+
expect(sc === null || sc === void 0 ? void 0 : sc.status).toBe('SUCCEEDED');
|
|
82
|
+
expect(sc === null || sc === void 0 ? void 0 : sc.summary).toMatch(/SUCCEEDED/);
|
|
83
|
+
expectUsageCostMeta(callResult);
|
|
84
|
+
}),
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'returns immediately with a non-terminal RunResponse when waitSecs=0',
|
|
88
|
+
isDeploymentTest: false,
|
|
89
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
90
|
+
const callResult = await client.callTool({
|
|
91
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
92
|
+
arguments: { actor: ACTOR_NORMAL_MODE, input: { firstNumber: 1, secondNumber: 2 }, waitSecs: 0 },
|
|
93
|
+
});
|
|
94
|
+
validateStructuredOutputForTool(callResult, HELPER_TOOLS.ACTOR_CALL, 'default');
|
|
95
|
+
const sc = callResult.structuredContent;
|
|
96
|
+
expect(sc === null || sc === void 0 ? void 0 : sc.runId).toBeDefined();
|
|
97
|
+
// Non-blocking: status is typically READY or RUNNING at this point (terminal also tolerated for very fast actors).
|
|
98
|
+
expect(['READY', 'RUNNING', 'SUCCEEDED']).toContain(sc === null || sc === void 0 ? void 0 : sc.status);
|
|
99
|
+
}),
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: 'accepts but ignores the deprecated previewOutput field',
|
|
103
|
+
isDeploymentTest: false,
|
|
104
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
105
|
+
const callResult = await client.callTool({
|
|
106
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
107
|
+
arguments: {
|
|
108
|
+
actor: ACTOR_NORMAL_MODE,
|
|
109
|
+
input: { firstNumber: 1, secondNumber: 2 },
|
|
110
|
+
previewOutput: false,
|
|
111
|
+
waitSecs: 45,
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
// previewOutput is ignored; still canonical RunResponse.
|
|
115
|
+
validateStructuredOutputForTool(callResult, HELPER_TOOLS.ACTOR_CALL, 'default');
|
|
116
|
+
expectNormalModeTestStructuredContent(callResult);
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'accepts callOptions.maxItems on call-actor and runs successfully',
|
|
121
|
+
isDeploymentTest: false,
|
|
122
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
123
|
+
var _a, _b, _c;
|
|
124
|
+
const callResult = await client.callTool({
|
|
125
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
126
|
+
arguments: {
|
|
127
|
+
actor: ACTOR_NORMAL_MODE,
|
|
128
|
+
input: { firstNumber: 1, secondNumber: 2 },
|
|
129
|
+
callOptions: { maxItems: 1 },
|
|
130
|
+
waitSecs: 45,
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
expect(callResult.isError).not.toBe(true);
|
|
134
|
+
const sc = callResult.structuredContent;
|
|
135
|
+
expect(sc === null || sc === void 0 ? void 0 : sc.status).toBe('SUCCEEDED');
|
|
136
|
+
expect((_c = (_b = (_a = sc === null || sc === void 0 ? void 0 : sc.storages) === null || _a === void 0 ? void 0 : _a.datasets) === null || _b === void 0 ? void 0 : _b.default) === null || _c === void 0 ? void 0 : _c.id).toBeDefined();
|
|
137
|
+
}),
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'surfaces dataset fields in the canonical response (no inline preview)',
|
|
141
|
+
isDeploymentTest: false,
|
|
142
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
143
|
+
var _a, _b, _c, _d;
|
|
144
|
+
const callResult = await client.callTool({
|
|
145
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
146
|
+
arguments: { actor: ACTOR_NORMAL_MODE, input: { firstNumber: 1, secondNumber: 2 }, waitSecs: 45 },
|
|
147
|
+
});
|
|
148
|
+
// No inlined items — agents use dataset id + fields via get-dataset-items.
|
|
149
|
+
validateStructuredOutputForTool(callResult, HELPER_TOOLS.ACTOR_CALL, 'default');
|
|
150
|
+
expectNormalModeTestStructuredContent(callResult);
|
|
151
|
+
const sc = callResult.structuredContent;
|
|
152
|
+
// nextStep should interpolate the datasetId so a text-only client can act without parsing storages.
|
|
153
|
+
expect(sc === null || sc === void 0 ? void 0 : sc.nextStep).toContain((_d = (_c = (_b = (_a = sc === null || sc === void 0 ? void 0 : sc.storages) === null || _a === void 0 ? void 0 : _a.datasets) === null || _b === void 0 ? void 0 : _b.default) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : '__unset__');
|
|
154
|
+
}),
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
name: 'surfaces aliased storages from run.storageIds in the canonical response',
|
|
158
|
+
isDeploymentTest: false,
|
|
159
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
160
|
+
var _a, _b, _c, _d, _e, _f;
|
|
161
|
+
const callResult = await client.callTool({
|
|
162
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
163
|
+
arguments: { actor: ACTOR_NORMAL_MODE, input: { firstNumber: 1, secondNumber: 2 }, waitSecs: 45 },
|
|
164
|
+
});
|
|
165
|
+
expect(callResult.isError).not.toBe(true);
|
|
166
|
+
// Schema validation must accept the alias entries (additionalProperties: full dataset shape).
|
|
167
|
+
validateStructuredOutputForTool(callResult, HELPER_TOOLS.ACTOR_CALL, 'default');
|
|
168
|
+
const sc = callResult.structuredContent;
|
|
169
|
+
// Aliased `books` dataset must appear beside default.
|
|
170
|
+
expect((_c = (_b = (_a = sc === null || sc === void 0 ? void 0 : sc.storages) === null || _a === void 0 ? void 0 : _a.datasets) === null || _b === void 0 ? void 0 : _b.default) === null || _c === void 0 ? void 0 : _c.id).toBeDefined();
|
|
171
|
+
expect((_f = (_e = (_d = sc === null || sc === void 0 ? void 0 : sc.storages) === null || _d === void 0 ? void 0 : _d.datasets) === null || _e === void 0 ? void 0 : _e.books) === null || _f === void 0 ? void 0 : _f.id).toEqual(expect.any(String));
|
|
172
|
+
}),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
// isDeploymentTest: store-search discovery path.
|
|
176
|
+
name: 'should find Actors in store search',
|
|
177
|
+
isDeploymentTest: true,
|
|
178
|
+
run: withClient(undefined, async (client) => {
|
|
179
|
+
const result = await client.callTool({
|
|
180
|
+
name: HELPER_TOOLS.STORE_SEARCH,
|
|
181
|
+
arguments: { keywords: 'normal-mode-test-actor', limit: 5 },
|
|
182
|
+
});
|
|
183
|
+
const content = result.content;
|
|
184
|
+
expect(content.some((item) => item.text.includes(ACTOR_NORMAL_MODE))).toBe(true);
|
|
185
|
+
}),
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: 'should not return rental Actors from store search',
|
|
189
|
+
isDeploymentTest: false,
|
|
190
|
+
run: withClient(undefined, async (client) => {
|
|
191
|
+
const result = await client.callTool({
|
|
192
|
+
name: HELPER_TOOLS.STORE_SEARCH,
|
|
193
|
+
arguments: { keywords: 'rental', limit: MAX_LIMIT_WITH_INPUT_SCHEMA },
|
|
194
|
+
});
|
|
195
|
+
const content = result.content;
|
|
196
|
+
expect(content.length).toBe(1);
|
|
197
|
+
const outputText = content[0].text;
|
|
198
|
+
expect(outputText).toContain('This Actor');
|
|
199
|
+
expect(outputText).not.toContain('This Actor is rental');
|
|
200
|
+
}),
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: 'should return an Actor-not-found error when calling a non-existent actor via call-actor',
|
|
204
|
+
isDeploymentTest: false,
|
|
205
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
206
|
+
const nonExistentActor = 'apify/this-actor-does-not-exist';
|
|
207
|
+
const result = await client.callTool({
|
|
208
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
209
|
+
arguments: { actor: nonExistentActor, input: {} },
|
|
210
|
+
});
|
|
211
|
+
expect(result).toBeDefined();
|
|
212
|
+
expect(result.isError).toBe(true);
|
|
213
|
+
const content = result.content;
|
|
214
|
+
expect(content.length).toBeGreaterThan(0);
|
|
215
|
+
expect(content[0].text).toContain(nonExistentActor);
|
|
216
|
+
expect(content[0].text).toContain('was not found');
|
|
217
|
+
}),
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
name: 'should return structured output for fetch-actor-details matching outputSchema',
|
|
221
|
+
isDeploymentTest: false,
|
|
222
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
223
|
+
const toolName = HELPER_TOOLS.ACTOR_GET_DETAILS;
|
|
224
|
+
const result = await client.callTool({ name: toolName, arguments: { actor: ACTOR_NORMAL_MODE } });
|
|
225
|
+
const content = result.content;
|
|
226
|
+
expect(content.length).toBeGreaterThan(0);
|
|
227
|
+
validateStructuredOutputForTool(result, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
228
|
+
}),
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: 'should return only input schema when output={ inputSchema: true }',
|
|
232
|
+
isDeploymentTest: false,
|
|
233
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
234
|
+
const result = await client.callTool({
|
|
235
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
236
|
+
arguments: {
|
|
237
|
+
actor: ACTOR_NORMAL_MODE,
|
|
238
|
+
output: {
|
|
239
|
+
description: false,
|
|
240
|
+
stats: false,
|
|
241
|
+
pricing: false,
|
|
242
|
+
rating: false,
|
|
243
|
+
metadata: false,
|
|
244
|
+
inputSchema: true,
|
|
245
|
+
readme: false,
|
|
246
|
+
mcpTools: false,
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
const content = result.content;
|
|
251
|
+
// Should contain schema but NOT readme or actor card
|
|
252
|
+
expect(content.some((item) => item.text.includes('Input schema'))).toBe(true);
|
|
253
|
+
expect(content.some((item) => item.text.includes('README'))).toBe(false);
|
|
254
|
+
}),
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
name: 'should return only description and stats when specified',
|
|
258
|
+
isDeploymentTest: false,
|
|
259
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
260
|
+
const result = await client.callTool({
|
|
261
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
262
|
+
arguments: {
|
|
263
|
+
actor: ACTOR_NORMAL_MODE,
|
|
264
|
+
output: {
|
|
265
|
+
description: true,
|
|
266
|
+
stats: true,
|
|
267
|
+
pricing: false,
|
|
268
|
+
rating: false,
|
|
269
|
+
metadata: false,
|
|
270
|
+
inputSchema: false,
|
|
271
|
+
readme: false,
|
|
272
|
+
mcpTools: false,
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
});
|
|
276
|
+
const content = result.content;
|
|
277
|
+
// Should contain actor info but NOT readme or schema
|
|
278
|
+
expect(content.some((item) => item.text.includes('Actor information'))).toBe(true);
|
|
279
|
+
expect(content.some((item) => item.text.includes('Input schema'))).toBe(false);
|
|
280
|
+
}),
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
name: 'should list MCP tools when output={ mcpTools: true } for MCP server Actor',
|
|
284
|
+
isDeploymentTest: false,
|
|
285
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
286
|
+
const result = await client.callTool({
|
|
287
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
288
|
+
arguments: {
|
|
289
|
+
actor: ACTOR_EXAMPLE_MCP_SERVER,
|
|
290
|
+
output: {
|
|
291
|
+
description: false,
|
|
292
|
+
stats: false,
|
|
293
|
+
pricing: false,
|
|
294
|
+
rating: false,
|
|
295
|
+
metadata: false,
|
|
296
|
+
inputSchema: false,
|
|
297
|
+
readme: false,
|
|
298
|
+
mcpTools: true,
|
|
299
|
+
},
|
|
300
|
+
},
|
|
301
|
+
});
|
|
302
|
+
const content = result.content;
|
|
303
|
+
expect(content.some((item) => item.text.includes('Available MCP Tools'))).toBe(true);
|
|
304
|
+
expect(content.some((item) => item.text.includes('add'))).toBe(true);
|
|
305
|
+
}),
|
|
306
|
+
},
|
|
307
|
+
{
|
|
308
|
+
name: 'should return graceful note when output={ mcpTools: true } for regular Actor',
|
|
309
|
+
isDeploymentTest: false,
|
|
310
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
311
|
+
const result = await client.callTool({
|
|
312
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
313
|
+
arguments: {
|
|
314
|
+
actor: ACTOR_NORMAL_MODE,
|
|
315
|
+
output: {
|
|
316
|
+
description: false,
|
|
317
|
+
stats: false,
|
|
318
|
+
pricing: false,
|
|
319
|
+
rating: false,
|
|
320
|
+
metadata: false,
|
|
321
|
+
inputSchema: false,
|
|
322
|
+
readme: false,
|
|
323
|
+
mcpTools: true,
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
});
|
|
327
|
+
const content = result.content;
|
|
328
|
+
expect(content.some((item) => item.text.includes('This Actor is not an MCP server'))).toBe(true);
|
|
329
|
+
}),
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
name: 'should return structured output for fetch-actor-details with selective output matching outputSchema',
|
|
333
|
+
isDeploymentTest: false,
|
|
334
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
335
|
+
const toolName = HELPER_TOOLS.ACTOR_GET_DETAILS;
|
|
336
|
+
// Test with output={ mcpTools: true } - should validate against schema even with selective fields
|
|
337
|
+
const result = await client.callTool({
|
|
338
|
+
name: toolName,
|
|
339
|
+
arguments: {
|
|
340
|
+
actor: ACTOR_EXAMPLE_MCP_SERVER,
|
|
341
|
+
output: {
|
|
342
|
+
description: false,
|
|
343
|
+
stats: false,
|
|
344
|
+
pricing: false,
|
|
345
|
+
rating: false,
|
|
346
|
+
metadata: false,
|
|
347
|
+
inputSchema: false,
|
|
348
|
+
readme: false,
|
|
349
|
+
mcpTools: true,
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
const content = result.content;
|
|
354
|
+
expect(content.length).toBeGreaterThan(0);
|
|
355
|
+
// This should validate successfully - structured output must match schema
|
|
356
|
+
validateStructuredOutputForTool(result, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
357
|
+
}),
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: 'should return structured output for fetch-actor-details with output={ description: true, readme: true } matching outputSchema',
|
|
361
|
+
isDeploymentTest: false,
|
|
362
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
363
|
+
const toolName = HELPER_TOOLS.ACTOR_GET_DETAILS;
|
|
364
|
+
// Test with output={ description: true, readme: true } - inputSchema should be undefined
|
|
365
|
+
const result = await client.callTool({
|
|
366
|
+
name: toolName,
|
|
367
|
+
arguments: {
|
|
368
|
+
actor: ACTOR_NORMAL_MODE,
|
|
369
|
+
output: {
|
|
370
|
+
description: true,
|
|
371
|
+
stats: false,
|
|
372
|
+
pricing: false,
|
|
373
|
+
rating: false,
|
|
374
|
+
metadata: false,
|
|
375
|
+
inputSchema: false,
|
|
376
|
+
readme: true,
|
|
377
|
+
mcpTools: false,
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
const content = result.content;
|
|
382
|
+
expect(content.length).toBeGreaterThan(0);
|
|
383
|
+
// This should validate successfully - structured output must match schema
|
|
384
|
+
validateStructuredOutputForTool(result, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
385
|
+
}),
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
name: 'should return only pricing when output={ pricing: true }',
|
|
389
|
+
isDeploymentTest: false,
|
|
390
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
391
|
+
const result = await client.callTool({
|
|
392
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
393
|
+
arguments: {
|
|
394
|
+
actor: ACTOR_NORMAL_MODE,
|
|
395
|
+
output: {
|
|
396
|
+
description: false,
|
|
397
|
+
stats: false,
|
|
398
|
+
pricing: true,
|
|
399
|
+
rating: false,
|
|
400
|
+
metadata: false,
|
|
401
|
+
inputSchema: false,
|
|
402
|
+
readme: false,
|
|
403
|
+
mcpTools: false,
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
});
|
|
407
|
+
const content = result.content;
|
|
408
|
+
// Should contain actor info (pricing is part of actor card) but NOT readme or schema
|
|
409
|
+
expect(content.some((item) => item.text.includes('Actor information'))).toBe(true);
|
|
410
|
+
expect(content.some((item) => item.text.includes('README'))).toBe(false);
|
|
411
|
+
expect(content.some((item) => item.text.includes('Input schema'))).toBe(false);
|
|
412
|
+
// Validate structured output
|
|
413
|
+
validateStructuredOutputForTool(result, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
414
|
+
}),
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
name: 'should return only readme when output={ readme: true }',
|
|
418
|
+
isDeploymentTest: false,
|
|
419
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
420
|
+
const result = await client.callTool({
|
|
421
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
422
|
+
arguments: {
|
|
423
|
+
actor: ACTOR_NORMAL_MODE,
|
|
424
|
+
output: {
|
|
425
|
+
description: false,
|
|
426
|
+
stats: false,
|
|
427
|
+
pricing: false,
|
|
428
|
+
rating: false,
|
|
429
|
+
metadata: false,
|
|
430
|
+
inputSchema: false,
|
|
431
|
+
readme: true,
|
|
432
|
+
mcpTools: false,
|
|
433
|
+
},
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
const content = result.content;
|
|
437
|
+
// Should contain readme text but NOT actor info card or input schema
|
|
438
|
+
expect(content.length).toBeGreaterThan(0);
|
|
439
|
+
expect(content.some((item) => item.text.includes('Actor information'))).toBe(false);
|
|
440
|
+
expect(content.some((item) => item.text.includes('Input schema'))).toBe(false);
|
|
441
|
+
// Validate structured output
|
|
442
|
+
validateStructuredOutputForTool(result, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
443
|
+
}),
|
|
444
|
+
},
|
|
445
|
+
{
|
|
446
|
+
name: 'should return README content (summary or full) in text and structured response for fetch-actor-details',
|
|
447
|
+
isDeploymentTest: false,
|
|
448
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
449
|
+
var _a;
|
|
450
|
+
const result = await client.callTool({
|
|
451
|
+
name: 'fetch-actor-details',
|
|
452
|
+
arguments: { actor: ACTOR_NORMAL_MODE, output: { description: true, readme: true, inputSchema: true } },
|
|
453
|
+
});
|
|
454
|
+
expect(result.content).toBeDefined();
|
|
455
|
+
const content = result.content;
|
|
456
|
+
const allText = content.map((item) => item.text).join('\n');
|
|
457
|
+
// Text should contain actor card, README section (summary or full fallback), and input schema
|
|
458
|
+
expect(allText).toContain('Actor information');
|
|
459
|
+
expect(allText).toMatch(/# README summary|# README/);
|
|
460
|
+
expect(allText).toContain('Input schema');
|
|
461
|
+
expectReadmeInStructuredContent(result, ACTOR_NORMAL_MODE);
|
|
462
|
+
validateStructuredOutput(result, (_a = findToolByName(HELPER_TOOLS.ACTOR_GET_DETAILS, 'default')) === null || _a === void 0 ? void 0 : _a.outputSchema, 'fetch-actor-details');
|
|
463
|
+
}),
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
name: 'should use default values when output object is not provided',
|
|
467
|
+
isDeploymentTest: false,
|
|
468
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
469
|
+
// When output is not provided, all fields should default to their default values
|
|
470
|
+
const result = await client.callTool({
|
|
471
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
472
|
+
arguments: { actor: ACTOR_NORMAL_MODE },
|
|
473
|
+
});
|
|
474
|
+
const content = result.content;
|
|
475
|
+
// Should contain all default sections (description, stats, pricing, rating, metadata, readme, inputSchema)
|
|
476
|
+
// but NOT mcpTools (which defaults to false)
|
|
477
|
+
expect(content.some((item) => item.text.includes('Actor information'))).toBe(true);
|
|
478
|
+
expect(content.some((item) => item.text.includes('Input schema'))).toBe(true);
|
|
479
|
+
expect(content.some((item) => item.text.includes('Available MCP Tools'))).toBe(false);
|
|
480
|
+
}),
|
|
481
|
+
},
|
|
482
|
+
{
|
|
483
|
+
name: 'should return all fields when output includes all standard options',
|
|
484
|
+
isDeploymentTest: false,
|
|
485
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
486
|
+
var _a, _b;
|
|
487
|
+
const result = await client.callTool({
|
|
488
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
489
|
+
arguments: {
|
|
490
|
+
actor: ACTOR_NORMAL_MODE,
|
|
491
|
+
output: {
|
|
492
|
+
description: true,
|
|
493
|
+
stats: true,
|
|
494
|
+
pricing: true,
|
|
495
|
+
rating: false,
|
|
496
|
+
metadata: false,
|
|
497
|
+
inputSchema: true,
|
|
498
|
+
readme: true,
|
|
499
|
+
mcpTools: false,
|
|
500
|
+
},
|
|
501
|
+
},
|
|
502
|
+
});
|
|
503
|
+
const content = result.content;
|
|
504
|
+
// Should contain all sections in text
|
|
505
|
+
expect(content.some((item) => item.text.includes('Actor information'))).toBe(true);
|
|
506
|
+
expect(content.some((item) => item.text.includes('Input schema'))).toBe(true);
|
|
507
|
+
// Validate structured output exists and has all fields
|
|
508
|
+
const resultWithStructured = result;
|
|
509
|
+
expect(resultWithStructured.structuredContent).toBeDefined();
|
|
510
|
+
expect((_a = resultWithStructured.structuredContent) === null || _a === void 0 ? void 0 : _a.actorInfo).toBeDefined();
|
|
511
|
+
expect((_b = resultWithStructured.structuredContent) === null || _b === void 0 ? void 0 : _b.inputSchema).toBeDefined();
|
|
512
|
+
// Validate against schema
|
|
513
|
+
validateStructuredOutputForTool(result, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
514
|
+
}),
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
name: 'should support granular output controls for rating and metadata',
|
|
518
|
+
isDeploymentTest: false,
|
|
519
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
520
|
+
// Test 1: Only pricing (should include pricing, NOT other sections)
|
|
521
|
+
const pricingOnlyResult = await client.callTool({
|
|
522
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
523
|
+
arguments: {
|
|
524
|
+
actor: ACTOR_NORMAL_MODE,
|
|
525
|
+
output: {
|
|
526
|
+
description: false,
|
|
527
|
+
stats: false,
|
|
528
|
+
pricing: true,
|
|
529
|
+
rating: false,
|
|
530
|
+
metadata: false,
|
|
531
|
+
inputSchema: false,
|
|
532
|
+
readme: false,
|
|
533
|
+
mcpTools: false,
|
|
534
|
+
},
|
|
535
|
+
},
|
|
536
|
+
});
|
|
537
|
+
const pricingContent = pricingOnlyResult.content;
|
|
538
|
+
const pricingText = pricingContent.map((item) => item.text).join('\n');
|
|
539
|
+
// Should include actor card header and pricing
|
|
540
|
+
expect(pricingText).toContain('Actor information');
|
|
541
|
+
expect(pricingText).toContain('Pricing');
|
|
542
|
+
// Should NOT include other sections
|
|
543
|
+
expect(pricingText).not.toContain('Description:');
|
|
544
|
+
expect(pricingText).not.toContain('Stats:');
|
|
545
|
+
expect(pricingText).not.toContain('Rating:');
|
|
546
|
+
expect(pricingText).not.toContain('Developed by:');
|
|
547
|
+
expect(pricingText).not.toContain('Categories:');
|
|
548
|
+
expect(pricingText).not.toContain('Last modified:');
|
|
549
|
+
expect(pricingText).not.toContain('README');
|
|
550
|
+
// Test 2: Only rating
|
|
551
|
+
const ratingOnlyResult = await client.callTool({
|
|
552
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
553
|
+
arguments: {
|
|
554
|
+
actor: ACTOR_NORMAL_MODE,
|
|
555
|
+
output: {
|
|
556
|
+
description: false,
|
|
557
|
+
stats: false,
|
|
558
|
+
pricing: false,
|
|
559
|
+
rating: true,
|
|
560
|
+
metadata: false,
|
|
561
|
+
inputSchema: false,
|
|
562
|
+
readme: false,
|
|
563
|
+
mcpTools: false,
|
|
564
|
+
},
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
const ratingContent = ratingOnlyResult.content;
|
|
568
|
+
const ratingText = ratingContent.map((item) => item.text).join('\n');
|
|
569
|
+
// Should include actor card header and rating
|
|
570
|
+
expect(ratingText).toContain('Actor information');
|
|
571
|
+
// TODO: re-enable once apify/normal-mode-test-actor has reviews; Rating: is omitted when review count is 0
|
|
572
|
+
// expect(ratingText).toContain('Rating:');
|
|
573
|
+
// Should NOT include other sections
|
|
574
|
+
expect(ratingText).not.toContain('Description:');
|
|
575
|
+
expect(ratingText).not.toContain('Stats:');
|
|
576
|
+
expect(ratingText).not.toContain('Pricing');
|
|
577
|
+
expect(ratingText).not.toContain('Developed by:');
|
|
578
|
+
expect(ratingText).not.toContain('Categories:');
|
|
579
|
+
expect(ratingText).not.toContain('Last modified:');
|
|
580
|
+
expect(ratingText).not.toContain('README');
|
|
581
|
+
// Test 3: Only metadata (should include developer, categories, last modified, deprecation status)
|
|
582
|
+
const metadataOnlyResult = await client.callTool({
|
|
583
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
584
|
+
arguments: {
|
|
585
|
+
actor: ACTOR_NORMAL_MODE,
|
|
586
|
+
output: {
|
|
587
|
+
description: false,
|
|
588
|
+
stats: false,
|
|
589
|
+
pricing: false,
|
|
590
|
+
rating: false,
|
|
591
|
+
metadata: true,
|
|
592
|
+
inputSchema: false,
|
|
593
|
+
readme: false,
|
|
594
|
+
mcpTools: false,
|
|
595
|
+
},
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
const metadataContent = metadataOnlyResult.content;
|
|
599
|
+
const metadataText = metadataContent.map((item) => item.text).join('\n');
|
|
600
|
+
// Should include developer, categories, and last modified date
|
|
601
|
+
expect(metadataText).toContain('Developed by:');
|
|
602
|
+
expect(metadataText).toContain('Categories:');
|
|
603
|
+
expect(metadataText).toContain('Last modified:');
|
|
604
|
+
// Should NOT include other sections
|
|
605
|
+
expect(metadataText).not.toContain('Description:');
|
|
606
|
+
expect(metadataText).not.toContain('Stats:');
|
|
607
|
+
expect(metadataText).not.toContain('Pricing');
|
|
608
|
+
expect(metadataText).not.toContain('Rating:');
|
|
609
|
+
expect(metadataText).not.toContain('README');
|
|
610
|
+
// Test 4: Combination - pricing + rating + metadata (should exclude description, stats, readme, input-schema)
|
|
611
|
+
const combinationResult = await client.callTool({
|
|
612
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
613
|
+
arguments: {
|
|
614
|
+
actor: ACTOR_NORMAL_MODE,
|
|
615
|
+
output: {
|
|
616
|
+
description: false,
|
|
617
|
+
stats: false,
|
|
618
|
+
pricing: true,
|
|
619
|
+
rating: true,
|
|
620
|
+
metadata: true,
|
|
621
|
+
inputSchema: false,
|
|
622
|
+
readme: false,
|
|
623
|
+
mcpTools: false,
|
|
624
|
+
},
|
|
625
|
+
},
|
|
626
|
+
});
|
|
627
|
+
const combinationContent = combinationResult.content;
|
|
628
|
+
const combinationText = combinationContent.map((item) => item.text).join('\n');
|
|
629
|
+
// Should include: pricing, rating, metadata (developer, categories, last modified)
|
|
630
|
+
expect(combinationText).toContain('Pricing');
|
|
631
|
+
// TODO: re-enable once apify/normal-mode-test-actor has reviews; Rating: is omitted when review count is 0
|
|
632
|
+
// expect(combinationText).toContain('Rating:');
|
|
633
|
+
expect(combinationText).toContain('Developed by:');
|
|
634
|
+
expect(combinationText).toContain('Categories:');
|
|
635
|
+
expect(combinationText).toContain('Last modified:');
|
|
636
|
+
// Should NOT include: description, stats, readme, input-schema
|
|
637
|
+
expect(combinationText).not.toContain('Description:');
|
|
638
|
+
expect(combinationText).not.toContain('Stats:');
|
|
639
|
+
expect(combinationText).not.toContain('README');
|
|
640
|
+
expect(combinationText).not.toContain('Input schema');
|
|
641
|
+
// Validate structured output for all test cases
|
|
642
|
+
validateStructuredOutputForTool(pricingOnlyResult, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
643
|
+
validateStructuredOutputForTool(ratingOnlyResult, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
644
|
+
validateStructuredOutputForTool(metadataOnlyResult, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
645
|
+
validateStructuredOutputForTool(combinationResult, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
646
|
+
}),
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
name: 'should dynamically test all output options and verify section presence/absence',
|
|
650
|
+
isDeploymentTest: false,
|
|
651
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
652
|
+
// Define all output options with their expected markers in text
|
|
653
|
+
const outputOptions = [
|
|
654
|
+
{
|
|
655
|
+
name: 'description',
|
|
656
|
+
field: 'description',
|
|
657
|
+
markers: ['Description:'],
|
|
658
|
+
notMarkers: [
|
|
659
|
+
'Developed by:',
|
|
660
|
+
'Categories:',
|
|
661
|
+
'Stats:',
|
|
662
|
+
'Pricing',
|
|
663
|
+
'Rating:',
|
|
664
|
+
'Last modified:',
|
|
665
|
+
'README',
|
|
666
|
+
'Input schema',
|
|
667
|
+
],
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: 'stats',
|
|
671
|
+
field: 'stats',
|
|
672
|
+
markers: ['Stats:', 'total users', 'monthly users'],
|
|
673
|
+
notMarkers: [
|
|
674
|
+
'Developed by:',
|
|
675
|
+
'Categories:',
|
|
676
|
+
'Description:',
|
|
677
|
+
'Pricing',
|
|
678
|
+
'Rating:',
|
|
679
|
+
'Last modified:',
|
|
680
|
+
'README',
|
|
681
|
+
'Input schema',
|
|
682
|
+
],
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
name: 'pricing',
|
|
686
|
+
field: 'pricing',
|
|
687
|
+
markers: ['Pricing'],
|
|
688
|
+
notMarkers: [
|
|
689
|
+
'Developed by:',
|
|
690
|
+
'Categories:',
|
|
691
|
+
'Description:',
|
|
692
|
+
'Stats:',
|
|
693
|
+
'Rating:',
|
|
694
|
+
'Last modified:',
|
|
695
|
+
'README',
|
|
696
|
+
'Input schema',
|
|
697
|
+
],
|
|
698
|
+
},
|
|
699
|
+
{
|
|
700
|
+
name: 'rating',
|
|
701
|
+
field: 'rating',
|
|
702
|
+
// TODO: restore markers to ['Rating:', 'out of 5'] once apify/normal-mode-test-actor has reviews;
|
|
703
|
+
// Rating: is omitted when review count is 0
|
|
704
|
+
markers: [],
|
|
705
|
+
notMarkers: [
|
|
706
|
+
'Developed by:',
|
|
707
|
+
'Categories:',
|
|
708
|
+
'Description:',
|
|
709
|
+
'Stats:',
|
|
710
|
+
'Pricing',
|
|
711
|
+
'Last modified:',
|
|
712
|
+
'README',
|
|
713
|
+
'Input schema',
|
|
714
|
+
],
|
|
715
|
+
},
|
|
716
|
+
{
|
|
717
|
+
name: 'metadata',
|
|
718
|
+
field: 'metadata',
|
|
719
|
+
markers: ['Developed by:', 'Categories:', 'Last modified:'],
|
|
720
|
+
notMarkers: ['Description:', 'Stats:', 'Pricing', 'Rating:', 'README', 'Input schema'],
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
name: 'input-schema',
|
|
724
|
+
field: 'inputSchema',
|
|
725
|
+
markers: ['Input schema', '```json'],
|
|
726
|
+
notMarkers: [
|
|
727
|
+
'Developed by:',
|
|
728
|
+
'Description:',
|
|
729
|
+
'Stats:',
|
|
730
|
+
'Pricing',
|
|
731
|
+
'Rating:',
|
|
732
|
+
'Last modified:',
|
|
733
|
+
'README',
|
|
734
|
+
],
|
|
735
|
+
},
|
|
736
|
+
{
|
|
737
|
+
name: 'readme',
|
|
738
|
+
field: 'readme',
|
|
739
|
+
markers: [],
|
|
740
|
+
notMarkers: ['Input schema'],
|
|
741
|
+
},
|
|
742
|
+
];
|
|
743
|
+
// Test each output option individually
|
|
744
|
+
for (const option of outputOptions) {
|
|
745
|
+
const result = await client.callTool({
|
|
746
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
747
|
+
arguments: {
|
|
748
|
+
actor: ACTOR_NORMAL_MODE,
|
|
749
|
+
output: {
|
|
750
|
+
description: option.field === 'description',
|
|
751
|
+
stats: option.field === 'stats',
|
|
752
|
+
pricing: option.field === 'pricing',
|
|
753
|
+
rating: option.field === 'rating',
|
|
754
|
+
metadata: option.field === 'metadata',
|
|
755
|
+
inputSchema: option.field === 'inputSchema',
|
|
756
|
+
readme: option.field === 'readme',
|
|
757
|
+
mcpTools: false,
|
|
758
|
+
},
|
|
759
|
+
},
|
|
760
|
+
});
|
|
761
|
+
const content = result.content;
|
|
762
|
+
const text = content.map((item) => item.text).join('\n');
|
|
763
|
+
// Verify expected markers are present
|
|
764
|
+
for (const marker of option.markers) {
|
|
765
|
+
expect(text, `output=${option.name} should contain "${marker}"`).toContain(marker);
|
|
766
|
+
}
|
|
767
|
+
// Verify unwanted markers are absent
|
|
768
|
+
for (const notMarker of option.notMarkers) {
|
|
769
|
+
expect(text, `output=${option.name} should NOT contain "${notMarker}"`).not.toContain(notMarker);
|
|
770
|
+
}
|
|
771
|
+
// Validate structured output
|
|
772
|
+
validateStructuredOutputForTool(result, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
773
|
+
}
|
|
774
|
+
// Test a combination: all actor card sections (description, stats, pricing, rating, metadata)
|
|
775
|
+
const allCardSectionsResult = await client.callTool({
|
|
776
|
+
name: HELPER_TOOLS.ACTOR_GET_DETAILS,
|
|
777
|
+
arguments: {
|
|
778
|
+
actor: ACTOR_NORMAL_MODE,
|
|
779
|
+
output: {
|
|
780
|
+
description: true,
|
|
781
|
+
stats: true,
|
|
782
|
+
pricing: true,
|
|
783
|
+
rating: true,
|
|
784
|
+
metadata: true,
|
|
785
|
+
inputSchema: false,
|
|
786
|
+
readme: false,
|
|
787
|
+
mcpTools: false,
|
|
788
|
+
},
|
|
789
|
+
},
|
|
790
|
+
});
|
|
791
|
+
const allCardContent = allCardSectionsResult.content;
|
|
792
|
+
const allCardText = allCardContent.map((item) => item.text).join('\n');
|
|
793
|
+
// Should include all actor card sections
|
|
794
|
+
expect(allCardText).toContain('Description:');
|
|
795
|
+
expect(allCardText).toContain('Stats:');
|
|
796
|
+
expect(allCardText).toContain('Pricing');
|
|
797
|
+
// TODO: re-enable once apify/normal-mode-test-actor has reviews; Rating: is omitted when review count is 0
|
|
798
|
+
// expect(allCardText).toContain('Rating:');
|
|
799
|
+
expect(allCardText).toContain('Developed by:');
|
|
800
|
+
expect(allCardText).toContain('Categories:');
|
|
801
|
+
expect(allCardText).toContain('Last modified:');
|
|
802
|
+
// Should NOT include readme or input-schema
|
|
803
|
+
expect(allCardText).not.toContain('README');
|
|
804
|
+
expect(allCardText).not.toContain('Input schema');
|
|
805
|
+
validateStructuredOutputForTool(allCardSectionsResult, HELPER_TOOLS.ACTOR_GET_DETAILS, 'default');
|
|
806
|
+
}),
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
name: 'should return structured output for search-actors matching outputSchema',
|
|
810
|
+
isDeploymentTest: false,
|
|
811
|
+
run: withClient({ tools: ['actors'] }, async (client) => {
|
|
812
|
+
const toolName = HELPER_TOOLS.STORE_SEARCH;
|
|
813
|
+
const result = await client.callTool({
|
|
814
|
+
name: toolName,
|
|
815
|
+
arguments: { keywords: 'rag web browser', limit: 5, offset: 0 },
|
|
816
|
+
});
|
|
817
|
+
const content = result.content;
|
|
818
|
+
expect(content.length).toBeGreaterThan(0);
|
|
819
|
+
validateStructuredOutputForTool(result, HELPER_TOOLS.STORE_SEARCH, 'default');
|
|
820
|
+
}),
|
|
821
|
+
},
|
|
822
|
+
{
|
|
823
|
+
name: 'should return structured output for get-actor-run matching outputSchema',
|
|
824
|
+
isDeploymentTest: false,
|
|
825
|
+
run: withClient({ tools: ['actors', 'runs'] }, async (client) => {
|
|
826
|
+
var _a;
|
|
827
|
+
// First, start an async actor run to get a runId
|
|
828
|
+
const callResult = await client.callTool({
|
|
829
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
830
|
+
arguments: { actor: ACTOR_NORMAL_MODE, input: { firstNumber: 1, secondNumber: 2 }, waitSecs: 0 },
|
|
831
|
+
});
|
|
832
|
+
const resultWithStructured = callResult;
|
|
833
|
+
expect((_a = resultWithStructured.structuredContent) === null || _a === void 0 ? void 0 : _a.runId).toBeDefined();
|
|
834
|
+
const runId = resultWithStructured.structuredContent.runId;
|
|
835
|
+
// Now test get-actor-run
|
|
836
|
+
const runResult = await client.callTool({ name: HELPER_TOOLS.ACTOR_RUNS_GET, arguments: { runId } });
|
|
837
|
+
expect(runResult.content).toBeDefined();
|
|
838
|
+
// Validate structured output for get-actor-run
|
|
839
|
+
validateStructuredOutputForTool(runResult, HELPER_TOOLS.ACTOR_RUNS_GET, 'default');
|
|
840
|
+
}),
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: 'should return Actor details both for full Actor name and ID',
|
|
844
|
+
isDeploymentTest: false,
|
|
845
|
+
run: withClient(undefined, async (client, ctx) => {
|
|
846
|
+
const apifyClient = ctx.createApifyClient();
|
|
847
|
+
const actor = await apifyClient.actor(ACTOR_NORMAL_MODE).get();
|
|
848
|
+
expect(actor).toBeDefined();
|
|
849
|
+
const actorId = actor.id;
|
|
850
|
+
// Fetch by full Actor name
|
|
851
|
+
const resultByName = await client.callTool({
|
|
852
|
+
name: 'fetch-actor-details',
|
|
853
|
+
arguments: { actor: ACTOR_NORMAL_MODE },
|
|
854
|
+
});
|
|
855
|
+
const contentByName = resultByName.content;
|
|
856
|
+
expect(contentByName[0].text).toContain(ACTOR_NORMAL_MODE);
|
|
857
|
+
// Fetch by Actor ID only
|
|
858
|
+
const resultById = await client.callTool({
|
|
859
|
+
name: 'fetch-actor-details',
|
|
860
|
+
arguments: { actor: actorId },
|
|
861
|
+
});
|
|
862
|
+
const contentById = resultById.content;
|
|
863
|
+
expect(contentById[0].text).toContain(ACTOR_NORMAL_MODE);
|
|
864
|
+
}),
|
|
865
|
+
},
|
|
866
|
+
{
|
|
867
|
+
name: 'returns structuredContent for get-actor-run',
|
|
868
|
+
isDeploymentTest: false,
|
|
869
|
+
run: withClient({ tools: ['actors', 'runs'] }, async (client) => {
|
|
870
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
871
|
+
// First, start an async actor run to get a runId
|
|
872
|
+
const callResult = await client.callTool({
|
|
873
|
+
name: HELPER_TOOLS.ACTOR_CALL,
|
|
874
|
+
arguments: { actor: ACTOR_NORMAL_MODE, input: { firstNumber: 1, secondNumber: 2 }, waitSecs: 0 },
|
|
875
|
+
});
|
|
876
|
+
const resultWithStructured = callResult;
|
|
877
|
+
const runId = resultWithStructured.structuredContent.runId;
|
|
878
|
+
// Now test get-actor-run with waitSecs to drive it to terminal state.
|
|
879
|
+
const runResult = await client.callTool({
|
|
880
|
+
name: HELPER_TOOLS.ACTOR_RUNS_GET,
|
|
881
|
+
arguments: { runId, waitSecs: 30 },
|
|
882
|
+
});
|
|
883
|
+
const runContent = runResult;
|
|
884
|
+
expect(runContent.structuredContent).toBeDefined();
|
|
885
|
+
expect((_a = runContent.structuredContent) === null || _a === void 0 ? void 0 : _a.runId).toBe(runId);
|
|
886
|
+
expect((_b = runContent.structuredContent) === null || _b === void 0 ? void 0 : _b.actorId).toBeDefined();
|
|
887
|
+
expect((_c = runContent.structuredContent) === null || _c === void 0 ? void 0 : _c.status).toBeDefined();
|
|
888
|
+
expect((_d = runContent.structuredContent) === null || _d === void 0 ? void 0 : _d.summary).toBeDefined();
|
|
889
|
+
expect((_e = runContent.structuredContent) === null || _e === void 0 ? void 0 : _e.nextStep).toBeDefined();
|
|
890
|
+
expect((_f = runContent.structuredContent) === null || _f === void 0 ? void 0 : _f.storages).toBeDefined();
|
|
891
|
+
// No inlined dataset items or KV record bodies anywhere on the response.
|
|
892
|
+
const dump = JSON.stringify(runContent.structuredContent);
|
|
893
|
+
expect(dump).not.toContain('previewItems');
|
|
894
|
+
if (((_g = runContent.structuredContent) === null || _g === void 0 ? void 0 : _g.status) === 'SUCCEEDED') {
|
|
895
|
+
expect((_j = (_h = runContent.structuredContent) === null || _h === void 0 ? void 0 : _h.storages.datasets) === null || _j === void 0 ? void 0 : _j.default.id).toBeDefined();
|
|
896
|
+
}
|
|
897
|
+
}),
|
|
898
|
+
},
|
|
899
|
+
{
|
|
900
|
+
name: 'rejects get-actor-run waitSecs above 45',
|
|
901
|
+
isDeploymentTest: false,
|
|
902
|
+
run: withClient({ tools: ['actors', 'runs'] }, async (client) => {
|
|
903
|
+
// Fake-but-plausible runId so failure is waitSecs validation, not missing run.
|
|
904
|
+
await expect(client.callTool({
|
|
905
|
+
name: HELPER_TOOLS.ACTOR_RUNS_GET,
|
|
906
|
+
arguments: { runId: 'aaaaaaaaaaaaaaaaa', waitSecs: 46 },
|
|
907
|
+
})).rejects.toThrow(/waitSecs|less than or equal to 45|<= 45/i);
|
|
908
|
+
}),
|
|
909
|
+
},
|
|
910
|
+
];
|
|
911
|
+
//# sourceMappingURL=actors.cases.js.map
|