Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 0 additions & 76 deletions apps/backend/src/rhesis/backend/app/crud/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1084,82 +1084,6 @@ def _recompute_affected_test_sets(deleted_ids: List[uuid.UUID]) -> None:
)


# Test Result CRUD
_TEST_RESULT_RELATED_FIELDS = (
include(models.TestResult.test_run),
include(models.TestResult.test),
include(models.TestResult.test, models.Test.prompt),
include(models.TestResult.test, models.Test.requirement),
)


def get_test_result(
db: Session, test_result_id: uuid.UUID, organization_id: str = None, user_id: str = None
) -> Optional[models.TestResult]:
"""Get test_result with relationships (tags, test, test_run) eagerly loaded."""
return get_item_detail(
db,
models.TestResult,
test_result_id,
organization_id=organization_id,
user_id=user_id,
related_fields=_TEST_RESULT_RELATED_FIELDS,
)


def get_test_results(
db: Session,
skip: int = 0,
limit: int = 10,
sort_by: str = "created_at",
sort_order: str = "desc",
filter: str | None = None,
organization_id: str = None,
user_id: str = None,
) -> List[models.TestResult]:
"""Get test_results with relationships (tags, test, test_run) eagerly loaded."""
return (
QueryBuilder(db, models.TestResult)
.with_related(*_TEST_RESULT_RELATED_FIELDS)
.with_default_derived_field_loads()
.with_organization_filter(organization_id)
.with_visibility_filter(user_id)
.with_odata_filter(filter)
.with_sorting(sort_by, sort_order)
.with_pagination(skip, limit)
.all()
)


def create_test_result(
db: Session,
test_result: schemas.TestResultCreate,
organization_id: str = None,
user_id: str = None,
) -> models.TestResult:
"""Create test_result."""
return create_item(db, models.TestResult, test_result, organization_id, user_id)


def update_test_result(
db: Session,
test_result_id: uuid.UUID,
test_result: schemas.TestResultUpdate,
organization_id: str = None,
user_id: str = None,
) -> Optional[models.TestResult]:
"""Update test_result."""
return update_item(db, models.TestResult, test_result_id, test_result, organization_id, user_id)


def delete_test_result(
db: Session, test_result_id: uuid.UUID, organization_id: str, user_id: str
) -> Optional[models.TestResult]:
return delete_item(
db, models.TestResult, test_result_id, organization_id=organization_id, user_id=user_id
)


