Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions packages/argent-installer/test/uninstall.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ vi.mock("@clack/prompts", () => ({

let tmpDir: string;
let originalCwd: string;
let savedAgent: string | undefined;

function writeFile(filePath: string, contents = "test"): void {
fs.mkdirSync(path.dirname(filePath), { recursive: true });
Expand All @@ -66,13 +67,22 @@ function writeFile(filePath: string, contents = "test"): void {
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "argent-uninstall-test-"));
originalCwd = process.cwd();
// detectPackageManager() reads npm_config_user_agent, so the uninstall
// command these tests assert on is whichever package manager runs the suite.
// Unset it to pin the npm shape — otherwise the execFileSync mock below,
// which throws only for "npm", never fires and the failure-path tests
// silently exercise the success path.
savedAgent = process.env.npm_config_user_agent;
delete process.env.npm_config_user_agent;
vi.clearAllMocks();
childProcessMock.execSync.mockImplementation(() => "/usr/local/bin/argent\n");
childProcessMock.execFileSync.mockImplementation(() => undefined);
});

afterEach(() => {
process.chdir(originalCwd);
if (savedAgent === undefined) delete process.env.npm_config_user_agent;
else process.env.npm_config_user_agent = savedAgent;
fs.rmSync(tmpDir, { recursive: true, force: true });
});

Expand Down
8 changes: 8 additions & 0 deletions packages/argent-installer/test/update-decline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,15 @@ let projDir: string;
let originalCwd: string;
let savedHome: string | undefined;
let savedUserProfile: string | undefined;
let savedAgent: string | undefined;
let exitSpy: ReturnType<typeof vi.spyOn>;

beforeEach(() => {
// detectPackageManager() reads npm_config_user_agent, so the install/update
// commands these tests assert on are whichever package manager runs the
// suite. Unset it to pin the npm shape.
savedAgent = process.env.npm_config_user_agent;
delete process.env.npm_config_user_agent;
vi.clearAllMocks();
topologyState.globalInstalled = true;
topologyState.globalVersion = "1.0.0";
Expand Down Expand Up @@ -113,6 +119,8 @@ beforeEach(() => {
afterEach(() => {
exitSpy.mockRestore();
process.chdir(originalCwd);
if (savedAgent === undefined) delete process.env.npm_config_user_agent;
else process.env.npm_config_user_agent = savedAgent;
if (savedHome === undefined) delete process.env.HOME;
else process.env.HOME = savedHome;
if (savedUserProfile === undefined) delete process.env.USERPROFILE;
Expand Down
18 changes: 16 additions & 2 deletions packages/argent-installer/test/utils.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -829,6 +829,19 @@ describe("localUninstallCommand", () => {
// ── detectProjectPackageManager ──────────────────────────────────────────────

describe("detectProjectPackageManager", () => {
// With no marker found the walk falls back to detectPackageManager(), which
// reads npm_config_user_agent — the agent of whichever package manager is
// running the suite. Pin it so the boundary case below tests the walk rather
// than the contributor's package manager.
const originalAgent = process.env.npm_config_user_agent;
beforeEach(() => {
delete process.env.npm_config_user_agent;
});
afterEach(() => {
if (originalAgent === undefined) delete process.env.npm_config_user_agent;
else process.env.npm_config_user_agent = originalAgent;
});

it("detects pnpm from pnpm-lock.yaml", () => {
fs.writeFileSync(path.join(tmpDir, "pnpm-lock.yaml"), "");
expect(detectProjectPackageManager(tmpDir)).toBe("pnpm");
Expand Down Expand Up @@ -959,8 +972,9 @@ describe("detectProjectPackageManager", () => {
const repo = path.join(tmpDir, "other-repo");
fs.mkdirSync(path.join(repo, ".git"), { recursive: true });
// The sibling repo has no lockfile of its own; the outer pnpm lockfile
// must NOT bleed through the .git boundary.
expect(["npm", "yarn", "bun"]).toContain(detectProjectPackageManager(repo));
// must NOT bleed through the .git boundary, so the walk stops and falls
// back to the (pinned-unset) agent default.
expect(detectProjectPackageManager(repo)).toBe("npm");
});
});

Expand Down
26 changes: 23 additions & 3 deletions packages/telemetry/test/base-props.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,30 @@ describe("base-props", () => {
});

it("sets cloud_agent when a cloud/remote agent runtime is detected", () => {
// REPLIT_AGENT is an env-only signal (no filesystem check) and is not the
// ambient env of this test process, so it resolves deterministically.
const restore = snapshotEnv(["REPLIT_AGENT"]);
// REPLIT_AGENT is an env-only signal (no filesystem check). detectCloudAgent
// ranks claude_code, cursor and copilot ahead of it, and those are the
// literal ambient env of a Claude Code / Cursor / Copilot cloud runner — so
// clear every higher-ranked signal to pin the replit branch.
const restore = snapshotEnv([
"REPLIT_AGENT",
"CLAUDE_CODE_ENVIRONMENT_KIND",
"CLAUDE_CODE_ENTRYPOINT",
"CLAUDE_CODE_REMOTE_SESSION_ID",
"CURSOR_AGENT_WORKER_ID",
"CURSOR_WORKER_POOL_NAME",
"GITHUB_ACTIONS",
"GITHUB_ACTOR",
"GITHUB_WORKFLOW_REF",
]);
try {
delete process.env.CLAUDE_CODE_ENVIRONMENT_KIND;
delete process.env.CLAUDE_CODE_ENTRYPOINT;
delete process.env.CLAUDE_CODE_REMOTE_SESSION_ID;
delete process.env.CURSOR_AGENT_WORKER_ID;
delete process.env.CURSOR_WORKER_POOL_NAME;
delete process.env.GITHUB_ACTIONS;
delete process.env.GITHUB_ACTOR;
delete process.env.GITHUB_WORKFLOW_REF;
process.env.REPLIT_AGENT = "1";
expect(getBaseProps("cli").cloud_agent).toBe("replit");
} finally {
Expand Down
8 changes: 8 additions & 0 deletions packages/telemetry/test/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,14 @@ vi.mock("posthog-node", () => {
describe("telemetry public surface", () => {
const { tmp } = scopeHome();

// DO_NOT_TRACK is a consortium-standard opt-out any developer may export.
// Left ambient it disables consent for the whole suite, so these tests would
// assert what the developer's shell says rather than what markEnabled() does.
let restoreOptOut: () => void;

beforeEach(() => {
restoreOptOut = snapshotEnv(["DO_NOT_TRACK"]);
delete process.env.DO_NOT_TRACK;
posthogMock.instances.length = 0;
posthogMock.flushImpl = () => Promise.resolve();
resetClient();
Expand Down Expand Up @@ -93,6 +100,7 @@ describe("telemetry public surface", () => {
delete (globalThis as Record<string, unknown>).__ARGENT_POSTHOG_KEY_TEST;
resetClient();
vi.restoreAllMocks();
restoreOptOut();
});

it("markDisabled persists disabled state and drains prior events without emitting an opt-out event", async () => {
Expand Down
33 changes: 29 additions & 4 deletions packages/tool-server/test/adb-resolve-avd-path.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,43 @@
// string kept its surrounding quotes, so the `startsWith("/")` guard rejected
// an otherwise-valid absolute path).

import { describe, it, expect, afterEach } from "vitest";
import { describe, it, expect, afterEach, beforeEach } from "vitest";
import * as os from "node:os";
import * as fs from "node:fs/promises";
import * as path from "node:path";
import { resolveAvdPath } from "../src/utils/adb";

const prevAvdHome = process.env.ANDROID_AVD_HOME;
// resolveAvdPath consults five roots in priority order; ANDROID_AVD_HOME is
// only the second. Snapshot all of them (plus HOME, which backs the default
// ~/.android/avd root) so an ambient ANDROID_USER_HOME — the Studio >= 4.2
// convention, which outranks ANDROID_AVD_HOME — cannot decide these answers.
// Same set avd-snapshot.test.ts pins for the same function.
const ENV_KEYS = [
"HOME",
"ANDROID_USER_HOME",
"ANDROID_AVD_HOME",
"ANDROID_SDK_HOME",
"XDG_CONFIG_HOME",
] as const;
const originalEnv: Record<string, string | undefined> = {};
const created: string[] = [];

beforeEach(async () => {
for (const k of ENV_KEYS) originalEnv[k] = process.env[k];
const home = await fs.mkdtemp(path.join(os.tmpdir(), "argent-avd-home-"));
created.push(home);
process.env.HOME = home;
delete process.env.ANDROID_USER_HOME;
delete process.env.ANDROID_AVD_HOME;
delete process.env.ANDROID_SDK_HOME;
delete process.env.XDG_CONFIG_HOME;
});

afterEach(async () => {
if (prevAvdHome === undefined) delete process.env.ANDROID_AVD_HOME;
else process.env.ANDROID_AVD_HOME = prevAvdHome;
for (const k of ENV_KEYS) {
if (originalEnv[k] === undefined) delete process.env[k];
else process.env[k] = originalEnv[k];
}
await Promise.all(created.splice(0).map((d) => fs.rm(d, { recursive: true, force: true })));
});

Expand Down
5 changes: 5 additions & 0 deletions packages/tool-server/test/android-binary-windows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ describe("resolveAndroidBinary on Windows", () => {
delete process.env.ANDROID_HOME;
delete process.env.ANDROID_SDK_ROOT;
tmpRoot = await mkdtemp(join(tmpdir(), "argent-android-win-"));
// The win32 resolver also derives a root from the home directory
// (%USERPROFILE%\AppData\Local\Android\Sdk). Point HOME at the same empty
// temp root so a real Studio install under the developer's home cannot
// satisfy a lookup these tests expect to fail.
process.env.HOME = tmpRoot;
});

afterEach(async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ import {
} from "../../src/blueprints/native-profiler-session";
import { profilerLoadTool } from "../../src/tools/profiler/query/profiler-load";
import { nativeProfilerAnalyzeTool } from "../../src/tools/profiler/native-profiler/native-profiler-analyze";
import { __primeDepCacheForTests, __resetDepCacheForTests } from "../../src/utils/check-deps";
import {
writeAndroidNativeProfilerMetadata,
readAndroidNativeProfilerMetadata,
Expand Down Expand Up @@ -110,6 +111,11 @@ describe("native-profiler freshness flagging — real analyze/render path", () =
let originalTmpdir: string | undefined;

beforeEach(async () => {
// The analyze path's dependency gate resolves a real `adb` off PATH,
// $ANDROID_HOME and the default SDK locations, so without priming these
// tests pass only on a machine that has the Android SDK installed.
__resetDepCacheForTests();
__primeDepCacheForTests(["adb"]);
runTpQueryMock.mockReset();
routeCleanTrace();
// Resolve the isolated dir off the REAL tmpdir before we redirect TMPDIR.
Expand Down
8 changes: 8 additions & 0 deletions packages/tool-server/test/bind-failure-telemetry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,14 @@ vi.mock("@argent/registry", async (importOriginal) => {
const actual = await importOriginal<typeof import("@argent/registry")>();
return { ...actual, attachRegistryLogger: vi.fn() };
});
// start() gates its event log on the `tool-server-event-log` flag, which the
// real reader resolves from the developer's own ~/.argent/flags.json. Left
// live, a developer who enables that documented flag fails these tests and
// gets unit-test records appended to their real event log. Pin it off.
vi.mock("@argent/configuration-core", async (importOriginal) => {
const actual = await importOriginal<typeof import("@argent/configuration-core")>();
return { ...actual, isFlagEnabled: vi.fn(() => false) };
});
vi.mock("../src/utils/setup-registry", () => ({
createRegistry: vi.fn(() => registryMock),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,11 @@ import {
nativeDevtoolsBlueprint,
} from "../../src/blueprints/native-devtools";

const UDID = "FACTORY1-1111-1111-1111-111111111111";
// The factory really binds /tmp/argent-nd-<first 8 UDID chars>.sock, and the
// bind unlinks and rebinds over whatever already holds that path. Tag the UDID
// with this process's pid so a concurrent run of this file owns a different
// socket instead of silently orphaning ours (and we, theirs).
const UDID = `${process.pid.toString(16).toUpperCase().padStart(8, "0")}-1111-1111-1111-111111111111`;
const device: DeviceInfo = { id: UDID, platform: "ios", kind: "simulator" };
const SOCKET_PATH = `/tmp/argent-nd-${UDID.slice(0, 8)}.sock`;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import {
import { resolveDevice } from "../src/utils/device-info";
import type { ChromiumCdpApi } from "../src/blueprints/chromium-cdp";
import type { CDPClientEvents } from "../src/utils/debugger/cdp-client";
import { scopeTempHome } from "./helpers/temp-home";

// The JS-runtime-debugger / network blueprints build a real LogFileWriter,
// whose constructor mkdir -p's os.homedir()/.argent/tmp. Keep that out of the
// developer's real home.
scopeTempHome("argent-chromium-jsdbg-home-");

function makeFakeChromiumCdpApi(): {
api: ChromiumCdpApi;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { describe, it, expect, afterEach } from "vitest";
import { LogFileWriter } from "../../src/utils/debugger/log-file-writer";
import { scopeTempHome } from "../helpers/temp-home";

scopeTempHome("argent-log-level-home-");

/**
* Regression for level truncation: CDP emits console levels longer than 5 chars
Expand Down
3 changes: 3 additions & 0 deletions packages/tool-server/test/debugger/log-file-writer.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import * as fs from "node:fs";
import { LogFileWriter, type RichLogEntry } from "../../src/utils/debugger/log-file-writer";
import { scopeTempHome } from "../helpers/temp-home";

scopeTempHome("argent-log-writer-home-");

let writer: LogFileWriter;

Expand Down
3 changes: 3 additions & 0 deletions packages/tool-server/test/debugger/log-registry.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { LogFileWriter } from "../../src/utils/debugger/log-file-writer";
import { scopeTempHome } from "../helpers/temp-home";

scopeTempHome("argent-log-registry-home-");

/**
* Tests for the debugger-log-registry tool behavior.
Expand Down
48 changes: 31 additions & 17 deletions packages/tool-server/test/file-inputs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -177,27 +177,41 @@ describe("resolveFileInputs", () => {
{ target: "b", path: "${b}", kind: "file" },
];

// resolveFileInputs materializes into mkdtemp(join(os.tmpdir(),
// "argent-file-input-")), and os.tmpdir() reads process.env.TMPDIR at call
// time. Scope TMPDIR to this test so the listing below covers only dirs
// this run created — the machine-wide tmpdir also holds the in-flight dirs
// of any concurrent run, which would read as an uncleaned leak.
const savedTmpdir = process.env.TMPDIR;
const scratch = await fs.mkdtemp(path.join(os.tmpdir(), "argent-file-input-scan-"));
process.env.TMPDIR = scratch;

const listInputTempDirs = async () => {
const entries = await fs.readdir(os.tmpdir());
const entries = await fs.readdir(scratch);
return entries.filter((e) => e.startsWith("argent-file-input-"));
};
const before = await listInputTempDirs();

await expect(
resolveFileInputs(
{ fileInputs: specs },
{
a: wire({
path: "/client/a.png",
size: content.length,
content: content.toString("base64"),
}),
b: wire({ path: path.join(tmpDir, "ghost.png") }),
}
)
).rejects.toThrow(FileInputError);

expect(await listInputTempDirs()).toEqual(before);
try {
await expect(
resolveFileInputs(
{ fileInputs: specs },
{
a: wire({
path: "/client/a.png",
size: content.length,
content: content.toString("base64"),
}),
b: wire({ path: path.join(tmpDir, "ghost.png") }),
}
)
).rejects.toThrow(FileInputError);

expect(await listInputTempDirs()).toEqual([]);
} finally {
if (savedTmpdir === undefined) delete process.env.TMPDIR;
else process.env.TMPDIR = savedTmpdir;
await fs.rm(scratch, { recursive: true, force: true });
}
});

it("rejects a missing file with no uploaded content", async () => {
Expand Down
Loading
Loading