cornflow 1.2.3a4__py3-none-any.whl → 1.2.4__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.
@@ -13,6 +13,7 @@ from cornflow.shared.const import (
13
13
  PATCH_ACTION,
14
14
  DELETE_ACTION,
15
15
  GET_ACTION,
16
+ PUT_ACTION,
16
17
  )
17
18
 
18
19
 
@@ -106,6 +107,13 @@ class ExternalRoleCreationTestCase(CustomTestCase):
106
107
  (VIEWER_ROLE, POST_ACTION, "scheduling_optimizer"),
107
108
  (PLANNER_ROLE, DELETE_ACTION, "quality_control"),
108
109
  ]
110
+ # Define permissions for custom roles used in endpoints
111
+ mock_const.CUSTOM_ROLES_ACTIONS = {
112
+ # Default permission for role 888
113
+ 888: [GET_ACTION],
114
+ # Default permission for role 777
115
+ 777: [GET_ACTION],
116
+ }
109
117
  mock_shared.const = mock_const
110
118
  mock_external_app.shared = mock_shared
111
119
 
@@ -206,6 +214,17 @@ class ExternalRoleCreationTestCase(CustomTestCase):
206
214
  (10000, POST_ACTION, "production_planning"),
207
215
  (999, PATCH_ACTION, "quality_control"),
208
216
  ]
217
+ # Define permissions for custom roles used in endpoints (888, 777) and test roles (10000, 999)
218
+ mock_const.CUSTOM_ROLES_ACTIONS = {
219
+ # Used in endpoints
220
+ 888: [GET_ACTION],
221
+ # Used in endpoints
222
+ 777: [GET_ACTION],
223
+ # Used in test
224
+ 10000: [GET_ACTION],
225
+ # Used in test
226
+ 999: [GET_ACTION],
227
+ }
209
228
  mock_shared.const = mock_const
210
229
  mock_external_app.shared = mock_shared
211
230
 
@@ -224,6 +243,7 @@ class ExternalRoleCreationTestCase(CustomTestCase):
224
243
  mock_const.EXTRA_PERMISSION_ASSIGNATION = [
225
244
  (10000, POST_ACTION, "production_planning"),
226
245
  ]
246
+ # Keep the same CUSTOM_ROLES_ACTIONS (role definitions don't change)
227
247
 
228
248
  # Re-run permissions registration
229
249
  access_init_command(verbose=True)
@@ -266,6 +286,13 @@ class ExternalRoleCreationTestCase(CustomTestCase):
266
286
  mock_const = MagicMock()
267
287
  # Don't set EXTRA_PERMISSION_ASSIGNATION to trigger AttributeError
268
288
  del mock_const.EXTRA_PERMISSION_ASSIGNATION
289
+ # Define permissions for custom roles used in endpoints
290
+ mock_const.CUSTOM_ROLES_ACTIONS = {
291
+ # Used in endpoints
292
+ 888: [GET_ACTION],
293
+ # Used in endpoints
294
+ 777: [GET_ACTION],
295
+ }
269
296
  mock_shared.const = mock_const
270
297
  mock_external_app.shared = mock_shared
271
298
 
@@ -304,6 +331,13 @@ class ExternalRoleCreationTestCase(CustomTestCase):
304
331
  (VIEWER_ROLE, POST_ACTION, "production_planning"), # Extend standard role
305
332
  (PLANNER_ROLE, DELETE_ACTION, "quality_control"), # Extend standard role
306
333
  ]
334
+ # Define permissions for custom roles used in endpoints
335
+ mock_const.CUSTOM_ROLES_ACTIONS = {
336
+ # Used in endpoints
337
+ 888: [GET_ACTION],
338
+ # Used in endpoints
339
+ 777: [GET_ACTION],
340
+ }
307
341
  mock_shared.const = mock_const
308
342
  mock_external_app.shared = mock_shared
309
343
 
@@ -385,6 +419,13 @@ class ExternalRoleCreationTestCase(CustomTestCase):
385
419
  mock_shared_initial = MagicMock()
386
420
  mock_const_initial = MagicMock()
387
421
  mock_const_initial.EXTRA_PERMISSION_ASSIGNATION = []
