mirror of https://github.com/openclaw/openclaw.git
test: deduplicate session target discovery cases
This commit is contained in:
parent
a4525b721e
commit
1ff8de3a8a
|
|
@ -15,6 +15,58 @@ async function resolveRealStorePath(sessionsDir: string): Promise<string> {
|
||||||
return fsSync.realpathSync.native(path.join(sessionsDir, "sessions.json"));
|
return fsSync.realpathSync.native(path.join(sessionsDir, "sessions.json"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function createAgentSessionStores(
|
||||||
|
root: string,
|
||||||
|
agentIds: string[],
|
||||||
|
): Promise<Record<string, string>> {
|
||||||
|
const storePaths: Record<string, string> = {};
|
||||||
|
for (const agentId of agentIds) {
|
||||||
|
const sessionsDir = path.join(root, "agents", agentId, "sessions");
|
||||||
|
await fs.mkdir(sessionsDir, { recursive: true });
|
||||||
|
await fs.writeFile(path.join(sessionsDir, "sessions.json"), "{}", "utf8");
|
||||||
|
storePaths[agentId] = await resolveRealStorePath(sessionsDir);
|
||||||
|
}
|
||||||
|
return storePaths;
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCustomRootCfg(customRoot: string, defaultAgentId = "ops"): OpenClawConfig {
|
||||||
|
return {
|
||||||
|
session: {
|
||||||
|
store: path.join(customRoot, "agents", "{agentId}", "sessions", "sessions.json"),
|
||||||
|
},
|
||||||
|
agents: {
|
||||||
|
list: [{ id: defaultAgentId, default: true }],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function expectTargetsToContainStores(
|
||||||
|
targets: Array<{ agentId: string; storePath: string }>,
|
||||||
|
stores: Record<string, string>,
|
||||||
|
): void {
|
||||||
|
expect(targets).toEqual(
|
||||||
|
expect.arrayContaining(
|
||||||
|
Object.entries(stores).map(([agentId, storePath]) => ({
|
||||||
|
agentId,
|
||||||
|
storePath,
|
||||||
|
})),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const discoveryResolvers = [
|
||||||
|
{
|
||||||
|
label: "async",
|
||||||
|
resolve: async (cfg: OpenClawConfig, env: NodeJS.ProcessEnv) =>
|
||||||
|
await resolveAllAgentSessionStoreTargets(cfg, { env }),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "sync",
|
||||||
|
resolve: async (cfg: OpenClawConfig, env: NodeJS.ProcessEnv) =>
|
||||||
|
resolveAllAgentSessionStoreTargetsSync(cfg, { env }),
|
||||||
|
},
|
||||||
|
] as const;
|
||||||
|
|
||||||
describe("resolveSessionStoreTargets", () => {
|
describe("resolveSessionStoreTargets", () => {
|
||||||
it("resolves all configured agent stores", () => {
|
it("resolves all configured agent stores", () => {
|
||||||
const cfg: OpenClawConfig = {
|
const cfg: OpenClawConfig = {
|
||||||
|
|
@ -83,97 +135,39 @@ describe("resolveAllAgentSessionStoreTargets", () => {
|
||||||
it("includes discovered on-disk agent stores alongside configured targets", async () => {
|
it("includes discovered on-disk agent stores alongside configured targets", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const stateDir = path.join(home, ".openclaw");
|
const stateDir = path.join(home, ".openclaw");
|
||||||
const opsSessionsDir = path.join(stateDir, "agents", "ops", "sessions");
|
const storePaths = await createAgentSessionStores(stateDir, ["ops", "retired"]);
|
||||||
const retiredSessionsDir = path.join(stateDir, "agents", "retired", "sessions");
|
|
||||||
await fs.mkdir(opsSessionsDir, { recursive: true });
|
|
||||||
await fs.mkdir(retiredSessionsDir, { recursive: true });
|
|
||||||
await fs.writeFile(path.join(opsSessionsDir, "sessions.json"), "{}", "utf8");
|
|
||||||
await fs.writeFile(path.join(retiredSessionsDir, "sessions.json"), "{}", "utf8");
|
|
||||||
|
|
||||||
const cfg: OpenClawConfig = {
|
const cfg: OpenClawConfig = {
|
||||||
agents: {
|
agents: {
|
||||||
list: [{ id: "ops", default: true }],
|
list: [{ id: "ops", default: true }],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const opsStorePath = await resolveRealStorePath(opsSessionsDir);
|
|
||||||
const retiredStorePath = await resolveRealStorePath(retiredSessionsDir);
|
|
||||||
|
|
||||||
const targets = await resolveAllAgentSessionStoreTargets(cfg, { env: process.env });
|
const targets = await resolveAllAgentSessionStoreTargets(cfg, { env: process.env });
|
||||||
|
|
||||||
expect(targets).toEqual(
|
expectTargetsToContainStores(targets, storePaths);
|
||||||
expect.arrayContaining([
|
expect(targets.filter((target) => target.storePath === storePaths.ops)).toHaveLength(1);
|
||||||
{
|
|
||||||
agentId: "ops",
|
|
||||||
storePath: opsStorePath,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
agentId: "retired",
|
|
||||||
storePath: retiredStorePath,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
expect(targets.filter((target) => target.storePath === opsStorePath)).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("discovers retired agent stores under a configured custom session root", async () => {
|
it("discovers retired agent stores under a configured custom session root", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const customRoot = path.join(home, "custom-state");
|
const customRoot = path.join(home, "custom-state");
|
||||||
const opsSessionsDir = path.join(customRoot, "agents", "ops", "sessions");
|
const storePaths = await createAgentSessionStores(customRoot, ["ops", "retired"]);
|
||||||
const retiredSessionsDir = path.join(customRoot, "agents", "retired", "sessions");
|
const cfg = createCustomRootCfg(customRoot);
|
||||||
await fs.mkdir(opsSessionsDir, { recursive: true });
|
|
||||||
await fs.mkdir(retiredSessionsDir, { recursive: true });
|
|
||||||
await fs.writeFile(path.join(opsSessionsDir, "sessions.json"), "{}", "utf8");
|
|
||||||
await fs.writeFile(path.join(retiredSessionsDir, "sessions.json"), "{}", "utf8");
|
|
||||||
|
|
||||||
const cfg: OpenClawConfig = {
|
|
||||||
session: {
|
|
||||||
store: path.join(customRoot, "agents", "{agentId}", "sessions", "sessions.json"),
|
|
||||||
},
|
|
||||||
agents: {
|
|
||||||
list: [{ id: "ops", default: true }],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const opsStorePath = await resolveRealStorePath(opsSessionsDir);
|
|
||||||
const retiredStorePath = await resolveRealStorePath(retiredSessionsDir);
|
|
||||||
|
|
||||||
const targets = await resolveAllAgentSessionStoreTargets(cfg, { env: process.env });
|
const targets = await resolveAllAgentSessionStoreTargets(cfg, { env: process.env });
|
||||||
|
|
||||||
expect(targets).toEqual(
|
expectTargetsToContainStores(targets, storePaths);
|
||||||
expect.arrayContaining([
|
expect(targets.filter((target) => target.storePath === storePaths.ops)).toHaveLength(1);
|
||||||
{
|
|
||||||
agentId: "ops",
|
|
||||||
storePath: opsStorePath,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
agentId: "retired",
|
|
||||||
storePath: retiredStorePath,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
expect(targets.filter((target) => target.storePath === opsStorePath)).toHaveLength(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("keeps the actual on-disk store path for discovered retired agents", async () => {
|
it("keeps the actual on-disk store path for discovered retired agents", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
const customRoot = path.join(home, "custom-state");
|
const customRoot = path.join(home, "custom-state");
|
||||||
const opsSessionsDir = path.join(customRoot, "agents", "ops", "sessions");
|
const storePaths = await createAgentSessionStores(customRoot, ["ops", "Retired Agent"]);
|
||||||
const retiredSessionsDir = path.join(customRoot, "agents", "Retired Agent", "sessions");
|
const cfg = createCustomRootCfg(customRoot);
|
||||||
await fs.mkdir(opsSessionsDir, { recursive: true });
|
|
||||||
await fs.mkdir(retiredSessionsDir, { recursive: true });
|
|
||||||
await fs.writeFile(path.join(opsSessionsDir, "sessions.json"), "{}", "utf8");
|
|
||||||
await fs.writeFile(path.join(retiredSessionsDir, "sessions.json"), "{}", "utf8");
|
|
||||||
|
|
||||||
const cfg: OpenClawConfig = {
|
|
||||||
session: {
|
|
||||||
store: path.join(customRoot, "agents", "{agentId}", "sessions", "sessions.json"),
|
|
||||||
},
|
|
||||||
agents: {
|
|
||||||
list: [{ id: "ops", default: true }],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const retiredStorePath = await resolveRealStorePath(retiredSessionsDir);
|
|
||||||
|
|
||||||
const targets = await resolveAllAgentSessionStoreTargets(cfg, { env: process.env });
|
const targets = await resolveAllAgentSessionStoreTargets(cfg, { env: process.env });
|
||||||
|
|
||||||
|
|
@ -181,7 +175,7 @@ describe("resolveAllAgentSessionStoreTargets", () => {
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
agentId: "retired-agent",
|
agentId: "retired-agent",
|
||||||
storePath: retiredStorePath,
|
storePath: storePaths["Retired Agent"],
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
);
|
);
|
||||||
|
|
@ -223,73 +217,52 @@ describe("resolveAllAgentSessionStoreTargets", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it("skips unreadable or invalid discovery roots when other roots are still readable", async () => {
|
for (const resolver of discoveryResolvers) {
|
||||||
await withTempHome(async (home) => {
|
it(`skips unreadable or invalid discovery roots when other roots are still readable (${resolver.label})`, async () => {
|
||||||
const customRoot = path.join(home, "custom-state");
|
await withTempHome(async (home) => {
|
||||||
await fs.mkdir(customRoot, { recursive: true });
|
const customRoot = path.join(home, "custom-state");
|
||||||
await fs.writeFile(path.join(customRoot, "agents"), "not-a-directory", "utf8");
|
await fs.mkdir(customRoot, { recursive: true });
|
||||||
|
await fs.writeFile(path.join(customRoot, "agents"), "not-a-directory", "utf8");
|
||||||
|
|
||||||
const envStateDir = path.join(home, "env-state");
|
const envStateDir = path.join(home, "env-state");
|
||||||
const mainSessionsDir = path.join(envStateDir, "agents", "main", "sessions");
|
const storePaths = await createAgentSessionStores(envStateDir, ["main", "retired"]);
|
||||||
const retiredSessionsDir = path.join(envStateDir, "agents", "retired", "sessions");
|
const cfg = createCustomRootCfg(customRoot, "main");
|
||||||
await fs.mkdir(mainSessionsDir, { recursive: true });
|
const env = {
|
||||||
await fs.mkdir(retiredSessionsDir, { recursive: true });
|
...process.env,
|
||||||
await fs.writeFile(path.join(mainSessionsDir, "sessions.json"), "{}", "utf8");
|
OPENCLAW_STATE_DIR: envStateDir,
|
||||||
await fs.writeFile(path.join(retiredSessionsDir, "sessions.json"), "{}", "utf8");
|
};
|
||||||
|
|
||||||
const cfg: OpenClawConfig = {
|
await expect(resolver.resolve(cfg, env)).resolves.toEqual(
|
||||||
session: {
|
expect.arrayContaining([
|
||||||
store: path.join(customRoot, "agents", "{agentId}", "sessions", "sessions.json"),
|
{
|
||||||
},
|
agentId: "retired",
|
||||||
agents: {
|
storePath: storePaths.retired,
|
||||||
list: [{ id: "main", default: true }],
|
},
|
||||||
},
|
]),
|
||||||
};
|
);
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
OPENCLAW_STATE_DIR: envStateDir,
|
|
||||||
};
|
|
||||||
const retiredStorePath = await resolveRealStorePath(retiredSessionsDir);
|
|
||||||
|
|
||||||
await expect(resolveAllAgentSessionStoreTargets(cfg, { env })).resolves.toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{
|
|
||||||
agentId: "retired",
|
|
||||||
storePath: retiredStorePath,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("skips symlinked discovered stores under templated agents roots", async () => {
|
|
||||||
await withTempHome(async (home) => {
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const customRoot = path.join(home, "custom-state");
|
|
||||||
const opsSessionsDir = path.join(customRoot, "agents", "ops", "sessions");
|
|
||||||
const leakedFile = path.join(home, "outside.json");
|
|
||||||
await fs.mkdir(opsSessionsDir, { recursive: true });
|
|
||||||
await fs.writeFile(leakedFile, JSON.stringify({ leak: { secret: "x" } }), "utf8");
|
|
||||||
await fs.symlink(leakedFile, path.join(opsSessionsDir, "sessions.json"));
|
|
||||||
|
|
||||||
const cfg: OpenClawConfig = {
|
|
||||||
session: {
|
|
||||||
store: path.join(customRoot, "agents", "{agentId}", "sessions", "sessions.json"),
|
|
||||||
},
|
|
||||||
agents: {
|
|
||||||
list: [{ id: "ops", default: true }],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const targets = await resolveAllAgentSessionStoreTargets(cfg, { env: process.env });
|
|
||||||
expect(targets).not.toContainEqual({
|
|
||||||
agentId: "ops",
|
|
||||||
storePath: expect.stringContaining(path.join("ops", "sessions", "sessions.json")),
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
it(`skips symlinked discovered stores under templated agents roots (${resolver.label})`, async () => {
|
||||||
|
await withTempHome(async (home) => {
|
||||||
|
if (process.platform === "win32") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const customRoot = path.join(home, "custom-state");
|
||||||
|
const opsSessionsDir = path.join(customRoot, "agents", "ops", "sessions");
|
||||||
|
const leakedFile = path.join(home, "outside.json");
|
||||||
|
await fs.mkdir(opsSessionsDir, { recursive: true });
|
||||||
|
await fs.writeFile(leakedFile, JSON.stringify({ leak: { secret: "x" } }), "utf8");
|
||||||
|
await fs.symlink(leakedFile, path.join(opsSessionsDir, "sessions.json"));
|
||||||
|
|
||||||
|
const targets = await resolver.resolve(createCustomRootCfg(customRoot), process.env);
|
||||||
|
expect(targets).not.toContainEqual({
|
||||||
|
agentId: "ops",
|
||||||
|
storePath: expect.stringContaining(path.join("ops", "sessions", "sessions.json")),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
it("skips discovered directories that only normalize into the default main agent", async () => {
|
it("skips discovered directories that only normalize into the default main agent", async () => {
|
||||||
await withTempHome(async (home) => {
|
await withTempHome(async (home) => {
|
||||||
|
|
@ -315,73 +288,3 @@ describe("resolveAllAgentSessionStoreTargets", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("resolveAllAgentSessionStoreTargetsSync", () => {
|
|
||||||
it("skips unreadable or invalid discovery roots when other roots are still readable", async () => {
|
|
||||||
await withTempHome(async (home) => {
|
|
||||||
const customRoot = path.join(home, "custom-state");
|
|
||||||
await fs.mkdir(customRoot, { recursive: true });
|
|
||||||
await fs.writeFile(path.join(customRoot, "agents"), "not-a-directory", "utf8");
|
|
||||||
|
|
||||||
const envStateDir = path.join(home, "env-state");
|
|
||||||
const mainSessionsDir = path.join(envStateDir, "agents", "main", "sessions");
|
|
||||||
const retiredSessionsDir = path.join(envStateDir, "agents", "retired", "sessions");
|
|
||||||
await fs.mkdir(mainSessionsDir, { recursive: true });
|
|
||||||
await fs.mkdir(retiredSessionsDir, { recursive: true });
|
|
||||||
await fs.writeFile(path.join(mainSessionsDir, "sessions.json"), "{}", "utf8");
|
|
||||||
await fs.writeFile(path.join(retiredSessionsDir, "sessions.json"), "{}", "utf8");
|
|
||||||
|
|
||||||
const cfg: OpenClawConfig = {
|
|
||||||
session: {
|
|
||||||
store: path.join(customRoot, "agents", "{agentId}", "sessions", "sessions.json"),
|
|
||||||
},
|
|
||||||
agents: {
|
|
||||||
list: [{ id: "main", default: true }],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
const env = {
|
|
||||||
...process.env,
|
|
||||||
OPENCLAW_STATE_DIR: envStateDir,
|
|
||||||
};
|
|
||||||
const retiredStorePath = await resolveRealStorePath(retiredSessionsDir);
|
|
||||||
|
|
||||||
expect(resolveAllAgentSessionStoreTargetsSync(cfg, { env })).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
{
|
|
||||||
agentId: "retired",
|
|
||||||
storePath: retiredStorePath,
|
|
||||||
},
|
|
||||||
]),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("skips symlinked discovered stores under templated agents roots", async () => {
|
|
||||||
await withTempHome(async (home) => {
|
|
||||||
if (process.platform === "win32") {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const customRoot = path.join(home, "custom-state");
|
|
||||||
const opsSessionsDir = path.join(customRoot, "agents", "ops", "sessions");
|
|
||||||
const leakedFile = path.join(home, "outside.json");
|
|
||||||
await fs.mkdir(opsSessionsDir, { recursive: true });
|
|
||||||
await fs.writeFile(leakedFile, JSON.stringify({ leak: { secret: "x" } }), "utf8");
|
|
||||||
await fs.symlink(leakedFile, path.join(opsSessionsDir, "sessions.json"));
|
|
||||||
|
|
||||||
const cfg: OpenClawConfig = {
|
|
||||||
session: {
|
|
||||||
store: path.join(customRoot, "agents", "{agentId}", "sessions", "sessions.json"),
|
|
||||||
},
|
|
||||||
agents: {
|
|
||||||
list: [{ id: "ops", default: true }],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const targets = resolveAllAgentSessionStoreTargetsSync(cfg, { env: process.env });
|
|
||||||
expect(targets).not.toContainEqual({
|
|
||||||
agentId: "ops",
|
|
||||||
storePath: expect.stringContaining(path.join("ops", "sessions", "sessions.json")),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue