@access-mcp/events 0.2.0 → 0.3.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.
- package/README.md +46 -214
- package/dist/index.js +2 -1
- package/dist/server.d.ts +22 -195
- package/dist/server.js +196 -361
- package/package.json +2 -2
- package/src/__tests__/server.integration.test.ts +79 -149
- package/src/__tests__/server.test.ts +135 -486
- package/src/index.ts +2 -1
- package/src/server.ts +270 -421
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi, afterEach } from "vitest";
|
|
1
|
+
import { describe, it, expect, beforeEach, vi, afterEach, Mock } from "vitest";
|
|
2
2
|
import { EventsServer } from "../server.js";
|
|
3
|
-
import
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
const { version } = require("../../package.json");
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
interface MockHttpClient {
|
|
8
|
+
get: Mock<(url: string) => Promise<{ status: number; data: unknown }>>;
|
|
9
|
+
}
|
|
7
10
|
|
|
8
11
|
describe("EventsServer", () => {
|
|
9
12
|
let server: EventsServer;
|
|
10
|
-
let mockHttpClient:
|
|
13
|
+
let mockHttpClient: MockHttpClient;
|
|
11
14
|
|
|
12
15
|
beforeEach(() => {
|
|
13
16
|
server = new EventsServer();
|
|
@@ -32,18 +35,15 @@ describe("EventsServer", () => {
|
|
|
32
35
|
it("should initialize with correct server name and version", () => {
|
|
33
36
|
expect(server).toBeDefined();
|
|
34
37
|
expect(server["serverName"]).toBe("access-mcp-events");
|
|
35
|
-
expect(server["version"]).toBe(
|
|
38
|
+
expect(server["version"]).toBe(version);
|
|
36
39
|
expect(server["baseURL"]).toBe("https://support.access-ci.org");
|
|
37
40
|
});
|
|
38
41
|
|
|
39
42
|
it("should provide the correct tools", () => {
|
|
40
43
|
const tools = server["getTools"]();
|
|
41
44
|
|
|
42
|
-
expect(tools).toHaveLength(
|
|
43
|
-
expect(tools.
|
|
44
|
-
expect(tools.map((t) => t.name)).toContain("get_upcoming_events");
|
|
45
|
-
expect(tools.map((t) => t.name)).toContain("search_events");
|
|
46
|
-
expect(tools.map((t) => t.name)).toContain("get_events_by_tag");
|
|
45
|
+
expect(tools).toHaveLength(1);
|
|
46
|
+
expect(tools[0].name).toBe("search_events");
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
it("should provide the correct resources", () => {
|
|
@@ -51,140 +51,94 @@ describe("EventsServer", () => {
|
|
|
51
51
|
|
|
52
52
|
expect(resources).toHaveLength(4);
|
|
53
53
|
expect(resources.map((r) => r.uri)).toContain("accessci://events");
|
|
54
|
-
expect(resources.map((r) => r.uri)).toContain(
|
|
55
|
-
|
|
56
|
-
);
|
|
57
|
-
expect(resources.map((r) => r.uri)).toContain(
|
|
58
|
-
"accessci://events/workshops",
|
|
59
|
-
);
|
|
60
|
-
expect(resources.map((r) => r.uri)).toContain(
|
|
61
|
-
"accessci://events/webinars",
|
|
62
|
-
);
|
|
54
|
+
expect(resources.map((r) => r.uri)).toContain("accessci://events/upcoming");
|
|
55
|
+
expect(resources.map((r) => r.uri)).toContain("accessci://events/workshops");
|
|
56
|
+
expect(resources.map((r) => r.uri)).toContain("accessci://events/webinars");
|
|
63
57
|
});
|
|
64
58
|
});
|
|
65
59
|
|
|
66
60
|
describe("URL Building", () => {
|
|
67
|
-
it("should build correct URLs with v2.
|
|
61
|
+
it("should build correct URLs with v2.2 endpoint", () => {
|
|
68
62
|
const url = server["buildEventsUrl"]({});
|
|
69
|
-
expect(url).toContain("/api/2.
|
|
63
|
+
expect(url).toContain("/api/2.2/events");
|
|
70
64
|
});
|
|
71
65
|
|
|
72
|
-
it("should
|
|
73
|
-
const url = server["buildEventsUrl"]({
|
|
74
|
-
beginning_date_relative: "today",
|
|
75
|
-
end_date_relative: "+1week",
|
|
76
|
-
});
|
|
77
|
-
|
|
66
|
+
it("should map 'date: today' to beginning_date_relative", () => {
|
|
67
|
+
const url = server["buildEventsUrl"]({ date: "today" });
|
|
78
68
|
expect(url).toContain("beginning_date_relative=today");
|
|
79
|
-
expect(url).toContain("end_date_relative=%2B1week");
|
|
80
69
|
});
|
|
81
70
|
|
|
82
|
-
it("should
|
|
83
|
-
const url = server["buildEventsUrl"]({
|
|
84
|
-
beginning_date_relative: "today",
|
|
85
|
-
timezone: "America/New_York",
|
|
86
|
-
});
|
|
87
|
-
|
|
71
|
+
it("should map 'date: upcoming' to beginning_date_relative", () => {
|
|
72
|
+
const url = server["buildEventsUrl"]({ date: "upcoming" });
|
|
88
73
|
expect(url).toContain("beginning_date_relative=today");
|
|
89
|
-
expect(url).toContain("timezone=America%2FNew_York");
|
|
90
74
|
});
|
|
91
75
|
|
|
92
|
-
it("should
|
|
93
|
-
const url = server["buildEventsUrl"]({
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
expect(url).toContain("beginning_date=2024-01-01");
|
|
99
|
-
expect(url).toContain("end_date=2024-12-31");
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it("should build correct URLs with faceted filters", () => {
|
|
103
|
-
const url = server["buildEventsUrl"]({
|
|
104
|
-
event_type: "workshop",
|
|
105
|
-
skill_level: "beginner",
|
|
106
|
-
event_tags: "python",
|
|
107
|
-
event_affiliation: "ACCESS",
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
expect(url).toContain("f%5B0%5D=custom_event_type%3Aworkshop");
|
|
111
|
-
expect(url).toContain("f%5B1%5D=custom_event_affiliation%3AACCESS");
|
|
112
|
-
expect(url).toContain("f%5B2%5D=skill_level%3Abeginner");
|
|
113
|
-
expect(url).toContain("f%5B3%5D=custom_event_tags%3Apython");
|
|
76
|
+
it("should map 'date: past' to date range", () => {
|
|
77
|
+
const url = server["buildEventsUrl"]({ date: "past" });
|
|
78
|
+
expect(url).toContain("beginning_date_relative=-1year");
|
|
79
|
+
expect(url).toContain("end_date_relative=today");
|
|
114
80
|
});
|
|
115
81
|
|
|
116
|
-
it("should
|
|
117
|
-
const url = server["buildEventsUrl"]({
|
|
118
|
-
beginning_date_relative: "today",
|
|
119
|
-
end_date: "2024-12-31",
|
|
120
|
-
event_type: "webinar",
|
|
121
|
-
skill_level: "intermediate",
|
|
122
|
-
});
|
|
123
|
-
|
|
82
|
+
it("should map 'date: this_week' to date range", () => {
|
|
83
|
+
const url = server["buildEventsUrl"]({ date: "this_week" });
|
|
124
84
|
expect(url).toContain("beginning_date_relative=today");
|
|
125
|
-
expect(url).toContain("
|
|
126
|
-
expect(url).toContain("f%5B0%5D=custom_event_type%3Awebinar");
|
|
127
|
-
expect(url).toContain("f%5B1%5D=skill_level%3Aintermediate");
|
|
85
|
+
expect(url).toContain("end_date_relative=%2B1week");
|
|
128
86
|
});
|
|
129
87
|
|
|
130
|
-
it("should
|
|
131
|
-
const url = server["buildEventsUrl"]({
|
|
132
|
-
beginning_date_relative: "today",
|
|
133
|
-
end_date_relative: "+1month",
|
|
134
|
-
timezone: "Europe/Berlin",
|
|
135
|
-
event_type: "workshop",
|
|
136
|
-
});
|
|
137
|
-
|
|
88
|
+
it("should map 'date: this_month' to date range", () => {
|
|
89
|
+
const url = server["buildEventsUrl"]({ date: "this_month" });
|
|
138
90
|
expect(url).toContain("beginning_date_relative=today");
|
|
139
91
|
expect(url).toContain("end_date_relative=%2B1month");
|
|
140
|
-
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("should map 'type' to faceted filter", () => {
|
|
95
|
+
const url = server["buildEventsUrl"]({ type: "workshop" });
|
|
141
96
|
expect(url).toContain("f%5B0%5D=custom_event_type%3Aworkshop");
|
|
142
97
|
});
|
|
143
98
|
|
|
144
|
-
it("should
|
|
145
|
-
const url = server["buildEventsUrl"]({
|
|
146
|
-
|
|
147
|
-
|
|
99
|
+
it("should map 'tags' to faceted filter", () => {
|
|
100
|
+
const url = server["buildEventsUrl"]({ tags: "python" });
|
|
101
|
+
expect(url).toContain("f%5B0%5D=custom_event_tags%3Apython");
|
|
102
|
+
});
|
|
148
103
|
|
|
149
|
-
|
|
150
|
-
|
|
104
|
+
it("should map 'skill' to faceted filter", () => {
|
|
105
|
+
const url = server["buildEventsUrl"]({ skill: "beginner" });
|
|
106
|
+
expect(url).toContain("f%5B0%5D=skill_level%3Abeginner");
|
|
151
107
|
});
|
|
152
108
|
|
|
153
|
-
it("should
|
|
154
|
-
const
|
|
155
|
-
"
|
|
156
|
-
"
|
|
157
|
-
"
|
|
158
|
-
"Europe/London",
|
|
159
|
-
"Asia/Tokyo"
|
|
160
|
-
];
|
|
161
|
-
|
|
162
|
-
timezones.forEach(tz => {
|
|
163
|
-
const url = server["buildEventsUrl"]({
|
|
164
|
-
beginning_date_relative: "today",
|
|
165
|
-
timezone: tz,
|
|
166
|
-
});
|
|
167
|
-
expect(url).toContain(`timezone=${encodeURIComponent(tz)}`);
|
|
109
|
+
it("should build URLs with multiple faceted filters", () => {
|
|
110
|
+
const url = server["buildEventsUrl"]({
|
|
111
|
+
type: "workshop",
|
|
112
|
+
tags: "python",
|
|
113
|
+
skill: "beginner",
|
|
168
114
|
});
|
|
115
|
+
|
|
116
|
+
expect(url).toContain("f%5B0%5D=custom_event_type%3Aworkshop");
|
|
117
|
+
expect(url).toContain("f%5B1%5D=custom_event_tags%3Apython");
|
|
118
|
+
expect(url).toContain("f%5B2%5D=skill_level%3Abeginner");
|
|
169
119
|
});
|
|
170
120
|
|
|
171
|
-
it("should include search_api_fulltext parameter", () => {
|
|
121
|
+
it("should include search_api_fulltext for query parameter", () => {
|
|
172
122
|
const url = server["buildEventsUrl"]({
|
|
173
|
-
|
|
174
|
-
beginning_date_relative: "today",
|
|
123
|
+
query: "python machine learning",
|
|
175
124
|
});
|
|
176
125
|
|
|
177
126
|
expect(url).toContain("search_api_fulltext=python+machine+learning");
|
|
178
|
-
expect(url).toContain("beginning_date_relative=today");
|
|
179
127
|
});
|
|
180
128
|
|
|
181
|
-
it("should
|
|
129
|
+
it("should build URLs with mixed universal parameters", () => {
|
|
182
130
|
const url = server["buildEventsUrl"]({
|
|
183
|
-
|
|
131
|
+
query: "gpu",
|
|
132
|
+
date: "this_week",
|
|
133
|
+
type: "webinar",
|
|
134
|
+
skill: "intermediate",
|
|
184
135
|
});
|
|
185
136
|
|
|
186
|
-
expect(url).
|
|
137
|
+
expect(url).toContain("search_api_fulltext=gpu");
|
|
187
138
|
expect(url).toContain("beginning_date_relative=today");
|
|
139
|
+
expect(url).toContain("end_date_relative=%2B1week");
|
|
140
|
+
expect(url).toContain("f%5B0%5D=custom_event_type%3Awebinar");
|
|
141
|
+
expect(url).toContain("f%5B1%5D=skill_level%3Aintermediate");
|
|
188
142
|
});
|
|
189
143
|
});
|
|
190
144
|
|
|
@@ -194,12 +148,12 @@ describe("EventsServer", () => {
|
|
|
194
148
|
id: "1",
|
|
195
149
|
title: "Python Workshop",
|
|
196
150
|
description: "Learn Python basics",
|
|
197
|
-
|
|
198
|
-
|
|
151
|
+
start_date: "2024-08-30T09:00:00",
|
|
152
|
+
end_date: "2024-08-30T17:00:00",
|
|
199
153
|
location: "Online",
|
|
200
154
|
event_type: "workshop",
|
|
201
155
|
event_affiliation: "ACCESS",
|
|
202
|
-
|
|
156
|
+
tags: ["python", "programming", "beginner"],
|
|
203
157
|
skill_level: "beginner",
|
|
204
158
|
speakers: "Dr. Smith",
|
|
205
159
|
contact: "events@example.com",
|
|
@@ -212,12 +166,12 @@ describe("EventsServer", () => {
|
|
|
212
166
|
id: "2",
|
|
213
167
|
title: "Machine Learning Webinar",
|
|
214
168
|
description: "Introduction to ML",
|
|
215
|
-
|
|
216
|
-
|
|
169
|
+
start_date: "2024-09-01T14:00:00",
|
|
170
|
+
end_date: "2024-09-01T15:30:00",
|
|
217
171
|
location: "Virtual",
|
|
218
172
|
event_type: "webinar",
|
|
219
173
|
event_affiliation: "Community",
|
|
220
|
-
|
|
174
|
+
tags: ["machine-learning", "ai", "python"],
|
|
221
175
|
skill_level: "intermediate",
|
|
222
176
|
speakers: "Prof. Johnson",
|
|
223
177
|
contact: "ml@example.com",
|
|
@@ -228,7 +182,7 @@ describe("EventsServer", () => {
|
|
|
228
182
|
},
|
|
229
183
|
];
|
|
230
184
|
|
|
231
|
-
describe("
|
|
185
|
+
describe("search_events", () => {
|
|
232
186
|
it("should get events with no filters", async () => {
|
|
233
187
|
mockHttpClient.get.mockResolvedValue({
|
|
234
188
|
status: 200,
|
|
@@ -236,30 +190,31 @@ describe("EventsServer", () => {
|
|
|
236
190
|
});
|
|
237
191
|
|
|
238
192
|
const result = await server["handleToolCall"]({
|
|
193
|
+
method: "tools/call",
|
|
239
194
|
params: {
|
|
240
|
-
name: "
|
|
195
|
+
name: "search_events",
|
|
241
196
|
arguments: {},
|
|
242
197
|
},
|
|
243
198
|
});
|
|
244
199
|
|
|
245
200
|
expect(mockHttpClient.get).toHaveBeenCalled();
|
|
246
201
|
const responseData = JSON.parse(result.content[0].text);
|
|
247
|
-
expect(responseData.
|
|
248
|
-
expect(responseData.
|
|
202
|
+
expect(responseData.total).toBe(2);
|
|
203
|
+
expect(responseData.items).toHaveLength(2);
|
|
249
204
|
});
|
|
250
205
|
|
|
251
|
-
it("should get events with date
|
|
206
|
+
it("should get events with date filter", async () => {
|
|
252
207
|
mockHttpClient.get.mockResolvedValue({
|
|
253
208
|
status: 200,
|
|
254
209
|
data: mockEventsData,
|
|
255
210
|
});
|
|
256
211
|
|
|
257
|
-
|
|
212
|
+
await server["handleToolCall"]({
|
|
213
|
+
method: "tools/call",
|
|
258
214
|
params: {
|
|
259
|
-
name: "
|
|
215
|
+
name: "search_events",
|
|
260
216
|
arguments: {
|
|
261
|
-
|
|
262
|
-
end_date_relative: "+1month",
|
|
217
|
+
date: "this_month",
|
|
263
218
|
},
|
|
264
219
|
},
|
|
265
220
|
});
|
|
@@ -269,433 +224,158 @@ describe("EventsServer", () => {
|
|
|
269
224
|
expect(calledUrl).toContain("end_date_relative=%2B1month");
|
|
270
225
|
});
|
|
271
226
|
|
|
272
|
-
it("should get events with
|
|
227
|
+
it("should get events with type filter", async () => {
|
|
273
228
|
mockHttpClient.get.mockResolvedValue({
|
|
274
229
|
status: 200,
|
|
275
230
|
data: [mockEventsData[0]], // Only workshop
|
|
276
231
|
});
|
|
277
232
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
name: "get_events",
|
|
281
|
-
arguments: {
|
|
282
|
-
event_type: "workshop",
|
|
283
|
-
skill_level: "beginner",
|
|
284
|
-
},
|
|
285
|
-
},
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
289
|
-
expect(calledUrl).toContain("custom_event_type%3Aworkshop");
|
|
290
|
-
expect(calledUrl).toContain("skill_level%3Abeginner");
|
|
291
|
-
});
|
|
292
|
-
|
|
293
|
-
it("should apply limit to events", async () => {
|
|
294
|
-
mockHttpClient.get.mockResolvedValue({
|
|
295
|
-
status: 200,
|
|
296
|
-
data: mockEventsData,
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
const result = await server["handleToolCall"]({
|
|
300
|
-
params: {
|
|
301
|
-
name: "get_events",
|
|
302
|
-
arguments: {
|
|
303
|
-
limit: 1,
|
|
304
|
-
},
|
|
305
|
-
},
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
309
|
-
expect(responseData.events).toHaveLength(1);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
it("should enhance events with calculated fields", async () => {
|
|
313
|
-
mockHttpClient.get.mockResolvedValue({
|
|
314
|
-
status: 200,
|
|
315
|
-
data: mockEventsData,
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
const result = await server["handleToolCall"]({
|
|
319
|
-
params: {
|
|
320
|
-
name: "get_events",
|
|
321
|
-
arguments: {},
|
|
322
|
-
},
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
326
|
-
const event = responseData.events[0];
|
|
327
|
-
|
|
328
|
-
// Check enhanced fields
|
|
329
|
-
expect(event.tags).toEqual(["python", "programming", "beginner"]);
|
|
330
|
-
expect(event.duration_hours).toBe(8); // 9am to 5pm
|
|
331
|
-
expect(event.starts_in_hours).toBeDefined();
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
it("should extract popular tags", async () => {
|
|
335
|
-
mockHttpClient.get.mockResolvedValue({
|
|
336
|
-
status: 200,
|
|
337
|
-
data: mockEventsData,
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
const result = await server["handleToolCall"]({
|
|
341
|
-
params: {
|
|
342
|
-
name: "get_events",
|
|
343
|
-
arguments: {},
|
|
344
|
-
},
|
|
345
|
-
});
|
|
346
|
-
|
|
347
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
348
|
-
expect(responseData.popular_tags).toContain("python");
|
|
349
|
-
expect(responseData.popular_tags).toContain("machine-learning");
|
|
350
|
-
});
|
|
351
|
-
|
|
352
|
-
it("should include timezone parameter in URL when provided", async () => {
|
|
353
|
-
mockHttpClient.get.mockResolvedValue({
|
|
354
|
-
status: 200,
|
|
355
|
-
data: mockEventsData,
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
const result = await server["handleToolCall"]({
|
|
359
|
-
params: {
|
|
360
|
-
name: "get_events",
|
|
361
|
-
arguments: {
|
|
362
|
-
beginning_date_relative: "today",
|
|
363
|
-
timezone: "America/New_York",
|
|
364
|
-
},
|
|
365
|
-
},
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
369
|
-
expect(calledUrl).toContain("timezone=America%2FNew_York");
|
|
370
|
-
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
it("should include API info with timezone used", async () => {
|
|
374
|
-
mockHttpClient.get.mockResolvedValue({
|
|
375
|
-
status: 200,
|
|
376
|
-
data: mockEventsData,
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
const result = await server["handleToolCall"]({
|
|
380
|
-
params: {
|
|
381
|
-
name: "get_events",
|
|
382
|
-
arguments: {
|
|
383
|
-
beginning_date_relative: "today",
|
|
384
|
-
timezone: "Europe/London",
|
|
385
|
-
},
|
|
386
|
-
},
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
390
|
-
expect(responseData.api_info).toBeDefined();
|
|
391
|
-
expect(responseData.api_info.endpoint_version).toBe("2.1");
|
|
392
|
-
expect(responseData.api_info.timezone_used).toBe("Europe/London");
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
it("should default to UTC timezone when not specified", async () => {
|
|
396
|
-
mockHttpClient.get.mockResolvedValue({
|
|
397
|
-
status: 200,
|
|
398
|
-
data: mockEventsData,
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
const result = await server["handleToolCall"]({
|
|
402
|
-
params: {
|
|
403
|
-
name: "get_events",
|
|
404
|
-
arguments: {
|
|
405
|
-
beginning_date_relative: "today",
|
|
406
|
-
},
|
|
407
|
-
},
|
|
408
|
-
});
|
|
409
|
-
|
|
410
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
411
|
-
expect(responseData.api_info.timezone_used).toBe("UTC");
|
|
412
|
-
});
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
describe("get_upcoming_events", () => {
|
|
416
|
-
it("should get upcoming events with default limit", async () => {
|
|
417
|
-
mockHttpClient.get.mockResolvedValue({
|
|
418
|
-
status: 200,
|
|
419
|
-
data: mockEventsData,
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
const result = await server["handleToolCall"]({
|
|
423
|
-
params: {
|
|
424
|
-
name: "get_upcoming_events",
|
|
425
|
-
arguments: {},
|
|
426
|
-
},
|
|
427
|
-
});
|
|
428
|
-
|
|
429
|
-
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
430
|
-
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
431
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
432
|
-
expect(responseData.events).toBeDefined();
|
|
433
|
-
});
|
|
434
|
-
|
|
435
|
-
it("should filter upcoming events by type", async () => {
|
|
436
|
-
mockHttpClient.get.mockResolvedValue({
|
|
437
|
-
status: 200,
|
|
438
|
-
data: [mockEventsData[1]], // Only webinar
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
const result = await server["handleToolCall"]({
|
|
233
|
+
await server["handleToolCall"]({
|
|
234
|
+
method: "tools/call",
|
|
442
235
|
params: {
|
|
443
|
-
name: "
|
|
236
|
+
name: "search_events",
|
|
444
237
|
arguments: {
|
|
445
|
-
|
|
446
|
-
limit: 10,
|
|
238
|
+
type: "workshop",
|
|
447
239
|
},
|
|
448
240
|
},
|
|
449
241
|
});
|
|
450
242
|
|
|
451
243
|
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
452
|
-
expect(calledUrl).toContain("custom_event_type%
|
|
244
|
+
expect(calledUrl).toContain("f%5B0%5D=custom_event_type%3Aworkshop");
|
|
453
245
|
});
|
|
454
246
|
|
|
455
|
-
it("should
|
|
247
|
+
it("should get events with skill filter", async () => {
|
|
456
248
|
mockHttpClient.get.mockResolvedValue({
|
|
457
249
|
status: 200,
|
|
458
|
-
data: mockEventsData,
|
|
250
|
+
data: [mockEventsData[0]],
|
|
459
251
|
});
|
|
460
252
|
|
|
461
|
-
|
|
253
|
+
await server["handleToolCall"]({
|
|
254
|
+
method: "tools/call",
|
|
462
255
|
params: {
|
|
463
|
-
name: "
|
|
256
|
+
name: "search_events",
|
|
464
257
|
arguments: {
|
|
465
|
-
|
|
466
|
-
limit: 5,
|
|
258
|
+
skill: "beginner",
|
|
467
259
|
},
|
|
468
260
|
},
|
|
469
261
|
});
|
|
470
262
|
|
|
471
263
|
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
472
|
-
expect(calledUrl).toContain("
|
|
473
|
-
expect(calledUrl).toContain("timezone=America%2FChicago");
|
|
264
|
+
expect(calledUrl).toContain("f%5B0%5D=skill_level%3Abeginner");
|
|
474
265
|
});
|
|
475
|
-
});
|
|
476
266
|
|
|
477
|
-
|
|
478
|
-
it("should search events by query in title", async () => {
|
|
267
|
+
it("should get events with tags filter", async () => {
|
|
479
268
|
mockHttpClient.get.mockResolvedValue({
|
|
480
269
|
status: 200,
|
|
481
270
|
data: mockEventsData,
|
|
482
271
|
});
|
|
483
272
|
|
|
484
|
-
|
|
273
|
+
await server["handleToolCall"]({
|
|
274
|
+
method: "tools/call",
|
|
485
275
|
params: {
|
|
486
276
|
name: "search_events",
|
|
487
277
|
arguments: {
|
|
488
|
-
|
|
278
|
+
tags: "python",
|
|
489
279
|
},
|
|
490
280
|
},
|
|
491
281
|
});
|
|
492
282
|
|
|
493
|
-
const
|
|
494
|
-
expect(
|
|
495
|
-
expect(responseData.total_matches).toBe(2); // Both events have 'python' in tags
|
|
496
|
-
expect(responseData.events[0].title).toContain("Python");
|
|
283
|
+
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
284
|
+
expect(calledUrl).toContain("f%5B0%5D=custom_event_tags%3Apython");
|
|
497
285
|
});
|
|
498
286
|
|
|
499
|
-
it("should
|
|
287
|
+
it("should apply limit to events", async () => {
|
|
500
288
|
mockHttpClient.get.mockResolvedValue({
|
|
501
289
|
status: 200,
|
|
502
290
|
data: mockEventsData,
|
|
503
291
|
});
|
|
504
292
|
|
|
505
293
|
const result = await server["handleToolCall"]({
|
|
294
|
+
method: "tools/call",
|
|
506
295
|
params: {
|
|
507
296
|
name: "search_events",
|
|
508
297
|
arguments: {
|
|
509
|
-
|
|
298
|
+
limit: 1,
|
|
510
299
|
},
|
|
511
300
|
},
|
|
512
301
|
});
|
|
513
302
|
|
|
514
|
-
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
515
|
-
expect(calledUrl).toContain("search_api_fulltext=ML");
|
|
516
|
-
|
|
517
303
|
const responseData = JSON.parse(result.content[0].text);
|
|
518
|
-
expect(responseData.
|
|
519
|
-
expect(responseData.search_method).toBe("API native full-text search");
|
|
520
|
-
expect(responseData.total_matches).toBe(mockEventsData.length); // API returns raw count
|
|
304
|
+
expect(responseData.items).toHaveLength(1);
|
|
521
305
|
});
|
|
522
306
|
|
|
523
|
-
it("should
|
|
307
|
+
it("should enhance events with calculated fields", async () => {
|
|
524
308
|
mockHttpClient.get.mockResolvedValue({
|
|
525
309
|
status: 200,
|
|
526
310
|
data: mockEventsData,
|
|
527
311
|
});
|
|
528
312
|
|
|
529
313
|
const result = await server["handleToolCall"]({
|
|
314
|
+
method: "tools/call",
|
|
530
315
|
params: {
|
|
531
316
|
name: "search_events",
|
|
532
|
-
arguments: {
|
|
533
|
-
query: "machine-learning",
|
|
534
|
-
},
|
|
317
|
+
arguments: {},
|
|
535
318
|
},
|
|
536
319
|
});
|
|
537
320
|
|
|
538
|
-
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
539
|
-
expect(calledUrl).toContain("search_api_fulltext=machine-learning");
|
|
540
|
-
|
|
541
321
|
const responseData = JSON.parse(result.content[0].text);
|
|
542
|
-
|
|
543
|
-
});
|
|
322
|
+
const event = responseData.items[0];
|
|
544
323
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
const result = await server["handleToolCall"]({
|
|
552
|
-
params: {
|
|
553
|
-
name: "search_events",
|
|
554
|
-
arguments: {
|
|
555
|
-
query: "workshop",
|
|
556
|
-
beginning_date_relative: "-1month",
|
|
557
|
-
limit: 5,
|
|
558
|
-
},
|
|
559
|
-
},
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
563
|
-
expect(calledUrl).toContain("beginning_date_relative=-1month");
|
|
324
|
+
// Check enhanced fields
|
|
325
|
+
expect(event.tags).toEqual(["python", "programming", "beginner"]);
|
|
326
|
+
expect(event.duration_hours).toBe(8); // 9am to 5pm
|
|
327
|
+
expect(event.starts_in_hours).toBeDefined();
|
|
564
328
|
});
|
|
565
329
|
|
|
566
|
-
it("should
|
|
330
|
+
it("should search events by query", async () => {
|
|
567
331
|
mockHttpClient.get.mockResolvedValue({
|
|
568
332
|
status: 200,
|
|
569
333
|
data: mockEventsData,
|
|
570
334
|
});
|
|
571
335
|
|
|
572
336
|
const result = await server["handleToolCall"]({
|
|
337
|
+
method: "tools/call",
|
|
573
338
|
params: {
|
|
574
339
|
name: "search_events",
|
|
575
340
|
arguments: {
|
|
576
|
-
query: "
|
|
577
|
-
timezone: "Asia/Tokyo",
|
|
578
|
-
},
|
|
579
|
-
},
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
583
|
-
expect(calledUrl).toContain("timezone=Asia%2FTokyo");
|
|
584
|
-
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
585
|
-
});
|
|
586
|
-
});
|
|
587
|
-
|
|
588
|
-
describe("get_events_by_tag", () => {
|
|
589
|
-
it("should get events by tag for upcoming time range", async () => {
|
|
590
|
-
mockHttpClient.get.mockResolvedValue({
|
|
591
|
-
status: 200,
|
|
592
|
-
data: mockEventsData,
|
|
593
|
-
});
|
|
594
|
-
|
|
595
|
-
const result = await server["handleToolCall"]({
|
|
596
|
-
params: {
|
|
597
|
-
name: "get_events_by_tag",
|
|
598
|
-
arguments: {
|
|
599
|
-
tag: "python",
|
|
600
|
-
time_range: "upcoming",
|
|
341
|
+
query: "Python",
|
|
601
342
|
},
|
|
602
343
|
},
|
|
603
344
|
});
|
|
604
345
|
|
|
605
346
|
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
606
|
-
expect(calledUrl).toContain("
|
|
607
|
-
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
347
|
+
expect(calledUrl).toContain("search_api_fulltext=Python");
|
|
608
348
|
|
|
609
349
|
const responseData = JSON.parse(result.content[0].text);
|
|
610
|
-
expect(responseData.
|
|
611
|
-
expect(responseData.
|
|
612
|
-
});
|
|
613
|
-
|
|
614
|
-
it("should get events by tag for this week", async () => {
|
|
615
|
-
mockHttpClient.get.mockResolvedValue({
|
|
616
|
-
status: 200,
|
|
617
|
-
data: mockEventsData,
|
|
618
|
-
});
|
|
619
|
-
|
|
620
|
-
const result = await server["handleToolCall"]({
|
|
621
|
-
params: {
|
|
622
|
-
name: "get_events_by_tag",
|
|
623
|
-
arguments: {
|
|
624
|
-
tag: "ai",
|
|
625
|
-
time_range: "this_week",
|
|
626
|
-
},
|
|
627
|
-
},
|
|
628
|
-
});
|
|
629
|
-
|
|
630
|
-
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
631
|
-
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
632
|
-
expect(calledUrl).toContain("end_date_relative=%2B1week");
|
|
350
|
+
expect(responseData.total).toBe(2);
|
|
351
|
+
expect(responseData.items).toHaveLength(2);
|
|
633
352
|
});
|
|
634
353
|
|
|
635
|
-
it("should
|
|
354
|
+
it("should combine multiple universal parameters", async () => {
|
|
636
355
|
mockHttpClient.get.mockResolvedValue({
|
|
637
356
|
status: 200,
|
|
638
|
-
data: mockEventsData,
|
|
357
|
+
data: [mockEventsData[0]],
|
|
639
358
|
});
|
|
640
359
|
|
|
641
|
-
|
|
360
|
+
await server["handleToolCall"]({
|
|
361
|
+
method: "tools/call",
|
|
642
362
|
params: {
|
|
643
|
-
name: "
|
|
644
|
-
arguments: {
|
|
645
|
-
tag: "programming",
|
|
646
|
-
time_range: "this_month",
|
|
647
|
-
},
|
|
648
|
-
},
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
652
|
-
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
653
|
-
expect(calledUrl).toContain("end_date_relative=%2B1month");
|
|
654
|
-
});
|
|
655
|
-
|
|
656
|
-
it("should get all events by tag", async () => {
|
|
657
|
-
mockHttpClient.get.mockResolvedValue({
|
|
658
|
-
status: 200,
|
|
659
|
-
data: mockEventsData,
|
|
660
|
-
});
|
|
661
|
-
|
|
662
|
-
const result = await server["handleToolCall"]({
|
|
663
|
-
params: {
|
|
664
|
-
name: "get_events_by_tag",
|
|
665
|
-
arguments: {
|
|
666
|
-
tag: "beginner",
|
|
667
|
-
time_range: "all",
|
|
668
|
-
},
|
|
669
|
-
},
|
|
670
|
-
});
|
|
671
|
-
|
|
672
|
-
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
673
|
-
expect(calledUrl).not.toContain("beginning_date_relative");
|
|
674
|
-
expect(calledUrl).not.toContain("end_date_relative");
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
it("should include timezone parameter for time-based ranges", async () => {
|
|
678
|
-
mockHttpClient.get.mockResolvedValue({
|
|
679
|
-
status: 200,
|
|
680
|
-
data: mockEventsData,
|
|
681
|
-
});
|
|
682
|
-
|
|
683
|
-
const result = await server["handleToolCall"]({
|
|
684
|
-
params: {
|
|
685
|
-
name: "get_events_by_tag",
|
|
363
|
+
name: "search_events",
|
|
686
364
|
arguments: {
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
365
|
+
query: "Python",
|
|
366
|
+
date: "this_week",
|
|
367
|
+
type: "workshop",
|
|
368
|
+
skill: "beginner",
|
|
690
369
|
},
|
|
691
370
|
},
|
|
692
371
|
});
|
|
693
372
|
|
|
694
373
|
const calledUrl = mockHttpClient.get.mock.calls[0][0];
|
|
695
|
-
expect(calledUrl).toContain("
|
|
374
|
+
expect(calledUrl).toContain("search_api_fulltext=Python");
|
|
696
375
|
expect(calledUrl).toContain("beginning_date_relative=today");
|
|
697
376
|
expect(calledUrl).toContain("end_date_relative=%2B1week");
|
|
698
|
-
expect(calledUrl).toContain("
|
|
377
|
+
expect(calledUrl).toContain("f%5B0%5D=custom_event_type%3Aworkshop");
|
|
378
|
+
expect(calledUrl).toContain("f%5B1%5D=skill_level%3Abeginner");
|
|
699
379
|
});
|
|
700
380
|
});
|
|
701
381
|
|
|
@@ -707,13 +387,14 @@ describe("EventsServer", () => {
|
|
|
707
387
|
});
|
|
708
388
|
|
|
709
389
|
const result = await server["handleToolCall"]({
|
|
390
|
+
method: "tools/call",
|
|
710
391
|
params: {
|
|
711
|
-
name: "
|
|
392
|
+
name: "search_events",
|
|
712
393
|
arguments: {},
|
|
713
394
|
},
|
|
714
395
|
});
|
|
715
396
|
|
|
716
|
-
expect(result.content[0].text).toContain("
|
|
397
|
+
expect(result.content[0].text).toContain("error");
|
|
717
398
|
expect(result.content[0].text).toContain("404");
|
|
718
399
|
});
|
|
719
400
|
|
|
@@ -721,17 +402,19 @@ describe("EventsServer", () => {
|
|
|
721
402
|
mockHttpClient.get.mockRejectedValue(new Error("Network error"));
|
|
722
403
|
|
|
723
404
|
const result = await server["handleToolCall"]({
|
|
405
|
+
method: "tools/call",
|
|
724
406
|
params: {
|
|
725
|
-
name: "
|
|
407
|
+
name: "search_events",
|
|
726
408
|
arguments: {},
|
|
727
409
|
},
|
|
728
410
|
});
|
|
729
411
|
|
|
730
|
-
expect(result.content[0].text).toContain("
|
|
412
|
+
expect(result.content[0].text).toContain("error");
|
|
731
413
|
});
|
|
732
414
|
|
|
733
415
|
it("should handle unknown tools", async () => {
|
|
734
416
|
const result = await server["handleToolCall"]({
|
|
417
|
+
method: "tools/call",
|
|
735
418
|
params: {
|
|
736
419
|
name: "unknown_tool",
|
|
737
420
|
arguments: {},
|
|
@@ -749,9 +432,9 @@ describe("EventsServer", () => {
|
|
|
749
432
|
id: "1",
|
|
750
433
|
title: "Test Event",
|
|
751
434
|
event_type: "workshop",
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
435
|
+
start_date: "2024-08-30T09:00:00",
|
|
436
|
+
end_date: "2024-08-30T17:00:00",
|
|
437
|
+
tags: ["test"],
|
|
755
438
|
},
|
|
756
439
|
];
|
|
757
440
|
|
|
@@ -816,38 +499,4 @@ describe("EventsServer", () => {
|
|
|
816
499
|
}).rejects.toThrow("Unknown resource");
|
|
817
500
|
});
|
|
818
501
|
});
|
|
819
|
-
|
|
820
|
-
describe("Utility Methods", () => {
|
|
821
|
-
it("should extract popular tags correctly", () => {
|
|
822
|
-
const events = [
|
|
823
|
-
{ tags: ["python", "ai", "machine-learning"] },
|
|
824
|
-
{ tags: ["python", "data-science"] },
|
|
825
|
-
{ tags: ["ai", "gpu"] },
|
|
826
|
-
{ tags: ["python"] },
|
|
827
|
-
{ tags: ["machine-learning"] },
|
|
828
|
-
];
|
|
829
|
-
|
|
830
|
-
const popularTags = server["getPopularTags"](events);
|
|
831
|
-
expect(popularTags[0]).toBe("python"); // Most frequent (3 times)
|
|
832
|
-
expect(popularTags[1]).toBe("ai"); // Second most frequent (2 times)
|
|
833
|
-
expect(popularTags[2]).toBe("machine-learning"); // Also 2 times
|
|
834
|
-
expect(popularTags).toHaveLength(Math.min(10, 5)); // Should return up to 10 tags
|
|
835
|
-
});
|
|
836
|
-
|
|
837
|
-
it("should handle empty events for popular tags", () => {
|
|
838
|
-
const popularTags = server["getPopularTags"]([]);
|
|
839
|
-
expect(popularTags).toEqual([]);
|
|
840
|
-
});
|
|
841
|
-
|
|
842
|
-
it("should handle events without tags", () => {
|
|
843
|
-
const events = [
|
|
844
|
-
{ title: "Event 1" },
|
|
845
|
-
{ title: "Event 2", tags: null },
|
|
846
|
-
{ title: "Event 3", tags: undefined },
|
|
847
|
-
];
|
|
848
|
-
|
|
849
|
-
const popularTags = server["getPopularTags"](events);
|
|
850
|
-
expect(popularTags).toEqual([]);
|
|
851
|
-
});
|
|
852
|
-
});
|
|
853
502
|
});
|