Coverage for src/paperap/tests/unittest.py: 97%

150 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: testcase.py 

10 Project: paperap 

11 Created: 2025-03-04 

12 Version: 0.0.5 

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-04 By Jess Mann 

22 

23""" 

24from __future__ import annotations 

25 

26import json 

27import logging 

28import os 

29import unittest 

30from pathlib import Path 

31from typing import TYPE_CHECKING, Any, Callable, Generic, Iterator, override 

32from unittest.mock import MagicMock, patch 

33 

34from pydantic import ValidationError 

35from typing_extensions import TypeAlias, TypeVar 

36 

37from paperap.client import PaperlessClient 

38from paperap.models import (BaseQuerySet, Correspondent, CorrespondentQuerySet, 

39 CustomField, CustomFieldQuerySet, 

40 Document, DocumentQuerySet, DocumentType, 

41 DocumentTypeQuerySet, Group, GroupQuerySet, 

42 Profile, ProfileQuerySet, SavedView, 

43 SavedViewQuerySet, ShareLinks, ShareLinksQuerySet, 

44 StandardModel, StandardQuerySet, StoragePath, 

45 StoragePathQuerySet, Tag, TagQuerySet, Task, 

46 TaskQuerySet, UISettings, UISettingsQuerySet, User, 

47 UserQuerySet, Workflow, WorkflowAction, 

48 WorkflowActionQuerySet, WorkflowQuerySet, 

49 WorkflowTrigger, WorkflowTriggerQuerySet) 

50from paperap.resources import (BaseResource, CorrespondentResource, 

51 CustomFieldResource, 

52 DocumentResource, DocumentTypeResource, 

53 GroupResource, ProfileResource, 

54 SavedViewResource, ShareLinksResource, 

55 StandardResource, StoragePathResource, 

56 TagResource, TaskResource, UISettingsResource, 

57 UserResource, WorkflowActionResource, 

58 WorkflowResource, WorkflowTriggerResource) 

59from paperap.tests.factories import (CorrespondentFactory, DocumentFactory, 

60 DocumentTypeFactory, GroupFactory, 

61 ProfileFactory, PydanticFactory, 

62 SavedViewFactory, ShareLinksFactory, 

63 StoragePathFactory, TagFactory, 

64 TaskFactory, UISettingsFactory, 

65 UserFactory, WorkflowActionFactory, 

66 WorkflowFactory, WorkflowTriggerFactory) 

67 

68from paperap.tests.testcase import TestMixin 

69 

70logger = logging.getLogger(__name__) 

71 

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

73_StandardResource = TypeVar("_StandardResource", bound="StandardResource", default="StandardResource") 

74_StandardQuerySet = TypeVar("_StandardQuerySet", bound="StandardQuerySet", default="StandardQuerySet") 

75 

76class UnitTestCase( 

77 unittest.TestCase, 

78 TestMixin[_StandardModel, _StandardResource, _StandardQuerySet], 

79 Generic[_StandardModel, _StandardResource, _StandardQuerySet] 

80): 

81 

82 @override 

83 def setUp(self) -> None: 

84 """ 

85 Set up the test case by initializing the client, resource, and model data. 

86 """ 

87 self._reset_attributes() 

88 

89 @override 

90 def setup_client(self, **kwargs) -> None: 

91 """ 

92 Set up the PaperlessClient instance, optionally mocking environment variables. 

93 """ 

94 if not hasattr(self, "client") or not self.client: 

95 if self.mock_env: 

96 with patch.dict(os.environ, self.env_data, clear=True): 

97 self.client = PaperlessClient() 

98 else: 

99 self.client = PaperlessClient() 

100 

101 @override 

102 def validate_field(self, field_name : str, test_cases : list[tuple[Any, Any]]): 

103 """ 

104 Validate that a field is parsed correctly with various types of data. 

105 

106 Args: 

107 field_name: The name of the field to test. 

108 test_cases: A list of tuples with input values and expected results. 

109 

110 Examples: 

111 test_cases = [ 

112 (42, 42), 

113 ("42", 42), 

114 (None, None), 

115 (0, ValidationError), 

116 (Decimal('42.5'), ValidationError), 

117 ] 

118 self.validate_field("age", test_cases) 

