refactor: move remaining SDK test helper files

This commit is contained in:
Peter Steinberger
2026-04-28 03:28:08 +01:00
parent e1acb61317
commit 7bf08e7344
12 changed files with 0 additions and 431 deletions

View File

@@ -1,43 +0,0 @@
import {
formatUtcTimestamp,
formatZonedTimestamp,
} from "../../src/infra/format-time/format-datetime.js";
export { escapeRegExp } from "../../src/utils.js";
type EnvelopeTimestampZone = string;
export function formatEnvelopeTimestamp(date: Date, zone: EnvelopeTimestampZone = "utc"): string {
const trimmedZone = zone.trim();
const normalized = trimmedZone.toLowerCase();
const weekday = (() => {
try {
if (normalized === "utc" || normalized === "gmt") {
return new Intl.DateTimeFormat("en-US", { timeZone: "UTC", weekday: "short" }).format(date);
}
if (normalized === "local" || normalized === "host") {
return new Intl.DateTimeFormat("en-US", { weekday: "short" }).format(date);
}
return new Intl.DateTimeFormat("en-US", { timeZone: trimmedZone, weekday: "short" }).format(
date,
);
} catch {
return undefined;
}
})();
if (normalized === "utc" || normalized === "gmt") {
const ts = formatUtcTimestamp(date);
return weekday ? `${weekday} ${ts}` : ts;
}
if (normalized === "local" || normalized === "host") {
const ts = formatZonedTimestamp(date) ?? formatUtcTimestamp(date);
return weekday ? `${weekday} ${ts}` : ts;
}
const ts = formatZonedTimestamp(date, { timeZone: trimmedZone }) ?? formatUtcTimestamp(date);
return weekday ? `${weekday} ${ts}` : ts;
}
export function formatLocalEnvelopeTimestamp(date: Date): string {
return formatEnvelopeTimestamp(date, "local");
}

View File

@@ -1,54 +0,0 @@
import { describe, expect, it } from "vitest";
import { mockNodeBuiltinModule } from "./node-builtin-mocks.js";
describe("mockNodeBuiltinModule", () => {
it("merges partial overrides into the original module", async () => {
const actual = { readFileSync: () => "actual", watch: () => "watch" };
const readFileSync = () => "mock";
const mocked = await mockNodeBuiltinModule(async () => actual, {
readFileSync,
});
expect(mocked.readFileSync).toBe(readFileSync);
expect(mocked.watch).toBe(actual.watch);
expect("default" in mocked).toBe(false);
});
it("mirrors overrides into the default export when requested", async () => {
const homedir = () => "/tmp/home";
const mocked = await mockNodeBuiltinModule(
async () => ({ tmpdir: () => "/tmp" }),
{ homedir },
{ mirrorToDefault: true },
);
expect(mocked.default).toMatchObject({
homedir,
tmpdir: expect.any(Function),
});
});
it("preserves existing default exports while overriding members", async () => {
const actual = {
readFileSync: () => "actual",
default: {
readFileSync: () => "actual",
statSync: () => "stat",
},
};
const readFileSync = () => "mock";
const mocked = await mockNodeBuiltinModule(
async () => actual,
{ readFileSync },
{ mirrorToDefault: true },
);
expect(mocked.default).toMatchObject({
readFileSync,
statSync: expect.any(Function),
});
});
});

View File

@@ -1,35 +0,0 @@
import { registerSingleProviderPlugin } from "openclaw/plugin-sdk/plugin-test-runtime";
import { expect } from "vitest";
export async function expectPassthroughReplayPolicy(params: {
modelId: string;
plugin: unknown;
providerId: string;
sanitizeThoughtSignatures?: boolean;
}) {
const provider = await registerSingleProviderPlugin(params.plugin as never);
const policy = provider.buildReplayPolicy?.({
provider: params.providerId,
modelApi: "openai-completions",
modelId: params.modelId,
} as never);
expect(policy).toMatchObject({
applyAssistantFirstOrderingFix: false,
validateGeminiTurns: false,
validateAnthropicTurns: false,
});
if (params.sanitizeThoughtSignatures) {
expect(policy).toMatchObject({
sanitizeThoughtSignatures: {
allowBase64Only: true,
includeCamelCase: true,
},
});
} else {
expect(policy).not.toHaveProperty("sanitizeThoughtSignatures");
}
return provider;
}

View File

