Per operator-direction 2026-05-17 (R089 — Migrate Voice IVR / ElevenLabs On-Call Paging Infrastructure out of SF). Migration target landed in centralcloud monorepo: - centralcloud_core/lib/centralcloud_core/voice.ex (TwiML + ElevenLabs) - centralcloud_staff/lib/.../controllers/voice_controller.ex (Phoenix) - centralcloud_staff/lib/.../controllers/voice_prompt_controller.ex - centralcloud_staff/lib/.../router.ex (/twilio scope) SF removal: - web/app/api/voice/route.ts - web/app/api/voice/prompt/route.ts - web/app/api/voice/ directory - src/tests/integration/web-voice-ivr-contract.test.ts Operator-paging infra was historical drift in SF (per-project compiler); belongs in centralcloud (org-level ops). R088 (Pre-Removal Test-Import Safety Gate) not yet built — operator manually verified safety scan: TWILIO_/ELEVENLABS_ env vars only referenced in the deleted files; no internal SF callers; centralcloud version verified present. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
220 lines
6 KiB
TypeScript
220 lines
6 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import {
|
|
chmodSync,
|
|
copyFileSync,
|
|
mkdirSync,
|
|
mkdtempSync,
|
|
rmSync,
|
|
} from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { delimiter, join } from "node:path";
|
|
import { afterEach, beforeEach, test } from "vitest";
|
|
|
|
import {
|
|
buildRtkEnv,
|
|
ensureRtkAvailable,
|
|
getManagedRtkDir,
|
|
prependPathEntry,
|
|
resolveRtkAssetName,
|
|
resolveRtkBinaryPath,
|
|
rewriteCommandWithRtk,
|
|
SF_RTK_DISABLED_ENV,
|
|
SF_RTK_PATH_ENV,
|
|
SF_SKIP_RTK_INSTALL_ENV,
|
|
validateRtkBinary,
|
|
} from "../rtk.ts";
|
|
import { createFakeRtk } from "./rtk-test-utils.ts";
|
|
|
|
// Store original env values for restoration
|
|
let originalRtkDisabled: string | undefined;
|
|
|
|
beforeEach(() => {
|
|
// Save and clear SF_RTK_DISABLED so tests can use fake RTK binaries
|
|
originalRtkDisabled = process.env.SF_RTK_DISABLED;
|
|
delete process.env.SF_RTK_DISABLED;
|
|
});
|
|
|
|
afterEach(() => {
|
|
// Restore original env
|
|
if (originalRtkDisabled !== undefined) {
|
|
process.env.SF_RTK_DISABLED = originalRtkDisabled;
|
|
} else {
|
|
delete process.env.SF_RTK_DISABLED;
|
|
}
|
|
});
|
|
|
|
test("resolveRtkAssetName maps supported release assets correctly", () => {
|
|
// darwin removed 2026-05-17 per operator direction — no Mac support
|
|
assert.equal(resolveRtkAssetName("darwin", "arm64"), null);
|
|
assert.equal(resolveRtkAssetName("darwin", "x64"), null);
|
|
assert.equal(
|
|
resolveRtkAssetName("linux", "arm64"),
|
|
"rtk-aarch64-unknown-linux-gnu.tar.gz",
|
|
);
|
|
assert.equal(
|
|
resolveRtkAssetName("linux", "x64"),
|
|
"rtk-x86_64-unknown-linux-musl.tar.gz",
|
|
);
|
|
assert.equal(
|
|
resolveRtkAssetName("win32", "x64"),
|
|
"rtk-x86_64-pc-windows-msvc.zip",
|
|
);
|
|
assert.equal(resolveRtkAssetName("win32", "arm64"), null);
|
|
});
|
|
|
|
test("prependPathEntry preserves the original PATH key casing and avoids duplicates", () => {
|
|
const env: NodeJS.ProcessEnv = { Path: "/usr/bin" };
|
|
prependPathEntry(env, "/tmp/sf-bin");
|
|
assert.equal(env.Path, `/tmp/sf-bin${delimiter}${"/usr/bin"}`);
|
|
prependPathEntry(env, "/tmp/sf-bin");
|
|
assert.equal(env.Path, `/tmp/sf-bin${delimiter}${"/usr/bin"}`);
|
|
});
|
|
|
|
test("buildRtkEnv prepends the managed bin dir and disables telemetry", () => {
|
|
const env = buildRtkEnv({ PATH: "/usr/bin" });
|
|
assert.ok(env.PATH?.startsWith(`${getManagedRtkDir()}${delimiter}`));
|
|
assert.equal(env.RTK_TELEMETRY_DISABLED, "1");
|
|
});
|
|
|
|
test("rewriteCommandWithRtk rewrites when RTK returns exit 0 or 3", () => {
|
|
const spawnSyncImpl = ((_binary: string, _args: string[]) => ({
|
|
status: 0,
|
|
stdout: "rtk git status",
|
|
error: undefined,
|
|
})) as typeof import("node:child_process").spawnSync;
|
|
assert.equal(
|
|
rewriteCommandWithRtk("git status", {
|
|
binaryPath: "/tmp/rtk",
|
|
spawnSyncImpl,
|
|
}),
|
|
"rtk git status",
|
|
);
|
|
|
|
const askSpawn = ((_binary: string, _args: string[]) => ({
|
|
status: 3,
|
|
stdout: "rtk npm run test",
|
|
error: undefined,
|
|
})) as typeof import("node:child_process").spawnSync;
|
|
assert.equal(
|
|
rewriteCommandWithRtk("npm run test", {
|
|
binaryPath: "/tmp/rtk",
|
|
spawnSyncImpl: askSpawn,
|
|
}),
|
|
"rtk npm run test",
|
|
);
|
|
});
|
|
|
|
test("rewriteCommandWithRtk passes commands through on no-match or process error", () => {
|
|
const passthroughSpawn = ((_binary: string, _args: string[]) => ({
|
|
status: 1,
|
|
stdout: "",
|
|
error: undefined,
|
|
})) as typeof import("node:child_process").spawnSync;
|
|
assert.equal(
|
|
rewriteCommandWithRtk("echo hello", {
|
|
binaryPath: "/tmp/rtk",
|
|
spawnSyncImpl: passthroughSpawn,
|
|
}),
|
|
"echo hello",
|
|
);
|
|
|
|
const failingSpawn = ((_binary: string, _args: string[]) => ({
|
|
status: null,
|
|
stdout: "",
|
|
error: new Error("boom"),
|
|
})) as typeof import("node:child_process").spawnSync;
|
|
assert.equal(
|
|
rewriteCommandWithRtk("git status", {
|
|
binaryPath: "/tmp/rtk",
|
|
spawnSyncImpl: failingSpawn,
|
|
}),
|
|
"git status",
|
|
);
|
|
});
|
|
|
|
test("rewriteCommandWithRtk respects the disable flag", () => {
|
|
const spawnSyncImpl = (() => {
|
|
throw new Error("should not be called");
|
|
}) as unknown as typeof import("node:child_process").spawnSync;
|
|
|
|
assert.equal(
|
|
rewriteCommandWithRtk("git status", {
|
|
binaryPath: "/tmp/rtk",
|
|
spawnSyncImpl,
|
|
env: { [SF_RTK_DISABLED_ENV]: "1" },
|
|
}),
|
|
"git status",
|
|
);
|
|
});
|
|
|
|
test("rewriteCommandWithRtk falls back to the managed RTK path when SF_RTK_PATH is unset", () => {
|
|
const fake = createFakeRtk({ "git status": "rtk git status" });
|
|
const managedHome = mkdtempSync(join(tmpdir(), "sf-rtk-managed-home-"));
|
|
const managedDir = join(managedHome, "agent", "bin");
|
|
const managedPath = join(
|
|
managedDir,
|
|
process.platform === "win32" ? "rtk.cmd" : "rtk",
|
|
);
|
|
|
|
mkdirSync(managedDir, { recursive: true });
|
|
copyFileSync(fake.path, managedPath);
|
|
if (process.platform !== "win32") {
|
|
chmodSync(managedPath, 0o755);
|
|
}
|
|
|
|
try {
|
|
const env = {
|
|
...process.env,
|
|
SF_HOME: managedHome,
|
|
};
|
|
delete env.SF_RTK_PATH;
|
|
|
|
assert.equal(resolveRtkBinaryPath({ env }), managedPath);
|
|
assert.equal(
|
|
rewriteCommandWithRtk("git status", { env }),
|
|
"rtk git status",
|
|
);
|
|
} finally {
|
|
fake.cleanup();
|
|
rmSync(managedHome, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
test("validateRtkBinary checks the rewrite contract", () => {
|
|
const validSpawn = ((_binary: string, _args: string[]) => ({
|
|
status: 0,
|
|
stdout: "rtk git status",
|
|
error: undefined,
|
|
})) as typeof import("node:child_process").spawnSync;
|
|
assert.equal(
|
|
validateRtkBinary("/tmp/rtk", { spawnSyncImpl: validSpawn }),
|
|
true,
|
|
);
|
|
|
|
const invalidSpawn = ((_binary: string, _args: string[]) => ({
|
|
status: 0,
|
|
stdout: "wrong output",
|
|
error: undefined,
|
|
})) as typeof import("node:child_process").spawnSync;
|
|
assert.equal(
|
|
validateRtkBinary("/tmp/rtk", { spawnSyncImpl: invalidSpawn }),
|
|
false,
|
|
);
|
|
});
|
|
|
|
test("ensureRtkAvailable respects explicit disable and skip flags without downloading", async () => {
|
|
const disabled = await ensureRtkAvailable({
|
|
env: { [SF_RTK_DISABLED_ENV]: "1" },
|
|
});
|
|
assert.equal(disabled.enabled, false);
|
|
assert.equal(disabled.source, "disabled");
|
|
|
|
const skipped = await ensureRtkAvailable({
|
|
env: {
|
|
[SF_SKIP_RTK_INSTALL_ENV]: "1",
|
|
[SF_RTK_PATH_ENV]: "/tmp/nonexistent-rtk",
|
|
},
|
|
});
|
|
assert.equal(skipped.available, false);
|
|
assert.equal(skipped.source, "missing");
|
|
});
|