Skip to content

Commit 541b9d5

Browse files
authored
Fixing Backend's API for the CLI Test Run Execution (#283)
* Fixing Backend's API for the CLI Test Run Execution * Gemini suggestions: Updating logic for efficiency Also avoiding modifying external variable. * Solve flake8 error in singleton.py file
1 parent b4b12d5 commit 541b9d5

3 files changed

Lines changed: 75 additions & 57 deletions

File tree

app/api/api_v1/endpoints/test_run_executions.py

Lines changed: 57 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 2023 Project CHIP Authors
2+
# Copyright (c) 2025 Project CHIP Authors
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
3232
from app.crud.crud_test_run_execution import ImportError
3333
from app.db.session import get_db
3434
from app.default_environment_config import default_environment_config
35+
from app.models.project import Project
3536
from app.models.test_run_execution import TestRunExecution
3637
from app.schemas.test_run_execution import TestRunExecutionUpdate
3738
from app.test_engine import TEST_ENGINE_ABORTING_TESTING_MESSAGE
@@ -123,6 +124,49 @@ def __convert_pics_dict_to_object(pics: dict) -> Optional[schemas.PICS]:
123124
return None
124125

125126

127+
def __cli_project(
128+
db: Session,
129+
project_id: Optional[int],
130+
config: Optional[dict],
131+
pics_obj: schemas.PICS,
132+
) -> Project:
133+
"""Retrieve or create the default CLI project."""
134+
135+
# If project_id not is provided, try to retrieve the Default CLI project
136+
if project_id is None:
137+
# If the default CLI project does not exist, create it
138+
if not (
139+
cli_project := crud.project.get_by_name(
140+
db=db, name=DEFAULT_CLI_PROJECT_NAME
141+
)
142+
):
143+
new_config = (
144+
default_environment_config.__dict__ if config is None else config
145+
)
146+
project_create = schemas.ProjectCreate(
147+
name=DEFAULT_CLI_PROJECT_NAME, config=new_config, pics=pics_obj
148+
)
149+
return crud.project.create(db=db, obj_in=project_create)
150+
else:
151+
cli_project = crud.project.get(db=db, id=project_id)
152+
if not cli_project:
153+
raise HTTPException(
154+
status_code=HTTPStatus.NOT_FOUND,
155+
detail=f"Project with ID {project_id} not found.",
156+
)
157+
158+
if config is not None:
159+
logger.info(f"CLI Config Arguments: {config}")
160+
project_update = schemas.ProjectUpdate(
161+
name=cli_project.name, config=config, pics=pics_obj
162+
)
163+
cli_project = crud.project.update(
164+
db=db, db_obj=cli_project, obj_in=project_update
165+
)
166+
167+
return cli_project
168+
169+
126170
@router.post("/cli", response_model=schemas.TestRunExecutionWithChildren)
127171
def create_cli_test_run_execution(
128172
*,
@@ -140,55 +184,19 @@ def create_cli_test_run_execution(
140184
config: Configuration parameters (optional)
141185
pics: PICS configuration (optional)
142186
"""
143-
if config is None:
144-
config = default_environment_config.__dict__
145-
146-
logger.info(f"CLI Config Arguments: {config}")
147-
logger.info(f"CLI PICS Arguments: {pics}")
148187

149188
# Convert pics dict to PICS object if provided
189+
logger.info(f"CLI PICS Arguments: {pics}")
150190
pics_obj = __convert_pics_dict_to_object(pics)
151191
if pics_obj is None:
152192
raise HTTPException(
153193
status_code=HTTPStatus.UNPROCESSABLE_ENTITY,
154194
detail="Invalid PICS data provided. Please check the format.",
155195
)
156196

157-
# Use provided project_id or default CLI project
158-
if test_run_execution_in.project_id is not None:
159-
project = crud.project.get(db=db, id=test_run_execution_in.project_id)
160-
# Use the specified project_id
161-
if not project:
162-
raise HTTPException(
163-
status_code=HTTPStatus.NOT_FOUND,
164-
detail=f"Project with id {test_run_execution_in.project_id} not found.",
165-
)
166-
project_update = schemas.ProjectUpdate(config=config)
167-
168-
if pics_obj:
169-
project_update.pics = pics_obj
170-
171-
project = crud.project.update(db=db, db_obj=project, obj_in=project_update)
172-
else:
173-
# Retrieve the default CLI project
174-
cli_project = crud.project.get_by_name(db=db, name=DEFAULT_CLI_PROJECT_NAME)
175-
176-
# If the default CLI project does not exist, create it
177-
if not cli_project:
178-
project_create = schemas.ProjectCreate(name=DEFAULT_CLI_PROJECT_NAME)
179-
project_create.config = config
180-
if pics_obj:
181-
project_create.pics = pics_obj
182-
project = crud.project.create(db=db, obj_in=project_create)
183-
else:
184-
# Update the default CLI project with the cli config argument and pics
185-
project_update = schemas.ProjectUpdate(config=config)
186-
if pics_obj:
187-
project_update.pics = pics_obj
188-
project = crud.project.update(
189-
db=db, db_obj=cli_project, obj_in=project_update
190-
)
191-
test_run_execution_in.project_id = project.id
197+
# Retrieve or create the CLI project
198+
cli_project = __cli_project(db, test_run_execution_in.project_id, config, pics_obj)
199+
test_run_execution_in.project_id = cli_project.id
192200

193201
test_run_execution_in.certification_mode = False
194202

@@ -386,13 +394,14 @@ def repeat_test_run_execution(
386394
date_now = formated_datetime_now_str()
387395
title += date_now
388396

389-
test_run_execution_in = schemas.TestRunExecutionCreate(title=title)
390-
test_run_execution_in.description = execution_to_repeat.description
391-
test_run_execution_in.project_id = execution_to_repeat.project_id
392-
test_run_execution_in.operator_id = execution_to_repeat.operator_id
393-
test_run_execution_in.certification_mode = execution_to_repeat.certification_mode
394-
# TODO: Remove test_run_config completely from the project
395-
test_run_execution_in.test_run_config_id = None
397+
test_run_execution_in = schemas.TestRunExecutionCreate(
398+
title=title,
399+
description=execution_to_repeat.description,
400+
project_id=execution_to_repeat.project_id,
401+
operator_id=execution_to_repeat.operator_id,
402+
certification_mode=execution_to_repeat.certification_mode,
403+
test_run_config_id=None,
404+
)
396405

397406
selected_tests = selected_tests_from_execution(execution_to_repeat)
398407

app/singleton.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#
2-
# Copyright (c) 2023 Project CHIP Authors
2+
# Copyright (c) 2025 Project CHIP Authors
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -13,11 +13,13 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
#
16-
from typing import Any, Dict, Type
16+
from typing import Any, Dict, Type, TypeVar, cast
17+
18+
T = TypeVar("T")
1719

1820

1921
class Singleton(type):
20-
"""This is a metaclass for declaring classes a singletons
22+
"""This is a metaclass for declaring classes as singletons
2123
2224
usage:
2325
```
@@ -27,7 +29,9 @@ class NewSingletonClass(baseClass, metaclass=Singleton):
2729

2830
_instances: Dict[Type, object] = {}
2931

30-
def __call__(cls, *args: Any, **kwargs: Any) -> object:
31-
if cls not in cls._instances:
32-
cls._instances[cls] = super(Singleton, cls).__call__(*args, **kwargs)
33-
return cls._instances[cls]
32+
def __call__(cls: Type[T], *args: Any, **kwargs: Any) -> T:
33+
if cls not in Singleton._instances:
34+
Singleton._instances[cls] = super().__call__( # type: ignore[misc]
35+
*args, **kwargs
36+
)
37+
return cast(T, Singleton._instances[cls])

app/tests/api/api_v1/test_test_run_executions.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,7 +1528,7 @@ def test_create_cli_test_run_execution_with_invalid_project_id_in_execution(
15281528

15291529
assert response.status_code == HTTPStatus.NOT_FOUND
15301530
data = response.json()
1531-
assert "Project with id 999 not found" in data["detail"]
1531+
assert "Project with ID 999 not found" in data["detail"]
15321532

15331533

15341534
def test_create_cli_test_run_execution_without_project_id_in_execution_uses_default(
@@ -1612,6 +1612,8 @@ def test_create_cli_test_run_execution_creates_default_project_when_missing(
16121612
description=test_run_execution_create.description,
16131613
project_id=1,
16141614
operator_id=1,
1615+
certification_mode=False,
1616+
state=TestStateEnum.PENDING,
16151617
)
16161618

16171619
with patch(
@@ -1623,7 +1625,10 @@ def test_create_cli_test_run_execution_creates_default_project_when_missing(
16231625
"app.api.api_v1.endpoints.test_run_executions.crud.project.create",
16241626
return_value=mock_new_project,
16251627
), patch(
1626-
"app.api.api_v1.endpoints.test_run_executions.create_test_run_execution",
1628+
"app.api.api_v1.endpoints.test_run_executions.crud.project.update",
1629+
return_value=mock_new_project,
1630+
), patch(
1631+
"app.api.api_v1.endpoints.test_run_executions.crud.test_run_execution.create",
16271632
return_value=mock_test_run,
16281633
):
16291634
response = client.post(

0 commit comments

Comments
 (0)