@@ -1,139 +0,0 @@
import type {
RealtimeTranscriptionProviderConfig,
RealtimeTranscriptionProviderPlugin,
} from "openclaw/plugin-sdk/realtime-transcription";
import { expect } from "vitest";
const DEFAULT_ELEVENLABS_BASE_URL = "https://api.elevenlabs.io";
const DEFAULT_ELEVENLABS_VOICE_ID = "pMsXgVXv3BLzUgSXRplE";
const DEFAULT_ELEVENLABS_TTS_MODEL_ID = "eleven_multilingual_v2";
export function normalizeTranscriptForMatch(value: string): string {
return value.toLowerCase().replace(/[^a-z0-9]+/g, "");
}
type ExpectedTranscriptMatch = RegExp | string;
const DEFAULT_OPENCLAW_TRANSCRIPT_MATCH = /open(?:claw|flaw|clar)/;
export async function waitForLiveExpectation(expectation: () => void, timeoutMs = 30_000) {
const started = Date.now();
let lastError: unknown;
while (Date.now() - started < timeoutMs) {
try {
expectation();
return;
} catch (error) {
lastError = error;
await new Promise((resolve) => setTimeout(resolve, 100));
}
}
throw lastError;
}
export async function synthesizeElevenLabsLiveSpeech(params: {
text: string;
apiKey: string;
outputFormat: "mp3_44100_128" | "ulaw_8000";
timeoutMs?: number;
}): Promise<Buffer> {
const baseUrl = process.env.ELEVENLABS_BASE_URL?.trim() || DEFAULT_ELEVENLABS_BASE_URL;
const voiceId = process.env.ELEVENLABS_LIVE_VOICE_ID?.trim() || DEFAULT_ELEVENLABS_VOICE_ID;
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), params.timeoutMs ?? 30_000);
try {
const url = new URL(`${baseUrl.replace(/\/+$/, "")}/v1/text-to-speech/${voiceId}`);
url.searchParams.set("output_format", params.outputFormat);
const response = await fetch(url, {
method: "POST",
headers: {
"xi-api-key": params.apiKey,
"Content-Type": "application/json",
},
body: JSON.stringify({
text: params.text,
model_id: DEFAULT_ELEVENLABS_TTS_MODEL_ID,
voice_settings: {
stability: 0.5,
similarity_boost: 0.75,
style: 0,
use_speaker_boost: true,
speed: 1,
},
}),
signal: controller.signal,
});
if (!response.ok) {
throw new Error(`ElevenLabs live TTS failed (${response.status})`);
}
return Buffer.from(await response.arrayBuffer());
} finally {
clearTimeout(timeout);
}
}
export async function streamAudioForLiveTest(params: {
audio: Buffer;
sendAudio: (chunk: Buffer) => void;
chunkSize?: number;
delayMs?: number;
}) {
const chunkSize = params.chunkSize ?? 160;
const delayMs = params.delayMs ?? 5;
for (let offset = 0; offset < params.audio.byteLength; offset += chunkSize) {
params.sendAudio(params.audio.subarray(offset, offset + chunkSize));
await new Promise((resolve) => setTimeout(resolve, delayMs));
}
}
export async function runRealtimeSttLiveTest(params: {
provider: RealtimeTranscriptionProviderPlugin;
providerConfig: RealtimeTranscriptionProviderConfig;
audio: Buffer;
expectedNormalizedText?: ExpectedTranscriptMatch;
timeoutMs?: number;
closeBeforeWait?: boolean;
chunkSize?: number;
delayMs?: number;
}): Promise<{ transcripts: string[]; partials: string[]; errors: Error[] }> {
const transcripts: string[] = [];
const partials: string[] = [];
const errors: Error[] = [];
const expected = params.expectedNormalizedText ?? DEFAULT_OPENCLAW_TRANSCRIPT_MATCH;
const session = params.provider.createSession({
providerConfig: params.providerConfig,
onPartial: (partial) => partials.push(partial),
onTranscript: (transcript) => transcripts.push(transcript),
onError: (error) => errors.push(error),
});
try {
await session.connect();
await streamAudioForLiveTest({
audio: params.audio,
sendAudio: (chunk) => session.sendAudio(chunk),
chunkSize: params.chunkSize,
delayMs: params.delayMs,
});
if (params.closeBeforeWait) {
session.close();
}
await waitForLiveExpectation(() => {
if (errors[0]) {
throw errors[0];
}
const normalized = normalizeTranscriptForMatch(transcripts.join(" "));
if (typeof expected === "string") {
expect(normalized).toContain(expected);
} else {
expect(normalized).toMatch(expected);
}
}, params.timeoutMs ?? 60_000);
} finally {
session.close();
}
expect(partials.length + transcripts.length).toBeGreaterThan(0);
return { transcripts, partials, errors };
}

