use chrono::{NaiveDate, TimeZone, Utc};
use shiplog::engine::Engine;
use shiplog::ids::RunId;
use shiplog::ports::{IngestOutput, Redactor, Renderer, WorkstreamClusterer};
use shiplog::redact::DeterministicRedactor;
use shiplog::schema::bundle::BundleProfile;
use shiplog::schema::coverage::{Completeness, CoverageManifest, TimeWindow};
use shiplog::schema::event::EventEnvelope;
use shiplog::workstreams::RepoClusterer;
use shiplog_testkit::TestMarkdownRenderer as MarkdownRenderer;
const RUNS: usize = 3;
fn test_events() -> Vec<EventEnvelope> {
vec![
shiplog_testkit::pr_event("acme/frontend", 1, "Add login page"),
shiplog_testkit::pr_event("acme/frontend", 2, "Fix CSS layout"),
shiplog_testkit::pr_event("acme/backend", 3, "Add REST API"),
shiplog_testkit::pr_event("acme/backend", 4, "Fix auth middleware"),
shiplog_testkit::pr_event("acme/infra", 5, "Setup CI pipeline"),
]
}
fn test_coverage() -> CoverageManifest {
CoverageManifest {
run_id: RunId("determinism_test_run".into()),
generated_at: Utc.timestamp_opt(1_700_000_000, 0).unwrap(),
user: "testuser".into(),
window: TimeWindow {
since: NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
until: NaiveDate::from_ymd_opt(2025, 4, 1).unwrap(),
},
mode: "merged".into(),
sources: vec!["github".into()],
slices: vec![],
warnings: vec![],
completeness: Completeness::Complete,
}
}
fn test_ingest() -> IngestOutput {
IngestOutput {
events: test_events(),
coverage: test_coverage(),
freshness: Vec::new(),
}
}
fn make_engine() -> Engine<'static> {
let renderer: &'static dyn Renderer = Box::leak(Box::new(MarkdownRenderer::new()));
let clusterer: &'static dyn WorkstreamClusterer = Box::leak(Box::new(RepoClusterer));
let redactor: &'static dyn Redactor =
Box::leak(Box::new(DeterministicRedactor::new(b"determinism-key")));
Engine::new(renderer, clusterer, redactor)
}
fn run_pipeline() -> (String, String, String) {
let dir = tempfile::tempdir().unwrap();
let out_dir = dir.path().join("run");
let engine = make_engine();
let ingest = test_ingest();
let (outputs, _) = engine
.run(
ingest,
"testuser",
"2025-01-01..2025-04-01",
&out_dir,
false,
&BundleProfile::Internal,
)
.unwrap();
let packet_md = std::fs::read_to_string(&outputs.packet_md).unwrap();
let ledger_jsonl = std::fs::read_to_string(&outputs.ledger_events_jsonl).unwrap();
let coverage_json = std::fs::read_to_string(&outputs.coverage_manifest_json).unwrap();
(packet_md, ledger_jsonl, coverage_json)
}
#[test]
fn packet_md_byte_identical_across_runs() {
let reference = run_pipeline();
for i in 1..RUNS {
let current = run_pipeline();
assert_eq!(
reference.0, current.0,
"packet.md must be byte-identical on run {i}"
);
}
}
#[test]
fn ledger_jsonl_byte_identical_across_runs() {
let reference = run_pipeline();
for i in 1..RUNS {
let current = run_pipeline();
assert_eq!(
reference.1, current.1,
"ledger.events.jsonl must be byte-identical on run {i}"
);
}
}
#[test]
fn coverage_manifest_byte_identical_across_runs() {
let reference = run_pipeline();
for i in 1..RUNS {
let current = run_pipeline();
assert_eq!(
reference.2, current.2,
"coverage.manifest.json must be byte-identical on run {i}"
);
}
}
#[test]
fn profile_packets_deterministic_across_runs() {
let mut manager_packets = Vec::new();
let mut public_packets = Vec::new();
for _ in 0..RUNS {
let dir = tempfile::tempdir().unwrap();
let out_dir = dir.path().join("run");
let engine = make_engine();
let ingest = test_ingest();
engine
.run(
ingest,
"testuser",
"2025-01-01..2025-04-01",
&out_dir,
false,
&BundleProfile::Internal,
)
.unwrap();
let manager_md =
std::fs::read_to_string(out_dir.join("profiles").join("manager").join("packet.md"))
.unwrap();
let public_md =
std::fs::read_to_string(out_dir.join("profiles").join("public").join("packet.md"))
.unwrap();
manager_packets.push(manager_md);
public_packets.push(public_md);
}
for i in 1..RUNS {
assert_eq!(
manager_packets[0], manager_packets[i],
"manager profile packet must be identical on run {i}"
);
assert_eq!(
public_packets[0], public_packets[i],
"public profile packet must be identical on run {i}"
);
}
}
#[test]
fn import_pipeline_deterministic_across_runs() {
let mut packets = Vec::new();
for _ in 0..RUNS {
let dir = tempfile::tempdir().unwrap();
let out_dir = dir.path().join("import");
let engine = make_engine();
let ingest = test_ingest();
let (outputs, _) = engine
.import(
ingest,
"testuser",
"2025-01-01..2025-04-01",
&out_dir,
false,
None,
&BundleProfile::Internal,
)
.unwrap();
let packet_md = std::fs::read_to_string(&outputs.packet_md).unwrap();
packets.push(packet_md);
}
for i in 1..RUNS {
assert_eq!(
packets[0], packets[i],
"import packet.md must be identical on run {i}"
);
}
}