use std::fs;
use std::time::{Duration, UNIX_EPOCH};
use super::{PairingError, PairingStore};
#[test]
fn bootstrap_token_is_one_time_and_plaintexts_are_never_persisted() {
let directory = tempfile::tempdir().unwrap();
let path = directory.path().join("pairs.json");
let store = PairingStore::open(path.clone()).unwrap();
let token = store.issue_token(None).unwrap().expose_once();
assert_eq!(token.len(), 32);
assert!(token.bytes().all(|byte| byte.is_ascii_hexdigit()));
let persisted = fs::read_to_string(&path).unwrap();
assert!(!persisted.contains(&token));
assert_eq!(serde_json::from_str::<serde_json::Value>(&persisted).unwrap()["version"], 1);
let enrollment = store.consume_token(&token, "tablet".to_string(), "Tablet".to_string()).unwrap();
let bearer = enrollment.expose_credential_once();
assert_eq!(bearer.len(), 64);
assert!(bearer.bytes().all(|byte| byte.is_ascii_hexdigit()));
assert!(!fs::read_to_string(&path).unwrap().contains(&bearer));
assert_eq!(store.authenticate_bearer(&bearer).unwrap().as_str(), "paired-device:tablet");
assert!(matches!(store.consume_token(&token, "other".to_string(), "Other".to_string()), Err(PairingError::InvalidPairingToken)));
assert!(PairingStore::open(path.clone()).is_err(), "one process owns the pairing store lock");
store.revoke_device_credentials("tablet").unwrap();
assert!(matches!(store.authenticate_bearer(&bearer), Err(PairingError::Unauthenticated)));
drop(store);
let reopened = PairingStore::open(path).unwrap();
assert!(matches!(reopened.authenticate_bearer(&bearer), Err(PairingError::Unauthenticated)));
}
#[test]
fn bootstrap_token_expires_only_when_a_ttl_was_requested() {
let directory = tempfile::tempdir().unwrap();
let store = PairingStore::open(directory.path().join("pairs.json")).unwrap();
let issued_at = UNIX_EPOCH + Duration::from_secs(100);
let expiring = store.issue_token_at(Some(Duration::from_secs(30)), issued_at).unwrap().expose_once();
assert!(matches!(
store.consume_token_at(&expiring, "late".to_string(), "Late".to_string(), UNIX_EPOCH + Duration::from_secs(130)),
Err(PairingError::InvalidPairingToken)
));
let persistent = store.issue_token_at(None, issued_at).unwrap().expose_once();
store
.consume_token_at(&persistent, "later".to_string(), "Later".to_string(), UNIX_EPOCH + Duration::from_secs(10_000))
.expect("token without explicit TTL remains valid");
}
#[test]
fn disable_and_remove_apply_to_the_next_bearer_lookup() {
let directory = tempfile::tempdir().unwrap();
let store = PairingStore::open(directory.path().join("pairs.json")).unwrap();
let token = store.issue_token(None).unwrap().expose_once();
let bearer = store.consume_token(&token, "tablet".to_string(), "Tablet".to_string()).unwrap().expose_credential_once();
assert!(store.set_enabled("tablet", false).unwrap());
assert!(!store.set_enabled("tablet", false).unwrap());
assert!(matches!(store.authenticate_bearer(&bearer), Err(PairingError::Unauthenticated)));
assert!(store.set_enabled("tablet", true).unwrap());
assert!(store.authenticate_bearer(&bearer).is_ok());
store.remove_pair("tablet").unwrap();
assert!(matches!(store.authenticate_bearer(&bearer), Err(PairingError::Unauthenticated)));
}
#[test]
fn credential_revocation_resolves_labels_and_id_prefixes() {
let directory = tempfile::tempdir().unwrap();
let store = PairingStore::open(directory.path().join("pairs.json")).unwrap();
let first_token = store.issue_token(None).unwrap().expose_once();
let first = store.consume_token(&first_token, "device-tablet-0123".to_string(), "Tablet".to_string()).unwrap().expose_credential_once();
let second_token = store.issue_token(None).unwrap().expose_once();
let second = store.consume_token(&second_token, "device-phone-4567".to_string(), "Phone".to_string()).unwrap().expose_credential_once();
assert!(auv_api_server::control::Pairing::revoke_device_credentials(&store, "Tablet").unwrap());
assert!(matches!(store.authenticate_bearer(&first), Err(PairingError::Unauthenticated)));
assert!(auv_api_server::control::Pairing::revoke_device_credentials(&store, "device-phone").unwrap());
assert!(matches!(store.authenticate_bearer(&second), Err(PairingError::Unauthenticated)));
}