#![allow(dead_code)]
pub fn ctx() -> pitboard_core::context::Context {
pitboard_core::context::Context::from_env()
}
pub fn guard_not_live(service: &str) {
assert_ne!(
service,
pitboard_core::testing::LIVE_SERVICE,
"a test must never address the default credential slot"
);
assert_ne!(
service,
pitboard_core::testing::live_service(&ctx()),
"a test must never address the slot this machine's Claude Code reads"
);
}
#[test]
fn the_guard_refuses_the_slots_that_hold_a_real_login() {
guard_not_live("pitboard-citest-1");
guard_not_live("Claude Code-credentials-deadbeef");
let caught = std::panic::catch_unwind(|| guard_not_live(pitboard_core::testing::LIVE_SERVICE));
assert!(caught.is_err(), "the default slot must be refused");
let caught =
std::panic::catch_unwind(|| guard_not_live(&pitboard_core::testing::live_service(&ctx())));
assert!(caught.is_err(), "this machine's live slot must be refused");
}
#[test]
fn the_harness_can_park_a_login_find_it_and_take_it_away() {
let env = Env::new("harness-round-trip");
let service = format!(
"pitboard-park-{}-1",
pitboard_core::testing::dir_hash("harness")
);
assert!(!env.is_parked(&service), "nothing is parked to begin with");
env.write_park(&service, r#"{"claudeAiOauth":{"refreshToken":"r"}}"#);
assert!(env.is_parked(&service), "what was written is found");
env.delete_park(&service);
assert!(!env.is_parked(&service), "what was deleted is gone");
env.delete_park(&service);
}
#[test]
fn every_tool_is_pointed_at_a_scratch_home() {
let env = Env::new("isolation");
let command = env.command(&["status"]);
let named: Vec<(String, String)> = command
.get_envs()
.filter_map(|(k, v)| {
Some((
k.to_string_lossy().into_owned(),
v?.to_string_lossy().into_owned(),
))
})
.collect();
for home in ["CLAUDE_CONFIG_DIR", "CODEX_HOME", "PITBOARD_HOME"] {
let set = named.iter().find(|(k, _)| k == home).unwrap_or_else(|| {
panic!("{home} is not pointed anywhere, so a test reads a real one")
});
assert!(
std::path::Path::new(&set.1).starts_with(&env.root),
"{home} is {} , which is outside this test's own directory",
set.1
);
}
let removed: Vec<String> = command
.get_envs()
.filter(|(_, v)| v.is_none())
.map(|(k, _)| k.to_string_lossy().into_owned())
.collect();
assert!(
removed
.iter()
.any(|k| k == "CLAUDE_SECURESTORAGE_CONFIG_DIR"),
"CLAUDE_SECURESTORAGE_CONFIG_DIR is inherited, so a test can read the real slot"
);
}
use std::path::PathBuf;
use std::process::Command;
const SECURITY: &str = "/usr/bin/security";
pub struct Env {
pub root: PathBuf,
pub service: String,
name: String,
server: mockito::ServerGuard,
usage: mockito::Mock,
mocks: Vec<mockito::Mock>,
}
pub fn two_accounts(name: &str) -> Env {
let mut env = Env::new(name);
let (a, o, b, p) = (env.uuid('a'), env.uuid('o'), env.uuid('b'), env.uuid('p'));
env.sign_in(&a, "a@example.com", &o, "refresh-a");
let (_, err, code) = env.run(&["enroll", "alpha"]);
assert_eq!(code, 0, "enroll alpha: {err}");
let (_, err, code) = env.enroll_by_signing_in("beta", &b, "b@example.com", &p, "refresh-b");
assert_eq!(code, 0, "enroll beta: {err}");
env
}
pub fn uuid_for(test: &str, who: char) -> String {
format!(
"{}-{who}111-4111-8111-111111111111",
pitboard_core::testing::dir_hash(test)
)
}
pub fn account() -> String {
std::env::var("USER").unwrap_or_else(|_| "claude-code-user".into())
}
impl Env {
pub fn new(name: &str) -> Env {
let root = std::env::temp_dir().join(format!("pitboard-e2e-{}-{name}", std::process::id()));
let _ = std::fs::remove_dir_all(&root);
std::fs::create_dir_all(&root).unwrap();
let service = pitboard_core::testing::service_for_dir(&root.to_string_lossy());
guard_not_live(&service);
let mut server = mockito::Server::new();
let usage = server
.mock("GET", "/api/oauth/usage")
.with_status(200)
.with_body(
serde_json::json!({
"five_hour": {"utilization": 12.0, "resets_at": "2026-09-21T10:30:00+00:00"},
"seven_day": {"utilization": 40.0, "resets_at": "2026-09-27T02:00:00+00:00"},
})
.to_string(),
)
.create();
Env {
root,
service,
name: name.to_string(),
server,
usage,
mocks: Vec::new(),
}
}
pub fn expect_usage_requests(&mut self, expected: usize) {
self.usage.remove();
self.usage = self
.server
.mock("GET", "/api/oauth/usage")
.with_status(200)
.with_body(
serde_json::json!({
"five_hour": {"utilization": 12.0, "resets_at": "2026-09-21T10:30:00+00:00"},
"seven_day": {"utilization": 40.0, "resets_at": "2026-09-27T02:00:00+00:00"},
})
.to_string(),
)
.expect(expected)
.create();
}
pub fn assert_usage_requests(&self) {
self.usage.assert();
}
pub fn command(&self, args: &[&str]) -> Command {
let path = format!(
"{}:{}",
self.root.join("bin").display(),
std::env::var("PATH").unwrap_or_default()
);
let mut c = Command::new(env!("CARGO_BIN_EXE_pitboard"));
c.args(args)
.env("CLAUDE_CONFIG_DIR", &self.root)
.env_remove("CLAUDE_SECURESTORAGE_CONFIG_DIR")
.env("CODEX_HOME", self.codex_home())
.env("PITBOARD_HOME", self.root.join("pitboard"))
.env("PITBOARD_API_BASE", self.server.url())
.env("PATH", path);
c
}
pub fn codex_home(&self) -> PathBuf {
let dir = self.root.join("codex");
let _ = std::fs::create_dir_all(&dir);
dir
}
pub fn run(&self, args: &[&str]) -> (String, String, i32) {
let out = self.command(args).output().expect("run pitboard");
(
String::from_utf8_lossy(&out.stdout).into_owned(),
String::from_utf8_lossy(&out.stderr).into_owned(),
out.status.code().unwrap_or(-1),
)
}
pub fn enroll_by_signing_in(
&mut self,
label: &str,
uuid: &str,
email: &str,
org: &str,
refresh: &str,
) -> (String, String, i32) {
let credential = credential(refresh).to_string();
self.install_fake_claude(&credential);
self.owns(&format!("access-{refresh}"), uuid, email, org);
self.run(&["enroll", label, "--sign-in"])
}
pub fn enroll_by_signing_in_json(
&mut self,
label: &str,
uuid: &str,
email: &str,
org: &str,
refresh: &str,
) -> (String, String, i32) {
let credential = credential(refresh).to_string();
self.install_fake_claude(&credential);
self.owns(&format!("access-{refresh}"), uuid, email, org);
self.run(&["enroll", label, "--sign-in", "--json"])
}
pub fn install_fake_claude(&self, credential: &str) {
let bin = self.root.join("bin");
std::fs::create_dir_all(&bin).unwrap();
let store = if cfg!(target_os = "macos") {
format!(
r#"hash=$(printf %s "$CLAUDE_CONFIG_DIR" | shasum -a 256 | cut -c1-8)
/usr/bin/security add-generic-password -U -a "{account}" -s "Claude Code-credentials-$hash" -w '{credential}'"#,
account = account(),
)
} else {
format!(
r#"printf %s '{credential}' > "$CLAUDE_CONFIG_DIR/.credentials.json"
chmod 600 "$CLAUDE_CONFIG_DIR/.credentials.json""#
)
};
let script = bin.join("claude");
std::fs::write(
&script,
format!(
"#!/bin/sh\n[ \"$1 $2\" = \"auth login\" ] || exit 64\n\
echo 'Opening browser to sign in'\nsleep \"${{FAKE_SIGN_IN_SECONDS:-0}}\"\n{store}\n"
),
)
.unwrap();
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
}
pub fn write_park(&self, service: &str, contents: &str) {
guard_not_live(service);
if cfg!(target_os = "macos") {
pitboard_core::testing::vault_write(&ctx(), service, contents).unwrap();
} else {
use std::os::unix::fs::PermissionsExt;
let vault = self.root.join("pitboard/vault");
std::fs::create_dir_all(&vault).unwrap();
std::fs::set_permissions(&vault, std::fs::Permissions::from_mode(0o700)).unwrap();
let path = vault.join(format!("{service}.json"));
std::fs::write(&path, contents).unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).unwrap();
}
}
pub fn is_parked(&self, service: &str) -> bool {
if cfg!(target_os = "macos") {
pitboard_core::testing::vault_read(&ctx(), service)
.unwrap()
.is_some()
} else {
self.root
.join(format!("pitboard/vault/{service}.json"))
.exists()
}
}
pub fn delete_park(&self, service: &str) {
if cfg!(target_os = "macos") {
let _ = pitboard_core::testing::vault_delete(&ctx(), service);
} else {
let _ = std::fs::remove_file(self.root.join(format!("pitboard/vault/{service}.json")));
}
}
pub fn answers_renewal(
&mut self,
refresh: &str,
status: usize,
body: serde_json::Value,
) -> mockito::Mock {
self.server
.mock("POST", "/v1/oauth/token")
.match_body(mockito::Matcher::PartialJson(serde_json::json!({
"grant_type": "refresh_token",
"refresh_token": refresh,
"client_id": "9d1c250a-e61b-44d9-88ed-5944d1962f5e",
})))
.with_status(status)
.with_body(body.to_string())
.expect(1)
.create()
}
pub fn expire(&mut self, access_token: &str) {
let mock = self
.server
.mock("GET", "/api/oauth/profile")
.match_header("authorization", format!("Bearer {access_token}").as_str())
.with_status(401)
.with_body(r#"{"type":"error","error":{"type":"authentication_error"}}"#)
.create();
self.mocks.push(mock);
}
pub fn profile_trouble(&mut self, status: usize) {
let mock = self
.server
.mock("GET", "/api/oauth/profile")
.with_status(status)
.with_body(r#"{"type":"error","error":{"type":"api_error"}}"#)
.create();
self.mocks.push(mock);
}
pub fn owns(&mut self, access_token: &str, uuid: &str, email: &str, org: &str) {
let mock = self
.server
.mock("GET", "/api/oauth/profile")
.match_header("authorization", format!("Bearer {access_token}").as_str())
.with_status(200)
.with_body(
serde_json::json!({
"account": {"uuid": uuid, "email": email},
"organization": {"uuid": org},
})
.to_string(),
)
.create();
self.mocks.push(mock);
}
pub fn sign_in(&mut self, uuid: &str, email: &str, org: &str, refresh: &str) {
let credential = credential(refresh);
self.write_live(&credential.to_string());
self.owns(&format!("access-{refresh}"), uuid, email, org);
let config = serde_json::json!({
"oauthAccount": {
"accountUuid": uuid, "emailAddress": email, "organizationUuid": org,
"organizationType": "claude_max", "profileFetchedAt": 1789871209282i64
},
"cachedArtifactRoster": {"org": org},
"numStartups": 7
});
std::fs::write(self.root.join(".claude.json"), config.to_string()).unwrap();
}
pub fn uuid(&self, who: char) -> String {
uuid_for(&self.name, who)
}
pub fn sign_in_codex(&self, account: &str, email: &str, refresh: &str) {
let login = codex_login(account, email, refresh);
use std::os::unix::fs::PermissionsExt;
let path = self.codex_home().join("auth.json");
std::fs::write(&path, login.to_string()).unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).unwrap();
}
pub fn install_fake_codex_login(&self, login: &serde_json::Value) {
use std::os::unix::fs::PermissionsExt;
let bin = self.root.join("bin");
std::fs::create_dir_all(&bin).unwrap();
let script = bin.join("codex");
let _ = std::fs::remove_file(&script);
std::fs::write(&script, format!("#!/bin/sh\n{}", fake_codex_login(login))).unwrap();
std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
}
pub fn install_fake_npm_codex_login(&self, login: &serde_json::Value) -> PathBuf {
use std::os::unix::fs::PermissionsExt;
let prefix = self.root.join("npm");
let bin = prefix.join("bin");
let package = prefix.join("lib/node_modules/@openai/codex/bin");
std::fs::create_dir_all(&bin).unwrap();
std::fs::create_dir_all(&package).unwrap();
let node = bin.join("fakenode");
std::fs::write(&node, "#!/bin/sh\nexec /bin/sh \"$@\"\n").unwrap();
std::fs::set_permissions(&node, std::fs::Permissions::from_mode(0o755)).unwrap();
let script = package.join("codex.js");
std::fs::write(
&script,
format!("#!/usr/bin/env fakenode\n{}", fake_codex_login(login)),
)
.unwrap();
std::fs::set_permissions(&script, std::fs::Permissions::from_mode(0o755)).unwrap();
let program = bin.join("codex");
let _ = std::fs::remove_file(&program);
std::os::unix::fs::symlink("../lib/node_modules/@openai/codex/bin/codex.js", &program)
.unwrap();
program
}
pub fn codex_live(&self) -> serde_json::Value {
let raw = std::fs::read_to_string(self.codex_home().join("auth.json")).unwrap();
serde_json::from_str(&raw).unwrap()
}
pub fn sign_in_codex_with_an_api_key(&self) {
use std::os::unix::fs::PermissionsExt;
let login = serde_json::json!({
"auth_mode": "apikey",
"OPENAI_API_KEY": "sk-not-a-real-key",
});
let path = self.codex_home().join("auth.json");
std::fs::write(&path, login.to_string()).unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).unwrap();
}
pub fn install_fake_codex(&self, version: &str) {
use std::os::unix::fs::PermissionsExt;
let installed = self
.root
.join("codex-install/releases")
.join(format!("{version}-test-target"))
.join("bin/codex");
std::fs::create_dir_all(installed.parent().unwrap()).unwrap();
std::fs::write(
&installed,
"#!/bin/sh\necho 'a test stand-in for codex, not meant to run' >&2\nexit 64\n",
)
.unwrap();
std::fs::set_permissions(&installed, std::fs::Permissions::from_mode(0o755)).unwrap();
let bin = self.root.join("bin");
std::fs::create_dir_all(&bin).unwrap();
let link = bin.join("codex");
let _ = std::fs::remove_file(&link);
std::os::unix::fs::symlink(&installed, &link).unwrap();
}
pub fn codex_usage(&mut self, five_hour: f64, weekly: f64) {
let window = |percent: f64, seconds: i64, reset_at: i64| {
serde_json::json!({
"used_percent": percent,
"limit_window_seconds": seconds,
"reset_after_seconds": 3600,
"reset_at": reset_at,
})
};
let mock = self
.server
.mock("GET", "/wham/usage")
.with_status(200)
.with_body(
serde_json::json!({
"plan_type": "pro",
"rate_limit": {
"allowed": true,
"limit_reached": false,
"primary_window": window(five_hour, 18_000, 1_789_990_000),
"secondary_window": window(weekly, 604_800, 1_790_500_000),
},
})
.to_string(),
)
.create();
self.mocks.push(mock);
}
fn live_path(&self) -> PathBuf {
self.root.join(".credentials.json")
}
pub fn replace_live(&self, credential: &serde_json::Value) {
let body = credential.to_string();
if !cfg!(target_os = "macos") {
self.write_live(&body);
return;
}
guard_not_live(&self.service);
let done = Command::new(SECURITY)
.args([
"add-generic-password",
"-U",
"-a",
&account(),
"-s",
&self.service,
"-X",
&hex(body.as_bytes()),
])
.status()
.expect("security ran");
assert!(
done.success(),
"security refused to write the test credential"
);
}
fn write_live(&self, credential: &str) {
if cfg!(target_os = "macos") {
pitboard_core::testing::vault_write(&ctx(), &self.service, credential).unwrap();
} else {
use std::os::unix::fs::PermissionsExt;
let path = self.live_path();
std::fs::write(&path, credential).unwrap();
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o600)).unwrap();
}
}
pub fn live(&self) -> serde_json::Value {
let raw = if cfg!(target_os = "macos") {
pitboard_core::testing::vault_read(&ctx(), &self.service)
.unwrap()
.unwrap()
} else {
std::fs::read_to_string(self.live_path()).unwrap()
};
serde_json::from_str(&raw).unwrap()
}
pub fn config(&self) -> serde_json::Value {
let raw = std::fs::read_to_string(self.root.join(".claude.json")).unwrap();
serde_json::from_str(&raw).unwrap()
}
pub fn state(&self) -> serde_json::Value {
let raw = std::fs::read_to_string(self.root.join("pitboard/state.json")).unwrap();
serde_json::from_str(&raw).unwrap()
}
pub fn edit_state(&self, edit: impl FnOnce(&mut serde_json::Value)) {
let mut state = self.state();
edit(&mut state);
std::fs::write(self.root.join("pitboard/state.json"), state.to_string()).unwrap();
}
pub fn parked_service(&self, label: &str) -> Option<String> {
self.state()["accounts"]
.as_array()?
.iter()
.find(|a| a["label"] == label)?["parked"]["service"]
.as_str()
.map(str::to_owned)
}
}
impl Drop for Env {
fn drop(&mut self) {
if cfg!(target_os = "macos")
&& let Ok(state) = std::fs::read_to_string(self.root.join("pitboard/state.json"))
&& let Ok(v) = serde_json::from_str::<serde_json::Value>(&state)
{
let parked = v["accounts"]
.as_array()
.into_iter()
.flatten()
.map(|a| &a["parked"]["service"])
.chain(v["discarded"].as_array().into_iter().flatten());
for s in parked.filter_map(serde_json::Value::as_str) {
let _ = Command::new(SECURITY)
.args(["delete-generic-password", "-a", &account(), "-s", s])
.output();
}
}
if cfg!(target_os = "macos") {
let _ = Command::new(SECURITY)
.args([
"delete-generic-password",
"-a",
&account(),
"-s",
&self.service,
])
.output();
}
let _ = std::fs::remove_dir_all(&self.root);
}
}
pub fn codex_login(account: &str, email: &str, refresh: &str) -> serde_json::Value {
let claims = serde_json::json!({
"email": email,
"https://api.openai.com/auth": {
"chatgpt_account_id": account,
"chatgpt_user_id": format!("user-{account}"),
"chatgpt_plan_type": "pro",
},
});
serde_json::json!({
"auth_mode": "chatgpt",
"OPENAI_API_KEY": null,
"tokens": {
"id_token": format!(
"{}.{}.{}",
base64url(br#"{"alg":"RS256"}"#),
base64url(claims.to_string().as_bytes()),
base64url(b"not a real signature"),
),
"access_token": format!("codex-access-{refresh}"),
"refresh_token": refresh,
"account_id": account,
},
"last_refresh": "2026-09-15T05:05:11Z",
})
}
fn fake_codex_login(login: &serde_json::Value) -> String {
format!(
"[ \"$1\" = login ] || exit 64\n\
[ -n \"$CODEX_HOME\" ] || exit 65\n\
cat > \"$CODEX_HOME/auth.json\" <<'LOGIN'\n{login}\nLOGIN\n\
chmod 600 \"$CODEX_HOME/auth.json\"\n\
echo 'Successfully logged in' >&2\n"
)
}
fn base64url(bytes: &[u8]) -> String {
const ALPHABET: &[u8; 64] = b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
let mut out = String::new();
for chunk in bytes.chunks(3) {
let mut held = 0u32;
for (at, byte) in chunk.iter().enumerate() {
held |= u32::from(*byte) << (16 - 8 * at);
}
for at in 0..(chunk.len() * 8).div_ceil(6) {
out.push(char::from(
ALPHABET[((held >> (18 - 6 * at)) & 0x3f) as usize],
));
}
}
out
}
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
pub fn credential(refresh: &str) -> serde_json::Value {
let now_ms = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as i64;
serde_json::json!({
"claudeAiOauth": {
"accessToken": format!("access-{refresh}"),
"refreshToken": refresh,
"expiresAt": now_ms + 8 * 3_600_000,
"refreshTokenExpiresAt": now_ms + 30 * 86_400_000,
"scopes": ["user:inference", "user:profile"],
"subscriptionType": "max"
},
"slackTag": {"machineBound": true}
})
}