use std::time::Duration;
use simvar::plan::InteractionPlan;
use strum::{EnumDiscriminants, EnumIter};
use crate::host::moosicbox_server::{HOST, PORT};
pub struct InteractionPlanContext {}
impl Default for InteractionPlanContext {
fn default() -> Self {
Self::new()
}
}
impl InteractionPlanContext {
#[must_use]
pub const fn new() -> Self {
Self {}
}
}
pub struct HealthCheckInteractionPlan {
#[allow(unused)]
context: InteractionPlanContext,
step: u64,
pub plan: Vec<Interaction>,
}
impl Default for HealthCheckInteractionPlan {
fn default() -> Self {
Self::new()
}
}
impl HealthCheckInteractionPlan {
#[must_use]
pub const fn new() -> Self {
Self {
context: InteractionPlanContext::new(),
step: 0,
plan: vec![],
}
}
}
#[derive(Clone, Debug, EnumDiscriminants)]
#[strum_discriminants(derive(EnumIter))]
#[strum_discriminants(name(InteractionType))]
pub enum Interaction {
Sleep(Duration),
HealthCheck(String),
}
impl InteractionPlan<Interaction> for HealthCheckInteractionPlan {
fn step(&mut self) -> Option<&Interaction> {
#[allow(clippy::cast_possible_truncation)]
if let Some(item) = self.plan.get(self.step as usize) {
self.step += 1;
log::debug!("step: {}", self.step);
Some(item)
} else {
None
}
}
fn gen_interactions(&mut self, count: u64) {
self.plan.clear();
self.step = 0;
let len = self.plan.len() as u64;
for i in 1..=count {
let interaction_type = if (i + len).is_multiple_of(2) {
InteractionType::Sleep
} else {
InteractionType::HealthCheck
};
log::trace!(
"gen_interactions: generating interaction {i}/{count} ({}) interaction_type={interaction_type:?}",
i + len
);
match interaction_type {
InteractionType::Sleep => {
#[allow(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
self.add_interaction(Interaction::Sleep(Duration::from_secs(1)));
}
InteractionType::HealthCheck => {
self.add_interaction(Interaction::HealthCheck(format!("{HOST}:{PORT}")));
}
}
}
}
fn add_interaction(&mut self, interaction: Interaction) {
log::trace!("add_interaction: adding interaction interaction={interaction:?}");
match &interaction {
Interaction::Sleep(..) | Interaction::HealthCheck(..) => {}
}
self.plan.push(interaction);
}
}
#[cfg(test)]
mod tests {
use super::*;
mod health_check_interaction_plan {
use super::*;
#[test_log::test]
fn step_returns_none_for_empty_plan() {
let mut plan = HealthCheckInteractionPlan::new();
assert!(plan.step().is_none());
}
#[test_log::test]
fn step_iterates_through_plan_sequentially() {
let mut plan = HealthCheckInteractionPlan::new();
plan.add_interaction(Interaction::Sleep(Duration::from_millis(100)));
plan.add_interaction(Interaction::HealthCheck("host:1234".to_string()));
plan.add_interaction(Interaction::Sleep(Duration::from_millis(200)));
let first = plan.step();
assert!(first.is_some());
assert!(
matches!(first.unwrap(), Interaction::Sleep(d) if *d == Duration::from_millis(100))
);
let second = plan.step();
assert!(second.is_some());
assert!(matches!(second.unwrap(), Interaction::HealthCheck(h) if h == "host:1234"));
let third = plan.step();
assert!(third.is_some());
assert!(
matches!(third.unwrap(), Interaction::Sleep(d) if *d == Duration::from_millis(200))
);
assert!(plan.step().is_none());
}
#[test_log::test]
fn add_interaction_appends_to_plan() {
let mut plan = HealthCheckInteractionPlan::new();
assert!(plan.plan.is_empty());
plan.add_interaction(Interaction::Sleep(Duration::from_secs(1)));
assert_eq!(plan.plan.len(), 1);
plan.add_interaction(Interaction::HealthCheck("localhost:8080".to_string()));
assert_eq!(plan.plan.len(), 2);
}
#[test_log::test]
fn gen_interactions_creates_alternating_interactions() {
let mut plan = HealthCheckInteractionPlan::new();
plan.gen_interactions(4);
assert_eq!(plan.plan.len(), 4);
assert!(matches!(&plan.plan[0], Interaction::HealthCheck(_)));
assert!(matches!(&plan.plan[1], Interaction::Sleep(_)));
assert!(matches!(&plan.plan[2], Interaction::HealthCheck(_)));
assert!(matches!(&plan.plan[3], Interaction::Sleep(_)));
}
#[test_log::test]
fn gen_interactions_clears_existing_plan_and_resets_step() {
let mut plan = HealthCheckInteractionPlan::new();
plan.add_interaction(Interaction::Sleep(Duration::from_secs(1)));
plan.step();
plan.gen_interactions(2);
assert_eq!(plan.plan.len(), 2);
let first = plan.step();
assert!(first.is_some());
}
#[test_log::test]
fn gen_interactions_generates_health_check_with_correct_host_port() {
let mut plan = HealthCheckInteractionPlan::new();
plan.gen_interactions(1);
if let Interaction::HealthCheck(host) = &plan.plan[0] {
assert_eq!(host, &format!("{HOST}:{PORT}"));
} else {
panic!("Expected HealthCheck interaction");
}
}
#[test_log::test]
fn gen_interactions_generates_sleep_with_1000ms_duration() {
let mut plan = HealthCheckInteractionPlan::new();
plan.gen_interactions(2);
if let Interaction::Sleep(duration) = &plan.plan[1] {
assert_eq!(*duration, Duration::from_secs(1));
} else {
panic!("Expected Sleep interaction");
}
}
}
}