cornflow 1.2.3a3__py3-none-any.whl → 1.2.3a4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cornflow/app.py +1 -1
- cornflow/cli/roles.py +1 -1
- cornflow/cli/service.py +32 -4
- cornflow/commands/access.py +14 -3
- cornflow/commands/auxiliar.py +100 -0
- cornflow/commands/permissions.py +164 -102
- cornflow/commands/roles.py +15 -14
- cornflow/commands/views.py +171 -41
- cornflow/shared/authentication/auth.py +3 -3
- cornflow/shared/const.py +1 -1
- cornflow/tests/unit/test_commands.py +0 -193
- cornflow/tests/unit/test_external_role_creation.py +539 -0
- {cornflow-1.2.3a3.dist-info → cornflow-1.2.3a4.dist-info}/METADATA +2 -2
- {cornflow-1.2.3a3.dist-info → cornflow-1.2.3a4.dist-info}/RECORD +17 -15
- {cornflow-1.2.3a3.dist-info → cornflow-1.2.3a4.dist-info}/WHEEL +0 -0
- {cornflow-1.2.3a3.dist-info → cornflow-1.2.3a4.dist-info}/entry_points.txt +0 -0
- {cornflow-1.2.3a3.dist-info → cornflow-1.2.3a4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,539 @@
|
|
1
|
+
import unittest
|
2
|
+
import os
|
3
|
+
import json
|
4
|
+
from unittest.mock import patch, MagicMock
|
5
|
+
from cornflow.shared import db
|
6
|
+
from cornflow.tests.custom_test_case import CustomTestCase
|
7
|
+
from cornflow.models import ViewModel
|
8
|
+
from cornflow.commands.access import access_init_command
|
9
|
+
from cornflow.shared.const import (
|
10
|
+
VIEWER_ROLE,
|
11
|
+
PLANNER_ROLE,
|
12
|
+
POST_ACTION,
|
13
|
+
PATCH_ACTION,
|
14
|
+
DELETE_ACTION,
|
15
|
+
GET_ACTION,
|
16
|
+
)
|
17
|
+
|
18
|
+
|
19
|
+
class ExternalRoleCreationTestCase(CustomTestCase):
|
20
|
+
"""
|
21
|
+
Test cases for external app custom role creation and removal functionality
|
22
|
+
"""
|
23
|
+
|
24
|
+
def _load_expected_permissions(self, test_name):
|
25
|
+
"""Helper method to load expected permissions from JSON file"""
|
26
|
+
test_data_path = os.path.join(
|
27
|
+
os.path.dirname(__file__), "..", "data", "expected_permissions.json"
|
28
|
+
)
|
29
|
+
with open(test_data_path, "r") as f:
|
30
|
+
data = json.load(f)
|
31
|
+
return [
|
32
|
+
(role_id, action_id, endpoint_name)
|
33
|
+
for role_id, action_id, endpoint_name in data[test_name]
|
34
|
+
]
|
35
|
+
|
36
|
+
def _create_mock_external_app_resources(self):
|
37
|
+
"""Helper method to create mock external app resources"""
|
38
|
+
# Create mock endpoint classes for external app
|
39
|
+
mock_production_endpoint = MagicMock()
|
40
|
+
mock_production_endpoint.ROLES_WITH_ACCESS = [
|
41
|
+
888,
|
42
|
+
PLANNER_ROLE,
|
43
|
+
] # Custom role + standard role
|
44
|
+
mock_production_endpoint.DESCRIPTION = "Production planning endpoint"
|
45
|
+
|
46
|
+
mock_quality_endpoint = MagicMock()
|
47
|
+
mock_quality_endpoint.ROLES_WITH_ACCESS = [
|
48
|
+
777,
|
49
|
+
VIEWER_ROLE,
|
50
|
+
] # Custom role + standard role
|
51
|
+
mock_quality_endpoint.DESCRIPTION = "Quality control endpoint"
|
52
|
+
|
53
|
+
mock_scheduling_endpoint = MagicMock()
|
54
|
+
mock_scheduling_endpoint.ROLES_WITH_ACCESS = [
|
55
|
+
888,
|
56
|
+
777,
|
57
|
+
PLANNER_ROLE,
|
58
|
+
] # Multiple custom roles
|
59
|
+
mock_scheduling_endpoint.DESCRIPTION = "Scheduling optimizer endpoint"
|
60
|
+
|
61
|
+
# Create mock resources structure for external app endpoints
|
62
|
+
mock_resources = [
|
63
|
+
{
|
64
|
+
"endpoint": "production_planning", # External app endpoint
|
65
|
+
"urls": "/production-planning/",
|
66
|
+
"resource": mock_production_endpoint,
|
67
|
+
},
|
68
|
+
{
|
69
|
+
"endpoint": "quality_control", # External app endpoint
|
70
|
+
"urls": "/quality-control/",
|
71
|
+
"resource": mock_quality_endpoint,
|
72
|
+
},
|
73
|
+
{
|
74
|
+
"endpoint": "scheduling_optimizer", # External app endpoint
|
75
|
+
"urls": "/scheduling/",
|
76
|
+
"resource": mock_scheduling_endpoint,
|
77
|
+
},
|
78
|
+
]
|
79
|
+
|
80
|
+
return mock_resources
|
81
|
+
|
82
|
+
@patch("cornflow.commands.auxiliar.import_module")
|
83
|
+
@patch("cornflow.commands.views.import_module")
|
84
|
+
@patch.dict(
|
85
|
+
os.environ, {"EXTERNAL_APP": "1", "EXTERNAL_APP_MODULE": "external_test_app"}
|
86
|
+
)
|
87
|
+
def test_custom_role_creation_removal(
|
88
|
+
self, mock_import_views, mock_import_auxiliar
|
89
|
+
):
|
90
|
+
"""
|
91
|
+
Test that custom roles (like role 888) are properly created and removed
|
92
|
+
when external app is configured with EXTRA_PERMISSION_ASSIGNATION
|
93
|
+
"""
|
94
|
+
# Mock external app configuration
|
95
|
+
mock_external_app = MagicMock()
|
96
|
+
|
97
|
+
# Mock the shared.const module
|
98
|
+
mock_shared = MagicMock()
|
99
|
+
mock_const = MagicMock()
|
100
|
+
mock_const.EXTRA_PERMISSION_ASSIGNATION = [
|
101
|
+
# Try adding an existing permission and it works
|
102
|
+
(888, GET_ACTION, "production_planning"),
|
103
|
+
# Try adding additional permission
|
104
|
+
(888, POST_ACTION, "production_planning"),
|
105
|
+
(777, PATCH_ACTION, "quality_control"),
|
106
|
+
(VIEWER_ROLE, POST_ACTION, "scheduling_optimizer"),
|
107
|
+
(PLANNER_ROLE, DELETE_ACTION, "quality_control"),
|
108
|
+
]
|
109
|
+
mock_shared.const = mock_const
|
110
|
+
mock_external_app.shared = mock_shared
|
111
|
+
|
112
|
+
# Mock the endpoints.resources with fake external app endpoints
|
113
|
+
mock_endpoints = MagicMock()
|
114
|
+
mock_endpoints.resources = self._create_mock_external_app_resources()
|
115
|
+
mock_external_app.endpoints = mock_endpoints
|
116
|
+
|
117
|
+
mock_import_views.return_value = mock_external_app
|
118
|
+
mock_import_auxiliar.return_value = mock_external_app
|
119
|
+
|
120
|
+
# Run the permissions registration
|
121
|
+
from cornflow.commands.access import access_init_command
|
122
|
+
|
123
|
+
# Mock the database session for testing
|
124
|
+
with patch.object(db.session, "commit"):
|
125
|
+
with patch.object(db.session, "rollback"):
|
126
|
+
# Run the complete access initialization
|
127
|
+
access_init_command(verbose=True)
|
128
|
+
|
129
|
+
# Verify that custom permissions were created for the external roles
|
130
|
+
from cornflow.models import PermissionViewRoleModel
|
131
|
+
|
132
|
+
# Get all permissions for external app endpoints
|
133
|
+
all_permissions = PermissionViewRoleModel.query.all()
|
134
|
+
external_permissions = [
|
135
|
+
perm
|
136
|
+
for perm in all_permissions
|
137
|
+
if perm.api_view.name
|
138
|
+
in [
|
139
|
+
"production_planning",
|
140
|
+
"quality_control",
|
141
|
+
"scheduling_optimizer",
|
142
|
+
]
|
143
|
+
]
|
144
|
+
|
145
|
+
# Load expected permissions from JSON file
|
146
|
+
expected_permissions = self._load_expected_permissions(
|
147
|
+
"test_custom_role_creation_removal"
|
148
|
+
)
|
149
|
+
|
150
|
+
# Verify each expected permission exists
|
151
|
+
for role_id, action_id, endpoint_name in expected_permissions:
|
152
|
+
permission_exists = any(
|
153
|
+
p.role_id == role_id
|
154
|
+
and p.action_id == action_id
|
155
|
+
and p.api_view.name == endpoint_name
|
156
|
+
for p in external_permissions
|
157
|
+
)
|
158
|
+
self.assertTrue(
|
159
|
+
permission_exists,
|
160
|
+
f"Expected permission not found: role_id={role_id}, action_id={action_id}, endpoint={endpoint_name}",
|
161
|
+
)
|
162
|
+
|
163
|
+
# Verify we don't have unexpected permissions
|
164
|
+
actual_permission_tuples = {
|
165
|
+
(p.role_id, p.action_id, p.api_view.name)
|
166
|
+
for p in external_permissions
|
167
|
+
}
|
168
|
+
expected_permission_tuples = set(expected_permissions)
|
169
|
+
|
170
|
+
unexpected_permissions = (
|
171
|
+
actual_permission_tuples - expected_permission_tuples
|
172
|
+
)
|
173
|
+
self.assertEqual(
|
174
|
+
len(unexpected_permissions),
|
175
|
+
0,
|
176
|
+
f"Found unexpected permissions: {unexpected_permissions}",
|
177
|
+
)
|
178
|
+
|
179
|
+
missing_permissions = (
|
180
|
+
expected_permission_tuples - actual_permission_tuples
|
181
|
+
)
|
182
|
+
self.assertEqual(
|
183
|
+
len(missing_permissions),
|
184
|
+
0,
|
185
|
+
f"Missing expected permissions: {missing_permissions}",
|
186
|
+
)
|
187
|
+
|
188
|
+
@patch("cornflow.commands.auxiliar.import_module")
|
189
|
+
@patch("cornflow.commands.views.import_module")
|
190
|
+
@patch.dict(
|
191
|
+
os.environ, {"EXTERNAL_APP": "1", "EXTERNAL_APP_MODULE": "external_test_app"}
|
192
|
+
)
|
193
|
+
def test_role_removal_when_not_in_config(
|
194
|
+
self, mock_import_views, mock_import_auxiliar
|
195
|
+
):
|
196
|
+
"""
|
197
|
+
Test that roles are properly removed when they're no longer in EXTRA_PERMISSION_ASSIGNATION
|
198
|
+
"""
|
199
|
+
# First, create roles with permissions
|
200
|
+
mock_external_app = MagicMock()
|
201
|
+
|
202
|
+
# Mock the shared.const module
|
203
|
+
mock_shared = MagicMock()
|
204
|
+
mock_const = MagicMock()
|
205
|
+
mock_const.EXTRA_PERMISSION_ASSIGNATION = [
|
206
|
+
(10000, POST_ACTION, "production_planning"),
|
207
|
+
(999, PATCH_ACTION, "quality_control"),
|
208
|
+
]
|
209
|
+
mock_shared.const = mock_const
|
210
|
+
mock_external_app.shared = mock_shared
|
211
|
+
|
212
|
+
# Mock the endpoints.resources
|
213
|
+
mock_endpoints = MagicMock()
|
214
|
+
mock_endpoints.resources = self._create_mock_external_app_resources()
|
215
|
+
mock_external_app.endpoints = mock_endpoints
|
216
|
+
|
217
|
+
mock_import_views.return_value = mock_external_app
|
218
|
+
mock_import_auxiliar.return_value = mock_external_app
|
219
|
+
|
220
|
+
# Create initial roles
|
221
|
+
access_init_command(verbose=True)
|
222
|
+
|
223
|
+
# Now update config to remove role 777
|
224
|
+
mock_const.EXTRA_PERMISSION_ASSIGNATION = [
|
225
|
+
(10000, POST_ACTION, "production_planning"),
|
226
|
+
]
|
227
|
+
|
228
|
+
# Re-run permissions registration
|
229
|
+
access_init_command(verbose=True)
|
230
|
+
|
231
|
+
# Verify role 888 still has permissions but 777 does not
|
232
|
+
from cornflow.models import PermissionViewRoleModel
|
233
|
+
|
234
|
+
permissions_10000 = PermissionViewRoleModel.query.filter_by(role_id=10000).all()
|
235
|
+
permissions_999 = PermissionViewRoleModel.query.filter_by(role_id=999).all()
|
236
|
+
|
237
|
+
self.assertTrue(len(permissions_10000) > 0)
|
238
|
+
self.assertEqual(len(permissions_999), 0)
|
239
|
+
|
240
|
+
def test_fallback_when_no_external_config(self):
|
241
|
+
"""
|
242
|
+
Test that the system falls back gracefully when EXTRA_PERMISSION_ASSIGNATION is not available
|
243
|
+
"""
|
244
|
+
# Should not raise any exceptions
|
245
|
+
try:
|
246
|
+
access_init_command(verbose=True)
|
247
|
+
except Exception as e:
|
248
|
+
self.fail(f"access_init_command raised an exception: {e}")
|
249
|
+
|
250
|
+
@patch("cornflow.commands.auxiliar.import_module")
|
251
|
+
@patch("cornflow.commands.views.import_module")
|
252
|
+
@patch.dict(
|
253
|
+
os.environ, {"EXTERNAL_APP": "1", "EXTERNAL_APP_MODULE": "external_test_app"}
|
254
|
+
)
|
255
|
+
def test_external_app_missing_extra_permissions(
|
256
|
+
self, mock_import_views, mock_import_auxiliar
|
257
|
+
):
|
258
|
+
"""
|
259
|
+
Test graceful handling when external app doesn't have EXTRA_PERMISSION_ASSIGNATION
|
260
|
+
"""
|
261
|
+
# Mock external app without EXTRA_PERMISSION_ASSIGNATION
|
262
|
+
mock_external_app = MagicMock()
|
263
|
+
|
264
|
+
# Mock the shared module but without const.EXTRA_PERMISSION_ASSIGNATION
|
265
|
+
mock_shared = MagicMock()
|
266
|
+
mock_const = MagicMock()
|
267
|
+
# Don't set EXTRA_PERMISSION_ASSIGNATION to trigger AttributeError
|
268
|
+
del mock_const.EXTRA_PERMISSION_ASSIGNATION
|
269
|
+
mock_shared.const = mock_const
|
270
|
+
mock_external_app.shared = mock_shared
|
271
|
+
|
272
|
+
# Mock the endpoints.resources
|
273
|
+
mock_endpoints = MagicMock()
|
274
|
+
mock_endpoints.resources = self._create_mock_external_app_resources()
|
275
|
+
mock_external_app.endpoints = mock_endpoints
|
276
|
+
|
277
|
+
mock_import_views.return_value = mock_external_app
|
278
|
+
mock_import_auxiliar.return_value = mock_external_app
|
279
|
+
|
280
|
+
# Should not raise any exceptions, should fall back gracefully
|
281
|
+
try:
|
282
|
+
access_init_command(verbose=True)
|
283
|
+
except Exception as e:
|
284
|
+
self.fail(f"access_init_command raised an exception: {e}")
|
285
|
+
|
286
|
+
@patch("cornflow.commands.auxiliar.import_module")
|
287
|
+
@patch("cornflow.commands.views.import_module")
|
288
|
+
@patch.dict(
|
289
|
+
os.environ, {"EXTERNAL_APP": "1", "EXTERNAL_APP_MODULE": "external_test_app"}
|
290
|
+
)
|
291
|
+
def test_standard_role_extended_permissions(
|
292
|
+
self, mock_import_views, mock_import_auxiliar
|
293
|
+
):
|
294
|
+
"""
|
295
|
+
Test that standard roles (like VIEWER_ROLE) can get extended permissions from external app
|
296
|
+
"""
|
297
|
+
# Mock external app configuration with extended permissions for existing roles
|
298
|
+
mock_external_app = MagicMock()
|
299
|
+
|
300
|
+
# Mock the shared.const module
|
301
|
+
mock_shared = MagicMock()
|
302
|
+
mock_const = MagicMock()
|
303
|
+
mock_const.EXTRA_PERMISSION_ASSIGNATION = [
|
304
|
+
(VIEWER_ROLE, POST_ACTION, "production_planning"), # Extend standard role
|
305
|
+
(PLANNER_ROLE, DELETE_ACTION, "quality_control"), # Extend standard role
|
306
|
+
]
|
307
|
+
mock_shared.const = mock_const
|
308
|
+
mock_external_app.shared = mock_shared
|
309
|
+
|
310
|
+
# Mock the endpoints.resources
|
311
|
+
mock_endpoints = MagicMock()
|
312
|
+
mock_endpoints.resources = self._create_mock_external_app_resources()
|
313
|
+
mock_external_app.endpoints = mock_endpoints
|
314
|
+
|
315
|
+
mock_import_views.return_value = mock_external_app
|
316
|
+
mock_import_auxiliar.return_value = mock_external_app
|
317
|
+
|
318
|
+
# Mock the database session for testing
|
319
|
+
with patch.object(db.session, "commit"):
|
320
|
+
with patch.object(db.session, "rollback"):
|
321
|
+
# Run the complete access initialization
|
322
|
+
access_init_command(verbose=True)
|
323
|
+
|
324
|
+
# Verify that existing roles got the additional permissions
|
325
|
+
from cornflow.models import PermissionViewRoleModel
|
326
|
+
|
327
|
+
# Check that VIEWER_ROLE has additional permissions
|
328
|
+
viewer_permissions = PermissionViewRoleModel.query.filter_by(
|
329
|
+
role_id=VIEWER_ROLE
|
330
|
+
).all()
|
331
|
+
self.assertTrue(len(viewer_permissions) > 0)
|
332
|
+
|
333
|
+
# Check that PLANNER_ROLE has additional permissions
|
334
|
+
planner_permissions = PermissionViewRoleModel.query.filter_by(
|
335
|
+
role_id=PLANNER_ROLE
|
336
|
+
).all()
|
337
|
+
self.assertTrue(len(planner_permissions) > 0)
|
338
|
+
|
339
|
+
@patch("cornflow.commands.auxiliar.import_module")
|
340
|
+
@patch("cornflow.commands.views.import_module")
|
341
|
+
@patch.dict(
|
342
|
+
os.environ, {"EXTERNAL_APP": "1", "EXTERNAL_APP_MODULE": "external_test_app"}
|
343
|
+
)
|
344
|
+
def test_view_update_and_deletion(self, mock_import_views, mock_import_auxiliar):
|
345
|
+
"""
|
346
|
+
Test that views are updated when URLs change and deleted when resources are removed
|
347
|
+
"""
|
348
|
+
# === INITIAL SETUP ===
|
349
|
+
# Create initial mock external app with 3 endpoints
|
350
|
+
mock_external_app_initial = MagicMock()
|
351
|
+
|
352
|
+
# Initial mock endpoints
|
353
|
+
mock_production_endpoint = MagicMock()
|
354
|
+
mock_production_endpoint.ROLES_WITH_ACCESS = [888]
|
355
|
+
mock_production_endpoint.DESCRIPTION = "Production planning endpoint"
|
356
|
+
|
357
|
+
mock_quality_endpoint = MagicMock()
|
358
|
+
mock_quality_endpoint.ROLES_WITH_ACCESS = [777]
|
359
|
+
mock_quality_endpoint.DESCRIPTION = "Quality control endpoint"
|
360
|
+
|
361
|
+
mock_scheduling_endpoint = MagicMock()
|
362
|
+
mock_scheduling_endpoint.ROLES_WITH_ACCESS = [888, 777]
|
363
|
+
mock_scheduling_endpoint.DESCRIPTION = "Scheduling optimizer endpoint"
|
364
|
+
|
365
|
+
# Initial resources structure
|
366
|
+
initial_resources = [
|
367
|
+
{
|
368
|
+
"endpoint": "production_planning",
|
369
|
+
"urls": "/production-planning/",
|
370
|
+
"resource": mock_production_endpoint,
|
371
|
+
},
|
372
|
+
{
|
373
|
+
"endpoint": "quality_control",
|
374
|
+
"urls": "/quality-control/",
|
375
|
+
"resource": mock_quality_endpoint,
|
376
|
+
},
|
377
|
+
{
|
378
|
+
"endpoint": "scheduling_optimizer",
|
379
|
+
"urls": "/scheduling/",
|
380
|
+
"resource": mock_scheduling_endpoint,
|
381
|
+
},
|
382
|
+
]
|
383
|
+
|
384
|
+
# Mock the shared.const module (no extra permissions)
|
385
|
+
mock_shared_initial = MagicMock()
|
386
|
+
mock_const_initial = MagicMock()
|
387
|
+
mock_const_initial.EXTRA_PERMISSION_ASSIGNATION = []
|
388
|
+
mock_shared_initial.const = mock_const_initial
|
389
|
+
mock_external_app_initial.shared = mock_shared_initial
|
390
|
+
|
391
|
+
# Mock the endpoints.resources
|
392
|
+
mock_endpoints_initial = MagicMock()
|
393
|
+
mock_endpoints_initial.resources = initial_resources
|
394
|
+
mock_external_app_initial.endpoints = mock_endpoints_initial
|
395
|
+
|
396
|
+
mock_import_views.return_value = mock_external_app_initial
|
397
|
+
mock_import_auxiliar.return_value = mock_external_app_initial
|
398
|
+
|
399
|
+
# === INITIAL ACCESS INIT ===
|
400
|
+
|
401
|
+
# Mock the database session for testing - INITIAL SETUP ONLY
|
402
|
+
with patch.object(db.session, "commit"):
|
403
|
+
with patch.object(db.session, "rollback"):
|
404
|
+
# Run initial access initialization
|
405
|
+
access_init_command(verbose=True)
|
406
|
+
|
407
|
+
# Verify initial views were created
|
408
|
+
initial_views = ViewModel.query.filter(
|
409
|
+
ViewModel.name.in_(
|
410
|
+
[
|
411
|
+
"production_planning",
|
412
|
+
"quality_control",
|
413
|
+
"scheduling_optimizer",
|
414
|
+
]
|
415
|
+
)
|
416
|
+
).all()
|
417
|
+
|
418
|
+
self.assertEqual(
|
419
|
+
len(initial_views), 3, "Expected 3 initial views to be created"
|
420
|
+
)
|
421
|
+
|
422
|
+
# Get initial URLs
|
423
|
+
initial_views_dict = {
|
424
|
+
view.name: view.url_rule for view in initial_views
|
425
|
+
}
|
426
|
+
self.assertEqual(
|
427
|
+
initial_views_dict["production_planning"], "/production-planning/"
|
428
|
+
)
|
429
|
+
self.assertEqual(
|
430
|
+
initial_views_dict["quality_control"], "/quality-control/"
|
431
|
+
)
|
432
|
+
self.assertEqual(
|
433
|
+
initial_views_dict["scheduling_optimizer"], "/scheduling/"
|
434
|
+
)
|
435
|
+
|
436
|
+
# === MODIFY CONFIGURATION ===
|
437
|
+
# Create updated mock external app with:
|
438
|
+
# 1. Changed URL for production_planning
|
439
|
+
# 2. Changed URL for quality_control
|
440
|
+
# 3. Remove scheduling_optimizer entirely
|
441
|
+
mock_external_app_updated = MagicMock()
|
442
|
+
|
443
|
+
# Updated resources structure (scheduling_optimizer removed, URLs changed)
|
444
|
+
updated_resources = [
|
445
|
+
{
|
446
|
+
"endpoint": "production_planning",
|
447
|
+
"urls": "/new-production-planning/", # CHANGED URL
|
448
|
+
"resource": mock_production_endpoint,
|
449
|
+
},
|
450
|
+
{
|
451
|
+
"endpoint": "quality_control",
|
452
|
+
"urls": "/quality-control/",
|
453
|
+
"resource": mock_quality_endpoint,
|
454
|
+
},
|
455
|
+
# scheduling_optimizer REMOVED entirely
|
456
|
+
]
|
457
|
+
|
458
|
+
# Mock shared const (still no extra permissions)
|
459
|
+
mock_shared_updated = MagicMock()
|
460
|
+
mock_const_updated = MagicMock()
|
461
|
+
mock_const_updated.EXTRA_PERMISSION_ASSIGNATION = [] # Empty list
|
462
|
+
mock_shared_updated.const = mock_const_updated
|
463
|
+
mock_external_app_updated.shared = mock_shared_updated
|
464
|
+
|
465
|
+
# Mock updated endpoints.resources
|
466
|
+
mock_endpoints_updated = MagicMock()
|
467
|
+
mock_endpoints_updated.resources = updated_resources
|
468
|
+
mock_external_app_updated.endpoints = mock_endpoints_updated
|
469
|
+
|
470
|
+
# Update mocks to return updated configuration
|
471
|
+
mock_import_views.return_value = mock_external_app_updated
|
472
|
+
mock_import_auxiliar.return_value = mock_external_app_updated
|
473
|
+
|
474
|
+
# === SECOND ACCESS INIT (with updated config) ===
|
475
|
+
# Allow real commits for the update to be persisted
|
476
|
+
access_init_command(verbose=True)
|
477
|
+
|
478
|
+
# === VERIFY UPDATES ===
|
479
|
+
# Check that URLs were updated
|
480
|
+
updated_views = ViewModel.query.filter(
|
481
|
+
ViewModel.name.in_(["production_planning", "quality_control"])
|
482
|
+
).all()
|
483
|
+
|
484
|
+
self.assertEqual(
|
485
|
+
len(updated_views), 2, "Expected 2 views to remain after update"
|
486
|
+
)
|
487
|
+
|
488
|
+
updated_views_dict = {view.name: view.url_rule for view in updated_views}
|
489
|
+
self.assertEqual(
|
490
|
+
updated_views_dict["production_planning"],
|
491
|
+
"/new-production-planning/",
|
492
|
+
"production_planning URL should be updated",
|
493
|
+
)
|
494
|
+
self.assertEqual(
|
495
|
+
updated_views_dict["quality_control"],
|
496
|
+
"/quality-control/",
|
497
|
+
"quality_control URL should be updated",
|
498
|
+
)
|
499
|
+
|
500
|
+
# === VERIFY DELETION ===
|
501
|
+
# Check that scheduling_optimizer view was deleted
|
502
|
+
deleted_view = ViewModel.query.filter_by(name="scheduling_optimizer").first()
|
503
|
+
self.assertIsNone(deleted_view, "scheduling_optimizer view should be deleted")
|
504
|
+
|
505
|
+
# === VERIFY PERMISSIONS ARE CLEANED UP ===
|
506
|
+
from cornflow.models import PermissionViewRoleModel
|
507
|
+
|
508
|
+
# Check that permissions for deleted view are cleaned up
|
509
|
+
remaining_permissions = PermissionViewRoleModel.query.all()
|
510
|
+
scheduling_permissions = [
|
511
|
+
perm
|
512
|
+
for perm in remaining_permissions
|
513
|
+
if perm.api_view and perm.api_view.name == "scheduling_optimizer"
|
514
|
+
]
|
515
|
+
self.assertEqual(
|
516
|
+
len(scheduling_permissions),
|
517
|
+
0,
|
518
|
+
"All permissions for deleted scheduling_optimizer view should be removed",
|
519
|
+
)
|
520
|
+
|
521
|
+
# Check that permissions for remaining views still exist
|
522
|
+
remaining_external_permissions = [
|
523
|
+
perm
|
524
|
+
for perm in remaining_permissions
|
525
|
+
if perm.api_view
|
526
|
+
and perm.api_view.name in ["production_planning", "quality_control"]
|
527
|
+
]
|
528
|
+
|
529
|
+
all_view_names = [
|
530
|
+
perm.api_view.name for perm in remaining_permissions if perm.api_view
|
531
|
+
]
|
532
|
+
self.assertTrue(
|
533
|
+
len(remaining_external_permissions) > 0,
|
534
|
+
f"Permissions for remaining views should still exist. Found views: {set(all_view_names)}",
|
535
|
+
)
|
536
|
+
|
537
|
+
|
538
|
+
if __name__ == "__main__":
|
539
|
+
unittest.main()
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cornflow
|
3
|
-
Version: 1.2.
|
3
|
+
Version: 1.2.3a4
|
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.
|
17
|
+
Requires-Dist: cornflow-client>=1.2.3.a4
|
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
|
@@ -5,7 +5,7 @@ airflow_config/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3
|
|
5
5
|
airflow_config/plugins/XCom/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
6
|
airflow_config/plugins/XCom/gce_xcom_backend.py,sha256=vCGvF2jbfZt5bOv-pk5Q_kUR6LomFUojIymimSJmj3o,1795
|
7
7
|
cornflow/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
cornflow/app.py,sha256=
|
8
|
+
cornflow/app.py,sha256=5ebZuYTfUFKVQ-lxcTeS0hATPGn3zacYdQ7jkApwmrE,7698
|
9
9
|
cornflow/config.py,sha256=c3CNu6wzm5mDJLF6GjrnBKQSNKsRr69S2vQ9jCSAhYw,5628
|
10
10
|
cornflow/gunicorn.py,sha256=uO-Yk7w7nvQSWh12iDxsVvlG-_2BiKIIjm2UiTk4P9E,480
|
11
11
|
cornflow/cli/__init__.py,sha256=2QfFLxLcX5zYt3Ok3QKNWQvUvAeEnLsH7xiGN3GjwFU,853
|
@@ -14,9 +14,9 @@ cornflow/cli/arguments.py,sha256=9EEyyny5cJJ1t3WAs6zMgTDvTre0JdQ2N_oZfFQmixs,657
|
|
14
14
|
cornflow/cli/config.py,sha256=AWWoLj3AIbKISIJ58Q89OdC_Ocjs_TPLCMF2jTEb5Sw,1258
|
15
15
|
cornflow/cli/migrations.py,sha256=nJbmulqwFxuaSPmo9hJiTl1pUSojeXfAdd9aGM_g9ic,2426
|
16
16
|
cornflow/cli/permissions.py,sha256=fyIp5M0ZpJWVf_tGZG0DN0Gnk1geruadkdKK11cmRk8,1116
|
17
|
-
cornflow/cli/roles.py,sha256=
|
17
|
+
cornflow/cli/roles.py,sha256=1dvAq1Bp1iEAUUfXFeL8kYjI0ZchYlDwIBQpTtwV6vs,529
|
18
18
|
cornflow/cli/schemas.py,sha256=s9IUJWa2G0kpqJaN6PcwbwZtGChTaqq451QqWEyWPBI,6197
|
19
|
-
cornflow/cli/service.py,sha256=
|
19
|
+
cornflow/cli/service.py,sha256=iAV-RvKpCoUg2Nr4BqatTD6he_2THWuhQdJrUoFlqaI,13303
|
20
20
|
cornflow/cli/users.py,sha256=VBUqOrS80qdp9E4XLn4ihocHVNWVhWSt19tp0o8mZII,2324
|
21
21
|
cornflow/cli/utils.py,sha256=p54xJEnYWda6rqSQDoZU2qZrFu9kTs4FoF0y3pJLQvI,1377
|
22
22
|
cornflow/cli/views.py,sha256=BjJRIQ5T3C8zl3-pVVkToVjQ6xdAGwnuT9LVEnn_jqM,746
|
@@ -28,15 +28,16 @@ cornflow/cli/tools/schema_generator.py,sha256=BQxfB4RvA010BjrXZ4D33AvWU9e7jfZ_m2
|
|
28
28
|
cornflow/cli/tools/schemas_tools.py,sha256=cTAM4_-0uu7dNlv95Oo2edM5qWEnfNKzIt_EhP6qx4c,2232
|
29
29
|
cornflow/cli/tools/tools.py,sha256=Qm0X-wHN12vXYJNRHONGjqDZewwXyXy4R_j4UT_XMLs,929
|
30
30
|
cornflow/commands/__init__.py,sha256=_7mi2Sd8bnaSujU-L78z8Zrswz68NJ2xoocYpsEYmPM,544
|
31
|
-
cornflow/commands/access.py,sha256=
|
31
|
+
cornflow/commands/access.py,sha256=4as74PKwcq5VoPnT9Hh2uUEDQLOQo6jAelwmeBIPW3c,1013
|
32
32
|
cornflow/commands/actions.py,sha256=4AwgAmyI6VeaugkISvTlNGrIzMMU_-ZB3MhwDD_CIEA,1544
|
33
|
+
cornflow/commands/auxiliar.py,sha256=s1p5K-v5lBG3wivB8snl_32aiRQbgXIUmbSjKMs8Cm4,3436
|
33
34
|
cornflow/commands/cleanup.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
34
35
|
cornflow/commands/dag.py,sha256=AtagFGnB_ucfO0qUawDgd4iRoBCVc-RiOs08DqXSwXM,3786
|
35
|
-
cornflow/commands/permissions.py,sha256=
|
36
|
-
cornflow/commands/roles.py,sha256=
|
36
|
+
cornflow/commands/permissions.py,sha256=8olHdnEGAP4gSm4ati675NNkZZWEUg5P5HcJ8Ft-B2w,9799
|
37
|
+
cornflow/commands/roles.py,sha256=c-sPPz_3Zb3c7AekXhr7gE264I_BK6LpR2wSa80Eg7M,1602
|
37
38
|
cornflow/commands/schemas.py,sha256=40dZSJ2nEqBa7Crb6DbFmnclT5e8ljAIjscOgHr9lhk,1970
|
38
39
|
cornflow/commands/users.py,sha256=2YTbNYY5kZL6ujxGP4fyYgqtv5uuVGdkLR31n7OFFaE,2477
|
39
|
-
cornflow/commands/views.py,sha256=
|
40
|
+
cornflow/commands/views.py,sha256=yg3vuKoJwusBWm0v9PCGYr1xAbL-dWAi4aDV0aGpMnA,6978
|
40
41
|
cornflow/endpoints/__init__.py,sha256=ZlwhY8MiynQ0BdATkrsikGM2Kqo4DPxkVTc3faNfzRY,7492
|
41
42
|
cornflow/endpoints/action.py,sha256=ksHK3F919cjkONLcFV2tUIbG-eZw5XbYkqVjYx9iq5I,1359
|
42
43
|
cornflow/endpoints/alarms.py,sha256=M-fpRAm5ZgYyZXvcgS0NHkeMGnvcbfQh2O5qQJUaeoM,4372
|
@@ -119,7 +120,7 @@ cornflow/schemas/user_role.py,sha256=e5y6RgdZZtLqD-h2B3sa5WokI5-pT78tWw85IG34I74
|
|
119
120
|
cornflow/schemas/view.py,sha256=ctq9Y1TmjrWdyOqgDYeEx7qbbuNLKfSiNOlFTlXmpaw,429
|
120
121
|
cornflow/shared/__init__.py,sha256=1ahcBwWOsSjGI4FEm77JBQjitBdBszOncKcEMjzwGYE,29
|
121
122
|
cornflow/shared/compress.py,sha256=pohQaGs1xbH8CN6URIH6BAHA--pFq7Hmjz8oI3c3B5c,1347
|
122
|
-
cornflow/shared/const.py,sha256=
|
123
|
+
cornflow/shared/const.py,sha256=Oq2WnV-tHlU2EFfxSEMIhd6Ihsf_OuVbdU1E4DTps3Q,3556
|
123
124
|
cornflow/shared/email.py,sha256=QNDDMv86LZObkevSCyUbLQeR2UD3zWScPIr82NDzYHQ,3437
|
124
125
|
cornflow/shared/exceptions.py,sha256=E82488IiwTXCv8iwrnGvkTonhJcwbeE5ARO4Zsmhl2c,6966
|
125
126
|
cornflow/shared/licenses.py,sha256=Lc71Jw2NxVTFWtoXdQ9wJX_o3BDfYg1xVoehDXvnCkQ,1328
|
@@ -129,7 +130,7 @@ cornflow/shared/utils.py,sha256=g2ZsD3SwsqIHXZ7GWVAVB0F9gX7mT9dQkPgR2Ahsh6M,860
|
|
129
130
|
cornflow/shared/utils_tables.py,sha256=JnyaQ-5d1alj230nir-6elC2i0QX-u-_32SAs2eDtC0,3298
|
130
131
|
cornflow/shared/validators.py,sha256=Iy2jaGzS9ojqlTE_-vKH4oQKD89GN5C0EPEZMg6UZgc,4761
|
131
132
|
cornflow/shared/authentication/__init__.py,sha256=cJIChk5X6hbA_16usEvfHr8g4JDFI6WKo0GPVOOiYHA,137
|
132
|
-
cornflow/shared/authentication/auth.py,sha256=
|
133
|
+
cornflow/shared/authentication/auth.py,sha256=SiXMCHwyAXHL7Krd5Ubht16NPNXqSvf-85xyiEB2DK4,18247
|
133
134
|
cornflow/shared/authentication/decorators.py,sha256=_QpwOU1kYzpaK85Dl0Btdj5hG8Ps47PFgySp_gqhlgk,1276
|
134
135
|
cornflow/shared/authentication/ldap.py,sha256=QfdC2X_ZMcIJabKC5pYWDGMhS5pIOJJvdZXuuiruq-M,4853
|
135
136
|
cornflow/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -148,11 +149,12 @@ cornflow/tests/unit/test_apiview.py,sha256=03M1GsQRVK7zqmslhOJXr4lLDLY2gMAgg86nk
|
|
148
149
|
cornflow/tests/unit/test_application.py,sha256=ZVmTQDUOkPRxHqt6mWU9G_lQ3jJNMJR0cx7IkLMFGrU,1715
|
149
150
|
cornflow/tests/unit/test_cases.py,sha256=Ez9dxlZL-SUf9DW9b_A_qPowHqUZ-TA73DMOzeBeLIU,37718
|
150
151
|
cornflow/tests/unit/test_cli.py,sha256=E2w-Lzgx_k__0mYwlbg2z80_z9nwPZKI0CbgyGmpQRY,18775
|
151
|
-
cornflow/tests/unit/test_commands.py,sha256=
|
152
|
+
cornflow/tests/unit/test_commands.py,sha256=kvO8Vn60rj3WBG2oXw7NpDSEYoGLNe806txbJPhtNJo,14722
|
152
153
|
cornflow/tests/unit/test_dags.py,sha256=XsOi5bBJQdQz3DmYAVJf1myoAsRyBBdmku-xBr0Bku0,13386
|
153
154
|
cornflow/tests/unit/test_data_checks.py,sha256=6s50d1iuRTUcAYn14oEcRS39ZZ6E9ussU4YpkpYhtC4,8612
|
154
155
|
cornflow/tests/unit/test_example_data.py,sha256=D-Tgnqw7NZlnBXaDcUU0reNhAca5JlJP2Sdn3KdS4Sw,4127
|
155
156
|
cornflow/tests/unit/test_executions.py,sha256=fC9kMRqU3qMGQ9eH6WXlOkU8CKJe6l-DlAKln4ImCSU,18192
|
157
|
+
cornflow/tests/unit/test_external_role_creation.py,sha256=pVkEUTYxc_qt9weo_EKE-_AOnT2v8Z1mkSUOFsVDliI,21172
|
156
158
|
cornflow/tests/unit/test_generate_from_schema.py,sha256=L1EdnASbDJ8SjrX1V4WnUKKwV0sRTwVnNYnxSpyeSeQ,15376
|
157
159
|
cornflow/tests/unit/test_health.py,sha256=xT0OcnpxbxE0QJlyg4r1qCYDn-hwuk0Ynw5JA4cJtsY,1205
|
158
160
|
cornflow/tests/unit/test_instances.py,sha256=Mf9jijQOcDE3ylPfMTnVRocRegcugEdCnoMCqSmKKqQ,11083
|
@@ -169,8 +171,8 @@ cornflow/tests/unit/test_tables.py,sha256=SW_K8LRLwR1nB0uH8CPQCjeN8Gei-TasAgkOin
|
|
169
171
|
cornflow/tests/unit/test_token.py,sha256=PZ11b46UCQpCESsRiAPhpgWkGAsAwKCVNxVQai_kxXM,4199
|
170
172
|
cornflow/tests/unit/test_users.py,sha256=N5tcF5nSncD0F_ZlBxGuS87p6kNS4hUzRLr3_AcnK-o,22802
|
171
173
|
cornflow/tests/unit/tools.py,sha256=ag3sWv2WLi498R1GL5AOUnXqSsszD3UugzLZLC5NqAw,585
|
172
|
-
cornflow-1.2.
|
173
|
-
cornflow-1.2.
|
174
|
-
cornflow-1.2.
|
175
|
-
cornflow-1.2.
|
176
|
-
cornflow-1.2.
|
174
|
+
cornflow-1.2.3a4.dist-info/METADATA,sha256=m7K67ojro9FvxpT3XWKYd2NQSTkkGIqhNB-UhU_Oq_k,9532
|
175
|
+
cornflow-1.2.3a4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
176
|
+
cornflow-1.2.3a4.dist-info/entry_points.txt,sha256=q9cPKAFBsmHkERCqQ2JcOTM-tVBLHTl-DGxwCXowAWM,46
|
177
|
+
cornflow-1.2.3a4.dist-info/top_level.txt,sha256=Qj9kLFJW1PLb-ZV2s_aCkQ-Wi5W6KC6fFR-LTBrx-rU,24
|
178
|
+
cornflow-1.2.3a4.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|