fix(mail): guard empty message payload in processor (PR #935 round-6)

- processor.py: raise on an empty mail_message payload (OCS data=null with a
  <400 meta) so the task dead-letters instead of indexing a near-empty
  placeholder — mirrors the nc_mail_get_message tool guard. (the round-6
  approve-gating item)
- server/mail.py: clamp limit once in nc_mail_list_messages and base has_more on
  the effective (post-clamp) limit, so a caller passing limit<=0 doesn't get a
  misleading count.
- server/mail.py: note in nc_mail_get_message that attachments with id=null are
  inline body parts and can't be fetched via nc_mail_get_attachment.
- tests: add the first-time-missing incremental scanner case (enters the grace
  period, nothing queued/deleted).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Chris Coutinho
2026-06-20 13:35:49 +02:00
co-authored by Claude Opus 4.8
parent d006145444
commit 10e768e414
3 changed files with 39 additions and 2 deletions
+21
View File
@@ -229,3 +229,24 @@ async def test_incremental_deletes_after_grace_period(mocker):
assert queued == 1
assert [(t.doc_id, t.operation) for t in stream.tasks] == [("999", "delete")]
assert ("alice", "999") not in scanner_module._potentially_deleted
async def test_incremental_first_missing_starts_grace(mocker):
"""A newly-missing indexed message enters the grace period (no delete yet)."""
_patch_incremental(mocker, indexed_ids=["999"], existing_metadata=None)
# Not previously seen as missing, and the mailbox now returns no messages.
nc_client = _single_message_client([])
stream = _CollectingStream()
queued = await scan_mail_messages(
user_id="alice",
send_stream=stream,
nc_client=nc_client,
initial_sync=False,
scan_id=1,
)
# First miss only starts the grace period — nothing queued, nothing deleted.
assert queued == 0
assert stream.tasks == []
assert ("alice", "999") in scanner_module._potentially_deleted