cornflow 1.1.2__py3-none-any.whl → 1.1.5a1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cornflow/app.py +8 -0
- cornflow/config.py +43 -5
- cornflow/endpoints/login.py +86 -35
- cornflow/schemas/user.py +18 -2
- cornflow/shared/authentication/auth.py +10 -4
- cornflow/shared/exceptions.py +9 -8
- cornflow/tests/custom_test_case.py +342 -0
- cornflow/tests/unit/test_actions.py +46 -1
- cornflow/tests/unit/test_alarms.py +57 -9
- cornflow/tests/unit/test_apiview.py +45 -1
- cornflow/tests/unit/test_application.py +60 -0
- cornflow/tests/unit/test_cases.py +483 -5
- cornflow/tests/unit/test_cli.py +233 -0
- cornflow/tests/unit/test_commands.py +230 -2
- cornflow/tests/unit/test_dags.py +139 -11
- cornflow/tests/unit/test_data_checks.py +134 -2
- cornflow/tests/unit/test_log_in.py +481 -3
- {cornflow-1.1.2.dist-info → cornflow-1.1.5a1.dist-info}/METADATA +23 -19
- {cornflow-1.1.2.dist-info → cornflow-1.1.5a1.dist-info}/RECORD +22 -21
- {cornflow-1.1.2.dist-info → cornflow-1.1.5a1.dist-info}/WHEEL +1 -1
- {cornflow-1.1.2.dist-info → cornflow-1.1.5a1.dist-info}/entry_points.txt +0 -1
- {cornflow-1.1.2.dist-info → cornflow-1.1.5a1.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,17 @@
|
|
1
1
|
"""
|
2
2
|
This file contains the different custom test classes used to generalize the unit testing of cornflow.
|
3
|
+
It provides base test cases and utilities for testing authentication, API endpoints, and database operations.
|
4
|
+
|
5
|
+
Classes
|
6
|
+
-------
|
7
|
+
CustomTestCase
|
8
|
+
Base test case class with common testing utilities
|
9
|
+
BaseTestCases
|
10
|
+
Container for common test case scenarios
|
11
|
+
CheckTokenTestCase
|
12
|
+
Test cases for token validation
|
13
|
+
LoginTestCases
|
14
|
+
Test cases for login functionality
|
3
15
|
"""
|
4
16
|
|
5
17
|
# Import from libraries
|
@@ -39,12 +51,34 @@ except:
|
|
39
51
|
|
40
52
|
|
41
53
|
class CustomTestCase(TestCase):
|
54
|
+
"""
|
55
|
+
Base test case class that provides common utilities for testing Cornflow applications.
|
56
|
+
|
57
|
+
This class sets up a test environment with a test database, user authentication,
|
58
|
+
and common test methods for CRUD operations.
|
59
|
+
"""
|
60
|
+
|
42
61
|
def create_app(self):
|
62
|
+
"""
|
63
|
+
Creates and configures a Flask application for testing.
|
64
|
+
|
65
|
+
:returns: A configured Flask application instance
|
66
|
+
:rtype: Flask
|
67
|
+
"""
|
43
68
|
app = create_app("testing")
|
44
69
|
return app
|
45
70
|
|
46
71
|
@staticmethod
|
47
72
|
def load_file(_file, fk=None, fk_id=None):
|
73
|
+
"""
|
74
|
+
Loads and optionally modifies a JSON file.
|
75
|
+
|
76
|
+
:param str _file: Path to the JSON file to load
|
77
|
+
:param str fk: Foreign key field name to modify (optional)
|
78
|
+
:param int fk_id: Foreign key ID value to set (optional)
|
79
|
+
:returns: The loaded and potentially modified JSON data
|
80
|
+
:rtype: dict
|
81
|
+
"""
|
48
82
|
with open(_file) as f:
|
49
83
|
temp = json.load(f)
|
50
84
|
if fk is not None and fk_id is not None:
|
@@ -52,6 +86,11 @@ class CustomTestCase(TestCase):
|
|
52
86
|
return temp
|
53
87
|
|
54
88
|
def setUp(self):
|
89
|
+
"""
|
90
|
+
Sets up the test environment before each test.
|
91
|
+
|
92
|
+
Creates database tables, initializes access controls, and creates a test user.
|
93
|
+
"""
|
55
94
|
log.root.setLevel(current_app.config["LOG_LEVEL"])
|
56
95
|
db.create_all()
|
57
96
|
access_init_command(verbose=False)
|
@@ -91,9 +130,23 @@ class CustomTestCase(TestCase):
|
|
91
130
|
|
92
131
|
@staticmethod
|
93
132
|
def get_header_with_auth(token):
|
133
|
+
"""
|
134
|
+
Creates HTTP headers with authentication token.
|
135
|
+
|
136
|
+
:param str token: JWT authentication token
|
137
|
+
:returns: Headers dictionary with content type and authorization
|
138
|
+
:rtype: dict
|
139
|
+
"""
|
94
140
|
return {"Content-Type": "application/json", "Authorization": "Bearer " + token}
|
95
141
|
|
96
142
|
def create_user(self, data):
|
143
|
+
"""
|
144
|
+
Creates a new user through the API.
|
145
|
+
|
146
|
+
:param dict data: Dictionary containing user data (username, email, password)
|
147
|
+
:returns: API response from user creation
|
148
|
+
:rtype: Response
|
149
|
+
"""
|
97
150
|
return self.client.post(
|
98
151
|
SIGNUP_URL,
|
99
152
|
data=json.dumps(data),
|
@@ -103,6 +156,14 @@ class CustomTestCase(TestCase):
|
|
103
156
|
|
104
157
|
@staticmethod
|
105
158
|
def assign_role(user_id, role_id):
|
159
|
+
"""
|
160
|
+
Assigns a role to a user in the database.
|
161
|
+
|
162
|
+
:param int user_id: ID of the user
|
163
|
+
:param int role_id: ID of the role to assign
|
164
|
+
:returns: The created or existing user role association
|
165
|
+
:rtype: UserRoleModel
|
166
|
+
"""
|
106
167
|
if UserRoleModel.check_if_role_assigned(user_id, role_id):
|
107
168
|
user_role = UserRoleModel.query.filter_by(
|
108
169
|
user_id=user_id, role_id=role_id
|
@@ -113,6 +174,15 @@ class CustomTestCase(TestCase):
|
|
113
174
|
return user_role
|
114
175
|
|
115
176
|
def create_role_endpoint(self, user_id, role_id, token):
|
177
|
+
"""
|
178
|
+
Creates a role assignment through the API endpoint.
|
179
|
+
|
180
|
+
:param int user_id: ID of the user
|
181
|
+
:param int role_id: ID of the role to assign
|
182
|
+
:param str token: Authentication token
|
183
|
+
:returns: API response from role assignment
|
184
|
+
:rtype: Response
|
185
|
+
"""
|
116
186
|
return self.client.post(
|
117
187
|
USER_ROLE_URL,
|
118
188
|
data=json.dumps({"user_id": user_id, "role_id": role_id}),
|
@@ -121,6 +191,13 @@ class CustomTestCase(TestCase):
|
|
121
191
|
)
|
122
192
|
|
123
193
|
def create_user_with_role(self, role_id):
|
194
|
+
"""
|
195
|
+
Creates a new user and assigns them a specific role.
|
196
|
+
|
197
|
+
:param int role_id: ID of the role to assign
|
198
|
+
:returns: Authentication token for the created user
|
199
|
+
:rtype: str
|
200
|
+
"""
|
124
201
|
data = {
|
125
202
|
"username": "testuser" + str(role_id),
|
126
203
|
"email": "testemail" + str(role_id) + "@test.org",
|
@@ -138,21 +215,54 @@ class CustomTestCase(TestCase):
|
|
138
215
|
).json["token"]
|
139
216
|
|
140
217
|
def create_service_user(self):
|
218
|
+
"""
|
219
|
+
Creates a new user with service role.
|
220
|
+
|
221
|
+
:returns: Authentication token for the service user
|
222
|
+
:rtype: str
|
223
|
+
"""
|
141
224
|
return self.create_user_with_role(SERVICE_ROLE)
|
142
225
|
|
143
226
|
def create_admin(self):
|
227
|
+
"""
|
228
|
+
Creates a new user with admin role.
|
229
|
+
|
230
|
+
:returns: Authentication token for the admin user
|
231
|
+
:rtype: str
|
232
|
+
"""
|
144
233
|
return self.create_user_with_role(ADMIN_ROLE)
|
145
234
|
|
146
235
|
def create_planner(self):
|
236
|
+
"""
|
237
|
+
Creates a new user with planner role.
|
238
|
+
|
239
|
+
:returns: Authentication token for the planner user
|
240
|
+
:rtype: str
|
241
|
+
"""
|
147
242
|
return self.create_user_with_role(PLANNER_ROLE)
|
148
243
|
|
149
244
|
def tearDown(self):
|
245
|
+
"""
|
246
|
+
Cleans up the test environment after each test.
|
247
|
+
"""
|
150
248
|
db.session.remove()
|
151
249
|
db.drop_all()
|
152
250
|
|
153
251
|
def create_new_row(
|
154
252
|
self, url, model, payload, expected_status=201, check_payload=True, token=None
|
155
253
|
):
|
254
|
+
"""
|
255
|
+
Creates a new database row through the API.
|
256
|
+
|
257
|
+
:param str url: API endpoint URL
|
258
|
+
:param class model: Database model class
|
259
|
+
:param dict payload: Data to create the row
|
260
|
+
:param int expected_status: Expected HTTP status code (default: 201)
|
261
|
+
:param bool check_payload: Whether to verify the created data (default: True)
|
262
|
+
:param str token: Authentication token (optional)
|
263
|
+
:returns: ID of the created row
|
264
|
+
:rtype: int
|
265
|
+
"""
|
156
266
|
token = token or self.token
|
157
267
|
|
158
268
|
response = self.client.post(
|
@@ -177,6 +287,17 @@ class CustomTestCase(TestCase):
|
|
177
287
|
def get_rows(
|
178
288
|
self, url, data, token=None, check_data=True, keys_to_check: List[str] = None
|
179
289
|
):
|
290
|
+
"""
|
291
|
+
Retrieves multiple rows through the API and verifies their contents.
|
292
|
+
|
293
|
+
:param str url: API endpoint URL
|
294
|
+
:param list data: List of data dictionaries to create and verify
|
295
|
+
:param str token: Authentication token (optional)
|
296
|
+
:param bool check_data: Whether to verify the retrieved data (default: True)
|
297
|
+
:param list keys_to_check: Specific keys to verify in the response (optional)
|
298
|
+
:returns: API response containing the rows
|
299
|
+
:rtype: Response
|
300
|
+
"""
|
180
301
|
token = token or self.token
|
181
302
|
|
182
303
|
codes = [
|
@@ -200,6 +321,13 @@ class CustomTestCase(TestCase):
|
|
200
321
|
return rows
|
201
322
|
|
202
323
|
def get_keys_to_check(self, payload):
|
324
|
+
"""
|
325
|
+
Determines which keys should be checked in API responses.
|
326
|
+
|
327
|
+
:param dict payload: Data dictionary containing keys
|
328
|
+
:returns: List of keys to check
|
329
|
+
:rtype: list
|
330
|
+
"""
|
203
331
|
if len(self.items_to_check):
|
204
332
|
return self.items_to_check
|
205
333
|
return payload.keys()
|
@@ -213,6 +341,18 @@ class CustomTestCase(TestCase):
|
|
213
341
|
token=None,
|
214
342
|
keys_to_check: List[str] = None,
|
215
343
|
):
|
344
|
+
"""
|
345
|
+
Retrieves a single row through the API and verifies its contents.
|
346
|
+
|
347
|
+
:param str url: API endpoint URL
|
348
|
+
:param dict payload: Expected data dictionary
|
349
|
+
:param int expected_status: Expected HTTP status code (default: 200)
|
350
|
+
:param bool check_payload: Whether to verify the retrieved data (default: True)
|
351
|
+
:param str token: Authentication token (optional)
|
352
|
+
:param list keys_to_check: Specific keys to verify in the response (optional)
|
353
|
+
:returns: API response data
|
354
|
+
:rtype: dict
|
355
|
+
"""
|
216
356
|
token = token or self.token
|
217
357
|
|
218
358
|
row = self.client.get(
|
@@ -232,6 +372,14 @@ class CustomTestCase(TestCase):
|
|
232
372
|
return row.json
|
233
373
|
|
234
374
|
def get_no_rows(self, url, token=None):
|
375
|
+
"""
|
376
|
+
Verifies that no rows are returned from the API endpoint.
|
377
|
+
|
378
|
+
:param str url: API endpoint URL
|
379
|
+
:param str token: Authentication token (optional)
|
380
|
+
:returns: Empty list from API response
|
381
|
+
:rtype: list
|
382
|
+
"""
|
235
383
|
token = token or self.token
|
236
384
|
rows = self.client.get(
|
237
385
|
url, follow_redirects=True, headers=self.get_header_with_auth(token)
|
@@ -249,6 +397,18 @@ class CustomTestCase(TestCase):
|
|
249
397
|
check_payload=True,
|
250
398
|
token=None,
|
251
399
|
):
|
400
|
+
"""
|
401
|
+
Updates a row through the API and verifies the changes.
|
402
|
+
|
403
|
+
:param str url: API endpoint URL
|
404
|
+
:param dict change: Dictionary of changes to apply
|
405
|
+
:param dict payload_to_check: Expected data after update
|
406
|
+
:param int expected_status: Expected HTTP status code (default: 200)
|
407
|
+
:param bool check_payload: Whether to verify the updated data (default: True)
|
408
|
+
:param str token: Authentication token (optional)
|
409
|
+
:returns: Updated row data
|
410
|
+
:rtype: dict
|
411
|
+
"""
|
252
412
|
token = token or self.token
|
253
413
|
|
254
414
|
response = self.client.put(
|
@@ -280,6 +440,15 @@ class CustomTestCase(TestCase):
|
|
280
440
|
def patch_row(
|
281
441
|
self, url, json_patch, payload_to_check, expected_status=200, check_payload=True
|
282
442
|
):
|
443
|
+
"""
|
444
|
+
Patches a row through the API and verifies the changes.
|
445
|
+
|
446
|
+
:param str url: API endpoint URL
|
447
|
+
:param dict json_patch: JSON patch operations to apply
|
448
|
+
:param dict payload_to_check: Expected data after patch
|
449
|
+
:param int expected_status: Expected HTTP status code (default: 200)
|
450
|
+
:param bool check_payload: Whether to verify the patched data (default: True)
|
451
|
+
"""
|
283
452
|
response = self.client.patch(
|
284
453
|
url,
|
285
454
|
data=json.dumps(json_patch),
|
@@ -301,6 +470,13 @@ class CustomTestCase(TestCase):
|
|
301
470
|
self.assertEqual(payload_to_check["solution"], row.json["solution"])
|
302
471
|
|
303
472
|
def delete_row(self, url):
|
473
|
+
"""
|
474
|
+
Deletes a row through the API and verifies its removal.
|
475
|
+
|
476
|
+
:param str url: API endpoint URL
|
477
|
+
:returns: API response from the delete operation
|
478
|
+
:rtype: Response
|
479
|
+
"""
|
304
480
|
response = self.client.delete(
|
305
481
|
url, follow_redirects=True, headers=self.get_header_with_auth(self.token)
|
306
482
|
)
|
@@ -314,6 +490,13 @@ class CustomTestCase(TestCase):
|
|
314
490
|
return response
|
315
491
|
|
316
492
|
def apply_filter(self, url, _filter, result):
|
493
|
+
"""
|
494
|
+
Tests API filtering functionality.
|
495
|
+
|
496
|
+
:param str url: API endpoint URL
|
497
|
+
:param dict _filter: Filter parameters to apply
|
498
|
+
:param list result: Expected filtered results
|
499
|
+
"""
|
317
500
|
# we take out the potential query (e.g., ?param=1) arguments inside the url
|
318
501
|
get_with_opts = lambda data: self.client.get(
|
319
502
|
url.split("?")[0],
|
@@ -328,16 +511,39 @@ class CustomTestCase(TestCase):
|
|
328
511
|
return
|
329
512
|
|
330
513
|
def repr_method(self, idx, representation):
|
514
|
+
"""
|
515
|
+
Tests the string representation of a model instance.
|
516
|
+
|
517
|
+
:param int idx: ID of the model instance
|
518
|
+
:param str representation: Expected string representation
|
519
|
+
"""
|
331
520
|
row = self.model.query.get(idx)
|
332
521
|
self.assertEqual(repr(row), representation)
|
333
522
|
|
334
523
|
def str_method(self, idx, string: str):
|
524
|
+
"""
|
525
|
+
Tests the string conversion of a model instance.
|
526
|
+
|
527
|
+
:param int idx: ID of the model instance
|
528
|
+
:param str string: Expected string value
|
529
|
+
"""
|
335
530
|
row = self.model.query.get(idx)
|
336
531
|
self.assertEqual(str(row), string)
|
337
532
|
|
338
533
|
def cascade_delete(
|
339
534
|
self, url, model, payload, url_2, model_2, payload_2, parent_key
|
340
535
|
):
|
536
|
+
"""
|
537
|
+
Tests cascade deletion functionality between related models.
|
538
|
+
|
539
|
+
:param str url: Parent model API endpoint
|
540
|
+
:param class model: Parent model class
|
541
|
+
:param dict payload: Parent model data
|
542
|
+
:param str url_2: Child model API endpoint
|
543
|
+
:param class model_2: Child model class
|
544
|
+
:param dict payload_2: Child model data
|
545
|
+
:param str parent_key: Foreign key field linking child to parent
|
546
|
+
"""
|
341
547
|
parent_object_idx = self.create_new_row(url, model, payload)
|
342
548
|
payload_2[parent_key] = parent_object_idx
|
343
549
|
child_object_idx = self.create_new_row(url_2, model_2, payload_2)
|
@@ -356,24 +562,44 @@ class CustomTestCase(TestCase):
|
|
356
562
|
|
357
563
|
|
358
564
|
class BaseTestCases:
|
565
|
+
"""
|
566
|
+
Container class for common test case scenarios.
|
567
|
+
"""
|
568
|
+
|
359
569
|
class ListFilters(CustomTestCase):
|
570
|
+
"""
|
571
|
+
Test cases for list endpoint filtering functionality.
|
572
|
+
"""
|
573
|
+
|
360
574
|
def setUp(self):
|
575
|
+
"""
|
576
|
+
Sets up the test environment for filter tests.
|
577
|
+
"""
|
361
578
|
super().setUp()
|
362
579
|
self.payload = None
|
363
580
|
|
364
581
|
def test_opt_filters_limit(self):
|
582
|
+
"""
|
583
|
+
Tests the limit filter option.
|
584
|
+
"""
|
365
585
|
# we create 4 instances
|
366
586
|
data_many = [self.payload for _ in range(4)]
|
367
587
|
allrows = self.get_rows(self.url, data_many)
|
368
588
|
self.apply_filter(self.url, dict(limit=1), [allrows.json[0]])
|
369
589
|
|
370
590
|
def test_opt_filters_offset(self):
|
591
|
+
"""
|
592
|
+
Tests the offset filter option.
|
593
|
+
"""
|
371
594
|
# we create 4 instances
|
372
595
|
data_many = [self.payload for _ in range(4)]
|
373
596
|
allrows = self.get_rows(self.url, data_many)
|
374
597
|
self.apply_filter(self.url, dict(offset=1, limit=2), allrows.json[1:3])
|
375
598
|
|
376
599
|
def test_opt_filters_schema(self):
|
600
|
+
"""
|
601
|
+
Tests the schema filter option.
|
602
|
+
"""
|
377
603
|
# (we patch the request to airflow to check if the schema is valid)
|
378
604
|
# we create 4 instances
|
379
605
|
data_many = [self.payload for _ in range(4)]
|
@@ -382,6 +608,9 @@ class BaseTestCases:
|
|
382
608
|
self.apply_filter(self.url, dict(schema="timer"), allrows.json[:1])
|
383
609
|
|
384
610
|
def test_opt_filters_date_lte(self):
|
611
|
+
"""
|
612
|
+
Tests the less than or equal to date filter.
|
613
|
+
"""
|
385
614
|
# we create 4 instances
|
386
615
|
data_many = [self.payload for _ in range(4)]
|
387
616
|
allrows = self.get_rows(self.url, data_many)
|
@@ -397,6 +626,9 @@ class BaseTestCases:
|
|
397
626
|
)
|
398
627
|
|
399
628
|
def test_opt_filters_date_gte(self):
|
629
|
+
"""
|
630
|
+
Tests the greater than or equal to date filter.
|
631
|
+
"""
|
400
632
|
# we create 4 instances
|
401
633
|
data_many = [self.payload for _ in range(4)]
|
402
634
|
allrows = self.get_rows(self.url, data_many)
|
@@ -413,13 +645,26 @@ class BaseTestCases:
|
|
413
645
|
return
|
414
646
|
|
415
647
|
class DetailEndpoint(CustomTestCase):
|
648
|
+
"""
|
649
|
+
Test cases for detail endpoint functionality.
|
650
|
+
"""
|
651
|
+
|
416
652
|
def setUp(self):
|
653
|
+
"""
|
654
|
+
Sets up the test environment for detail endpoint tests.
|
655
|
+
"""
|
417
656
|
super().setUp()
|
418
657
|
self.payload = None
|
419
658
|
self.response_items = None
|
420
659
|
self.query_arguments = None
|
421
660
|
|
422
661
|
def url_with_query_arguments(self):
|
662
|
+
"""
|
663
|
+
Constructs URL with query arguments.
|
664
|
+
|
665
|
+
:returns: URL with query parameters
|
666
|
+
:rtype: str
|
667
|
+
"""
|
423
668
|
if self.query_arguments is None:
|
424
669
|
return self.url
|
425
670
|
else:
|
@@ -430,6 +675,9 @@ class BaseTestCases:
|
|
430
675
|
)
|
431
676
|
|
432
677
|
def test_get_one_row(self):
|
678
|
+
"""
|
679
|
+
Tests retrieving a single row.
|
680
|
+
"""
|
433
681
|
idx = self.create_new_row(
|
434
682
|
self.url_with_query_arguments(), self.model, self.payload
|
435
683
|
)
|
@@ -440,6 +688,9 @@ class BaseTestCases:
|
|
440
688
|
self.assertEqual(len(diff), 0)
|
441
689
|
|
442
690
|
def test_get_one_row_superadmin(self):
|
691
|
+
"""
|
692
|
+
Tests retrieving a single row as superadmin.
|
693
|
+
"""
|
443
694
|
idx = self.create_new_row(
|
444
695
|
self.url_with_query_arguments(), self.model, self.payload
|
445
696
|
)
|
@@ -449,11 +700,17 @@ class BaseTestCases:
|
|
449
700
|
)
|
450
701
|
|
451
702
|
def test_get_nonexistent_row(self):
|
703
|
+
"""
|
704
|
+
Tests attempting to retrieve a non-existent row.
|
705
|
+
"""
|
452
706
|
self.get_one_row(
|
453
707
|
self.url + "500" + "/", {}, expected_status=404, check_payload=False
|
454
708
|
)
|
455
709
|
|
456
710
|
def test_update_one_row(self):
|
711
|
+
"""
|
712
|
+
Tests updating a single row.
|
713
|
+
"""
|
457
714
|
idx = self.create_new_row(
|
458
715
|
self.url_with_query_arguments(), self.model, self.payload
|
459
716
|
)
|
@@ -465,6 +722,9 @@ class BaseTestCases:
|
|
465
722
|
)
|
466
723
|
|
467
724
|
def test_update_one_row_bad_format(self):
|
725
|
+
"""
|
726
|
+
Tests updating a row with invalid format.
|
727
|
+
"""
|
468
728
|
idx = self.create_new_row(
|
469
729
|
self.url_with_query_arguments(), self.model, self.payload
|
470
730
|
)
|
@@ -485,6 +745,9 @@ class BaseTestCases:
|
|
485
745
|
)
|
486
746
|
|
487
747
|
def test_delete_one_row(self):
|
748
|
+
"""
|
749
|
+
Tests deleting a single row.
|
750
|
+
"""
|
488
751
|
idx = self.create_new_row(
|
489
752
|
self.url_with_query_arguments(), self.model, self.payload
|
490
753
|
)
|
@@ -492,6 +755,9 @@ class BaseTestCases:
|
|
492
755
|
|
493
756
|
# TODO: move to base endpoint custom class
|
494
757
|
def test_incomplete_payload(self):
|
758
|
+
"""
|
759
|
+
Tests creating a row with incomplete payload.
|
760
|
+
"""
|
495
761
|
payload = {"description": "arg"}
|
496
762
|
self.create_new_row(
|
497
763
|
self.url_with_query_arguments(),
|
@@ -503,6 +769,9 @@ class BaseTestCases:
|
|
503
769
|
|
504
770
|
# TODO: move to base endpoint custom class
|
505
771
|
def test_payload_bad_format(self):
|
772
|
+
"""
|
773
|
+
Tests creating a row with invalid payload format.
|
774
|
+
"""
|
506
775
|
payload = {"name": 1}
|
507
776
|
self.create_new_row(
|
508
777
|
self.url_with_query_arguments(),
|
@@ -514,22 +783,45 @@ class BaseTestCases:
|
|
514
783
|
|
515
784
|
|
516
785
|
class CheckTokenTestCase:
|
786
|
+
"""
|
787
|
+
Container class for token validation test cases.
|
788
|
+
"""
|
789
|
+
|
517
790
|
class TokenEndpoint(TestCase):
|
791
|
+
"""
|
792
|
+
Test cases for token endpoint functionality.
|
793
|
+
"""
|
794
|
+
|
518
795
|
def create_app(self):
|
796
|
+
"""
|
797
|
+
Creates test application instance.
|
798
|
+
|
799
|
+
:returns: Test Flask application
|
800
|
+
:rtype: Flask
|
801
|
+
"""
|
519
802
|
app = create_app("testing")
|
520
803
|
return app
|
521
804
|
|
522
805
|
def setUp(self):
|
806
|
+
"""
|
807
|
+
Sets up test environment for token tests.
|
808
|
+
"""
|
523
809
|
db.create_all()
|
524
810
|
self.data = None
|
525
811
|
self.token = None
|
526
812
|
self.response = None
|
527
813
|
|
528
814
|
def tearDown(self):
|
815
|
+
"""
|
816
|
+
Cleans up test environment after token tests.
|
817
|
+
"""
|
529
818
|
db.session.remove()
|
530
819
|
db.drop_all()
|
531
820
|
|
532
821
|
def get_check_token(self):
|
822
|
+
"""
|
823
|
+
Tests token validation endpoint.
|
824
|
+
"""
|
533
825
|
if self.token:
|
534
826
|
self.response = self.client.get(
|
535
827
|
TOKEN_URL,
|
@@ -550,22 +842,45 @@ class CheckTokenTestCase:
|
|
550
842
|
|
551
843
|
|
552
844
|
class LoginTestCases:
|
845
|
+
"""
|
846
|
+
Container class for login-related test cases.
|
847
|
+
"""
|
848
|
+
|
553
849
|
class LoginEndpoint(TestCase):
|
850
|
+
"""
|
851
|
+
Test cases for login endpoint functionality.
|
852
|
+
"""
|
853
|
+
|
554
854
|
def create_app(self):
|
855
|
+
"""
|
856
|
+
Creates test application instance.
|
857
|
+
|
858
|
+
:returns: Test Flask application
|
859
|
+
:rtype: Flask
|
860
|
+
"""
|
555
861
|
app = create_app("testing")
|
556
862
|
return app
|
557
863
|
|
558
864
|
def setUp(self):
|
865
|
+
"""
|
866
|
+
Sets up test environment for login tests.
|
867
|
+
"""
|
559
868
|
log.root.setLevel(current_app.config["LOG_LEVEL"])
|
560
869
|
db.create_all()
|
561
870
|
self.data = None
|
562
871
|
self.response = None
|
563
872
|
|
564
873
|
def tearDown(self):
|
874
|
+
"""
|
875
|
+
Cleans up test environment after login tests.
|
876
|
+
"""
|
565
877
|
db.session.remove()
|
566
878
|
db.drop_all()
|
567
879
|
|
568
880
|
def test_successful_log_in(self):
|
881
|
+
"""
|
882
|
+
Tests successful login attempt.
|
883
|
+
"""
|
569
884
|
payload = self.data
|
570
885
|
|
571
886
|
self.response = self.client.post(
|
@@ -579,6 +894,9 @@ class LoginTestCases:
|
|
579
894
|
self.assertEqual(str, type(self.response.json["token"]))
|
580
895
|
|
581
896
|
def test_validation_error(self):
|
897
|
+
"""
|
898
|
+
Tests login with invalid data.
|
899
|
+
"""
|
582
900
|
payload = self.data
|
583
901
|
payload["email"] = "test"
|
584
902
|
|
@@ -593,6 +911,9 @@ class LoginTestCases:
|
|
593
911
|
self.assertEqual(str, type(response.json["error"]))
|
594
912
|
|
595
913
|
def test_missing_username(self):
|
914
|
+
"""
|
915
|
+
Tests login with missing username.
|
916
|
+
"""
|
596
917
|
payload = self.data
|
597
918
|
payload.pop("username", None)
|
598
919
|
response = self.client.post(
|
@@ -606,6 +927,9 @@ class LoginTestCases:
|
|
606
927
|
self.assertEqual(str, type(response.json["error"]))
|
607
928
|
|
608
929
|
def test_missing_password(self):
|
930
|
+
"""
|
931
|
+
Tests login with missing password.
|
932
|
+
"""
|
609
933
|
payload = self.data
|
610
934
|
payload.pop("password", None)
|
611
935
|
response = self.client.post(
|
@@ -619,6 +943,9 @@ class LoginTestCases:
|
|
619
943
|
self.assertEqual(str, type(response.json["error"]))
|
620
944
|
|
621
945
|
def test_invalid_username(self):
|
946
|
+
"""
|
947
|
+
Tests login with invalid username.
|
948
|
+
"""
|
622
949
|
payload = self.data
|
623
950
|
payload["username"] = "invalid_username"
|
624
951
|
|
@@ -634,6 +961,9 @@ class LoginTestCases:
|
|
634
961
|
self.assertEqual("Invalid credentials", response.json["error"])
|
635
962
|
|
636
963
|
def test_invalid_password(self):
|
964
|
+
"""
|
965
|
+
Tests login with invalid password.
|
966
|
+
"""
|
637
967
|
payload = self.data
|
638
968
|
payload["password"] = "testpassword_2"
|
639
969
|
|
@@ -649,6 +979,9 @@ class LoginTestCases:
|
|
649
979
|
self.assertEqual("Invalid credentials", response.json["error"])
|
650
980
|
|
651
981
|
def test_old_token(self):
|
982
|
+
"""
|
983
|
+
Tests using an expired token.
|
984
|
+
"""
|
652
985
|
token = (
|
653
986
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTA1MzYwNjUsImlhdCI6MTYxMDQ0OTY2NSwic3ViIjoxfQ"
|
654
987
|
".QEfmO-hh55PjtecnJ1RJT3aW2brGLadkg5ClH9yrRnc "
|
@@ -680,6 +1013,9 @@ class LoginTestCases:
|
|
680
1013
|
)
|
681
1014
|
|
682
1015
|
def test_bad_format_token(self):
|
1016
|
+
"""
|
1017
|
+
Tests using a malformed token.
|
1018
|
+
"""
|
683
1019
|
response = self.client.post(
|
684
1020
|
LOGIN_URL,
|
685
1021
|
data=json.dumps(self.data),
|
@@ -701,6 +1037,9 @@ class LoginTestCases:
|
|
701
1037
|
self.assertEqual(400, response.status_code)
|
702
1038
|
|
703
1039
|
def test_invalid_token(self):
|
1040
|
+
"""
|
1041
|
+
Tests using an invalid token.
|
1042
|
+
"""
|
704
1043
|
token = (
|
705
1044
|
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTA1Mzk5NTMsImlhdCI6MTYxMDQ1MzU1Mywic3ViIjoxfQ"
|
706
1045
|
".g3Gh7k7twXZ4K2MnQpgpSr76Sl9VX6TkDWusX5YzImo"
|
@@ -732,6 +1071,9 @@ class LoginTestCases:
|
|
732
1071
|
)
|
733
1072
|
|
734
1073
|
def test_token(self):
|
1074
|
+
"""
|
1075
|
+
Tests token generation and validation.
|
1076
|
+
"""
|
735
1077
|
payload = self.data
|
736
1078
|
|
737
1079
|
self.response = self.client.post(
|