openclaw/extensions/matrix/src/channel.setup.test.ts

270 lines
7.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../runtime-api.js";
const verificationMocks = vi.hoisted(() => ({
bootstrapMatrixVerification: vi.fn(),
}));
vi.mock("./matrix/actions/verification.js", () => ({
bootstrapMatrixVerification: verificationMocks.bootstrapMatrixVerification,
}));
import { matrixPlugin } from "./channel.js";
import { matrixSetupAdapter } from "./setup-core.js";
import { installMatrixTestRuntime } from "./test-runtime.js";
import type { CoreConfig } from "./types.js";
let runMatrixSetupBootstrapAfterConfigWrite: typeof import("./setup-bootstrap.js").runMatrixSetupBootstrapAfterConfigWrite;
describe("matrix setup post-write bootstrap", () => {
const log = vi.fn();
const error = vi.fn();
const exit = vi.fn((code: number): never => {
throw new Error(`exit ${code}`);
});
const encryptedDefaultCfg = {
channels: {
matrix: {
encryption: true,
},
},
} as CoreConfig;
const defaultPasswordInput = {
homeserver: "https://matrix.example.org",
userId: "@flurry:example.org",
password: "secret", // pragma: allowlist secret
} as const;
const runtime: RuntimeEnv = {
log,
error,
exit,
};
function applyAccountConfig(params: {
previousCfg: CoreConfig;
accountId: string;
input: Record<string, unknown>;
}) {
return {
previousCfg: params.previousCfg,
accountId: params.accountId,
input: params.input,
nextCfg: matrixSetupAdapter.applyAccountConfig({
cfg: params.previousCfg,
accountId: params.accountId,
input: params.input,
}) as CoreConfig,
};
}
function applyDefaultAccountConfig(input: Record<string, unknown> = defaultPasswordInput) {
return applyAccountConfig({
previousCfg: encryptedDefaultCfg,
accountId: "default",
input,
});
}
function mockBootstrapResult(params: {
success: boolean;
backupVersion?: string | null;
error?: string;
}) {
verificationMocks.bootstrapMatrixVerification.mockResolvedValue({
success: params.success,
...(params.error ? { error: params.error } : {}),
verification: {
backupVersion: params.backupVersion ?? null,
},
crossSigning: {},
pendingVerifications: 0,
cryptoBootstrap: null,
});
}
async function runAfterAccountConfigWritten(params: {
previousCfg: CoreConfig;
nextCfg: CoreConfig;
accountId: string;
input: Record<string, unknown>;
}) {
await runMatrixSetupBootstrapAfterConfigWrite({
previousCfg: params.previousCfg,
cfg: params.nextCfg,
accountId: params.accountId,
runtime,
});
}
async function withSavedEnv<T>(
values: Record<string, string | undefined>,
run: () => Promise<T> | T,
) {
const previousEnv = Object.fromEntries(
Object.keys(values).map((key) => [key, process.env[key]]),
) as Record<string, string | undefined>;
for (const [key, value] of Object.entries(values)) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
try {
return await run();
} finally {
for (const [key, value] of Object.entries(previousEnv)) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
}
}
beforeEach(async () => {
vi.resetModules();
({ runMatrixSetupBootstrapAfterConfigWrite } = await import("./setup-bootstrap.js"));
verificationMocks.bootstrapMatrixVerification.mockReset();
log.mockClear();
error.mockClear();
exit.mockClear();
installMatrixTestRuntime();
});
it("bootstraps verification for newly added encrypted accounts", async () => {
const { previousCfg, nextCfg, accountId, input } = applyDefaultAccountConfig();
mockBootstrapResult({ success: true, backupVersion: "7" });
await runAfterAccountConfigWritten({ previousCfg, nextCfg, accountId, input });
expect(verificationMocks.bootstrapMatrixVerification).toHaveBeenCalledWith({
accountId: "default",
});
expect(log).toHaveBeenCalledWith('Matrix verification bootstrap: complete for "default".');
expect(log).toHaveBeenCalledWith('Matrix backup version for "default": 7');
expect(error).not.toHaveBeenCalled();
});
it("does not bootstrap verification for already configured accounts", async () => {
const previousCfg = {
channels: {
matrix: {
accounts: {
flurry: {
encryption: true,
homeserver: "https://matrix.example.org",
userId: "@flurry:example.org",
accessToken: "token",
},
},
},
},
} as CoreConfig;
const input = {
homeserver: "https://matrix.example.org",
userId: "@flurry:example.org",
accessToken: "new-token",
};
const { nextCfg, accountId } = applyAccountConfig({
previousCfg,
accountId: "flurry",
input,
});
await runAfterAccountConfigWritten({ previousCfg, nextCfg, accountId, input });
expect(verificationMocks.bootstrapMatrixVerification).not.toHaveBeenCalled();
expect(log).not.toHaveBeenCalled();
expect(error).not.toHaveBeenCalled();
});
it("logs a warning when verification bootstrap fails", async () => {
const { previousCfg, nextCfg, accountId, input } = applyDefaultAccountConfig();
mockBootstrapResult({
success: false,
error: "no room-key backup exists on the homeserver",
});
await runAfterAccountConfigWritten({ previousCfg, nextCfg, accountId, input });
expect(error).toHaveBeenCalledWith(
'Matrix verification bootstrap warning for "default": no room-key backup exists on the homeserver',
);
});
it("bootstraps a newly added env-backed default account when encryption is already enabled", async () => {
await withSavedEnv(
{
MATRIX_HOMESERVER: "https://matrix.example.org",
MATRIX_ACCESS_TOKEN: "env-token",
},
async () => {
const { previousCfg, nextCfg, accountId, input } = applyDefaultAccountConfig({
useEnv: true,
});
mockBootstrapResult({ success: true, backupVersion: "9" });
await runAfterAccountConfigWritten({ previousCfg, nextCfg, accountId, input });
expect(verificationMocks.bootstrapMatrixVerification).toHaveBeenCalledWith({
accountId: "default",
});
expect(log).toHaveBeenCalledWith('Matrix verification bootstrap: complete for "default".');
},
);
});
it("rejects default useEnv setup when no Matrix auth env vars are available", () => {
return withSavedEnv(
{
MATRIX_HOMESERVER: undefined,
MATRIX_USER_ID: undefined,
MATRIX_ACCESS_TOKEN: undefined,
MATRIX_PASSWORD: undefined,
MATRIX_DEFAULT_HOMESERVER: undefined,
MATRIX_DEFAULT_USER_ID: undefined,
MATRIX_DEFAULT_ACCESS_TOKEN: undefined,
MATRIX_DEFAULT_PASSWORD: undefined,
},
() => {
expect(
matrixSetupAdapter.validateInput?.({
cfg: {} as CoreConfig,
accountId: "default",
input: { useEnv: true },
}),
).toContain("Set Matrix env vars for the default account");
},
);
});
it("clears allowPrivateNetwork when deleting the default Matrix account config", () => {
const updated = matrixPlugin.config.deleteAccount?.({
cfg: {
channels: {
matrix: {
homeserver: "http://localhost.localdomain:8008",
allowPrivateNetwork: true,
accounts: {
ops: {
enabled: true,
},
},
},
},
} as CoreConfig,
accountId: "default",
}) as CoreConfig;
expect(updated.channels?.matrix).toEqual({
accounts: {
ops: {
enabled: true,
},
},
});
});
});