Radish alpha
r
Radicle CI broker
Radicle
Git (anonymous pull)
Log in to clone via SSH
feat(src/broker.rs): capture CI broker business logic
Lars Wirzenius committed 2 years ago
commit 7c562e1cbf5e356bb62c5f1eccc57bb88c44e7c5
parent 1c90e35cef8d04745536199ec37d81b7b9597a73
1 file changed +166 -0
added src/broker.rs
@@ -0,0 +1,166 @@
+
//! The business logic of the CI broker.
+
//!
+
//! This is type and module of its own to facilitate automated
+
//! testing.
+

+
use std::collections::HashMap;
+

+
use radicle::identity::RepoId;
+

+
use crate::{adapter::Adapter, error::BrokerError, msg::Request, run::Run};
+

+
/// A CI broker.
+
///
+
/// The broker gets repository change events from the local Radicle
+
/// node, and executes the appropriate adapter to run CI on the
+
/// repository.
+
#[derive(Debug, Default)]
+
pub struct Broker {
+
    default_adapter: Option<Adapter>,
+
    adapters: HashMap<RepoId, Adapter>,
+
}
+

+
impl Broker {
+
    pub fn set_default_adapter(&mut self, adapter: &Adapter) {
+
        self.default_adapter = Some(adapter.clone());
+
    }
+

+
    pub fn default_adapter(&self) -> Option<&Adapter> {
+
        self.default_adapter.as_ref()
+
    }
+

+
    pub fn set_repository_adapter(&mut self, rid: &RepoId, adapter: &Adapter) {
+
        self.adapters.insert(*rid, adapter.clone());
+
    }
+

+
    pub fn adapter(&self, rid: &RepoId) -> Option<&Adapter> {
+
        self.adapters.get(rid).or(self.default_adapter.as_ref())
+
    }
+

+
    #[allow(clippy::result_large_err)]
+
    pub fn execute_ci(&self, trigger: &Request) -> Result<Run, BrokerError> {
+
        let adapter = match trigger {
+
            Request::Trigger {
+
                common,
+
                push: _,
+
                patch: _,
+
            } => {
+
                let rid = &common.repository.id;
+
                if let Some(adapter) = self.adapter(rid) {
+
                    adapter
+
                } else {
+
                    return Err(BrokerError::NoAdapter(*rid));
+
                }
+
            }
+
        };
+

+
        let mut run = Run::default();
+
        adapter.run(trigger, &mut run)?;
+

+
        Ok(run)
+
    }
+
}
+

+
#[cfg(test)]
+
mod test {
+
    use tempfile::tempdir;
+

+
    use super::{Adapter, Broker, RepoId};
+
    use crate::{
+
        msg::{RunId, RunResult},
+
        run::RunState,
+
        test::{mock_adapter, trigger_request, TestResult},
+
    };
+

+
    fn rid() -> RepoId {
+
        const RID: &str = "rad:zwTxygwuz5LDGBq255RA2CbNGrz8";
+
        RepoId::from_urn(RID).unwrap()
+
    }
+

+
    fn rid2() -> RepoId {
+
        const RID: &str = "rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5";
+
        RepoId::from_urn(RID).unwrap()
+
    }
+

+
    #[test]
+
    fn has_no_adapters_initially() -> TestResult<()> {
+
        let broker = Broker::default();
+
        let rid = rid();
+
        assert_eq!(broker.adapter(&rid), None);
+
        Ok(())
+
    }
+

+
    #[test]
+
    fn adds_adapter() -> TestResult<()> {
+
        let mut broker = Broker::default();
+
        let adapter = Adapter::default();
+
        let rid = rid();
+
        broker.set_repository_adapter(&rid, &adapter);
+
        assert_eq!(broker.adapter(&rid), Some(&adapter));
+
        Ok(())
+
    }
+

+
    #[test]
+
    fn does_not_find_unknown_repo() -> TestResult<()> {
+
        let mut broker = Broker::default();
+
        let adapter = Adapter::default();
+
        let rid = rid();
+
        let rid2 = rid2();
+
        broker.set_repository_adapter(&rid, &adapter);
+
        assert_eq!(broker.adapter(&rid2), None);
+
        Ok(())
+
    }
+

+
    #[test]
+
    fn does_not_have_a_default_adapter_initially() -> TestResult<()> {
+
        let broker = Broker::default();
+
        assert_eq!(broker.default_adapter(), None);
+
        Ok(())
+
    }
+

+
    #[test]
+
    fn sets_a_default_adapter_initially() -> TestResult<()> {
+
        let mut broker = Broker::default();
+
        let adapter = Adapter::default();
+
        broker.set_default_adapter(&adapter);
+
        assert_eq!(broker.default_adapter(), Some(&adapter));
+
        Ok(())
+
    }
+

+
    #[test]
+
    fn finds_default_adapter_for_unknown_repo() -> TestResult<()> {
+
        let mut broker = Broker::default();
+
        let adapter = Adapter::default();
+
        broker.set_default_adapter(&adapter);
+

+
        let rid = rid();
+
        assert_eq!(broker.adapter(&rid), Some(&adapter));
+
        Ok(())
+
    }
+

+
    #[test]
+
    fn exectues_adapter() -> TestResult<()> {
+
        const ADAPTER: &str = r#"#!/bin/bash
+
echo '{"response":"triggered","run_id":{"id":"xyzzy"}}'
+
echo '{"response":"finished","result":"success"}'
+
"#;
+

+
        let tmp = tempdir()?;
+
        let bin = tmp.path().join("adapter.sh");
+
        let adapter = mock_adapter(&bin, ADAPTER)?;
+

+
        let mut broker = Broker::default();
+
        broker.set_default_adapter(&adapter);
+

+
        let trigger = trigger_request()?;
+

+
        let x = broker.execute_ci(&trigger);
+
        assert!(x.is_ok());
+
        let run = x.unwrap();
+
        assert_eq!(run.adapter_run_id(), Some(&RunId::from("xyzzy")));
+
        assert_eq!(run.state(), RunState::Finished);
+
        assert_eq!(run.result(), Some(&RunResult::Success));
+

+
        Ok(())
+
    }
+
}