mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-02 13:45:10 -05:00
1018 lines
38 KiB
Python
1018 lines
38 KiB
Python
from datetime import timedelta
|
|
from pathlib import Path
|
|
from unittest import mock
|
|
|
|
from django.contrib.auth.models import Group
|
|
from django.contrib.auth.models import User
|
|
from django.utils import timezone
|
|
from rest_framework.test import APITestCase
|
|
|
|
from documents import tasks
|
|
from documents.data_models import ConsumableDocument
|
|
from documents.data_models import DocumentSource
|
|
from documents.matching import document_matches_workflow
|
|
from documents.models import Correspondent
|
|
from documents.models import CustomField
|
|
from documents.models import Document
|
|
from documents.models import DocumentType
|
|
from documents.models import MatchingModel
|
|
from documents.models import StoragePath
|
|
from documents.models import Tag
|
|
from documents.models import Workflow
|
|
from documents.models import WorkflowAction
|
|
from documents.models import WorkflowTrigger
|
|
from documents.signals import document_consumption_finished
|
|
from documents.tests.utils import DirectoriesMixin
|
|
from documents.tests.utils import FileSystemAssertsMixin
|
|
from paperless_mail.models import MailAccount
|
|
from paperless_mail.models import MailRule
|
|
|
|
|
|
class TestWorkflows(DirectoriesMixin, FileSystemAssertsMixin, APITestCase):
|
|
SAMPLE_DIR = Path(__file__).parent / "samples"
|
|
|
|
def setUp(self) -> None:
|
|
self.c = Correspondent.objects.create(name="Correspondent Name")
|
|
self.c2 = Correspondent.objects.create(name="Correspondent Name 2")
|
|
self.dt = DocumentType.objects.create(name="DocType Name")
|
|
self.t1 = Tag.objects.create(name="t1")
|
|
self.t2 = Tag.objects.create(name="t2")
|
|
self.t3 = Tag.objects.create(name="t3")
|
|
self.sp = StoragePath.objects.create(path="/test/")
|
|
self.cf1 = CustomField.objects.create(name="Custom Field 1", data_type="string")
|
|
self.cf2 = CustomField.objects.create(
|
|
name="Custom Field 2",
|
|
data_type="integer",
|
|
)
|
|
|
|
self.user2 = User.objects.create(username="user2")
|
|
self.user3 = User.objects.create(username="user3")
|
|
self.group1 = Group.objects.create(name="group1")
|
|
|
|
account1 = MailAccount.objects.create(
|
|
name="Email1",
|
|
username="username1",
|
|
password="password1",
|
|
imap_server="server.example.com",
|
|
imap_port=443,
|
|
imap_security=MailAccount.ImapSecurity.SSL,
|
|
character_set="UTF-8",
|
|
)
|
|
self.rule1 = MailRule.objects.create(
|
|
name="Rule1",
|
|
account=account1,
|
|
folder="INBOX",
|
|
filter_from="from@example.com",
|
|
filter_to="someone@somewhere.com",
|
|
filter_subject="subject",
|
|
filter_body="body",
|
|
filter_attachment_filename_include="file.pdf",
|
|
maximum_age=30,
|
|
action=MailRule.MailAction.MARK_READ,
|
|
assign_title_from=MailRule.TitleSource.NONE,
|
|
assign_correspondent_from=MailRule.CorrespondentSource.FROM_NOTHING,
|
|
order=0,
|
|
attachment_type=MailRule.AttachmentProcessing.ATTACHMENTS_ONLY,
|
|
assign_owner_from_rule=False,
|
|
)
|
|
|
|
return super().setUp()
|
|
|
|
@mock.patch("documents.consumer.Consumer.try_consume_file")
|
|
def test_workflow_match(self, m):
|
|
"""
|
|
GIVEN:
|
|
- Existing workflow
|
|
WHEN:
|
|
- File that matches is consumed
|
|
THEN:
|
|
- Template overrides are applied
|
|
"""
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
sources=f"{DocumentSource.ApiUpload},{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
|
|
filter_filename="*simple*",
|
|
filter_path="*/samples/*",
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc from {correspondent}",
|
|
assign_correspondent=self.c,
|
|
assign_document_type=self.dt,
|
|
assign_storage_path=self.sp,
|
|
assign_owner=self.user2,
|
|
)
|
|
action.assign_tags.add(self.t1)
|
|
action.assign_tags.add(self.t2)
|
|
action.assign_tags.add(self.t3)
|
|
action.assign_view_users.add(self.user3.pk)
|
|
action.assign_view_groups.add(self.group1.pk)
|
|
action.assign_change_users.add(self.user3.pk)
|
|
action.assign_change_groups.add(self.group1.pk)
|
|
action.assign_custom_fields.add(self.cf1.pk)
|
|
action.assign_custom_fields.add(self.cf2.pk)
|
|
action.save()
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
self.assertEqual(w.__str__(), "Workflow: Workflow 1")
|
|
self.assertEqual(trigger.__str__(), "WorkflowTrigger 1")
|
|
self.assertEqual(action.__str__(), "WorkflowAction 1")
|
|
|
|
test_file = self.SAMPLE_DIR / "simple.pdf"
|
|
|
|
with mock.patch("documents.tasks.async_to_sync"):
|
|
with self.assertLogs("paperless.matching", level="INFO") as cm:
|
|
tasks.consume_file(
|
|
ConsumableDocument(
|
|
source=DocumentSource.ConsumeFolder,
|
|
original_file=test_file,
|
|
),
|
|
None,
|
|
)
|
|
m.assert_called_once()
|
|
_, overrides = m.call_args
|
|
self.assertEqual(overrides["override_correspondent_id"], self.c.pk)
|
|
self.assertEqual(overrides["override_document_type_id"], self.dt.pk)
|
|
self.assertEqual(
|
|
overrides["override_tag_ids"],
|
|
[self.t1.pk, self.t2.pk, self.t3.pk],
|
|
)
|
|
self.assertEqual(overrides["override_storage_path_id"], self.sp.pk)
|
|
self.assertEqual(overrides["override_owner_id"], self.user2.pk)
|
|
self.assertEqual(overrides["override_view_users"], [self.user3.pk])
|
|
self.assertEqual(overrides["override_view_groups"], [self.group1.pk])
|
|
self.assertEqual(overrides["override_change_users"], [self.user3.pk])
|
|
self.assertEqual(overrides["override_change_groups"], [self.group1.pk])
|
|
self.assertEqual(
|
|
overrides["override_title"],
|
|
"Doc from {correspondent}",
|
|
)
|
|
self.assertEqual(
|
|
overrides["override_custom_field_ids"],
|
|
[self.cf1.pk, self.cf2.pk],
|
|
)
|
|
|
|
info = cm.output[0]
|
|
expected_str = f"Document matched {trigger} from {w}"
|
|
self.assertIn(expected_str, info)
|
|
|
|
@mock.patch("documents.consumer.Consumer.try_consume_file")
|
|
def test_workflow_match_mailrule(self, m):
|
|
"""
|
|
GIVEN:
|
|
- Existing workflow
|
|
WHEN:
|
|
- File that matches is consumed via mail rule
|
|
THEN:
|
|
- Template overrides are applied
|
|
"""
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
sources=f"{DocumentSource.ApiUpload},{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
|
|
filter_mailrule=self.rule1,
|
|
)
|
|
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc from {correspondent}",
|
|
assign_correspondent=self.c,
|
|
assign_document_type=self.dt,
|
|
assign_storage_path=self.sp,
|
|
assign_owner=self.user2,
|
|
)
|
|
action.assign_tags.add(self.t1)
|
|
action.assign_tags.add(self.t2)
|
|
action.assign_tags.add(self.t3)
|
|
action.assign_view_users.add(self.user3.pk)
|
|
action.assign_view_groups.add(self.group1.pk)
|
|
action.assign_change_users.add(self.user3.pk)
|
|
action.assign_change_groups.add(self.group1.pk)
|
|
action.save()
|
|
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
test_file = self.SAMPLE_DIR / "simple.pdf"
|
|
with mock.patch("documents.tasks.async_to_sync"):
|
|
with self.assertLogs("paperless.matching", level="INFO") as cm:
|
|
tasks.consume_file(
|
|
ConsumableDocument(
|
|
source=DocumentSource.ConsumeFolder,
|
|
original_file=test_file,
|
|
mailrule_id=self.rule1.pk,
|
|
),
|
|
None,
|
|
)
|
|
m.assert_called_once()
|
|
_, overrides = m.call_args
|
|
self.assertEqual(overrides["override_correspondent_id"], self.c.pk)
|
|
self.assertEqual(overrides["override_document_type_id"], self.dt.pk)
|
|
self.assertEqual(
|
|
overrides["override_tag_ids"],
|
|
[self.t1.pk, self.t2.pk, self.t3.pk],
|
|
)
|
|
self.assertEqual(overrides["override_storage_path_id"], self.sp.pk)
|
|
self.assertEqual(overrides["override_owner_id"], self.user2.pk)
|
|
self.assertEqual(overrides["override_view_users"], [self.user3.pk])
|
|
self.assertEqual(overrides["override_view_groups"], [self.group1.pk])
|
|
self.assertEqual(overrides["override_change_users"], [self.user3.pk])
|
|
self.assertEqual(overrides["override_change_groups"], [self.group1.pk])
|
|
self.assertEqual(
|
|
overrides["override_title"],
|
|
"Doc from {correspondent}",
|
|
)
|
|
|
|
info = cm.output[0]
|
|
expected_str = f"Document matched {trigger} from {w}"
|
|
self.assertIn(expected_str, info)
|
|
|
|
@mock.patch("documents.consumer.Consumer.try_consume_file")
|
|
def test_workflow_match_multiple(self, m):
|
|
"""
|
|
GIVEN:
|
|
- Multiple existing workflow
|
|
WHEN:
|
|
- File that matches is consumed
|
|
THEN:
|
|
- Template overrides are applied with subsequent templates overwriting previous values
|
|
or merging if multiple
|
|
"""
|
|
trigger1 = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
sources=f"{DocumentSource.ApiUpload},{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
|
|
filter_path="*/samples/*",
|
|
)
|
|
action1 = WorkflowAction.objects.create(
|
|
assign_title="Doc from {correspondent}",
|
|
assign_correspondent=self.c,
|
|
assign_document_type=self.dt,
|
|
)
|
|
action1.assign_tags.add(self.t1)
|
|
action1.assign_tags.add(self.t2)
|
|
action1.assign_view_users.add(self.user2)
|
|
action1.save()
|
|
|
|
w1 = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w1.triggers.add(trigger1)
|
|
w1.actions.add(action1)
|
|
w1.save()
|
|
|
|
trigger2 = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
sources=f"{DocumentSource.ApiUpload},{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
|
|
filter_filename="*simple*",
|
|
)
|
|
action2 = WorkflowAction.objects.create(
|
|
assign_title="Doc from {correspondent}",
|
|
assign_correspondent=self.c2,
|
|
assign_storage_path=self.sp,
|
|
)
|
|
action2.assign_tags.add(self.t3)
|
|
action2.assign_view_users.add(self.user3)
|
|
action2.save()
|
|
|
|
w2 = Workflow.objects.create(
|
|
name="Workflow 2",
|
|
order=0,
|
|
)
|
|
w2.triggers.add(trigger2)
|
|
w2.actions.add(action2)
|
|
w2.save()
|
|
|
|
test_file = self.SAMPLE_DIR / "simple.pdf"
|
|
|
|
with mock.patch("documents.tasks.async_to_sync"):
|
|
with self.assertLogs("paperless.matching", level="INFO") as cm:
|
|
tasks.consume_file(
|
|
ConsumableDocument(
|
|
source=DocumentSource.ConsumeFolder,
|
|
original_file=test_file,
|
|
),
|
|
None,
|
|
)
|
|
m.assert_called_once()
|
|
_, overrides = m.call_args
|
|
# template 1
|
|
self.assertEqual(overrides["override_document_type_id"], self.dt.pk)
|
|
# template 2
|
|
self.assertEqual(overrides["override_correspondent_id"], self.c2.pk)
|
|
self.assertEqual(overrides["override_storage_path_id"], self.sp.pk)
|
|
# template 1 & 2
|
|
self.assertEqual(
|
|
overrides["override_tag_ids"],
|
|
[self.t1.pk, self.t2.pk, self.t3.pk],
|
|
)
|
|
self.assertEqual(
|
|
overrides["override_view_users"],
|
|
[self.user2.pk, self.user3.pk],
|
|
)
|
|
|
|
expected_str = f"Document matched {trigger1} from {w1}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = f"Document matched {trigger2} from {w2}"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
@mock.patch("documents.consumer.Consumer.try_consume_file")
|
|
def test_workflow_no_match_filename(self, m):
|
|
"""
|
|
GIVEN:
|
|
- Existing workflow
|
|
WHEN:
|
|
- File that does not match on filename is consumed
|
|
THEN:
|
|
- Template overrides are not applied
|
|
"""
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
sources=f"{DocumentSource.ApiUpload},{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
|
|
filter_filename="*foobar*",
|
|
filter_path=None,
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc from {correspondent}",
|
|
assign_correspondent=self.c,
|
|
assign_document_type=self.dt,
|
|
assign_storage_path=self.sp,
|
|
assign_owner=self.user2,
|
|
)
|
|
action.save()
|
|
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
test_file = self.SAMPLE_DIR / "simple.pdf"
|
|
|
|
with mock.patch("documents.tasks.async_to_sync"):
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
tasks.consume_file(
|
|
ConsumableDocument(
|
|
source=DocumentSource.ConsumeFolder,
|
|
original_file=test_file,
|
|
),
|
|
None,
|
|
)
|
|
m.assert_called_once()
|
|
_, overrides = m.call_args
|
|
self.assertIsNone(overrides["override_correspondent_id"])
|
|
self.assertIsNone(overrides["override_document_type_id"])
|
|
self.assertIsNone(overrides["override_tag_ids"])
|
|
self.assertIsNone(overrides["override_storage_path_id"])
|
|
self.assertIsNone(overrides["override_owner_id"])
|
|
self.assertIsNone(overrides["override_view_users"])
|
|
self.assertIsNone(overrides["override_view_groups"])
|
|
self.assertIsNone(overrides["override_change_users"])
|
|
self.assertIsNone(overrides["override_change_groups"])
|
|
self.assertIsNone(overrides["override_title"])
|
|
|
|
expected_str = f"Document did not match {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = f"Document filename {test_file.name} does not match"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
@mock.patch("documents.consumer.Consumer.try_consume_file")
|
|
def test_workflow_no_match_path(self, m):
|
|
"""
|
|
GIVEN:
|
|
- Existing workflow
|
|
WHEN:
|
|
- File that does not match on path is consumed
|
|
THEN:
|
|
- Template overrides are not applied
|
|
"""
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
sources=f"{DocumentSource.ApiUpload},{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
|
|
filter_path="*foo/bar*",
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc from {correspondent}",
|
|
assign_correspondent=self.c,
|
|
assign_document_type=self.dt,
|
|
assign_storage_path=self.sp,
|
|
assign_owner=self.user2,
|
|
)
|
|
action.save()
|
|
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
test_file = self.SAMPLE_DIR / "simple.pdf"
|
|
|
|
with mock.patch("documents.tasks.async_to_sync"):
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
tasks.consume_file(
|
|
ConsumableDocument(
|
|
source=DocumentSource.ConsumeFolder,
|
|
original_file=test_file,
|
|
),
|
|
None,
|
|
)
|
|
m.assert_called_once()
|
|
_, overrides = m.call_args
|
|
self.assertIsNone(overrides["override_correspondent_id"])
|
|
self.assertIsNone(overrides["override_document_type_id"])
|
|
self.assertIsNone(overrides["override_tag_ids"])
|
|
self.assertIsNone(overrides["override_storage_path_id"])
|
|
self.assertIsNone(overrides["override_owner_id"])
|
|
self.assertIsNone(overrides["override_view_users"])
|
|
self.assertIsNone(overrides["override_view_groups"])
|
|
self.assertIsNone(overrides["override_change_users"])
|
|
self.assertIsNone(overrides["override_change_groups"])
|
|
self.assertIsNone(overrides["override_title"])
|
|
|
|
expected_str = f"Document did not match {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = f"Document path {test_file} does not match"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
@mock.patch("documents.consumer.Consumer.try_consume_file")
|
|
def test_workflow_no_match_mail_rule(self, m):
|
|
"""
|
|
GIVEN:
|
|
- Existing workflow
|
|
WHEN:
|
|
- File that does not match on source is consumed
|
|
THEN:
|
|
- Template overrides are not applied
|
|
"""
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
sources=f"{DocumentSource.ApiUpload},{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
|
|
filter_mailrule=self.rule1,
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc from {correspondent}",
|
|
assign_correspondent=self.c,
|
|
assign_document_type=self.dt,
|
|
assign_storage_path=self.sp,
|
|
assign_owner=self.user2,
|
|
)
|
|
action.save()
|
|
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
test_file = self.SAMPLE_DIR / "simple.pdf"
|
|
|
|
with mock.patch("documents.tasks.async_to_sync"):
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
tasks.consume_file(
|
|
ConsumableDocument(
|
|
source=DocumentSource.ConsumeFolder,
|
|
original_file=test_file,
|
|
mailrule_id=99,
|
|
),
|
|
None,
|
|
)
|
|
m.assert_called_once()
|
|
_, overrides = m.call_args
|
|
self.assertIsNone(overrides["override_correspondent_id"])
|
|
self.assertIsNone(overrides["override_document_type_id"])
|
|
self.assertIsNone(overrides["override_tag_ids"])
|
|
self.assertIsNone(overrides["override_storage_path_id"])
|
|
self.assertIsNone(overrides["override_owner_id"])
|
|
self.assertIsNone(overrides["override_view_users"])
|
|
self.assertIsNone(overrides["override_view_groups"])
|
|
self.assertIsNone(overrides["override_change_users"])
|
|
self.assertIsNone(overrides["override_change_groups"])
|
|
self.assertIsNone(overrides["override_title"])
|
|
|
|
expected_str = f"Document did not match {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = "Document mail rule 99 !="
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
@mock.patch("documents.consumer.Consumer.try_consume_file")
|
|
def test_workflow_no_match_source(self, m):
|
|
"""
|
|
GIVEN:
|
|
- Existing workflow
|
|
WHEN:
|
|
- File that does not match on source is consumed
|
|
THEN:
|
|
- Template overrides are not applied
|
|
"""
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
sources=f"{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
|
|
filter_path="*",
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc from {correspondent}",
|
|
assign_correspondent=self.c,
|
|
assign_document_type=self.dt,
|
|
assign_storage_path=self.sp,
|
|
assign_owner=self.user2,
|
|
)
|
|
action.save()
|
|
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
test_file = self.SAMPLE_DIR / "simple.pdf"
|
|
|
|
with mock.patch("documents.tasks.async_to_sync"):
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
tasks.consume_file(
|
|
ConsumableDocument(
|
|
source=DocumentSource.ApiUpload,
|
|
original_file=test_file,
|
|
),
|
|
None,
|
|
)
|
|
m.assert_called_once()
|
|
_, overrides = m.call_args
|
|
self.assertIsNone(overrides["override_correspondent_id"])
|
|
self.assertIsNone(overrides["override_document_type_id"])
|
|
self.assertIsNone(overrides["override_tag_ids"])
|
|
self.assertIsNone(overrides["override_storage_path_id"])
|
|
self.assertIsNone(overrides["override_owner_id"])
|
|
self.assertIsNone(overrides["override_view_users"])
|
|
self.assertIsNone(overrides["override_view_groups"])
|
|
self.assertIsNone(overrides["override_change_users"])
|
|
self.assertIsNone(overrides["override_change_groups"])
|
|
self.assertIsNone(overrides["override_title"])
|
|
|
|
expected_str = f"Document did not match {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = f"Document source {DocumentSource.ApiUpload.name} not in ['{DocumentSource.ConsumeFolder.name}', '{DocumentSource.MailFetch.name}']"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
def test_document_added_no_match_trigger_type(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc assign owner",
|
|
assign_owner=self.user2,
|
|
)
|
|
action.save()
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
doc = Document.objects.create(
|
|
title="sample test",
|
|
correspondent=self.c,
|
|
original_filename="sample.pdf",
|
|
)
|
|
doc.save()
|
|
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
document_matches_workflow(
|
|
doc,
|
|
w,
|
|
WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
|
|
)
|
|
expected_str = f"Document did not match {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = f"No matching triggers with type {WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED} found"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
@mock.patch("documents.consumer.Consumer.try_consume_file")
|
|
def test_workflow_repeat_custom_fields(self, m):
|
|
"""
|
|
GIVEN:
|
|
- Existing workflows which assign the same custom field
|
|
WHEN:
|
|
- File that matches is consumed
|
|
THEN:
|
|
- Custom field is added the first time successfully
|
|
"""
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.CONSUMPTION,
|
|
sources=f"{DocumentSource.ApiUpload},{DocumentSource.ConsumeFolder},{DocumentSource.MailFetch}",
|
|
filter_filename="*simple*",
|
|
)
|
|
action1 = WorkflowAction.objects.create()
|
|
action1.assign_custom_fields.add(self.cf1.pk)
|
|
action1.save()
|
|
|
|
action2 = WorkflowAction.objects.create()
|
|
action2.assign_custom_fields.add(self.cf1.pk)
|
|
action2.save()
|
|
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action1, action2)
|
|
w.save()
|
|
|
|
test_file = self.SAMPLE_DIR / "simple.pdf"
|
|
|
|
with mock.patch("documents.tasks.async_to_sync"):
|
|
with self.assertLogs("paperless.matching", level="INFO") as cm:
|
|
tasks.consume_file(
|
|
ConsumableDocument(
|
|
source=DocumentSource.ConsumeFolder,
|
|
original_file=test_file,
|
|
),
|
|
None,
|
|
)
|
|
m.assert_called_once()
|
|
_, overrides = m.call_args
|
|
self.assertEqual(
|
|
overrides["override_custom_field_ids"],
|
|
[self.cf1.pk],
|
|
)
|
|
|
|
expected_str = f"Document matched {trigger} from {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
|
|
def test_document_added_workflow(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
|
|
filter_filename="*sample*",
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc created in {created_year}",
|
|
assign_correspondent=self.c2,
|
|
assign_document_type=self.dt,
|
|
assign_storage_path=self.sp,
|
|
assign_owner=self.user2,
|
|
)
|
|
action.assign_tags.add(self.t1)
|
|
action.assign_tags.add(self.t2)
|
|
action.assign_tags.add(self.t3)
|
|
action.assign_view_users.add(self.user3.pk)
|
|
action.assign_view_groups.add(self.group1.pk)
|
|
action.assign_change_users.add(self.user3.pk)
|
|
action.assign_change_groups.add(self.group1.pk)
|
|
action.assign_custom_fields.add(self.cf1.pk)
|
|
action.assign_custom_fields.add(self.cf2.pk)
|
|
action.save()
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
now = timezone.localtime(timezone.now())
|
|
created = now - timedelta(weeks=520)
|
|
doc = Document.objects.create(
|
|
title="sample test",
|
|
correspondent=self.c,
|
|
original_filename="sample.pdf",
|
|
added=now,
|
|
created=created,
|
|
)
|
|
|
|
document_consumption_finished.send(
|
|
sender=self.__class__,
|
|
document=doc,
|
|
)
|
|
|
|
self.assertEqual(doc.correspondent, self.c2)
|
|
self.assertEqual(doc.title, f"Doc created in {created.year}")
|
|
|
|
def test_document_added_no_match_filename(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
|
|
filter_filename="*foobar*",
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc assign owner",
|
|
assign_owner=self.user2,
|
|
)
|
|
action.save()
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
doc = Document.objects.create(
|
|
title="sample test",
|
|
correspondent=self.c,
|
|
original_filename="sample.pdf",
|
|
)
|
|
doc.tags.set([self.t3])
|
|
doc.save()
|
|
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
document_consumption_finished.send(
|
|
sender=self.__class__,
|
|
document=doc,
|
|
)
|
|
expected_str = f"Document did not match {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = f"Document filename {doc.original_filename} does not match"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
def test_document_added_match_content_matching(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
|
|
matching_algorithm=MatchingModel.MATCH_LITERAL,
|
|
match="foo",
|
|
is_insensitive=True,
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc content matching worked",
|
|
assign_owner=self.user2,
|
|
)
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
doc = Document.objects.create(
|
|
title="sample test",
|
|
correspondent=self.c,
|
|
original_filename="sample.pdf",
|
|
content="Hello world foo bar",
|
|
)
|
|
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
document_consumption_finished.send(
|
|
sender=self.__class__,
|
|
document=doc,
|
|
)
|
|
expected_str = f"WorkflowTrigger {trigger} matched on document"
|
|
expected_str2 = 'because it contains this string: "foo"'
|
|
self.assertIn(expected_str, cm.output[0])
|
|
self.assertIn(expected_str2, cm.output[0])
|
|
expected_str = f"Document matched {trigger} from {w}"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
def test_document_added_no_match_content_matching(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
|
|
matching_algorithm=MatchingModel.MATCH_LITERAL,
|
|
match="foo",
|
|
is_insensitive=True,
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc content matching worked",
|
|
assign_owner=self.user2,
|
|
)
|
|
action.save()
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
doc = Document.objects.create(
|
|
title="sample test",
|
|
correspondent=self.c,
|
|
original_filename="sample.pdf",
|
|
content="Hello world bar",
|
|
)
|
|
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
document_consumption_finished.send(
|
|
sender=self.__class__,
|
|
document=doc,
|
|
)
|
|
expected_str = f"Document did not match {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = f"Document content matching settings for algorithm '{trigger.matching_algorithm}' did not match"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
def test_document_added_no_match_tags(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
|
|
)
|
|
trigger.filter_has_tags.set([self.t1, self.t2])
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc assign owner",
|
|
assign_owner=self.user2,
|
|
)
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
doc = Document.objects.create(
|
|
title="sample test",
|
|
correspondent=self.c,
|
|
original_filename="sample.pdf",
|
|
)
|
|
doc.tags.set([self.t3])
|
|
doc.save()
|
|
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
document_consumption_finished.send(
|
|
sender=self.__class__,
|
|
document=doc,
|
|
)
|
|
expected_str = f"Document did not match {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = f"Document tags {doc.tags.all()} do not include {trigger.filter_has_tags.all()}"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
def test_document_added_no_match_doctype(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
|
|
filter_has_document_type=self.dt,
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc assign owner",
|
|
assign_owner=self.user2,
|
|
)
|
|
action.save()
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
doc = Document.objects.create(
|
|
title="sample test",
|
|
original_filename="sample.pdf",
|
|
)
|
|
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
document_consumption_finished.send(
|
|
sender=self.__class__,
|
|
document=doc,
|
|
)
|
|
expected_str = f"Document did not match {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = f"Document doc type {doc.document_type} does not match {trigger.filter_has_document_type}"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
def test_document_added_no_match_correspondent(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
|
|
filter_has_correspondent=self.c,
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc assign owner",
|
|
assign_owner=self.user2,
|
|
)
|
|
action.save()
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
doc = Document.objects.create(
|
|
title="sample test",
|
|
correspondent=self.c2,
|
|
original_filename="sample.pdf",
|
|
)
|
|
|
|
with self.assertLogs("paperless.matching", level="DEBUG") as cm:
|
|
document_consumption_finished.send(
|
|
sender=self.__class__,
|
|
document=doc,
|
|
)
|
|
expected_str = f"Document did not match {w}"
|
|
self.assertIn(expected_str, cm.output[0])
|
|
expected_str = f"Document correspondent {doc.correspondent} does not match {trigger.filter_has_correspondent}"
|
|
self.assertIn(expected_str, cm.output[1])
|
|
|
|
def test_document_updated_workflow(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_UPDATED,
|
|
filter_has_document_type=self.dt,
|
|
)
|
|
action = WorkflowAction.objects.create()
|
|
action.assign_custom_fields.add(self.cf1)
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
doc = Document.objects.create(
|
|
title="sample test",
|
|
correspondent=self.c,
|
|
original_filename="sample.pdf",
|
|
)
|
|
|
|
superuser = User.objects.create_superuser("superuser")
|
|
self.client.force_authenticate(user=superuser)
|
|
|
|
self.client.patch(
|
|
f"/api/documents/{doc.id}/",
|
|
{"document_type": self.dt.id},
|
|
format="json",
|
|
)
|
|
|
|
self.assertEqual(doc.custom_fields.all().count(), 1)
|
|
|
|
def test_workflow_enabled_disabled(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=WorkflowTrigger.WorkflowTriggerType.DOCUMENT_ADDED,
|
|
filter_filename="*sample*",
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Title assign correspondent",
|
|
assign_correspondent=self.c2,
|
|
)
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
enabled=False,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
action2 = WorkflowAction.objects.create(
|
|
assign_title="Title assign owner",
|
|
assign_owner=self.user2,
|
|
)
|
|
w2 = Workflow.objects.create(
|
|
name="Workflow 2",
|
|
order=0,
|
|
enabled=True,
|
|
)
|
|
w2.triggers.add(trigger)
|
|
w2.actions.add(action2)
|
|
w2.save()
|
|
|
|
doc = Document.objects.create(
|
|
title="sample test",
|
|
correspondent=self.c,
|
|
original_filename="sample.pdf",
|
|
)
|
|
|
|
document_consumption_finished.send(
|
|
sender=self.__class__,
|
|
document=doc,
|
|
)
|
|
|
|
self.assertEqual(doc.correspondent, self.c)
|
|
self.assertEqual(doc.title, "Title assign owner")
|
|
self.assertEqual(doc.owner, self.user2)
|
|
|
|
def test_new_trigger_type_raises_exception(self):
|
|
trigger = WorkflowTrigger.objects.create(
|
|
type=4,
|
|
)
|
|
action = WorkflowAction.objects.create(
|
|
assign_title="Doc assign owner",
|
|
)
|
|
w = Workflow.objects.create(
|
|
name="Workflow 1",
|
|
order=0,
|
|
)
|
|
w.triggers.add(trigger)
|
|
w.actions.add(action)
|
|
w.save()
|
|
|
|
doc = Document.objects.create(
|
|
title="test",
|
|
)
|
|
self.assertRaises(Exception, document_matches_workflow, doc, w, 4)
|