cornflow 2.0.0a11__py3-none-any.whl → 2.0.0a12__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.
Files changed (52) hide show
  1. airflow_config/airflow_local_settings.py +1 -1
  2. cornflow/app.py +8 -3
  3. cornflow/cli/migrations.py +23 -3
  4. cornflow/cli/service.py +18 -18
  5. cornflow/cli/utils.py +16 -1
  6. cornflow/commands/dag.py +1 -1
  7. cornflow/config.py +13 -8
  8. cornflow/endpoints/__init__.py +8 -2
  9. cornflow/endpoints/alarms.py +66 -2
  10. cornflow/endpoints/data_check.py +53 -26
  11. cornflow/endpoints/execution.py +387 -132
  12. cornflow/endpoints/login.py +81 -63
  13. cornflow/endpoints/meta_resource.py +11 -3
  14. cornflow/migrations/versions/999b98e24225.py +34 -0
  15. cornflow/models/base_data_model.py +4 -32
  16. cornflow/models/execution.py +2 -3
  17. cornflow/models/meta_models.py +28 -22
  18. cornflow/models/user.py +7 -10
  19. cornflow/schemas/alarms.py +8 -0
  20. cornflow/schemas/execution.py +1 -1
  21. cornflow/schemas/query.py +2 -1
  22. cornflow/schemas/user.py +5 -20
  23. cornflow/shared/authentication/auth.py +201 -264
  24. cornflow/shared/const.py +3 -14
  25. cornflow/shared/databricks.py +5 -1
  26. cornflow/tests/const.py +1 -0
  27. cornflow/tests/custom_test_case.py +77 -26
  28. cornflow/tests/unit/test_actions.py +2 -2
  29. cornflow/tests/unit/test_alarms.py +55 -1
  30. cornflow/tests/unit/test_apiview.py +108 -3
  31. cornflow/tests/unit/test_cases.py +20 -29
  32. cornflow/tests/unit/test_cli.py +6 -5
  33. cornflow/tests/unit/test_commands.py +3 -3
  34. cornflow/tests/unit/test_dags.py +5 -6
  35. cornflow/tests/unit/test_executions.py +443 -123
  36. cornflow/tests/unit/test_instances.py +14 -2
  37. cornflow/tests/unit/test_instances_file.py +1 -1
  38. cornflow/tests/unit/test_licenses.py +1 -1
  39. cornflow/tests/unit/test_log_in.py +230 -207
  40. cornflow/tests/unit/test_permissions.py +8 -8
  41. cornflow/tests/unit/test_roles.py +48 -10
  42. cornflow/tests/unit/test_schemas.py +1 -1
  43. cornflow/tests/unit/test_tables.py +7 -7
  44. cornflow/tests/unit/test_token.py +19 -5
  45. cornflow/tests/unit/test_users.py +22 -6
  46. cornflow/tests/unit/tools.py +75 -10
  47. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/METADATA +16 -15
  48. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/RECORD +51 -51
  49. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/WHEEL +1 -1
  50. cornflow/endpoints/execution_databricks.py +0 -808
  51. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/entry_points.txt +0 -0
  52. {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/top_level.txt +0 -0
cornflow/shared/const.py CHANGED
@@ -7,6 +7,8 @@ AIRFLOW_BACKEND = 1
7
7
  DATABRICKS_BACKEND = 2
8
8
 
9
9
 
10
+ INTERNAL_TOKEN_ISSUER = "cornflow"
11
+
10
12
  # endpoints responses for health check
11
13
  STATUS_HEALTHY = "healthy"
12
14
  STATUS_UNHEALTHY = "unhealthy"
@@ -57,6 +59,7 @@ DATABRICKS_TO_STATE_MAP = dict(
57
59
  SUCCESS=EXEC_STATE_CORRECT,
58
60
  USER_CANCELED=EXEC_STATE_STOPPED,
59
61
  OTHER_FINISH_ERROR=EXEC_STATE_ERROR,
62
+ RUN_EXECUTION_ERROR=EXEC_STATE_ERROR
60
63
  )
61
64
 
62
65
  DATABRICKS_FINISH_TO_STATE_MAP = dict(
@@ -72,20 +75,6 @@ AUTH_LDAP = 2
72
75
  AUTH_OAUTH = 4
73
76
  AUTH_OID = 0
74
77
 
75
- # Providers of open ID:
76
- OID_NONE = 0
77
- OID_AZURE = 1
78
- OID_GOOGLE = 2
79
-
80
-
81
- # AZURE OPEN ID URLS
82
- OID_AZURE_DISCOVERY_COMMON_URL = (
83
- "https://login.microsoftonline.com/common/.well-known/openid-configuration"
84
- )
85
- OID_AZURE_DISCOVERY_TENANT_URL = (
86
- "https://login.microsoftonline.com/{tenant_id}/.well-known/openid-configuration"
87
- )
88
-
89
78
  GET_ACTION = 1
90
79
  PATCH_ACTION = 2
91
80
  POST_ACTION = 3
@@ -92,7 +92,11 @@ class Databricks:
92
92
  # TODO AGA: revisar si deben ser notebook parameters o job parameters.
93
93
  # Entender cómo se usa checks_only
94
94
  payload = dict(
95
- job_id=orch_name, notebook_parameters=dict(checks_only=checks_only)
95
+ job_id=orch_name,
96
+ notebook_parameters=dict(
97
+ checks_only=checks_only,
98
+ execution_id=execution_id,
99
+ ),
96
100
  )
97
101
  return self.request_headers_auth(method="POST", url=url, json=payload)
98
102
 
cornflow/tests/const.py CHANGED
@@ -9,6 +9,7 @@ def _get_file(relative_path):
9
9
 
10
10
  PREFIX = ""
11
11
  INSTANCE_PATH = _get_file("./data/new_instance.json")
12
+ EMPTY_INSTANCE_PATH = _get_file("./data/empty_instance.json")
12
13
  INSTANCES_LIST = [INSTANCE_PATH, _get_file("./data/new_instance_2.json")]
13
14
  INSTANCE_URL = PREFIX + "/instance/"
14
15
  INSTANCE_MPS = _get_file("./data/test_mps.mps")
@@ -14,25 +14,32 @@ LoginTestCases
14
14
  Test cases for login functionality
15
15
  """
16
16
 
17
+ import json
18
+
17
19
  # Import from libraries
18
20
  import logging as log
19
- from datetime import datetime, timedelta
20
-
21
+ from datetime import datetime, timedelta, timezone
21
22
  from typing import List
22
23
 
24
+ import jwt
23
25
  from flask import current_app
24
26
  from flask_testing import TestCase
25
- import json
26
- import jwt
27
27
 
28
28
  # Import from internal modules
29
29
  from cornflow.app import create_app
30
- from cornflow.models import UserRoleModel
30
+ from cornflow.models import UserRoleModel, UserModel
31
31
  from cornflow.commands.access import access_init_command
32
32
  from cornflow.commands.dag import register_deployed_dags_command_test
33
33
  from cornflow.commands.permissions import register_dag_permissions_command
34
+ from cornflow.models import UserRoleModel
35
+ from cornflow.shared import db
34
36
  from cornflow.shared.authentication import Auth
35
- from cornflow.shared.const import ADMIN_ROLE, PLANNER_ROLE, SERVICE_ROLE
37
+ from cornflow.shared.const import (
38
+ ADMIN_ROLE,
39
+ PLANNER_ROLE,
40
+ SERVICE_ROLE,
41
+ INTERNAL_TOKEN_ISSUER,
42
+ )
36
43
  from cornflow.shared import db
37
44
  from cornflow.tests.const import (
38
45
  LOGIN_URL,
@@ -121,7 +128,8 @@ class CustomTestCase(TestCase):
121
128
  headers={"Content-Type": "application/json"},
122
129
  ).json["token"]
123
130
 
124
- self.user = Auth.return_user_from_token(self.token)
131
+ data = Auth().decode_token(self.token)
132
+ self.user = UserModel.get_one_object(username=data["sub"])
125
133
  self.url = None
126
134
  self.model = None
127
135
  self.copied_items = set()
@@ -587,6 +595,14 @@ class BaseTestCases:
587
595
  allrows = self.get_rows(self.url, data_many)
588
596
  self.apply_filter(self.url, dict(limit=1), [allrows.json[0]])
589
597
 
598
+ def test_opt_filters_limit_none(self):
599
+ """
600
+ Tests the limit filter option
601
+ """
602
+ data_many = [self.payload for _ in range(4)]
603
+ allrows = self.get_rows(self.url, data_many)
604
+ self.apply_filter(self.url, dict(limit=None), allrows.json)
605
+
590
606
  def test_opt_filters_offset(self):
591
607
  """
592
608
  Tests the offset filter option.
@@ -596,6 +612,22 @@ class BaseTestCases:
596
612
  allrows = self.get_rows(self.url, data_many)
597
613
  self.apply_filter(self.url, dict(offset=1, limit=2), allrows.json[1:3])
598
614
 
615
+ def test_opt_filters_offset_zero(self):
616
+ """
617
+ Tests the offset filter option with a zero value.
618
+ """
619
+ data_many = [self.payload for _ in range(4)]
620
+ allrows = self.get_rows(self.url, data_many)
621
+ self.apply_filter(self.url, dict(offset=0), allrows.json)
622
+
623
+ def test_opt_filters_offset_none(self):
624
+ """
625
+ Tests the offset filter option with a None value.
626
+ """
627
+ data_many = [self.payload for _ in range(4)]
628
+ allrows = self.get_rows(self.url, data_many)
629
+ self.apply_filter(self.url, dict(offset=None), allrows.json)
630
+
599
631
  def test_opt_filters_schema(self):
600
632
  """
601
633
  Tests the schema filter option.
@@ -828,7 +860,7 @@ class CheckTokenTestCase:
828
860
  follow_redirects=True,
829
861
  headers={
830
862
  "Content-Type": "application/json",
831
- "Authorization": "Bearer " + self.token,
863
+ "Authorization": f"Bearer {self.token}",
832
864
  },
833
865
  )
834
866
  else:
@@ -982,28 +1014,38 @@ class LoginTestCases:
982
1014
  """
983
1015
  Tests using an expired token.
984
1016
  """
985
- token = (
986
- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTA1MzYwNjUsImlhdCI6MTYxMDQ0OTY2NSwic3ViIjoxfQ"
987
- ".QEfmO-hh55PjtecnJ1RJT3aW2brGLadkg5ClH9yrRnc "
988
- )
989
-
1017
+ # First log in to get a valid user
990
1018
  payload = self.data
991
-
992
1019
  response = self.client.post(
993
1020
  LOGIN_URL,
994
1021
  data=json.dumps(payload),
995
1022
  follow_redirects=True,
996
1023
  headers={"Content-Type": "application/json"},
997
1024
  )
998
-
999
1025
  self.idx = response.json["id"]
1000
1026
 
1027
+ # Generate an expired token
1028
+ expired_payload = {
1029
+ # Token expired 1 hour ago
1030
+ "exp": datetime.utcnow() - timedelta(hours=1),
1031
+ # Token created 2 hours ago
1032
+ "iat": datetime.utcnow() - timedelta(hours=2),
1033
+ "sub": self.idx,
1034
+ "iss": INTERNAL_TOKEN_ISSUER,
1035
+ }
1036
+ expired_token = jwt.encode(
1037
+ expired_payload,
1038
+ current_app.config["SECRET_TOKEN_KEY"],
1039
+ algorithm="HS256",
1040
+ )
1041
+
1042
+ # Try to use the expired token
1001
1043
  response = self.client.get(
1002
1044
  USER_URL + str(self.idx) + "/",
1003
1045
  follow_redirects=True,
1004
1046
  headers={
1005
1047
  "Content-Type": "application/json",
1006
- "Authorization": "Bearer " + token,
1048
+ "Authorization": f"Bearer {expired_token}",
1007
1049
  },
1008
1050
  )
1009
1051
 
@@ -1040,13 +1082,8 @@ class LoginTestCases:
1040
1082
  """
1041
1083
  Tests using an invalid token.
1042
1084
  """
1043
- token = (
1044
- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2MTA1Mzk5NTMsImlhdCI6MTYxMDQ1MzU1Mywic3ViIjoxfQ"
1045
- ".g3Gh7k7twXZ4K2MnQpgpSr76Sl9VX6TkDWusX5YzImo"
1046
- )
1047
-
1085
+ # First log in to get a valid user
1048
1086
  payload = self.data
1049
-
1050
1087
  response = self.client.post(
1051
1088
  LOGIN_URL,
1052
1089
  data=json.dumps(payload),
@@ -1055,18 +1092,32 @@ class LoginTestCases:
1055
1092
  )
1056
1093
  self.idx = response.json["id"]
1057
1094
 
1095
+ # Generate an invalid token with wrong issuer
1096
+ invalid_payload = {
1097
+ "exp": datetime.utcnow() + timedelta(hours=1),
1098
+ "iat": datetime.utcnow(),
1099
+ "sub": self.idx,
1100
+ "iss": "invalid_issuer",
1101
+ }
1102
+ invalid_token = jwt.encode(
1103
+ invalid_payload,
1104
+ current_app.config["SECRET_TOKEN_KEY"],
1105
+ algorithm="HS256",
1106
+ )
1107
+
1108
+ # Try to use the invalid token
1058
1109
  response = self.client.get(
1059
1110
  USER_URL + str(self.idx) + "/",
1060
1111
  follow_redirects=True,
1061
1112
  headers={
1062
1113
  "Content-Type": "application/json",
1063
- "Authorization": "Bearer " + token,
1114
+ "Authorization": f"Bearer {invalid_token}",
1064
1115
  },
1065
1116
  )
1066
1117
 
1067
1118
  self.assertEqual(400, response.status_code)
1068
1119
  self.assertEqual(
1069
- "Invalid token, please try again with a new token",
1120
+ "Invalid token issuer. Token must be issued by a valid provider",
1070
1121
  response.json["error"],
1071
1122
  )
1072
1123
 
@@ -1092,7 +1143,7 @@ class LoginTestCases:
1092
1143
  )
1093
1144
 
1094
1145
  self.assertAlmostEqual(
1095
- datetime.utcnow(),
1096
- datetime.utcfromtimestamp(decoded_token["iat"]),
1146
+ datetime.now(timezone.utc),
1147
+ datetime.fromtimestamp(decoded_token["iat"], timezone.utc),
1097
1148
  delta=timedelta(seconds=2),
1098
1149
  )
@@ -67,7 +67,7 @@ class TestActionsListEndpoint(CustomTestCase):
67
67
  follow_redirects=True,
68
68
  headers={
69
69
  "Content-Type": "application/json",
70
- "Authorization": "Bearer " + self.token,
70
+ "Authorization": f"Bearer {self.token}",
71
71
  },
72
72
  )
73
73
  self.assertEqual(200, response.status_code)
@@ -89,7 +89,7 @@ class TestActionsListEndpoint(CustomTestCase):
89
89
  follow_redirects=True,
90
90
  headers={
91
91
  "Content-Type": "application/json",
92
- "Authorization": "Bearer " + self.token,
92
+ "Authorization": f"Bearer {self.token}",
93
93
  },
94
94
  )
95
95
 
@@ -13,9 +13,10 @@ TestAlarmsEndpoint
13
13
  """
14
14
 
15
15
  # Imports from internal modules
16
+ import json
16
17
  from cornflow.models import AlarmsModel
17
18
  from cornflow.tests.const import ALARMS_URL
18
- from cornflow.tests.custom_test_case import CustomTestCase
19
+ from cornflow.tests.custom_test_case import BaseTestCases, CustomTestCase
19
20
 
20
21
 
21
22
  class TestAlarmsEndpoint(CustomTestCase):
@@ -85,3 +86,56 @@ class TestAlarmsEndpoint(CustomTestCase):
85
86
  self.assertIn(key, rows_data[i])
86
87
  if key in data[i]:
87
88
  self.assertEqual(rows_data[i][key], data[i][key])
89
+
90
+
91
+ class TestAlarmsDetailEndpoint(TestAlarmsEndpoint, BaseTestCases.DetailEndpoint):
92
+ def setUp(self):
93
+ super().setUp()
94
+ self.url = self.url
95
+ self.idx = 0
96
+ self.payload = {
97
+ "name": "Alarm 1",
98
+ "description": "Description Alarm 1",
99
+ "criticality": 1,
100
+ }
101
+
102
+ def test_disable_alarm_detail(self):
103
+ """
104
+ The idea would be to read the alarm_id from the query and be able to disable the entire row for that alarm in the database.
105
+ To check this, I would use an example data set of different alarms and, after giving a specific id, be able to return the same data set,
106
+ excluding those related to the given alarm_id.
107
+
108
+ Verifies:
109
+ - Retrieval of a single alarm using its ID
110
+ - Correct validation of alarm data fields
111
+
112
+ """
113
+
114
+ data = {
115
+ "name": "Alarm 1",
116
+ "description": "Description Alarm 1",
117
+ "criticality": 1,
118
+ }
119
+ id = self.create_new_row(self.url, self.model, data)
120
+ data = {
121
+ "name": "Alarm 2",
122
+ "description": "Description Alarm 2",
123
+ "criticality": 1,
124
+ }
125
+ self.idx = id
126
+ url = self.url + str(id) + "/"
127
+ response = self.client.delete(
128
+ url,
129
+ data=json.dumps(data),
130
+ follow_redirects=True,
131
+ headers=self.get_header_with_auth(self.token),
132
+ )
133
+ self.assertEqual(response.status_code, 200)
134
+ self.assertEqual(response.json, {"message": "Object marked as disabled"})
135
+ all_rows = AlarmsModel.query.all()
136
+ for row in all_rows:
137
+ if row.id == id:
138
+ # We check deleted at has a value
139
+ self.assertIsNotNone(row.deleted_at)
140
+ else:
141
+ self.assertIsNone(row.deleted_at)
@@ -13,7 +13,8 @@ TestApiViewListEndpoint
13
13
  """
14
14
 
15
15
  # Import from internal modules
16
- from cornflow.endpoints import ApiViewListEndpoint, resources
16
+ from cornflow.endpoints import ApiViewListEndpoint, resources, alarms_resources
17
+ from cornflow.models import ViewModel
17
18
  from cornflow.shared.const import ROLES_MAP
18
19
  from cornflow.tests.const import APIVIEW_URL
19
20
  from cornflow.tests.custom_test_case import CustomTestCase
@@ -71,7 +72,7 @@ class TestApiViewListEndpoint(CustomTestCase):
71
72
  follow_redirects=True,
72
73
  headers={
73
74
  "Content-Type": "application/json",
74
- "Authorization": "Bearer " + self.token,
75
+ "Authorization": f"Bearer {self.token}",
75
76
  },
76
77
  )
77
78
  self.assertEqual(200, response.status_code)
@@ -97,8 +98,112 @@ class TestApiViewListEndpoint(CustomTestCase):
97
98
  follow_redirects=True,
98
99
  headers={
99
100
  "Content-Type": "application/json",
100
- "Authorization": "Bearer " + self.token,
101
+ "Authorization": f"Bearer {self.token}",
101
102
  },
102
103
  )
103
104
 
104
105
  self.assertEqual(403, response.status_code)
106
+
107
+
108
+ class TestApiViewModel(CustomTestCase):
109
+ """
110
+ Test cases for the API views list endpoint.
111
+
112
+ This class tests the functionality of listing available API views, including:
113
+ - Authorization checks for different user roles
114
+ - Validation of returned API view data
115
+ - Access control for authorized and unauthorized roles
116
+ """
117
+
118
+ def setUp(self):
119
+ """
120
+ Set up test environment before each test.
121
+
122
+ Initializes test data including:
123
+ - Base test case setup
124
+ - Roles with access permissions
125
+ - Test payload with API view data
126
+ - Items to check in responses
127
+ """
128
+ super().setUp()
129
+ self.roles_with_access = ApiViewListEndpoint.ROLES_WITH_ACCESS
130
+ self.payload = [
131
+ {
132
+ "name": view["endpoint"],
133
+ "url_rule": view["urls"],
134
+ "description": view["resource"].DESCRIPTION,
135
+ }
136
+ for view in resources
137
+ ]
138
+ self.items_to_check = ["name", "description", "url_rule"]
139
+
140
+ def test_get_all_objects(self):
141
+ """
142
+ Test that the get_all_objects method works properly
143
+ """
144
+ expected_count = len(resources) + len(alarms_resources)
145
+ # Test getting all objects
146
+ all_instances = ViewModel.get_all_objects().all()
147
+ self.assertEqual(len(all_instances), expected_count)
148
+
149
+ # Check that all resources are included in the results
150
+ api_view_names = [view.name for view in all_instances]
151
+ expected_names = [view["endpoint"] for view in resources]
152
+
153
+ # Verify all expected resource names are in the results
154
+ for name in expected_names:
155
+ self.assertIn(name, api_view_names)
156
+
157
+ # Test with offset and limit
158
+ limited_instances = ViewModel.get_all_objects(offset=10, limit=10).all()
159
+ self.assertEqual(len(limited_instances), 10)
160
+
161
+ # Verify these are different from the first 10 results
162
+ first_ten = [view.id for view in all_instances[:10]]
163
+ offset_ten = [view.id for view in limited_instances]
164
+ self.assertNotEqual(first_ten, offset_ten)
165
+
166
+ # Test with only offset
167
+ offset_instances = ViewModel.get_all_objects(offset=10).all()
168
+ self.assertEqual(len(offset_instances), expected_count - 10)
169
+
170
+ # Verify these match with the correct slice of all results
171
+ offset_ids = [view.id for view in offset_instances]
172
+ expected_offset_ids = [view.id for view in all_instances[10:]]
173
+ self.assertEqual(offset_ids, expected_offset_ids)
174
+
175
+ # Test with only limit
176
+ limit_instances = ViewModel.get_all_objects(limit=10).all()
177
+ self.assertEqual(len(limit_instances), 10)
178
+
179
+ # Verify these match with the first 10 of all results
180
+ limit_ids = [view.id for view in limit_instances]
181
+ expected_limit_ids = [view.id for view in all_instances[:10]]
182
+ self.assertEqual(limit_ids, expected_limit_ids)
183
+
184
+ def test_get_one_object_by_name(self):
185
+ """
186
+ Test that the get_one_by_name method works properly
187
+ """
188
+ instance = ViewModel.get_one_by_name(name="instance")
189
+ self.assertEqual(instance.name, "instance")
190
+
191
+ def test_get_one_object(self):
192
+ """
193
+ Test that the get_one_object method works properly
194
+ """
195
+ instance = ViewModel.get_one_object(idx=1)
196
+ self.assertEqual(instance.name, "instance")
197
+
198
+ def test_get_one_object_without_idx(self):
199
+ """
200
+ Test that the get_one_object method works properly when called without any filters
201
+ """
202
+ # Call get_one_object without any filters
203
+ instance = ViewModel.get_one_object()
204
+
205
+ # It should return the first instance by default
206
+ first_instance = ViewModel.get_all_objects().first()
207
+ self.assertIsNotNone(instance)
208
+ self.assertEqual(instance.id, first_instance.id)
209
+ self.assertEqual(instance.name, first_instance.name)
@@ -90,11 +90,10 @@ class TestCasesModels(CustomTestCase):
90
90
  self.payload = load_file(INSTANCE_PATH)
91
91
  self.payloads = [load_file(f) for f in INSTANCES_LIST]
92
92
  parents = [None, 1, 1, 3, 3, 3, 1, 7, 7, 9, 7]
93
- user = UserModel.get_one_user(self.user)
94
- data = {**self.payload, **dict(user_id=user.id)}
93
+ data = {**self.payload, **dict(user_id=self.user.id)}
95
94
  for parent in parents:
96
95
  if parent is not None:
97
- parent = CaseModel.get_one_object(user=user, idx=parent)
96
+ parent = CaseModel.get_one_object(user=self.user, idx=parent)
98
97
  node = CaseModel(data=data, parent=parent)
99
98
  node.save()
100
99
 
@@ -106,10 +105,9 @@ class TestCasesModels(CustomTestCase):
106
105
  - Correct path generation for cases
107
106
  - Proper parent-child relationships
108
107
  """
109
- user = UserModel.get_one_user(self.user)
110
- case = CaseModel.get_one_object(user=user, idx=6)
108
+ case = CaseModel.get_one_object(user=self.user, idx=6)
111
109
  self.assertEqual(case.path, "1/3/")
112
- case = CaseModel.get_one_object(user=user, idx=11)
110
+ case = CaseModel.get_one_object(user=self.user, idx=11)
113
111
  self.assertEqual(case.path, "1/7/")
114
112
 
115
113
  def test_move_case(self):
@@ -120,9 +118,8 @@ class TestCasesModels(CustomTestCase):
120
118
  - Cases can be moved to new parents
121
119
  - Path updates correctly after move
122
120
  """
123
- user = UserModel.get_one_user(self.user)
124
- case6 = CaseModel.get_one_object(user=user, idx=6)
125
- case11 = CaseModel.get_one_object(user=user, idx=11)
121
+ case6 = CaseModel.get_one_object(user=self.user, idx=6)
122
+ case11 = CaseModel.get_one_object(user=self.user, idx=11)
126
123
  case6.move_to(case11)
127
124
  self.assertEqual(case6.path, "1/7/11/")
128
125
 
@@ -135,11 +132,10 @@ class TestCasesModels(CustomTestCase):
135
132
  - Nested path updates
136
133
  - Path integrity after moves
137
134
  """
138
- user = UserModel.get_one_user(self.user)
139
- case3 = CaseModel.get_one_object(user=user, idx=3)
140
- case11 = CaseModel.get_one_object(user=user, idx=11)
141
- case9 = CaseModel.get_one_object(user=user, idx=9)
142
- case10 = CaseModel.get_one_object(user=user, idx=10)
135
+ case3 = CaseModel.get_one_object(user=self.user, idx=3)
136
+ case11 = CaseModel.get_one_object(user=self.user, idx=11)
137
+ case9 = CaseModel.get_one_object(user=self.user, idx=9)
138
+ case10 = CaseModel.get_one_object(user=self.user, idx=10)
143
139
  case3.move_to(case11)
144
140
  case9.move_to(case3)
145
141
  self.assertEqual(case10.path, "1/7/11/3/9/")
@@ -152,10 +148,9 @@ class TestCasesModels(CustomTestCase):
152
148
  - Case deletion removes the case
153
149
  - Child cases are properly handled
154
150
  """
155
- user = UserModel.get_one_user(self.user)
156
- case7 = CaseModel.get_one_object(user=user, idx=7)
151
+ case7 = CaseModel.get_one_object(user=self.user, idx=7)
157
152
  case7.delete()
158
- case11 = CaseModel.get_one_object(user=user, idx=11)
153
+ case11 = CaseModel.get_one_object(user=self.user, idx=11)
159
154
  self.assertIsNone(case11)
160
155
 
161
156
  def test_descendants(self):
@@ -166,8 +161,7 @@ class TestCasesModels(CustomTestCase):
166
161
  - Correct counting of descendants
167
162
  - Proper descendant relationships
168
163
  """
169
- user = UserModel.get_one_user(self.user)
170
- case7 = CaseModel.get_one_object(user=user, idx=7)
164
+ case7 = CaseModel.get_one_object(user=self.user, idx=7)
171
165
  self.assertEqual(len(case7.descendants), 4)
172
166
 
173
167
  def test_depth(self):
@@ -178,8 +172,7 @@ class TestCasesModels(CustomTestCase):
178
172
  - Correct depth calculation in case tree
179
173
  - Proper nesting level determination
180
174
  """
181
- user = UserModel.get_one_user(self.user)
182
- case10 = CaseModel.get_one_object(user=user, idx=10)
175
+ case10 = CaseModel.get_one_object(user=self.user, idx=10)
183
176
  self.assertEqual(case10.depth, 4)
184
177
 
185
178
 
@@ -234,12 +227,11 @@ class TestCasesFromInstanceExecutionEndpoint(CustomTestCase):
234
227
  "execution_id": execution_id,
235
228
  "schema": "solve_model_dag",
236
229
  }
237
- self.user_object = UserModel.get_one_user(self.user)
238
230
  self.instance = InstanceModel.get_one_object(
239
- user=self.user_object, idx=instance_id
231
+ user=self.user, idx=instance_id
240
232
  )
241
233
  self.execution = ExecutionModel.get_one_object(
242
- user=self.user_object, idx=execution_id
234
+ user=self.user, idx=execution_id
243
235
  )
244
236
 
245
237
  def test_new_case_execution(self):
@@ -266,7 +258,7 @@ class TestCasesFromInstanceExecutionEndpoint(CustomTestCase):
266
258
  self.payload["schema"] = self.instance.schema
267
259
  self.payload["solution"] = self.execution.data
268
260
  self.payload["solution_hash"] = self.execution.data_hash
269
- self.payload["user_id"] = self.user
261
+ self.payload["user_id"] = self.user.id
270
262
  self.payload["indicators"] = ""
271
263
 
272
264
  for key in self.response_items:
@@ -293,7 +285,7 @@ class TestCasesFromInstanceExecutionEndpoint(CustomTestCase):
293
285
  self.payload["data"] = self.instance.data
294
286
  self.payload["data_hash"] = self.instance.data_hash
295
287
  self.payload["schema"] = self.instance.schema
296
- self.payload["user_id"] = self.user
288
+ self.payload["user_id"] = self.user.id
297
289
  self.payload["solution"] = None
298
290
  self.payload["solution_hash"] = hash_json_256(None)
299
291
  self.payload["indicators"] = ""
@@ -488,10 +480,9 @@ class TestCaseCopyEndpoint(CustomTestCase):
488
480
  new_case = self.create_new_row(
489
481
  self.url + str(self.case_id) + "/copy/", self.model, {}, check_payload=False
490
482
  )
491
- user = UserModel.get_one_user(self.user)
492
483
 
493
- original_case = CaseModel.get_one_object(user=user, idx=self.case_id)
494
- new_case = CaseModel.get_one_object(user=user, idx=new_case["id"])
484
+ original_case = CaseModel.get_one_object(user=self.user, idx=self.case_id)
485
+ new_case = CaseModel.get_one_object(user=self.user, idx=new_case["id"])
495
486
 
496
487
  for key in self.copied_items:
497
488
  self.assertEqual(getattr(original_case, key), getattr(new_case, key))
@@ -33,6 +33,7 @@ from cornflow.models import (
33
33
  from cornflow.models import UserModel
34
34
  from cornflow.shared import db
35
35
  from cornflow.shared.exceptions import NoPermission, ObjectDoesNotExist
36
+ from cornflow.endpoints import resources, alarms_resources
36
37
 
37
38
 
38
39
  class CLITests(TestCase):
@@ -281,7 +282,7 @@ class CLITests(TestCase):
281
282
  result = runner.invoke(cli, ["views", "init", "-v"])
282
283
  self.assertEqual(result.exit_code, 0)
283
284
  views = ViewModel.get_all_objects().all()
284
- self.assertEqual(len(views), 49)
285
+ self.assertEqual(len(views), (len(resources) + len(alarms_resources)))
285
286
 
286
287
  def test_permissions_entrypoint(self):
287
288
  """
@@ -323,8 +324,8 @@ class CLITests(TestCase):
323
324
  permissions = PermissionViewRoleModel.get_all_objects().all()
324
325
  self.assertEqual(len(actions), 5)
325
326
  self.assertEqual(len(roles), 4)
326
- self.assertEqual(len(views), 49)
327
- self.assertEqual(len(permissions), 546)
327
+ self.assertEqual(len(views), (len(resources) + len(alarms_resources)))
328
+ self.assertEqual(len(permissions), 562)
328
329
 
329
330
  def test_permissions_base_command(self):
330
331
  """
@@ -348,8 +349,8 @@ class CLITests(TestCase):
348
349
  permissions = PermissionViewRoleModel.get_all_objects().all()
349
350
  self.assertEqual(len(actions), 5)
350
351
  self.assertEqual(len(roles), 4)
351
- self.assertEqual(len(views), 49)
352
- self.assertEqual(len(permissions), 546)
352
+ self.assertEqual(len(views), (len(resources) + len(alarms_resources)))
353
+ self.assertEqual(len(permissions), 562)
353
354
 
354
355
  def test_service_entrypoint(self):
355
356
  """
@@ -381,8 +381,8 @@ class TestCommands(TestCase):
381
381
  service_permissions = PermissionsDAG.get_user_dag_permissions(service.id)
382
382
  admin_permissions = PermissionsDAG.get_user_dag_permissions(admin.id)
383
383
 
384
- self.assertEqual(3, len(service_permissions))
385
- self.assertEqual(3, len(admin_permissions))
384
+ self.assertEqual(4, len(service_permissions))
385
+ self.assertEqual(4, len(admin_permissions))
386
386
 
387
387
  def test_dag_permissions_command_no_open(self):
388
388
  """
@@ -405,7 +405,7 @@ class TestCommands(TestCase):
405
405
  service_permissions = PermissionsDAG.get_user_dag_permissions(service.id)
406
406
  admin_permissions = PermissionsDAG.get_user_dag_permissions(admin.id)
407
407
 
408
- self.assertEqual(3, len(service_permissions))
408
+ self.assertEqual(4, len(service_permissions))
409
409
  self.assertEqual(0, len(admin_permissions))
410
410
 
411
411
  def test_argument_parsing_correct(self):