use std::collections::{BTreeMap, BTreeSet};
use crate::collect::run::RunFailure;
use crate::config::Project;
use crate::model::types::Bead;
pub trait Tracker {
fn fingerprint(&self) -> Option<Result<String, RunFailure>>;
fn all(&self) -> Result<Vec<Bead>, RunFailure>;
fn ready(&self) -> Result<BTreeSet<String>, RunFailure>;
fn blocked(&self) -> Result<BTreeMap<String, Vec<String>>, RunFailure>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum OpenFailure {
NoEnvironment,
NoCredential,
Refused(RunFailure),
}
impl From<RunFailure> for OpenFailure {
fn from(failure: RunFailure) -> Self {
OpenFailure::Refused(failure)
}
}
pub trait Trackers: Sync {
fn of(&self, project: &Project) -> Result<Box<dyn Tracker + '_>, OpenFailure>;
}
impl<T: Tracker + ?Sized> Tracker for &T {
fn fingerprint(&self) -> Option<Result<String, RunFailure>> {
(**self).fingerprint()
}
fn all(&self) -> Result<Vec<Bead>, RunFailure> {
(**self).all()
}
fn ready(&self) -> Result<BTreeSet<String>, RunFailure> {
(**self).ready()
}
fn blocked(&self) -> Result<BTreeMap<String, Vec<String>>, RunFailure> {
(**self).blocked()
}
}
#[cfg(feature = "testing")]
pub mod testing {
use std::sync::Mutex;
use super::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Asked {
Fingerprint,
All,
Ready,
Blocked,
}
pub struct Fake {
fingerprint: Option<Result<String, RunFailure>>,
all: Result<Vec<Bead>, RunFailure>,
ready: Result<BTreeSet<String>, RunFailure>,
blocked: Result<BTreeMap<String, Vec<String>>, RunFailure>,
asked: Mutex<Vec<Asked>>,
}
const UNMOVED: &str = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
impl Fake {
pub fn holding(beads: Vec<Bead>) -> Self {
Self {
fingerprint: Some(Ok(UNMOVED.to_string())),
all: Ok(beads),
ready: Ok(BTreeSet::new()),
blocked: Ok(BTreeMap::new()),
asked: Mutex::new(Vec::new()),
}
}
pub fn also(mut self, beads: Vec<Bead>) -> Self {
if let Ok(all) = &mut self.all {
all.extend(beads);
}
self
}
pub fn ready<'a>(mut self, ids: impl IntoIterator<Item = &'a str>) -> Self {
self.ready = Ok(ids.into_iter().map(str::to_string).collect());
self
}
pub fn blocked(mut self, id: &str, by: &[&str]) -> Self {
if let Ok(blocked) = &mut self.blocked {
blocked.insert(id.to_string(), by.iter().map(|b| b.to_string()).collect());
}
self
}
pub fn moved(mut self) -> Self {
self.fingerprint = Some(Ok("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string()));
self
}
pub fn without_a_fingerprint(mut self) -> Self {
self.fingerprint = None;
self
}
pub fn failing(mut self, question: Asked, failure: RunFailure) -> Self {
match question {
Asked::Fingerprint => self.fingerprint = Some(Err(failure)),
Asked::All => self.all = Err(failure),
Asked::Ready => self.ready = Err(failure),
Asked::Blocked => self.blocked = Err(failure),
}
self
}
pub fn asked(&self) -> Vec<Asked> {
self.asked.lock().unwrap().clone()
}
fn note(&self, question: Asked) {
self.asked.lock().unwrap().push(question);
}
}
impl Tracker for Fake {
fn fingerprint(&self) -> Option<Result<String, RunFailure>> {
self.note(Asked::Fingerprint);
self.fingerprint.clone()
}
fn all(&self) -> Result<Vec<Bead>, RunFailure> {
self.note(Asked::All);
self.all.clone()
}
fn ready(&self) -> Result<BTreeSet<String>, RunFailure> {
self.note(Asked::Ready);
self.ready.clone()
}
fn blocked(&self) -> Result<BTreeMap<String, Vec<String>>, RunFailure> {
self.note(Asked::Blocked);
self.blocked.clone()
}
}
#[derive(Default)]
pub struct Fakes {
by_project: BTreeMap<String, Fake>,
unopenable: BTreeMap<String, OpenFailure>,
}
impl Fakes {
pub fn with(mut self, project: &str, tracker: Fake) -> Self {
self.by_project.insert(project.to_string(), tracker);
self
}
pub fn without_the_credential_it_asked_for(mut self, project: &str) -> Self {
self.unopenable
.insert(project.to_string(), OpenFailure::NoCredential);
self
}
pub fn without_the_environment_it_asked_for(mut self, project: &str) -> Self {
self.unopenable
.insert(project.to_string(), OpenFailure::NoEnvironment);
self
}
pub fn tracker(&self, project: &str) -> &Fake {
self.by_project
.get(project)
.unwrap_or_else(|| panic!("no fake tracker was staged for {project}"))
}
}
impl Trackers for Fakes {
fn of(&self, project: &Project) -> Result<Box<dyn Tracker + '_>, OpenFailure> {
if let Some(failure) = self.unopenable.get(&project.name) {
return Err(failure.clone());
}
Ok(Box::new(self.tracker(&project.name)))
}
}
}