mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
test: cover codex auth sync-back edge cases
Co-authored-by: Paperclip <noreply@paperclip.ing>
This commit is contained in:
3 files changed
+118
-1
No files matched your search
@@ -10,6 +10,7 @@ import {
|
||||
prepareCommandManagedRuntime,
|
||||
type CommandManagedRuntimeRunner,
|
||||
} from "./command-managed-runtime.js";
|
||||
import { SANDBOX_READ_FILE_TOO_LARGE_ERROR_CODE } from "./sandbox-managed-runtime.js";
|
||||
import type { RunProcessResult } from "./server-utils.js";
|
||||
|
||||
const execFile = promisify(execFileCallback);
|
||||
@@ -396,6 +397,25 @@ describe("command managed runtime", () => {
|
||||
expect(progress.at(-1)?.done).toBe(payload.length);
|
||||
});
|
||||
|
||||
it("rejects oversized downloads after the remote size probe", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-command-read-max-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
const remotePath = path.join(rootDir, "download.bin");
|
||||
await writeFile(remotePath, Buffer.alloc(1024));
|
||||
|
||||
const { runner, calls } = makeSpawnRunner();
|
||||
const client = createCommandManagedRuntimeClient({ runner, commandCwd: "/", timeoutMs: 30_000 });
|
||||
|
||||
await expect(client.readFile(remotePath, { maxBytes: 16 })).rejects.toMatchObject({
|
||||
code: SANDBOX_READ_FILE_TOO_LARGE_ERROR_CODE,
|
||||
actualBytes: 1024,
|
||||
maxBytes: 16,
|
||||
});
|
||||
|
||||
expect(calls.some((call) => call.args?.join(" ").includes("wc -c"))).toBe(true);
|
||||
expect(calls.some((call) => call.args?.join(" ").includes("dd if="))).toBe(false);
|
||||
});
|
||||
|
||||
it("includes stdout diagnostics when a managed runtime command fails", async () => {
|
||||
const startedAt = new Date().toISOString();
|
||||
const runner: CommandManagedRuntimeRunner = {
|
||||
|
||||
@@ -599,6 +599,99 @@ describe("sandbox managed runtime", () => {
|
||||
expect(lines.join("")).not.toContain("acct-sandbox");
|
||||
});
|
||||
|
||||
it("does not sync sandbox API-key Codex auth.json back to subscription host auth", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-codex-auth-apikey-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
const localWorkspaceDir = path.join(rootDir, "local-workspace");
|
||||
const remoteWorkspaceDir = path.join(rootDir, "remote-workspace");
|
||||
const localCodexHome = path.join(rootDir, "local-codex-home");
|
||||
const hostCodexHome = path.join(rootDir, "host-codex-home");
|
||||
const hostAuthSource = path.join(hostCodexHome, "auth.json");
|
||||
await mkdir(localWorkspaceDir, { recursive: true });
|
||||
await mkdir(localCodexHome, { recursive: true });
|
||||
await mkdir(hostCodexHome, { recursive: true });
|
||||
|
||||
const hostAuth = codexSubscriptionAuth({
|
||||
lastRefresh: "2026-01-01T00:00:00.000Z",
|
||||
refreshToken: "refresh-host-before-apikey",
|
||||
});
|
||||
const sandboxApiKey = "sk-sandbox-secret-should-not-copy-back";
|
||||
const sandboxAuth = `${JSON.stringify({ OPENAI_API_KEY: sandboxApiKey })}\n`;
|
||||
await writeFile(hostAuthSource, hostAuth, { mode: 0o600 });
|
||||
await symlink(hostAuthSource, path.join(localCodexHome, "auth.json"));
|
||||
|
||||
const lines: string[] = [];
|
||||
const prepared = await prepareSandboxManagedRuntime({
|
||||
spec: {
|
||||
transport: "sandbox",
|
||||
provider: "test",
|
||||
sandboxId: "sandbox-1",
|
||||
remoteCwd: remoteWorkspaceDir,
|
||||
timeoutMs: 30_000,
|
||||
apiKey: null,
|
||||
},
|
||||
adapterKey: "codex",
|
||||
client: createLocalSandboxClient(),
|
||||
workspaceLocalDir: localWorkspaceDir,
|
||||
assets: [{ key: "home", localDir: localCodexHome, followSymlinks: true }],
|
||||
onProgress: (line) => {
|
||||
lines.push(line);
|
||||
},
|
||||
});
|
||||
await writeFile(path.join(prepared.assetDirs.home, "auth.json"), sandboxAuth, "utf8");
|
||||
|
||||
await prepared.restoreWorkspace();
|
||||
|
||||
await expect(readFile(hostAuthSource, "utf8")).resolves.toBe(hostAuth);
|
||||
expect(lines.join("")).toContain("API-key auth is not synced back");
|
||||
expect(lines.join("")).not.toContain(sandboxApiKey);
|
||||
});
|
||||
|
||||
it("skips Codex auth.json sync-back when sandbox auth is missing", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-codex-auth-missing-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
const localWorkspaceDir = path.join(rootDir, "local-workspace");
|
||||
const remoteWorkspaceDir = path.join(rootDir, "remote-workspace");
|
||||
const localCodexHome = path.join(rootDir, "local-codex-home");
|
||||
const hostCodexHome = path.join(rootDir, "host-codex-home");
|
||||
const hostAuthSource = path.join(hostCodexHome, "auth.json");
|
||||
await mkdir(localWorkspaceDir, { recursive: true });
|
||||
await mkdir(localCodexHome, { recursive: true });
|
||||
await mkdir(hostCodexHome, { recursive: true });
|
||||
|
||||
const hostAuth = codexSubscriptionAuth({
|
||||
lastRefresh: "2026-01-01T00:00:00.000Z",
|
||||
refreshToken: "refresh-host-before-missing",
|
||||
});
|
||||
await writeFile(hostAuthSource, hostAuth, { mode: 0o600 });
|
||||
await symlink(hostAuthSource, path.join(localCodexHome, "auth.json"));
|
||||
|
||||
const lines: string[] = [];
|
||||
const prepared = await prepareSandboxManagedRuntime({
|
||||
spec: {
|
||||
transport: "sandbox",
|
||||
provider: "test",
|
||||
sandboxId: "sandbox-1",
|
||||
remoteCwd: remoteWorkspaceDir,
|
||||
timeoutMs: 30_000,
|
||||
apiKey: null,
|
||||
},
|
||||
adapterKey: "codex",
|
||||
client: createLocalSandboxClient(),
|
||||
workspaceLocalDir: localWorkspaceDir,
|
||||
assets: [{ key: "home", localDir: localCodexHome, followSymlinks: true }],
|
||||
onProgress: (line) => {
|
||||
lines.push(line);
|
||||
},
|
||||
});
|
||||
await rm(path.join(prepared.assetDirs.home, "auth.json"), { force: true });
|
||||
|
||||
await prepared.restoreWorkspace();
|
||||
|
||||
await expect(readFile(hostAuthSource, "utf8")).resolves.toBe(hostAuth);
|
||||
expect(lines.join("")).toContain("sandbox auth.json is missing");
|
||||
});
|
||||
|
||||
it("does not sync Codex auth.json back through a regular host auth file", async () => {
|
||||
const rootDir = await mkdtemp(path.join(os.tmpdir(), "paperclip-sandbox-codex-auth-regular-"));
|
||||
cleanupDirs.push(rootDir);
|
||||
|
||||
@@ -117,7 +117,11 @@ type ParsedCodexAuth =
|
||||
};
|
||||
|
||||
type CodexAuthTokenField = "id_token" | "access_token" | "refresh_token";
|
||||
const CODEX_AUTH_TOKEN_FIELDS = ["id_token", "access_token", "refresh_token"] as const satisfies readonly CodexAuthTokenField[];
|
||||
const CODEX_AUTH_TOKEN_FIELDS = [
|
||||
"id_token",
|
||||
"access_token",
|
||||
"refresh_token",
|
||||
] as const satisfies readonly CodexAuthTokenField[];
|
||||
|
||||
interface CanonicalCodexSubscriptionAuth {
|
||||
tokens: { account_id: string } & Partial<Record<CodexAuthTokenField, string>>;
|
||||
|
||||
Reference in new issue
Block a user