#![allow(dead_code)]
use rusqlite::Connection;
use std::fs;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Output, Stdio};
use tempfile::TempDir;
pub const NOW: &str = "now";
pub const LONG_AGO: &str = "2000-01-01";
pub const SESSION: &str = "current";
pub const SESSION_SPEND: u64 = 5_000_000_000_000;
pub fn payload(session_id: &str, session_nano_aiu: u64) -> String {
format!(
r#"{{"session_id":"{session_id}","context_window":{{"current_context_tokens":1000,"displayed_context_limit":200000,"current_context_used_percentage":1,"total_input_tokens":1000,"total_output_tokens":100}},"ai_used":{{"total_nano_aiu":{session_nano_aiu}}}}}"#
)
}
pub struct Sandbox {
home: TempDir,
}
impl Sandbox {
pub fn new() -> Self {
Self {
home: tempfile::tempdir().unwrap(),
}
}
pub fn home(&self) -> &Path {
self.home.path()
}
pub fn copilot_dir(&self) -> PathBuf {
self.home().join(".copilot")
}
pub fn state_dir(&self) -> PathBuf {
let cache = if cfg!(target_os = "macos") {
self.home().join("Library/Caches")
} else {
self.home().join(".cache")
};
cache.join("copilot-powerline")
}
pub fn write_config(&self, toml: &str) {
fs::create_dir_all(self.copilot_dir()).unwrap();
fs::write(self.copilot_dir().join("powerline.toml"), toml).unwrap();
}
pub fn create_default_db(&self) -> Connection {
create_db(&self.copilot_dir().join("session-store.db"))
}
fn command(&self, args: &[&str], envs: &[(&str, &Path)]) -> Command {
let mut command = Command::new(env!("CARGO_BIN_EXE_copilot-powerline"));
command
.args(args)
.env_remove("COPILOT_POWERLINE_LOG")
.envs(envs.iter().copied())
.env("HOME", self.home())
.env_remove("XDG_CACHE_HOME")
.current_dir(self.home());
command
}
pub fn refresh(&self, payload: &str, args: &[&str]) -> String {
self.refresh_with_env(payload, args, &[])
}
pub fn refresh_with_env(&self, payload: &str, args: &[&str], envs: &[(&str, &Path)]) -> String {
let mut child = self
.command(args, envs)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.unwrap();
child
.stdin
.take()
.unwrap()
.write_all(payload.as_bytes())
.unwrap();
let output = child.wait_with_output().unwrap();
assert!(
output.status.success(),
"refresh failed: {}",
String::from_utf8_lossy(&output.stderr)
);
String::from_utf8(output.stdout)
.unwrap()
.trim_end()
.to_string()
}
pub fn run(&self, args: &[&str]) -> Output {
self.command(args, &[])
.stdin(Stdio::null())
.output()
.unwrap()
}
pub fn refresh_plain(&self) -> String {
self.refresh(
&payload(SESSION, SESSION_SPEND),
&["--style", "plain", "--icon-set", "plain"],
)
}
pub fn state_files(&self, prefix: &str) -> Vec<PathBuf> {
let Ok(entries) = fs::read_dir(self.state_dir()) else {
return Vec::new();
};
entries
.map(|entry| entry.unwrap().path())
.filter(|path| {
path.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.starts_with(prefix))
})
.collect()
}
}
pub fn create_db(path: &Path) -> Connection {
fs::create_dir_all(path.parent().unwrap()).unwrap();
let conn = Connection::open(path).unwrap();
conn.execute(
"CREATE TABLE assistant_usage_events (
session_id TEXT,
total_nano_aiu INTEGER,
created_at TEXT
)",
[],
)
.unwrap();
conn
}
pub fn assert_shows(line: &str, text: &str) {
assert!(line.contains(text), "expected {text:?} in {line:?}");
}
pub fn insert_usage(conn: &Connection, session_id: &str, nano_aiu: i64, created_at: &str) {
conn.execute(
"INSERT INTO assistant_usage_events (session_id, total_nano_aiu, created_at)
VALUES (?1, ?2, datetime(?3))",
(session_id, nano_aiu, created_at),
)
.unwrap();
}