Files
event_curator/tests/test_reconcile.py

197 lines
8.8 KiB
Python

"""Spec for reconcile.py — written before the implementation.
Fixtures are the REAL events pulled from the live "Events" calendar on
2026-06-06, trimmed to the fields reconcile uses. Crucially, none of them carry
an `autoKey` (they were hand-created via the chat workflow), so these fixtures
exercise exactly the case the fallback (title, date) matching must handle.
"""
import os
import sys
import unittest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
import reconcile as R # noqa: E402
TIGNOR = {
"summary": "Christopher Tignor + Julia Kent — Public Records (tickets req'd)",
"start": {"dateTime": "2026-06-12T19:00:00-04:00", "timeZone": "America/New_York"},
}
BRANCA = {
"summary": 'Glenn Branca: Symphony No. 13 "Hallucination City" for 100 Guitars '
"— Lincoln Center (tickets rec'd)",
"start": {"dateTime": "2026-06-12T19:30:00-04:00", "timeZone": "America/New_York"},
}
SMORG = { # recurring instance (RRULE master expanded)
"summary": "Smorgasburg @ The Oculus (day-of option)",
"start": {"dateTime": "2026-06-19T11:00:00-04:00", "timeZone": "America/New_York"},
"recurringEventId": "dcu0np1bp18mknfpdjdpbidamg",
}
GOVISLAND = { # recurring all-day instance
"summary": "Governors Island + Six Coasts (day-of option)",
"start": {"date": "2026-06-19"},
"recurringEventId": "ul9ncfjd8po4augmn04k11g5es",
}
EXISTING = [TIGNOR, BRANCA, SMORG, GOVISLAND]
def cand(title, start, **kw):
return {"title": title, "start": start, **kw}
class TestNormalize(unittest.TestCase):
def test_strips_trailing_tag(self):
self.assertEqual(R.normalize_title("Foo Bar (tickets req'd)"), "foo bar")
def test_strips_trailing_dayof_tag(self):
self.assertEqual(R.normalize_title("Smorgasburg @ The Oculus (day-of option)"),
"smorgasburg @ the oculus")
def test_strip_venue_em_dash(self):
self.assertEqual(
R.strip_venue("christopher tignor + julia kent — public records"),
"christopher tignor + julia kent")
def test_strip_venue_noop_without_separator(self):
self.assertEqual(R.strip_venue("smorgasburg @ the oculus"),
"smorgasburg @ the oculus")
class TestStartDate(unittest.TestCase):
def test_google_datetime(self):
self.assertEqual(R.start_date(TIGNOR), "2026-06-12")
def test_google_all_day(self):
self.assertEqual(R.start_date(GOVISLAND), "2026-06-19")
def test_candidate_iso_string(self):
self.assertEqual(R.start_date(cand("x", "2026-07-10T20:00:00")), "2026-07-10")
def test_candidate_all_day_date(self):
self.assertEqual(R.start_date(cand("x", "2026-07-10", all_day=True)), "2026-07-10")
class TestDedup(unittest.TestCase):
def test_exact_re_report_is_duplicate(self):
c = cand("Christopher Tignor + Julia Kent — Public Records (tickets req'd)",
"2026-06-12T19:00:00")
self.assertTrue(R.is_duplicate(c, TIGNOR))
def test_same_event_without_venue_or_tag(self):
c = cand("Christopher Tignor + Julia Kent", "2026-06-12T19:00:00")
self.assertTrue(R.is_duplicate(c, TIGNOR))
def test_branca_variant_tag_and_time_same_date(self):
# model re-reports with the other tag (req'd vs rec'd), no venue, 7:00 vs 7:30
c = cand('Glenn Branca: Symphony No. 13 "Hallucination City" for 100 Guitars',
"2026-06-12T19:00:00")
self.assertTrue(R.is_duplicate(c, BRANCA))
def test_recurring_candidate_matches_series_on_other_date(self):
c = cand("Smorgasburg @ The Oculus", "2026-07-03T11:00:00",
recurrence="RRULE:FREQ=WEEKLY;BYDAY=FR")
self.assertTrue(R.is_duplicate(c, SMORG))
def test_unrelated_event_same_date_not_duplicate(self):
c = cand("Ryoji Ikeda — The Shed", "2026-06-12T20:00:00") # same date as Tignor/Branca
self.assertFalse(any(R.is_duplicate(c, e) for e in EXISTING))
def test_same_title_different_date_not_duplicate(self):
# a genuinely new one-off Tignor show on another date should NOT be suppressed
c = cand("Christopher Tignor + Julia Kent — Public Records", "2026-09-04T19:00:00")
self.assertFalse(any(R.is_duplicate(c, e) for e in EXISTING))
class TestReconcile(unittest.TestCase):
def test_filters_and_stamps_autokey(self):
cands = [
cand("Christopher Tignor + Julia Kent", "2026-06-12T19:00:00"), # dup
cand("Ryoji Ikeda — The Shed", "2026-07-10T20:00:00"), # new
]
out = R.reconcile(cands, EXISTING)
self.assertEqual([i["title"] for i in out["insert"]], ["Ryoji Ikeda — The Shed"])
self.assertEqual(len(out["skip"]), 1)
self.assertEqual(out["insert"][0]["autoKey"],
R.auto_key("Ryoji Ikeda — The Shed", "2026-07-10"))
def test_autokey_exact_match_short_circuits_title(self):
# if an existing event carries OUR autoKey, match even when the display
# name is unrecognizable (e.g. the user renamed it on the calendar)
key = R.auto_key("Some Talk", "2026-08-01")
ev = {"summary": "Totally Different Display Name",
"start": {"date": "2026-08-01"},
"extendedProperties": {"private": {"autoKey": key}}}
self.assertTrue(R.is_duplicate(cand("Some Talk", "2026-08-01"), ev))
def test_idempotent_second_run(self):
# feeding the previous run's inserts back in (now present on the calendar)
# produces zero new inserts
first = R.reconcile([cand("Ryoji Ikeda — The Shed", "2026-07-10T20:00:00")], EXISTING)
as_calendar_event = {
"summary": "Ryoji Ikeda — The Shed",
"start": {"dateTime": "2026-07-10T20:00:00-04:00"},
"extendedProperties": {"private": {"autoKey": first["insert"][0]["autoKey"]}},
}
second = R.reconcile([cand("Ryoji Ikeda — The Shed", "2026-07-10T20:00:00")],
EXISTING + [as_calendar_event])
self.assertEqual(second["insert"], [])
class TestLoader(unittest.TestCase):
def test_bare_list(self):
self.assertEqual(R.as_event_list([TIGNOR]), [TIGNOR])
def test_events_wrapper(self): # shape returned by the list-events tool
self.assertEqual(R.as_event_list({"events": [TIGNOR], "summary": "Events"}), [TIGNOR])
def test_items_wrapper(self): # raw Google API shape
self.assertEqual(R.as_event_list({"items": [TIGNOR]}), [TIGNOR])
def test_unrecognized_returns_empty(self):
self.assertEqual(R.as_event_list({"nope": 1}), [])
class TestHardening(unittest.TestCase):
"""Regression tests for defects the adversarial review reproduced live."""
def test_intra_run_collapses_same_date_variants(self):
# two titles for the same show on the same date, against an EMPTY calendar
cands = [cand("Tim Hecker — Pioneer Works", "2026-08-07T20:00:00"),
cand("Tim Hecker (tickets req'd)", "2026-08-07T20:00:00")]
out = R.reconcile(cands, [])
self.assertEqual(len(out["insert"]), 1)
self.assertEqual(len(out["skip"]), 1)
def test_recurring_candidate_matches_nonrecurring_same_title(self):
# connector may return an expanded instance with no recurringEventId
existing = {"summary": "Smorgasburg @ The Oculus",
"start": {"dateTime": "2026-06-19T11:00:00-04:00"}}
c = cand("Smorgasburg @ The Oculus", "2026-09-04T11:00:00",
recurrence="RRULE:FREQ=WEEKLY;BYDAY=FR")
self.assertTrue(R.is_duplicate(c, existing))
def test_null_extended_properties_does_not_crash(self):
ev1 = {"summary": "X", "start": {"date": "2026-08-01"}, "extendedProperties": None}
ev2 = {"summary": "Y", "start": {"date": "2026-08-01"},
"extendedProperties": {"private": None}}
self.assertFalse(R.is_duplicate(cand("Totally Other", "2026-08-01"), ev1))
self.assertFalse(R.is_duplicate(cand("Totally Other", "2026-08-01"), ev2))
def test_today_drops_past_dated(self):
cands = [cand("Old Show", "2020-01-01T20:00:00"),
cand("Future Show", "2026-12-01T20:00:00")]
out = R.reconcile(cands, [], today="2026-06-08")
self.assertEqual([R.title_of(i) for i in out["insert"]], ["Future Show"])
self.assertEqual(len(out["dropped_past"]), 1)
self.assertEqual(R.title_of(out["dropped_past"][0]["candidate"]), "Old Show")
def test_max_caps_inserts_and_overflows_in_order(self):
cands = [cand(f"Show {i}", f"2026-07-0{i}T20:00:00") for i in range(1, 6)]
out = R.reconcile(cands, [], max_new=3)
self.assertEqual([R.title_of(i) for i in out["insert"]],
["Show 1", "Show 2", "Show 3"])
self.assertEqual(len(out["dropped_overflow"]), 2)
if __name__ == "__main__":
unittest.main()