mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(config): consolidate regression fixtures (#114459)
This commit is contained in:
committed by
GitHub
parent
b869d5e73f
commit
4cb33d48cd
@@ -3,12 +3,31 @@ import { describe, expect, it } from "vitest";
|
||||
import type { OpenClawConfig } from "./config.js";
|
||||
import { resolveChannelGroupRequireMention, resolveToolsBySender } from "./group-policy.js";
|
||||
import {
|
||||
resolveScopeKeyCaseInsensitive,
|
||||
resolveScopeIntroHint,
|
||||
resolveScopeRequireMention,
|
||||
resolveScopeToolsPolicy,
|
||||
type ScopeTree,
|
||||
} from "./group-scope-tree.js";
|
||||
|
||||
describe("resolveScopeKeyCaseInsensitive", () => {
|
||||
it("preserves exact scope identity before case-insensitive fallback", () => {
|
||||
const tree: ScopeTree = {
|
||||
scopes: {
|
||||
"Room:Mixed": { requireMention: true },
|
||||
"room:mixed": { requireMention: false },
|
||||
},
|
||||
};
|
||||
|
||||
expect(resolveScopeKeyCaseInsensitive(tree, "Room:Mixed")).toBe("Room:Mixed");
|
||||
expect(resolveScopeKeyCaseInsensitive(tree, "ROOM:MIXED")).toBe("Room:Mixed");
|
||||
expect(resolveScopeKeyCaseInsensitive(tree, " room:mixed ")).toBe("Room:Mixed");
|
||||
expect(resolveScopeKeyCaseInsensitive(tree, "unknown")).toBeUndefined();
|
||||
expect(resolveScopeKeyCaseInsensitive(tree, undefined)).toBeUndefined();
|
||||
expect(resolveScopeKeyCaseInsensitive(tree, null)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveScopeRequireMention", () => {
|
||||
const scalarCases: Array<{
|
||||
name: string;
|
||||
|
||||
@@ -34,6 +34,30 @@ function applyWithApnChannelConfig(extra?: {
|
||||
});
|
||||
}
|
||||
|
||||
function materializeEnvCatalogCandidates(
|
||||
stateDir: string,
|
||||
candidates: Parameters<typeof materializePluginAutoEnableCandidates>[0]["candidates"] = [
|
||||
{ pluginId: "env-primary", kind: "channel-configured", channelId: "env-primary" },
|
||||
{ pluginId: "env-secondary", kind: "channel-configured", channelId: "env-secondary" },
|
||||
],
|
||||
) {
|
||||
return materializePluginAutoEnableCandidates({
|
||||
config: {
|
||||
channels: {
|
||||
"env-primary": { token: "primary" },
|
||||
"env-secondary": { token: "secondary" },
|
||||
},
|
||||
},
|
||||
candidates,
|
||||
env: {
|
||||
...makeIsolatedEnv(),
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
|
||||
},
|
||||
manifestRegistry: makeRegistry([]),
|
||||
});
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
resetPluginAutoEnableTestState();
|
||||
});
|
||||
@@ -73,32 +97,7 @@ describe("applyPluginAutoEnable channels", () => {
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
const result = materializePluginAutoEnableCandidates({
|
||||
config: {
|
||||
channels: {
|
||||
"env-primary": { token: "primary" },
|
||||
"env-secondary": { token: "secondary" },
|
||||
},
|
||||
},
|
||||
candidates: [
|
||||
{
|
||||
pluginId: "env-primary",
|
||||
kind: "channel-configured",
|
||||
channelId: "env-primary",
|
||||
},
|
||||
{
|
||||
pluginId: "env-secondary",
|
||||
kind: "channel-configured",
|
||||
channelId: "env-secondary",
|
||||
},
|
||||
],
|
||||
env: {
|
||||
...makeIsolatedEnv(),
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
|
||||
},
|
||||
manifestRegistry: makeRegistry([]),
|
||||
});
|
||||
const result = materializeEnvCatalogCandidates(stateDir);
|
||||
|
||||
expect(result.config.plugins?.entries?.["env-secondary"]?.enabled).toBe(true);
|
||||
expect(result.config.plugins?.entries?.["env-primary"]).toBeUndefined();
|
||||
@@ -151,25 +150,14 @@ describe("applyPluginAutoEnable channels", () => {
|
||||
const realpathSpy = vi.spyOn(fs, "realpathSync");
|
||||
|
||||
try {
|
||||
materializePluginAutoEnableCandidates({
|
||||
config: {
|
||||
channels: {
|
||||
"env-primary": { token: "primary" },
|
||||
"env-secondary": { token: "secondary" },
|
||||
},
|
||||
},
|
||||
candidates: Array.from({ length: 20 }, (_, index) => ({
|
||||
materializeEnvCatalogCandidates(
|
||||
stateDir,
|
||||
Array.from({ length: 20 }, (_, index) => ({
|
||||
pluginId: index % 2 === 0 ? "env-primary" : "env-secondary",
|
||||
kind: "channel-configured" as const,
|
||||
channelId: index % 2 === 0 ? "env-primary" : "env-secondary",
|
||||
})),
|
||||
env: {
|
||||
...makeIsolatedEnv(),
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
|
||||
},
|
||||
manifestRegistry: makeRegistry([]),
|
||||
});
|
||||
);
|
||||
|
||||
expect(
|
||||
realpathSpy.mock.calls.filter(([filePath]) =>
|
||||
@@ -211,32 +199,7 @@ describe("applyPluginAutoEnable channels", () => {
|
||||
const catalogPath = path.join(pluginsDir, "catalog.json");
|
||||
fs.symlinkSync(realPath, catalogPath);
|
||||
|
||||
const result = materializePluginAutoEnableCandidates({
|
||||
config: {
|
||||
channels: {
|
||||
"env-primary": { token: "primary" },
|
||||
"env-secondary": { token: "secondary" },
|
||||
},
|
||||
},
|
||||
candidates: [
|
||||
{
|
||||
pluginId: "env-primary",
|
||||
kind: "channel-configured" as const,
|
||||
channelId: "env-primary",
|
||||
},
|
||||
{
|
||||
pluginId: "env-secondary",
|
||||
kind: "channel-configured" as const,
|
||||
channelId: "env-secondary",
|
||||
},
|
||||
],
|
||||
env: {
|
||||
...makeIsolatedEnv(),
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
|
||||
},
|
||||
manifestRegistry: makeRegistry([]),
|
||||
});
|
||||
const result = materializeEnvCatalogCandidates(stateDir);
|
||||
|
||||
expect(result.config.plugins?.entries?.["env-secondary"]?.enabled).toBe(true);
|
||||
expect(result.config.plugins?.entries?.["env-primary"]).toBeUndefined();
|
||||
@@ -257,32 +220,7 @@ describe("applyPluginAutoEnable channels", () => {
|
||||
fs.closeSync(fd);
|
||||
}
|
||||
|
||||
const result = materializePluginAutoEnableCandidates({
|
||||
config: {
|
||||
channels: {
|
||||
"env-primary": { token: "primary" },
|
||||
"env-secondary": { token: "secondary" },
|
||||
},
|
||||
},
|
||||
candidates: [
|
||||
{
|
||||
pluginId: "env-primary",
|
||||
kind: "channel-configured" as const,
|
||||
channelId: "env-primary",
|
||||
},
|
||||
{
|
||||
pluginId: "env-secondary",
|
||||
kind: "channel-configured" as const,
|
||||
channelId: "env-secondary",
|
||||
},
|
||||
],
|
||||
env: {
|
||||
...makeIsolatedEnv(),
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_BUNDLED_PLUGINS_DIR: "/nonexistent/bundled/plugins",
|
||||
},
|
||||
manifestRegistry: makeRegistry([]),
|
||||
});
|
||||
const result = materializeEnvCatalogCandidates(stateDir);
|
||||
|
||||
// Selection continues: env-secondary is still auto-enabled.
|
||||
expect(result.config.plugins?.entries?.["env-secondary"]?.enabled).toBe(true);
|
||||
|
||||
@@ -4,6 +4,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import {
|
||||
decodeSessionArchiveBytes,
|
||||
encodeSessionArchiveContent,
|
||||
materializeSessionArchiveForRead,
|
||||
readSessionArchiveContentSync,
|
||||
@@ -30,6 +31,31 @@ function makeTempDir(): string {
|
||||
}
|
||||
|
||||
describe("archive compression", () => {
|
||||
it("decodes both plain bytes and runtime-supported compressed archive bytes", () => {
|
||||
const content = `${JSON.stringify({ type: "message", body: "archive round trip" })}\n`;
|
||||
const encoded = encodeSessionArchiveContent(content);
|
||||
|
||||
expect(decodeSessionArchiveBytes(Buffer.from(content, "utf8"), false)).toBe(content);
|
||||
expect(decodeSessionArchiveBytes(encoded.bytes, encoded.suffix !== "")).toBe(content);
|
||||
});
|
||||
|
||||
it("invalidates a materialized cache when its source archive is removed", () => {
|
||||
const encoded = encodeSessionArchiveContent("cached archive contents\n");
|
||||
if (encoded.suffix !== SESSION_ARCHIVE_ZSTD_SUFFIX) {
|
||||
return;
|
||||
}
|
||||
const dir = makeTempDir();
|
||||
const archivePath = path.join(dir, `removed.jsonl.deleted.2026-07-11${encoded.suffix}`);
|
||||
fs.writeFileSync(archivePath, encoded.bytes);
|
||||
const cachePath = materializeSessionArchiveForRead(archivePath);
|
||||
expect(fs.existsSync(cachePath)).toBe(true);
|
||||
|
||||
fs.rmSync(archivePath);
|
||||
|
||||
expect(() => materializeSessionArchiveForRead(archivePath)).toThrow();
|
||||
expect(fs.existsSync(cachePath)).toBe(false);
|
||||
});
|
||||
|
||||
it("round-trips archived transcript content through encode and read", () => {
|
||||
const content = `${JSON.stringify({ type: "message", body: "hello" })}\n`.repeat(200);
|
||||
const encoded = encodeSessionArchiveContent(content);
|
||||
|
||||
@@ -71,6 +71,14 @@ const buildEntry = (deliveryContext: DeliveryContext): SessionEntryFixture => ({
|
||||
deliveryContext,
|
||||
});
|
||||
|
||||
function createMixedCaseMatrixDelivery(): DeliveryContext {
|
||||
return { channel: "matrix", to: "room:!MixedCase:Example.Org", accountId: "matrix-account" };
|
||||
}
|
||||
|
||||
function createTelegramUserDelivery(): DeliveryContext {
|
||||
return { channel: "telegram", to: "telegram:user-123", accountId: "default" };
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
({ extractDeliveryInfo } = await import("./delivery-info.js"));
|
||||
});
|
||||
@@ -121,11 +129,7 @@ describe("extractDeliveryInfo", () => {
|
||||
|
||||
it("reads borrowed accessor views for direct session keys", () => {
|
||||
const sessionKey = "agent:main:telegram:dm:user-123";
|
||||
storeState.store[sessionKey] = buildEntry({
|
||||
channel: "telegram",
|
||||
to: "telegram:user-123",
|
||||
accountId: "default",
|
||||
});
|
||||
storeState.store[sessionKey] = buildEntry(createTelegramUserDelivery());
|
||||
|
||||
const result = extractDeliveryInfo(sessionKey);
|
||||
|
||||
@@ -142,11 +146,7 @@ describe("extractDeliveryInfo", () => {
|
||||
// extractDeliveryInfo would return no delivery context.
|
||||
storeState.store = new Proxy(
|
||||
{
|
||||
[sessionKey]: buildEntry({
|
||||
channel: "telegram",
|
||||
to: "telegram:user-123",
|
||||
accountId: "default",
|
||||
}),
|
||||
[sessionKey]: buildEntry(createTelegramUserDelivery()),
|
||||
},
|
||||
{
|
||||
ownKeys() {
|
||||
@@ -158,31 +158,19 @@ describe("extractDeliveryInfo", () => {
|
||||
const result = extractDeliveryInfo(sessionKey);
|
||||
|
||||
expect(result).toEqual({
|
||||
deliveryContext: {
|
||||
channel: "telegram",
|
||||
to: "telegram:user-123",
|
||||
accountId: "default",
|
||||
},
|
||||
deliveryContext: createTelegramUserDelivery(),
|
||||
threadId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
it("returns deliveryContext for direct session keys", () => {
|
||||
const sessionKey = "agent:main:telegram:dm:user-123";
|
||||
storeState.store[sessionKey] = buildEntry({
|
||||
channel: "telegram",
|
||||
to: "telegram:user-123",
|
||||
accountId: "default",
|
||||
});
|
||||
storeState.store[sessionKey] = buildEntry(createTelegramUserDelivery());
|
||||
|
||||
const result = extractDeliveryInfo(sessionKey);
|
||||
|
||||
expect(result).toEqual({
|
||||
deliveryContext: {
|
||||
channel: "telegram",
|
||||
to: "telegram:user-123",
|
||||
accountId: "default",
|
||||
},
|
||||
deliveryContext: createTelegramUserDelivery(),
|
||||
threadId: undefined,
|
||||
});
|
||||
});
|
||||
@@ -366,11 +354,7 @@ describe("extractDeliveryInfo", () => {
|
||||
storeState.store[sessionKey] = {
|
||||
sessionId: "direct-routable-session",
|
||||
updatedAt: Date.now() - 1_000,
|
||||
deliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "room:!MixedCase:Example.Org",
|
||||
accountId: "matrix-account",
|
||||
},
|
||||
deliveryContext: createMixedCaseMatrixDelivery(),
|
||||
};
|
||||
storeState.store[canonicalKey] = {
|
||||
sessionId: "fresh-normalized-session",
|
||||
@@ -383,11 +367,7 @@ describe("extractDeliveryInfo", () => {
|
||||
const result = extractDeliveryInfo(sessionKey);
|
||||
|
||||
expect(result).toEqual({
|
||||
deliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "room:!MixedCase:Example.Org",
|
||||
accountId: "matrix-account",
|
||||
},
|
||||
deliveryContext: createMixedCaseMatrixDelivery(),
|
||||
threadId: undefined,
|
||||
});
|
||||
});
|
||||
@@ -492,11 +472,7 @@ describe("extractDeliveryInfo", () => {
|
||||
storeState.store[queriedKey] = {
|
||||
sessionId: "exact-mixedcase-session",
|
||||
updatedAt: Date.now() - 1_000,
|
||||
deliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "room:!MixedCase:Example.Org",
|
||||
accountId: "matrix-account",
|
||||
},
|
||||
deliveryContext: createMixedCaseMatrixDelivery(),
|
||||
};
|
||||
storeState.store[legacyFoldedKey] = {
|
||||
sessionId: "fresher-legacy-folded-session",
|
||||
@@ -511,11 +487,7 @@ describe("extractDeliveryInfo", () => {
|
||||
const result = extractDeliveryInfo(queriedKey);
|
||||
|
||||
expect(result).toEqual({
|
||||
deliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "room:!MixedCase:Example.Org",
|
||||
accountId: "matrix-account",
|
||||
},
|
||||
deliveryContext: createMixedCaseMatrixDelivery(),
|
||||
threadId: undefined,
|
||||
});
|
||||
});
|
||||
@@ -569,11 +541,7 @@ describe("extractDeliveryInfo", () => {
|
||||
it("does not return a mixed-case Matrix sibling for a lowercase room query", () => {
|
||||
const queriedKey = "agent:main:matrix:channel:!mixedcase:example.org";
|
||||
const mixedSiblingKey = "agent:main:matrix:channel:!MixedCase:Example.Org";
|
||||
storeState.store[mixedSiblingKey] = buildEntry({
|
||||
channel: "matrix",
|
||||
to: "room:!MixedCase:Example.Org",
|
||||
accountId: "matrix-account",
|
||||
});
|
||||
storeState.store[mixedSiblingKey] = buildEntry(createMixedCaseMatrixDelivery());
|
||||
|
||||
const result = extractDeliveryInfo(queriedKey);
|
||||
|
||||
@@ -585,11 +553,7 @@ describe("extractDeliveryInfo", () => {
|
||||
|
||||
it("does not return an exact lowercase Matrix key with mixed-case delivery metadata", () => {
|
||||
const queriedKey = "agent:main:matrix:channel:!mixedcase:example.org";
|
||||
storeState.store[queriedKey] = buildEntry({
|
||||
channel: "matrix",
|
||||
to: "room:!MixedCase:Example.Org",
|
||||
accountId: "matrix-account",
|
||||
});
|
||||
storeState.store[queriedKey] = buildEntry(createMixedCaseMatrixDelivery());
|
||||
|
||||
const result = extractDeliveryInfo(queriedKey);
|
||||
|
||||
@@ -602,20 +566,12 @@ describe("extractDeliveryInfo", () => {
|
||||
it("returns a confirmed lowercased Matrix legacy artifact for a mixed-case key", () => {
|
||||
const queriedKey = "agent:main:matrix:channel:!MixedCase:Example.Org";
|
||||
const legacyArtifactKey = "agent:main:matrix:channel:!mixedcase:example.org";
|
||||
storeState.store[legacyArtifactKey] = buildEntry({
|
||||
channel: "matrix",
|
||||
to: "room:!MixedCase:Example.Org",
|
||||
accountId: "matrix-account",
|
||||
});
|
||||
storeState.store[legacyArtifactKey] = buildEntry(createMixedCaseMatrixDelivery());
|
||||
|
||||
const result = extractDeliveryInfo(queriedKey);
|
||||
|
||||
expect(result).toEqual({
|
||||
deliveryContext: {
|
||||
channel: "matrix",
|
||||
to: "room:!MixedCase:Example.Org",
|
||||
accountId: "matrix-account",
|
||||
},
|
||||
deliveryContext: createMixedCaseMatrixDelivery(),
|
||||
threadId: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -368,61 +368,47 @@ describe("applyFileBackedSessionStoreMaintenance", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves every active admission instead of only the writer session", async () => {
|
||||
it.each([
|
||||
{
|
||||
name: "preserves every active admission instead of only the writer session",
|
||||
storeName: "active-admissions",
|
||||
preserved: [
|
||||
["agent:main:cron:job:run:active", "active-session"],
|
||||
["writer", "writer-session"],
|
||||
],
|
||||
identities: ["agent:main:cron:job:run:active", "active-session"],
|
||||
activeSessionKey: "writer",
|
||||
},
|
||||
{
|
||||
name: "preserves every store alias backed by an active session id",
|
||||
storeName: "active-aliases",
|
||||
preserved: [
|
||||
["agent:main:cron:job:run:active", "active-alias-session"],
|
||||
["agent:main:cron:job:run:active:thread:reply", "active-alias-session"],
|
||||
],
|
||||
identities: ["active-alias-session"],
|
||||
activeSessionKey: undefined,
|
||||
},
|
||||
{
|
||||
name: "preserves a raw legacy store key matched by a canonical admission identity",
|
||||
storeName: "active-legacy-key",
|
||||
preserved: [["Agent:Main:Subagent:CHILD", "active-legacy-session"]],
|
||||
identities: ["agent:main:subagent:child"],
|
||||
activeSessionKey: undefined,
|
||||
},
|
||||
] as const)("$name", async ({ storeName, preserved, identities, activeSessionKey }) => {
|
||||
const now = Date.now();
|
||||
const storePath = "/tmp/openclaw-sessions/active-admissions.json";
|
||||
const activeKey = "agent:main:cron:job:run:active";
|
||||
const storePath = `/tmp/openclaw-sessions/${storeName}.json`;
|
||||
const store = makeStore([
|
||||
[activeKey, { sessionId: "active-session", updatedAt: now - 3 }],
|
||||
["removable", { sessionId: "removable-session", updatedAt: now - 2 }],
|
||||
["writer", { sessionId: "writer-session", updatedAt: now - 1 }],
|
||||
]);
|
||||
const admission = await beginSessionWorkAdmission({
|
||||
scope: storePath,
|
||||
identities: [activeKey, "active-session"],
|
||||
assertAllowed: () => {},
|
||||
});
|
||||
|
||||
try {
|
||||
await applyFileBackedSessionStoreMaintenance({
|
||||
storePath,
|
||||
store,
|
||||
activeSessionKey: "writer",
|
||||
maintenanceConfig: {
|
||||
mode: "enforce",
|
||||
pruneAfterMs: 30 * DAY_MS,
|
||||
maxEntries: 1,
|
||||
modelRunPruneAfterMs: DAY_MS,
|
||||
resetArchiveRetentionMs: null,
|
||||
maxDiskBytes: null,
|
||||
highWaterBytes: null,
|
||||
},
|
||||
log: { warn: () => {}, info: () => {} },
|
||||
artifacts: createMaintenanceArtifacts(),
|
||||
});
|
||||
|
||||
expect(store).toHaveProperty(activeKey);
|
||||
expect(store).toHaveProperty("writer");
|
||||
expect(store.removable).toBeUndefined();
|
||||
} finally {
|
||||
admission.release();
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves every store alias backed by an active session id", async () => {
|
||||
const now = Date.now();
|
||||
const storePath = "/tmp/openclaw-sessions/active-aliases.json";
|
||||
const activeSessionId = "active-alias-session";
|
||||
const firstAlias = "agent:main:cron:job:run:active";
|
||||
const secondAlias = "agent:main:cron:job:run:active:thread:reply";
|
||||
const store = makeStore([
|
||||
[firstAlias, { sessionId: activeSessionId, updatedAt: now - 3 }],
|
||||
[secondAlias, { sessionId: activeSessionId, updatedAt: now - 2 }],
|
||||
...preserved.map(([key, sessionId], index): [string, SessionEntry] => [
|
||||
key,
|
||||
{ sessionId, updatedAt: now - preserved.length - 1 + index },
|
||||
]),
|
||||
["removable", { sessionId: "removable-session", updatedAt: now - 1 }],
|
||||
]);
|
||||
const admission = await beginSessionWorkAdmission({
|
||||
scope: storePath,
|
||||
identities: [activeSessionId],
|
||||
identities: [...identities],
|
||||
assertAllowed: () => {},
|
||||
});
|
||||
|
||||
@@ -430,6 +416,7 @@ describe("applyFileBackedSessionStoreMaintenance", () => {
|
||||
await applyFileBackedSessionStoreMaintenance({
|
||||
storePath,
|
||||
store,
|
||||
activeSessionKey,
|
||||
maintenanceConfig: {
|
||||
mode: "enforce",
|
||||
pruneAfterMs: 30 * DAY_MS,
|
||||
@@ -442,48 +429,9 @@ describe("applyFileBackedSessionStoreMaintenance", () => {
|
||||
log: { warn: () => {}, info: () => {} },
|
||||
artifacts: createMaintenanceArtifacts(),
|
||||
});
|
||||
|
||||
expect(store).toHaveProperty(firstAlias);
|
||||
expect(store).toHaveProperty(secondAlias);
|
||||
expect(store.removable).toBeUndefined();
|
||||
} finally {
|
||||
admission.release();
|
||||
}
|
||||
});
|
||||
|
||||
it("preserves a raw legacy store key matched by a canonical admission identity", async () => {
|
||||
const now = Date.now();
|
||||
const storePath = "/tmp/openclaw-sessions/active-legacy-key.json";
|
||||
const rawActiveKey = "Agent:Main:Subagent:CHILD";
|
||||
const canonicalActiveKey = "agent:main:subagent:child";
|
||||
const store = makeStore([
|
||||
[rawActiveKey, { sessionId: "active-legacy-session", updatedAt: now - 2 }],
|
||||
["removable", { sessionId: "removable-session", updatedAt: now - 1 }],
|
||||
]);
|
||||
const admission = await beginSessionWorkAdmission({
|
||||
scope: storePath,
|
||||
identities: [canonicalActiveKey],
|
||||
assertAllowed: () => {},
|
||||
});
|
||||
|
||||
try {
|
||||
await applyFileBackedSessionStoreMaintenance({
|
||||
storePath,
|
||||
store,
|
||||
maintenanceConfig: {
|
||||
mode: "enforce",
|
||||
pruneAfterMs: 30 * DAY_MS,
|
||||
maxEntries: 1,
|
||||
modelRunPruneAfterMs: DAY_MS,
|
||||
resetArchiveRetentionMs: null,
|
||||
maxDiskBytes: null,
|
||||
highWaterBytes: null,
|
||||
},
|
||||
log: { warn: () => {}, info: () => {} },
|
||||
artifacts: createMaintenanceArtifacts(),
|
||||
});
|
||||
|
||||
expect(store).toHaveProperty(rawActiveKey);
|
||||
for (const [key] of preserved) {
|
||||
expect(store).toHaveProperty(key);
|
||||
}
|
||||
expect(store.removable).toBeUndefined();
|
||||
} finally {
|
||||
admission.release();
|
||||
|
||||
@@ -852,16 +852,27 @@ describe("validateConfigObjectRawWithPlugins channel metadata", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("names the external plugin owner for unsupported channel properties", () => {
|
||||
mockLoadPluginManifestRegistry.mockReturnValue(createExternalFeishuSchemaRegistry());
|
||||
|
||||
it.each([
|
||||
{
|
||||
name: "names the external plugin owner for unsupported channel properties",
|
||||
createRegistry: createExternalFeishuSchemaRegistry,
|
||||
rejectedOwner: undefined,
|
||||
},
|
||||
{
|
||||
name: "keeps unsupported property diagnostics assigned to the schema owner",
|
||||
createRegistry: createExternalFeishuSchemaWithCloserMetadataRegistry,
|
||||
rejectedOwner: "workspace-channel-labels",
|
||||
},
|
||||
{
|
||||
name: "keeps schema ownership coupled when closer root metadata preserves a schema",
|
||||
createRegistry: createExternalFeishuSchemaWithRootOnlyShadowRegistry,
|
||||
rejectedOwner: "other-global-feishu",
|
||||
},
|
||||
] as const)("$name", ({ createRegistry, rejectedOwner }) => {
|
||||
mockLoadPluginManifestRegistry.mockReturnValue(createRegistry());
|
||||
const result = validateConfigObjectRawWithPlugins({
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app-id",
|
||||
appSecret: "secret",
|
||||
unsupportedField: true,
|
||||
},
|
||||
feishu: { appId: "app-id", appSecret: "secret", unsupportedField: true },
|
||||
},
|
||||
});
|
||||
|
||||
@@ -874,66 +885,11 @@ describe("validateConfigObjectRawWithPlugins channel metadata", () => {
|
||||
'invalid config for plugin openclaw-lark: must not have additional properties: "unsupportedField"',
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps unsupported property diagnostics assigned to the schema owner", () => {
|
||||
mockLoadPluginManifestRegistry.mockReturnValue(
|
||||
createExternalFeishuSchemaWithCloserMetadataRegistry(),
|
||||
);
|
||||
|
||||
const result = validateConfigObjectRawWithPlugins({
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app-id",
|
||||
appSecret: "secret",
|
||||
unsupportedField: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: "channels.feishu",
|
||||
message:
|
||||
'invalid config for plugin openclaw-lark: must not have additional properties: "unsupportedField"',
|
||||
}),
|
||||
);
|
||||
expect(result.issues.map((issue) => issue.message)).not.toContain(
|
||||
'invalid config for plugin workspace-channel-labels: must not have additional properties: "unsupportedField"',
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps schema ownership coupled when closer root metadata preserves a schema", () => {
|
||||
mockLoadPluginManifestRegistry.mockReturnValue(
|
||||
createExternalFeishuSchemaWithRootOnlyShadowRegistry(),
|
||||
);
|
||||
|
||||
const result = validateConfigObjectRawWithPlugins({
|
||||
channels: {
|
||||
feishu: {
|
||||
appId: "app-id",
|
||||
appSecret: "secret",
|
||||
unsupportedField: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.ok).toBe(false);
|
||||
if (!result.ok) {
|
||||
expect(result.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: "channels.feishu",
|
||||
message:
|
||||
'invalid config for plugin openclaw-lark: must not have additional properties: "unsupportedField"',
|
||||
}),
|
||||
);
|
||||
expect(result.issues.map((issue) => issue.message)).not.toContain(
|
||||
'invalid config for plugin other-global-feishu: must not have additional properties: "unsupportedField"',
|
||||
);
|
||||
if (rejectedOwner) {
|
||||
expect(result.issues.map((issue) => issue.message)).not.toContain(
|
||||
`invalid config for plugin ${rejectedOwner}: must not have additional properties: "unsupportedField"`,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user