mirror of
https://github.com/openclaw/openclaw.git
synced 2026-07-16 20:51:43 +00:00
* fix(ai): preserve streamed tool-call identity * fix(computer): bind actions to current tool authority * fix(macos): serialize computer control lifecycle * docs(computer): document hardened control contract * chore: follow release-owned changelog policy * test(agents): cover node list cancellation
131 lines
4.2 KiB
TypeScript
131 lines
4.2 KiB
TypeScript
// Phone Control tests cover doctor contract api plugin behavior.
|
|
import fs from "node:fs/promises";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import {
|
|
createPluginStateKeyedStoreForTests,
|
|
resetPluginStateStoreForTests,
|
|
} from "openclaw/plugin-sdk/plugin-state-test-runtime";
|
|
import type {
|
|
OpenKeyedStoreOptions,
|
|
PluginDoctorStateMigrationContext,
|
|
} from "openclaw/plugin-sdk/runtime-doctor";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { stateMigrations } from "./doctor-contract-api.js";
|
|
|
|
function createDoctorContext(env: NodeJS.ProcessEnv): PluginDoctorStateMigrationContext {
|
|
return {
|
|
openPluginStateKeyedStore<T>(options: OpenKeyedStoreOptions) {
|
|
return createPluginStateKeyedStoreForTests<T>("phone-control", {
|
|
...options,
|
|
env: options.env ?? env,
|
|
});
|
|
},
|
|
};
|
|
}
|
|
|
|
describe("phone-control doctor state migration", () => {
|
|
let stateDir = "";
|
|
let env: NodeJS.ProcessEnv;
|
|
|
|
beforeEach(async () => {
|
|
resetPluginStateStoreForTests();
|
|
stateDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-phone-control-doctor-"));
|
|
env = { ...process.env, OPENCLAW_STATE_DIR: stateDir };
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await fs.rm(stateDir, { recursive: true, force: true });
|
|
});
|
|
|
|
it("imports legacy armed state into plugin state", async () => {
|
|
const sourcePath = path.join(stateDir, "plugins", "phone-control", "armed.json");
|
|
const legacyState = {
|
|
version: 2,
|
|
armedAtMs: 100,
|
|
expiresAtMs: 200,
|
|
group: "writes",
|
|
armedCommands: ["sms.send"],
|
|
addedToAllow: ["sms.send"],
|
|
removedFromDeny: [],
|
|
};
|
|
await fs.mkdir(path.dirname(sourcePath), { recursive: true });
|
|
await fs.writeFile(sourcePath, JSON.stringify(legacyState));
|
|
|
|
const migration = stateMigrations[0];
|
|
await expect(
|
|
migration.detectLegacyState({
|
|
config: {},
|
|
env,
|
|
stateDir,
|
|
oauthDir: path.join(stateDir, "oauth"),
|
|
context: createDoctorContext(env),
|
|
}),
|
|
).resolves.toMatchObject({
|
|
preview: [expect.stringContaining("Phone Control armed state")],
|
|
});
|
|
|
|
const result = await migration.migrateLegacyState({
|
|
config: {},
|
|
env,
|
|
stateDir,
|
|
oauthDir: path.join(stateDir, "oauth"),
|
|
context: createDoctorContext(env),
|
|
});
|
|
|
|
expect(result.warnings).toEqual([]);
|
|
expect(result.changes).toEqual([
|
|
"Migrated Phone Control armed state -> plugin state",
|
|
expect.stringContaining("Archived Phone Control armed-state legacy source"),
|
|
]);
|
|
await expect(fs.access(sourcePath)).rejects.toThrow();
|
|
await expect(fs.access(`${sourcePath}.migrated`)).resolves.toBeUndefined();
|
|
await expect(
|
|
createDoctorContext(env)
|
|
.openPluginStateKeyedStore({
|
|
namespace: "armed",
|
|
maxEntries: 1,
|
|
overflowPolicy: "reject-new",
|
|
})
|
|
.lookup("current"),
|
|
).resolves.toEqual(legacyState);
|
|
});
|
|
|
|
it("does not overwrite a generation-keyed arm lease", async () => {
|
|
const sourcePath = path.join(stateDir, "plugins", "phone-control", "armed.json");
|
|
const legacyState = {
|
|
version: 1,
|
|
armedAtMs: 100,
|
|
expiresAtMs: 200,
|
|
removedFromDeny: ["sms.send"],
|
|
};
|
|
await fs.mkdir(path.dirname(sourcePath), { recursive: true });
|
|
await fs.writeFile(sourcePath, JSON.stringify(legacyState));
|
|
const context = createDoctorContext(env);
|
|
const store = context.openPluginStateKeyedStore<Record<string, unknown>>({
|
|
namespace: "armed",
|
|
maxEntries: 1,
|
|
overflowPolicy: "reject-new",
|
|
});
|
|
await store.register("generation-1", { version: 3, generation: "generation-1" });
|
|
|
|
const result = await stateMigrations[0]?.migrateLegacyState({
|
|
config: {},
|
|
env,
|
|
stateDir,
|
|
oauthDir: path.join(stateDir, "oauth"),
|
|
context,
|
|
});
|
|
|
|
expect(result?.changes).toEqual([]);
|
|
expect(result?.warnings).toEqual([
|
|
"Left Phone Control armed-state source in place because plugin state exists",
|
|
]);
|
|
await expect(store.lookup("generation-1")).resolves.toEqual({
|
|
version: 3,
|
|
generation: "generation-1",
|
|
});
|
|
await expect(fs.access(sourcePath)).resolves.toBeUndefined();
|
|
});
|
|
});
|