Skip to content

Commit

Permalink
test(delete-apis): not allow deletion for areas, links and thermals w…
Browse files Browse the repository at this point in the history
…hen referenced in a bc
  • Loading branch information
mabw-rte committed Jun 18, 2024
1 parent 92a558d commit 67e0b0b
Show file tree
Hide file tree
Showing 4 changed files with 287 additions and 27 deletions.
5 changes: 3 additions & 2 deletions antarest/study/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -2544,12 +2544,13 @@ def get_matrix_with_index_and_header(

return df_matrix

def assert_no_cluster_referenced_in_bcs(self, study: Study, cluster_ids: t.Sequence[str]) -> None:
def assert_no_cluster_referenced_in_bcs(self, study: Study, area_id: str, cluster_ids: t.Sequence[str]) -> None:
"""
Check that no cluster is referenced in a binding constraint otherwise raise an ClusterDeletionNotAllowed Exception
Args:
study: input study
area_id: area ID to be checked
cluster_ids: cluster IDs to be checked
Returns:
Expand All @@ -2558,7 +2559,7 @@ def assert_no_cluster_referenced_in_bcs(self, study: Study, cluster_ids: t.Seque

for cluster_id in cluster_ids:
referencing_binding_constraints = self.binding_constraint_manager.get_binding_constraints(
study, ConstraintFilters(cluster_id=cluster_id)
study, ConstraintFilters(cluster_id=f"{area_id}.{cluster_id}")
)[:MAX_BINDING_CONSTRAINTS_TO_DISPLAY]
if referencing_binding_constraints:
first_bcs_ids = ""
Expand Down
2 changes: 1 addition & 1 deletion antarest/study/web/study_data_blueprint.py
Original file line number Diff line number Diff line change
Expand Up @@ -2185,7 +2185,7 @@ def delete_thermal_clusters(
)
request_params = RequestParameters(user=current_user)
study = study_service.check_study_access(uuid, StudyPermissionType.WRITE, request_params)
study_service.assert_no_cluster_referenced_in_bcs(study, cluster_ids)
study_service.assert_no_cluster_referenced_in_bcs(study, area_id, cluster_ids)
study_service.thermal_manager.delete_clusters(study, area_id, cluster_ids)

@bp.get(
Expand Down
223 changes: 200 additions & 23 deletions tests/integration/study_data_blueprint/test_thermal.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
from antarest.core.utils.string import to_camel_case
from antarest.study.storage.rawstudy.model.filesystem.config.model import transform_name_to_id
from antarest.study.storage.rawstudy.model.filesystem.config.thermal import ThermalProperties
from antarest.study.storage.variantstudy.model.command.common import CommandName
from tests.integration.utils import wait_task_completion

DEFAULT_PROPERTIES = json.loads(ThermalProperties(name="Dummy").json())
Expand Down Expand Up @@ -647,17 +646,38 @@ def test_lifecycle(
)
assert res.status_code in {200, 201}, res.json()

# To delete a thermal cluster, we need to provide its ID.
# verify that we can't delete the thermal cluster because it is referenced in a binding constraint
res = client.request(
"DELETE",
f"/v1/studies/{study_id}/areas/{area_id}/clusters/thermal",
headers={"Authorization": f"Bearer {user_access_token}"},
json=[fr_gas_conventional_id],
)
assert res.status_code == 403, res.json()
assert res.json() == {
"description": "Cluster FR_Gas conventional is not allowed to be deleted\n"
"Cluster is referenced in the following binding constraints:"
"\n1- binding constraint\n",
"exception": "ClusterDeletionNotAllowed",
}

# delete the binding constraint
res = client.delete(
f"/v1/studies/{study_id}/bindingconstraints/{bc_obj['name']}",
headers={"Authorization": f"Bearer {user_access_token}"},
)
assert res.status_code == 200, res.json()

# Now we can delete the thermal cluster
res = client.request(
"DELETE",
f"/v1/studies/{study_id}/areas/{area_id}/clusters/thermal",
headers={"Authorization": f"Bearer {user_access_token}"},
json=[fr_gas_conventional_id],
)
assert res.status_code == 204, res.json()
assert res.text in {"", "null"} # Old FastAPI versions return 'null'.

# When we delete a thermal cluster, we should also delete the binding constraints that reference it.
# check that the binding constraint has been deleted
# noinspection SpellCheckingInspection
res = client.get(
f"/v1/studies/{study_id}/bindingconstraints",
Expand Down Expand Up @@ -1004,25 +1024,6 @@ def test_variant_lifecycle(self, client: TestClient, user_access_token: str, var
assert res.status_code == 200
assert res.json()["data"] == matrix

# # create a binding constraint for
# res = client.post(
# f"/v1/studies/{variant_id}/commands",
# headers={"Authorization": f"Bearer {user_access_token}"},
# json=[
# {
# "action": CommandName.CREATE_BINDING_CONSTRAINT.value,
# "args": {
# "name": "binding constraint 1",
# "enabled": True,
# "time_step": "hourly",
# "operator": "less",
# "coeffs": {f"{area_id}.{cluster_id}": [2.0, 4]},
# },
# }
# ],
# )
# res.raise_for_status()

# Delete the thermal cluster
res = client.delete(
f"/v1/studies/{variant_id}/areas/{area_id}/clusters/thermal",
Expand All @@ -1049,3 +1050,179 @@ def test_variant_lifecycle(self, client: TestClient, user_access_token: str, var
"replace_matrix",
"remove_cluster",
]

def test_thermal_cluster_deletion(self, client: TestClient, user_access_token: str) -> None:
"""
Test that creating a thermal cluster with invalid properties raises a validation error.
"""

client.headers = {"Authorization": f"Bearer {user_access_token}"}

# Create a new study
res = client.post(
f"/v1/studies",
params={"name": "My Study"},
)
assert res.status_code in {200, 201}, res.json()
study_id = res.json()

# Create an area "area_1" in the study
res = client.post(
f"/v1/studies/{study_id}/areas",
json={
"name": "area_1",
"type": "AREA",
"metadata": {"country": "FR"},
},
)
assert res.status_code == 200, res.json()

# Create an area "area_2" in the study
res = client.post(
f"/v1/studies/{study_id}/areas",
json={
"name": "area_2",
"type": "AREA",
"metadata": {"country": "DE"},
},
)
assert res.status_code == 200, res.json()

# Create an area "area_3" in the study
res = client.post(
f"/v1/studies/{study_id}/areas",
json={
"name": "area_3",
"type": "AREA",
"metadata": {"country": "ES"},
},
)
assert res.status_code == 200, res.json()

# Create a thermal cluster in the study for area_1
res = client.post(
f"/v1/studies/{study_id}/areas/area_1/clusters/thermal",
json={
"name": "cluster_1",
"group": "Nuclear",
"unitCount": 13,
"nominalCapacity": 42500,
"marginalCost": 0.1,
},
)
assert res.status_code == 200, res.json()

# Create a thermal cluster in the study for area_2
res = client.post(
f"/v1/studies/{study_id}/areas/area_2/clusters/thermal",
json={
"name": "cluster_2",
"group": "Nuclear",
"unitCount": 13,
"nominalCapacity": 42500,
"marginalCost": 0.1,
},
)
assert res.status_code == 200, res.json()

# Create a thermal cluster in the study for area_3
res = client.post(
f"/v1/studies/{study_id}/areas/area_3/clusters/thermal",
json={
"name": "cluster_3",
"group": "Nuclear",
"unitCount": 13,
"nominalCapacity": 42500,
"marginalCost": 0.1,
},
)
assert res.status_code == 200, res.json()

# add a binding constraint that references the thermal cluster in area_1
bc_obj = {
"name": "bc_1",
"enabled": True,
"time_step": "hourly",
"operator": "less",
"terms": [
{
"id": "area_1.cluster_1",
"weight": 2,
"offset": 5,
"data": {"area": "area_1", "cluster": "cluster_1"},
}
],
}
res = client.post(
f"/v1/studies/{study_id}/bindingconstraints",
json=bc_obj,
)
assert res.status_code == 200, res.json()

# add a binding constraint that references the thermal cluster in area_2
bc_obj = {
"name": "bc_2",
"enabled": True,
"time_step": "hourly",
"operator": "less",
"terms": [
{
"id": "area_2.cluster_2",
"weight": 2,
"offset": 5,
"data": {"area": "area_2", "cluster": "cluster_2"},
}
],
}
res = client.post(
f"/v1/studies/{study_id}/bindingconstraints",
json=bc_obj,
)
assert res.status_code == 200, res.json()

# check that deleting the thermal cluster in area_1 fails
res = client.delete(
f"/v1/studies/{study_id}/areas/area_1/clusters/thermal",
json=["cluster_1"],
)
assert res.status_code == 403, res.json()

# now delete the binding constraint that references the thermal cluster in area_1
res = client.delete(
f"/v1/studies/{study_id}/bindingconstraints/bc_1",
)
assert res.status_code == 200, res.json()

# check that deleting the thermal cluster in area_1 succeeds
res = client.delete(
f"/v1/studies/{study_id}/areas/area_1/clusters/thermal",
json=["cluster_1"],
)
assert res.status_code == 204, res.json()

# check that deleting the thermal cluster in area_2 fails
res = client.delete(
f"/v1/studies/{study_id}/areas/area_2/clusters/thermal",
json=["cluster_2"],
)
assert res.status_code == 403, res.json()

# now delete the binding constraint that references the thermal cluster in area_2
res = client.delete(
f"/v1/studies/{study_id}/bindingconstraints/bc_2",
)
assert res.status_code == 200, res.json()

# check that deleting the thermal cluster in area_2 succeeds
res = client.delete(
f"/v1/studies/{study_id}/areas/area_2/clusters/thermal",
json=["cluster_2"],
)
assert res.status_code == 204, res.json()

# check that deleting the thermal cluster in area_3 succeeds
res = client.delete(
f"/v1/studies/{study_id}/areas/area_3/clusters/thermal",
json=["cluster_3"],
)
assert res.status_code == 204, res.json()
84 changes: 83 additions & 1 deletion tests/integration/test_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -1439,7 +1439,7 @@ def test_area_management(client: TestClient, admin_access_token: str) -> None:
assert result.json()["exception"] == "AreaDeletionNotAllowed"

# delete binding constraint 1
result = client.delete(f"/v1/studies/{study_id}/bindingconstraints/binding%20constraint%201")
client.delete(f"/v1/studies/{study_id}/bindingconstraints/binding%20constraint%201")
# check now that we can delete the area 1
result = client.delete(f"/v1/studies/{study_id}/areas/area%201")
assert result.status_code == 200, res.json()
Expand Down Expand Up @@ -1715,3 +1715,85 @@ def test_copy(client: TestClient, admin_access_token: str, study_id: str) -> Non
res = client.get(f"/v1/studies/{copied.json()}").json()
assert res["groups"] == []
assert res["public_mode"] == "READ"


def test_links_deletion(client: TestClient, user_access_token: str) -> None:
"""
Test the deletion of links between areas.
"""

# set client headers to user access token
client.headers = {"Authorization": f"Bearer {user_access_token}"}

# create a study
study_id = client.post("/v1/studies?name=test").json()

# Create an area "area_1" in the study
res = client.post(
f"/v1/studies/{study_id}/areas",
json={
"name": "area_1",
"type": "AREA",
"metadata": {"country": "FR"},
},
)
assert res.status_code == 200, res.json()

# Create an area "area_2" in the study
res = client.post(
f"/v1/studies/{study_id}/areas",
json={
"name": "area_2",
"type": "AREA",
"metadata": {"country": "DE"},
},
)
assert res.status_code == 200, res.json()

# create a link between the two areas
res = client.post(
f"/v1/studies/{study_id}/links",
json={
"area1": "area_1",
"area2": "area_2",
},
)
assert res.status_code == 200, res.json()

# create a binding constraint that references the link
bc_obj = {
"name": "bc_1",
"enabled": True,
"time_step": "hourly",
"operator": "less",
"terms": [
{
"id": "area_1%area_2",
"weight": 2,
"data": {"area1": "area_1", "area2": "area_2"},
}
],
}
res = client.post(
f"/v1/studies/{study_id}/bindingconstraints",
json=bc_obj,
)
assert res.status_code == 200, res.json()

# try to delete the link before deleting the binding constraint
res = client.delete(f"/v1/studies/{study_id}/links/area_1/area_2")
assert res.status_code == 403, res.json()
assert res.json() == {
"description": "Link area_1%area_2 is not allowed to be deleted\n"
"Link is referenced in the following binding constraints:\n"
"1- bc_1\n",
"exception": "LinkDeletionNotAllowed",
}

# delete the binding constraint
res = client.delete(f"/v1/studies/{study_id}/bindingconstraints/bc_1")
assert res.status_code == 200, res.json()

# delete the link
res = client.delete(f"/v1/studies/{study_id}/links/area_1/area_2")
assert res.status_code == 200, res.json()

0 comments on commit 67e0b0b

Please sign in to comment.