422
+ # Define permissions for custom roles used in test endpoints
423
+ mock_const_initial.CUSTOM_ROLES_ACTIONS = {
424
+ # Used in test endpoints
425
+ 888: [GET_ACTION],
426
+ # Used in test endpoints
427
+ 777: [GET_ACTION],
428
+ }
388
429
  mock_shared_initial.const = mock_const_initial
389
430
  mock_external_app_initial.shared = mock_shared_initial
390
431
 
@@ -459,6 +500,13 @@ class ExternalRoleCreationTestCase(CustomTestCase):
459
500
  mock_shared_updated = MagicMock()
460
501
  mock_const_updated = MagicMock()
461
502
  mock_const_updated.EXTRA_PERMISSION_ASSIGNATION = [] # Empty list
503
+ # Define permissions for custom roles used in test endpoints
504
+ mock_const_updated.CUSTOM_ROLES_ACTIONS = {
505
+ # Used in test endpoints
506
+ 888: [GET_ACTION],
507
+ # Used in test endpoints
508
+ 777: [GET_ACTION],
509
+ }
462
510
  mock_shared_updated.const = mock_const_updated
463
511
  mock_external_app_updated.shared = mock_shared_updated
464
512
 
@@ -534,6 +582,204 @@ class ExternalRoleCreationTestCase(CustomTestCase):
534
582
  f"Permissions for remaining views should still exist. Found views: {set(all_view_names)}",
535
583
  )
536
584
 