119 """ 

120 self._meta.save_on_write = False 

121 for (input_value, expected) in test_cases: 

122 with self.subTest(field=field_name, input_value=input_value): 

123 if type(expected) is type and issubclass(expected, Exception): 

124 with self.assertRaises(expected, msg=f"Setting {self.model.__class__.__name__} field {field_name} failed with input {input_value}"): 

125 setattr(self.model, field_name, input_value) 

126 else: 

127 setattr(self.model, field_name, input_value) 

128 real_value = getattr(self.model, field_name) 

129 self.assertIsInstance(real_value, type(expected), f"Setting {self.model.__class__.__name__} field {field_name} failed with input {input_value}, expected {expected}") 

130 self.assertEqual( 

131 real_value, 

132 expected, 

133 f"Setting {self.model.__class__.__name__} field {field_name} failed with input {input_value}" 

134 ) 

135 

136 def assert_queryset_callback( 

137 self, 

138 *, 

139 queryset : StandardQuerySet[_StandardModel], 

140 callback : Callable[[_StandardModel], bool] | None = None, 

141 expected_count : int | None = None 

142 ) -> None: 

143 """ 

144 Generic method to test queryset filtering. 

145 

146 Args: 

147 queryset: The queryset to test 

148 callback: A callback function to test each model instance. 

149 expected_count: The expected result count of the queryset. 

150 """ 

151 if expected_count is not None: 

152 self.assertEqual(queryset.count(), expected_count) 

153 

154 count = 0 

155 for model in queryset: 

156 count += 1 

157 if self.model_type: 

158 self.assertIsInstance(model, self.model_type) 

159 else: 

160 self.assertIsInstance(model, StandardModel) 

161 

162 if callback: 

163 self.assertTrue(callback(model), f"Condition failed for {model}") 

164 

165 # Check multiple results, but avoid paging 

166 if count > 5: 

167 break 

168 

169 if expected_count is not None: 

170 expected_iterations = min(expected_count, 6) 

171 self.assertEqual(count, expected_iterations, f"Documents iteration unexpected. Count: {expected_count} -> Expected {expected_iterations} iterations, got {count}.") 

172 

173 def assert_queryset_callback_patched( 

174 self, 

175 *, 

176 queryset : StandardQuerySet[_StandardModel] | Callable[..., StandardQuerySet[_StandardModel]], 

177 sample_data : dict[str, Any], 

178 callback : Callable[[_StandardModel], bool] | None = None, 

179 expected_count : int | None = None, 

180 ) -> None: 

181 """ 

182 Generic method to test queryset filtering. 

183 

184 Args: 

185 queryset: The queryset to test, or a method which retrieves a queryset. 

186 sample_data: The sample data to use for the queryset. 

187 callback: A callback function to test each model instance. 

188 expected_count: The expected result count of the queryset. 

189 """ 

190 # Setup defaults 

191 if expected_count is None: 

192 expected_count = int(sample_data['count']) 

193 

194 with patch('paperap.client.PaperlessClient.request') as mock_request: 

195 mock_request.return_value = sample_data 

196 if not isinstance(queryset, Callable): 

197 qs = queryset 

198 else: 

199 qs = queryset() 

200 if self.queryset_type: 

201 self.assertIsInstance(qs, self.queryset_type) 

202 else: 

203 self.assertIsInstance(qs, BaseQuerySet) 

204 

205 self.assertEqual(qs.count(), expected_count) 

206 

207 self.assert_queryset_callback( 

208 queryset = qs, 

209 expected_count = expected_count, 

210 callback = callback 

211 ) 

212 

213class CustomFieldUnitTest(UnitTestCase["CustomField", "CustomFieldResource", "CustomFieldQuerySet"]): 

214 """ 

215 A test case for the CustomField model and resource. 

216 """ 

217 resource_class = CustomFieldResource 

218 model_type = CustomField 

219 queryset_type = CustomFieldQuerySet 

220 #factory = PydanticFactory 

221 

222class DocumentUnitTest(UnitTestCase["Document", "DocumentResource", "DocumentQuerySet"]): 

223 """ 

224 A test case for the Document model and resource. 

225 """ 

226 resource_class = DocumentResource 

227 model_type = Document 

228 queryset_type = DocumentQuerySet 

229 factory = DocumentFactory 

230 

231class DocumentTypeUnitTest(UnitTestCase["DocumentType", "DocumentTypeResource", "DocumentTypeQuerySet"]): 

232 """ 

233 A test case for the DocumentType model and resource. 

234 """ 

235 resource_class = DocumentTypeResource 

236 model_type = DocumentType 

237 queryset_type = DocumentTypeQuerySet 

238 factory = DocumentTypeFactory 

239 

240class CorrespondentUnitTest(UnitTestCase["Correspondent", "CorrespondentResource", "CorrespondentQuerySet"]): 

241 """ 

242 A test case for the Correspondent model and resource. 

