fix(matrix): harden health probe storage metadata

This commit is contained in:
Gustavo Madeira Santana 2026-04-06 00:18:30 -04:00
parent 92e802da93
commit 8a7eb2329a
5 changed files with 119 additions and 18 deletions

View File

@ -67,7 +67,7 @@ describe("matrix account path propagation", () => {
);
});
it("forwards accountId to matrix probes", async () => {
it("forwards accountId and deviceId to matrix probes", async () => {
await matrixPlugin.status!.probeAccount?.({
cfg: {} as never,
timeoutMs: 500,

View File

@ -0,0 +1,85 @@
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const ensureMatrixSdkLoggingConfiguredMock = vi.hoisted(() => vi.fn());
const resolveValidatedMatrixHomeserverUrlMock = vi.hoisted(() => vi.fn());
const maybeMigrateLegacyStorageMock = vi.hoisted(() => vi.fn(async () => undefined));
const resolveMatrixStoragePathsMock = vi.hoisted(() => vi.fn());
const writeStorageMetaMock = vi.hoisted(() => vi.fn());
const MatrixClientMock = vi.hoisted(() => vi.fn());
vi.mock("./logging.js", () => ({
ensureMatrixSdkLoggingConfigured: ensureMatrixSdkLoggingConfiguredMock,
}));
vi.mock("./config.js", () => ({
resolveValidatedMatrixHomeserverUrl: resolveValidatedMatrixHomeserverUrlMock,
}));
vi.mock("./storage.js", () => ({
maybeMigrateLegacyStorage: maybeMigrateLegacyStorageMock,
resolveMatrixStoragePaths: resolveMatrixStoragePathsMock,
writeStorageMeta: writeStorageMetaMock,
}));
vi.mock("../sdk.js", () => ({
MatrixClient: MatrixClientMock,
}));
let createMatrixClient: typeof import("./create-client.js").createMatrixClient;
describe("createMatrixClient", () => {
const storagePaths = {
rootDir: "/tmp/openclaw-matrix-create-client-test",
storagePath: "/tmp/openclaw-matrix-create-client-test/storage.json",
recoveryKeyPath: "/tmp/openclaw-matrix-create-client-test/recovery.key",
idbSnapshotPath: "/tmp/openclaw-matrix-create-client-test/idb.snapshot",
metaPath: "/tmp/openclaw-matrix-create-client-test/storage-meta.json",
accountKey: "default",
tokenHash: "token-hash",
};
beforeAll(async () => {
({ createMatrixClient } = await import("./create-client.js"));
});
beforeEach(() => {
vi.clearAllMocks();
ensureMatrixSdkLoggingConfiguredMock.mockReturnValue(undefined);
resolveValidatedMatrixHomeserverUrlMock.mockResolvedValue("https://matrix.example.org");
resolveMatrixStoragePathsMock.mockReturnValue(storagePaths);
MatrixClientMock.mockImplementation(function MockMatrixClient() {
return {
stop: vi.fn(),
};
});
});
it("persists storage metadata by default", async () => {
await createMatrixClient({
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok",
});
expect(writeStorageMetaMock).toHaveBeenCalledWith({
storagePaths,
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accountId: undefined,
deviceId: undefined,
});
expect(MatrixClientMock).toHaveBeenCalledTimes(1);
});
it("skips storage metadata writes when persistence is disabled", async () => {
await createMatrixClient({
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok",
persistStorageMeta: false,
});
expect(writeStorageMetaMock).not.toHaveBeenCalled();
expect(MatrixClientMock).toHaveBeenCalledTimes(1);
});
});

View File

@ -33,6 +33,7 @@ export async function createMatrixClient(params: {
accessToken: string;
password?: string;
deviceId?: string;
persistStorageMeta?: boolean;
encryption?: boolean;
localTimeoutMs?: number;
initialSyncLimit?: number;
@ -66,13 +67,16 @@ export async function createMatrixClient(params: {
});
fs.mkdirSync(storagePaths.rootDir, { recursive: true });
writeStorageMeta({
storagePaths,
homeserver,
userId,
accountId: params.accountId,
deviceId: params.deviceId,
});
// Health probes still need validated paths, but they must not rewrite durable identity metadata.
if (params.persistStorageMeta !== false) {
writeStorageMeta({
storagePaths,
homeserver,
userId,
accountId: params.accountId,
deviceId: params.deviceId,
});
}
const cryptoDatabasePrefix = `openclaw-matrix-${storagePaths.accountKey}-${storagePaths.tokenHash}`;

View File

@ -34,6 +34,7 @@ describe("probeMatrix", () => {
homeserver: "https://matrix.example.org",
userId: undefined,
accessToken: "tok",
persistStorageMeta: false,
localTimeoutMs: 1234,
});
});
@ -50,6 +51,7 @@ describe("probeMatrix", () => {
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok",
persistStorageMeta: false,
localTimeoutMs: 500,
});
});
@ -67,6 +69,7 @@ describe("probeMatrix", () => {
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok",
persistStorageMeta: false,
localTimeoutMs: 500,
accountId: "ops",
});
@ -87,6 +90,7 @@ describe("probeMatrix", () => {
homeserver: "https://matrix.example.org",
userId: undefined,
accessToken: "tok",
persistStorageMeta: false,
localTimeoutMs: 500,
dispatcherPolicy: {
mode: "explicit-proxy",
@ -105,11 +109,15 @@ describe("probeMatrix", () => {
accountId: "ops",
});
expect(createMatrixClientMock).toHaveBeenCalledWith(
expect.objectContaining({
deviceId: "ABCDEF",
}),
);
expect(createMatrixClientMock).toHaveBeenCalledWith({
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok",
deviceId: "ABCDEF",
persistStorageMeta: false,
localTimeoutMs: 500,
accountId: "ops",
});
});
it("omits deviceId when not provided", async () => {
@ -119,11 +127,14 @@ describe("probeMatrix", () => {
timeoutMs: 500,
});
expect(createMatrixClientMock).toHaveBeenCalledWith(
expect.objectContaining({
deviceId: undefined,
}),
);
expect(createMatrixClientMock).toHaveBeenCalledWith({
homeserver: "https://matrix.example.org",
userId: undefined,
accessToken: "tok",
deviceId: undefined,
persistStorageMeta: false,
localTimeoutMs: 500,
});
});
it("returns client validation errors for insecure public http homeservers", async () => {

View File

@ -67,6 +67,7 @@ export async function probeMatrix(params: {
userId: inputUserId,
accessToken: params.accessToken,
deviceId: params.deviceId,
persistStorageMeta: false,
localTimeoutMs: params.timeoutMs,
accountId: params.accountId,
allowPrivateNetwork: params.allowPrivateNetwork,