585
+ @patch("cornflow.commands.auxiliar.import_module")
586
+ @patch("cornflow.commands.views.import_module")
587
+ @patch.dict(
588
+ os.environ, {"EXTERNAL_APP": "1", "EXTERNAL_APP_MODULE": "external_test_app"}
589
+ )
590
+ def test_custom_roles_actions_success(
591
+ self, mock_import_views, mock_import_auxiliar
592
+ ):
593
+ """
594
+ Test that custom roles get their defined actions from CUSTOM_ROLES_ACTIONS
595
+ """
596
+ # Mock external app configuration
597
+ mock_external_app = MagicMock()
598
+
599
+ # Mock the shared.const module with CUSTOM_ROLES_ACTIONS
600
+ mock_shared = MagicMock()
601
+ mock_const = MagicMock()
602
+ mock_const.EXTRA_PERMISSION_ASSIGNATION = []
603
+ # Define custom roles actions
604
+ mock_const.CUSTOM_ROLES_ACTIONS = {
605
+ # Custom role with multiple actions
606
+ 888: [
607
+ GET_ACTION,
608
+ POST_ACTION,
609
+ PUT_ACTION,
610
+ ],
611
+ # Another custom role with different actions
612
+ 777: [
613
+ GET_ACTION,
614
+ PATCH_ACTION,
615
+ ],
616
+ }
617
+ mock_shared.const = mock_const
618
+ mock_external_app.shared = mock_shared
619
+
620
+ # Mock the endpoints.resources with fake external app endpoints
621
+ mock_endpoints = MagicMock()
622
+ mock_endpoints.resources = self._create_mock_external_app_resources()
623
+ mock_external_app.endpoints = mock_endpoints
624
+
625
+ mock_import_views.return_value = mock_external_app
626
+ mock_import_auxiliar.return_value = mock_external_app
627
+
628
+ # Mock the database session for testing
629
+ with patch.object(db.session, "commit"):
630
+ with patch.object(db.session, "rollback"):
631
+ # Run the complete access initialization
632
+ access_init_command(verbose=True)
633
+
634
+ # Verify that custom permissions were created with the correct actions
635
+ from cornflow.models import PermissionViewRoleModel
636
+
637
+ # Get all permissions for role 888
638
+ permissions_888 = PermissionViewRoleModel.query.filter_by(
639
+ role_id=888
640
+ ).all()
641
+ actions_888 = {perm.action_id for perm in permissions_888}
642
+
643
+ # Get all permissions for role 777
644
+ permissions_777 = PermissionViewRoleModel.query.filter_by(
645
+ role_id=777
646
+ ).all()
647
+ actions_777 = {perm.action_id for perm in permissions_777}
648
+
649
+ # Verify role 888 has GET, POST, PUT actions
650
+ expected_actions_888 = {GET_ACTION, POST_ACTION, PUT_ACTION}
651
+ self.assertTrue(
652
+ expected_actions_888.issubset(actions_888),
653
+ f"Role 888 should have actions {expected_actions_888}, but got {actions_888}",
654
+ )
655
+
656
+ # Verify role 777 has GET, PATCH actions
657
+ expected_actions_777 = {GET_ACTION, PATCH_ACTION}
658
+ self.assertTrue(
659
+ expected_actions_777.issubset(actions_777),
660
+ f"Role 777 should have actions {expected_actions_777}, but got {actions_777}",
661
+ )
662
+
663
+ # Verify that role 888 does NOT have actions that were not defined
664
+ # We check that no DELETE or PATCH actions exist for role 888
665
+ forbidden_actions_888 = {DELETE_ACTION, PATCH_ACTION}
666
+ actual_forbidden_888 = actions_888.intersection(forbidden_actions_888)
667
+ self.assertEqual(
668
+ len(actual_forbidden_888),
669
+ 0,
670
+ f"Role 888 should not have actions {forbidden_actions_888}, but found {actual_forbidden_888}",
671
+ )
672
+
673
+ # Verify that role 777 does NOT have actions that were not defined
674
+ # We check that no POST, PUT, DELETE actions exist for role 777
675
+ forbidden_actions_777 = {POST_ACTION, PUT_ACTION, DELETE_ACTION}
676
+ actual_forbidden_777 = actions_777.intersection(forbidden_actions_777)
677
+ self.assertEqual(
678
+ len(actual_forbidden_777),
679
+ 0,
680
+ f"Role 777 should not have actions {forbidden_actions_777}, but found {actual_forbidden_777}",
681
+ )
682
+
683
+ @patch("cornflow.commands.auxiliar.import_module")
684
+ @patch("cornflow.commands.views.import_module")
685
+ @patch.dict(
686
+ os.environ, {"EXTERNAL_APP": "1", "EXTERNAL_APP_MODULE": "external_test_app"}
687
+ )
688
+ def test_custom_roles_actions_error_on_undefined_role(
689
+ self, mock_import_views, mock_import_auxiliar
690
+ ):
691
+ """
692
+ Test that an error is raised when a custom role is used but not defined in CUSTOM_ROLES_ACTIONS
693
+ """
694
+ # Mock external app configuration
695
+ mock_external_app = MagicMock()
696
+
697
+ # Mock the shared.const module with CUSTOM_ROLES_ACTIONS that doesn't include role 888
698
+ mock_shared = MagicMock()
699
+ mock_const = MagicMock()
700
+ mock_const.EXTRA_PERMISSION_ASSIGNATION = []
701
+ # Define custom roles permissions but MISSING role 888 which is used in endpoints
702
+ mock_const.CUSTOM_ROLES_ACTIONS = {
703
+ # Only define role 777, but role 888 is used in endpoints
704
+ 777: [
705
+ GET_ACTION,
706
+ PATCH_ACTION,
707
+ ],
708
+ }
709
+ mock_shared.const = mock_const
710
+ mock_external_app.shared = mock_shared
711
+
712
+ # Mock the endpoints.resources with fake external app endpoints that use role 888
713
+ mock_endpoints = MagicMock()
714
+ mock_endpoints.resources = (
715
+ self._create_mock_external_app_resources()
716
+ ) # This includes role 888
717
+ mock_external_app.endpoints = mock_endpoints
718
+
719
+ mock_import_views.return_value = mock_external_app
720
+ mock_import_auxiliar.return_value = mock_external_app
721
+
722
+ # Verify that a ValueError is raised for undefined role 888
723
+ with self.assertRaises(ValueError) as context:
724
+ access_init_command(verbose=True)
725
+
726
+ # Verify the error message contains the undefined role
727
+ error_message = str(context.exception)
728
+ self.assertIn("888", error_message)
729
+ self.assertIn("CUSTOM_ROLES_ACTIONS", error_message)
730
+ self.assertIn("not defined", error_message)
731
+
732
+ @patch("cornflow.commands.auxiliar.import_module")
733
+ @patch("cornflow.commands.views.import_module")
734
+ @patch.dict(
735
+ os.environ, {"EXTERNAL_APP": "1", "EXTERNAL_APP_MODULE": "external_test_app"}
736
+ )
737
+ def test_custom_roles_actions_fallback_when_not_defined(
738
+ self, mock_import_views, mock_import_auxiliar
739
+ ):
740
+ """
741
+ Test that the system falls back gracefully when CUSTOM_ROLES_ACTIONS is not defined
742
+ but no custom roles are used
743
+ """
744
+ # Mock external app configuration
745
+ mock_external_app = MagicMock()
746
+
747
+ # Mock the shared.const module WITHOUT CUSTOM_ROLES_ACTIONS
748
+ mock_shared = MagicMock()
749
+ mock_const = MagicMock()
750
+ mock_const.EXTRA_PERMISSION_ASSIGNATION = []
751
+ # Don't set CUSTOM_ROLES_ACTIONS to trigger AttributeError
752
+ # This simulates an external app that doesn't define the new constant
753
+ mock_shared.const = mock_const
754
+ mock_external_app.shared = mock_shared
755
+
756
+ # Mock endpoints that only use standard roles (no custom roles)
757
+ mock_production_endpoint = MagicMock()
758
+ # Only standard role
759
+ mock_production_endpoint.ROLES_WITH_ACCESS = [PLANNER_ROLE]
760
+ mock_production_endpoint.DESCRIPTION = "Production planning endpoint"
761
+
762
+ mock_resources = [
763
+ {
764
+ "endpoint": "production_planning",
765
+ "urls": "/production-planning/",
766
+ "resource": mock_production_endpoint,
767
+ }
768
+ ]
769
+
770
+ mock_endpoints = MagicMock()
771
+ mock_endpoints.resources = mock_resources
772
+ mock_external_app.endpoints = mock_endpoints
773
+
774
+ mock_import_views.return_value = mock_external_app
775
+ mock_import_auxiliar.return_value = mock_external_app
776
+
777
+ # Should not raise any exceptions since no custom roles are used
778
+ try:
779
+ access_init_command(verbose=True)
780
+ except Exception as e:
781
+ self.fail(f"access_init_command raised an exception when it shouldn't: {e}")
782
+
537
783
 
