| + |
//! 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(())
|
| + |
}
|
| + |
}
|