Pin the behaviour of an aggregate with no sink
An aggregate without a default_sink leaves events created in it alone, which is how a target calendar keeps events of its own. The reconciler unit tests cover the decision; this covers it surviving the round trip, and specifically that it holds on every subsequent cycle — an event that survived the first cycle and was swept up by the second would be worse than never having worked. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
25d181108e
commit
7607078394
1 changed files with 57 additions and 0 deletions
|
|
@ -23,6 +23,12 @@ const MEETING: &str = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//test//EN\r\n\
|
||||||
BEGIN:VALARM\r\nACTION:DISPLAY\r\nTRIGGER:-PT15M\r\nDESCRIPTION:Soon\r\n\
|
BEGIN:VALARM\r\nACTION:DISPLAY\r\nTRIGGER:-PT15M\r\nDESCRIPTION:Soon\r\n\
|
||||||
END:VALARM\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n";
|
END:VALARM\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n";
|
||||||
|
|
||||||
|
/// An event written straight into the aggregate, belonging to no source.
|
||||||
|
const HAND_WRITTEN: &str = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//phone//EN\r\n\
|
||||||
|
BEGIN:VEVENT\r\nUID:hand-written@phone\r\nDTSTAMP:20260101T000000Z\r\n\
|
||||||
|
DTSTART:20260911T140000Z\r\nDTEND:20260911T150000Z\r\nSUMMARY:Dentist\r\n\
|
||||||
|
END:VEVENT\r\nEND:VCALENDAR\r\n";
|
||||||
|
|
||||||
const HOLIDAY_FEED: &str = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//feed//EN\r\n\
|
const HOLIDAY_FEED: &str = "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//feed//EN\r\n\
|
||||||
BEGIN:VEVENT\r\nUID:newyear@feed\r\nDTSTAMP:20260101T000000Z\r\n\
|
BEGIN:VEVENT\r\nUID:newyear@feed\r\nDTSTAMP:20260101T000000Z\r\n\
|
||||||
DTSTART;VALUE=DATE:20260101\r\nDTEND;VALUE=DATE:20260102\r\n\
|
DTSTART;VALUE=DATE:20260101\r\nDTEND;VALUE=DATE:20260102\r\n\
|
||||||
|
|
@ -272,3 +278,54 @@ fn a_retired_endpoints_mirror_is_reported_and_then_removed() {
|
||||||
assert!(removed.succeeded(), "{}", removed.stderr);
|
assert!(removed.succeeded(), "{}", removed.stderr);
|
||||||
assert!(!mirror.exists(), "the orphaned mirror should be gone");
|
assert!(!mirror.exists(), "the orphaned mirror should be gone");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An aggregate with no `default_sink` leaves events created in it alone.
|
||||||
|
///
|
||||||
|
/// This is how a target calendar keeps events of its own: with nowhere
|
||||||
|
/// configured to file a new event, calcalist refuses to guess rather than
|
||||||
|
/// picking a source, so the event simply stays where it was written. Worth
|
||||||
|
/// pinning, because "left alone" has to hold on every subsequent cycle too —
|
||||||
|
/// an event that survived the first one and was swept up by the second would
|
||||||
|
/// be worse than never having worked.
|
||||||
|
#[test]
|
||||||
|
fn an_event_created_in_a_sinkless_aggregate_stays_where_it_is() {
|
||||||
|
let Some(fixture) = fixture() else { return };
|
||||||
|
|
||||||
|
// The fixture's aggregate has a default_sink; take it away.
|
||||||
|
let sinkless = std::fs::read_to_string(&fixture.calcalist.config)
|
||||||
|
.expect("read config")
|
||||||
|
.replace("default_sink = \"work\"\n", "");
|
||||||
|
std::fs::write(&fixture.calcalist.config, sinkless).expect("rewrite config");
|
||||||
|
|
||||||
|
assert!(fixture.calcalist.run(&["sync"]).succeeded());
|
||||||
|
|
||||||
|
// Now add an event by hand, as a calendar app would.
|
||||||
|
fixture
|
||||||
|
.server
|
||||||
|
.put_event("unified", "dentist.ics", HAND_WRITTEN);
|
||||||
|
|
||||||
|
for cycle in 1..=3 {
|
||||||
|
let run = fixture.calcalist.run(&["sync"]);
|
||||||
|
assert!(
|
||||||
|
run.succeeded(),
|
||||||
|
"cycle {cycle} failed\n{}\n{}",
|
||||||
|
run.stdout,
|
||||||
|
run.stderr
|
||||||
|
);
|
||||||
|
|
||||||
|
let published = fixture.server.stored("unified");
|
||||||
|
assert!(
|
||||||
|
published.iter().any(|item| item.contains("Dentist")),
|
||||||
|
"cycle {cycle}: the hand-written event was removed:\n{published:?}"
|
||||||
|
);
|
||||||
|
// It never reaches a source: there is nowhere it was told to go.
|
||||||
|
assert!(
|
||||||
|
!fixture
|
||||||
|
.server
|
||||||
|
.stored("work")
|
||||||
|
.iter()
|
||||||
|
.any(|item| item.contains("Dentist")),
|
||||||
|
"cycle {cycle}: the hand-written event leaked into the source"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue