Files
leocrm/test_report.md
T

3.4 KiB

Test Report — Mail Attachment Support

Date: 2026-07-15 Task: Mail attachment support — sync, display, download, upload

Changes Summary

Backend

  • services.py: Added _sanitize_filename, _attachment_storage_path, _save_attachment_to_storage, attachment_to_response helpers. Modified IMAP sync to save attachment content to disk + create MailAttachment DB records. Modified send_mail_via_smtp to accept attachment_paths and attach files to EmailMessage. Updated mail_to_response to use attachment_to_response.
  • routes.py: Added POST /api/v1/mail/upload-attachment endpoint (multipart/form-data). Added _resolve_attachment_paths helper. Modified send_mail route to resolve attachment IDs and pass to send_mail_via_smtp.

Frontend

  • api/mail.ts: Updated MailAttachment interface (added size_bytes, dms_file_id). Added uploadAttachment function and UploadedAttachment interface.
  • MailDetail.tsx: Fixed att.sizeatt.size_bytes for formatBytes call.
  • ComposeModal.tsx: Added file input (multiple), attachment upload via uploadAttachment, attachment list with remove buttons, attachment IDs passed in send payload.
  • i18n locales: Added mail.addAttachment and common.remove keys to en.json and de.json.

Test Results

Python Syntax Check

/opt/venv/bin/python -c "import py_compile; py_compile.compile('app/plugins/builtins/mail/services.py', doraise=True); py_compile.compile('app/plugins/builtins/mail/routes.py', doraise=True)"
→ Python syntax OK

Frontend TypeScript Check

npx tsc --noEmit 2>&1 | grep -E 'mail|Mail|Compose|MailDetail'
→ No errors in mail files

Pre-existing errors in SettingsPlugins.tsx and SettingsRoles.tsx (unrelated to this task).

Frontend Tests (vitest)

Test File Result
ComposeModal.test.tsx 10/10 passed
MailSettings.test.tsx 10/10 passed
MailPage.test.tsx ⚠️ 6 passed, 8 failed (PRE-EXISTING — confirmed via git stash)

Backend Tests (pytest)

pytest tests/test_mail.py
→ ImportError: No module named 'redis' (pre-existing environment issue)

Smoke Test Description

  1. IMAP Sync: Attachment content is now captured during IMAP sync (part.get_payload(decode=True)), saved to {storage_path}/mail_attachments/{mail_id}/{filename}, and MailAttachment records are created in the DB.
  2. Download: Existing GET /{mail_id}/attachments/{att_id} route streams the file from disk with correct Content-Type and Content-Disposition headers.
  3. Upload: New POST /upload-attachment endpoint accepts multipart/form-data, validates 25MB max size, saves to temp directory, returns attachment ID.
  4. Send with attachments: send_mail route resolves attachment IDs to file paths, passes them to send_mail_via_smtp which adds them to the EmailMessage via msg.add_attachment().
  5. Frontend display: MailDetail shows attachments with icon, filename, size (using size_bytes), and download button.
  6. Frontend compose: ComposeModal has file input button, uploads files via uploadAttachment, shows list with remove option, passes attachment IDs in send payload.

Security

  • Filenames sanitized via _sanitize_filename (removes path components, replaces dangerous characters)
  • Max 25MB per attachment enforced in both upload endpoint and frontend
  • No path traversal possible (basename extraction + character replacement)