import { spawn } from "node:child_process"; import fs from "node:fs/promises"; import os from "node:os"; import path from "node:path"; import { fileURLToPath } from "node:url"; const helperDir = path.dirname(fileURLToPath(import.meta.url)); export const testsDir = path.resolve(helperDir, ".."); export const skillHome = path.resolve(testsDir, ".."); export const fetchScriptPath = path.join(skillHome, "scripts", "fetch-sales-intelligence.mjs"); let preparedRuntimeHomePromise = null; export function testRuntimeHome() { return ( process.env.ORACLE_SI_TEST_RUNTIME_HOME || path.join(os.tmpdir(), "knowledge-one-view-cli-tests", "runtime") ); } async function prepareRuntimeHome() { if (process.env.ORACLE_SI_TEST_RUNTIME_HOME) { await fs.mkdir(process.env.ORACLE_SI_TEST_RUNTIME_HOME, { recursive: true }); return process.env.ORACLE_SI_TEST_RUNTIME_HOME; } const runtimeHome = testRuntimeHome(); if (!process.env.ORACLE_SI_RUNTIME_HOME) { await fs.mkdir(runtimeHome, { recursive: true }); return runtimeHome; } if (!preparedRuntimeHomePromise) { preparedRuntimeHomePromise = (async () => { await fs.rm(runtimeHome, { recursive: true, force: true }); await fs.mkdir(runtimeHome, { recursive: true }); for (const fileName of ["auth-state.json", "session-meta.json"]) { await fs.copyFile( path.join(process.env.ORACLE_SI_RUNTIME_HOME, fileName), path.join(runtimeHome, fileName) ); } return runtimeHome; })(); } return preparedRuntimeHomePromise; } export function flagsInArgs(args) { return args.filter((arg) => String(arg).startsWith("--")); } export function argValue(args, flagName) { const index = args.indexOf(flagName); return index >= 0 ? args[index + 1] : null; } export function executionModes(args) { return ["--intent", "--view", "--query", "--sql"].filter((flagName) => args.includes(flagName)); } export function withoutFlags(args, flagsToRemove) { const flags = new Set(flagsToRemove); const next = []; for (let index = 0; index < args.length; index += 1) { const token = args[index]; if (!flags.has(token)) { next.push(token); continue; } if (token !== "--query" && token !== "--render-sql" && args[index + 1] && !args[index + 1].startsWith("--")) { index += 1; } } return next; } export async function runFetch(cliArgs, options = {}) { const { expectCode = 0, timeoutMs = 120000, runtimeHome: requestedRuntimeHome = null, } = options; const runtimeHome = requestedRuntimeHome || (await prepareRuntimeHome()); const noCacheArgs = cliArgs.includes("--no-cache") ? [...cliArgs] : [...cliArgs, "--no-cache"]; const finalArgs = noCacheArgs.includes("--runtime-home") ? noCacheArgs : [...noCacheArgs, "--runtime-home", runtimeHome]; return new Promise((resolve, reject) => { const child = spawn(process.execPath, [fetchScriptPath, ...finalArgs], { stdio: ["ignore", "pipe", "pipe"], env: process.env, }); let stdout = ""; let stderr = ""; let timedOut = false; const startedAt = process.hrtime.bigint(); const timer = setTimeout(() => { timedOut = true; child.kill(); }, timeoutMs); child.stdout.setEncoding("utf8"); child.stderr.setEncoding("utf8"); child.stdout.on("data", (chunk) => { stdout += chunk; }); child.stderr.on("data", (chunk) => { stderr += chunk; }); child.on("error", reject); child.on("close", (code) => { clearTimeout(timer); const durationMs = Number(process.hrtime.bigint() - startedAt) / 1_000_000; let json = null; try { json = stdout.trim() ? JSON.parse(stdout) : null; } catch {} const result = { args: finalArgs, code, stdout, stderr, json, timedOut, durationMs, }; if (expectCode !== null && code !== expectCode) { reject( new Error( [ `Unexpected exit code ${code}; expected ${expectCode}.`, `Command: ${process.execPath} ${fetchScriptPath} ${finalArgs.join(" ")}`, stderr.trim() || stdout.trim(), ] .filter(Boolean) .join("\n") ) ); return; } if (expectCode === 0 && (!json || typeof json !== "object")) { reject( new Error( [ "Expected JSON output from fetch-sales-intelligence.mjs.", `Command: ${process.execPath} ${fetchScriptPath} ${finalArgs.join(" ")}`, stderr.trim() || stdout.trim(), ] .filter(Boolean) .join("\n") ) ); return; } resolve(result); }); }); }