feat(tasks): refactor IMAP and watch folder polling to support multi-tenant user attribution

- Add owner_id parameter to pull_inbox() and fetch_attachments_and_enqueue()
  to attribute ingested documents to the correct user
- Add _pull_user_integration_imap() to poll IMAP sources from UserIntegration model
- Add _pull_user_integration_watch_folders() to scan watch folders from UserIntegration model
- Add _is_safe_watch_path() for path traversal security on user-configured paths
- Add _scan_user_watch_folder() that passes owner_id to _enqueue_file()
- Update _enqueue_file() to forward owner_id to process_document/convert_to_pdf
- Update celery beat schedule to always enable IMAP and watch folder polling
  (user integrations can exist without system-level config)
- Ensure individual connection failures don't crash the polling loop
- Update existing tests for new function signatures

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-03-08 18:38:22 +00:00
parent 322aef98ae
commit 019807d0f5
5 changed files with 366 additions and 44 deletions
+3 -3
View File
@@ -834,7 +834,7 @@ class TestEnqueueFileNonPdf:
patch("app.tasks.watch_folder_tasks.process_document") as mock_proc,
):
_enqueue_file("/tmp/doc.docx")
mock_conv.delay.assert_called_once_with("/tmp/doc.docx")
mock_conv.delay.assert_called_once_with("/tmp/doc.docx", owner_id=None)
mock_proc.delay.assert_not_called()
def test_pdf_triggers_process_document(self):
@@ -846,7 +846,7 @@ class TestEnqueueFileNonPdf:
patch("app.tasks.watch_folder_tasks.process_document") as mock_proc,
):
_enqueue_file("/tmp/report.pdf")
mock_proc.delay.assert_called_once_with("/tmp/report.pdf")
mock_proc.delay.assert_called_once_with("/tmp/report.pdf", owner_id=None)
mock_conv.delay.assert_not_called()
def test_pdf_detected_via_filename_param(self):
@@ -858,7 +858,7 @@ class TestEnqueueFileNonPdf:
patch("app.tasks.watch_folder_tasks.process_document") as mock_proc,
):
_enqueue_file("/tmp/somefile", filename="renamed.pdf")
mock_proc.delay.assert_called_once_with("/tmp/somefile")
mock_proc.delay.assert_called_once_with("/tmp/somefile", owner_id=None)
mock_conv.delay.assert_not_called()