mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-24 01:14:28 +00:00
293 lines
8.6 KiB
TypeScript
293 lines
8.6 KiB
TypeScript
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import type { CoreConfig } from "../types.js";
|
|
import { resolveMatrixAuth, resolveMatrixConfig } from "./client.js";
|
|
import * as credentialsModule from "./credentials.js";
|
|
import * as sdkModule from "./sdk.js";
|
|
|
|
const saveMatrixCredentialsMock = vi.hoisted(() => vi.fn());
|
|
|
|
vi.mock("./credentials.js", () => ({
|
|
loadMatrixCredentials: vi.fn(() => null),
|
|
saveMatrixCredentials: saveMatrixCredentialsMock,
|
|
credentialsMatchConfig: vi.fn(() => false),
|
|
touchMatrixCredentials: vi.fn(),
|
|
}));
|
|
|
|
describe("resolveMatrixConfig", () => {
|
|
it("prefers config over env", () => {
|
|
const cfg = {
|
|
channels: {
|
|
"matrix-js": {
|
|
homeserver: "https://cfg.example.org",
|
|
userId: "@cfg:example.org",
|
|
accessToken: "cfg-token",
|
|
password: "cfg-pass",
|
|
deviceName: "CfgDevice",
|
|
initialSyncLimit: 5,
|
|
},
|
|
},
|
|
} as CoreConfig;
|
|
const env = {
|
|
MATRIX_HOMESERVER: "https://env.example.org",
|
|
MATRIX_USER_ID: "@env:example.org",
|
|
MATRIX_ACCESS_TOKEN: "env-token",
|
|
MATRIX_PASSWORD: "env-pass",
|
|
MATRIX_DEVICE_NAME: "EnvDevice",
|
|
} as NodeJS.ProcessEnv;
|
|
const resolved = resolveMatrixConfig(cfg, env);
|
|
expect(resolved).toEqual({
|
|
homeserver: "https://cfg.example.org",
|
|
userId: "@cfg:example.org",
|
|
accessToken: "cfg-token",
|
|
password: "cfg-pass",
|
|
deviceId: undefined,
|
|
deviceName: "CfgDevice",
|
|
initialSyncLimit: 5,
|
|
encryption: false,
|
|
});
|
|
});
|
|
|
|
it("uses env when config is missing", () => {
|
|
const cfg = {} as CoreConfig;
|
|
const env = {
|
|
MATRIX_HOMESERVER: "https://env.example.org",
|
|
MATRIX_USER_ID: "@env:example.org",
|
|
MATRIX_ACCESS_TOKEN: "env-token",
|
|
MATRIX_PASSWORD: "env-pass",
|
|
MATRIX_DEVICE_ID: "ENVDEVICE",
|
|
MATRIX_DEVICE_NAME: "EnvDevice",
|
|
} as NodeJS.ProcessEnv;
|
|
const resolved = resolveMatrixConfig(cfg, env);
|
|
expect(resolved.homeserver).toBe("https://env.example.org");
|
|
expect(resolved.userId).toBe("@env:example.org");
|
|
expect(resolved.accessToken).toBe("env-token");
|
|
expect(resolved.password).toBe("env-pass");
|
|
expect(resolved.deviceId).toBe("ENVDEVICE");
|
|
expect(resolved.deviceName).toBe("EnvDevice");
|
|
expect(resolved.initialSyncLimit).toBeUndefined();
|
|
expect(resolved.encryption).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("resolveMatrixAuth", () => {
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
vi.unstubAllGlobals();
|
|
saveMatrixCredentialsMock.mockReset();
|
|
});
|
|
|
|
it("uses the hardened client request path for password login and persists deviceId", async () => {
|
|
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({
|
|
access_token: "tok-123",
|
|
user_id: "@bot:example.org",
|
|
device_id: "DEVICE123",
|
|
});
|
|
|
|
const cfg = {
|
|
channels: {
|
|
"matrix-js": {
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
password: "secret",
|
|
encryption: true,
|
|
},
|
|
},
|
|
} as CoreConfig;
|
|
|
|
const auth = await resolveMatrixAuth({
|
|
cfg,
|
|
env: {} as NodeJS.ProcessEnv,
|
|
});
|
|
|
|
expect(doRequestSpy).toHaveBeenCalledWith(
|
|
"POST",
|
|
"/_matrix/client/v3/login",
|
|
undefined,
|
|
expect.objectContaining({
|
|
type: "m.login.password",
|
|
}),
|
|
);
|
|
expect(auth).toMatchObject({
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
accessToken: "tok-123",
|
|
deviceId: "DEVICE123",
|
|
encryption: true,
|
|
});
|
|
expect(saveMatrixCredentialsMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
accessToken: "tok-123",
|
|
deviceId: "DEVICE123",
|
|
}),
|
|
expect.any(Object),
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
it("surfaces password login errors when account credentials are invalid", async () => {
|
|
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest");
|
|
doRequestSpy.mockRejectedValueOnce(new Error("Invalid username or password"));
|
|
|
|
const cfg = {
|
|
channels: {
|
|
"matrix-js": {
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
password: "secret",
|
|
},
|
|
},
|
|
} as CoreConfig;
|
|
|
|
await expect(
|
|
resolveMatrixAuth({
|
|
cfg,
|
|
env: {} as NodeJS.ProcessEnv,
|
|
}),
|
|
).rejects.toThrow("Invalid username or password");
|
|
|
|
expect(doRequestSpy).toHaveBeenCalledWith(
|
|
"POST",
|
|
"/_matrix/client/v3/login",
|
|
undefined,
|
|
expect.objectContaining({
|
|
type: "m.login.password",
|
|
}),
|
|
);
|
|
expect(saveMatrixCredentialsMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("uses cached matching credentials when access token is not configured", async () => {
|
|
vi.mocked(credentialsModule.loadMatrixCredentials).mockReturnValue({
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
accessToken: "cached-token",
|
|
deviceId: "CACHEDDEVICE",
|
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
});
|
|
vi.mocked(credentialsModule.credentialsMatchConfig).mockReturnValue(true);
|
|
|
|
const cfg = {
|
|
channels: {
|
|
"matrix-js": {
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
password: "secret",
|
|
},
|
|
},
|
|
} as CoreConfig;
|
|
|
|
const auth = await resolveMatrixAuth({
|
|
cfg,
|
|
env: {} as NodeJS.ProcessEnv,
|
|
});
|
|
|
|
expect(auth).toMatchObject({
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
accessToken: "cached-token",
|
|
deviceId: "CACHEDDEVICE",
|
|
});
|
|
expect(saveMatrixCredentialsMock).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it("falls back to config deviceId when cached credentials are missing it", async () => {
|
|
vi.mocked(credentialsModule.loadMatrixCredentials).mockReturnValue({
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
accessToken: "tok-123",
|
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
});
|
|
vi.mocked(credentialsModule.credentialsMatchConfig).mockReturnValue(true);
|
|
|
|
const cfg = {
|
|
channels: {
|
|
"matrix-js": {
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
accessToken: "tok-123",
|
|
deviceId: "DEVICE123",
|
|
encryption: true,
|
|
},
|
|
},
|
|
} as CoreConfig;
|
|
|
|
const auth = await resolveMatrixAuth({ cfg, env: {} as NodeJS.ProcessEnv });
|
|
|
|
expect(auth.deviceId).toBe("DEVICE123");
|
|
expect(saveMatrixCredentialsMock).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
accessToken: "tok-123",
|
|
deviceId: "DEVICE123",
|
|
}),
|
|
expect.any(Object),
|
|
undefined,
|
|
);
|
|
});
|
|
|
|
it("resolves missing whoami identity fields for token auth", async () => {
|
|
const doRequestSpy = vi.spyOn(sdkModule.MatrixClient.prototype, "doRequest").mockResolvedValue({
|
|
user_id: "@bot:example.org",
|
|
device_id: "DEVICE123",
|
|
});
|
|
|
|
const cfg = {
|
|
channels: {
|
|
"matrix-js": {
|
|
homeserver: "https://matrix.example.org",
|
|
accessToken: "tok-123",
|
|
encryption: true,
|
|
},
|
|
},
|
|
} as CoreConfig;
|
|
|
|
const auth = await resolveMatrixAuth({
|
|
cfg,
|
|
env: {} as NodeJS.ProcessEnv,
|
|
});
|
|
|
|
expect(doRequestSpy).toHaveBeenCalledWith("GET", "/_matrix/client/v3/account/whoami");
|
|
expect(auth).toMatchObject({
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
accessToken: "tok-123",
|
|
deviceId: "DEVICE123",
|
|
encryption: true,
|
|
});
|
|
});
|
|
|
|
it("uses config deviceId with cached credentials when token is loaded from cache", async () => {
|
|
vi.mocked(credentialsModule.loadMatrixCredentials).mockReturnValue({
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
accessToken: "tok-123",
|
|
createdAt: "2026-01-01T00:00:00.000Z",
|
|
});
|
|
vi.mocked(credentialsModule.credentialsMatchConfig).mockReturnValue(true);
|
|
|
|
const cfg = {
|
|
channels: {
|
|
"matrix-js": {
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
deviceId: "DEVICE123",
|
|
encryption: true,
|
|
},
|
|
},
|
|
} as CoreConfig;
|
|
|
|
const auth = await resolveMatrixAuth({ cfg, env: {} as NodeJS.ProcessEnv });
|
|
|
|
expect(auth).toMatchObject({
|
|
homeserver: "https://matrix.example.org",
|
|
userId: "@bot:example.org",
|
|
accessToken: "tok-123",
|
|
deviceId: "DEVICE123",
|
|
encryption: true,
|
|
});
|
|
});
|
|
});
|