Coverage for src/paperap/tests/factories/models.py: 99%
217 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-15 03:55 -0400
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-15 03:55 -0400
1"""
5 ----------------------------------------------------------------------------
7 METADATA:
9 File: models.py
10 Project: paperap
11 Created: 2025-03-07
12 Version: 0.0.7
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
34import secrets
36from paperap.models import (StandardModel, Correspondent, CustomField, Document,
37 DocumentNote, DocumentType, Group, Profile, SavedView,
38 ShareLinks, StoragePath, Tag, Task, UISettings,
39 User, Workflow, WorkflowAction, WorkflowTrigger)
40if TYPE_CHECKING:
41 from paperap.resources import BaseResource
43fake = Faker()
45_StandardModel = TypeVar("_StandardModel", bound="StandardModel", default="StandardModel")
47class PydanticFactory(factory.Factory[_StandardModel], Generic[_StandardModel]):
48 """Base factory for Pydantic models."""
49 class Meta: # type: ignore # pyright handles this wrong
50 abstract = True
52 @classmethod
53 def get_resource(cls) -> "BaseResource":
54 """
55 Get the resource for the model.
57 Returns:
58 The resource for the model specified in this factory's Meta.model
59 """
60 return cls._meta.model._meta.resource # type: ignore # model is always defined on subclasses
62 @classmethod
63 def create_api_data(cls, exclude_unset : bool = False, **kwargs : Any) -> dict[str, Any]:
64 """
65 Create a model, then transform its fields into sample API data.
67 Args:
68 **kwargs: Arbitrary keyword arguments to pass to the model creation.
70 Returns:
71 dict: A dictionary of the model's fields.
72 """
73 _instance = cls.create(**kwargs)
74 return cls.get_resource().transform_data_output(_instance, exclude_unset = exclude_unset)
76 @classmethod
77 def to_dict(cls, exclude_unset : bool = False, **kwargs) -> dict[str, Any]:
78 """
79 Create a model, and return a dictionary of the model's fields.
81 Args:
82 exclude_unset (bool): Whether to exclude fields that are unset.
83 **kwargs: Arbitrary keyword arguments to pass to the model creation.
85 Returns:
86 dict: A dictionary of the model's fields.
87 """
88 _instance = cls.create(**kwargs)
89 return _instance.to_dict(exclude_unset=exclude_unset)
91class CorrespondentFactory(PydanticFactory[Correspondent]):
92 class Meta: # type: ignore # pyright handles this wrong
93 model = Correspondent
95 slug = factory.LazyFunction(fake.slug)
96 name = factory.Faker("name")
97 match = factory.Faker("word")
98 matching_algorithm = factory.Faker("random_int", min=0, max=3)
99 is_insensitive = factory.Faker("boolean")
100 document_count = factory.Faker("random_int", min=0, max=100)
101 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
102 user_can_change = factory.Faker("boolean")
104class CustomFieldFactory(PydanticFactory[CustomField]):
105 class Meta: # type: ignore # pyright handles this wrong
106 model = CustomField
108 name = factory.Faker("word")
109 data_type = factory.Faker("word")
110 extra_data = factory.Dict({"key": fake.word(), "value": fake.word()})
111 document_count = factory.Faker("random_int", min=0, max=100)
113class DocumentNoteFactory(PydanticFactory[DocumentNote]):
114 class Meta: # type: ignore # pyright handles this wrong
115 model = DocumentNote
117 note = factory.Faker("sentence")
118 created = factory.LazyFunction(datetime.now)
119 deleted_at = None
120 restored_at = None
121 transaction_id = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
122 document = factory.Faker("random_int", min=1, max=1000)
123 user = factory.Faker("random_int", min=1, max=1000)
125class DocumentFactory(PydanticFactory[Document]):
126 class Meta: # type: ignore # pyright handles this wrong
127 model = Document
129 added = factory.LazyFunction(datetime.now)
130 archive_serial_number = factory.Faker("random_int", min=1, max=100000)
131 archived_file_name = factory.Faker("file_name")
132 content = factory.Faker("text")
133 correspondent = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
134 created = factory.LazyFunction(datetime.now)
135 created_date = factory.Maybe(factory.Faker("boolean"), factory.Faker("date"), None)
136 updated = factory.LazyFunction(datetime.now)
137 deleted_at = None
138 document_type = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
139 is_shared_by_requester = factory.Faker("boolean")
140 original_file_name = factory.Faker("file_name")
141 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
142 page_count = factory.Faker("random_int", min=1, max=500)
143 storage_path = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
144 tag_ids = factory.List([factory.Faker("random_int", min=1, max=50) for _ in range(5)])
145 title = factory.Faker("sentence")
146 user_can_change = factory.Faker("boolean")
147 # notes is a list of DocumentNote instances
148 notes = factory.LazyFunction(lambda: [DocumentNoteFactory.create() for _ in range(3)])
150class DocumentTypeFactory(PydanticFactory[DocumentType]):
151 class Meta: # type: ignore # pyright handles this wrong
152 model = DocumentType
154 name = factory.Faker("word")
155 slug = factory.LazyFunction(fake.slug)
156 match = factory.Faker("word")
157 matching_algorithm = factory.Faker("random_int", min=0, max=3)
158 is_insensitive = factory.Faker("boolean")
159 document_count = factory.Faker("random_int", min=0, max=1000)
160 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
161 user_can_change = factory.Faker("boolean")
163class TagFactory(PydanticFactory[Tag]):
164 class Meta: # type: ignore # pyright handles this wrong
165 model = Tag
167 name = factory.Faker("word")
168 slug = factory.LazyFunction(fake.slug)
169 colour = factory.Faker("hex_color")
170 match = factory.Faker("word")
171 matching_algorithm = factory.Faker("random_int", min=0, max=3)
172 is_insensitive = factory.Faker("boolean")
173 is_inbox_tag = factory.Faker("boolean")
174 document_count = factory.Faker("random_int", min=0, max=500)
175 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
176 user_can_change = factory.Faker("boolean")
178class ProfileFactory(PydanticFactory[Profile]):
179 class Meta: # type: ignore # pyright handles this wrong
180 model = Profile
182 email = factory.Faker("email")
183 password = factory.Faker("password")
184 first_name = factory.Faker("first_name")
185 last_name = factory.Faker("last_name")
186 auth_token = factory.LazyFunction(lambda: secrets.token_hex(20))
187 social_accounts = factory.List([factory.Faker("url") for _ in range(3)])
188 has_usable_password = factory.Faker("boolean")
190class UserFactory(PydanticFactory[User]):
191 class Meta: # type: ignore # pyright handles this wrong
192 model = User
194 username = factory.Faker("user_name")
195 email = factory.Faker("email")
196 password = factory.Faker("password")
197 first_name = factory.Faker("first_name")
198 last_name = factory.Faker("last_name")
199 date_joined = factory.Faker("iso8601")
200 is_staff = factory.Faker("boolean")
201 is_active = factory.Faker("boolean")
202 is_superuser = factory.Faker("boolean")
203 groups = factory.List([factory.Faker("random_int", min=1, max=10) for _ in range(3)])
204 user_permissions = factory.List([factory.Faker("word") for _ in range(5)])
205 inherited_permissions = factory.List([factory.Faker("word") for _ in range(5)])
207class StoragePathFactory(PydanticFactory[StoragePath]):
208 class Meta: # type: ignore # pyright handles this wrong
209 model = StoragePath
211 name = factory.Faker("word")
212 slug = factory.LazyFunction(fake.slug)
213 path = factory.Faker("file_path")
214 match = factory.Faker("word")
215 matching_algorithm = factory.Faker("random_int", min=0, max=3)
216 is_insensitive = factory.Faker("boolean")
217 document_count = factory.Faker("random_int", min=0, max=500)
218 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
219 user_can_change = factory.Faker("boolean")
221class SavedViewFactory(PydanticFactory[SavedView]):
222 class Meta: # type: ignore # pyright handles this wrong
223 model = SavedView
225 name = factory.Faker("sentence", nb_words=3)
226 show_on_dashboard = factory.Faker("boolean")
227 show_in_sidebar = factory.Faker("boolean")
228 sort_field = factory.Faker("word")
229 sort_reverse = factory.Faker("boolean")
230 filter_rules = factory.List([{"key": fake.word(), "value": fake.word()} for _ in range(3)])
231 page_size = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=10, max=100), None)
232 display_mode = factory.Faker("word")
233 display_fields = factory.List([factory.Faker("word") for _ in range(5)])
234 owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
235 user_can_change = factory.Faker("boolean")
237class ShareLinksFactory(PydanticFactory[ShareLinks]):
238 class Meta: # type: ignore # pyright handles this wrong
239 model = ShareLinks
241 expiration = factory.Maybe(factory.Faker("boolean"), factory.Faker("future_datetime"), None)
242 slug = factory.Faker("slug")
243 document = factory.Faker("random_int", min=1, max=1000)
244 created = factory.LazyFunction(datetime.now)
245 file_version = factory.Faker("word")
247class TaskFactory(PydanticFactory[Task]):
248 class Meta: # type: ignore # pyright handles this wrong
249 model = Task
251 task_id = factory.Faker("uuid4")
252 task_file_name = factory.Faker("file_name")
253 date_done = factory.Maybe(factory.Faker("boolean"), factory.Faker("iso8601"), None)
254 type = factory.Maybe(factory.Faker("boolean"), factory.Faker("word"), None)
255 status = factory.Faker("random_element", elements=["pending", "completed", "failed"])
256 result = factory.Maybe(factory.Faker("boolean"), factory.Faker("sentence"), None)
257 acknowledged = factory.Faker("boolean")
258 related_document = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=1000), None)
260class UISettingsFactory(PydanticFactory[UISettings]):
261 class Meta: # type: ignore # pyright handles this wrong
262 model = UISettings
264 user = factory.Dict({"theme": "dark", "language": "en"})
265 settings = factory.Dict({"dashboard_layout": "grid", "notification_settings": {"email": True}})
266 permissions = factory.List([factory.Faker("word") for _ in range(5)])
268class GroupFactory(PydanticFactory[Group]):
269 class Meta: # type: ignore # pyright handles this wrong
270 model = Group
272 name = factory.Faker("word")
273 permissions = factory.List([factory.Faker("word") for _ in range(5)])
275class WorkflowTriggerFactory(PydanticFactory[WorkflowTrigger]):
276 class Meta: # type: ignore # pyright handles this wrong
277 model = WorkflowTrigger
279 sources = factory.List([factory.Faker("word") for _ in range(3)])
280 type = factory.Faker("random_int", min=1, max=10)
281 filter_path = factory.Maybe(factory.Faker("boolean"), factory.Faker("file_path"), None)
282 filter_filename = factory.Maybe(factory.Faker("boolean"), factory.Faker("file_name"), None)
283 filter_mailrule = factory.Maybe(factory.Faker("boolean"), factory.Faker("word"), None)
284 matching_algorithm = factory.Faker("random_int", min=0, max=3)
285 match = factory.Faker("word")
286 is_insensitive = factory.Faker("boolean")
287 filter_has_tags = factory.List([factory.Faker("random_int", min=1, max=50) for _ in range(5)])
288 filter_has_correspondent = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
289 filter_has_document_type = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
291class WorkflowActionFactory(PydanticFactory[WorkflowAction]):
292 class Meta: # type: ignore # pyright handles this wrong
293 model = WorkflowAction
295 type = factory.Faker("word")
296 assign_title = factory.Maybe(factory.Faker("boolean"), factory.Faker("sentence"), None)
297 assign_tags = factory.List([factory.Faker("random_int", min=1, max=50) for _ in range(3)])
298 assign_correspondent = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
299 assign_document_type = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
300 assign_storage_path = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
301 assign_owner = factory.Maybe(factory.Faker("boolean"), factory.Faker("random_int", min=1, max=100), None)
302 assign_view_users = factory.List([factory.Faker("random_int", min=1, max=50) for _ in range(3)])
303 assign_view_groups = factory.List([factory.Faker("random_int", min=1, max=10) for _ in range(3)])
304 remove_all_tags = factory.Faker("boolean")
305 remove_all_custom_fields = factory.Faker("boolean")
307class WorkflowFactory(PydanticFactory[Workflow]):
308 class Meta: # type: ignore # pyright handles this wrong
309 model = Workflow
311 name = factory.Faker("sentence", nb_words=3)
312 order = factory.Faker("random_int", min=1, max=100)
313 enabled = factory.Faker("boolean")
314 triggers = factory.List([factory.Dict({"type": fake.random_int(min=1, max=10), "match": fake.word()}) for _ in range(3)])
315 actions = factory.List([factory.Dict({"type": fake.word(), "assign_tags": [fake.random_int(min=1, max=50)]}) for _ in range(3)])