#![allow(
clippy::unwrap_used,
clippy::expect_used,
reason = "test code: unwrap/expect on assert_cmd assertions is the expected diagnostic"
)]
mod common;
use std::io::Write as _;
use predicates::str::contains;
use tempfile::NamedTempFile;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
use common::aviso;
fn write_listener_file(yaml_body: &str) -> NamedTempFile {
let mut f = NamedTempFile::new().expect("create tempfile");
f.write_all(yaml_body.as_bytes()).expect("write yaml");
f
}
async fn mount_replay_finite_stream(server: &MockServer) {
let body = concat!(
"event: replay-control\n",
"data: {\"type\":\"replay_completed\",\"timestamp\":\"2026-05-17T13:00:00Z\",\"topic\":\"mars\",\"request_id\":\"req-rc\"}\n\n",
"event: connection-closing\n",
"data: {\"reason\":\"end_of_stream\",\"timestamp\":\"2026-05-17T13:00:00Z\",\"message\":\"\",\"topic\":\"mars\",\"request_id\":\"req-eos\"}\n\n",
);
Mock::given(method("POST"))
.and(path("/api/v1/replay"))
.respond_with(
ResponseTemplate::new(200)
.insert_header("content-type", "text/event-stream")
.set_body_string(body),
)
.mount(server)
.await;
}
#[test]
fn replay_requires_from() {
aviso().args(["replay"]).assert().failure().code(2);
}
#[test]
fn replay_with_bad_from_value_exits_2() {
aviso()
.args([
"--base-url",
"http://unused",
"replay",
"--from",
"not-a-date-and-not-an-id-and-has-dashes",
"--listener",
"x",
])
.assert()
.failure()
.code(2)
.stderr(contains("--from"));
}
#[tokio::test]
async fn replay_with_single_resolved_listener_succeeds() {
let server = MockServer::start().await;
mount_replay_finite_stream(&server).await;
let listener = write_listener_file("listeners:\n - name: only\n event: mars\n");
aviso()
.args([
"--base-url",
&server.uri(),
"replay",
"--from",
"42",
listener.path().to_str().unwrap(),
])
.timeout(std::time::Duration::from_secs(10))
.assert()
.success();
}
#[tokio::test]
async fn replay_with_multiple_listeners_requires_disambiguation() {
let listener = write_listener_file(
"listeners:\n - name: a\n event: mars\n - name: b\n event: era5\n",
);
aviso()
.args([
"--base-url",
"http://unused",
"replay",
"--from",
"42",
listener.path().to_str().unwrap(),
])
.assert()
.failure()
.code(2)
.stderr(contains("--listener"));
}
#[tokio::test]
async fn replay_with_listener_name_picks_correct_one() {
let server = MockServer::start().await;
mount_replay_finite_stream(&server).await;
let listener = write_listener_file(
"listeners:\n - name: a\n event: mars\n - name: b\n event: era5\n",
);
aviso()
.args([
"--base-url",
&server.uri(),
"replay",
"--listener",
"a",
"--from",
"42",
listener.path().to_str().unwrap(),
])
.timeout(std::time::Duration::from_secs(10))
.assert()
.success();
}
#[tokio::test]
async fn replay_ad_hoc_with_event_and_identifiers_succeeds() {
let server = MockServer::start().await;
mount_replay_finite_stream(&server).await;
aviso()
.args([
"--base-url",
&server.uri(),
"replay",
"--event",
"mars",
"--identifiers",
"{\"class\":\"od\"}",
"--from",
"42",
])
.timeout(std::time::Duration::from_secs(10))
.assert()
.success();
}
#[test]
fn replay_event_without_identifiers_exits_2() {
aviso()
.args([
"--base-url",
"http://unused",
"replay",
"--event",
"mars",
"--from",
"42",
])
.assert()
.failure()
.code(2);
}
#[test]
fn replay_invalid_identifiers_json_exits_2() {
aviso()
.args([
"--base-url",
"http://unused",
"replay",
"--event",
"mars",
"--identifiers",
"not json",
"--from",
"42",
])
.assert()
.failure()
.code(2);
}