use std::ffi::OsString;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
fn unique_temp_dir(label: &str) -> PathBuf {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map_or(0_u128, |value| value.as_nanos());
std::env::temp_dir().join(format!("index-cli-command-surface-{label}-{nanos}"))
}
fn index_command_with_base(base: &Path) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_index"));
command
.env("XDG_CONFIG_HOME", base.join("config").as_os_str())
.env("XDG_CACHE_HOME", base.join("cache").as_os_str())
.env("XDG_STATE_HOME", base.join("state").as_os_str());
command
}
fn output_text(bytes: &[u8]) -> String {
String::from_utf8_lossy(bytes).to_string()
}
fn write_pack_file(
path: &Path,
id: &str,
selector: &str,
) -> Result<(), Box<dyn std::error::Error>> {
fs::write(
path,
format!(
r#"{{
"version": "index.pack/v1",
"id": "{id}",
"rules": [
{{
"host": "example.org",
"path_prefix": "/docs",
"manifest": {{
"version": "index.idx/v1",
"scope": "/docs",
"content": {{ "main_selector": "{selector}" }}
}}
}}
]
}}"#
),
)?;
Ok(())
}
#[test]
fn compatibility_pack_install_list_and_remove_cycle() -> Result<(), Box<dyn std::error::Error>> {
let base = unique_temp_dir("pack-cycle");
fs::create_dir_all(&base)?;
let pack_path = base.join("cycle.pack.json");
write_pack_file(&pack_path, "cycle-pack", "main article")?;
let install = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("install"),
pack_path.clone().into_os_string(),
OsString::from("--user"),
])
.output()?;
assert!(install.status.success());
assert!(output_text(&install.stdout).contains("index-compat-pack-install-v1"));
let list = index_command_with_base(&base)
.args([OsString::from("compatibility-pack"), OsString::from("list")])
.output()?;
assert!(list.status.success());
let list_stdout = output_text(&list.stdout);
assert!(list_stdout.contains("index-compat-pack-list-v1"));
assert!(list_stdout.contains("count: 1"));
assert!(list_stdout.contains("user\t"));
let remove = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("remove"),
OsString::from("cycle-pack"),
])
.output()?;
assert!(remove.status.success());
assert!(output_text(&remove.stdout).contains("removed_files: 1"));
let remove_again = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("remove"),
OsString::from("cycle-pack"),
])
.output()?;
assert!(remove_again.status.success());
assert!(output_text(&remove_again.stdout).contains("removed_files: 0"));
Ok(())
}
#[test]
fn compatibility_pack_update_mismatch_and_verify_failure() -> Result<(), Box<dyn std::error::Error>>
{
let base = unique_temp_dir("pack-update-mismatch");
fs::create_dir_all(&base)?;
let pack_path = base.join("mismatch.pack.json");
write_pack_file(&pack_path, "actual-pack", "main article")?;
let mismatch = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("update"),
OsString::from("expected-pack"),
pack_path.clone().into_os_string(),
])
.output()?;
assert!(!mismatch.status.success());
assert!(output_text(&mismatch.stderr).contains("pack id mismatch"));
let verify = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("verify"),
pack_path.clone().into_os_string(),
OsString::from("key-1"),
OsString::from("secret-1"),
OsString::from("deadbeef"),
])
.output()?;
assert!(!verify.status.success());
assert!(output_text(&verify.stderr).contains("signature mismatch"));
Ok(())
}
#[test]
fn compatibility_pack_rollback_and_runtime_inspect_paths() -> Result<(), Box<dyn std::error::Error>>
{
let base = unique_temp_dir("pack-rollback-inspect");
fs::create_dir_all(&base)?;
let first = base.join("first.pack.json");
let second = base.join("second.pack.json");
write_pack_file(&first, "roll-pack", "main first")?;
write_pack_file(&second, "roll-pack", "main second")?;
let install_first = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("install"),
first.clone().into_os_string(),
])
.output()?;
assert!(install_first.status.success());
let install_second = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("install"),
second.clone().into_os_string(),
])
.output()?;
assert!(install_second.status.success());
let inspect_enabled = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("inspect"),
OsString::from("https://example.org/docs/page"),
])
.output()?;
assert!(inspect_enabled.status.success());
let inspect_enabled_stdout = output_text(&inspect_enabled.stdout);
assert!(inspect_enabled_stdout.contains("index-compat-pack-runtime-v1"));
assert!(inspect_enabled_stdout.contains("selected: user:roll-pack"));
let rollback = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("rollback"),
OsString::from("roll-pack"),
])
.output()?;
assert!(rollback.status.success());
assert!(output_text(&rollback.stdout).contains("index-compat-pack-rollback-v1"));
let policy_path = base
.join("config")
.join("index")
.join("compat-pack-runtime.conf");
if let Some(parent) = policy_path.parent() {
fs::create_dir_all(parent)?;
}
fs::write(
&policy_path,
"enabled=false\nallow_user=true\nallow_trusted=true\n",
)?;
let inspect_disabled = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("inspect"),
OsString::from("https://example.org/docs/page"),
])
.output()?;
assert!(inspect_disabled.status.success());
let inspect_disabled_stdout = output_text(&inspect_disabled.stdout);
assert!(inspect_disabled_stdout.contains("policy: enabled=false"));
assert!(inspect_disabled_stdout.contains("selected: none"));
Ok(())
}
#[test]
fn auth_assist_import_export_inspect_and_challenge() -> Result<(), Box<dyn std::error::Error>> {
let base = unique_temp_dir("auth-assist");
fs::create_dir_all(&base)?;
let bundle_path = base.join("cookies.store");
fs::write(
&bundle_path,
"index-cookies-v1\nhttps://news.ycombinator.com\tsid=secret; HttpOnly=true; Secure=true\n",
)?;
let imported = index_command_with_base(&base)
.args([
OsString::from("auth-assist"),
OsString::from("import"),
bundle_path.clone().into_os_string(),
])
.output()?;
assert!(imported.status.success());
assert!(output_text(&imported.stdout).contains("index-auth-assist-import-v1"));
let inspected = index_command_with_base(&base)
.args([
OsString::from("auth-assist"),
OsString::from("inspect"),
OsString::from("news.ycombinator.com"),
])
.output()?;
assert!(inspected.status.success());
let inspected_stdout = output_text(&inspected.stdout);
assert!(inspected_stdout.contains("index-auth-assist-inspect-v1"));
assert!(inspected_stdout.contains("origin_cookie_names: sid"));
let export_path = base.join("cookies.exported.store");
let exported = index_command_with_base(&base)
.args([
OsString::from("auth-assist"),
OsString::from("export"),
export_path.clone().into_os_string(),
])
.output()?;
assert!(exported.status.success());
assert!(output_text(&exported.stdout).contains("index-auth-assist-export-v1"));
assert!(fs::read_to_string(export_path)?.contains("index-cookies-v1"));
let diagnosed = index_command_with_base(&base)
.args([
OsString::from("auth-assist"),
OsString::from("diagnose-submit"),
OsString::from("example.org/login"),
OsString::from("419"),
OsString::from("expired"),
OsString::from("session"),
])
.output()?;
assert!(diagnosed.status.success());
assert!(output_text(&diagnosed.stdout).contains("class: session-expired"));
let challenge = index_command_with_base(&base)
.args([
OsString::from("challenge-diagnose"),
OsString::from("example.org"),
OsString::from("country not supported in your region"),
])
.output()?;
assert!(challenge.status.success());
assert!(output_text(&challenge.stdout).contains("class: geo-gate"));
Ok(())
}
#[test]
fn compatibility_pack_argument_error_paths_are_actionable() -> Result<(), Box<dyn std::error::Error>>
{
let base = unique_temp_dir("pack-arg-errors");
fs::create_dir_all(&base)?;
let inspect_extra = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("inspect"),
OsString::from("https://example.org"),
OsString::from("extra"),
])
.output()?;
assert!(!inspect_extra.status.success());
assert!(output_text(&inspect_extra.stderr).contains("too many arguments"));
let list_extra = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("list"),
OsString::from("extra"),
])
.output()?;
assert!(!list_extra.status.success());
assert!(output_text(&list_extra.stderr).contains("too many arguments"));
let rollback_missing = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("rollback"),
OsString::from("missing-pack"),
])
.output()?;
assert!(!rollback_missing.status.success());
assert!(output_text(&rollback_missing.stderr).contains("rollback directory"));
let unknown = index_command_with_base(&base)
.args([
OsString::from("compatibility-pack"),
OsString::from("unknown"),
])
.output()?;
assert!(!unknown.status.success());
assert!(output_text(&unknown.stderr).contains("unsupported compatibility-pack command"));
Ok(())
}
#[test]
fn challenge_diagnose_classifies_multiple_blocked_flow_types()
-> Result<(), Box<dyn std::error::Error>> {
let base = unique_temp_dir("challenge-cases");
fs::create_dir_all(&base)?;
let captcha = index_command_with_base(&base)
.args([
OsString::from("challenge-diagnose"),
OsString::from("example.org"),
OsString::from("captcha"),
])
.output()?;
assert!(captcha.status.success());
assert!(output_text(&captcha.stdout).contains("class: captcha"));
let auth_wall = index_command_with_base(&base)
.args([
OsString::from("challenge-diagnose"),
OsString::from("example.org"),
OsString::from("login required"),
])
.output()?;
assert!(auth_wall.status.success());
assert!(output_text(&auth_wall.stdout).contains("class: auth-wall"));
let policy_block = index_command_with_base(&base)
.args([
OsString::from("challenge-diagnose"),
OsString::from("example.org"),
OsString::from("access denied"),
])
.output()?;
assert!(policy_block.status.success());
assert!(output_text(&policy_block.stdout).contains("class: policy-blocked"));
Ok(())
}