Coverage for src/paperap/tests/factories/models.py: 99%

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

24 from __future__ import annotations

25

26 from abc import ABC

27 from datetime import datetime, timezone

28 from typing import TYPE_CHECKING, Any, Generic, override

29

30 import factory

31 from factory.base import StubObject

32 from faker import Faker

33 from typing_extensions import TypeVar

34

35 from paperap.models import (StandardModel, Correspondent, CustomField, Document,

36 DocumentNote, DocumentType, Group, Profile, SavedView,

37 ShareLinks, StoragePath, Tag, Task, UISettings,

38 User, Workflow, WorkflowAction, WorkflowTrigger)

39 if TYPE_CHECKING:

40 from paperap.resources import BaseResource

41

42 fake = Faker()

43

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

45

46 class 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

90 class 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

103 class 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

112 class 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

124 class 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

149 class 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

162 class 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

177 class 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

189 class 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

206 class 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

220 class 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

236 class 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

246 class 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

259 class 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

267 class 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

274 class 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

290 class 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

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