View File

@@ -1,160 +0,0 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { cleanupSessionStateForTest } from "../../src/test-utils/session-state-cleanup.js";
type EnvValue = string | undefined | ((home: string) => string | undefined);
type EnvSnapshot = {
home: string | undefined;
userProfile: string | undefined;
homeDrive: string | undefined;
homePath: string | undefined;
openclawHome: string | undefined;
stateDir: string | undefined;
};
type SharedHomeRootState = {
rootPromise: Promise<string>;
nextCaseId: number;
};
const SHARED_HOME_ROOTS = new Map<string, SharedHomeRootState>();
function snapshotEnv(): EnvSnapshot {
return {
home: process.env.HOME,
userProfile: process.env.USERPROFILE,
homeDrive: process.env.HOMEDRIVE,
homePath: process.env.HOMEPATH,
openclawHome: process.env.OPENCLAW_HOME,
stateDir: process.env.OPENCLAW_STATE_DIR,
};
}
function restoreEnv(snapshot: EnvSnapshot) {
const restoreKey = (key: string, value: string | undefined) => {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
};
restoreKey("HOME", snapshot.home);
restoreKey("USERPROFILE", snapshot.userProfile);
restoreKey("HOMEDRIVE", snapshot.homeDrive);
restoreKey("HOMEPATH", snapshot.homePath);
restoreKey("OPENCLAW_HOME", snapshot.openclawHome);
restoreKey("OPENCLAW_STATE_DIR", snapshot.stateDir);
}
function snapshotExtraEnv(keys: string[]): Record<string, string | undefined> {
const snapshot: Record<string, string | undefined> = {};
for (const key of keys) {
snapshot[key] = process.env[key];
}
return snapshot;
}
function restoreExtraEnv(snapshot: Record<string, string | undefined>) {
for (const [key, value] of Object.entries(snapshot)) {
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
}
function setTempHome(base: string) {
process.env.HOME = base;
process.env.USERPROFILE = base;
// Ensure tests using HOME isolation aren't affected by leaked OPENCLAW_HOME.
delete process.env.OPENCLAW_HOME;
process.env.OPENCLAW_STATE_DIR = path.join(base, ".openclaw");
if (process.platform !== "win32") {
return;
}
const match = base.match(/^([A-Za-z]:)(.*)$/);
if (!match) {
return;
}
process.env.HOMEDRIVE = match[1];
process.env.HOMEPATH = match[2] || "\\";
}
async function allocateTempHomeBase(prefix: string): Promise<string> {
let state = SHARED_HOME_ROOTS.get(prefix);
if (!state) {
state = {
rootPromise: fs.mkdtemp(path.join(os.tmpdir(), prefix)),
nextCaseId: 0,
};
SHARED_HOME_ROOTS.set(prefix, state);
}
const root = await state.rootPromise;
const base = path.join(root, `case-${state.nextCaseId++}`);
await fs.mkdir(base, { recursive: true });
return base;
}
export async function withTempHome<T>(
fn: (home: string) => Promise<T>,
opts: {
env?: Record<string, EnvValue>;
prefix?: string;
skipSessionCleanup?: boolean;
} = {},
): Promise<T> {
const prefix = opts.prefix ?? "openclaw-test-home-";
const base = await allocateTempHomeBase(prefix);
const snapshot = snapshotEnv();
const envKeys = Object.keys(opts.env ?? {});
for (const key of envKeys) {
if (key === "HOME" || key === "USERPROFILE" || key === "HOMEDRIVE" || key === "HOMEPATH") {
throw new Error(`withTempHome: use built-in home env (got ${key})`);
}
}
const envSnapshot = snapshotExtraEnv(envKeys);
setTempHome(base);
await fs.mkdir(path.join(base, ".openclaw", "agents", "main", "sessions"), { recursive: true });
if (opts.env) {
for (const [key, raw] of Object.entries(opts.env)) {
const value = typeof raw === "function" ? raw(base) : raw;
if (value === undefined) {
delete process.env[key];
} else {
process.env[key] = value;
}
}
}
try {
return await fn(base);
} finally {
if (!opts.skipSessionCleanup) {
await cleanupSessionStateForTest().catch(() => undefined);
}
restoreExtraEnv(envSnapshot);
restoreEnv(snapshot);
try {
if (process.platform === "win32") {
await fs.rm(base, {
recursive: true,
force: true,
maxRetries: 10,
retryDelay: 50,
});
} else {
await fs.rm(base, {
recursive: true,
force: true,
});
}
} catch {
// ignore cleanup failures in tests
}
}
}