538
784
  if __name__ == "__main__":
539
785
  unittest.main()
@@ -0,0 +1,103 @@
1
+ """
2
+ Unit tests for the get_resources function
3
+ """
4
+
5
+ import unittest
6
+ from unittest.mock import patch, MagicMock
7
+ from flask import Flask
8
+ from cornflow.endpoints import get_resources, resources
9
+ from cornflow.shared.const import CONDITIONAL_ENDPOINTS
10
+
11
+
12
+ class TestGetResources(unittest.TestCase):
13
+ """Test the get_resources function"""
14
+
15
+ def setUp(self):
16
+ self.app = Flask(__name__)
17
+ self.mock_signup_view = MagicMock()
18
+ self.mock_signup_view.view_class = MagicMock()
19
+ self.mock_login_view = MagicMock()
20
+ self.mock_login_view.view_class = MagicMock()
21
+
22
+ def test_returns_base_resources_when_no_conditional_endpoints(self):
23
+ """Test that get_resources returns base resources when no conditional endpoints are registered"""
24
+ with self.app.app_context():
25
+ with patch("flask.current_app.view_functions", {}):
26
+ result = get_resources()
27
+ self.assertEqual(result, resources)
28
+
29
+ def test_adds_conditional_endpoint_when_registered(self):
30
+ """Test that get_resources adds conditional endpoints when they are registered"""
31
+ with self.app.app_context():
32
+ mock_view_functions = {"signup": self.mock_signup_view}
33
+
34
+ with patch("flask.current_app.view_functions", mock_view_functions):
35
+ result = get_resources()
36
+
37
+ # Should have one more resource than base
38
+ self.assertEqual(len(result), len(resources) + 1)
39
+
40
+ # Check that signup endpoint was added correctly
41
+ signup_resource = next(
42
+ (r for r in result if r["endpoint"] == "signup"), None
43
+ )
44
+ self.assertIsNotNone(signup_resource)
45
+ self.assertEqual(
46
+ signup_resource["urls"], CONDITIONAL_ENDPOINTS["signup"]
47
+ )
48
+ self.assertEqual(
49
+ signup_resource["resource"], self.mock_signup_view.view_class
50
+ )
51
+
52
+ def test_adds_both_signup_and_login_when_registered(self):
53
+ """Test that get_resources adds both signup and login when both are registered"""
54
+ with self.app.app_context():
55
+ mock_view_functions = {
56
+ "signup": self.mock_signup_view,
57
+ "login": self.mock_login_view,
58
+ }
59
+
60
+ with patch("flask.current_app.view_functions", mock_view_functions):
61
+ result = get_resources()
62
+
63
+ # Should have two more resources than base
64
+ self.assertEqual(len(result), len(resources) + 2)
65
+
66
+ # Check that both endpoints were added correctly
67
+ signup_resource = next(
68
+ (r for r in result if r["endpoint"] == "signup"), None
69
+ )
70
+ login_resource = next(
71
+ (r for r in result if r["endpoint"] == "login"), None
72
+ )
73
+
74
+ self.assertIsNotNone(signup_resource)
75
+ self.assertIsNotNone(login_resource)
76
+ self.assertEqual(
77
+ signup_resource["urls"], CONDITIONAL_ENDPOINTS["signup"]
78
+ )
79
+ self.assertEqual(login_resource["urls"], CONDITIONAL_ENDPOINTS["login"])
80
+
81
+ def test_does_not_add_duplicate_endpoints(self):
82
+ """Test that get_resources does not add endpoints that already exist in base resources"""
83
+ with self.app.app_context():
84
+ mock_view_functions = {
85
+ "instance": MagicMock()
86
+ } # 'instance' already exists in base resources
87
+
88
+ with patch("flask.current_app.view_functions", mock_view_functions):
89
+ result = get_resources()
90
+ self.assertEqual(len(result), len(resources))
91
+
92
+ def test_ignores_non_conditional_endpoints(self):
93
+ """Test that get_resources ignores endpoints that are not in CONDITIONAL_ENDPOINTS"""
94
+ with self.app.app_context():
95
+ mock_view_functions = {"non_conditional_endpoint": MagicMock()}
96
+
97
+ with patch("flask.current_app.view_functions", mock_view_functions):
98
+ result = get_resources()
99
+ self.assertEqual(len(result), len(resources))
100
+
101
+
102
+ if __name__ == "__main__":
103
+ unittest.main()
@@ -1,18 +1,25 @@
1
1
  """
2
2
  Unit test for the sign-up endpoint
3
3
  """
