Coverage for src/paperap/tests/factories/models.py: 99%
216 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 23:40 -0400
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-12 23:40 -0400
1"""
5 ----------------------------------------------------------------------------
7 METADATA:
9 File: models.py
10 Project: paperap
11 Created: 2025-03-07
12 Version: 0.0.6
13 Author: Jess Mann
14 Email: jess@jmann.me
15 Copyright (c) 2025 Jess Mann
17 ----------------------------------------------------------------------------
19 LAST MODIFIED:
21 2025-03-07 By Jess Mann
23"""
24from __future__ import annotations
26from abc import ABC
27from datetime import datetime, timezone
28from typing import TYPE_CHECKING, Any, Generic, override
30import factory
31from factory.base import StubObject
32from faker import Faker
33from typing_extensions import TypeVar
35from paperap.models import (StandardModel, Correspondent, CustomField, Document,
36 DocumentNote, DocumentType, Group, Profile, SavedView,
37 ShareLinks, StoragePath, Tag, Task, UISettings,
38 User, Workflow, WorkflowAction, WorkflowTrigger)
39if TYPE_CHECKING:
40 from paperap.resources import BaseResource
42fake = Faker()
44_StandardModel = TypeVar("_StandardModel", bound="StandardModel", default="StandardModel")
46class PydanticFactory(factory.Factory[_StandardModel], Generic[_StandardModel]):
47 """Base factory for Pydantic models."""
48 class Meta: # type: ignore # pyright handles this wrong
49 abstract = True
51 @classmethod
52 def get_resource(cls) -> "BaseResource":
53 """
54 Get the resource for the model.
56 Returns:
57 The resource for the model specified in this factory's Meta.model
58 """
59 return cls._meta.model._meta.resource # type: ignore # model is always defined on subclasses
61 @classmethod
62 def create_api_data(cls, exclude_unset : bool = False, **kwargs : Any) -> dict[str, Any]:
63 """
64 Create a model, then transform its fields into sample API data.
66 Args:
67 **kwargs: Arbitrary keyword arguments to pass to the model creation.
69 Returns:
70 dict: A dictionary of the model's fields.
71 """
72 _instance = cls.create(**kwargs)
73 return cls.get_resource().transform_data_output(_instance, exclude_unset = exclude_unset)
75 @classmethod
76 def to_dict(cls, exclude_unset : bool = False, **kwargs) -> dict[str, Any]:
77 """
78 Create a model, and return a dictionary of the model's fields.
80 Args:
81 exclude_unset (bool): Whether to exclude fields that are unset.
82 **kwargs: Arbitrary keyword arguments to pass to the model creation.
84 Returns:
85 dict: A dictionary of the model's fields.
86 """
87 _instance = cls.create(**kwargs)
88 return _instance.to_dict(exclude_unset=exclude_unset)
90class CorrespondentFactory(PydanticFactory[Correspondent]):
91 class Meta: # type: ignore # pyright handles this wrong
92 model = Correspondent
94 slug = factory.LazyFunction(fake.slug)
95 name = factory.Faker("name")
96 match = factory.Faker("word")
97 matching_algorithm = factory.Faker("random_int", min=0, max=3)
98 is_insensitive = factory.Faker("boolean")
99 document_count = factory.Faker("random_int", min=0, max=100)
100 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
101 user_can_change = factory.Faker("boolean")
103class CustomFieldFactory(PydanticFactory[CustomField]):
104 class Meta: # type: ignore # pyright handles this wrong
105 model = CustomField
107 name = factory.Faker("word")
108 data_type = factory.Faker("word")
109 extra_data = factory.Dict({"key": fake.word(), "value": fake.word()})
110 document_count = factory.Faker("random_int", min=0, max=100)
112class DocumentNoteFactory(PydanticFactory[DocumentNote]):
113 class Meta: # type: ignore # pyright handles this wrong
114 model = DocumentNote
116 note = factory.Faker("sentence")
117 created = factory.LazyFunction(datetime.now)
118 deleted_at = None
119 restored_at = None
120 transaction_id = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
121 document = factory.Faker("random_int", min=1, max=1000)
122 user = factory.Faker("random_int", min=1, max=1000)
124class DocumentFactory(PydanticFactory[Document]):
125 class Meta: # type: ignore # pyright handles this wrong
126 model = Document
128 added = factory.LazyFunction(datetime.now)
129 archive_serial_number = factory.Faker("random_int", min=1, max=100000)
130 archived_file_name = factory.Faker("file_name")
131 content = factory.Faker("text")
132 correspondent = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
133 created = factory.LazyFunction(datetime.now)
134 created_date = factory.Maybe(factory.Faker("boolean"), factory.Faker("date"), None)
135 updated = factory.LazyFunction(datetime.now)
136 deleted_at = None
137 document_type = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
138 is_shared_by_requester = factory.Faker("boolean")
139 original_file_name = factory.Faker("file_name")
140 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
141 page_count = factory.Faker("random_int", min=1, max=500)
142 storage_path = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
143 tag_ids = factory.List([factory.Faker("random_int", min=1, max=50) for _ in range(5)])
144 title = factory.Faker("sentence")
145 user_can_change = factory.Faker("boolean")
146 # notes is a list of DocumentNote instances
147 notes = factory.LazyFunction(lambda: [DocumentNoteFactory.create() for _ in range(3)])
149class DocumentTypeFactory(PydanticFactory[DocumentType]):
150 class Meta: # type: ignore # pyright handles this wrong
151 model = DocumentType
153 name = factory.Faker("word")
154 slug = factory.LazyFunction(fake.slug)
155 match = factory.Faker("word")
156 matching_algorithm = factory.Faker("random_int", min=0, max=3)
157 is_insensitive = factory.Faker("boolean")
158 document_count = factory.Faker("random_int", min=0, max=1000)
159 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
160 user_can_change = factory.Faker("boolean")
162class TagFactory(PydanticFactory[Tag]):
163 class Meta: # type: ignore # pyright handles this wrong
164 model = Tag
166 name = factory.Faker("word")
167 slug = factory.LazyFunction(fake.slug)
168 colour = factory.Faker("hex_color")
169 match = factory.Faker("word")
170 matching_algorithm = factory.Faker("random_int", min=0, max=3)
171 is_insensitive = factory.Faker("boolean")
172 is_inbox_tag = factory.Faker("boolean")
173 document_count = factory.Faker("random_int", min=0, max=500)
174 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
175 user_can_change = factory.Faker("boolean")
177class ProfileFactory(PydanticFactory[Profile]):
178 class Meta: # type: ignore # pyright handles this wrong
179 model = Profile
181 email = factory.Faker("email")
182 password = factory.Faker("password")
183 first_name = factory.Faker("first_name")
184 last_name = factory.Faker("last_name")
185 auth_token = factory.Faker("uuid4")
186 social_accounts = factory.List([factory.Faker("url") for _ in range(3)])
187 has_usable_password = factory.Faker("boolean")
189class UserFactory(PydanticFactory[User]):
190 class Meta: # type: ignore # pyright handles this wrong
191 model = User
193 username = factory.Faker("user_name")
194 email = factory.Faker("email")
195 password = factory.Faker("password")
196 first_name = factory.Faker("first_name")
197 last_name = factory.Faker("last_name")
198 date_joined = factory.Faker("iso8601")
199 is_staff = factory.Faker("boolean")
200 is_active = factory.Faker("boolean")
201 is_superuser = factory.Faker("boolean")
202 groups = factory.List([factory.Faker("random_int", min=1, max=10) for _ in range(3)])
203 user_permissions = factory.List([factory.Faker("word") for _ in range(5)])
204 inherited_permissions = factory.List([factory.Faker("word") for _ in range(5)])
206class StoragePathFactory(PydanticFactory[StoragePath]):
207 class Meta: # type: ignore # pyright handles this wrong
208 model = StoragePath
210 name = factory.Faker("word")
211 slug = factory.LazyFunction(fake.slug)
212 path = factory.Faker("file_path")
213 match = factory.Faker("word")
214 matching_algorithm = factory.Faker("random_int", min=0, max=3)
215 is_insensitive = factory.Faker("boolean")
216 document_count = factory.Faker("random_int", min=0, max=500)
217 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
218 user_can_change = factory.Faker("boolean")
220class SavedViewFactory(PydanticFactory[SavedView]):
221 class Meta: # type: ignore # pyright handles this wrong
222 model = SavedView
224 name = factory.Faker("sentence", nb_words=3)
225 show_on_dashboard = factory.Faker("boolean")
226 show_in_sidebar = factory.Faker("boolean")
227 sort_field = factory.Faker("word")
228 sort_reverse = factory.Faker("boolean")
229 filter_rules = factory.List([{"key": fake.word(), "value": fake.word()} for _ in range(3)])
230 page_size = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=10, max=100), None)
231 display_mode = factory.Faker("word")
232 display_fields = factory.List([factory.Faker("word") for _ in range(5)])
233 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
234 user_can_change = factory.Faker("boolean")
236class ShareLinksFactory(PydanticFactory[ShareLinks]):
237 class Meta: # type: ignore # pyright handles this wrong
238 model = ShareLinks
240 expiration = factory.Maybe(factory.Faker("boolean"), factory.Faker("future_datetime"), None)
241 slug = factory.Faker("slug")
242 document = factory.Faker("random_int", min=1, max=1000)
243 created = factory.LazyFunction(datetime.now)
244 file_version = factory.Faker("word")
246class TaskFactory(PydanticFactory[Task]):
247 class Meta: # type: ignore # pyright handles this wrong
248 model = Task
250 task_id = factory.Faker("uuid4")
251 task_file_name = factory.Faker("file_name")
252 date_done = factory.Maybe(factory.Faker("boolean"), factory.Faker("iso8601"), None)
253 type = factory.Maybe(factory.Faker("boolean"), factory.Faker("word"), None)
254 status = factory.Faker("random_element", elements=["pending", "completed", "failed"])
255 result = factory.Maybe(factory.Faker("boolean"), factory.Faker("sentence"), None)
256 acknowledged = factory.Faker("boolean")
257 related_document = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=1000), None)
259class UISettingsFactory(PydanticFactory[UISettings]):
260 class Meta: # type: ignore # pyright handles this wrong
261 model = UISettings
263 user = factory.Dict({"theme": "dark", "language": "en"})
264 settings = factory.Dict({"dashboard_layout": "grid", "notification_settings": {"email": True}})
265 permissions = factory.List([factory.Faker("word") for _ in range(5)])
267class GroupFactory(PydanticFactory[Group]):
268 class Meta: # type: ignore # pyright handles this wrong
269 model = Group
271 name = factory.Faker("word")
272 permissions = factory.List([factory.Faker("word") for _ in range(5)])
274class WorkflowTriggerFactory(PydanticFactory[WorkflowTrigger]):
275 class Meta: # type: ignore # pyright handles this wrong
276 model = WorkflowTrigger
278 sources = factory.List([factory.Faker("word") for _ in range(3)])
279 type = factory.Faker("random_int", min=1, max=10)
280 filter_path = factory.Maybe(factory.Faker("boolean"), factory.Faker("file_path"), None)
281 filter_filename = factory.Maybe(factory.Faker("boolean"), factory.Faker("file_name"), None)
282 filter_mailrule = factory.Maybe(factory.Faker("boolean"), factory.Faker("word"), None)
283 matching_algorithm = factory.Faker("random_int", min=0, max=3)
284 match = factory.Faker("word")
285 is_insensitive = factory.Faker("boolean")
286 filter_has_tags = factory.List([factory.Faker("random_int", min=1, max=50) for _ in range(5)])
287 filter_has_correspondent = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
288 filter_has_document_type = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
290class WorkflowActionFactory(PydanticFactory[WorkflowAction]):
291 class Meta: # type: ignore # pyright handles this wrong
292 model = WorkflowAction
294 type = factory.Faker("word")
295 assign_title = factory.Maybe(factory.Faker("boolean"), factory.Faker("sentence"), None)
296 assign_tags = factory.List([factory.Faker("random_int", min=1, max=50) for _ in range(3)])
297 assign_correspondent = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
298 assign_document_type = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
299 assign_storage_path = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
300 assign_owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
301 assign_view_users = factory.List([factory.Faker("random_int", min=1, max=50) for _ in range(3)])
302 assign_view_groups = factory.List([factory.Faker("random_int", min=1, max=10) for _ in range(3)])
303 remove_all_tags = factory.Faker("boolean")
304 remove_all_custom_fields = factory.Faker("boolean")
306class WorkflowFactory(PydanticFactory[Workflow]):
307 class Meta: # type: ignore # pyright handles this wrong
308 model = Workflow
310 name = factory.Faker("sentence", nb_words=3)
311 order = factory.Faker("random_int", min=1, max=100)
312 enabled = factory.Faker("boolean")
313 triggers = factory.List([factory.Dict({"type": fake.random_int(min=1, max=10), "match": fake.word()}) for _ in range(3)])
314 actions = factory.List([factory.Dict({"type": fake.word(), "assign_tags": [fake.random_int(min=1, max=50)]}) for _ in range(3)])