Skip to content

Commit 3af4d21

Browse files
Adapt activities
1 parent 98b0836 commit 3af4d21

File tree

7 files changed

+162
-12
lines changed

7 files changed

+162
-12
lines changed

app/db/utils.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,10 @@ def load_db_model_from_pydantic[I: DeclarativeBase](
7676
created_by_id: uuid.UUID | None,
7777
updated_by_id: uuid.UUID | None,
7878
ignore_attributes: set[str] | None = None,
79+
*,
80+
exclude_defaults: bool = False,
7981
) -> I:
80-
data = json_model.model_dump(by_alias=True)
82+
data = json_model.model_dump(by_alias=True, exclude_defaults=exclude_defaults)
8183

8284
if created_by_id or updated_by_id:
8385
data["created_by_id"] = created_by_id

app/queries/common.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
from app.schemas.activity import ActivityCreate, ActivityUpdate
3232
from app.schemas.auth import UserContext, UserContextWithProjectId, UserProfile
3333
from app.schemas.types import ListResponse, PaginationResponse
34+
from app.schemas.utils import NOT_SET
3435
from app.utils.uuid import create_uuid
3536

3637

@@ -431,14 +432,17 @@ def router_update_activity_one[T: BaseModel, I: Activity](
431432
update_data = json_model.model_dump(
432433
exclude_unset=True,
433434
exclude_none=True,
434-
exclude_defaults=True,
435435
exclude={"used_ids", "generated_ids"},
436+
exclude_defaults=True, # ignore NOT_SET default values
436437
)
437438

438439
for key, value in update_data.items():
439440
setattr(obj, key, value)
440441

441-
if generated_ids := json_model.generated_ids:
442+
# ignore NOT_SET values
443+
generated_ids = json_model.generated_ids if json_model.generated_ids != NOT_SET else []
444+
445+
if generated_ids:
442446
if obj.generated:
443447
raise HTTPException(
444448
status_code=404,

app/schemas/activity.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import uuid
22
from datetime import datetime
3+
from typing import Literal
34

45
from pydantic import BaseModel, ConfigDict
56

@@ -12,6 +13,7 @@
1213
IdentifiableMixin,
1314
)
1415
from app.schemas.entity import NestedEntityRead
16+
from app.schemas.utils import NOT_SET
1517

1618

1719
class ActivityBase(BaseModel):
@@ -34,6 +36,7 @@ class ActivityCreate(ActivityBase, AuthorizationOptionalPublicMixin):
3436
generated_ids: list[uuid.UUID] = []
3537

3638

37-
class ActivityUpdate(ActivityBase, AuthorizationOptionalPublicMixin):
38-
end_time: datetime | None = None
39-
generated_ids: list[uuid.UUID] | None = None
39+
class ActivityUpdate(ActivityBase):
40+
start_time: datetime | Literal[NOT_SET] | None = NOT_SET
41+
end_time: datetime | Literal[NOT_SET] | None = NOT_SET
42+
generated_ids: list[uuid.UUID] | Literal[NOT_SET] | None = NOT_SET

tests/test_calibration.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
DateTimeAdapter = TypeAdapter(datetime)
2020

2121
ROUTE = "calibration"
22+
ADMIN_ROUTE = "/admin/calibration"
2223
MODEL = Validation
2324
TYPE = str(ActivityType.calibration)
2425

@@ -319,7 +320,7 @@ def _is_deleted(db, model_id):
319320
return db.get(MODEL, model_id) is None
320321

321322

322-
def test_update_one(client, root_circuit, simulation_result, create_id):
323+
def test_update_one(client, client_admin, root_circuit, simulation_result, create_id):
323324
gen1 = create_id(
324325
used_ids=[str(root_circuit.id)],
325326
generated_ids=[],
@@ -337,6 +338,39 @@ def test_update_one(client, root_circuit, simulation_result, create_id):
337338
assert len(data["generated"]) == 1
338339
assert data["generated"][0]["id"] == str(simulation_result.id)
339340

341+
# only admin client can hit admin endpoint
342+
data = assert_request(
343+
client.patch,
344+
url=f"{ADMIN_ROUTE}/{gen1}",
345+
json={
346+
"end_time": str(end_time),
347+
},
348+
expected_status_code=403,
349+
).json()
350+
assert data["error_code"] == "NOT_AUTHORIZED"
351+
assert data["message"] == "Service admin role required"
352+
353+
data = assert_request(
354+
client_admin.patch,
355+
url=f"{ADMIN_ROUTE}/{gen1}",
356+
json={
357+
"end_time": str(end_time),
358+
},
359+
).json()
360+
361+
assert DateTimeAdapter.validate_python(data["end_time"]) == end_time
362+
363+
# admin is treated as regular user for regular route (no project context)
364+
data = assert_request(
365+
client_admin.patch,
366+
url=f"{ROUTE}/{gen1}",
367+
json={
368+
"end_time": str(end_time),
369+
},
370+
expected_status_code=403,
371+
).json()
372+
assert data["error_code"] == "NOT_AUTHORIZED"
373+
340374

341375
def test_update_one__fail_if_generated_ids_unauthorized(
342376
client_user_1, client_user_2, json_data, species_id, brain_region_id

tests/test_simulation_execution.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
DateTimeAdapter = TypeAdapter(datetime)
2020

2121
ROUTE = "simulation-execution"
22+
ADMIN_ROUTE = "/admin/simulation-execution"
2223
MODEL = SimulationExecution
2324

2425

@@ -318,7 +319,7 @@ def _is_deleted(db, model_id):
318319
return db.get(MODEL, model_id) is None
319320

320321

321-
def test_update_one(client, root_circuit, simulation_result, create_id):
322+
def test_update_one(client, client_admin, root_circuit, simulation_result, create_id):
322323
gen1 = create_id(
323324
used_ids=[str(root_circuit.id)],
324325
generated_ids=[],
@@ -336,6 +337,42 @@ def test_update_one(client, root_circuit, simulation_result, create_id):
336337
assert len(data["generated"]) == 1
337338
assert data["generated"][0]["id"] == str(simulation_result.id)
338339

340+
gen2 = create_id(
341+
used_ids=[str(root_circuit.id)],
342+
generated_ids=[],
343+
)
344+
345+
# only admin client can hit admin endpoint
346+
data = assert_request(
347+
client.patch,
348+
url=f"{ADMIN_ROUTE}/{gen2}",
349+
json=update_json,
350+
expected_status_code=403,
351+
).json()
352+
assert data["error_code"] == "NOT_AUTHORIZED"
353+
assert data["message"] == "Service admin role required"
354+
355+
data = assert_request(
356+
client_admin.patch,
357+
url=f"{ADMIN_ROUTE}/{gen2}",
358+
json=update_json,
359+
).json()
360+
361+
assert DateTimeAdapter.validate_python(data["end_time"]) == end_time
362+
assert len(data["generated"]) == 1
363+
assert data["generated"][0]["id"] == str(simulation_result.id)
364+
365+
# admin is treated as regular user for regular route (no authorized project ids)
366+
data = assert_request(
367+
client_admin.patch,
368+
url=f"{ROUTE}/{gen2}",
369+
json={
370+
"end_time": str(end_time),
371+
},
372+
expected_status_code=403,
373+
).json()
374+
assert data["error_code"] == "NOT_AUTHORIZED"
375+
339376

340377
def test_update_one__fail_if_generated_ids_unauthorized(
341378
client_user_1, client_user_2, json_data, species_id, brain_region_id

tests/test_simulation_generation.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
DateTimeAdapter = TypeAdapter(datetime)
2020

2121
ROUTE = "simulation-generation"
22+
ADMIN_ROUTE = "/admin/simulation-generation"
2223
MODEL = SimulationGeneration
2324

2425

@@ -297,7 +298,7 @@ def _is_deleted(db, model_id):
297298
return db.get(MODEL, model_id) is None
298299

299300

300-
def test_update_one(client, root_circuit, simulation_result, create_id):
301+
def test_update_one(client, client_admin, root_circuit, simulation_result, create_id):
301302
gen1 = create_id(
302303
used_ids=[str(root_circuit.id)],
303304
generated_ids=[],
@@ -315,6 +316,40 @@ def test_update_one(client, root_circuit, simulation_result, create_id):
315316
assert len(data["generated"]) == 1
316317
assert data["generated"][0]["id"] == str(simulation_result.id)
317318

319+
gen2 = create_id(
320+
used_ids=[str(root_circuit.id)],
321+
generated_ids=[],
322+
)
323+
324+
# only admin client can hit admin endpoint
325+
data = assert_request(
326+
client.patch,
327+
url=f"{ADMIN_ROUTE}/{gen2}",
328+
json=update_json,
329+
expected_status_code=403,
330+
).json()
331+
assert data["error_code"] == "NOT_AUTHORIZED"
332+
assert data["message"] == "Service admin role required"
333+
334+
data = assert_request(
335+
client_admin.patch,
336+
url=f"{ADMIN_ROUTE}/{gen2}",
337+
json=update_json,
338+
).json()
339+
340+
assert DateTimeAdapter.validate_python(data["end_time"]) == end_time
341+
assert len(data["generated"]) == 1
342+
assert data["generated"][0]["id"] == str(simulation_result.id)
343+
344+
# admin is treated as regular user for regular route (no project context)
345+
data = assert_request(
346+
client_admin.patch,
347+
url=f"{ROUTE}/{gen2}",
348+
json=update_json,
349+
expected_status_code=403,
350+
).json()
351+
assert data["error_code"] == "NOT_AUTHORIZED"
352+
318353

319354
def test_update_one__fail_if_generated_ids_unauthorized(
320355
client_user_1, client_user_2, json_data, species_id, brain_region_id

tests/test_validation.py

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
DateTimeAdapter = TypeAdapter(datetime)
2020

2121
ROUTE = "validation"
22+
ADMIN_ROUTE = "/admin/validation"
2223
MODEL = Validation
2324
TYPE = str(ActivityType.validation)
2425

@@ -319,8 +320,8 @@ def _is_deleted(db, model_id):
319320
return db.get(MODEL, model_id) is None
320321

321322

322-
def test_update_one(client, root_circuit, simulation_result, create_id):
323-
gen1 = create_id(
323+
def test_update_one(client, client_admin, root_circuit, simulation_result, create_id):
324+
entity_id = create_id(
324325
used_ids=[str(root_circuit.id)],
325326
generated_ids=[],
326327
)
@@ -332,11 +333,45 @@ def test_update_one(client, root_circuit, simulation_result, create_id):
332333
"generated_ids": [str(simulation_result.id)],
333334
}
334335

335-
data = assert_request(client.patch, url=f"{ROUTE}/{gen1}", json=update_json).json()
336+
data = assert_request(client.patch, url=f"{ROUTE}/{entity_id}", json=update_json).json()
337+
assert DateTimeAdapter.validate_python(data["end_time"]) == end_time
338+
assert len(data["generated"]) == 1
339+
assert data["generated"][0]["id"] == str(simulation_result.id)
340+
341+
# only admin client can hit admin endpoint
342+
data = assert_request(
343+
client.patch,
344+
url=f"{ADMIN_ROUTE}/{entity_id}",
345+
json=update_json,
346+
expected_status_code=403,
347+
).json()
348+
assert data["error_code"] == "NOT_AUTHORIZED"
349+
assert data["message"] == "Service admin role required"
350+
351+
entity_id = create_id(
352+
used_ids=[str(root_circuit.id)],
353+
generated_ids=[],
354+
)
355+
356+
data = assert_request(
357+
client_admin.patch,
358+
url=f"{ADMIN_ROUTE}/{entity_id}",
359+
json=update_json,
360+
).json()
361+
336362
assert DateTimeAdapter.validate_python(data["end_time"]) == end_time
337363
assert len(data["generated"]) == 1
338364
assert data["generated"][0]["id"] == str(simulation_result.id)
339365

366+
# admin is treated as regular user for regular route (no project context)
367+
data = assert_request(
368+
client_admin.patch,
369+
url=f"{ROUTE}/{entity_id}",
370+
json=update_json,
371+
expected_status_code=403,
372+
).json()
373+
assert data["error_code"] == "NOT_AUTHORIZED"
374+
340375

341376
def test_update_one__fail_if_generated_ids_unauthorized(
342377
client_user_1, client_user_2, json_data, species_id, brain_region_id

0 commit comments

Comments
 (0)