test: refactored test suites to eliminate time-based sleeps and polling loops

- Replaced time-based sleeps and polling loops with event-driven promise resolvers and fake timers across agent and tool tests.
- Migrated test suites to share in-memory auth storage and fixtures using lifecycle hooks.
- Updated catalog model definitions, metadata, and configurations.
This commit is contained in:
can1357
2026-08-13 19:30:27 +02:00
parent 28997c0d46
commit b279db1790
263 changed files with 4603 additions and 6625 deletions
@@ -1,11 +1,8 @@
import { describe, expect, it } from "bun:test";
import * as path from "node:path";
import { buildSpec, type CompletionSpec, generateCompletion } from "@oh-my-pi/pi-coding-agent/cli/completion-gen";
import { generateLiveCompletion } from "@oh-my-pi/pi-coding-agent/commands/completions";
import type { CliConfig, CommandCtor } from "@oh-my-pi/pi-utils/cli";
const repoRoot = path.resolve(import.meta.dir, "..", "..", "..", "..");
const cliEntry = path.join(repoRoot, "packages", "coding-agent", "src", "cli.ts");
// A compact synthetic spec exercising every value-source kind and an aliased
// subcommand. The generators are pure functions of this shape, so pinning their
// output here defends the exact bytes each shell parses without booting the CLI.
@@ -188,20 +185,9 @@ describe("buildSpec", () => {
});
});
describe("omp completions (integration / drift)", () => {
it("emits a zsh script reflecting the live command + flag surface", async () => {
const proc = Bun.spawn([process.execPath, cliEntry, "completions", "zsh"], {
cwd: repoRoot,
stdout: "pipe",
stderr: "pipe",
env: { ...process.env, NO_COLOR: "1", PI_NO_TITLE: "1" },
});
const [stdout, , exitCode] = await Promise.all([
new Response(proc.stdout).text(),
new Response(proc.stderr).text(),
proc.exited,
]);
expect(exitCode).toBe(0);
describe("live completion surface", () => {
it("generates a zsh script reflecting the registered commands and flags", async () => {
const stdout = await generateLiveCompletion("zsh");
// Real top-level flags from launch's static `flags` table. Flags with a
// short char render as `{-r,--resume}`, so only assert the bracket form for
@@ -224,8 +210,5 @@ describe("omp completions (integration / drift)", () => {
// Hidden/default commands must NOT surface as completable subcommands.
expect(stdout).not.toContain("_omp_cmd_launch");
expect(stdout).not.toContain("_omp_cmd___complete");
// Spawns the whole CLI entry graph, so the wall time is cold-transpile bound
// (~1s warm) rather than an assertion about latency. Bun's 5s default starves
// it when CI runs several test chunks in parallel on a shared runner.
}, 30_000);
});
@@ -17,11 +17,10 @@
* Each scenario runs the full install-new/remove-old/verify transaction and
* asserts the resulting launcher executes the NEW version.
*/
import { afterEach, beforeAll, describe, expect, it, vi } from "bun:test";
import { afterAll, beforeAll, describe, expect, it, vi } from "bun:test";
import * as fs from "node:fs/promises";
import * as os from "node:os";
import * as path from "node:path";
import { $which, removeWithRetries } from "@oh-my-pi/pi-utils";
import { $which, TempDir } from "@oh-my-pi/pi-utils";
import { $ } from "bun";
import {
type InstalledVersionVerification,
@@ -35,24 +34,27 @@ const OLD_PKG = "omp-rename-fixture-old";
const NEW_PKG = "omp-rename-fixture-new";
const OLD_VERSION = "1.0.0";
const NEW_VERSION = "2.0.0";
const tempDirs: string[] = [];
let fixtureDir: TempDir;
let oldDir: string;
let newDir: string;
// printVerifiedVersion renders theme glyphs; the update command initializes
// the theme before calling into update-cli, so the tests must too.
// the theme before calling into update-cli, so the tests must too. Keep one
// process-wide log spy so the package-manager scenarios can run concurrently.
beforeAll(async () => {
vi.spyOn(console, "log").mockImplementation(() => {});
await initTheme();
fixtureDir = await TempDir.create("@omp-rename-itest-");
({ oldDir, newDir } = await makeFixtures(fixtureDir.path()));
});
afterEach(async () => {
afterAll(async () => {
vi.restoreAllMocks();
await Promise.all(tempDirs.splice(0).map(dir => removeWithRetries(dir)));
await fixtureDir.remove();
});
/** Two installable packages that both expose an `omp` bin, plus an empty prefix. */
async function makeFixtures(): Promise<{ root: string; oldDir: string; newDir: string }> {
const root = await fs.mkdtemp(path.join(os.tmpdir(), "omp-rename-itest-"));
tempDirs.push(root);
/** Two shared, read-only packages that expose the same `omp` bin. */
async function makeFixtures(root: string): Promise<{ oldDir: string; newDir: string }> {
const mkpkg = async (name: string, version: string): Promise<string> => {
const dir = path.join(root, name);
await Bun.write(path.join(dir, "package.json"), JSON.stringify({ name, version, bin: { omp: "cli.js" } }));
@@ -61,7 +63,7 @@ async function makeFixtures(): Promise<{ root: string; oldDir: string; newDir: s
await fs.chmod(cli, 0o755);
return dir;
};
return { root, oldDir: await mkpkg(OLD_PKG, OLD_VERSION), newDir: await mkpkg(NEW_PKG, NEW_VERSION) };
return { oldDir: await mkpkg(OLD_PKG, OLD_VERSION), newDir: await mkpkg(NEW_PKG, NEW_VERSION) };
}
/** Run the installed launcher and parse its reported version, mirroring verifyBinaryAtPath. */
@@ -80,9 +82,8 @@ const RELEASE: ReleaseInfo = {
};
describe.skipIf(process.platform === "win32" || !$which("npm"))("rename migration over real npm", () => {
it("takes bin ownership with --force, survives the uninstall deleting the bin, and lands on the new version", async () => {
vi.spyOn(console, "log").mockImplementation(() => {});
const { root, oldDir, newDir } = await makeFixtures();
it.concurrent("takes bin ownership with --force, survives the uninstall deleting the bin, and lands on the new version", async () => {
const root = fixtureDir.path();
const prefix = path.join(root, "npm-prefix");
const binDir = path.join(prefix, "bin");
const env = {
@@ -95,7 +96,6 @@ describe.skipIf(process.platform === "win32" || !$which("npm"))("rename migratio
const seed = await $`npm install -g --prefix ${prefix} ${oldDir}`.env(env).quiet().nothrow();
expect(seed.exitCode).toBe(0);
expect(await verifyLauncher(binDir, OLD_VERSION)).toMatchObject({ ok: true, actual: OLD_VERSION });
// The load-bearing precondition for --force: while the old package owns
// the bin, a plain install of the new package fails instead of clobbering.
@@ -103,6 +103,7 @@ describe.skipIf(process.platform === "win32" || !$which("npm"))("rename migratio
expect(plain.exitCode).not.toBe(0);
expect(await verifyLauncher(binDir, OLD_VERSION)).toMatchObject({ ok: true, actual: OLD_VERSION });
const verifications: InstalledVersionVerification[] = [];
const steps: RenameMigrationSteps = {
async install() {
return (await $`npm install -g --force --prefix ${prefix} ${newDir}`.env(env).quiet().nothrow()).exitCode;
@@ -110,11 +111,18 @@ describe.skipIf(process.platform === "win32" || !$which("npm"))("rename migratio
async removeOld() {
return (await $`npm uninstall -g --prefix ${prefix} ${OLD_PKG}`.env(env).quiet().nothrow()).exitCode;
},
verify: () => verifyLauncher(binDir, NEW_VERSION),
async verify() {
const result = await verifyLauncher(binDir, NEW_VERSION);
verifications.push(result);
return result;
},
};
await migrateRenamedInstall(RELEASE, steps);
expect(await verifyLauncher(binDir, NEW_VERSION)).toMatchObject({ ok: true, actual: NEW_VERSION });
expect(verifications.map(result => ({ ok: result.ok, actual: result.actual }))).toEqual([
{ ok: false, actual: undefined },
{ ok: true, actual: NEW_VERSION },
]);
const globalPackages = await fs.readdir(path.join(prefix, "lib", "node_modules"));
expect(globalPackages).toContain(NEW_PKG);
expect(globalPackages).not.toContain(OLD_PKG);
@@ -122,9 +130,8 @@ describe.skipIf(process.platform === "win32" || !$which("npm"))("rename migratio
});
describe.skipIf(process.platform === "win32")("rename migration over real bun", () => {
it("clobbers the old bin on install, survives removing the old package, and lands on the new version", async () => {
vi.spyOn(console, "log").mockImplementation(() => {});
const { root, oldDir, newDir } = await makeFixtures();
it.concurrent("clobbers the old bin on install, survives removing the old package, and lands on the new version", async () => {
const root = fixtureDir.path();
const binDir = path.join(root, "bun-bin");
await fs.mkdir(binDir, { recursive: true });
const env = {
@@ -137,6 +144,7 @@ describe.skipIf(process.platform === "win32")("rename migration over real bun",
expect(seed.exitCode).toBe(0);
expect(await verifyLauncher(binDir, OLD_VERSION)).toMatchObject({ ok: true, actual: OLD_VERSION });
const verifications: InstalledVersionVerification[] = [];
const steps: RenameMigrationSteps = {
async install() {
return (await $`bun add -g file:${newDir}`.env(env).quiet().nothrow()).exitCode;
@@ -144,11 +152,17 @@ describe.skipIf(process.platform === "win32")("rename migration over real bun",
async removeOld() {
return (await $`bun remove -g ${OLD_PKG}`.env(env).quiet().nothrow()).exitCode;
},
verify: () => verifyLauncher(binDir, NEW_VERSION),
async verify() {
const result = await verifyLauncher(binDir, NEW_VERSION);
verifications.push(result);
return result;
},
};
await migrateRenamedInstall(RELEASE, steps);
expect(await verifyLauncher(binDir, NEW_VERSION)).toMatchObject({ ok: true, actual: NEW_VERSION });
expect(verifications.map(result => ({ ok: result.ok, actual: result.actual }))).toEqual([
{ ok: true, actual: NEW_VERSION },
]);
const globalManifest = await Bun.file(path.join(root, "bun-global", "package.json")).json();
expect(Object.keys(globalManifest.dependencies ?? {})).toEqual([NEW_PKG]);
}, 120_000);