4
- from cornflow.commands import access_init_command
5
- from cornflow.commands.dag import register_deployed_dags_command_test
6
4
 
7
5
  # Import from libraries
8
- from flask_testing import TestCase
6
+
7
+ # Import from internal modules
8
+
9
9
  import json
10
+ from unittest.mock import patch
11
+
12
+ # Import from libraries
13
+ from flask_testing import TestCase
10
14
 
11
15
  # Import from internal modules
12
16
  from cornflow.app import create_app
13
- from cornflow.models import UserModel, UserRoleModel
14
- from cornflow.shared.const import PLANNER_ROLE
17
+ from cornflow.commands import access_init_command
18
+ from cornflow.commands.dag import register_deployed_dags_command_test
19
+ from cornflow.models import UserModel, UserRoleModel, ViewModel, PermissionViewRoleModel
15
20
  from cornflow.shared import db
21
+ from cornflow.shared.authentication import Auth
22
+ from cornflow.shared.const import PLANNER_ROLE, ADMIN_ROLE, POST_ACTION, NO_SIGNUP, SIGNUP_WITH_AUTH
16
23
  from cornflow.tests.const import SIGNUP_URL
17
24
 
18
25
 
@@ -95,7 +102,7 @@ class TestSignUp(TestCase):
95
102
  class TestSignUpDeactivated(TestCase):
