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