#![cfg(unix)]
use rusqlite::Connection;
use sessionwiki::index;
use std::fs;
use std::os::unix::fs::PermissionsExt;
use std::path::Path;
fn archived(conn: &Connection, like: &str) -> Option<bool> {
conn.query_row(
"SELECT archived_at IS NOT NULL FROM files WHERE path LIKE ?1",
[format!("%{like}%")],
|r| r.get(0),
)
.ok()
}
fn chmod(path: &Path, mode: u32) {
fs::set_permissions(path, fs::Permissions::from_mode(mode)).unwrap();
}
#[test]
fn partial_walk_does_not_archive_live_sessions() {
if unsafe { libc_geteuid() } == 0 {
eprintln!("skipping: running as root");
return;
}
let home = std::env::temp_dir().join("sessionwiki-test-sync-guard");
let _ = fs::remove_dir_all(&home); let proj = home.join(".claude").join("projects").join("proj-a");
fs::create_dir_all(&proj).unwrap();
let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/claude-code/proj-a/0a000000-0000-4000-8000-000000000001.jsonl");
let session = proj.join("0a000000-0000-4000-8000-000000000001.jsonl");
fs::copy(&fixture, &session).unwrap();
std::env::set_var("HOME", &home);
std::env::set_var("SESSIONWIKI_DATA", home.join("data"));
let mut conn = index::open().unwrap();
index::sync(&mut conn, Some("claude-code")).unwrap();
assert_eq!(
archived(&conn, "0a000000"),
Some(false),
"session indexed live after the first sync"
);
chmod(&proj, 0o000);
let r = index::sync(&mut conn, Some("claude-code"));
chmod(&proj, 0o755); r.unwrap();
assert_eq!(
archived(&conn, "0a000000"),
Some(false),
"a partial walk must not archive live sessions"
);
index::sync(&mut conn, Some("claude-code")).unwrap();
assert_eq!(archived(&conn, "0a000000"), Some(false));
fs::remove_file(&session).unwrap();
index::sync(&mut conn, Some("claude-code")).unwrap();
assert_eq!(
archived(&conn, "0a000000"),
Some(true),
"real deletions still archive on a clean walk"
);
}
#[test]
fn parse_failures_are_warned_and_counted_not_swallowed() {
if unsafe { libc_geteuid() } == 0 {
eprintln!("skipping: running as root");
return;
}
let home = std::env::temp_dir().join("sessionwiki-test-sync-warn");
let _ = fs::remove_dir_all(&home);
let proj = home.join(".claude").join("projects").join("proj-a");
fs::create_dir_all(&proj).unwrap();
let fixture = Path::new(env!("CARGO_MANIFEST_DIR"))
.join("tests/fixtures/claude-code/proj-a/0a000000-0000-4000-8000-000000000001.jsonl");
fs::copy(
&fixture,
proj.join("0a000000-0000-4000-8000-000000000001.jsonl"),
)
.unwrap();
let bad = proj.join("0b000000-0000-4000-8000-000000000002.jsonl");
fs::write(&bad, "{}").unwrap();
chmod(&bad, 0o000);
let out = std::process::Command::new(env!("CARGO_BIN_EXE_sessionwiki"))
.args(["sync", "--tool", "claude-code"])
.env("HOME", &home)
.env("SESSIONWIKI_DATA", home.join("data"))
.output()
.unwrap();
chmod(&bad, 0o644); let stderr = String::from_utf8_lossy(&out.stderr);
assert!(out.status.success(), "sync must not die: {stderr}");
assert!(
stderr.contains("failed to parse"),
"the failure must be warned: {stderr}"
);
assert!(
stderr.contains("(1 failed to parse)"),
"the count line must be honest: {stderr}"
);
assert!(
stderr.contains("indexed 1/2"),
"only the good session counts as indexed: {stderr}"
);
}
extern "C" {
#[link_name = "geteuid"]
fn libc_geteuid() -> u32;
}