openclaw/extensions/matrix/src/matrix/profile.test.ts

155 lines
5.1 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
import {
isSupportedMatrixAvatarSource,
syncMatrixOwnProfile,
type MatrixProfileSyncResult,
} from "./profile.js";
function createClientStub() {
return {
getUserProfile: vi.fn(async () => ({})),
setDisplayName: vi.fn(async () => {}),
setAvatarUrl: vi.fn(async () => {}),
uploadContent: vi.fn(async () => "mxc://example/avatar"),
};
}
function expectNoUpdates(result: MatrixProfileSyncResult) {
expect(result.displayNameUpdated).toBe(false);
expect(result.avatarUpdated).toBe(false);
}
describe("matrix profile sync", () => {
it("skips when no desired profile values are provided", async () => {
const client = createClientStub();
const result = await syncMatrixOwnProfile({
client,
userId: "@bot:example.org",
});
expect(result.skipped).toBe(true);
expectNoUpdates(result);
expect(result.uploadedAvatarSource).toBeNull();
expect(client.setDisplayName).not.toHaveBeenCalled();
expect(client.setAvatarUrl).not.toHaveBeenCalled();
});
it("updates display name when desired name differs", async () => {
const client = createClientStub();
client.getUserProfile.mockResolvedValue({
displayname: "Old Name",
avatar_url: "mxc://example/existing",
});
const result = await syncMatrixOwnProfile({
client,
userId: "@bot:example.org",
displayName: "New Name",
});
expect(result.skipped).toBe(false);
expect(result.displayNameUpdated).toBe(true);
expect(result.avatarUpdated).toBe(false);
expect(result.uploadedAvatarSource).toBeNull();
expect(client.setDisplayName).toHaveBeenCalledWith("New Name");
});
it("does not update when name and avatar already match", async () => {
const client = createClientStub();
client.getUserProfile.mockResolvedValue({
displayname: "Bot",
avatar_url: "mxc://example/avatar",
});
const result = await syncMatrixOwnProfile({
client,
userId: "@bot:example.org",
displayName: "Bot",
avatarUrl: "mxc://example/avatar",
});
expect(result.skipped).toBe(false);
expectNoUpdates(result);
expect(client.setDisplayName).not.toHaveBeenCalled();
expect(client.setAvatarUrl).not.toHaveBeenCalled();
});
it("converts http avatar URL by uploading and then updates profile avatar", async () => {
const client = createClientStub();
client.getUserProfile.mockResolvedValue({
displayname: "Bot",
avatar_url: "mxc://example/old",
});
client.uploadContent.mockResolvedValue("mxc://example/new-avatar");
const loadAvatarFromUrl = vi.fn(async () => ({
buffer: Buffer.from("avatar-bytes"),
contentType: "image/png",
fileName: "avatar.png",
}));
const result = await syncMatrixOwnProfile({
client,
userId: "@bot:example.org",
avatarUrl: "https://cdn.example.org/avatar.png",
loadAvatarFromUrl,
});
expect(result.convertedAvatarFromHttp).toBe(true);
expect(result.uploadedAvatarSource).toBe("http");
expect(result.resolvedAvatarUrl).toBe("mxc://example/new-avatar");
expect(result.avatarUpdated).toBe(true);
expect(loadAvatarFromUrl).toHaveBeenCalledWith(
"https://cdn.example.org/avatar.png",
10 * 1024 * 1024,
);
expect(client.setAvatarUrl).toHaveBeenCalledWith("mxc://example/new-avatar");
});
it("uploads avatar media from a local path and then updates profile avatar", async () => {
const client = createClientStub();
client.getUserProfile.mockResolvedValue({
displayname: "Bot",
avatar_url: "mxc://example/old",
});
client.uploadContent.mockResolvedValue("mxc://example/path-avatar");
const loadAvatarFromPath = vi.fn(async () => ({
buffer: Buffer.from("avatar-bytes"),
contentType: "image/jpeg",
fileName: "avatar.jpg",
}));
const result = await syncMatrixOwnProfile({
client,
userId: "@bot:example.org",
avatarPath: "/tmp/avatar.jpg",
loadAvatarFromPath,
});
expect(result.convertedAvatarFromHttp).toBe(false);
expect(result.uploadedAvatarSource).toBe("path");
expect(result.resolvedAvatarUrl).toBe("mxc://example/path-avatar");
expect(result.avatarUpdated).toBe(true);
expect(loadAvatarFromPath).toHaveBeenCalledWith("/tmp/avatar.jpg", 10 * 1024 * 1024);
expect(client.setAvatarUrl).toHaveBeenCalledWith("mxc://example/path-avatar");
});
it("rejects unsupported avatar URL schemes", async () => {
const client = createClientStub();
await expect(
syncMatrixOwnProfile({
client,
userId: "@bot:example.org",
avatarUrl: "file:///tmp/avatar.png",
}),
).rejects.toThrow("Matrix avatar URL must be an mxc:// URI or an http(s) URL.");
});
it("recognizes supported avatar sources", () => {
expect(isSupportedMatrixAvatarSource("mxc://example/avatar")).toBe(true);
expect(isSupportedMatrixAvatarSource("https://example.org/avatar.png")).toBe(true);
expect(isSupportedMatrixAvatarSource("http://example.org/avatar.png")).toBe(true);
expect(isSupportedMatrixAvatarSource("ftp://example.org/avatar.png")).toBe(false);
});
});