brewlog/tests/cli/timeline_cli.rs
Jon Seager 0e3847ade9
feat: sync timeline events with entity edits
Add background timeline rebuild task (mirroring stats cache pattern)
that refreshes denormalized timeline event snapshots when entities are
updated. Includes cascade logic so editing a roaster refreshes timeline
events for its roasts, bags, brews, and cups.

- Add update_by_entity/delete_by_entity/delete_all to TimelineEventRepository
- Add TimelineInvalidator with debounced background rebuild task
- Add invalidate() calls to all 7 entity update handlers
- Add delete_by_entity cleanup to define_delete_handler! macro
- Add gear_id filter to BrewFilter for cascade traversal
- Add `brewlog timeline rebuild` CLI command for full rebuild
- Add 5 integration tests for timeline sync behavior
2026-02-15 11:11:07 +00:00

75 lines
2.4 KiB
Rust

use crate::helpers::{create_roast, create_roaster, create_token, run_brewlog, server_info};
#[test]
fn test_editing_roaster_updates_timeline_events_after_rebuild() {
let token = create_token("test-timeline-rebuild");
let (address, _) = server_info();
// Create a roaster and roast
let original_name = "Timeline CLI Roasters";
let roaster_id = create_roaster(original_name, &token);
let _roast_id = create_roast(&roaster_id, "Timeline CLI Roast", &token);
// Allow timeline events to be created
std::thread::sleep(std::time::Duration::from_millis(50));
// Verify original roaster name appears in timeline HTML
let client = reqwest::blocking::Client::new();
let body = client
.get(format!("{address}/timeline"))
.send()
.expect("failed to fetch timeline")
.text()
.expect("failed to read body");
assert!(
body.contains(original_name),
"Expected original roaster name in timeline before edit"
);
// Update the roaster name via CLI
let updated_name = "Renamed CLI Roasters";
let output = run_brewlog(
&[
"roaster",
"update",
"--id",
&roaster_id,
"--name",
updated_name,
],
&[("BREWLOG_TOKEN", &token)],
);
assert!(
output.status.success(),
"roaster update should succeed: {}",
String::from_utf8_lossy(&output.stderr)
);
// Trigger timeline rebuild via CLI
let output = run_brewlog(&["timeline", "rebuild"], &[("BREWLOG_TOKEN", &token)]);
assert!(
output.status.success(),
"timeline rebuild should succeed: {}",
String::from_utf8_lossy(&output.stderr)
);
// Wait for background rebuild task to process
std::thread::sleep(std::time::Duration::from_millis(500));
// Verify both timeline events now show the updated roaster name
let body = client
.get(format!("{address}/timeline"))
.send()
.expect("failed to fetch timeline after rebuild")
.text()
.expect("failed to read body");
assert!(
body.contains(updated_name),
"Expected updated roaster name '{updated_name}' in timeline after rebuild, got: {body}"
);
assert!(
!body.contains(original_name),
"Expected original roaster name '{original_name}' to no longer appear in timeline, got: {body}"
);
}