fix: preserve deadline calendar days (Closes #719)
This commit is contained in:
1 parent
9ec88179c3
commit
dce9ae04c9
7 files changed
+135
-26
No files matched your search
@@ -54,6 +54,36 @@ async def test_deadline_reminder_sends_one_private_local_day_digest_and_deduplic
|
||||
assert "private/repo" not in json.dumps(sent)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_deadline_reminder_counts_calendar_days_per_device_timezone(tmp_path):
|
||||
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
||||
for device, timezone_name in (("tokyo", "Asia/Tokyo"), ("la", "America/Los_Angeles")):
|
||||
store.upsert(device, {
|
||||
"endpoint": f"https://push.example/{device}",
|
||||
"keys": {"p256dh": "public-key", "auth": "auth-secret"},
|
||||
})
|
||||
store.set_deadline_preferences(device, enabled=True, timezone=timezone_name, reminder_hour=0)
|
||||
|
||||
async def assigned():
|
||||
return {"complete": True, "items": [
|
||||
{"id": 1, "due_date": "2026-08-15T23:59:59Z"},
|
||||
{"id": 2, "due_date": "2026-08-16T23:59:59Z"},
|
||||
]}
|
||||
|
||||
sent = {}
|
||||
async def send(subscription, payload):
|
||||
device = subscription["endpoint"].rsplit("/", 1)[-1]
|
||||
sent[device] = json.loads(payload)["deadline_count"]
|
||||
|
||||
delivered = await dispatch_deadline_reminders(
|
||||
store, PushConfiguration("public", "private", "mailto:ops@example.com"),
|
||||
assigned, send, now=datetime(2026, 8, 13, 23, 30, tzinfo=timezone.utc),
|
||||
)
|
||||
|
||||
assert delivered == 2
|
||||
assert sent == {"tokyo": 2, "la": 1}
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_deadline_reminder_fails_closed_for_incomplete_snapshot_and_before_local_hour(tmp_path):
|
||||
store = PushSubscriptionStore(tmp_path / "push.sqlite3")
|
||||
|
||||
@@ -83,6 +83,24 @@ run();
|
||||
}
|
||||
|
||||
|
||||
def test_overdue_replan_orders_by_calendar_day_without_timezone_drift():
|
||||
script = f"""
|
||||
const createSweep = require({json.dumps(str(REPLAN))});
|
||||
const sweep = createSweep({{update:async()=>({{}})}});
|
||||
const started = sweep.start([
|
||||
{{key:'o/r#2',repository:'o/r',number:2,due_date:'2026-08-13T01:00:00Z'}},
|
||||
{{key:'o/r#1',repository:'o/r',number:1,due_date:'2026-08-12T23:59:59Z'}},
|
||||
]);
|
||||
process.stdout.write(JSON.stringify(started));
|
||||
"""
|
||||
environment = {**__import__('os').environ, "TZ": "Asia/Tokyo"}
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], capture_output=True, text=True, env=environment
|
||||
)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout)["current"] == "o/r#1"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mobile_agenda_exposes_thumb_safe_replan_controls_and_wires_existing_mutation():
|
||||
html = await dashboard()
|
||||
|
||||
@@ -109,6 +109,14 @@ async def test_mobile_batch_planning_can_select_and_clear_active_queue_matches()
|
||||
assert "max-width:100%" in html
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_issue_sheet_formats_due_date_as_a_calendar_day_without_local_timestamp_conversion():
|
||||
source = await dashboard()
|
||||
|
||||
assert "formatCalendarDueDate(detail.due_date)" in source
|
||||
assert "new Date(detail.due_date).toLocaleDateString()" not in source
|
||||
|
||||
|
||||
def test_queue_finder_matches_repository_number_and_title_without_reordering():
|
||||
script = f"""
|
||||
const work = require({json.dumps(str(MY_WORK))});
|
||||
@@ -173,6 +181,31 @@ process.stdout.write(JSON.stringify(work.agendaMyWork(items, new Date('2026-08-1
|
||||
assert json.loads(result.stdout) == ["a/r#2", "a/r#10", "z/r#1"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("timezone_name", ["Asia/Tokyo", "America/Los_Angeles"])
|
||||
def test_mobile_agenda_preserves_the_gitea_calendar_day_in_every_timezone(timezone_name):
|
||||
script = f"""
|
||||
const work = require({json.dumps(str(MY_WORK))});
|
||||
const now = new Date(2026, 7, 13, 12, 0, 0);
|
||||
const built = work({{
|
||||
user:{{login:'timmy'}}, notifications:[], pull_requests:[],
|
||||
issues:[{{number:1,title:'Ship',repository:'o/r',labels:[],assignees:['timmy'],due_date:'2026-08-13T23:59:59Z'}}],
|
||||
}}, now);
|
||||
process.stdout.write(JSON.stringify({{
|
||||
dueLabel:built[0].due_label,
|
||||
agenda:work.agendaMyWork(built, now).map(item => [item.key, item.agenda_group]),
|
||||
}}));
|
||||
"""
|
||||
environment = {**os.environ, "TZ": timezone_name}
|
||||
result = subprocess.run(
|
||||
["node", "-e", script], capture_output=True, text=True, env=environment
|
||||
)
|
||||
assert result.returncode == 0, result.stderr
|
||||
assert json.loads(result.stdout) == {
|
||||
"dueLabel": "Due today",
|
||||
"agenda": [["o/r#1", "Today"]],
|
||||
}
|
||||
|
||||
|
||||
def test_agenda_pager_loads_every_issue_page_single_flight_and_retries_failed_page():
|
||||
script = f"""
|
||||
const work = require({json.dumps(str(MY_WORK))});
|
||||
|
||||
Reference in new issue
Block a user