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.
- airflow_config/airflow_local_settings.py +1 -1
- cornflow/app.py +8 -3
- cornflow/cli/migrations.py +23 -3
- cornflow/cli/service.py +18 -18
- cornflow/cli/utils.py +16 -1
- cornflow/commands/dag.py +1 -1
- cornflow/config.py +13 -8
- cornflow/endpoints/__init__.py +8 -2
- cornflow/endpoints/alarms.py +66 -2
- cornflow/endpoints/data_check.py +53 -26
- cornflow/endpoints/execution.py +387 -132
- cornflow/endpoints/login.py +81 -63
- cornflow/endpoints/meta_resource.py +11 -3
- cornflow/migrations/versions/999b98e24225.py +34 -0
- cornflow/models/base_data_model.py +4 -32
- cornflow/models/execution.py +2 -3
- cornflow/models/meta_models.py +28 -22
- cornflow/models/user.py +7 -10
- cornflow/schemas/alarms.py +8 -0
- cornflow/schemas/execution.py +1 -1
- cornflow/schemas/query.py +2 -1
- cornflow/schemas/user.py +5 -20
- cornflow/shared/authentication/auth.py +201 -264
- cornflow/shared/const.py +3 -14
- cornflow/shared/databricks.py +5 -1
- cornflow/tests/const.py +1 -0
- cornflow/tests/custom_test_case.py +77 -26
- cornflow/tests/unit/test_actions.py +2 -2
- cornflow/tests/unit/test_alarms.py +55 -1
- cornflow/tests/unit/test_apiview.py +108 -3
- cornflow/tests/unit/test_cases.py +20 -29
- cornflow/tests/unit/test_cli.py +6 -5
- cornflow/tests/unit/test_commands.py +3 -3
- cornflow/tests/unit/test_dags.py +5 -6
- cornflow/tests/unit/test_executions.py +443 -123
- cornflow/tests/unit/test_instances.py +14 -2
- cornflow/tests/unit/test_instances_file.py +1 -1
- cornflow/tests/unit/test_licenses.py +1 -1
- cornflow/tests/unit/test_log_in.py +230 -207
- cornflow/tests/unit/test_permissions.py +8 -8
- cornflow/tests/unit/test_roles.py +48 -10
- cornflow/tests/unit/test_schemas.py +1 -1
- cornflow/tests/unit/test_tables.py +7 -7
- cornflow/tests/unit/test_token.py +19 -5
- cornflow/tests/unit/test_users.py +22 -6
- cornflow/tests/unit/tools.py +75 -10
- {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/METADATA +16 -15
- {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/RECORD +51 -51
- {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/WHEEL +1 -1
- cornflow/endpoints/execution_databricks.py +0 -808
- {cornflow-2.0.0a11.dist-info → cornflow-2.0.0a12.dist-info}/entry_points.txt +0 -0
- {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
|
cornflow/shared/databricks.py
CHANGED
@@ -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,
|
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
|
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
|
-
|
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
|
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
|
-
|
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 "
|
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
|
-
|
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 "
|
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
|
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.
|
1096
|
-
datetime.
|
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
|
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
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
124
|
-
|
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
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
231
|
+
user=self.user, idx=instance_id
|
240
232
|
)
|
241
233
|
self.execution = ExecutionModel.get_one_object(
|
242
|
-
user=self.
|
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))
|
cornflow/tests/unit/test_cli.py
CHANGED
@@ -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),
|
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),
|
327
|
-
self.assertEqual(len(permissions),
|
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),
|
352
|
-
self.assertEqual(len(permissions),
|
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(
|
385
|
-
self.assertEqual(
|
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(
|
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):
|