use std::sync::Arc;
use chrono::{DateTime, Utc};
use crossbeam_channel::{Receiver, Sender, unbounded};
use rite_model::StepId;
use crate::clock::{Clock, SystemClock};
use crate::protocol::{ExecEvent, PromptId, Response, UiCommand};
use crate::reporter::Reporter;
use crate::transcript_sink::{InMemorySink, JsonlFileSink, TranscriptSink};
use rite_model::{Sha256Digest, StepFact, TranscriptHeader};
pub struct ReporterHarness {
sink: InMemorySink,
event_tx: Sender<ExecEvent>,
_event_rx: Receiver<ExecEvent>,
cmd_tx: Sender<UiCommand>,
cmd_rx: Receiver<UiCommand>,
step_rx: Receiver<UiCommand>,
next_response_id: u64,
}
impl ReporterHarness {
#[must_use]
pub fn new() -> Self {
let (event_tx, event_rx) = unbounded();
let (cmd_tx, cmd_rx) = unbounded();
Self {
sink: begun_sink(),
event_tx,
_event_rx: event_rx,
cmd_tx,
cmd_rx,
step_rx: unbounded().1,
next_response_id: 0,
}
}
pub fn enqueue_response(&mut self, response: Response) {
let prompt_id = PromptId::new(self.next_response_id);
self.next_response_id = self.next_response_id.wrapping_add(1);
let _ = self.cmd_tx.send(UiCommand::PromptResponse {
prompt_id,
response,
});
}
pub fn enqueue_retry(&mut self, response: Response) {
let prompt_id = PromptId::new(self.next_response_id.wrapping_sub(1));
let _ = self.cmd_tx.send(UiCommand::PromptResponse {
prompt_id,
response,
});
}
pub fn reporter(&mut self, step: StepId) -> Reporter<'_> {
let (tx, rx) = unbounded();
for cmd in self.step_rx.try_iter().chain(self.cmd_rx.try_iter()) {
let _ = tx.send(cmd);
}
self.step_rx = rx;
let mut reporter = Reporter::new(
&self.event_tx,
&self.step_rx,
&mut self.sink,
Arc::new(SystemClock),
);
reporter.set_current_step(Some(step));
reporter.seed_entropy(b"rite-test-harness-seed");
reporter
}
#[must_use]
pub fn facts(&self) -> &[StepFact] {
self.sink.facts()
}
}
impl Default for ReporterHarness {
fn default() -> Self {
Self::new()
}
}
#[must_use]
pub fn test_header() -> TranscriptHeader {
TranscriptHeader::new("rite test", &"0".repeat(32), false)
}
#[must_use]
#[allow(clippy::expect_used)]
pub fn begun_sink() -> InMemorySink {
let mut sink = InMemorySink::new();
sink.begin(&test_header())
.expect("a fresh in-memory sink takes a header");
sink
}
pub fn write_transcript(
dir: &std::path::Path,
facts: &[StepFact],
) -> std::io::Result<Sha256Digest> {
let mut sink = JsonlFileSink::create(dir)?;
sink.begin(&test_header())?;
for fact in facts {
sink.record(fixed_test_time(), fact.default_level(), fact)?;
}
sink.finalize()
}
#[must_use]
pub fn ceremony_started(name: &str) -> StepFact {
StepFact::CeremonyStarted {
name: name.to_string(),
template: Sha256Digest::of(b"test ceremony"),
}
}
#[must_use]
pub fn fixed_test_time() -> DateTime<Utc> {
DateTime::from_timestamp_nanos(1_700_000_000_000_000_000)
}
#[must_use]
pub fn fact_event(fact: StepFact) -> ExecEvent {
ExecEvent::Fact {
at: fixed_test_time(),
fact,
}
}
pub struct FixedClock(pub DateTime<Utc>);
impl Clock for FixedClock {
fn now(&self) -> DateTime<Utc> {
self.0
}
}