import * as processRuntime from "openclaw/plugin-sdk/process-runtime" ;
import * as setupRuntime from "openclaw/plugin-sdk/setup" ;
import { beforeEach, describe, expect, it, vi } from "vitest" ;
import { createPluginSetupWizardStatus } from "../../../test/helpers/plugins/setup-wizard.js" ;
import { resolveIMessageAccount } from "./accounts.js" ;
import * as channelRuntimeModule from "./channel.runtime.js" ;
import * as clientModule from "./client.js" ;
import { probeIMessage } from "./probe.js" ;
import { imessageSetupWizard } from "./setup-surface.js" ;
import { probeIMessageStatusAccount } from "./status-core.js" ;
const getIMessageSetupStatus = createPluginSetupWizardStatus({
id: "imessage" ,
meta: {
label: "iMessage" ,
},
setupWizard: imessageSetupWizard,
} as never);
const spawnMock = vi.hoisted(() => vi.fn());
vi.mock("node:child_process" , async () => {
const actual = await vi.importActual<typeof import ("node:child_process" )>("node:child_process" );
return {
...actual,
spawn: (...args: unknown[]) => spawnMock(...args),
};
});
describe("createIMessageRpcClient" , () => {
beforeEach(() => {
spawnMock.mockClear();
vi.stubEnv("VITEST" , "true" );
});
it("refuses to spawn imsg rpc in test environments" , async () => {
const { createIMessageRpcClient } = await import ("./client.js" );
await expect(createIMessageRpcClient()).rejects.toThrow(
/Refusing to start imsg rpc in test environment/i,
);
expect(spawnMock).not.toHaveBeenCalled();
});
});
describe("imessage setup status" , () => {
it("does not inherit configured state from a sibling account" , async () => {
const result = await getIMessageSetupStatus({
cfg: {
channels: {
imessage: {
accounts: {
default : {
cliPath: "/usr/local/bin/imsg" ,
},
work: {},
},
},
},
},
accountOverrides: {
imessage: "work" ,
},
});
expect(result.configured).toBe(false );
expect(result.statusLines).toContain("iMessage: needs setup" );
});
it("uses configured defaultAccount for omitted setup status cliPath" , async () => {
const status = await getIMessageSetupStatus({
cfg: {
channels: {
imessage: {
cliPath: "/tmp/root-imsg" ,
defaultAccount: "work" ,
accounts: {
work: {
cliPath: "/tmp/work-imsg" ,
},
},
},
},
} as never,
accountOverrides: {},
});
expect(status.statusLines).toContain("imsg: missing (/tmp/work-imsg)" );
});
it("does not inherit configured state from a sibling when defaultAccount is named" , async () => {
const status = await getIMessageSetupStatus({
cfg: {
channels: {
imessage: {
defaultAccount: "work" ,
accounts: {
default : {
cliPath: "/usr/local/bin/imsg" ,
},
work: {},
},
},
},
} as never,
accountOverrides: {},
});
expect(status.configured).toBe(false );
expect(status.statusLines).toContain("iMessage: needs setup" );
});
it("setup status lines use the selected account cliPath" , async () => {
const status = await getIMessageSetupStatus({
cfg: {
channels: {
imessage: {
cliPath: "/tmp/root-imsg" ,
accounts: {
work: {
cliPath: "/tmp/work-imsg" ,
},
},
},
},
} as never,
accountOverrides: { imessage: "work" },
});
expect(status.statusLines).toContain("imsg: missing (/tmp/work-imsg)" );
});
});
describe("probeIMessage" , () => {
beforeEach(() => {
vi.restoreAllMocks();
spawnMock.mockClear();
vi.spyOn(setupRuntime, "detectBinary" ).mockResolvedValue(true );
vi.spyOn(processRuntime, "runCommandWithTimeout" ).mockResolvedValue({
stdout: "" ,
stderr: 'unknown command "rpc" for "imsg"' ,
code: 1 ,
signal: null ,
killed: false ,
termination: "exit" ,
});
});
it("marks unknown rpc subcommand as fatal" , async () => {
const createIMessageRpcClientMock = vi
.spyOn(clientModule, "createIMessageRpcClient" )
.mockResolvedValue({
request: vi.fn(),
stop: vi.fn(),
} as unknown as Awaited<ReturnType<typeof clientModule.createIMessageRpcClient>>);
const result = await probeIMessage(1000 , { cliPath: "imsg-test-rpc" });
expect(result.ok).toBe(false );
expect(result.fatal).toBe(true );
expect(result.error).toMatch(/rpc/i);
expect(createIMessageRpcClientMock).not.toHaveBeenCalled();
});
it("status probe uses account-scoped cliPath and dbPath" , async () => {
const probeSpy = vi.spyOn(channelRuntimeModule, "probeIMessageAccount" ).mockResolvedValue({
ok: true ,
cliPath: "imsg-work" ,
dbPath: "/tmp/work-db" ,
} as Awaited<ReturnType<typeof channelRuntimeModule.probeIMessageAccount>>);
const cfg = {
channels: {
imessage: {
cliPath: "imsg-root" ,
dbPath: "/tmp/root-db" ,
accounts: {
work: {
cliPath: "imsg-work" ,
dbPath: "/tmp/work-db" ,
},
},
},
},
} as const ;
const account = resolveIMessageAccount({ cfg, accountId: "work" });
await probeIMessageStatusAccount({
account,
timeoutMs: 2500 ,
probeIMessageAccount: channelRuntimeModule.probeIMessageAccount,
});
expect(probeSpy).toHaveBeenCalledWith({
timeoutMs: 2500 ,
cliPath: "imsg-work" ,
dbPath: "/tmp/work-db" ,
});
});
});
Messung V0.5 in Prozent C=96 H=97 G=96
¤ Dauer der Verarbeitung: 0.9 Sekunden
(vorverarbeitet am 2026-06-05)
¤
*© Formatika GbR, Deutschland