mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
docs: document shared test helpers
This commit is contained in:
@@ -4,13 +4,17 @@ import { findTaskByRunId, resetTaskRegistryForTests } from "../../src/tasks/task
|
||||
import { withTempDir } from "../../src/test-helpers/temp-dir.js";
|
||||
import { installInMemoryTaskRegistryRuntime } from "../../src/test-utils/task-registry-runtime.js";
|
||||
|
||||
// Shared ACP manager task registry setup for tests.
|
||||
|
||||
export { findTaskByRunId };
|
||||
|
||||
/** Reset task and task-flow registries without persisting state. */
|
||||
export function resetAcpManagerTaskStateForTests(): void {
|
||||
resetTaskRegistryForTests({ persist: false });
|
||||
resetTaskFlowRegistryForTests({ persist: false });
|
||||
}
|
||||
|
||||
/** Run a test with isolated ACP manager task state rooted in a temp dir. */
|
||||
export async function withAcpManagerTaskStateDir(
|
||||
run: (root: string) => Promise<void>,
|
||||
): Promise<void> {
|
||||
@@ -37,6 +41,7 @@ export async function withAcpManagerTaskStateDir(
|
||||
});
|
||||
}
|
||||
|
||||
/** Return a task by run id or fail the test with a clear message. */
|
||||
export function requireTaskByRunId(runId: string) {
|
||||
const task = findTaskByRunId(runId);
|
||||
if (!task) {
|
||||
|
||||
@@ -27,6 +27,8 @@ import {
|
||||
CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR,
|
||||
} from "./prompt-snapshot-paths.js";
|
||||
|
||||
// Builds Codex happy-path prompt snapshot fixtures for agent prompt regression tests.
|
||||
|
||||
export { CODEX_MODEL_PROMPT_FIXTURE_DIR, CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR };
|
||||
|
||||
const WORKSPACE_DIR = "/tmp/openclaw-happy-path/workspace";
|
||||
@@ -123,6 +125,7 @@ const CODEX_TEST_API_MODULE_ID = resolveRelativeBundledPluginPublicModuleId({
|
||||
artifactBasename: "test-api.js",
|
||||
});
|
||||
|
||||
/** Load the Codex public test API without hardcoding plugin-private paths. */
|
||||
async function loadCodexPromptSnapshotApi(): Promise<CodexPromptSnapshotApi> {
|
||||
return (await import(CODEX_TEST_API_MODULE_ID)) as CodexPromptSnapshotApi;
|
||||
}
|
||||
@@ -917,6 +920,7 @@ function renderReadme(scenarios: PromptScenario[]): string {
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
/** Build all Codex happy-path prompt snapshot files without writing them. */
|
||||
export async function createHappyPathPromptSnapshotFiles(): Promise<PromptSnapshotFile[]> {
|
||||
const codexApi = await loadCodexPromptSnapshotApi();
|
||||
const scenarios = createScenarios(codexApi);
|
||||
|
||||
@@ -26,6 +26,9 @@ import { SILENT_REPLY_TOKEN } from "../../../src/auto-reply/tokens.js";
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
import { makeTempWorkspace, writeWorkspaceFile } from "../../../src/test-helpers/workspace.js";
|
||||
|
||||
// Prompt composition scenarios for system/body prompt stability tests.
|
||||
|
||||
/** One turn in a prompt composition scenario. */
|
||||
export type PromptScenarioTurn = {
|
||||
id: string;
|
||||
label: string;
|
||||
@@ -34,6 +37,7 @@ export type PromptScenarioTurn = {
|
||||
notes: string[];
|
||||
};
|
||||
|
||||
/** Multi-turn prompt composition scenario fixture. */
|
||||
export type PromptScenario = {
|
||||
scenario: string;
|
||||
focus: string;
|
||||
@@ -733,6 +737,7 @@ async function createMaintenanceScenario(workspaceDir: string): Promise<PromptSc
|
||||
};
|
||||
}
|
||||
|
||||
/** Create a temp workspace with prompt composition context files. */
|
||||
export async function createWorkspaceWithPromptCompositionFiles(): Promise<string> {
|
||||
const workspaceDir = await makeTempWorkspace("openclaw-prompt-cache-");
|
||||
await writeWorkspaceFile({
|
||||
@@ -761,6 +766,7 @@ export async function createWorkspaceWithPromptCompositionFiles(): Promise<strin
|
||||
return workspaceDir;
|
||||
}
|
||||
|
||||
/** Create all prompt composition scenarios plus cleanup handles. */
|
||||
export async function createPromptCompositionScenarios(): Promise<{
|
||||
workspaceDir: string;
|
||||
warningWorkspaceDir: string;
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
// Shared prompt snapshot fixture directories.
|
||||
|
||||
/** Codex runtime happy-path prompt snapshot fixture directory. */
|
||||
export const CODEX_RUNTIME_HAPPY_PATH_PROMPT_SNAPSHOT_DIR =
|
||||
"test/fixtures/agents/prompt-snapshots/codex-runtime-happy-path";
|
||||
/** Codex model prompt fixture directory. */
|
||||
export const CODEX_MODEL_PROMPT_FIXTURE_DIR =
|
||||
"test/fixtures/agents/prompt-snapshots/codex-model-catalog";
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
// Shared transport parameter contract fixtures for GPT-5 model tests.
|
||||
|
||||
/** Expected OpenAI GPT-5 transport defaults. */
|
||||
export const OPENAI_GPT5_TRANSPORT_DEFAULTS = {
|
||||
parallel_tool_calls: true,
|
||||
text_verbosity: "low",
|
||||
} as const;
|
||||
|
||||
/** OpenAI GPT-5 cases that should receive GPT transport defaults. */
|
||||
export const OPENAI_GPT5_TRANSPORT_DEFAULT_CASES = [
|
||||
{
|
||||
provider: "openai",
|
||||
@@ -14,11 +18,13 @@ export const OPENAI_GPT5_TRANSPORT_DEFAULT_CASES = [
|
||||
},
|
||||
] as const;
|
||||
|
||||
/** Non-OpenAI GPT-5 case that should not receive OpenAI defaults. */
|
||||
export const NON_OPENAI_GPT5_TRANSPORT_CASE = {
|
||||
provider: "openrouter",
|
||||
modelId: "gpt-5.4",
|
||||
} as const;
|
||||
|
||||
/** Payload APIs that support parallel_tool_calls in GPT tests. */
|
||||
export const GPT_PARALLEL_TOOL_CALLS_PAYLOAD_APIS = [
|
||||
"openai-completions",
|
||||
"openai-responses",
|
||||
@@ -26,6 +32,7 @@ export const GPT_PARALLEL_TOOL_CALLS_PAYLOAD_APIS = [
|
||||
"azure-openai-responses",
|
||||
] as const;
|
||||
|
||||
/** Payload APIs unrelated to GPT parallel tool call defaults. */
|
||||
export const UNRELATED_TOOL_CALLS_PAYLOAD_APIS = [
|
||||
"anthropic-messages",
|
||||
"google-generative-ai",
|
||||
|
||||
@@ -6,9 +6,12 @@ import { makeTempWorkspace } from "../../src/test-helpers/workspace.js";
|
||||
import { captureEnv } from "../../src/test-utils/env.js";
|
||||
import type { WizardPrompter } from "../../src/wizard/prompts.js";
|
||||
|
||||
// Shared auth wizard test helpers for runtime/env setup.
|
||||
|
||||
const noopAsync = async () => {};
|
||||
const noop = () => {};
|
||||
|
||||
/** Create a RuntimeEnv whose exit method throws for assertions. */
|
||||
export function createExitThrowingRuntime(): RuntimeEnv {
|
||||
return {
|
||||
log: vi.fn(),
|
||||
@@ -19,6 +22,7 @@ export function createExitThrowingRuntime(): RuntimeEnv {
|
||||
};
|
||||
}
|
||||
|
||||
/** Create a WizardPrompter with default mock answers and caller overrides. */
|
||||
export function createWizardPrompter(
|
||||
overrides: Partial<WizardPrompter>,
|
||||
options?: { defaultSelect?: string },
|
||||
@@ -36,6 +40,7 @@ export function createWizardPrompter(
|
||||
};
|
||||
}
|
||||
|
||||
/** Create isolated auth state and agent directories for auth tests. */
|
||||
export async function setupAuthTestEnv(
|
||||
prefix = "openclaw-auth-",
|
||||
options?: { agentSubdir?: string },
|
||||
@@ -56,6 +61,7 @@ type AuthTestLifecycle = {
|
||||
cleanup: () => Promise<void>;
|
||||
};
|
||||
|
||||
/** Capture env and track one state dir for cleanup. */
|
||||
export function createAuthTestLifecycle(envKeys: string[]): AuthTestLifecycle {
|
||||
const envSnapshot = captureEnv(envKeys);
|
||||
let stateDir: string | null = null;
|
||||
@@ -73,6 +79,7 @@ export function createAuthTestLifecycle(envKeys: string[]): AuthTestLifecycle {
|
||||
};
|
||||
}
|
||||
|
||||
/** Return OPENCLAW_AGENT_DIR or fail the test clearly. */
|
||||
export function requireOpenClawAgentDir(): string {
|
||||
const agentDir = process.env.OPENCLAW_AGENT_DIR;
|
||||
if (!agentDir) {
|
||||
@@ -81,10 +88,12 @@ export function requireOpenClawAgentDir(): string {
|
||||
return agentDir;
|
||||
}
|
||||
|
||||
/** Resolve the auth profile JSON path for an agent directory. */
|
||||
function authProfilePathForAgent(agentDir: string): string {
|
||||
return path.join(agentDir, "auth-profiles.json");
|
||||
}
|
||||
|
||||
/** Read and parse auth profiles for an agent directory. */
|
||||
export async function readAuthProfilesForAgent<T>(agentDir: string): Promise<T> {
|
||||
const raw = await fs.readFile(authProfilePathForAgent(agentDir), "utf8");
|
||||
return JSON.parse(raw) as T;
|
||||
|
||||
@@ -2,6 +2,8 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
// Bundled browser plugin fixture used by plugin/package tests.
|
||||
|
||||
const BROWSER_FIXTURE_MANIFEST = {
|
||||
id: "browser",
|
||||
enabledByDefault: true,
|
||||
@@ -52,6 +54,7 @@ const BROWSER_FIXTURE_ENTRY = `module.exports = {
|
||||
},
|
||||
};`;
|
||||
|
||||
/** Create a temporary bundled browser plugin fixture and cleanup callback. */
|
||||
export function createBundledBrowserPluginFixture(): { rootDir: string; cleanup: () => void } {
|
||||
const rootDir = fs.mkdtempSync(path.join(os.tmpdir(), "openclaw-browser-bundled-"));
|
||||
const pluginDir = path.join(rootDir, "browser");
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// Bundled runtime sidecar paths that package/build tests expect to exist.
|
||||
|
||||
/** Runtime sidecar files shipped with bundled channel plugins. */
|
||||
export const TEST_BUNDLED_RUNTIME_SIDECAR_PATHS = [
|
||||
"dist/extensions/discord/runtime-api.js",
|
||||
"dist/extensions/telegram/runtime-api.js",
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// In-memory stdout/stderr capture helper for command tests.
|
||||
|
||||
/** Create a minimal IO object plus readers for captured output. */
|
||||
export function createCapturedIo() {
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
|
||||
@@ -5,6 +5,8 @@ import type {
|
||||
} from "../../../src/channels/plugins/types.plugin.js";
|
||||
import { listBundledPluginMetadata } from "../../../src/plugins/bundled-plugin-metadata.js";
|
||||
|
||||
// Shared bundled channel config runtime maps for config contract tests.
|
||||
|
||||
type BundledChannelRuntimeMap = ReadonlyMap<string, ChannelConfigRuntimeSchema>;
|
||||
type BundledChannelConfigSchemaMap = ReadonlyMap<string, ChannelConfigSchema>;
|
||||
type BundledChannelPluginShape = {
|
||||
@@ -18,6 +20,7 @@ type BundledChannelMaps = {
|
||||
|
||||
let cachedBundledChannelMaps: BundledChannelMaps | undefined;
|
||||
|
||||
/** Build runtime/config maps from public bundled channel plugin metadata. */
|
||||
function buildBundledChannelMaps(
|
||||
plugins: readonly BundledChannelPluginShape[],
|
||||
): BundledChannelMaps {
|
||||
@@ -61,6 +64,7 @@ function buildBundledChannelMaps(
|
||||
return { runtimeMap, configSchemaMap };
|
||||
}
|
||||
|
||||
/** Read bundled channel plugin surfaces when available in this test process. */
|
||||
function readBundledChannelPlugins(): readonly BundledChannelPluginShape[] | undefined {
|
||||
try {
|
||||
if (typeof bundledChannelModule.listBundledChannelPlugins !== "function") {
|
||||
@@ -76,6 +80,7 @@ function readBundledChannelPlugins(): readonly BundledChannelPluginShape[] | und
|
||||
}
|
||||
}
|
||||
|
||||
/** Return cached maps when live bundled plugin surfaces were available. */
|
||||
function getBundledChannelMaps(): BundledChannelMaps {
|
||||
const plugins = readBundledChannelPlugins();
|
||||
if (plugins && cachedBundledChannelMaps) {
|
||||
@@ -89,10 +94,12 @@ function getBundledChannelMaps(): BundledChannelMaps {
|
||||
return maps;
|
||||
}
|
||||
|
||||
/** Return runtime config schemas keyed by bundled channel id. */
|
||||
export function getBundledChannelRuntimeMap(): BundledChannelRuntimeMap {
|
||||
return getBundledChannelMaps().runtimeMap;
|
||||
}
|
||||
|
||||
/** Return channel config schemas keyed by bundled channel id. */
|
||||
export function getBundledChannelConfigSchemaMap(): BundledChannelConfigSchemaMap {
|
||||
return getBundledChannelMaps().configSchemaMap;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ import path from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { computeBaseConfigSchemaResponse } from "../../../src/config/schema-base.js";
|
||||
|
||||
// Config honor audit helpers that compare schema keys with proof inventories.
|
||||
|
||||
/** Inventory row describing where one config key is declared, merged, consumed, and tested. */
|
||||
export type ConfigHonorInventoryRow = {
|
||||
key: string;
|
||||
schemaPaths: string[];
|
||||
@@ -22,6 +25,7 @@ type ConfigHonorProofKey =
|
||||
| "reloadPaths"
|
||||
| "testPaths";
|
||||
|
||||
/** Result of auditing one config honor inventory. */
|
||||
export type ConfigHonorAuditResult = {
|
||||
schemaKeys: string[];
|
||||
missingKeys: string[];
|
||||
@@ -39,6 +43,7 @@ const BASE_CONFIG_SCHEMA = computeBaseConfigSchemaResponse({
|
||||
generatedAt: "2026-05-05T00:00:00.000Z",
|
||||
});
|
||||
|
||||
/** Return true when a dotted schema path exists in the generated base config schema. */
|
||||
function hasSchemaPath(schemaPath: string): boolean {
|
||||
const segments = schemaPath.split(".");
|
||||
let current: unknown = BASE_CONFIG_SCHEMA.schema;
|
||||
@@ -63,6 +68,7 @@ function hasSchemaPath(schemaPath: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** List leaf schema keys for the requested config prefixes. */
|
||||
export function listSchemaLeafKeysForPrefixes(prefixes: string[]): string[] {
|
||||
const keys = new Set<string>();
|
||||
for (const prefix of prefixes) {
|
||||
@@ -90,6 +96,7 @@ export function listSchemaLeafKeysForPrefixes(prefixes: string[]): string[] {
|
||||
return [...keys].toSorted();
|
||||
}
|
||||
|
||||
/** Audit an inventory against schema keys, proof paths, and file existence. */
|
||||
export function auditConfigHonorInventory(params: {
|
||||
prefixes: string[];
|
||||
rows: ConfigHonorInventoryRow[];
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import type { ConfigHonorInventoryRow } from "./config-honor-audit.js";
|
||||
|
||||
// Inventory of heartbeat config keys and the proof paths that should honor them.
|
||||
|
||||
/** Config prefixes audited for heartbeat key coverage. */
|
||||
export const HEARTBEAT_CONFIG_PREFIXES = [
|
||||
"agents.defaults.heartbeat",
|
||||
"agents.list.*.heartbeat",
|
||||
] as const;
|
||||
|
||||
/** Heartbeat config honor inventory consumed by config audit tests. */
|
||||
export const HEARTBEAT_CONFIG_HONOR_INVENTORY: ConfigHonorInventoryRow[] = [
|
||||
{
|
||||
key: "every",
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import type { OpenClawConfig } from "../../../src/config/config.js";
|
||||
|
||||
// Test helper for unwrapping gateway config.get response shapes.
|
||||
|
||||
/** Narrow unknown payloads to plain records for fixture parsing. */
|
||||
function asRecord(value: unknown): Record<string, unknown> {
|
||||
return typeof value === "object" && value !== null ? (value as Record<string, unknown>) : {};
|
||||
}
|
||||
|
||||
/** Unwrap current and legacy remote config snapshot envelopes. */
|
||||
export function unwrapRemoteConfigSnapshot(raw: unknown): OpenClawConfig {
|
||||
const rawObj = asRecord(raw);
|
||||
const resolved = asRecord(rawObj.resolved);
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { GatewayConnectionDetails } from "../../../src/gateway/call.js";
|
||||
|
||||
// Test helper for deciding when Android node policy config should be fetched remotely.
|
||||
|
||||
/** Return true when gateway details represent a remote node, not local loopback. */
|
||||
export function shouldFetchRemotePolicyConfig(details: GatewayConnectionDetails): boolean {
|
||||
return details.urlSource !== "local loopback";
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
} from "../../../src/infra/outbound/send-deps.js";
|
||||
import { createOutboundTestPlugin } from "../../../src/test-utils/channel-plugins.js";
|
||||
|
||||
// Channel plugin fixtures used by heartbeat runner tests.
|
||||
|
||||
type HeartbeatSendChannelId = "slack" | "telegram" | "whatsapp";
|
||||
type HeartbeatSendFn = (
|
||||
to: string,
|
||||
@@ -17,6 +19,7 @@ type HeartbeatSendFn = (
|
||||
opts?: Record<string, unknown>,
|
||||
) => Promise<Record<string, unknown>>;
|
||||
|
||||
/** Create an outbound adapter that routes through heartbeat send deps. */
|
||||
function createHeartbeatOutboundAdapter(channelId: HeartbeatSendChannelId): ChannelOutboundAdapter {
|
||||
return {
|
||||
deliveryMode: "direct",
|
||||
@@ -48,6 +51,7 @@ function createHeartbeatOutboundAdapter(channelId: HeartbeatSendChannelId): Chan
|
||||
};
|
||||
}
|
||||
|
||||
/** Create a channel plugin fixture with heartbeat/outbound behavior. */
|
||||
function createHeartbeatChannelPlugin(params: {
|
||||
id: HeartbeatSendChannelId;
|
||||
label: string;
|
||||
@@ -67,12 +71,14 @@ function createHeartbeatChannelPlugin(params: {
|
||||
};
|
||||
}
|
||||
|
||||
/** Slack heartbeat channel fixture. */
|
||||
export const heartbeatRunnerSlackPlugin = createHeartbeatChannelPlugin({
|
||||
id: "slack",
|
||||
label: "Slack",
|
||||
docsPath: "/channels/slack",
|
||||
});
|
||||
|
||||
/** Telegram heartbeat channel fixture with thread preservation. */
|
||||
export const heartbeatRunnerTelegramPlugin = createHeartbeatChannelPlugin({
|
||||
id: "telegram",
|
||||
label: "Telegram",
|
||||
@@ -82,6 +88,7 @@ export const heartbeatRunnerTelegramPlugin = createHeartbeatChannelPlugin({
|
||||
},
|
||||
});
|
||||
|
||||
/** WhatsApp heartbeat channel fixture with readiness checks. */
|
||||
export const heartbeatRunnerWhatsAppPlugin = createHeartbeatChannelPlugin({
|
||||
id: "whatsapp",
|
||||
label: "WhatsApp",
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
import type { OpenClawPluginApi } from "../../../src/plugins/types.js";
|
||||
import { loadBundledPluginPublicSurfaceSync } from "../../../src/test-utils/bundled-plugin-public-surface.js";
|
||||
|
||||
// Public-surface loader for bundled media provider plugin tests.
|
||||
|
||||
type BundledPluginEntryModule = {
|
||||
default: {
|
||||
register(api: OpenClawPluginApi): void;
|
||||
};
|
||||
};
|
||||
|
||||
/** Load a bundled provider plugin entrypoint through the public surface helper. */
|
||||
export function loadBundledProviderPlugin(pluginId: string): BundledPluginEntryModule["default"] {
|
||||
return loadBundledPluginPublicSurfaceSync<BundledPluginEntryModule>({
|
||||
pluginId,
|
||||
|
||||
@@ -5,6 +5,8 @@ import type { MusicGenerationProvider } from "../../../src/music-generation/type
|
||||
import type { VideoGenerationProvider } from "../../../src/video-generation/types.js";
|
||||
import { resetGenerationRuntimeMocks } from "./runtime-test-mocks.js";
|
||||
|
||||
// Shared Vitest module mocks for image, music, and video generation runtimes.
|
||||
|
||||
type ModelRef = { provider: string; model: string };
|
||||
|
||||
const mediaRuntimeMocks = vi.hoisted(() => {
|
||||
@@ -128,10 +130,12 @@ vi.mock("../../../src/video-generation/provider-registry.js", () => ({
|
||||
listVideoGenerationProviders: mediaRuntimeMocks.listVideoGenerationProviders,
|
||||
}));
|
||||
|
||||
/** Return the hoisted shared media generation runtime mocks. */
|
||||
export function getMediaGenerationRuntimeMocks() {
|
||||
return mediaRuntimeMocks;
|
||||
}
|
||||
|
||||
/** Reset image generation runtime mocks to default empty-provider behavior. */
|
||||
export function resetImageGenerationRuntimeMocks(): void {
|
||||
resetSharedRuntimeImportMocks();
|
||||
resetGenerationRuntimeMocks({
|
||||
@@ -142,6 +146,7 @@ export function resetImageGenerationRuntimeMocks(): void {
|
||||
});
|
||||
}
|
||||
|
||||
/** Reset music generation runtime mocks to default empty-provider behavior. */
|
||||
export function resetMusicGenerationRuntimeMocks(): void {
|
||||
resetSharedRuntimeImportMocks();
|
||||
resetGenerationRuntimeMocks({
|
||||
@@ -152,6 +157,7 @@ export function resetMusicGenerationRuntimeMocks(): void {
|
||||
});
|
||||
}
|
||||
|
||||
/** Reset video generation runtime mocks to default empty-provider behavior. */
|
||||
export function resetVideoGenerationRuntimeMocks(): void {
|
||||
resetSharedRuntimeImportMocks();
|
||||
resetGenerationRuntimeMocks({
|
||||
@@ -162,6 +168,7 @@ export function resetVideoGenerationRuntimeMocks(): void {
|
||||
});
|
||||
}
|
||||
|
||||
/** Reset shared auth/failover/logger mocks used by all media generation runtimes. */
|
||||
function resetSharedRuntimeImportMocks(): void {
|
||||
mediaRuntimeMocks.ensureAuthProfileStore.mockReset();
|
||||
mediaRuntimeMocks.ensureAuthProfileStore.mockReturnValue({ version: 1, profiles: {} });
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
// Shared mock reset contract for generated-media runtime tests.
|
||||
|
||||
type ClearableMock = {
|
||||
mockClear(): unknown;
|
||||
};
|
||||
@@ -10,6 +12,7 @@ type ResettableReturnMock = ResettableMock & {
|
||||
mockReturnValue(value: unknown): unknown;
|
||||
};
|
||||
|
||||
/** Common mock shape shared by image, music, and video generation runtime tests. */
|
||||
export type GenerationRuntimeMocks = {
|
||||
createSubsystemLogger: ClearableMock;
|
||||
describeFailoverError: ResettableMock;
|
||||
@@ -26,6 +29,7 @@ export type GenerationRuntimeMocks = {
|
||||
warn: ResettableMock;
|
||||
};
|
||||
|
||||
/** Reset generated-media runtime mocks to default no-provider behavior. */
|
||||
export function resetGenerationRuntimeMocks(mocks: GenerationRuntimeMocks): void {
|
||||
mocks.createSubsystemLogger.mockClear();
|
||||
mocks.describeFailoverError.mockReset();
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { stripAnsi } from "../../packages/terminal-core/src/ansi.js";
|
||||
|
||||
// Snapshot text normalization for terminal output tests.
|
||||
|
||||
/** Strip ANSI, normalize line endings, ellipses, and emoji/surrogate pairs. */
|
||||
export function normalizeTestText(input: string): string {
|
||||
return stripAnsi(input)
|
||||
.replaceAll("\r\n", "\n")
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import path from "node:path";
|
||||
|
||||
// Cross-platform path containment helper for tests.
|
||||
|
||||
/** Return true when target is equal to or inside base, with Windows case folding. */
|
||||
export function isPathWithinBase(base: string, target: string): boolean {
|
||||
if (process.platform === "win32") {
|
||||
const normalizedBase = path.win32.normalize(path.win32.resolve(base));
|
||||
|
||||
@@ -2,6 +2,9 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
// Temporary JSON pattern file helper for config/pattern tests.
|
||||
|
||||
/** Create a helper that writes JSON pattern files and cleans their temp dirs. */
|
||||
export function createPatternFileHelper(prefix: string) {
|
||||
const tempDirs = new Set<string>();
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"use strict";
|
||||
|
||||
// Universal CommonJS plugin-sdk stub for tests that only need import shape compatibility.
|
||||
|
||||
const stub = new Proxy(
|
||||
function pluginSdkStub() {
|
||||
return stub;
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import { sleep } from "../../src/utils.js";
|
||||
|
||||
// Polling helper for tests that wait on async state.
|
||||
|
||||
/** Polling timeout and interval options. */
|
||||
export type PollOptions = {
|
||||
timeoutMs?: number;
|
||||
intervalMs?: number;
|
||||
};
|
||||
|
||||
/** Poll until fn returns a non-nullish value or timeout elapses. */
|
||||
export async function pollUntil<T>(
|
||||
fn: () => Promise<T | null | undefined>,
|
||||
opts: PollOptions = {},
|
||||
|
||||
@@ -2,6 +2,9 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
// Synchronous temporary directory helpers for tests.
|
||||
|
||||
/** Create a temp dir and register it in an array or set for cleanup. */
|
||||
export function makeTempDir(tempDirs: string[] | Set<string>, prefix: string): string {
|
||||
const dir = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
if (Array.isArray(tempDirs)) {
|
||||
@@ -12,6 +15,7 @@ export function makeTempDir(tempDirs: string[] | Set<string>, prefix: string): s
|
||||
return dir;
|
||||
}
|
||||
|
||||
/** Remove all tracked temporary directories and clear the tracker. */
|
||||
export function cleanupTempDirs(tempDirs: string[] | Set<string>): void {
|
||||
const dirs = Array.isArray(tempDirs) ? tempDirs.splice(0) : [...tempDirs];
|
||||
for (const dir of dirs) {
|
||||
|
||||
@@ -2,17 +2,22 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
// Synchronous temporary repository helpers for tests.
|
||||
|
||||
/** Create and track a temporary repo root. */
|
||||
export function makeTempRepoRoot(tempDirs: string[], prefix: string): string {
|
||||
const repoRoot = fs.mkdtempSync(path.join(os.tmpdir(), prefix));
|
||||
tempDirs.push(repoRoot);
|
||||
return repoRoot;
|
||||
}
|
||||
|
||||
/** Write formatted JSON to a path, creating parent directories. */
|
||||
export function writeJsonFile(filePath: string, value: unknown): void {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
||||
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
/** Remove all tracked temporary directories. */
|
||||
export function cleanupTempDirs(tempDirs: string[]): void {
|
||||
for (const dir of tempDirs.splice(0)) {
|
||||
fs.rmSync(dir, { recursive: true, force: true, maxRetries: 5, retryDelay: 20 });
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import path from "node:path";
|
||||
|
||||
// Path normalization helpers for Vitest config snapshot assertions.
|
||||
|
||||
/** Convert absolute paths to cwd-relative POSIX-style paths. */
|
||||
export function normalizeConfigPath(value: unknown): unknown {
|
||||
if (typeof value !== "string" || !path.isAbsolute(value)) {
|
||||
return value;
|
||||
@@ -7,6 +10,7 @@ export function normalizeConfigPath(value: unknown): unknown {
|
||||
return path.relative(process.cwd(), value).split(path.sep).join("/");
|
||||
}
|
||||
|
||||
/** Normalize one or many config path values. */
|
||||
export function normalizeConfigPaths(
|
||||
values: readonly unknown[] | string | undefined,
|
||||
): unknown[] | undefined {
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { vi } from "vitest";
|
||||
import type { WizardPrompter } from "../../src/wizard/prompts.js";
|
||||
|
||||
// Vitest mock prompter for wizard tests.
|
||||
|
||||
/** Create a WizardPrompter with default mocked responses and optional overrides. */
|
||||
export function createWizardPrompter(overrides?: Partial<WizardPrompter>): WizardPrompter {
|
||||
const select = vi.fn(async () => "quickstart") as unknown as WizardPrompter["select"];
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user