use std::future::Future;
use uuid::Uuid;
use crate::publish::envelope::PublishEnvelope;
use crate::publish::error::PublishError;
#[derive(Debug, Clone)]
pub struct PublishOutcome {
pub run_id: Uuid,
pub view_url: Option<String>,
pub attempts: u32,
}
pub trait ResultSink: Send + Sync {
fn publish<'a>(
&'a self,
envelope: &'a PublishEnvelope<'a>,
) -> impl Future<Output = Result<PublishOutcome, PublishError>> + Send + 'a;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn outcome_is_cloneable() {
let o = PublishOutcome {
run_id: Uuid::now_v7(),
view_url: Some("https://app.example.com/runs/abc".into()),
attempts: 1,
};
let _ = o.clone();
}
struct NoopSink;
impl ResultSink for NoopSink {
#[allow(clippy::manual_async_fn)]
fn publish<'a>(
&'a self,
envelope: &'a PublishEnvelope<'a>,
) -> impl Future<Output = Result<PublishOutcome, PublishError>> + Send + 'a {
async move {
Ok(PublishOutcome {
run_id: envelope.run_id,
view_url: None,
attempts: 1,
})
}
}
}
#[tokio::test]
async fn noop_sink_returns_ok() {
use std::collections::BTreeMap;
use crate::output::{LatencyStats, RequestSummary, RunMeta, RunReport};
let report = RunReport {
version: 2,
run: RunMeta {
mode: "fixed".to_string(),
elapsed_ms: 1.0,
curve_duration_ms: None,
template_generation_ms: None,
},
requests: RequestSummary {
total: 1,
ok: 1,
failed: 0,
skipped: 0,
error_rate: 0.0,
throughput_rps: 1.0,
},
latency: LatencyStats {
min_ms: 1.0,
p10_ms: 1.0,
p25_ms: 1.0,
p50_ms: 1.0,
p75_ms: 1.0,
p90_ms: 1.0,
p95_ms: 1.0,
p99_ms: 1.0,
max_ms: 1.0,
avg_ms: 1.0,
},
status_codes: BTreeMap::new(),
response_stats: None,
curve_stages: None,
scenarios: None,
thresholds: None,
};
let env = PublishEnvelope::new("0.0.0", &report);
let sink = NoopSink;
let outcome = sink.publish(&env).await.unwrap();
assert_eq!(outcome.attempts, 1);
assert_eq!(outcome.run_id, env.run_id);
}
}