243 """ 

244 resource_class = CorrespondentResource 

245 model_type = Correspondent 

246 queryset_type = CorrespondentQuerySet 

247 factory = CorrespondentFactory 

248 

249class TagUnitTest(UnitTestCase["Tag", "TagResource", "TagQuerySet"]): 

250 """ 

251 A test case for the Tag model and resource. 

252 """ 

253 resource_class = TagResource 

254 model_type = Tag 

255 queryset_type = TagQuerySet 

256 factory = TagFactory 

257 

258class UserUnitTest(UnitTestCase["User", "UserResource", "UserQuerySet"]): 

259 """ 

260 A test case for the User model and resource. 

261 """ 

262 resource_class = UserResource 

263 model_type = User 

264 queryset_type = UserQuerySet 

265 factory = UserFactory 

266 

267class GroupUnitTest(UnitTestCase["Group", "GroupResource", "GroupQuerySet"]): 

268 """ 

269 A test case for the Group model and resource. 

270 """ 

271 resource_class = GroupResource 

272 model_type = Group 

273 queryset_type = GroupQuerySet 

274 factory = GroupFactory 

275 

276class ProfileUnitTest(UnitTestCase["Profile", "ProfileResource", "ProfileQuerySet"]): 

277 """ 

278 A test case for the Profile model and resource. 

279 """ 

280 resource_class = ProfileResource 

281 model_type = Profile 

282 queryset_type = ProfileQuerySet 

283 factory = ProfileFactory 

284 

285class TaskUnitTest(UnitTestCase["Task", "TaskResource", "TaskQuerySet"]): 

286 """ 

287 A test case for the Task model and resource. 

288 """ 

289 resource_class = TaskResource 

290 model_type = Task 

291 queryset_type = TaskQuerySet 

292 factory = TaskFactory 

293 

294class WorkflowUnitTest(UnitTestCase["Workflow", "WorkflowResource", "WorkflowQuerySet"]): 

295 """ 

296 A test case for the Workflow model and resource. 

297 """ 

298 resource_class = WorkflowResource 

299 model_type = Workflow 

300 queryset_type = WorkflowQuerySet 

301 factory = WorkflowFactory 

302 

303class SavedViewUnitTest(UnitTestCase["SavedView", "SavedViewResource", "SavedViewQuerySet"]): 

304 """ 

305 A test case for the SavedView model and resource. 

306 """ 

307 resource_class = SavedViewResource 

308 model_type = SavedView 

309 queryset_type = SavedViewQuerySet 

310 factory = SavedViewFactory 

311 

312class ShareLinksUnitTest(UnitTestCase["ShareLinks", "ShareLinksResource", "ShareLinksQuerySet"]): 

313 """ 

314 A test case for ShareLinks 

315 """ 

316 resource_class = ShareLinksResource 

317 model_type = ShareLinks 

318 queryset_type = ShareLinksQuerySet 

319 factory = ShareLinksFactory 

320 

321class UISettingsUnitTest(UnitTestCase["UISettings", "UISettingsResource", "UISettingsQuerySet"]): 

322 """ 

323 A test case for the UISettings model and resource. 

324 """ 

325 resource_class = UISettingsResource 

326 model_type = UISettings 

327 queryset_type = UISettingsQuerySet 

328 factory = UISettingsFactory 

329 

330class StoragePathUnitTest(UnitTestCase["StoragePath", "StoragePathResource", "StoragePathQuerySet"]): 

331 """ 

332 A test case for the StoragePath model and resource. 

333 """ 

334 resource_class = StoragePathResource 

335 model_type = StoragePath 

336 queryset_type = StoragePathQuerySet 

337 factory = StoragePathFactory 

338 

339class WorkflowActionUnitTest(UnitTestCase["WorkflowAction", "WorkflowActionResource", "WorkflowActionQuerySet"]): 

340 """ 

341 A test case for the WorkflowAction model and resource. 

342 """ 

343 resource_class = WorkflowActionResource 

344 model_type = WorkflowAction 

345 queryset_type = WorkflowActionQuerySet 

346 factory = WorkflowActionFactory 

347 

348class WorkflowTriggerUnitTest(UnitTestCase["WorkflowTrigger", "WorkflowTriggerResource", "WorkflowTriggerQuerySet"]): 

349 """ 

350 A test case for the WorkflowTrigger model and resource. 

351 """ 

352 resource_class = WorkflowTriggerResource 

353 model_type = WorkflowTrigger 

354 queryset_type = WorkflowTriggerQuerySet 

355 factory = WorkflowTriggerFactory