diff --git a/src/backend/api/routers/measurements.py b/src/backend/api/routers/measurements.py index 39d26b3..e291153 100644 --- a/src/backend/api/routers/measurements.py +++ b/src/backend/api/routers/measurements.py @@ -274,8 +274,11 @@ async def export_measurements_csv( db: AsyncSession = Depends(get_db), ): """Export measurements to CSV with configurable delimiter and decimal separator.""" - # Check user has MeasurementTec or Metrologist role - if not (user.has_role("MeasurementTec") or user.has_role("Metrologist")): + # Check user has MeasurementTec or Metrologist role, same as require_role: an + # admin with no explicit role assigned is still let through (#36 point 3). + if not ( + user.has_role("MeasurementTec") or user.has_role("Metrologist") or user.is_admin + ): raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="MeasurementTec or Metrologist role required", diff --git a/src/backend/tests/test_measurements.py b/src/backend/tests/test_measurements.py index 4c11711..e8d5d98 100644 --- a/src/backend/tests/test_measurements.py +++ b/src/backend/tests/test_measurements.py @@ -10,7 +10,7 @@ from src.backend.models.orm.user import User from src.backend.models.orm.recipe import RecipeVersion from src.backend.models.orm.task import RecipeTask, RecipeSubtask from src.backend.services import auth_service -from src.backend.tests.conftest import auth_headers, create_test_recipe +from src.backend.tests.conftest import _create_user, auth_headers, create_test_recipe async def _get_subtask_and_version( @@ -648,7 +648,34 @@ class TestMeasurementStatistics: class TestExportCsv: - """GET /api/measurements/export/csv tests (gitea#22).""" + """GET /api/measurements/export/csv tests (gitea#22, gitea#36 point 3).""" + + async def test_admin_without_explicit_role_can_export( + self, client: AsyncClient, db_session: AsyncSession, + ): + """An admin with no MeasurementTec/Metrologist role must not get 403 (#36.3). + + admin_user (conftest) already has both roles, which would pass even + without the fix: a bare admin (roles=[]) is the actual regression case. + """ + bare_admin = await _create_user( + db_session, username="bare_admin", roles=[], is_admin=True, + ) + resp = await client.get( + "/api/measurements/export/csv", + headers=auth_headers(bare_admin), + ) + assert resp.status_code == 200 + + async def test_maker_without_role_still_forbidden( + self, client: AsyncClient, maker_user: User, + ): + """A plain Maker (no MeasurementTec/Metrologist/admin) still gets 403.""" + resp = await client.get( + "/api/measurements/export/csv", + headers=auth_headers(maker_user), + ) + assert resp.status_code == 403 async def test_lot_number_formula_is_neutralised( self,