use serde::Serialize;
use crate::error::DaemonError;
use crate::state::{DaemonState, PAIR_CODE_TTL};
#[derive(Debug, Serialize)]
pub struct PairCode {
pub code: String,
pub expires_in_seconds: u64,
}
#[derive(Debug, Serialize)]
pub struct PairStatus {
pub paired: bool,
pub chat_id: Option<i64>,
}
pub struct PairingService<'s> {
state: &'s DaemonState,
}
impl<'s> PairingService<'s> {
pub fn new(state: &'s DaemonState) -> Self {
Self { state }
}
pub fn request_code(&self) -> PairCode {
PairCode {
code: self.state.generate_pair_code(),
expires_in_seconds: PAIR_CODE_TTL.as_secs(),
}
}
pub fn confirm(&self, code: &str, chat_id: i64) -> Result<(), DaemonError> {
if self.state.confirm_pair_code(code, chat_id) {
Ok(())
} else {
Err(DaemonError::InvalidPairCode)
}
}
pub fn status(&self) -> PairStatus {
let chat_id = self.state.paired_chat_id();
PairStatus {
paired: chat_id.is_some(),
chat_id,
}
}
pub fn reset(&self) {
self.state.clear_pairing();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn request_code_has_ttl() {
let state = DaemonState::new();
let svc = PairingService::new(&state);
let code = svc.request_code();
assert_eq!(code.code.len(), 6);
assert_eq!(code.expires_in_seconds, PAIR_CODE_TTL.as_secs());
}
#[test]
fn confirm_round_trip() {
let dir = tempfile::tempdir().expect("temp dir");
let state = DaemonState::with_root(dir.path().to_path_buf());
let svc = PairingService::new(&state);
let code = svc.request_code().code;
svc.confirm(&code, 4242).expect("valid code confirms");
assert_eq!(svc.status().chat_id, Some(4242));
assert!(svc.status().paired);
}
#[test]
fn reset_clears_pairing() {
let dir = tempfile::tempdir().expect("temp dir");
let state = DaemonState::with_root(dir.path().to_path_buf());
let svc = PairingService::new(&state);
let code = svc.request_code().code;
svc.confirm(&code, 99).expect("valid code confirms");
assert!(svc.status().paired);
svc.reset();
assert!(!svc.status().paired);
}
#[test]
fn confirm_rejects_bad_code() {
let dir = tempfile::tempdir().expect("temp dir");
let state = DaemonState::with_root(dir.path().to_path_buf());
let svc = PairingService::new(&state);
let _ = svc.request_code();
let err = svc.confirm("ZZZZZZ", 1).unwrap_err();
assert!(matches!(err, DaemonError::InvalidPairCode));
assert!(!svc.status().paired);
}
}