pub(crate) mod fs;
pub(crate) mod proc;
pub(crate) mod user;
use super::administered::Administered;
use super::{Elevation, Host, LoginPath, Os, Process, Scheduler};
use crate::context::{Context, Environment};
use crate::store::{Backend, PlainFile, RawStore, Unbuilt};
use std::path::PathBuf;
use std::sync::Arc;
pub(super) const OS: Os = Os::Windows;
pub(super) fn login_path(_env: &Environment) -> LoginPath {
LoginPath::Unknown
}
#[derive(Debug)]
struct Windows;
impl Host for Windows {
fn foreign_secrets(&self, _ctx: &Context, _account: &str) -> Option<Box<dyn RawStore>> {
None
}
fn file(&self, path: PathBuf) -> Box<dyn RawStore> {
Box::new(PlainFile::at(path))
}
fn vault(&self, _ctx: &Context) -> Box<dyn RawStore> {
Box::new(Unbuilt::new(
Backend::Unknown,
"Pitboard does not park logins on Windows yet",
))
}
fn vault_is_shared(&self) -> bool {
true
}
fn processes(&self, _program: &str) -> Option<Vec<Process>> {
None
}
fn scheduler(&self) -> Option<&dyn Scheduler> {
None
}
fn elevation(&self, ctx: &Context) -> Elevation {
user::elevation(ctx.sudo())
}
fn managed_preference(&self, _domain: &str, _key: &str) -> Administered {
Administered::Unset
}
}
pub(super) fn host() -> Arc<dyn Host> {
Arc::new(Windows)
}
pub(super) fn current_program() -> std::io::Result<PathBuf> {
std::env::current_exe()
}
#[cfg(any(test, feature = "test-support"))]
pub(super) fn pretend_scheduler(
_refuse_start: Arc<std::sync::atomic::AtomicBool>,
) -> Option<Box<dyn Scheduler>> {
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::provider::ProviderId;
use crate::store::Error;
fn ctx() -> Context {
Context::for_unit_test()
}
fn unreadable<T: std::fmt::Debug>(read: Result<T, Error>, why: &str) {
match read {
Err(Error::Unreadable(said)) => assert_eq!(said, why),
other => panic!("expected the store to be unreadable, {why}: {other:?}"),
}
}
#[test]
fn claude_codes_chain_is_unreadable_and_never_the_file() {
let ctx = ctx();
let why = "Pitboard does not switch Claude Code on Windows yet";
let live = crate::provider::of(ProviderId::Claude)
.live(&ctx)
.expect("a chain");
assert_eq!(
crate::store::resolve(&live.chain, &live.service).map_err(|e| e.to_string()),
Err(format!("the credential store could not be read: {why}"))
);
unreadable(crate::store::read_raw(&live.chain, &live.service), why);
let dir = std::env::temp_dir();
unreadable(crate::provider::claude::live::read_signin(&ctx, &dir), why);
unreadable(
crate::provider::claude::live::discard_signin(
&ctx,
crate::service::Permit::for_a_test(),
&dir,
),
why,
);
}
#[test]
fn codexs_store_is_refused_until_its_configuration_is_read() {
let ctx = ctx();
match crate::provider::of(ProviderId::Codex).live(&ctx) {
Err(crate::provider::ProviderError::Unsupported {
provider: ProviderId::Codex,
reason,
}) => assert!(
reason.contains("Pitboard does not read Codex's configuration on this system yet"),
"{reason}"
),
Err(other) => panic!("refused otherwise: {other:?}"),
Ok(_) => panic!("a live store Pitboard cannot tell is refused"),
}
}
#[test]
fn the_vault_is_unreadable() {
let ctx = ctx();
let vault = ctx.host().vault(&ctx);
let why = "Pitboard does not park logins on Windows yet";
assert_eq!(vault.kind(), Backend::Unknown);
assert_eq!(Backend::Unknown.name(), "unknown");
unreadable(vault.contains("pitboard-park"), why);
unreadable(vault.read("pitboard-park"), why);
unreadable(vault.list(), why);
let permit = crate::service::Permit::for_a_test();
unreadable(vault.write(permit, "pitboard-park", "{}"), why);
unreadable(vault.delete(permit, "pitboard-park"), why);
}
#[test]
fn holders_are_unknown() {
let ctx = ctx();
for tool in ["claude", "codex"] {
assert_eq!(ctx.host().processes(tool), None, "{tool}");
}
assert!(super::proc::may_be_running(std::process::id()));
assert!(super::proc::may_be_running(u32::MAX));
}
#[test]
fn there_is_no_scheduler() {
assert!(host().scheduler().is_none());
let memory = crate::host::memory::MemoryHost::new();
assert!(memory.scheduler().is_none());
assert!(matches!(
crate::schedule::status(&ctx()),
crate::schedule::Installed::Unsupported
));
}
#[test]
fn elevation_is_unknown_and_refused() {
let ctx = ctx();
assert_eq!(ctx.host().elevation(&ctx), Elevation::Unknown);
assert_eq!(user::elevation(true), Elevation::Unknown);
assert!(matches!(
crate::service::gate(&ctx),
Err(crate::error::Error::Elevated { why: None })
));
}
#[test]
fn no_file_is_made_and_no_program_found() {
let dir = std::env::temp_dir().join(format!("pitboard-windows-{}", std::process::id()));
let permit = crate::service::Permit::for_a_test();
let unsupported = |made: std::io::Result<()>| {
assert_eq!(
made.map_err(|e| e.kind()),
Err(std::io::ErrorKind::Unsupported)
);
};
unsupported(fs::create_private_dir(permit, &dir));
unsupported(fs::create_private(permit, &dir.join("a")).map(drop));
unsupported(fs::open_private_append(permit, &dir.join("b")).map(drop));
unsupported(fs::open_private_lock(permit, &dir.join("c")).map(drop));
unsupported(fs::create_dir_all(permit, &dir));
unsupported(fs::create_dir(permit, &dir));
assert!(!dir.exists(), "nothing was made");
assert_eq!(fs::access(&std::env::temp_dir()), None);
assert!(!proc::can_run(&std::env::current_exe().expect("this test")));
assert_eq!(login_path(&Environment::default()), LoginPath::Unknown);
assert_eq!(user::login_name(), None);
}
}