test(gateway): repair concurrent group defaults fixture

The rename regression performed a synchronous state write from a live-authority
assertion. Admitted workers now retain lifecycle custody, so that reentrant
fixture failed with lifecycle contention before the intended defaults conflict.

Await the real member update and cleanup, then use the public defaults writer
before catalog retirement. Preserve the moved member, both groups' defaults,
and source sidebar entry, and require the precise changed-before-completion
error. Production lifecycle and authority behavior remain unchanged.

Reproduced main CI run 35794059136, job 106969451974 in the original 25-file
subgroup and standalone on Linux Node 24.19.0. The repaired standalone file
passes 29 tests; the original subgroup passes 385 tests. The measured command
pnpm test src/gateway/session-groups.test.ts --maxWorkers=1 takes 49.243s wall
(including compilation/startup); its existing real SQLite lifecycle and
multi-store mutation cases account for the file's cost. No new test is added.
Changed-file checks and independent P0-P2 review pass.
This commit is contained in:
Peter Steinberger 2026-09-22 16:46:03 -07:00
parent 7d7f977ccf
commit 46dfea6a42

View file

@ -5,6 +5,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { SessionEntry } from "../config/sessions.js";
import { loadSessionEntry, replaceSessionEntry } from "../config/sessions/session-accessor.js";
import { writeSessionEntry } from "../config/sessions/session-accessor.sqlite-entry-store.js";
import * as sessionGroupCategories from "../config/sessions/session-group-categories.js";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { requireNodeSqlite } from "../infra/node-sqlite.js";
import { readConfigMachineState } from "../state/config-machine-state.js";
@ -685,26 +686,23 @@ describe("session groups catalog", () => {
const storePath = await seedSessionStore({
[sessionKey]: { sessionId: "changed-group", updatedAt: Date.now(), category: "Old" },
});
const updateCategories = sessionGroupCategories.updateSessionGroupCategoriesInWorker;
vi.spyOn(sessionGroupCategories, "updateSessionGroupCategoriesInWorker").mockImplementationOnce(
async (params) => {
const updated = await updateCategories(params);
// Interleave after member custody settles, before the rename retires its source.
await updateSessionGroupDefaults("Old", { cwd: "/repos/after", worktree: true }, env);
return updated;
},
);
await expect(
renameSessionGroup({
cfg,
name: "Old",
to: "New",
env,
assertTargetCurrent: () => {
stateDatabase.runOpenClawStateWriteTransaction(
({ db }) => {
db.prepare("UPDATE session_groups SET cwd = ?, worktree = ? WHERE name = ?").run(
"/repos/after",
1,
"Old",
);
},
{ env },
);
},
}),
).rejects.toThrow(/changed/);
).rejects.toThrow('session group "Old" changed before completion');
expect(loadSessionEntry({ agentId: "main", storePath, sessionKey })?.category).toBe("New");
expect(listSessionGroupDefaults(env)).toEqual(
expect.arrayContaining([