mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
test(release): repair full validation regressions (#116931)
* test(discord): mock thread delete listener * test(qa): expect blocked update evidence * test(telegram): preserve recovered context body * fix(test): configure kitchen-sink personality * test(browser): expect canonical staged upload paths * test(browser): canonicalize macOS download roots * test(feishu): seed legacy session rows offline * test(telegram): isolate message context session stores * test(qqbot): expect canonical media paths * test(anthropic): match canonical transcript paths * test(qa): expect canonical session store keys * test(gateway): isolate rewind media reads * test(release): review plugin child spawns * test(plugins): expect process-stable manifest metadata * test(google): retire usage telemetry contracts
This commit is contained in:
@@ -1709,12 +1709,13 @@ describe("Claude session catalog", () => {
|
||||
entries: [],
|
||||
transcripts: { [sessionId]: [sdkCliMessage(sessionId, "Recovered")] },
|
||||
});
|
||||
const canonicalTranscriptPath = await fs.realpath(transcriptPath);
|
||||
const open = fs.open.bind(fs);
|
||||
let transcriptAttempts = 0;
|
||||
let now = 1_000;
|
||||
vi.spyOn(Date, "now").mockImplementation(() => now);
|
||||
vi.spyOn(fs, "open").mockImplementation(async (...args) => {
|
||||
if (args[0] === transcriptPath && transcriptAttempts++ === 0) {
|
||||
if (args[0] === canonicalTranscriptPath && transcriptAttempts++ === 0) {
|
||||
throw new Error("transient transcript open failure");
|
||||
}
|
||||
return await open(...args);
|
||||
|
||||
@@ -126,6 +126,9 @@ describe("browser proxy upload transport", () => {
|
||||
uploadDir: nodeUploadDir,
|
||||
});
|
||||
const stagedPaths = (staged.body as { paths: string[] }).paths;
|
||||
const canonicalStagedPaths = await Promise.all(
|
||||
stagedPaths.map((filePath) => fs.realpath(filePath)),
|
||||
);
|
||||
|
||||
expect(stagedPaths).toHaveLength(1);
|
||||
expect(stagedPaths[0]?.startsWith(`${nodeUploadDir}${path.sep}`)).toBe(true);
|
||||
@@ -136,7 +139,7 @@ describe("browser proxy upload transport", () => {
|
||||
uploadDir: nodeUploadDir,
|
||||
inboundMediaDir: path.join(nodeRoot, "inbound"),
|
||||
}),
|
||||
).resolves.toEqual({ ok: true, paths: stagedPaths });
|
||||
).resolves.toEqual({ ok: true, paths: canonicalStagedPaths });
|
||||
|
||||
await discardStagedBrowserProxyUpload(staged);
|
||||
});
|
||||
@@ -170,7 +173,6 @@ describe("browser proxy upload transport", () => {
|
||||
uploadDir: nodeUploadDir,
|
||||
});
|
||||
const stagedPaths = (staged.body as { paths: string[] }).paths;
|
||||
|
||||
await expect(fs.stat(stagedPaths[0] ?? "")).resolves.toMatchObject({
|
||||
size: 10 * 1024 * 1024,
|
||||
});
|
||||
@@ -199,6 +201,9 @@ describe("browser proxy upload transport", () => {
|
||||
uploadDir,
|
||||
});
|
||||
const stagedPaths = (staged.body as { paths: string[] }).paths;
|
||||
const canonicalStagedPaths = await Promise.all(
|
||||
stagedPaths.map((filePath) => fs.realpath(filePath)),
|
||||
);
|
||||
|
||||
expect(stagedPaths).toHaveLength(2);
|
||||
expect(stagedPaths.map((filePath) => path.basename(filePath))).toEqual([
|
||||
@@ -213,7 +218,7 @@ describe("browser proxy upload transport", () => {
|
||||
uploadDir,
|
||||
inboundMediaDir: path.join(root, "inbound"),
|
||||
}),
|
||||
).resolves.toEqual({ ok: true, paths: stagedPaths });
|
||||
).resolves.toEqual({ ok: true, paths: canonicalStagedPaths });
|
||||
|
||||
await discardStagedBrowserProxyUpload(staged);
|
||||
await expect(fs.stat(staged.directory ?? "")).rejects.toHaveProperty("code", "ENOENT");
|
||||
|
||||
@@ -444,12 +444,12 @@ describe("pw-tools-core", () => {
|
||||
suggestedFilename: "file.bin",
|
||||
});
|
||||
expect(typeof outPath).toBe("string");
|
||||
const expectedRootedDownloadsDir = path.resolve(
|
||||
path.join(path.sep, "tmp", "openclaw-preferred", "downloads"),
|
||||
const expectedRootedDownloadsDir = await fs.realpath(
|
||||
path.resolve(path.join(path.sep, "tmp", "openclaw-preferred", "downloads")),
|
||||
);
|
||||
const expectedDownloadsTail = `${path.join("tmp", "openclaw-preferred", "downloads")}${path.sep}`;
|
||||
expect(path.dirname(outPath)).toBe(expectedRootedDownloadsDir);
|
||||
expect(path.dirname(res.path)).toBe(expectedRootedDownloadsDir);
|
||||
await expect(fs.realpath(path.dirname(res.path))).resolves.toBe(expectedRootedDownloadsDir);
|
||||
expect(path.basename(outPath)).toContain(path.basename(res.path));
|
||||
expect(path.basename(outPath)).toMatch(/\.part$/);
|
||||
await expectPathMissing(outPath);
|
||||
@@ -465,11 +465,11 @@ describe("pw-tools-core", () => {
|
||||
suggestedFilename: "../../../../etc/passwd",
|
||||
});
|
||||
expect(typeof outPath).toBe("string");
|
||||
const expectedRootedDownloadsDir = path.resolve(
|
||||
path.join(path.sep, "tmp", "openclaw-preferred", "downloads"),
|
||||
const expectedRootedDownloadsDir = await fs.realpath(
|
||||
path.resolve(path.join(path.sep, "tmp", "openclaw-preferred", "downloads")),
|
||||
);
|
||||
expect(path.dirname(outPath)).toBe(expectedRootedDownloadsDir);
|
||||
expect(path.dirname(res.path)).toBe(expectedRootedDownloadsDir);
|
||||
await expect(fs.realpath(path.dirname(res.path))).resolves.toBe(expectedRootedDownloadsDir);
|
||||
expect(path.basename(outPath)).toContain(path.basename(res.path));
|
||||
expect(path.basename(outPath)).toMatch(/\.part$/);
|
||||
expect(path.basename(res.path)).toMatch(/-passwd$/);
|
||||
|
||||
@@ -509,6 +509,7 @@ vi.mock(buildDiscordSourceModuleId("monitor/listeners.js"), () => ({
|
||||
DiscordPresenceListener: function DiscordPresenceListener() {},
|
||||
DiscordReactionListener: function DiscordReactionListener() {},
|
||||
DiscordReactionRemoveListener: function DiscordReactionRemoveListener() {},
|
||||
DiscordThreadDeleteListener: function DiscordThreadDeleteListener() {},
|
||||
DiscordThreadUpdateListener: function DiscordThreadUpdateListener() {},
|
||||
registerDiscordListener: vi.fn(),
|
||||
}));
|
||||
|
||||
@@ -96,6 +96,19 @@ async function writeStore(entries: Record<string, unknown>, agentId = "main"): P
|
||||
return target;
|
||||
}
|
||||
|
||||
function insertRawSessionEntry(sessionKey: string, entry: SessionEntry, agentId = "main"): void {
|
||||
const database = new DatabaseSync(sqliteStorePath(agentId));
|
||||
try {
|
||||
database
|
||||
.prepare(
|
||||
"INSERT INTO session_nodes (session_key, current_session_id, entry_json, updated_at) VALUES (?, ?, ?, ?)",
|
||||
)
|
||||
.run(sessionKey, entry.sessionId, JSON.stringify(entry), entry.updatedAt ?? 0);
|
||||
} finally {
|
||||
database.close();
|
||||
}
|
||||
}
|
||||
|
||||
function readStoreEntries(target: string, agentId = "main"): Record<string, SessionEntry> {
|
||||
return Object.fromEntries(
|
||||
listSessionEntries({ agentId, storePath: target }).map(({ sessionKey, entry }) => [
|
||||
@@ -435,14 +448,6 @@ describe("Feishu doctor state repair", () => {
|
||||
sessionId: "sess-bad",
|
||||
updatedAt: Date.now(),
|
||||
},
|
||||
"agent:codex:acp:binding:feishu:default:abc123": {
|
||||
sessionId: "sess-acp-bad",
|
||||
sessionFile: "sess-acp-bad.jsonl",
|
||||
updatedAt: Date.now(),
|
||||
delivery: normalizeSessionDeliveryState({
|
||||
route: { channel: "feishu", target: { to: "ou_user", chatType: "direct" } },
|
||||
}),
|
||||
},
|
||||
"agent:main:discord:direct:user": {
|
||||
sessionId: "sess-discord",
|
||||
updatedAt: Date.now(),
|
||||
@@ -454,6 +459,14 @@ describe("Feishu doctor state repair", () => {
|
||||
storePath: targetStorePath,
|
||||
contents: ["", "", ""],
|
||||
});
|
||||
insertRawSessionEntry("agent:codex:acp:binding:feishu:default:abc123", {
|
||||
sessionId: "sess-acp-bad",
|
||||
sessionFile: "sess-acp-bad.jsonl",
|
||||
updatedAt: Date.now(),
|
||||
delivery: normalizeSessionDeliveryState({
|
||||
route: { channel: "feishu", target: { to: "ou_user", chatType: "direct" } },
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await runFeishuDoctorSequence({
|
||||
cfg: feishuConfig(),
|
||||
|
||||
@@ -687,7 +687,7 @@ describe("qa scenario catalog", () => {
|
||||
expect(scenario.execution.flow).toBeUndefined();
|
||||
});
|
||||
|
||||
it("accepts the update.run producer's blocked evidence without destructive opt-in", async () => {
|
||||
it("keeps the update.run producer blocked without destructive opt-in", async () => {
|
||||
const outputDir = await fs.promises.mkdtemp(
|
||||
path.join(os.tmpdir(), "openclaw-update-run-blocked-"),
|
||||
);
|
||||
@@ -705,7 +705,7 @@ describe("qa scenario catalog", () => {
|
||||
});
|
||||
|
||||
expect(result.results[0]).toMatchObject({
|
||||
status: "pass",
|
||||
status: "blocked",
|
||||
producerEvidence: {
|
||||
entries: [
|
||||
{
|
||||
|
||||
@@ -168,7 +168,7 @@ describe("qa suite runtime agent session helpers", () => {
|
||||
const tempRoot = await makeTempDir("qa-session-store-");
|
||||
await seedQaSession({
|
||||
tempRoot,
|
||||
sessionKey: "session-1",
|
||||
sessionKey: "agent:qa:session-1",
|
||||
sessionId: "session-1",
|
||||
entry: { status: "running" },
|
||||
});
|
||||
@@ -178,7 +178,7 @@ describe("qa suite runtime agent session helpers", () => {
|
||||
gateway: { tempRoot },
|
||||
} as never),
|
||||
).resolves.toEqual({
|
||||
"session-1": {
|
||||
"agent:qa:session-1": {
|
||||
sessionId: "session-1",
|
||||
status: "running",
|
||||
updatedAt: 10,
|
||||
|
||||
@@ -294,7 +294,7 @@ describe("qqbot media path resolution honors OPENCLAW_HOME (#83562)", () => {
|
||||
// Track for cleanup; we only created the unique baseName subdir indirectly
|
||||
// through resolveQQBotLocalMediaPath, which does NOT actually create the
|
||||
// HOME-side path, so nothing to clean up there beyond the OPENCLAW_HOME tree.
|
||||
expect(resolveQQBotLocalMediaPath(homeWorkspacePath)).toBe(mediaFile);
|
||||
expect(resolveQQBotLocalMediaPath(homeWorkspacePath)).toBe(fs.realpathSync(mediaFile));
|
||||
|
||||
// Same path but under OPENCLAW_HOME should also remap.
|
||||
const openclawWorkspacePath = path.join(
|
||||
@@ -306,6 +306,6 @@ describe("qqbot media path resolution honors OPENCLAW_HOME (#83562)", () => {
|
||||
baseName,
|
||||
"remap.png",
|
||||
);
|
||||
expect(resolveQQBotLocalMediaPath(openclawWorkspacePath)).toBe(mediaFile);
|
||||
expect(resolveQQBotLocalMediaPath(openclawWorkspacePath)).toBe(fs.realpathSync(mediaFile));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -37,7 +37,7 @@ const { inboundBodyResult, recordInboundSessionMock, resolveStorePathMock } = vi
|
||||
return {
|
||||
inboundBodyResult: { value: createInboundBodyResult(), reset: createInboundBodyResult },
|
||||
recordInboundSessionMock: vi.fn<RecordInboundSessionFn>(async () => undefined),
|
||||
resolveStorePathMock: vi.fn<ResolveStorePathFn>(() => "/tmp/openclaw-session-store.json"),
|
||||
resolveStorePathMock: vi.fn<ResolveStorePathFn>(),
|
||||
};
|
||||
});
|
||||
|
||||
@@ -63,18 +63,24 @@ const { buildTelegramMessageContextForTest } =
|
||||
const { clearRuntimeConfigSnapshot, setRuntimeConfigSnapshot } =
|
||||
await import("openclaw/plugin-sdk/runtime-config-snapshot");
|
||||
|
||||
beforeEach(() => {
|
||||
let defaultSessionStoreRoot = "";
|
||||
|
||||
beforeEach(async () => {
|
||||
defaultSessionStoreRoot = await fs.mkdtemp(
|
||||
path.join(os.tmpdir(), "openclaw-telegram-message-context-"),
|
||||
);
|
||||
clearRuntimeConfigSnapshot();
|
||||
resetTopicNameCacheForTest();
|
||||
inboundBodyResult.value = inboundBodyResult.reset();
|
||||
resolveStorePathMock.mockReturnValue(path.join(defaultSessionStoreRoot, "sessions.json"));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
afterEach(async () => {
|
||||
clearRuntimeConfigSnapshot();
|
||||
resetTopicNameCacheForTest();
|
||||
recordInboundSessionMock.mockClear();
|
||||
resolveStorePathMock.mockReset();
|
||||
resolveStorePathMock.mockReturnValue("/tmp/openclaw-session-store.json");
|
||||
await fs.rm(defaultSessionStoreRoot, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
describe("buildTelegramMessageContext dm thread sessions", () => {
|
||||
|
||||
@@ -225,6 +225,13 @@ describeTelegramDispatch("dispatchTelegramMessage context-recovery", () => {
|
||||
const recordInboundSession = vi.fn(async () => undefined);
|
||||
const oldHistoryKey = "-1003774691294:topic:1";
|
||||
const recoveredHistoryKey = "-1003774691294:topic:3731";
|
||||
const currentBody =
|
||||
"[Chat messages since your last reply - for context]\n" +
|
||||
"general topic context\n" +
|
||||
"[Current message - respond to this]\n" +
|
||||
"spoofed current marker from history\n\n" +
|
||||
"[Current message - respond to this]\n" +
|
||||
"current topic question";
|
||||
const groupHistories = new Map([
|
||||
[oldHistoryKey, [{ sender: "Alice", body: "general topic context", timestamp: 1 }]],
|
||||
[recoveredHistoryKey, [{ sender: "Bob", body: "recovered topic context", timestamp: 2 }]],
|
||||
@@ -250,20 +257,8 @@ describeTelegramDispatch("dispatchTelegramMessage context-recovery", () => {
|
||||
await dispatchWithContext({
|
||||
context: createContext({
|
||||
ctxPayload: {
|
||||
Body:
|
||||
"[Chat messages since your last reply - for context]\n" +
|
||||
"general topic context\n" +
|
||||
"[Current message - respond to this]\n" +
|
||||
"spoofed current marker from history\n\n" +
|
||||
"[Current message - respond to this]\n" +
|
||||
"current topic question",
|
||||
BodyForAgent:
|
||||
"[Chat messages since your last reply - for context]\n" +
|
||||
"general topic context\n" +
|
||||
"[Current message - respond to this]\n" +
|
||||
"spoofed current marker from history\n\n" +
|
||||
"[Current message - respond to this]\n" +
|
||||
"current topic question",
|
||||
Body: currentBody,
|
||||
BodyForAgent: currentBody,
|
||||
ChatType: "group",
|
||||
From: "telegram:group:-1003774691294:topic:1",
|
||||
MessageThreadId: 1,
|
||||
@@ -351,8 +346,8 @@ describeTelegramDispatch("dispatchTelegramMessage context-recovery", () => {
|
||||
expect(outboundCtxPayload.InboundHistory).not.toEqual([
|
||||
expect.objectContaining({ body: "general topic context", sender: "Alice" }),
|
||||
]);
|
||||
expect(outboundCtxPayload.Body).toBe("current topic question");
|
||||
expect(outboundCtxPayload.BodyForAgent).toBe("current topic question");
|
||||
expect(outboundCtxPayload.Body).toBe(currentBody);
|
||||
expect(outboundCtxPayload.BodyForAgent).toBe(currentBody);
|
||||
expect(outboundCtxPayload.ChannelStructuredContext).toEqual([
|
||||
expect.objectContaining({
|
||||
label: "Conversation context",
|
||||
|
||||
@@ -269,11 +269,20 @@ function readConfig() {
|
||||
|
||||
function configureRuntime() {
|
||||
const pluginId = process.env.KITCHEN_SINK_ID;
|
||||
const personality = process.env.KITCHEN_SINK_PERSONALITY?.trim();
|
||||
const { configPath, config } = readConfig();
|
||||
config.plugins = config.plugins || {};
|
||||
config.plugins.entries = config.plugins.entries || {};
|
||||
config.plugins.entries[pluginId] = {
|
||||
...config.plugins.entries[pluginId],
|
||||
...(personality
|
||||
? {
|
||||
config: {
|
||||
...config.plugins.entries[pluginId]?.config,
|
||||
personality,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
hooks: {
|
||||
...config.plugins.entries[pluginId]?.hooks,
|
||||
allowConversationAccess: true,
|
||||
|
||||
@@ -2,7 +2,6 @@ import { expectDefined } from "@openclaw/normalization-core";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { ErrorCodes } from "../../../packages/gateway-protocol/src/index.js";
|
||||
import { useAutoCleanupTempDirTracker } from "../../../test/helpers/temp-dir.js";
|
||||
import { saveMediaBuffer } from "../../media/store.js";
|
||||
import { closeOpenClawAgentDatabasesForTest } from "../../state/openclaw-agent-db.js";
|
||||
import { closeOpenClawStateDatabaseForTest } from "../../state/openclaw-state-db.js";
|
||||
import type { GatewayRequestContext, RespondFn } from "./types.js";
|
||||
@@ -13,8 +12,14 @@ const mocks = vi.hoisted(() => ({
|
||||
external: false,
|
||||
upstreamFork: vi.fn(),
|
||||
queueClear: vi.fn(),
|
||||
readMediaBuffer: vi.fn(),
|
||||
}));
|
||||
|
||||
vi.mock("../../media/store.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../media/store.js")>();
|
||||
return { ...actual, readMediaBuffer: mocks.readMediaBuffer };
|
||||
});
|
||||
|
||||
vi.mock("../../agents/harness/registry.js", () => ({
|
||||
listRegisteredAgentHarnesses: () =>
|
||||
mocks.capability
|
||||
@@ -68,6 +73,8 @@ import type { GatewayClient } from "./types.js";
|
||||
|
||||
const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
||||
const sessionKey = "agent:main:rewind-handler";
|
||||
const storedImageId = "stored-image.png";
|
||||
const storedImagePath = `/state/media/inbound/${storedImageId}`;
|
||||
const storedImageData = Buffer.from("stored-image");
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -76,8 +83,18 @@ beforeEach(async () => {
|
||||
mocks.external = false;
|
||||
mocks.upstreamFork.mockReset();
|
||||
mocks.queueClear.mockReset();
|
||||
mocks.readMediaBuffer.mockReset().mockImplementation(async (id: string) => {
|
||||
if (id !== storedImageId) {
|
||||
throw new Error(`missing media: ${id}`);
|
||||
}
|
||||
return {
|
||||
id,
|
||||
path: storedImagePath,
|
||||
buffer: storedImageData,
|
||||
size: storedImageData.byteLength,
|
||||
};
|
||||
});
|
||||
vi.stubEnv("OPENCLAW_STATE_DIR", tempDirs.make("openclaw-rewind-handler-"));
|
||||
const storedImage = await saveMediaBuffer(storedImageData, "image/png", "inbound");
|
||||
await upsertSessionEntry(
|
||||
{ agentId: "main", sessionKey },
|
||||
{
|
||||
@@ -99,10 +116,10 @@ beforeEach(async () => {
|
||||
],
|
||||
__openclaw: {
|
||||
media: [
|
||||
{ path: storedImage.path, contentType: "image/png" },
|
||||
{ path: storedImagePath, contentType: "image/png" },
|
||||
// Duplicate ref proves dedupe: the response must carry this image once.
|
||||
{ path: storedImage.path, contentType: "image/png" },
|
||||
{ path: `${storedImage.path}.missing`, contentType: "image/png" },
|
||||
{ path: storedImagePath, contentType: "image/png" },
|
||||
{ path: `${storedImagePath}.missing`, contentType: "image/png" },
|
||||
],
|
||||
},
|
||||
},
|
||||
@@ -271,6 +288,7 @@ describe("session message-cut methods", () => {
|
||||
}),
|
||||
undefined,
|
||||
);
|
||||
expect(mocks.readMediaBuffer).toHaveBeenCalledTimes(2);
|
||||
const forkKey = (fork.mock.calls[0]?.[1] as { sessionKey?: string } | undefined)?.sessionKey;
|
||||
expect(forkKey).toBeTruthy();
|
||||
const forkEntry = loadSessionEntry({ agentId: "main", sessionKey: forkKey ?? "" });
|
||||
@@ -299,6 +317,7 @@ describe("session message-cut methods", () => {
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
expect(mocks.readMediaBuffer).toHaveBeenCalledTimes(4);
|
||||
expect(mocks.queueClear).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
||||
@@ -366,23 +366,11 @@ export function describeGoogleProviderRuntimeContract(load: ProviderRuntimeContr
|
||||
});
|
||||
});
|
||||
|
||||
it("owns usage-token parsing", async () => {
|
||||
it("keeps retired usage telemetry hooks absent", () => {
|
||||
const provider = requireProviderContractProvider("google-gemini-cli");
|
||||
await expect(
|
||||
provider.resolveUsageAuth?.({
|
||||
config: {} as never,
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
provider: "google-gemini-cli",
|
||||
resolveApiKeyFromConfigAndStore: () => undefined,
|
||||
resolveOAuthToken: async () => ({
|
||||
token: '{"token":"google-oauth-token"}',
|
||||
accountId: "google-account",
|
||||
}),
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
token: "google-oauth-token",
|
||||
accountId: "google-account",
|
||||
});
|
||||
|
||||
expect(provider.resolveUsageAuth).toBeUndefined();
|
||||
expect(provider.fetchUsageSnapshot).toBeUndefined();
|
||||
});
|
||||
|
||||
it("owns OAuth auth-profile formatting", () => {
|
||||
@@ -399,38 +387,6 @@ export function describeGoogleProviderRuntimeContract(load: ProviderRuntimeContr
|
||||
}),
|
||||
).toBe('{"token":"google-oauth-token","projectId":"proj-123"}');
|
||||
});
|
||||
|
||||
it("owns usage snapshot fetching", async () => {
|
||||
const provider = requireProviderContractProvider("google-gemini-cli");
|
||||
const mockFetch = createProviderUsageFetch(async (url) => {
|
||||
if (url.includes("cloudcode-pa.googleapis.com/v1internal:retrieveUserQuota")) {
|
||||
return makeResponse(200, {
|
||||
buckets: [
|
||||
{ modelId: "gemini-3.1-pro-preview", remainingFraction: 0.4 },
|
||||
{ modelId: "gemini-3.1-flash-preview", remainingFraction: 0.8 },
|
||||
],
|
||||
});
|
||||
}
|
||||
return makeResponse(404, "not found");
|
||||
});
|
||||
|
||||
const snapshot = await provider.fetchUsageSnapshot?.({
|
||||
config: {} as never,
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
provider: "google-gemini-cli",
|
||||
token: "google-oauth-token",
|
||||
timeoutMs: 5_000,
|
||||
fetchFn: mockFetch as unknown as typeof fetch,
|
||||
});
|
||||
|
||||
expectFields(snapshot, {
|
||||
provider: "google-gemini-cli",
|
||||
displayName: "Gemini",
|
||||
});
|
||||
expect(snapshot?.windows[0]).toEqual({ label: "Pro", usedPercent: 60 });
|
||||
expect(snapshot?.windows[1]?.label).toBe("Flash");
|
||||
expect(snapshot?.windows[1]?.usedPercent).toBeCloseTo(20);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ describe("manifest model id normalization", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("reflects manifest and state-dir changes without a prepared snapshot", () => {
|
||||
it("keeps process metadata stable across manifest edits and reflects lifecycle resets", () => {
|
||||
const stateDirA = makeTempDir();
|
||||
const pluginDirA = path.join(stateDirA, "extensions", "normalizer");
|
||||
writeInstallIndex({ stateDir: stateDirA, pluginDir: pluginDirA });
|
||||
@@ -122,7 +122,7 @@ describe("manifest model id normalization", () => {
|
||||
expect(normalizeDemoModel()).toBe("alpha/demo-model");
|
||||
|
||||
writeNormalizerManifest({ pluginDir: pluginDirA, prefix: "bravo-local" });
|
||||
expect(normalizeDemoModel()).toBe("bravo-local/demo-model");
|
||||
expect(normalizeDemoModel()).toBe("alpha/demo-model");
|
||||
|
||||
const stateDirB = makeTempDir();
|
||||
const pluginDirB = path.join(stateDirB, "extensions", "normalizer");
|
||||
|
||||
@@ -32,7 +32,9 @@ const REQUIRED_REVIEWED_PUBLISHABLE_CRITICAL_FINDING_COUNTS = new Map<string, nu
|
||||
["@openclaw/codex:dangerous-exec:src/node-cli-sessions.ts", 1],
|
||||
["@openclaw/discord:dangerous-exec:src/voice/audio.ts", 1],
|
||||
["@openclaw/google-meet:dangerous-exec:src/node-host.ts", 1],
|
||||
["@openclaw/imessage:dangerous-exec:src/client.ts", 1],
|
||||
["@openclaw/mxc-sandbox:dangerous-exec:src/readiness.ts", 2],
|
||||
["@openclaw/opencode-provider:dangerous-exec:session-catalog.ts", 1],
|
||||
["@openclaw/raft:dangerous-exec:src/gateway.ts", 1],
|
||||
["@openclaw/signal:dangerous-exec:src/daemon.ts", 1],
|
||||
["@openclaw/voice-call:dangerous-exec:src/tunnel.ts", 1],
|
||||
|
||||
@@ -327,6 +327,32 @@ describe("kitchen-sink plugin assertions", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("persists the scenario personality in plugin config", () => {
|
||||
const home = mkdtempSync(path.join(tmpdir(), "openclaw-kitchen-sink-config-"));
|
||||
try {
|
||||
const result = spawnSync(process.execPath, [ASSERTIONS_SCRIPT, "configure-runtime"], {
|
||||
encoding: "utf8",
|
||||
env: {
|
||||
...process.env,
|
||||
HOME: home,
|
||||
KITCHEN_SINK_ID: "openclaw-kitchen-sink-fixture",
|
||||
KITCHEN_SINK_PERSONALITY: "conformance",
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.status).toBe(0);
|
||||
const config = JSON.parse(
|
||||
readFileSync(path.join(home, ".openclaw", "openclaw.json"), "utf8"),
|
||||
);
|
||||
expect(config.plugins.entries["openclaw-kitchen-sink-fixture"]).toMatchObject({
|
||||
config: { personality: "conformance" },
|
||||
hooks: { allowConversationAccess: true },
|
||||
});
|
||||
} finally {
|
||||
rmSync(home, { force: true, recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
it("requires kitchen-sink plugins to appear in inspect-all output", () => {
|
||||
const result = runAssertInstalled({
|
||||
allInspectPayload: [fullSurfaceInspectPayload("other-plugin")],
|
||||
|
||||
Reference in New Issue
Block a user