96
103
  def create_app(self):
97
104
  app = create_app("testing")
98
- app.config["SIGNUP_ACTIVATED"] = 0
105
+ app.config["SIGNUP_ACTIVATED"] = NO_SIGNUP
99
106
  return app
100
107
 
101
108
  def setUp(self):
@@ -120,3 +127,171 @@ class TestSignUpDeactivated(TestCase):
120
127
  )
121
128
 
122
129
  self.assertEqual(response.status_code, 400)
130
+
131
+
132
+ class TestSignUpAuthenticated(TestCase):
133
+ """Test the authenticated signup endpoint (SIGNUP_ACTIVATED=SIGNUP_WITH_AUTH)"""
134
+
135
+ def create_app(self):
136
+ with patch("cornflow.config.Testing.SIGNUP_ACTIVATED", SIGNUP_WITH_AUTH):
137
+ app = create_app("testing")
138
+ return app
139
+
140
+ def setUp(self):
141
+ db.create_all()
142
+ access_init_command(verbose=False)
143
+ register_deployed_dags_command_test(verbose=False)
144
+
145
+ # Create test data
146
+ self.data = {
147
+ "username": "testname",
148
+ "email": "test@test.com",
149
+ "password": "Testpassword1!",
150
+ }
151
+
152
+ # Create an admin user for testing
153
+ self.admin_user = UserModel(
154
+ {
155
+ "username": "admin",
156
+ "email": "admin@test.com",
157
+ "password": "Adminpassword1!",
158
+ }
159
+ )
160
+ self.admin_user.save()
161
+
162
+ # Assign admin role to the user
163
+ admin_role = UserRoleModel(
164
+ {"user_id": self.admin_user.id, "role_id": ADMIN_ROLE}
165
+ )
166
+ admin_role.save()
167
+
168
+ # Create a regular user for testing
169
+ self.regular_user = UserModel(
170
+ {
171
+ "username": "regular",
172
+ "email": "regular@test.com",
173
+ "password": "Regularpassword1!",
174
+ }
175
+ )
176
+ self.regular_user.save()
177
+
178
+ # Assign planner role to the user
179
+ planner_role = UserRoleModel(
180
+ {"user_id": self.regular_user.id, "role_id": PLANNER_ROLE}
181
+ )
182
+ planner_role.save()
183
+
184
+ def tearDown(self):
185
+ db.session.remove()
186
+ db.drop_all()
187
+
188
+ def get_auth_token(self, user):
189
+ """Helper method to get authentication token for a user"""
190
+ auth = Auth()
191
+ return auth.generate_token(user.id)
192
+
193
+ def test_authenticated_signup_admin_can_register(self):
194
+ """Test that admin users can register new users"""
195
+ payload = self.data
196
+ admin_token = self.get_auth_token(self.admin_user)
197
+
198
+ response = self.client.post(
199
+ SIGNUP_URL,
200
+ data=json.dumps(payload),
201
+ follow_redirects=True,
202
+ headers={
203
+ "Content-Type": "application/json",
204
+ "Authorization": f"Bearer {admin_token}",
205
+ },
206
+ )
207
+
208
+ self.assertEqual(201, response.status_code)
209
+ self.assertEqual(str, type(response.json["token"]))
210
+ self.assertEqual(int, type(response.json["id"]))
211
+ self.assertEqual(
212
+ PLANNER_ROLE,
213
+ UserRoleModel.query.filter_by(user_id=response.json["id"]).first().role_id,
214
+ )
215
+ self.assertNotEqual(None, UserModel.get_one_user_by_email(self.data["email"]))
216
+
217
+ def test_authenticated_signup_regular_user_cannot_register(self):
218
+ """Test that regular users cannot register new users"""
219
+ payload = self.data
220
+ regular_token = self.get_auth_token(self.regular_user)
221
+
222
+ response = self.client.post(
223
+ SIGNUP_URL,
224
+ data=json.dumps(payload),
225
+ follow_redirects=True,
226
+ headers={
227
+ "Content-Type": "application/json",
228
+ "Authorization": f"Bearer {regular_token}",
229
+ },
230
+ )
231
+
232
+ # Should return 403 Forbidden due to insufficient permissions
233
+ self.assertEqual(403, response.status_code)
234
+ self.assertIn("error", response.json)
235
+
236
+ def test_authenticated_signup_no_auth_header_fails(self):
237
+ """Test that signup fails without authentication header"""
238
+ payload = self.data
239
+
240
+ response = self.client.post(
241
+ SIGNUP_URL,
242
+ data=json.dumps(payload),
243
+ follow_redirects=True,
244
+ headers={"Content-Type": "application/json"},
245
+ )
246
+
247
+ # Should return 400 Bad Request due to missing authorization
248
+ self.assertEqual(400, response.status_code)
249
+ self.assertIn("error", response.json)
250
+
251
+ def test_authenticated_signup_invalid_token_fails(self):
252
+ """Test that signup fails with invalid token"""
253
+ payload = self.data
254
+
255
+ response = self.client.post(
256
+ SIGNUP_URL,
257
+ data=json.dumps(payload),
258
+ follow_redirects=True,
259
+ headers={
260
+ "Content-Type": "application/json",
261
+ "Authorization": "Bearer invalid_token",
262
+ },
263
+ )
264
+
265
+ # Should return 400 Bad Request due to invalid token
266
+ self.assertEqual(400, response.status_code)
267
+ self.assertIn("error", response.json)
268
+
269
+ def test_signup_permissions_are_registered_in_database(self):
270
+ """Test that signup permissions are properly registered in the database"""
271
+ # Check that the signup endpoint exists in the views table
272
+ signup_view = ViewModel.query.filter_by(name="signup").first()
273
+ self.assertIsNotNone(
274
+ signup_view, "Signup endpoint should be registered in views table"
275
+ )
276
+ self.assertEqual("/signup/", signup_view.url_rule)
277
+
278
+ # Check that admin role has permissions for signup endpoint
279
+ admin_permissions = PermissionViewRoleModel.query.filter_by(
280
+ role_id=ADMIN_ROLE, api_view_id=signup_view.id
281
+ ).all()
282
+
283
+ # Admin should have POST permission for signup endpoint
284
+ self.assertGreater(
285
+ len(admin_permissions),
286
+ 0,
287
+ "Admin should have permissions for signup endpoint",
288
+ )
289
+
290
+ # Verify that the permission is for POST action
291
+ post_permission = PermissionViewRoleModel.query.filter_by(
292
+ role_id=ADMIN_ROLE, api_view_id=signup_view.id, action_id=POST_ACTION
293
+ ).first()
294
+
295
+ self.assertIsNotNone(
296
+ post_permission, "Admin should have POST permission for signup endpoint"
297
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cornflow
3
- Version: 1.2.3a4
3
+ Version: 1.2.4
4
4
  Summary: cornflow is an open source multi-solver optimization server with a REST API built using flask.
5
5
  Home-page: https://github.com/baobabsoluciones/cornflow
6
6
  Author: baobab soluciones
@@ -14,7 +14,7 @@ Requires-Dist: alembic==1.9.2
14
14
  Requires-Dist: apispec<=6.3.0
15
15
  Requires-Dist: cachetools==5.3.3
16
16
  Requires-Dist: click<=8.1.7
17
- Requires-Dist: cornflow-client>=1.2.3.a4
17
+ Requires-Dist: cornflow-client<=1.2.4
18
18
  Requires-Dist: cryptography<=44.0.1
19
19
  Requires-Dist: disposable-email-domains>=0.0.86
20
20
  Requires-Dist: Flask==2.3.2
@@ -37,7 +37,7 @@ Requires-Dist: PuLP<=2.9.0
37
37
  Requires-Dist: psycopg2<=2.9.9
38
38
  Requires-Dist: PyJWT<=2.8.0
39
39
  Requires-Dist: pytups>=0.86.2
40
- Requires-Dist: requests<=2.32.3
40
+ Requires-Dist: requests<=2.32.4
41
41
  Requires-Dist: SQLAlchemy==1.3.21
42
42
  Requires-Dist: webargs<=8.3.0
43
43
  Requires-Dist: Werkzeug==3.0.6