diff options
author | Lars Wirzenius <liw@liw.fi> | 2024-02-02 17:32:52 +0200 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2024-02-02 17:42:38 +0200 |
commit | 7c562e1cbf5e356bb62c5f1eccc57bb88c44e7c5 (patch) | |
tree | 530bc7b03393bbf38b77c4f582fada416bbc890f | |
parent | 1c90e35cef8d04745536199ec37d81b7b9597a73 (diff) | |
download | radicle-ci-broker-7c562e1cbf5e356bb62c5f1eccc57bb88c44e7c5.tar.gz |
feat(src/broker.rs): capture CI broker business logic
Implement a type to capture the business logic of the CI broker in a
way that is reasonable to test in isolation rather than as part of a
full Radicle network.
Signed-off-by: Lars Wirzenius <liw@liw.fi>
-rw-r--r-- | src/broker.rs | 166 |
1 files changed, 166 insertions, 0 deletions
diff --git a/src/broker.rs b/src/broker.rs new file mode 100644 index 0000000..5058a7f --- /dev/null +++ b/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(()) + } +} |