@softeria/ms-365-mcp-server 0.143.0 → 0.145.0
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/__tests__/graph-client.test.js +128 -0
- package/dist/__tests__/graph-tools.test.js +527 -31
- package/dist/auth-tools.js +0 -1
- package/dist/graph-client.js +114 -35
- package/dist/graph-tools.js +223 -26
- package/docs/deployment.md +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
+
import GraphClient from "../graph-client.js";
|
|
3
|
+
import { fetchWithResilience } from "../lib/graph-resilience.js";
|
|
4
|
+
vi.mock("../logger.js", () => ({
|
|
5
|
+
default: {
|
|
6
|
+
info: vi.fn(),
|
|
7
|
+
warn: vi.fn(),
|
|
8
|
+
error: vi.fn(),
|
|
9
|
+
debug: vi.fn()
|
|
10
|
+
}
|
|
11
|
+
}));
|
|
12
|
+
vi.mock("../lib/graph-resilience.js", () => ({
|
|
13
|
+
fetchWithResilience: vi.fn(),
|
|
14
|
+
getSharedBreaker: vi.fn(() => ({})),
|
|
15
|
+
loadResilienceConfig: vi.fn(() => ({}))
|
|
16
|
+
}));
|
|
17
|
+
const fetchWithResilienceMock = vi.mocked(fetchWithResilience);
|
|
18
|
+
function createGraphClient() {
|
|
19
|
+
return new GraphClient(
|
|
20
|
+
{
|
|
21
|
+
getToken: vi.fn().mockResolvedValue("token")
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
clientId: "client-id",
|
|
25
|
+
tenantId: "tenant-id",
|
|
26
|
+
cloudType: "global"
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
describe("GraphClient audit metadata", () => {
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
});
|
|
34
|
+
it("adds HTTP status metadata to successful Graph responses", async () => {
|
|
35
|
+
fetchWithResilienceMock.mockResolvedValue(
|
|
36
|
+
new Response(JSON.stringify({ id: "user-1" }), {
|
|
37
|
+
status: 200,
|
|
38
|
+
headers: { "content-type": "application/json" }
|
|
39
|
+
})
|
|
40
|
+
);
|
|
41
|
+
const result = await createGraphClient().graphRequest("/me");
|
|
42
|
+
expect(result._meta).toMatchObject({ http_status: 200 });
|
|
43
|
+
expect(JSON.parse(result.content[0].text)).toEqual({ id: "user-1" });
|
|
44
|
+
});
|
|
45
|
+
it("preserves HTTP status metadata when response headers are requested", async () => {
|
|
46
|
+
fetchWithResilienceMock.mockResolvedValue(
|
|
47
|
+
new Response(JSON.stringify({ id: "task-1" }), {
|
|
48
|
+
status: 200,
|
|
49
|
+
headers: { "content-type": "application/json", etag: 'W/"etag-1"' }
|
|
50
|
+
})
|
|
51
|
+
);
|
|
52
|
+
const result = await createGraphClient().graphRequest("/me/planner/tasks/task-1", {
|
|
53
|
+
includeHeaders: true
|
|
54
|
+
});
|
|
55
|
+
expect(result._meta).toMatchObject({ http_status: 200 });
|
|
56
|
+
expect(JSON.parse(result.content[0].text)).toMatchObject({
|
|
57
|
+
id: "task-1",
|
|
58
|
+
_etag: 'W/"etag-1"'
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
it("adds HTTP status and Graph error code metadata to failed Graph responses", async () => {
|
|
62
|
+
fetchWithResilienceMock.mockResolvedValue(
|
|
63
|
+
new Response(
|
|
64
|
+
JSON.stringify({
|
|
65
|
+
error: {
|
|
66
|
+
code: "accessDenied",
|
|
67
|
+
message: "Access denied"
|
|
68
|
+
}
|
|
69
|
+
}),
|
|
70
|
+
{
|
|
71
|
+
status: 403,
|
|
72
|
+
statusText: "Forbidden",
|
|
73
|
+
headers: { "content-type": "application/json" }
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
);
|
|
77
|
+
const result = await createGraphClient().graphRequest("/me/drive");
|
|
78
|
+
expect(result.isError).toBe(true);
|
|
79
|
+
expect(result._meta).toMatchObject({
|
|
80
|
+
http_status: 403,
|
|
81
|
+
error_code: "accessDenied"
|
|
82
|
+
});
|
|
83
|
+
expect(JSON.parse(result.content[0].text).error).toContain(
|
|
84
|
+
"Microsoft Graph API error: 403 Forbidden"
|
|
85
|
+
);
|
|
86
|
+
});
|
|
87
|
+
it("adds aggregate subrequest metadata to successful Graph batch responses", async () => {
|
|
88
|
+
fetchWithResilienceMock.mockResolvedValue(
|
|
89
|
+
new Response(
|
|
90
|
+
JSON.stringify({
|
|
91
|
+
responses: [
|
|
92
|
+
{ id: "1", status: 200, body: { id: "message-1" } },
|
|
93
|
+
{
|
|
94
|
+
id: "2",
|
|
95
|
+
status: 403,
|
|
96
|
+
body: { error: { code: "accessDenied", message: "Access denied" } }
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: "3",
|
|
100
|
+
status: 429,
|
|
101
|
+
body: { error: { code: "tooManyRequests", message: "Slow down" } }
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
id: "4",
|
|
105
|
+
status: 403,
|
|
106
|
+
body: { error: { code: "accessDenied", message: "Access denied" } }
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}),
|
|
110
|
+
{
|
|
111
|
+
status: 200,
|
|
112
|
+
headers: { "content-type": "application/json" }
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
);
|
|
116
|
+
const result = await createGraphClient().graphRequest("/$batch", {
|
|
117
|
+
method: "POST",
|
|
118
|
+
body: JSON.stringify({ requests: [] })
|
|
119
|
+
});
|
|
120
|
+
expect(result.isError).toBeUndefined();
|
|
121
|
+
expect(result._meta).toMatchObject({
|
|
122
|
+
http_status: 200,
|
|
123
|
+
graph_batch_subrequest_count: 4,
|
|
124
|
+
graph_batch_http_status_counts: { "200": 1, "403": 2, "429": 1 },
|
|
125
|
+
graph_batch_error_code_counts: { accessDenied: 2, tooManyRequests: 1 }
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|