mirror of https://github.com/openclaw/openclaw.git
215 lines
6.5 KiB
TypeScript
215 lines
6.5 KiB
TypeScript
import { describe, expect, it, vi } from "vitest";
|
|
import { withFetchPreconnect } from "../../../test/helpers/extensions/fetch-mock.js";
|
|
import { resolveGraphChatId, uploadToOneDrive, uploadToSharePoint } from "./graph-upload.js";
|
|
|
|
describe("graph upload helpers", () => {
|
|
const tokenProvider = {
|
|
getAccessToken: vi.fn(async () => "graph-token"),
|
|
};
|
|
|
|
it("uploads to OneDrive with the personal drive path", async () => {
|
|
const fetchFn = vi.fn(
|
|
async () =>
|
|
new Response(
|
|
JSON.stringify({ id: "item-1", webUrl: "https://example.com/1", name: "a.txt" }),
|
|
{
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
},
|
|
),
|
|
);
|
|
|
|
const result = await uploadToOneDrive({
|
|
buffer: Buffer.from("hello"),
|
|
filename: "a.txt",
|
|
tokenProvider,
|
|
fetchFn: withFetchPreconnect(fetchFn),
|
|
});
|
|
|
|
expect(fetchFn).toHaveBeenCalledWith(
|
|
"https://graph.microsoft.com/v1.0/me/drive/root:/OpenClawShared/a.txt:/content",
|
|
expect.objectContaining({
|
|
method: "PUT",
|
|
headers: expect.objectContaining({
|
|
Authorization: "Bearer graph-token",
|
|
"Content-Type": "application/octet-stream",
|
|
}),
|
|
}),
|
|
);
|
|
expect(result).toEqual({
|
|
id: "item-1",
|
|
webUrl: "https://example.com/1",
|
|
name: "a.txt",
|
|
});
|
|
});
|
|
|
|
it("uploads to SharePoint with the site drive path", async () => {
|
|
const fetchFn = vi.fn(
|
|
async () =>
|
|
new Response(
|
|
JSON.stringify({ id: "item-2", webUrl: "https://example.com/2", name: "b.txt" }),
|
|
{
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
},
|
|
),
|
|
);
|
|
|
|
const result = await uploadToSharePoint({
|
|
buffer: Buffer.from("world"),
|
|
filename: "b.txt",
|
|
siteId: "site-123",
|
|
tokenProvider,
|
|
fetchFn: withFetchPreconnect(fetchFn),
|
|
});
|
|
|
|
expect(fetchFn).toHaveBeenCalledWith(
|
|
"https://graph.microsoft.com/v1.0/sites/site-123/drive/root:/OpenClawShared/b.txt:/content",
|
|
expect.objectContaining({
|
|
method: "PUT",
|
|
headers: expect.objectContaining({
|
|
Authorization: "Bearer graph-token",
|
|
"Content-Type": "application/octet-stream",
|
|
}),
|
|
}),
|
|
);
|
|
expect(result).toEqual({
|
|
id: "item-2",
|
|
webUrl: "https://example.com/2",
|
|
name: "b.txt",
|
|
});
|
|
});
|
|
|
|
it("rejects upload responses missing required fields", async () => {
|
|
const fetchFn = vi.fn(
|
|
async () =>
|
|
new Response(JSON.stringify({ id: "item-3" }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
}),
|
|
);
|
|
|
|
await expect(
|
|
uploadToSharePoint({
|
|
buffer: Buffer.from("world"),
|
|
filename: "bad.txt",
|
|
siteId: "site-123",
|
|
tokenProvider,
|
|
fetchFn: withFetchPreconnect(fetchFn),
|
|
}),
|
|
).rejects.toThrow("SharePoint upload response missing required fields");
|
|
});
|
|
});
|
|
|
|
describe("resolveGraphChatId", () => {
|
|
const tokenProvider = {
|
|
getAccessToken: vi.fn(async () => "graph-token"),
|
|
};
|
|
|
|
it("returns the ID directly when it already starts with 19:", async () => {
|
|
const fetchFn = vi.fn();
|
|
const result = await resolveGraphChatId({
|
|
botFrameworkConversationId: "19:abc123@thread.tacv2",
|
|
tokenProvider,
|
|
fetchFn: withFetchPreconnect(fetchFn),
|
|
});
|
|
// Should short-circuit without making any API call
|
|
expect(fetchFn).not.toHaveBeenCalled();
|
|
expect(result).toBe("19:abc123@thread.tacv2");
|
|
});
|
|
|
|
it("resolves personal DM chat ID via Graph API using user AAD object ID", async () => {
|
|
const fetchFn = vi.fn(
|
|
async () =>
|
|
new Response(JSON.stringify({ value: [{ id: "19:dm-chat-id@unq.gbl.spaces" }] }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
}),
|
|
);
|
|
|
|
const result = await resolveGraphChatId({
|
|
botFrameworkConversationId: "a:1abc_bot_framework_dm_id",
|
|
userAadObjectId: "user-aad-object-id-123",
|
|
tokenProvider,
|
|
fetchFn: withFetchPreconnect(fetchFn),
|
|
});
|
|
|
|
expect(fetchFn).toHaveBeenCalledWith(
|
|
expect.stringContaining("/me/chats"),
|
|
expect.objectContaining({
|
|
headers: expect.objectContaining({ Authorization: "Bearer graph-token" }),
|
|
}),
|
|
);
|
|
const firstCall = fetchFn.mock.calls[0];
|
|
if (!firstCall) {
|
|
throw new Error("expected Graph chat lookup request");
|
|
}
|
|
const [callUrlRaw] = firstCall as unknown as [string, RequestInit?];
|
|
const callUrl = new URL(callUrlRaw);
|
|
expect(callUrl.origin).toBe("https://graph.microsoft.com");
|
|
expect(callUrl.pathname).toBe("/v1.0/me/chats");
|
|
expect(callUrl.searchParams.get("$filter")).toBe(
|
|
"chatType eq 'oneOnOne' and members/any(m:m/microsoft.graph.aadUserConversationMember/userId eq 'user-aad-object-id-123')",
|
|
);
|
|
expect(callUrl.searchParams.get("$select")).toBe("id");
|
|
expect(result).toBe("19:dm-chat-id@unq.gbl.spaces");
|
|
});
|
|
|
|
it("resolves personal DM chat ID without user AAD object ID (lists all 1:1 chats)", async () => {
|
|
const fetchFn = vi.fn(
|
|
async () =>
|
|
new Response(JSON.stringify({ value: [{ id: "19:fallback-chat@unq.gbl.spaces" }] }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
}),
|
|
);
|
|
|
|
const result = await resolveGraphChatId({
|
|
botFrameworkConversationId: "8:orgid:user-object-id",
|
|
tokenProvider,
|
|
fetchFn: withFetchPreconnect(fetchFn),
|
|
});
|
|
|
|
expect(fetchFn).toHaveBeenCalledOnce();
|
|
expect(result).toBe("19:fallback-chat@unq.gbl.spaces");
|
|
});
|
|
|
|
it("returns null when Graph API returns no chats", async () => {
|
|
const fetchFn = vi.fn(
|
|
async () =>
|
|
new Response(JSON.stringify({ value: [] }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/json" },
|
|
}),
|
|
);
|
|
|
|
const result = await resolveGraphChatId({
|
|
botFrameworkConversationId: "a:1unknown_dm",
|
|
userAadObjectId: "some-user",
|
|
tokenProvider,
|
|
fetchFn: withFetchPreconnect(fetchFn),
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
|
|
it("returns null when Graph API call fails", async () => {
|
|
const fetchFn = vi.fn(
|
|
async () =>
|
|
new Response("Unauthorized", {
|
|
status: 401,
|
|
headers: { "content-type": "text/plain" },
|
|
}),
|
|
);
|
|
|
|
const result = await resolveGraphChatId({
|
|
botFrameworkConversationId: "a:1some_dm_id",
|
|
userAadObjectId: "some-user",
|
|
tokenProvider,
|
|
fetchFn: withFetchPreconnect(fetchFn),
|
|
});
|
|
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|