# TypeLookup CRUD
def get_type_lookup(
db: Session, type_lookup_id: uuid.UUID, organization_id: str = None, user_id: str = None
Expand Down
98 changes: 98 additions & 0 deletions apps/backend/src/rhesis/backend/app/crud/test_result.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""CRUD operations for test results.

Part of the incremental split of the ``crud`` monolith: ``crud/__init__.py`` still holds
the bulk of the functions, and per-entity modules like this one take over as the code
around them is touched.

``_TEST_RESULT_RELATED_FIELDS`` is what ``TestResultDetail`` serializes -- the test run, the
test, and the test's prompt and requirement. All many-to-one, so eager-loading them in one
query costs nothing; without them a results list issues four queries per row.
"""

import uuid
from typing import List, Optional

from sqlalchemy.orm import Session

from rhesis.backend.app import models, schemas
from rhesis.backend.app.utils.crud_utils import (
create_item,
delete_item,
get_item_detail,
update_item,
)
from rhesis.backend.app.utils.query_utils import QueryBuilder, include

_TEST_RESULT_RELATED_FIELDS = (
include(models.TestResult.test_run),
include(models.TestResult.test),
include(models.TestResult.test, models.Test.prompt),
include(models.TestResult.test, models.Test.requirement),
)


def get_test_result(
db: Session, test_result_id: uuid.UUID, organization_id: str = None, user_id: str = None
) -> Optional[models.TestResult]:
"""Get test_result with relationships (tags, test, test_run) eagerly loaded."""
return get_item_detail(
db,
models.TestResult,
test_result_id,
organization_id=organization_id,
user_id=user_id,
related_fields=_TEST_RESULT_RELATED_FIELDS,
)


def get_test_results(
db: Session,
skip: int = 0,
limit: int = 10,
sort_by: str = "created_at",
sort_order: str = "desc",
filter: str | None = None,
organization_id: str = None,
user_id: str = None,
) -> List[models.TestResult]:
"""Get test_results with relationships (tags, test, test_run) eagerly loaded."""
return (
QueryBuilder(db, models.TestResult)
.with_related(*_TEST_RESULT_RELATED_FIELDS)
.with_default_derived_field_loads()
.with_organization_filter(organization_id)
.with_visibility_filter(user_id)
.with_odata_filter(filter)
.with_sorting(sort_by, sort_order)
.with_pagination(skip, limit)
.all()
)


def create_test_result(
db: Session,
test_result: schemas.TestResultCreate,
organization_id: str = None,
user_id: str = None,
) -> models.TestResult:
"""Create test_result."""
return create_item(db, models.TestResult, test_result, organization_id, user_id)


def update_test_result(
db: Session,
test_result_id: uuid.UUID,
test_result: schemas.TestResultUpdate,
organization_id: str = None,
user_id: str = None,
) -> Optional[models.TestResult]:
"""Update test_result."""
return update_item(db, models.TestResult, test_result_id, test_result, organization_id, user_id)


def delete_test_result(
db: Session, test_result_id: uuid.UUID, organization_id: str, user_id: str
) -> Optional[models.TestResult]:
return delete_item(
db, models.TestResult, test_result_id, organization_id=organization_id, user_id=user_id
)
23 changes: 12 additions & 11 deletions apps/backend/src/rhesis/backend/app/routers/test_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@
from sqlalchemy.orm import Session
from sqlalchemy.orm.attributes import flag_modified

from rhesis.backend.app import crud, models, schemas
from rhesis.backend.app import models, schemas
from rhesis.backend.app.auth.affordances import populate_review_permitted_actions
from rhesis.backend.app.auth.capabilities import Permission
from rhesis.backend.app.auth.principal import resolve_principal_from_request
from rhesis.backend.app.auth.rbac import authorize_object, project_id_from_scope
from rhesis.backend.app.auth.user_utils import require_current_user_or_token
from rhesis.backend.app.crud import file as file_crud
from rhesis.backend.app.crud import test_result as test_result_crud
from rhesis.backend.app.dependencies import (
get_tenant_context,
get_tenant_db_session,
Expand Down Expand Up @@ -92,7 +93,7 @@ def create_test_result(
)
test_result.status_id = status.id

return crud.create_test_result(
return test_result_crud.create_test_result(
db=db, test_result=test_result, organization_id=organization_id, user_id=user_id
)

Expand All @@ -117,7 +118,7 @@ def read_test_results(
):
"""Get all test results"""
organization_id, user_id = tenant_context
results = crud.get_test_results(
results = test_result_crud.get_test_results(
db,
skip=skip,
limit=limit,
Expand All @@ -143,7 +144,7 @@ def read_test_result(
):
"""Get a specific test result by ID"""
organization_id, user_id = tenant_context
db_test_result = crud.get_test_result(
db_test_result = test_result_crud.get_test_result(
db, test_result_id=test_result_id, organization_id=organization_id, user_id=user_id
)
if db_test_result is None:
Expand Down Expand Up @@ -173,7 +174,7 @@ def update_test_result(
automatically updated based on whether all metrics passed.
"""
organization_id, user_id = tenant_context
db_test_result = crud.get_test_result(
db_test_result = test_result_crud.get_test_result(
db, test_result_id=test_result_id, organization_id=organization_id, user_id=user_id
)
if db_test_result is None:
Expand Down Expand Up @@ -208,7 +209,7 @@ def update_test_result(
)
test_result.status_id = status.id

return crud.update_test_result(
return test_result_crud.update_test_result(
db=db,
test_result_id=test_result_id,
test_result=test_result,
Expand All @@ -227,7 +228,7 @@ def delete_test_result(
):
"""Delete a test result. Only the creator may delete their own result."""
organization_id, user_id = tenant_context
db_test_result = crud.get_test_result(
db_test_result = test_result_crud.get_test_result(
db, test_result_id=test_result_id, organization_id=organization_id, user_id=user_id
)
if db_test_result is None:
Expand All @@ -240,7 +241,7 @@ def delete_test_result(
):
raise HTTPException(status_code=403, detail="Not authorized to delete this test result")

return crud.delete_test_result(
return test_result_crud.delete_test_result(
db=db, test_result_id=test_result_id, organization_id=organization_id, user_id=user_id
)

Expand Down Expand Up @@ -272,7 +273,7 @@ def add_review(
organization_id, user_id = tenant_context

# Get the test result
db_test_result = crud.get_test_result(
db_test_result = test_result_crud.get_test_result(
db, test_result_id=test_result_id, organization_id=organization_id, user_id=user_id
)
if db_test_result is None:
Expand Down Expand Up @@ -373,7 +374,7 @@ def update_review(
organization_id, user_id = tenant_context

# Get the test result
db_test_result = crud.get_test_result(
db_test_result = test_result_crud.get_test_result(
db, test_result_id=test_result_id, organization_id=organization_id, user_id=user_id
)
if db_test_result is None:
Expand Down Expand Up @@ -496,7 +497,7 @@ def delete_review(
organization_id, user_id = tenant_context

# Get the test result
db_test_result = crud.get_test_result(
db_test_result = test_result_crud.get_test_result(
db, test_result_id=test_result_id, organization_id=organization_id, user_id=user_id
)
if db_test_result is None:
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/src/rhesis/backend/app/services/test_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from sqlalchemy.orm import Session

from rhesis.backend.app import crud, models, schemas
from rhesis.backend.app.crud import test_result as test_result_crud
from rhesis.backend.app.crud.metric import get_requirement_metrics
from rhesis.backend.app.crud.test_run import get_test_run, get_test_run_requirements

Expand Down Expand Up @@ -39,7 +40,7 @@ def get_test_results_for_test_run(
limit = 100 # Use maximum allowed limit

while True:
test_results_batch = crud.get_test_results(
test_results_batch = test_result_crud.get_test_results(
db, skip=skip, limit=limit, filter=filter_str, organization_id=organization_id
)
if not test_results_batch:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
if TYPE_CHECKING:
pass

from rhesis.backend.app import crud
from rhesis.backend.app.crud import test_result as test_result_crud
from rhesis.backend.app.crud.telemetry import get_trace_by_id
from rhesis.backend.app.dependencies import get_endpoint_service
from rhesis.backend.app.services.endpoint.result_processing import process_endpoint_result
Expand Down Expand Up @@ -363,7 +363,7 @@ async def get_output(
class TestResultOutput(OutputProvider):
"""Stored output from a previous TestResult -- works for any test type.

Uses crud.get_test_results() with OData filter for multi-tenant safe lookup.
Uses test_result_crud.get_test_results() with OData filter for multi-tenant safe lookup.
"""

def __init__(self, reference_test_run_id: str):
Expand All @@ -385,7 +385,7 @@ async def get_output(

# Reuse existing CRUD with OData filter (multi-tenant safe)
filter_str = f"test_run_id eq {self.reference_test_run_id} and test_id eq {test_id}"
results = crud.get_test_results(
results = test_result_crud.get_test_results(
db,
limit=1,
filter=filter_str,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@

from sqlalchemy.orm import Session

from rhesis.backend.app import crud, schemas
from rhesis.backend.app import schemas
from rhesis.backend.app.constants import TestResultStatus
from rhesis.backend.app.crud import file as file_crud
from rhesis.backend.app.crud import test_result as test_result_crud
from rhesis.backend.app.models.test import Test
from rhesis.backend.app.utils.crud_utils import get_or_create_status
from rhesis.backend.app.utils.response_extractor import has_http_error_in_result
Expand Down Expand Up @@ -71,7 +72,7 @@ def check_existing_result(
f"test_configuration_id eq {test_config_id} and "
f"test_run_id eq {test_run_id} and test_id eq {test_id}"
)
existing_results = crud.get_test_results(
existing_results = test_result_crud.get_test_results(
db, limit=1, filter=filter_str, organization_id=organization_id, user_id=user_id
)

Expand Down Expand Up @@ -305,7 +306,7 @@ def create_test_result_record(
}

try:
result = crud.create_test_result(
result = test_result_crud.create_test_result(
db,
schemas.TestResultCreate(**test_result_data),
organization_id=organization_id,
Expand Down
Loading
Loading