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

1""" 

2 

3 

4 

5 ---------------------------------------------------------------------------- 

6 

7 METADATA: 

8 

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 

16 

17 ---------------------------------------------------------------------------- 

18 

19 LAST MODIFIED: 

20 

21 2025-03-07 By Jess Mann 

22 

23""" 

24from __future__ import annotations 

25 

26from abc import ABC 

27from datetime import datetime, timezone 

28from typing import TYPE_CHECKING, Any, Generic, override 

29 

30import factory 

31from factory.base import StubObject 

32from faker import Faker 

33from typing_extensions import TypeVar 

34 

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 

41 

42fake = Faker() 

43 

44_StandardModel = TypeVar("_StandardModel", bound="StandardModel", default="StandardModel") 

45 

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 

50 

51 @classmethod 

52 def get_resource(cls) -> "BaseResource": 

53 """ 

54 Get the resource for the model. 

55 

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 

60 

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. 

65 

66 Args: 

67 **kwargs: Arbitrary keyword arguments to pass to the model creation. 

68 

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) 

74 

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. 

79 

80 Args: 

81 exclude_unset (bool): Whether to exclude fields that are unset. 

82 **kwargs: Arbitrary keyword arguments to pass to the model creation. 

83 

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) 

89 

90class CorrespondentFactory(PydanticFactory[Correspondent]): 

91 class Meta: # type: ignore # pyright handles this wrong 

92 model = Correspondent 

93 

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") 

102 

103class CustomFieldFactory(PydanticFactory[CustomField]): 

104 class Meta: # type: ignore # pyright handles this wrong 

105 model = CustomField 

106 

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) 

111 

112class DocumentNoteFactory(PydanticFactory[DocumentNote]): 

113 class Meta: # type: ignore # pyright handles this wrong 

114 model = DocumentNote 

115 

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) 

123 

124class DocumentFactory(PydanticFactory[Document]): 

125 class Meta: # type: ignore # pyright handles this wrong 

126 model = Document 

127 

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)]) 

148 

149class DocumentTypeFactory(PydanticFactory[DocumentType]): 

150 class Meta: # type: ignore # pyright handles this wrong 

151 model = DocumentType 

152 

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") 

161 

162class TagFactory(PydanticFactory[Tag]): 

163 class Meta: # type: ignore # pyright handles this wrong 

164 model = Tag 

165 

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") 

176 

177class ProfileFactory(PydanticFactory[Profile]): 

178 class Meta: # type: ignore # pyright handles this wrong 

179 model = Profile 

180 

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") 

188 

189class UserFactory(PydanticFactory[User]): 

190 class Meta: # type: ignore # pyright handles this wrong 

191 model = User 

192 

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)]) 

205 

206class StoragePathFactory(PydanticFactory[StoragePath]): 

207 class Meta: # type: ignore # pyright handles this wrong 

208 model = StoragePath 

209 

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") 

219 

220class SavedViewFactory(PydanticFactory[SavedView]): 

221 class Meta: # type: ignore # pyright handles this wrong 

222 model = SavedView 

223 

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") 

235 

236class ShareLinksFactory(PydanticFactory[ShareLinks]): 

237 class Meta: # type: ignore # pyright handles this wrong 

238 model = ShareLinks 

239 

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") 

245 

246class TaskFactory(PydanticFactory[Task]): 

247 class Meta: # type: ignore # pyright handles this wrong 

248 model = Task 

249 

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) 

258 

259class UISettingsFactory(PydanticFactory[UISettings]): 

260 class Meta: # type: ignore # pyright handles this wrong 

261 model = UISettings 

262 

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)]) 

266 

267class GroupFactory(PydanticFactory[Group]): 

268 class Meta: # type: ignore # pyright handles this wrong 

269 model = Group 

270 

271 name = factory.Faker("word") 

272 permissions = factory.List([factory.Faker("word") for _ in range(5)]) 

273 

274class WorkflowTriggerFactory(PydanticFactory[WorkflowTrigger]): 

275 class Meta: # type: ignore # pyright handles this wrong 

276 model = WorkflowTrigger 

277 

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) 

289 

290class WorkflowActionFactory(PydanticFactory[WorkflowAction]): 

291 class Meta: # type: ignore # pyright handles this wrong 

292 model = WorkflowAction 

293 

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") 

305 

306class WorkflowFactory(PydanticFactory[Workflow]): 

307 class Meta: # type: ignore # pyright handles this wrong 

308 model = Workflow 

309 

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)])