mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-31 10:12:21 +01:00
Merge pull request #6952 from redpersongpt/codex/mail-cli-date-order
fix(mail): sort CLI message batches by UTC date
This commit is contained in:
+12
-6
@@ -43,7 +43,7 @@ import logging
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
|
||||
# Anchor the path so the script works when invoked from anywhere.
|
||||
@@ -136,7 +136,7 @@ def cmd_list(args) -> None:
|
||||
emit([], args)
|
||||
return
|
||||
all_uids = data[0].split()
|
||||
# Newest first, limit
|
||||
# Keep a bounded arrival batch; order its decoded rows by Date below.
|
||||
uids = list(reversed(all_uids))[: args.limit]
|
||||
out = []
|
||||
for uid in uids:
|
||||
@@ -165,10 +165,15 @@ def cmd_list(args) -> None:
|
||||
sender_name, sender_addr = parseaddr(sender)
|
||||
date_raw = msg.get("Date", "")
|
||||
try:
|
||||
iso = parsedate_to_datetime(date_raw).isoformat() if date_raw else ""
|
||||
parsed_date = parsedate_to_datetime(date_raw) if date_raw else None
|
||||
iso = parsed_date.isoformat() if parsed_date else ""
|
||||
if parsed_date is not None and parsed_date.tzinfo is None:
|
||||
parsed_date = parsed_date.replace(tzinfo=timezone.utc)
|
||||
date_epoch = parsed_date.timestamp() if parsed_date else float("-inf")
|
||||
except Exception:
|
||||
iso = ""
|
||||
out.append({
|
||||
date_epoch = float("-inf")
|
||||
out.append((date_epoch, {
|
||||
"uid": uid.decode(),
|
||||
"date": iso,
|
||||
"from_addr": sender_addr,
|
||||
@@ -176,10 +181,11 @@ def cmd_list(args) -> None:
|
||||
"subject": subject,
|
||||
"is_read": "\\Seen" in flags,
|
||||
"is_answered": "\\Answered" in flags,
|
||||
})
|
||||
}))
|
||||
except Exception as e:
|
||||
sys.stderr.write(f"warn: skipping uid {uid!r}: {e}\n")
|
||||
emit(out, args)
|
||||
out.sort(key=lambda item: item[0], reverse=True)
|
||||
emit([row for _, row in out], args)
|
||||
|
||||
|
||||
# ─── read ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
"""The bounded arrival batch is displayed by Date, rather than arrival ID."""
|
||||
from contextlib import contextmanager
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
from tests.cli.test_mail_cli_read_empty_fetch import _load_mail_cli
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dates,expected', [
|
||||
(['Wed, 03 Jun 2026 12:00:00 +0000', 'Mon, 01 Jun 2026 09:00:00 +0000'], ['1', '2']),
|
||||
(['Wed, 03 Jun 2026 12:00:00 +0000', 'Wed, 03 Jun 2026 13:00:00 +0200'], ['1', '2']),
|
||||
(['Wed, 03 Jun 2026 12:00:00', 'Wed, 03 Jun 2026 11:00:00 +0000'], ['1', '2']),
|
||||
(['Wed, 03 Jun 2026 12:00:00 +0000', None, 'not a date'], ['1', '3', '2']),
|
||||
(['Wed, 03 Jun 2026 12:00:00 +0000', 'Wed, 03 Jun 2026 14:00:00 +0200'], ['2', '1']),
|
||||
(['Wed, 03 Jun 2026 12:00:00 +0000', 'Thu, 04 Jun 2026 12:00:00 +0000'], ['2', '1']),
|
||||
(['Wed, 31 Dec 1969 12:00:00 +0000', None], ['1', '2']),
|
||||
])
|
||||
def test_list_sorts_parsed_dates_without_changing_row_schema(monkeypatch, dates, expected):
|
||||
cli = _load_mail_cli(monkeypatch)
|
||||
fetched = []
|
||||
|
||||
class Connection:
|
||||
def select(self, folder, readonly=True):
|
||||
assert readonly is True
|
||||
return 'OK', [str(len(dates)).encode()]
|
||||
|
||||
def search(self, *_):
|
||||
return 'OK', [' '.join(str(i+1) for i in range(len(dates))).encode()]
|
||||
|
||||
def fetch(self, uid, _):
|
||||
fetched.append(uid.decode())
|
||||
date = dates[int(uid)-1]
|
||||
raw = 'From: Sender <sender@example.com>\r\nSubject: Item '+uid.decode()+'\r\n'
|
||||
if date is not None:
|
||||
raw += 'Date: '+date+'\r\n'
|
||||
return 'OK', [(b'1 (FLAGS (\\Seen) RFC822.HEADER)', (raw+'\r\n').encode())]
|
||||
|
||||
@contextmanager
|
||||
def imap(_):
|
||||
yield Connection()
|
||||
|
||||
rows = []
|
||||
monkeypatch.setattr(cli, '_imap', imap)
|
||||
monkeypatch.setattr(cli, 'emit', lambda value, args: rows.extend(value))
|
||||
cli.cmd_list(SimpleNamespace(account='fixture', folder='INBOX', limit=20))
|
||||
assert [row['uid'] for row in rows] == expected
|
||||
assert fetched == [str(i) for i in range(len(dates), 0, -1)]
|
||||
assert all(set(row) == {'uid', 'date', 'from_addr', 'from_name', 'subject', 'is_read', 'is_answered'} for row in rows)
|
||||
assert all(row['is_read'] and not row['is_answered'] for row in rows)
|
||||
|
||||
|
||||
def test_list_still_limits_the_arrival_batch(monkeypatch):
|
||||
cli = _load_mail_cli(monkeypatch)
|
||||
fetched = []
|
||||
class Connection:
|
||||
def select(self, *_args, **_kwargs):return 'OK', []
|
||||
def search(self, *_):return 'OK', [b'1 2 3']
|
||||
def fetch(self, uid, _):
|
||||
fetched.append(uid)
|
||||
return 'OK', [(b'3 (FLAGS () RFC822.HEADER)', b'From: x@example.com\r\nSubject: bounded\r\n\r\n')]
|
||||
@contextmanager
|
||||
def imap(_):yield Connection()
|
||||
monkeypatch.setattr(cli, '_imap', imap)
|
||||
monkeypatch.setattr(cli, 'emit', lambda *_:None)
|
||||
cli.cmd_list(SimpleNamespace(account='fixture', folder='INBOX', limit=1))
|
||||
assert fetched == [b'3']
|
||||
Reference in New Issue
Block a user