mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix: gate legacy session group migration
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
// @vitest-environment node
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import {
|
||||
GatewayRequestError,
|
||||
type GatewayBrowserClient,
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
type GatewayHelloOk,
|
||||
} from "../../api/gateway.ts";
|
||||
import type { SessionsListResult } from "../../api/types.ts";
|
||||
import { createStorageMock } from "../../test-helpers/storage.ts";
|
||||
import { waitForFast } from "../../test-helpers/wait-for.ts";
|
||||
import { createSessionCapability, reconcileSessionRunTerminal } from "./index.ts";
|
||||
|
||||
@@ -30,7 +31,11 @@ function deferred<T>() {
|
||||
return { promise, reject, resolve };
|
||||
}
|
||||
|
||||
function createGatewayHarness(client: GatewayBrowserClient, featureMethods?: string[]) {
|
||||
function createGatewayHarness(
|
||||
client: GatewayBrowserClient,
|
||||
featureMethods?: string[],
|
||||
scopes?: string[],
|
||||
) {
|
||||
let snapshot: {
|
||||
client: GatewayBrowserClient | null;
|
||||
phase: "connected" | "reconnecting";
|
||||
@@ -45,7 +50,10 @@ function createGatewayHarness(client: GatewayBrowserClient, featureMethods?: str
|
||||
hello:
|
||||
featureMethods === undefined
|
||||
? null
|
||||
: ({ features: { methods: featureMethods } } as GatewayHelloOk),
|
||||
: ({
|
||||
...(scopes ? { auth: { role: "operator", scopes } } : {}),
|
||||
features: { methods: featureMethods },
|
||||
} as GatewayHelloOk),
|
||||
};
|
||||
const listeners = new Set<(next: typeof snapshot) => void>();
|
||||
const eventListeners = new Set<(event: GatewayEventFrame) => void>();
|
||||
@@ -81,6 +89,10 @@ function createGatewayHarness(client: GatewayBrowserClient, featureMethods?: str
|
||||
};
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
vi.unstubAllGlobals();
|
||||
});
|
||||
|
||||
function sessionChangedEvent(key: string): GatewayEventFrame {
|
||||
return {
|
||||
type: "event",
|
||||
@@ -170,6 +182,61 @@ describe("createSessionCapability", () => {
|
||||
sessions.dispose();
|
||||
});
|
||||
|
||||
it("does not migrate legacy browser groups without operator.write", async () => {
|
||||
vi.stubGlobal("localStorage", createStorageMock());
|
||||
localStorage.setItem("openclaw:sessions:custom-groups", JSON.stringify(["Research"]));
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "sessions.groups.list") {
|
||||
return { groups: [] };
|
||||
}
|
||||
throw new Error(`Unexpected request: ${method}`);
|
||||
});
|
||||
const client = { request } as unknown as GatewayBrowserClient;
|
||||
const { gateway } = createGatewayHarness(
|
||||
client,
|
||||
["sessions.groups.list", "sessions.groups.put"],
|
||||
["operator.read"],
|
||||
);
|
||||
const sessions = createSessionCapability(gateway);
|
||||
|
||||
await sessions.groupsLoad();
|
||||
|
||||
expect(request).toHaveBeenCalledOnce();
|
||||
expect(request).toHaveBeenCalledWith("sessions.groups.list", {});
|
||||
expect(localStorage.getItem("openclaw:sessions:custom-groups")).toBe(
|
||||
JSON.stringify(["Research"]),
|
||||
);
|
||||
sessions.dispose();
|
||||
});
|
||||
|
||||
it("migrates legacy browser groups with operator.write", async () => {
|
||||
vi.stubGlobal("localStorage", createStorageMock());
|
||||
localStorage.setItem("openclaw:sessions:custom-groups", JSON.stringify(["Research"]));
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "sessions.groups.list") {
|
||||
return { groups: [] };
|
||||
}
|
||||
if (method === "sessions.groups.put") {
|
||||
return { groups: [{ name: "Research" }] };
|
||||
}
|
||||
throw new Error(`Unexpected request: ${method}`);
|
||||
});
|
||||
const client = { request } as unknown as GatewayBrowserClient;
|
||||
const { gateway } = createGatewayHarness(
|
||||
client,
|
||||
["sessions.groups.list", "sessions.groups.put"],
|
||||
["operator.write"],
|
||||
);
|
||||
const sessions = createSessionCapability(gateway);
|
||||
|
||||
await sessions.groupsLoad();
|
||||
|
||||
expect(request).toHaveBeenCalledWith("sessions.groups.put", { names: ["Research"] });
|
||||
expect(sessions.state.groups).toEqual(["Research"]);
|
||||
expect(localStorage.getItem("openclaw:sessions:custom-groups")).toBeNull();
|
||||
sessions.dispose();
|
||||
});
|
||||
|
||||
it("publishes state.error when group rename is rejected", async () => {
|
||||
const request = vi.fn(async (method: string) => {
|
||||
if (method === "sessions.groups.rename") {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { getSafeLocalStorage } from "../../local-storage.ts";
|
||||
import { isGatewayMethodAdvertised } from "../gateway-methods.ts";
|
||||
import { readSessionMethodAccess } from "../session-method-access.ts";
|
||||
import { readSessionCustomGroupNames, readSidebarSectionOrder } from "./custom-groups.ts";
|
||||
import type {
|
||||
SessionConnectionOwner,
|
||||
@@ -93,7 +94,11 @@ export function createSessionGroupCatalog(host: SessionGroupCatalogHost) {
|
||||
let sectionOrder = readSidebarSectionOrder(listed);
|
||||
// Browser-local catalogs predate the gateway store and migrate exactly once.
|
||||
const legacy = readLegacyStoredGroups();
|
||||
if (names.length === 0 && legacy.length > 0) {
|
||||
const legacyMigrationAccess = readSessionMethodAccess(host.snapshot(), {
|
||||
method: "sessions.groups.put",
|
||||
requiredScope: "operator.write",
|
||||
});
|
||||
if (names.length === 0 && legacy.length > 0 && legacyMigrationAccess.allowed) {
|
||||
const put = await scope.client.request("sessions.groups.put", { names: legacy });
|
||||
if (!host.connection.isCurrent(scope) || generation !== loadGeneration) {
|
||||
return;
|
||||
@@ -101,7 +106,7 @@ export function createSessionGroupCatalog(host: SessionGroupCatalogHost) {
|
||||
names = readSessionCustomGroupNames(put);
|
||||
sectionOrder = readSidebarSectionOrder(put);
|
||||
}
|
||||
if (legacy.length > 0) {
|
||||
if (legacy.length > 0 && legacyMigrationAccess.allowed) {
|
||||
try {
|
||||
getSafeLocalStorage()?.removeItem(LEGACY_GROUPS_STORAGE_KEY);
|
||||
} catch {
|
||||
|
||||
Reference in New Issue
Block a user