fix(project): show created project after creation #593
@@ -2,6 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Fixed `agents project show` not finding a project immediately after creation.
|
||||
Extended the `session.commit()` fix from #589 to also cover `update()` and
|
||||
`delete()` in `NamespacedProjectRepository`, and updated the class docstring
|
||||
to reflect that all mutating methods now commit within their own session.
|
||||
Includes 3 Behave BDD regression scenarios, Robot Framework integration
|
||||
smoke tests, and ASV benchmarks. (#590)
|
||||
- Fixed `agents project create` not persisting projects to the database.
|
||||
`NamespacedProjectRepository.create()` called `session.flush()` but never
|
||||
`session.commit()`, so projects were invisible to subsequent
|
||||
|
||||
@@ -0,0 +1,115 @@
|
||||
"""ASV benchmarks for project create + show round-trip.
|
||||
|
||||
Measures the cost of creating a project and then showing it via the
|
||||
``NamespacedProjectRepository``, using a file-based SQLite database so
|
||||
that each operation gets a fresh session (matching real CLI behaviour).
|
||||
|
||||
Targets bug #590 — ``create()`` now calls ``session.commit()`` so data
|
||||
is visible to a subsequent ``get()`` in a separate session.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import importlib
|
||||
import os
|
||||
import shutil
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure the local *source* tree is importable even when ASV has an
|
||||
# older build of the package installed.
|
||||
_SRC = str(Path(__file__).resolve().parents[1] / "src")
|
||||
if _SRC not in sys.path:
|
||||
sys.path.insert(0, _SRC)
|
||||
|
||||
# Force-reload so ASV picks up the source tree version.
|
||||
import cleveragents # noqa: E402
|
||||
|
||||
importlib.reload(cleveragents)
|
||||
|
||||
from sqlalchemy import create_engine # noqa: E402
|
||||
from sqlalchemy.orm import sessionmaker # noqa: E402
|
||||
from sqlalchemy.pool import NullPool # noqa: E402
|
||||
|
||||
from cleveragents.domain.models.core.project import ( # noqa: E402
|
||||
NamespacedProject,
|
||||
parse_namespaced_name,
|
||||
)
|
||||
from cleveragents.infrastructure.database.models import Base # noqa: E402
|
||||
from cleveragents.infrastructure.database.repositories import ( # noqa: E402
|
||||
NamespacedProjectRepository,
|
||||
ProjectNotFoundError,
|
||||
)
|
||||
|
||||
|
||||
def _fresh_repo(db_path: str) -> NamespacedProjectRepository:
|
||||
"""Return a repo backed by a *new* engine/session for *db_path*.
|
||||
|
||||
``NullPool`` ensures SQLite file handles are released immediately
|
||||
when the engine is no longer referenced.
|
||||
"""
|
||||
engine = create_engine(f"sqlite:///{db_path}", echo=False, poolclass=NullPool)
|
||||
Base.metadata.create_all(engine)
|
||||
factory = sessionmaker(bind=engine, expire_on_commit=False)
|
||||
return NamespacedProjectRepository(session_factory=factory)
|
||||
|
||||
|
||||
class ProjectShowAfterCreateSuite:
|
||||
"""Benchmark create-then-show round trip with separate sessions."""
|
||||
|
||||
timeout = 30.0
|
||||
|
||||
def setup(self) -> None:
|
||||
self._tmpdir = tempfile.mkdtemp(prefix="bench_show_590_")
|
||||
self._db_path = os.path.join(self._tmpdir, "bench.db")
|
||||
engine = create_engine(
|
||||
f"sqlite:///{self._db_path}",
|
||||
echo=False,
|
||||
poolclass=NullPool,
|
||||
)
|
||||
Base.metadata.create_all(engine)
|
||||
engine.dispose()
|
||||
self._counter = 0
|
||||
|
||||
def teardown(self) -> None:
|
||||
shutil.rmtree(self._tmpdir, ignore_errors=True)
|
||||
|
||||
def time_create_then_show(self) -> None:
|
||||
"""Create a project in one session, show (get) in another."""
|
||||
self._counter += 1
|
||||
|
|
||||
name = f"bench-show-{self._counter}"
|
||||
parsed = parse_namespaced_name(name)
|
||||
proj = NamespacedProject(name=parsed.name, namespace=parsed.namespace)
|
||||
|
||||
create_repo = _fresh_repo(self._db_path)
|
||||
create_repo.create(proj)
|
||||
|
||||
show_repo = _fresh_repo(self._db_path)
|
||||
with contextlib.suppress(ProjectNotFoundError):
|
||||
|
CoreRasurae
commented
F3 [Medium -- Bug]: This method always creates a project named Compare with Suggested fix: **F3 [Medium -- Bug]:** This method always creates a project named `"bench-show-track"` (hardcoded). ASV calls `setup()` once per suite instance, then may invoke each benchmark method multiple times for statistical sampling. On the second invocation, `create()` will hit the UNIQUE constraint on `namespaced_name` and raise `IntegrityError`, producing either a false `0` return or an outright crash.
Compare with `time_create_then_show` (line 68) which correctly uses `self._counter` for unique names.
**Suggested fix:**
```python
def setup(self) -> None:
...
self._track_counter = 0
def track_show_after_create_found(self) -> int:
self._track_counter += 1
name = f"bench-show-track-{self._track_counter}"
parsed = parse_namespaced_name(name)
...
show_repo.get(f"local/{name}")
```
|
||||
show_repo.get(f"local/{name}")
|
||||
|
||||
def track_show_after_create_found(self) -> int:
|
||||
"""Track whether show finds the created project.
|
||||
|
||||
Returns 1 when get() successfully finds the project; 0 if
|
||||
ProjectNotFoundError is raised.
|
||||
"""
|
||||
self._counter += 1
|
||||
name = f"bench-show-track-{self._counter}"
|
||||
parsed = parse_namespaced_name(name)
|
||||
proj = NamespacedProject(name=parsed.name, namespace=parsed.namespace)
|
||||
|
||||
create_repo = _fresh_repo(self._db_path)
|
||||
create_repo.create(proj)
|
||||
|
||||
show_repo = _fresh_repo(self._db_path)
|
||||
try:
|
||||
show_repo.get(f"local/{name}")
|
||||
return 1
|
||||
except ProjectNotFoundError:
|
||||
return 0
|
||||
|
||||
|
||||
ProjectShowAfterCreateSuite.track_show_after_create_found.unit = "found"
|
||||
@@ -0,0 +1,29 @@
|
||||
# Regression tests for bug #590: project show must find a project immediately after creation.
|
||||
|
CoreRasurae
commented
F8 [Low -- Documentation]: This comment says tests are "expected to fail until the fix is applied," but the fix commit ( Suggested fix: Update to reflect current state: **F8 [Low -- Documentation]:** This comment says tests are "expected to fail until the fix is applied," but the fix commit (`d262b0d0`) already removed the `@wip` tags from the scenarios below. After the fix, this comment is stale and misleading.
**Suggested fix:** Update to reflect current state:
```gherkin
# Regression tests for bug #590: project show must find a project immediately after creation.
```
|
||||
Feature: Project show displays a created project
|
||||
As a developer using the agents CLI
|
||||
I want "agents project show" to display a project I just created
|
||||
So that I can verify the project details after creation
|
||||
|
||||
Background:
|
||||
Given a fresh project-show database is initialised
|
||||
|
||||
@tdd @bug590
|
||||
Scenario: Show displays a project that was just created
|
||||
When I create a project named "local/my-app" via the project-show CLI
|
||||
And I show the project "local/my-app" via the project-show CLI
|
||||
Then the project-show output should contain "local/my-app"
|
||||
And the project-show exit code should be 0
|
||||
|
||||
@tdd @bug590
|
||||
Scenario: Show displays correct details for a created project with description
|
||||
When I create a described project named "local/webapp" with description "My web app" via the project-show CLI
|
||||
And I show the project "local/webapp" via the project-show CLI
|
||||
Then the project-show output should contain "local/webapp"
|
||||
And the project-show output should contain "My web app"
|
||||
And the project-show exit code should be 0
|
||||
|
||||
@tdd @bug590
|
||||
Scenario: Show returns error for a project that does not exist
|
||||
When I show the project "local/nonexistent" via the project-show CLI
|
||||
Then the project-show output should contain "not found"
|
||||
And the project-show exit code should not be 0
|
||||
@@ -0,0 +1,180 @@
|
||||
"""Step definitions for project_show_after_create.feature (bug #590).
|
||||
|
||||
Regression tests verifying that ``agents project show`` finds a project
|
||||
immediately after ``agents project create`` commits it.
|
||||
|
||||
Root cause (fixed)
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
Identical to bug #589. ``NamespacedProjectRepository.create()`` called
|
||||
``session.flush()`` but never ``session.commit()``. The ``show`` command
|
||||
obtains a fresh session via ``_get_namespaced_project_repo()`` and calls
|
||||
``repo.get(name)``, which could not see the flushed-but-uncommitted row.
|
||||
|
||||
Test design mirrors bug #589 tests: file-based SQLite with a fresh
|
||||
``NamespacedProjectRepository`` per CLI invocation.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from typing import Any
|
||||
from unittest.mock import patch
|
||||
|
||||
from behave import given, then, when
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.pool import NullPool
|
||||
from typer.testing import CliRunner
|
||||
|
||||
from cleveragents.cli.commands.project import app as project_app
|
||||
from cleveragents.infrastructure.database.models import Base
|
||||
from cleveragents.infrastructure.database.repositories import (
|
||||
NamespacedProjectRepository,
|
||||
)
|
||||
|
||||
# Patch targets - module-level DI helpers in the project CLI.
|
||||
_PATCH_PROJECT_REPO = "cleveragents.cli.commands.project._get_namespaced_project_repo"
|
||||
_PATCH_STORE_EXTRAS = "cleveragents.cli.commands.project._store_project_extras"
|
||||
|
||||
# NOTE: typer.testing.CliRunner always mixes stderr into stdout
|
||||
# (no mix_stderr parameter exposed). Scenario 3 relies on this
|
||||
# behaviour to assert "not found" appears in result.output even
|
||||
# though the CLI writes it to stderr via err_console.
|
||||
runner = CliRunner()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _make_fresh_repo(db_path: str) -> NamespacedProjectRepository:
|
||||
"""Build a *new* repo with its own engine/session for *db_path*.
|
||||
|
||||
``NullPool`` ensures SQLite file handles are released immediately
|
||||
when the engine is no longer referenced. Schema is pre-created
|
||||
once during the Background step, so no DDL is needed here.
|
||||
"""
|
||||
engine = create_engine(f"sqlite:///{db_path}", echo=False, poolclass=NullPool)
|
||||
factory = sessionmaker(bind=engine, expire_on_commit=False)
|
||||
return NamespacedProjectRepository(session_factory=factory)
|
||||
|
||||
|
||||
def _cleanup_show_db(context: Any) -> None:
|
||||
"""Remove the temporary directory."""
|
||||
shutil.rmtree(context.show_tmpdir, ignore_errors=True)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Background
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@given("a fresh project-show database is initialised")
|
||||
def step_init_show_db(context: Any) -> None:
|
||||
"""Create a temp dir with a fresh SQLite file for show tests."""
|
||||
context.show_tmpdir = tempfile.mkdtemp(prefix="show_590_")
|
||||
context.show_db_path = os.path.join(context.show_tmpdir, "test.db")
|
||||
|
||||
# Pre-create schema.
|
||||
engine = create_engine(
|
||||
f"sqlite:///{context.show_db_path}",
|
||||
echo=False,
|
||||
poolclass=NullPool,
|
||||
)
|
||||
Base.metadata.create_all(engine)
|
||||
engine.dispose()
|
||||
|
||||
context.show_result = None
|
||||
context.add_cleanup(_cleanup_show_db, context)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# When steps - create
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
CoreRasurae
commented
F1 [Medium -- Test Flaw]: The Since these are TDD tests designed to isolate the show-after-create bug specifically, a silent create failure would produce a confusing false-positive ("test fails as expected") for the wrong reason. Suggested fix: Same issue applies to **F1 [Medium -- Test Flaw]:** The `runner.invoke()` result is discarded without checking the exit code. If `project create` itself fails (import error, DB schema mismatch, Typer wiring issue), the test silently proceeds to the `show` step and fails there, misattributing the failure.
Since these are TDD tests designed to isolate the show-after-create bug specifically, a silent create failure would produce a confusing false-positive ("test fails as expected") for the wrong reason.
**Suggested fix:**
```python
result = runner.invoke(project_app, ["create", name])
assert result.exit_code == 0, (
f"project create failed unexpectedly: {result.output}"
)
```
Same issue applies to `step_create_project_show_desc` at line 111.
|
||||
|
||||
@when('I create a project named "{name}" via the project-show CLI')
|
||||
def step_create_project_show(context: Any, name: str) -> None:
|
||||
"""Invoke ``project create <name>`` with a fresh repo."""
|
||||
repo = _make_fresh_repo(context.show_db_path)
|
||||
with (
|
||||
patch(_PATCH_PROJECT_REPO, return_value=repo),
|
||||
patch(_PATCH_STORE_EXTRAS),
|
||||
):
|
||||
result = runner.invoke(project_app, ["create", name])
|
||||
assert result.exit_code == 0, (
|
||||
f"project create '{name}' should exit 0 but got "
|
||||
f"{result.exit_code}. output:\n{result.output}"
|
||||
)
|
||||
|
||||
|
||||
@when(
|
||||
'I create a described project named "{name}" with description "{desc}" '
|
||||
"via the project-show CLI"
|
||||
)
|
||||
def step_create_project_show_desc(context: Any, name: str, desc: str) -> None:
|
||||
"""Invoke ``project create <name> -d <desc>`` with a fresh repo."""
|
||||
repo = _make_fresh_repo(context.show_db_path)
|
||||
with (
|
||||
patch(_PATCH_PROJECT_REPO, return_value=repo),
|
||||
patch(_PATCH_STORE_EXTRAS),
|
||||
):
|
||||
result = runner.invoke(project_app, ["create", name, "-d", desc])
|
||||
assert result.exit_code == 0, (
|
||||
f"project create '{name}' with description should exit 0 but got "
|
||||
f"{result.exit_code}. output:\n{result.output}"
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# When steps - show
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@when('I show the project "{name}" via the project-show CLI')
|
||||
def step_show_project(context: Any, name: str) -> None:
|
||||
"""Invoke ``project show <name>`` with a fresh repo."""
|
||||
repo = _make_fresh_repo(context.show_db_path)
|
||||
with patch(_PATCH_PROJECT_REPO, return_value=repo):
|
||||
context.show_result = runner.invoke(project_app, ["show", name])
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Then steps
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@then('the project-show output should contain "{text}"')
|
||||
def step_show_output_contains(context: Any, text: str) -> None:
|
||||
"""Assert that the show output contains *text* (case-insensitive)."""
|
||||
result = context.show_result
|
||||
assert result is not None, "project show was not invoked"
|
||||
output = result.output
|
||||
assert text.lower() in output.lower(), (
|
||||
f"Expected '{text}' in show output but got:\n{output}"
|
||||
)
|
||||
|
||||
|
||||
@then("the project-show exit code should be {code:d}")
|
||||
def step_show_exit_code(context: Any, code: int) -> None:
|
||||
"""Assert the show command exited with the expected code."""
|
||||
result = context.show_result
|
||||
assert result is not None, "project show was not invoked"
|
||||
actual = result.exit_code
|
||||
assert actual == code, (
|
||||
f"Expected exit code {code}, got {actual}. Output:\n{result.output}"
|
||||
)
|
||||
|
||||
|
||||
@then("the project-show exit code should not be {code:d}")
|
||||
def step_show_exit_code_not(context: Any, code: int) -> None:
|
||||
"""Assert the show command did NOT exit with the given code."""
|
||||
result = context.show_result
|
||||
assert result is not None, "project show was not invoked"
|
||||
actual = result.exit_code
|
||||
assert actual != code, (
|
||||
f"Expected exit code != {code}, got {actual}. Output:\n{result.output}"
|
||||
)
|
||||
@@ -0,0 +1,67 @@
|
||||
*** Settings ***
|
||||
Documentation Integration smoke test for project show after create (bug #590).
|
||||
... Verifies that ``agents project show`` finds a project
|
||||
... immediately after ``agents project create`` commits it.
|
||||
Resource ${CURDIR}/common.resource
|
||||
Library Process
|
||||
Library OperatingSystem
|
||||
Library String
|
||||
Suite Setup Setup Test Environment
|
||||
Suite Teardown Cleanup Test Environment
|
||||
|
||||
*** Test Cases ***
|
||||
Project Show Displays Created Project
|
||||
[Documentation] After creating a project, show should display its details.
|
||||
${tmpdir}= Evaluate __import__('tempfile').mkdtemp(prefix='show_590_')
|
||||
${init}= Run Process ${PYTHON} -m cleveragents init show-test
|
||||
... timeout=60s cwd=${tmpdir}
|
||||
Should Be Equal As Integers ${init.rc} 0
|
||||
... msg=agents init failed: ${init.stderr}
|
||||
|
CoreRasurae
commented
F2 [Medium -- Test Flaw]: The Suggested fix: Same pattern applies to the second test case starting at line 34. **F2 [Medium -- Test Flaw]:** The `Run Process` calls for `agents init` (line 19) and `agents project create` (line 21) do not capture or verify return codes. If either command fails, the test only catches it at the `project show` assertion on line 25, obscuring the real failure point.
**Suggested fix:**
```robot
${init_result}= Run Process ${PYTHON} -m cleveragents init show-test
... timeout=60s cwd=${tmpdir}
Should Be Equal As Integers ${init_result.rc} 0
... msg=agents init failed: ${init_result.stderr}
${create_result}= Run Process ${PYTHON} -m cleveragents project create local/show-proj
... timeout=60s cwd=${tmpdir}
Should Be Equal As Integers ${create_result.rc} 0
... msg=project create failed: ${create_result.stderr}
```
Same pattern applies to the second test case starting at line 34.
|
||||
${create}= Run Process ${PYTHON} -m cleveragents project create local/show-proj
|
||||
... timeout=60s cwd=${tmpdir}
|
||||
Should Be Equal As Integers ${create.rc} 0
|
||||
... msg=project create failed: ${create.stderr}
|
||||
|
CoreRasurae
commented
F6 [Low -- Robustness]: The Suggested fix: Pass Same applies to line 40 in the second test case. **F6 [Low -- Robustness]:** The `project show` invocation uses the default output format (rich), which may include ANSI escape codes in real subprocess environments. The `Should Contain` assertion on line 27 could fail or produce false positives if ANSI codes appear inside the project name string.
**Suggested fix:** Pass `--format plain` to guarantee clean output:
```robot
${result}= Run Process ${PYTHON} -m cleveragents --format plain project show local/show-proj
```
Same applies to line 40 in the second test case.
|
||||
${result}= Run Process ${PYTHON} -m cleveragents project show --format plain local/show-proj
|
||||
... timeout=60s cwd=${tmpdir}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
... msg=project show should exit 0 but got ${result.rc}. stderr: ${result.stderr}
|
||||
Should Contain ${result.stdout} local/show-proj
|
||||
... msg=project show output should contain project name but got: ${result.stdout}
|
||||
[Teardown] Remove Directory ${tmpdir} recursive=True
|
||||
|
||||
Project Show With Description Displays Description
|
||||
[Documentation] Show should display the project description after creation.
|
||||
${tmpdir}= Evaluate __import__('tempfile').mkdtemp(prefix='show_590_desc_')
|
||||
${init}= Run Process ${PYTHON} -m cleveragents init show-desc-test
|
||||
... timeout=60s cwd=${tmpdir}
|
||||
Should Be Equal As Integers ${init.rc} 0
|
||||
... msg=agents init failed: ${init.stderr}
|
||||
${create}= Run Process ${PYTHON} -m cleveragents project create local/desc-proj
|
||||
... -d A test project
|
||||
... timeout=60s cwd=${tmpdir}
|
||||
Should Be Equal As Integers ${create.rc} 0
|
||||
... msg=project create failed: ${create.stderr}
|
||||
${result}= Run Process ${PYTHON} -m cleveragents project show --format plain local/desc-proj
|
||||
... timeout=60s cwd=${tmpdir}
|
||||
Should Be Equal As Integers ${result.rc} 0
|
||||
... msg=project show should exit 0 but got ${result.rc}. stderr: ${result.stderr}
|
||||
Should Contain ${result.stdout} A test project
|
||||
... msg=project show should display description but got: ${result.stdout}
|
||||
[Teardown] Remove Directory ${tmpdir} recursive=True
|
||||
|
||||
Project Show Returns Error For Nonexistent Project
|
||||
[Documentation] Showing a project that was never created should fail.
|
||||
${tmpdir}= Evaluate __import__('tempfile').mkdtemp(prefix='show_590_missing_')
|
||||
${init}= Run Process ${PYTHON} -m cleveragents init show-missing-test
|
||||
... timeout=60s cwd=${tmpdir}
|
||||
Should Be Equal As Integers ${init.rc} 0
|
||||
... msg=agents init failed: ${init.stderr}
|
||||
${result}= Run Process ${PYTHON} -m cleveragents project show --format plain local/nonexistent
|
||||
... timeout=60s cwd=${tmpdir}
|
||||
Should Not Be Equal As Integers ${result.rc} 0
|
||||
... msg=project show for nonexistent project should fail but exited 0
|
||||
${combined}= Set Variable ${result.stdout}${result.stderr}
|
||||
${lower}= Convert To Lower Case ${combined}
|
||||
Should Contain ${lower} not found
|
||||
... msg=project show for nonexistent project should mention "not found" but got: ${combined}
|
||||
[Teardown] Remove Directory ${tmpdir} recursive=True
|
||||
@@ -2812,8 +2812,8 @@ class NamespacedProjectRepository:
|
||||
Uses a session-factory pattern: each public method obtains its own
|
||||
session from the factory, ensuring proper session lifecycle management.
|
||||
|
||||
All mutating methods flush (but do NOT commit); the caller or a
|
||||
``UnitOfWork`` wrapper is responsible for committing the transaction.
|
||||
All mutating methods (``create``, ``update``, ``delete``) flush **and
|
||||
commit** within their own session. No external ``UnitOfWork`` is needed.
|
||||
"""
|
||||
|
||||
def __init__(self, session_factory: Callable[[], Session]) -> None:
|
||||
@@ -2954,12 +2954,15 @@ class NamespacedProjectRepository:
|
||||
row.updated_at = datetime.now(tz=UTC).isoformat() # type: ignore[assignment]
|
||||
|
||||
session.flush()
|
||||
session.commit()
|
||||
return project
|
||||
except ProjectNotFoundError:
|
||||
raise
|
||||
except (OperationalError, SQLAlchemyDatabaseError) as exc:
|
||||
session.rollback()
|
||||
raise DatabaseError(f"Failed to update project '{ns_name}': {exc}") from exc
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
@database_retry
|
||||
def delete(self, namespaced_name: str) -> bool:
|
||||
@@ -2987,12 +2990,15 @@ class NamespacedProjectRepository:
|
||||
return False
|
||||
session.delete(row)
|
||||
session.flush()
|
||||
session.commit()
|
||||
return True
|
||||
except (OperationalError, SQLAlchemyDatabaseError) as exc:
|
||||
session.rollback()
|
||||
raise DatabaseError(
|
||||
f"Failed to delete project '{namespaced_name}': {exc}"
|
||||
) from exc
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
F5 [Low -- Bug]: Bare
except Exception: passswallows all errors, not just the expectedProjectNotFoundError. AConnectionError,IntegrityError, orTypeErrorduring benchmarking would be silently masked, making failures undiagnosable.Suggested fix: