@access-mcp/announcements 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 +106 -135
- package/dist/server.d.ts +85 -90
- package/dist/server.js +905 -152
- package/package.json +2 -2
- package/src/index.ts +1 -1
- package/src/server.integration.test.ts +355 -26
- package/src/server.test.ts +1194 -30
- package/src/server.ts +1156 -58
- package/vitest.integration.config.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@access-mcp/announcements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "MCP server for ACCESS Support Announcements API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"author": "ACCESS-CI",
|
|
27
27
|
"license": "MIT",
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@access-mcp/shared": "
|
|
29
|
+
"@access-mcp/shared": "*",
|
|
30
30
|
"@modelcontextprotocol/sdk": "^0.5.0",
|
|
31
31
|
"axios": "^1.7.0"
|
|
32
32
|
},
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,29 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
1
|
+
import { describe, it, expect, beforeEach, afterAll } from "vitest";
|
|
2
2
|
import { AnnouncementsServer } from "./server.js";
|
|
3
|
+
import { requestContextStorage, RequestContext } from "@access-mcp/shared";
|
|
4
|
+
|
|
5
|
+
interface TextContent {
|
|
6
|
+
type: "text";
|
|
7
|
+
text: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface AnnouncementItem {
|
|
11
|
+
title: string;
|
|
12
|
+
body: string;
|
|
13
|
+
published_date: string;
|
|
14
|
+
tags: string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface MyAnnouncementItem {
|
|
18
|
+
uuid: string;
|
|
19
|
+
nid: number;
|
|
20
|
+
title: string;
|
|
21
|
+
status: string;
|
|
22
|
+
created: string;
|
|
23
|
+
published_date: string;
|
|
24
|
+
summary: string;
|
|
25
|
+
edit_url: string;
|
|
26
|
+
}
|
|
3
27
|
|
|
4
28
|
/**
|
|
5
29
|
* Integration tests for AnnouncementsServer
|
|
@@ -15,24 +39,25 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
15
39
|
describe("get_announcements", () => {
|
|
16
40
|
it("should fetch real announcements from API", async () => {
|
|
17
41
|
const result = await server["handleToolCall"]({
|
|
42
|
+
method: "tools/call",
|
|
18
43
|
params: {
|
|
19
44
|
name: "search_announcements",
|
|
20
45
|
arguments: {
|
|
21
46
|
limit: 5,
|
|
22
47
|
},
|
|
23
48
|
},
|
|
24
|
-
}
|
|
49
|
+
});
|
|
25
50
|
|
|
26
51
|
expect(result.isError).toBeFalsy();
|
|
27
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
28
|
-
|
|
52
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
53
|
+
|
|
29
54
|
// Check structure
|
|
30
55
|
expect(responseData).toHaveProperty("total");
|
|
31
56
|
expect(responseData).toHaveProperty("items");
|
|
32
57
|
|
|
33
58
|
// Items should be an array
|
|
34
59
|
expect(Array.isArray(responseData.items)).toBe(true);
|
|
35
|
-
|
|
60
|
+
|
|
36
61
|
// If there are announcements, check their structure
|
|
37
62
|
if (responseData.items.length > 0) {
|
|
38
63
|
const firstAnnouncement = responseData.items[0];
|
|
@@ -46,6 +71,7 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
46
71
|
|
|
47
72
|
it("should filter by tags", async () => {
|
|
48
73
|
const result = await server["handleToolCall"]({
|
|
74
|
+
method: "tools/call",
|
|
49
75
|
params: {
|
|
50
76
|
name: "search_announcements",
|
|
51
77
|
arguments: {
|
|
@@ -53,20 +79,19 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
53
79
|
limit: 3,
|
|
54
80
|
},
|
|
55
81
|
},
|
|
56
|
-
}
|
|
82
|
+
});
|
|
57
83
|
|
|
58
84
|
expect(result.isError).toBeFalsy();
|
|
59
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
85
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
60
86
|
|
|
61
87
|
expect(responseData).toHaveProperty("items");
|
|
62
88
|
expect(Array.isArray(responseData.items)).toBe(true);
|
|
63
89
|
|
|
64
90
|
// If maintenance announcements exist, they should contain the tag
|
|
65
91
|
if (responseData.items.length > 0) {
|
|
66
|
-
const hasMaintenanceTag = responseData.items.some(
|
|
67
|
-
ann
|
|
68
|
-
tag.toLowerCase().includes("maintenance")
|
|
69
|
-
)
|
|
92
|
+
const hasMaintenanceTag = responseData.items.some(
|
|
93
|
+
(ann: AnnouncementItem) =>
|
|
94
|
+
ann.tags && ann.tags.some((tag: string) => tag.toLowerCase().includes("maintenance"))
|
|
70
95
|
);
|
|
71
96
|
// This might not always be true if the API doesn't have maintenance announcements
|
|
72
97
|
console.log("Found maintenance announcements:", hasMaintenanceTag);
|
|
@@ -75,6 +100,7 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
75
100
|
|
|
76
101
|
it("should handle date range filters", async () => {
|
|
77
102
|
const result = await server["handleToolCall"]({
|
|
103
|
+
method: "tools/call",
|
|
78
104
|
params: {
|
|
79
105
|
name: "search_announcements",
|
|
80
106
|
arguments: {
|
|
@@ -82,10 +108,10 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
82
108
|
limit: 10,
|
|
83
109
|
},
|
|
84
110
|
},
|
|
85
|
-
}
|
|
111
|
+
});
|
|
86
112
|
|
|
87
113
|
expect(result.isError).toBeFalsy();
|
|
88
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
114
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
89
115
|
|
|
90
116
|
expect(responseData).toHaveProperty("items");
|
|
91
117
|
|
|
@@ -94,7 +120,7 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
94
120
|
const oneMonthAgo = new Date();
|
|
95
121
|
oneMonthAgo.setMonth(oneMonthAgo.getMonth() - 1);
|
|
96
122
|
|
|
97
|
-
responseData.items.forEach((ann:
|
|
123
|
+
responseData.items.forEach((ann: AnnouncementItem) => {
|
|
98
124
|
const annDate = new Date(ann.published_date);
|
|
99
125
|
expect(annDate.getTime()).toBeGreaterThanOrEqual(oneMonthAgo.getTime());
|
|
100
126
|
});
|
|
@@ -105,16 +131,17 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
105
131
|
describe("get_recent_announcements", () => {
|
|
106
132
|
it("should fetch announcements from the past week", async () => {
|
|
107
133
|
const result = await server["handleToolCall"]({
|
|
134
|
+
method: "tools/call",
|
|
108
135
|
params: {
|
|
109
136
|
name: "search_announcements",
|
|
110
137
|
arguments: {
|
|
111
138
|
date: "this_week",
|
|
112
139
|
},
|
|
113
140
|
},
|
|
114
|
-
}
|
|
141
|
+
});
|
|
115
142
|
|
|
116
143
|
expect(result.isError).toBeFalsy();
|
|
117
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
144
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
118
145
|
|
|
119
146
|
expect(responseData).toHaveProperty("items");
|
|
120
147
|
|
|
@@ -123,7 +150,7 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
123
150
|
const oneWeekAgo = new Date();
|
|
124
151
|
oneWeekAgo.setDate(oneWeekAgo.getDate() - 7);
|
|
125
152
|
|
|
126
|
-
responseData.items.forEach((ann:
|
|
153
|
+
responseData.items.forEach((ann: AnnouncementItem) => {
|
|
127
154
|
const annDate = new Date(ann.published_date);
|
|
128
155
|
expect(annDate.getTime()).toBeGreaterThanOrEqual(oneWeekAgo.getTime());
|
|
129
156
|
});
|
|
@@ -132,16 +159,17 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
132
159
|
|
|
133
160
|
it("should handle today filter", async () => {
|
|
134
161
|
const result = await server["handleToolCall"]({
|
|
162
|
+
method: "tools/call",
|
|
135
163
|
params: {
|
|
136
164
|
name: "search_announcements",
|
|
137
165
|
arguments: {
|
|
138
166
|
date: "today",
|
|
139
167
|
},
|
|
140
168
|
},
|
|
141
|
-
}
|
|
169
|
+
});
|
|
142
170
|
|
|
143
171
|
expect(result.isError).toBeFalsy();
|
|
144
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
172
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
145
173
|
|
|
146
174
|
expect(responseData).toHaveProperty("items");
|
|
147
175
|
|
|
@@ -153,16 +181,17 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
153
181
|
describe("get_announcements with limit", () => {
|
|
154
182
|
it("should respect limit parameter", async () => {
|
|
155
183
|
const result = await server["handleToolCall"]({
|
|
184
|
+
method: "tools/call",
|
|
156
185
|
params: {
|
|
157
186
|
name: "search_announcements",
|
|
158
187
|
arguments: {
|
|
159
188
|
limit: 5,
|
|
160
189
|
},
|
|
161
190
|
},
|
|
162
|
-
}
|
|
191
|
+
});
|
|
163
192
|
|
|
164
193
|
expect(result.isError).toBeFalsy();
|
|
165
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
194
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
166
195
|
|
|
167
196
|
// Should return a valid response
|
|
168
197
|
expect(responseData).toHaveProperty("items");
|
|
@@ -173,34 +202,57 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
173
202
|
}, 10000);
|
|
174
203
|
});
|
|
175
204
|
|
|
205
|
+
describe("search with query parameter", () => {
|
|
206
|
+
it("should perform full-text search", async () => {
|
|
207
|
+
const result = await server["handleToolCall"]({
|
|
208
|
+
method: "tools/call",
|
|
209
|
+
params: {
|
|
210
|
+
name: "search_announcements",
|
|
211
|
+
arguments: {
|
|
212
|
+
query: "ACCESS",
|
|
213
|
+
limit: 5,
|
|
214
|
+
},
|
|
215
|
+
},
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
expect(result.isError).toBeFalsy();
|
|
219
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
220
|
+
|
|
221
|
+
expect(responseData).toHaveProperty("items");
|
|
222
|
+
expect(Array.isArray(responseData.items)).toBe(true);
|
|
223
|
+
}, 10000);
|
|
224
|
+
});
|
|
225
|
+
|
|
176
226
|
describe("API Error Handling", () => {
|
|
177
227
|
it("should handle search with no parameters", async () => {
|
|
178
228
|
const result = await server["handleToolCall"]({
|
|
229
|
+
method: "tools/call",
|
|
179
230
|
params: {
|
|
180
231
|
name: "search_announcements",
|
|
181
232
|
arguments: {},
|
|
182
233
|
},
|
|
183
|
-
}
|
|
234
|
+
});
|
|
184
235
|
|
|
185
236
|
// Should return default results
|
|
186
237
|
expect(result).toBeDefined();
|
|
187
238
|
expect(result.content).toBeDefined();
|
|
188
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
239
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
189
240
|
expect(responseData).toHaveProperty("items");
|
|
190
241
|
}, 10000);
|
|
191
242
|
|
|
192
243
|
it("should handle empty results", async () => {
|
|
193
244
|
const result = await server["handleToolCall"]({
|
|
245
|
+
method: "tools/call",
|
|
194
246
|
params: {
|
|
195
247
|
name: "search_announcements",
|
|
196
248
|
arguments: {
|
|
197
249
|
tags: "nonexistent-tag-xyz-123-456",
|
|
198
250
|
},
|
|
199
251
|
},
|
|
200
|
-
}
|
|
252
|
+
});
|
|
201
253
|
|
|
202
254
|
expect(result.isError).toBeFalsy();
|
|
203
|
-
const responseData = JSON.parse(result.content[0].text);
|
|
255
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
204
256
|
|
|
205
257
|
// May have 0 or few results
|
|
206
258
|
expect(responseData).toHaveProperty("total");
|
|
@@ -208,4 +260,281 @@ describe("AnnouncementsServer Integration Tests", () => {
|
|
|
208
260
|
expect(Array.isArray(responseData.items)).toBe(true);
|
|
209
261
|
}, 10000);
|
|
210
262
|
});
|
|
211
|
-
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* E2E tests for authenticated CRUD operations.
|
|
267
|
+
*
|
|
268
|
+
* Requires env vars: DRUPAL_API_URL, DRUPAL_USERNAME, DRUPAL_PASSWORD, ACTING_USER
|
|
269
|
+
* Tests run against a live Drupal instance (e.g., accessmatch.ddev.site).
|
|
270
|
+
* Skipped if DRUPAL_USERNAME is not set.
|
|
271
|
+
*/
|
|
272
|
+
const hasDrupalCreds = !!process.env.DRUPAL_USERNAME && !!process.env.DRUPAL_PASSWORD;
|
|
273
|
+
|
|
274
|
+
describe.skipIf(!hasDrupalCreds)("Authenticated CRUD E2E Tests", () => {
|
|
275
|
+
let server: AnnouncementsServer;
|
|
276
|
+
const createdUuids: string[] = [];
|
|
277
|
+
|
|
278
|
+
beforeEach(() => {
|
|
279
|
+
server = new AnnouncementsServer();
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Clean up any announcements created during tests
|
|
283
|
+
afterAll(async () => {
|
|
284
|
+
if (createdUuids.length === 0) return;
|
|
285
|
+
const cleanupServer = new AnnouncementsServer();
|
|
286
|
+
for (const uuid of createdUuids) {
|
|
287
|
+
try {
|
|
288
|
+
await cleanupServer["handleToolCall"]({
|
|
289
|
+
method: "tools/call",
|
|
290
|
+
params: {
|
|
291
|
+
name: "delete_announcement",
|
|
292
|
+
arguments: { uuid, confirmed: true },
|
|
293
|
+
},
|
|
294
|
+
});
|
|
295
|
+
} catch {
|
|
296
|
+
// Best effort cleanup
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
describe("get_announcement_context", () => {
|
|
302
|
+
it("should return tags and coordinator status via views endpoint", async () => {
|
|
303
|
+
const result = await server["handleToolCall"]({
|
|
304
|
+
method: "tools/call",
|
|
305
|
+
params: {
|
|
306
|
+
name: "get_announcement_context",
|
|
307
|
+
arguments: {},
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
312
|
+
|
|
313
|
+
// Should have tags array
|
|
314
|
+
expect(responseData).toHaveProperty("tags");
|
|
315
|
+
expect(Array.isArray(responseData.tags)).toBe(true);
|
|
316
|
+
if (responseData.tags.length > 0) {
|
|
317
|
+
expect(responseData.tags[0]).toHaveProperty("name");
|
|
318
|
+
expect(responseData.tags[0]).toHaveProperty("uuid");
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Should have affinity groups (may be empty if user is not a coordinator)
|
|
322
|
+
expect(responseData).toHaveProperty("affinity_groups");
|
|
323
|
+
expect(Array.isArray(responseData.affinity_groups)).toBe(true);
|
|
324
|
+
|
|
325
|
+
// Should have is_coordinator boolean
|
|
326
|
+
expect(responseData).toHaveProperty("is_coordinator");
|
|
327
|
+
expect(typeof responseData.is_coordinator).toBe("boolean");
|
|
328
|
+
expect(responseData.is_coordinator).toBe(responseData.affinity_groups.length > 0);
|
|
329
|
+
|
|
330
|
+
// Should have static options
|
|
331
|
+
expect(responseData.affiliations).toContain("ACCESS Collaboration");
|
|
332
|
+
expect(responseData.affiliations).toContain("Community");
|
|
333
|
+
expect(responseData.where_to_share_options).toHaveLength(4);
|
|
334
|
+
}, 15000);
|
|
335
|
+
|
|
336
|
+
it("should work with request context acting user", async () => {
|
|
337
|
+
const savedActingUser = process.env.ACTING_USER;
|
|
338
|
+
delete process.env.ACTING_USER;
|
|
339
|
+
|
|
340
|
+
try {
|
|
341
|
+
const context: RequestContext = {
|
|
342
|
+
actingUser: savedActingUser || "apasquale@access-ci.org",
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
const result = await requestContextStorage.run(context, async () => {
|
|
346
|
+
return server["handleToolCall"]({
|
|
347
|
+
method: "tools/call",
|
|
348
|
+
params: {
|
|
349
|
+
name: "get_announcement_context",
|
|
350
|
+
arguments: {},
|
|
351
|
+
},
|
|
352
|
+
});
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
356
|
+
expect(responseData).toHaveProperty("tags");
|
|
357
|
+
expect(responseData).toHaveProperty("is_coordinator");
|
|
358
|
+
} finally {
|
|
359
|
+
if (savedActingUser) process.env.ACTING_USER = savedActingUser;
|
|
360
|
+
}
|
|
361
|
+
}, 15000);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
describe("create, get_my, update, delete announcement", () => {
|
|
365
|
+
it("should create an announcement and find it via get_my_announcements", async () => {
|
|
366
|
+
// 1. Create announcement
|
|
367
|
+
const createResult = await server["handleToolCall"]({
|
|
368
|
+
method: "tools/call",
|
|
369
|
+
params: {
|
|
370
|
+
name: "create_announcement",
|
|
371
|
+
arguments: {
|
|
372
|
+
title: "E2E Test Announcement",
|
|
373
|
+
body: "<p>This is an automated e2e test announcement.</p>",
|
|
374
|
+
summary: "E2E test summary",
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
const createData = JSON.parse((createResult.content[0] as TextContent).text);
|
|
380
|
+
expect(createData.success).toBe(true);
|
|
381
|
+
expect(createData.uuid).toBeTruthy();
|
|
382
|
+
expect(createData.title).toBe("E2E Test Announcement");
|
|
383
|
+
expect(createData.edit_url).toContain("/node/");
|
|
384
|
+
expect(createData.edit_url).toContain("/edit");
|
|
385
|
+
|
|
386
|
+
createdUuids.push(createData.uuid);
|
|
387
|
+
|
|
388
|
+
// 2. Verify it appears in get_my_announcements
|
|
389
|
+
const myResult = await server["handleToolCall"]({
|
|
390
|
+
method: "tools/call",
|
|
391
|
+
params: {
|
|
392
|
+
name: "get_my_announcements",
|
|
393
|
+
arguments: { limit: 50 },
|
|
394
|
+
},
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
const myData = JSON.parse((myResult.content[0] as TextContent).text);
|
|
398
|
+
expect(myData.total).toBeGreaterThanOrEqual(1);
|
|
399
|
+
|
|
400
|
+
const found = myData.items.find(
|
|
401
|
+
(item: MyAnnouncementItem) => item.uuid === createData.uuid
|
|
402
|
+
);
|
|
403
|
+
expect(found).toBeTruthy();
|
|
404
|
+
expect(found.title).toBe("E2E Test Announcement");
|
|
405
|
+
expect(found.status).toBe("draft");
|
|
406
|
+
expect(found.summary).toBe("E2E test summary");
|
|
407
|
+
expect(found.uuid).toBe(createData.uuid);
|
|
408
|
+
expect(found.edit_url).toContain("/edit");
|
|
409
|
+
}, 30000);
|
|
410
|
+
|
|
411
|
+
it("should update an existing announcement", async () => {
|
|
412
|
+
// Create one to update
|
|
413
|
+
const createResult = await server["handleToolCall"]({
|
|
414
|
+
method: "tools/call",
|
|
415
|
+
params: {
|
|
416
|
+
name: "create_announcement",
|
|
417
|
+
arguments: {
|
|
418
|
+
title: "E2E Update Test",
|
|
419
|
+
body: "<p>Original body</p>",
|
|
420
|
+
summary: "Original summary",
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
const createData = JSON.parse((createResult.content[0] as TextContent).text);
|
|
426
|
+
expect(createData.success).toBe(true);
|
|
427
|
+
createdUuids.push(createData.uuid);
|
|
428
|
+
|
|
429
|
+
// Update it
|
|
430
|
+
const updateResult = await server["handleToolCall"]({
|
|
431
|
+
method: "tools/call",
|
|
432
|
+
params: {
|
|
433
|
+
name: "update_announcement",
|
|
434
|
+
arguments: {
|
|
435
|
+
uuid: createData.uuid,
|
|
436
|
+
title: "E2E Update Test - Modified",
|
|
437
|
+
},
|
|
438
|
+
},
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
const updateData = JSON.parse((updateResult.content[0] as TextContent).text);
|
|
442
|
+
expect(updateData.success).toBe(true);
|
|
443
|
+
expect(updateData.title).toBe("E2E Update Test - Modified");
|
|
444
|
+
}, 30000);
|
|
445
|
+
|
|
446
|
+
it("should delete an announcement", async () => {
|
|
447
|
+
// Create one to delete
|
|
448
|
+
const createResult = await server["handleToolCall"]({
|
|
449
|
+
method: "tools/call",
|
|
450
|
+
params: {
|
|
451
|
+
name: "create_announcement",
|
|
452
|
+
arguments: {
|
|
453
|
+
title: "E2E Delete Test",
|
|
454
|
+
body: "<p>To be deleted</p>",
|
|
455
|
+
summary: "Will be deleted",
|
|
456
|
+
},
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
const createData = JSON.parse((createResult.content[0] as TextContent).text);
|
|
461
|
+
expect(createData.success).toBe(true);
|
|
462
|
+
const uuid = createData.uuid;
|
|
463
|
+
|
|
464
|
+
// Delete it
|
|
465
|
+
const deleteResult = await server["handleToolCall"]({
|
|
466
|
+
method: "tools/call",
|
|
467
|
+
params: {
|
|
468
|
+
name: "delete_announcement",
|
|
469
|
+
arguments: { uuid, confirmed: true },
|
|
470
|
+
},
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
const deleteData = JSON.parse((deleteResult.content[0] as TextContent).text);
|
|
474
|
+
expect(deleteData.success).toBe(true);
|
|
475
|
+
expect(deleteData.uuid).toBe(uuid);
|
|
476
|
+
|
|
477
|
+
// Remove from cleanup list since we already deleted it
|
|
478
|
+
const idx = createdUuids.indexOf(uuid);
|
|
479
|
+
if (idx !== -1) createdUuids.splice(idx, 1);
|
|
480
|
+
|
|
481
|
+
// Verify it's gone from get_my_announcements
|
|
482
|
+
const myResult = await server["handleToolCall"]({
|
|
483
|
+
method: "tools/call",
|
|
484
|
+
params: {
|
|
485
|
+
name: "get_my_announcements",
|
|
486
|
+
arguments: {},
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
const myData = JSON.parse((myResult.content[0] as TextContent).text);
|
|
491
|
+
const found = myData.items.find(
|
|
492
|
+
(item: MyAnnouncementItem) => item.uuid === uuid
|
|
493
|
+
);
|
|
494
|
+
expect(found).toBeUndefined();
|
|
495
|
+
}, 30000);
|
|
496
|
+
|
|
497
|
+
it("should reject delete without confirmation", async () => {
|
|
498
|
+
const result = await server["handleToolCall"]({
|
|
499
|
+
method: "tools/call",
|
|
500
|
+
params: {
|
|
501
|
+
name: "delete_announcement",
|
|
502
|
+
arguments: {
|
|
503
|
+
uuid: "some-uuid",
|
|
504
|
+
confirmed: false,
|
|
505
|
+
},
|
|
506
|
+
},
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
510
|
+
expect(responseData.error).toContain("explicit confirmation");
|
|
511
|
+
}, 10000);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
describe("get_my_announcements", () => {
|
|
515
|
+
it("should return proper structure from views endpoint", async () => {
|
|
516
|
+
const result = await server["handleToolCall"]({
|
|
517
|
+
method: "tools/call",
|
|
518
|
+
params: {
|
|
519
|
+
name: "get_my_announcements",
|
|
520
|
+
arguments: {},
|
|
521
|
+
},
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
const responseData = JSON.parse((result.content[0] as TextContent).text);
|
|
525
|
+
expect(responseData).toHaveProperty("total");
|
|
526
|
+
expect(responseData).toHaveProperty("items");
|
|
527
|
+
expect(Array.isArray(responseData.items)).toBe(true);
|
|
528
|
+
|
|
529
|
+
// If there are items, check structure
|
|
530
|
+
if (responseData.items.length > 0) {
|
|
531
|
+
const item = responseData.items[0];
|
|
532
|
+
expect(item).toHaveProperty("uuid");
|
|
533
|
+
expect(item).toHaveProperty("title");
|
|
534
|
+
expect(item).toHaveProperty("status");
|
|
535
|
+
expect(["draft", "published"]).toContain(item.status);
|
|
536
|
+
expect(item).toHaveProperty("edit_url");
|
|
537
|
+
}
|
|
538
|
+
}, 15000);
|
|
539
|
+
});
|
|
540
|
+
});
|