fix(coding-agent): run coding-agent tests offline by default (#7031)

This commit is contained in:
Thomas Mustier 2026-07-27 10:27:43 +01:00 • committed by GitHub
parent 5f9d025e7a
commit f08f58f5f9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 41 additions and 35 deletions

View file

@ -13,6 +13,7 @@ import { join } from "node:path";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { DefaultPackageManager } from "../src/core/package-manager.ts";
import { SettingsManager } from "../src/core/settings-manager.ts";
import { allowNetwork } from "./test-network-env.ts";
// Helper to run git commands in a directory
function git(args: string[], cwd: string): string {
@ -78,6 +79,7 @@ describe("DefaultPackageManager git update", () => {
const gitSource = "git:github.com/test/extension";
beforeEach(() => {
allowNetwork();
tempDir = join(tmpdir(), `git-update-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
mkdirSync(tempDir, { recursive: true });
remoteDir = join(tempDir, "remote");

View file

@ -652,7 +652,7 @@ describe("ModelRegistry", () => {
expect(anthropicModels.some((m) => m.id === "claude-custom")).toBe(false);
expect(anthropicModels.some((m) => m.id === "claude-custom-2")).toBe(true);
expect(anthropicModels.some((m) => m.id.includes("claude"))).toBe(true);
}, 60_000);
});
test("removing custom models from models.json keeps built-in provider models", async () => {
writeModelsJson({

View file

@ -10,6 +10,7 @@ import { ProjectTrustStore } from "../src/core/trust-manager.ts";
import { main } from "../src/main.ts";
import { ConfigSelectorComponent } from "../src/modes/interactive/components/config-selector.ts";
import { handlePackageCommand } from "../src/package-manager-cli.ts";
import { allowNetwork } from "./test-network-env.ts";
describe("package commands", () => {
let tempDir: string;
@ -51,6 +52,7 @@ describe("package commands", () => {
}
beforeEach(() => {
allowNetwork();
tempDir = join(tmpdir(), `pi-package-commands-${Date.now()}-${Math.random().toString(36).slice(2)}`);
agentDir = join(tempDir, "agent");
projectDir = join(tempDir, "project");

View file

@ -1101,42 +1101,29 @@ Content`,
it("should emit progress events on install attempt", async () => {
const events: ProgressEvent[] = [];
packageManager.setProgressCallback((event) => events.push(event));
vi.spyOn(packageManager as unknown as PackageManagerInternals, "runCommand").mockRejectedValue(
new Error("simulated npm install failure"),
);
// Use public install method which emits progress events
try {
await packageManager.install("npm:nonexistent-package@1.0.0");
} catch {
// Expected to fail - package doesn't exist
}
await expect(packageManager.install("npm:nonexistent-package@1.0.0")).rejects.toThrow(
"simulated npm install failure",
);
// Should have emitted start event before failure
expect(events.some((e) => e.type === "start" && e.action === "install")).toBe(true);
// Should have emitted error event
expect(events.some((e) => e.type === "error")).toBe(true);
});
it("should recognize github URLs without git: prefix", async () => {
const events: ProgressEvent[] = [];
packageManager.setProgressCallback((event) => events.push(event));
const previousGitTerminalPrompt = process.env.GIT_TERMINAL_PROMPT;
process.env.GIT_TERMINAL_PROMPT = "0";
const runCommand = vi
.spyOn(packageManager as unknown as PackageManagerInternals, "runCommand")
.mockRejectedValue(new Error("simulated git clone failure"));
const source = "https://github.com/nonexistent/repo";
try {
// This should be parsed as a git source, not throw "unsupported"
try {
await packageManager.install("https://github.com/nonexistent/repo");
} catch {
// Expected to fail - repo doesn't exist
}
} finally {
if (previousGitTerminalPrompt === undefined) {
delete process.env.GIT_TERMINAL_PROMPT;
} else {
process.env.GIT_TERMINAL_PROMPT = previousGitTerminalPrompt;
}
}
await expect(packageManager.install(source)).rejects.toThrow("simulated git clone failure");
// Should have attempted clone, not thrown unsupported error
expect(runCommand).toHaveBeenCalledWith("git", ["clone", source, expect.any(String)]);
expect(events.some((e) => e.type === "start" && e.action === "install")).toBe(true);
});

View file

@ -6,6 +6,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { AuthStorage } from "../src/core/auth-storage.ts";
import { ModelRuntime } from "../src/core/model-runtime.ts";
import { RADIUS_PROVIDER_ID } from "../src/core/radius.ts";
import { allowNetwork } from "./test-network-env.ts";
function radiusOAuthCredential(gatewayBaseUrl: string) {
return {
@ -37,6 +38,7 @@ function radiusConfig(baseUrl: string) {
let tempDir: string;
beforeEach(() => {
allowNetwork();
tempDir = join(tmpdir(), `pi-test-radius-${Date.now()}-${Math.random().toString(36).slice(2)}`);
mkdirSync(tempDir, { recursive: true });
});
@ -92,7 +94,7 @@ describe("Radius provider", () => {
});
it("does not refresh catalogs over the network by default", async () => {
const fetchSpy = vi.spyOn(globalThis, "fetch");
const fetchSpy = vi.spyOn(globalThis, "fetch").mockRejectedValue(new Error("unexpected catalog fetch"));
const runtime = await ModelRuntime.create({
credentials: AuthStorage.inMemory({
[RADIUS_PROVIDER_ID]: radiusOAuthCredential("https://radius.example.com/v1"),

View file

@ -2,13 +2,18 @@ import { spawn } from "node:child_process";
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join, resolve } from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { ENV_AGENT_DIR } from "../src/config.ts";
import { allowNetwork } from "./test-network-env.ts";
const cliPath = resolve(__dirname, "../src/cli.ts");
const tempDirs: string[] = [];
beforeEach(() => {
allowNetwork();
});
afterEach(() => {
for (const dir of tempDirs.splice(0)) {
rmSync(dir, { recursive: true, force: true });

View file

@ -0,0 +1,6 @@
import { vi } from "vitest";
/** Enable network code paths for tests that replace external I/O with local fixtures or mocks. */
export function allowNetwork(): void {
vi.stubEnv("PI_OFFLINE", undefined);
}

View file

@ -1,4 +1,4 @@
import { afterEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import {
checkForNewPiVersion,
comparePackageVersions,
@ -6,9 +6,13 @@ import {
getLatestPiVersion,
isNewerPackageVersion,
} from "../src/utils/version-check.ts";
import { allowNetwork } from "./test-network-env.ts";
const originalSkipVersionCheck = process.env.PI_SKIP_VERSION_CHECK;
const originalOffline = process.env.PI_OFFLINE;
beforeEach(() => {
allowNetwork();
});
afterEach(() => {
vi.unstubAllGlobals();
@ -17,11 +21,6 @@ afterEach(() => {
} else {
process.env.PI_SKIP_VERSION_CHECK = originalSkipVersionCheck;
}
if (originalOffline === undefined) {
delete process.env.PI_OFFLINE;
} else {
process.env.PI_OFFLINE = originalOffline;
}
});
describe("version checks", () => {

View file

@ -8,6 +8,9 @@ export default mergeConfig(
globals: true,
environment: "node",
testTimeout: 30000,
// Tests run offline by default; opt in with allowNetwork() from test/test-network-env.ts.
env: { PI_OFFLINE: "1" },
unstubEnvs: true,
reporters: process.env.GITHUB_ACTIONS ? ["dot", "github-actions"] : ["dot"],
silent: "passed-only",
server: {