use std::fs;
use super::*;
struct Scratch(std::path::PathBuf);
impl Scratch {
fn new(name: &str) -> Self {
let dir = std::env::temp_dir().join(format!("modelpipe-{}-{name}", std::process::id()));
fs::create_dir_all(&dir).expect("a scratch directory");
Self(dir)
}
fn join(&self, file: &str) -> std::path::PathBuf {
self.0.join(file)
}
}
impl Drop for Scratch {
fn drop(&mut self) {
let _ = fs::remove_dir_all(&self.0);
}
}
#[test]
fn a_stored_identity_is_the_same_identity_next_time() {
let scratch = Scratch::new("same");
let path = scratch.join("key");
let first = load_or_mint(&path).expect("a first run mints one");
let second = load_or_mint(&path).expect("a second run reads it back");
assert_eq!(first, second, "a restart must not change the identity");
}
#[test]
fn two_identity_files_are_two_identities() {
let scratch = Scratch::new("distinct");
let one = load_or_mint(&scratch.join("one")).expect("mints");
let two = load_or_mint(&scratch.join("two")).expect("mints");
assert_ne!(one, two, "each file gets its own key");
}
#[test]
fn the_stored_form_is_lowercase_base32_on_one_line() {
let scratch = Scratch::new("form");
let path = scratch.join("key");
let minted = load_or_mint(&path).expect("mints");
let written = fs::read_to_string(&path).expect("readable");
assert!(written.ends_with('\n'), "one line: {written:?}");
let body = written.trim();
assert_eq!(body, body.to_ascii_lowercase(), "lowercase: {body:?}");
assert_eq!(
base32::decode(&body.to_ascii_uppercase()).as_deref(),
Some(&minted[..]),
"and it decodes to the key that was handed back"
);
}
#[test]
fn a_file_that_is_not_base32_is_refused() {
let scratch = Scratch::new("garbage");
let path = scratch.join("key");
fs::write(&path, "not a key!!!\n").expect("write");
let refused = load_or_mint(&path).expect_err("must not be accepted");
assert!(
matches!(refused, ServeError::Identity { .. }),
"got: {refused:?}"
);
assert!(!refused.is_retryable(), "the operator named this path");
}
#[test]
fn a_file_of_the_wrong_length_is_refused() {
let scratch = Scratch::new("short");
let path = scratch.join("key");
fs::write(
&path,
format!("{}\n", base32::encode(b"too short")).to_lowercase(),
)
.expect("write");
assert!(
matches!(load_or_mint(&path), Err(ServeError::Identity { .. })),
"a key is exactly {KEY_BYTES} bytes"
);
}
#[cfg(unix)]
#[test]
fn an_identity_others_can_read_is_refused() {
use std::os::unix::fs::PermissionsExt as _;
let scratch = Scratch::new("exposed");
let path = scratch.join("key");
load_or_mint(&path).expect("mints");
fs::set_permissions(&path, fs::Permissions::from_mode(0o644)).expect("chmod");
let refused = load_or_mint(&path).expect_err("a readable key must be refused");
let explained = format!("{}", std::error::Error::source(&refused).expect("a cause"));
assert!(
explained.contains("chmod"),
"and it must say what to do: {explained}"
);
}
#[cfg(unix)]
#[test]
fn the_identity_this_module_writes_is_one_it_will_read() {
use std::os::unix::fs::PermissionsExt as _;
let scratch = Scratch::new("owner-only");
let path = scratch.join("key");
load_or_mint(&path).expect("mints");
let mode = fs::metadata(&path).expect("metadata").permissions().mode();
assert_eq!(mode & 0o077, 0, "created owner-only: {mode:04o}");
load_or_mint(&path).expect("and read back without complaint");
}