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

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.7 

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 

34import secrets 

35 

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 

42 

43fake = Faker() 

44 

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

46 

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 

51 

52 @classmethod 

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

54 """ 

55 Get the resource for the model. 

56 

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 

61 

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. 

66 

67 Args: 

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

69 

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) 

75 

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. 

80 

81 Args: 

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

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

84 

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) 

90 

91class CorrespondentFactory(PydanticFactory[Correspondent]): 

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

93 model = Correspondent 

94 

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

103 

104class CustomFieldFactory(PydanticFactory[CustomField]): 

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

106 model = CustomField 

107 

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) 

112 

113class DocumentNoteFactory(PydanticFactory[DocumentNote]): 

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

115 model = DocumentNote 

116 

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) 

124 

125class DocumentFactory(PydanticFactory[Document]): 

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

127 model = Document 

128 

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

149 

150class DocumentTypeFactory(PydanticFactory[DocumentType]): 

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

152 model = DocumentType 

153 

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

162 

163class TagFactory(PydanticFactory[Tag]): 

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

165 model = Tag 

166 

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

177 

178class ProfileFactory(PydanticFactory[Profile]): 

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

180 model = Profile 

181 

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

189 

190class UserFactory(PydanticFactory[User]): 

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

192 model = User 

193 

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

206 

207class StoragePathFactory(PydanticFactory[StoragePath]): 

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

209 model = StoragePath 

210 

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

220 

221class SavedViewFactory(PydanticFactory[SavedView]): 

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

223 model = SavedView 

224 

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

236 

237class ShareLinksFactory(PydanticFactory[ShareLinks]): 

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

239 model = ShareLinks 

240 

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

246 

247class TaskFactory(PydanticFactory[Task]): 

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

249 model = Task 

250 

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) 

259 

260class UISettingsFactory(PydanticFactory[UISettings]): 

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

262 model = UISettings 

263 

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

267 

268class GroupFactory(PydanticFactory[Group]): 

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

270 model = Group 

271 

272 name = factory.Faker("word") 

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

274 

275class WorkflowTriggerFactory(PydanticFactory[WorkflowTrigger]): 

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

277 model = WorkflowTrigger 

278 

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) 

290 

291class WorkflowActionFactory(PydanticFactory[WorkflowAction]): 

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

293 model = WorkflowAction 

294 

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

306 

307class WorkflowFactory(PydanticFactory[Workflow]): 

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

309 model = Workflow 

310 

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