From 94a56f69e3aec739d6414c6f4ff316f47ab03d2f Mon Sep 17 00:00:00 2001 From: Aviral Saxena Date: Sat, 28 Feb 2026 00:47:29 +0530 Subject: [PATCH 1/3] Added regression tests --- .github/workflows/backend-ci.yml | 43 +++++++++++++++++ apps/backend/tests/test_api_schema.py | 24 ++++++++++ apps/backend/tests/test_regression.py | 46 +++++++++++++++++++ apps/frontend/src/app/_home/UploadButtons.tsx | 2 +- .../frontend/src/app/_home/WhatIsThisTool.tsx | 4 +- .../src/app/_layout/navigation-data.ts | 4 +- .../src/app/convert/dcm-to-bdis/page.tsx | 2 +- docs/assets/Package-Arch.svg | 2 +- docs/development/architecture.mdx | 2 +- docs/user-guide/quick-start.mdx | 2 +- 10 files changed, 122 insertions(+), 9 deletions(-) create mode 100644 .github/workflows/backend-ci.yml create mode 100644 apps/backend/tests/test_api_schema.py create mode 100644 apps/backend/tests/test_regression.py diff --git a/.github/workflows/backend-ci.yml b/.github/workflows/backend-ci.yml new file mode 100644 index 00000000..65801f3e --- /dev/null +++ b/.github/workflows/backend-ci.yml @@ -0,0 +1,43 @@ +name: Backend CI + +on: + push: + paths: + - "apps/backend/**" + - "package/**" + pull_request: + paths: + - "apps/backend/**" + - "package/**" + +jobs: + test: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: ["3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + cd apps/backend + python -m pip install --upgrade pip + pip install -r requirements.txt + pip install pytest pytest-regressions httpx + + - name: Install package (editable) + run: | + cd package + pip install -e . + + - name: Run tests + run: | + cd apps/backend + pytest -v \ No newline at end of file diff --git a/apps/backend/tests/test_api_schema.py b/apps/backend/tests/test_api_schema.py new file mode 100644 index 00000000..bac9a562 --- /dev/null +++ b/apps/backend/tests/test_api_schema.py @@ -0,0 +1,24 @@ +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + + +def test_bids_endpoint_returns_expected_schema(): + response = client.post( + "/api/report/process/bids", + data={"modality": "ASL"} + ) + + assert response.status_code == 200 + data = response.json() + + expected_keys = { + "basic_report", + "extended_report", + "asl_parameters", + "errors", + "warnings", + } + + assert expected_keys.issubset(data.keys()) \ No newline at end of file diff --git a/apps/backend/tests/test_regression.py b/apps/backend/tests/test_regression.py new file mode 100644 index 00000000..e2a42f6b --- /dev/null +++ b/apps/backend/tests/test_regression.py @@ -0,0 +1,46 @@ +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + + +def normalize_response(data: dict) -> dict: + """ + Remove volatile fields to ensure deterministic regression. + """ + data.pop("nifti_slice_number", None) + return data + + +def test_root_endpoint_regression(data_regression): + response = client.get("/") + assert response.status_code == 200 + + data = response.json() + data_regression.check(data) + + +def test_bids_endpoint_regression(data_regression): + response = client.post( + "/api/report/process/bids", + data={"modality": "ASL"} + ) + + assert response.status_code == 200 + + data = normalize_response(response.json()) + data_regression.check(data) + + +def test_dicom_invalid_file_returns_500(tmp_path): + file_path = tmp_path / "invalid.txt" + file_path.write_text("not a dicom") + + with open(file_path, "rb") as f: + response = client.post( + "/api/report/process/dicom", # fixed /api prefix + files={"dcm_files": ("invalid.txt", f, "text/plain")}, + data={"modality": "ASL"}, + ) + + assert response.status_code == 500 \ No newline at end of file diff --git a/apps/frontend/src/app/_home/UploadButtons.tsx b/apps/frontend/src/app/_home/UploadButtons.tsx index 5c141393..40de33ca 100644 --- a/apps/frontend/src/app/_home/UploadButtons.tsx +++ b/apps/frontend/src/app/_home/UploadButtons.tsx @@ -126,7 +126,7 @@ const UploadButtons = () => { )} onClick={() => setActiveFileTypeOption(UploadDataType.BIDS)} > - BDIS + BIDS diff --git a/apps/frontend/src/app/_layout/navigation-data.ts b/apps/frontend/src/app/_layout/navigation-data.ts index 2a52bb75..583e8059 100644 --- a/apps/frontend/src/app/_layout/navigation-data.ts +++ b/apps/frontend/src/app/_layout/navigation-data.ts @@ -42,8 +42,8 @@ const NavData: { countType: "warnings", }, { - title: "Convert DICOM to BDIS", - url: "/convert/dcm-to-bdis", + title: "Convert DICOM to BIDS", + url: "/convert/dcm-to-bids", icon: IconTransform, showCount: false, } diff --git a/apps/frontend/src/app/convert/dcm-to-bdis/page.tsx b/apps/frontend/src/app/convert/dcm-to-bdis/page.tsx index f5f34d2e..7185100a 100644 --- a/apps/frontend/src/app/convert/dcm-to-bdis/page.tsx +++ b/apps/frontend/src/app/convert/dcm-to-bdis/page.tsx @@ -2,7 +2,7 @@ const Page = () => { return (
-

DCM to BDIS Conversion

+

DCM to BIDS Conversion

This feature is under development.

); diff --git a/docs/assets/Package-Arch.svg b/docs/assets/Package-Arch.svg index 62e00c51..819d25f2 100644 --- a/docs/assets/Package-Arch.svg +++ b/docs/assets/Package-Arch.svg @@ -1,4 +1,4 @@ -
Core
IO
Modalities
Sequences
Converters
Utils
CLI
ASL
DSC
Processors
Validators
Utils
Readers
Writers
PCASL_siemens
CASL_ucl
DICOM to BDIS
Desktop App
Web App
CLI App
Tests
\ No newline at end of file +
Core
IO
Modalities
Sequences
Converters
Utils
CLI
ASL
DSC
Processors
Validators
Utils
Readers
Writers
PCASL_siemens
CASL_ucl
DICOM to BIDS
Desktop App
Web App
CLI App
Tests
\ No newline at end of file diff --git a/docs/development/architecture.mdx b/docs/development/architecture.mdx index a4306b26..fc508fe7 100644 --- a/docs/development/architecture.mdx +++ b/docs/development/architecture.mdx @@ -95,7 +95,7 @@ modalities/ #### Sequence Management -This Handles the extraction of BDIS metadata (e.g. asl.json) from DICOM for vendors and organization implementation of modalities +This Handles the extraction of BIDS metadata (e.g. asl.json) from DICOM for vendors and organization implementation of modalities ``` sequences/ diff --git a/docs/user-guide/quick-start.mdx b/docs/user-guide/quick-start.mdx index b337ac0b..af3cb8bb 100644 --- a/docs/user-guide/quick-start.mdx +++ b/docs/user-guide/quick-start.mdx @@ -58,7 +58,7 @@ If you have DICOM files: - Or drag and drop files directly 2. **Select Data Type**: - - Choose "BDIS" for BIDS format + - Choose "BIDS" for BIDS format - Choose "DICOM" for DICOM files 3. **Select Modality**: From de7bd9db06cd887bfe715127932bc33695752098 Mon Sep 17 00:00:00 2001 From: Aviral Saxena Date: Sat, 28 Feb 2026 03:08:58 +0530 Subject: [PATCH 2/3] Added end to end tests --- apps/backend/app/routers/reports.py | 13 ++- apps/backend/app/utils/sample_nifti.nii.gz | Bin 0 -> 75 bytes .../fixtures/real_sample/sub-Sub1_asl.json | 37 +++++++ .../real_sample/sub-Sub1_aslcontext.tsv | 21 ++++ .../fixtures/real_sample/sub-Sub1_m0scan.json | 23 +++++ apps/backend/tests/test_api_schema.py | 16 +-- apps/backend/tests/test_fixture_regression.py | 43 ++++++++ .../test_real_asl_fixture_regression.yml | 96 ++++++++++++++++++ apps/backend/tests/test_regression.py | 11 +- .../test_bids_endpoint_regression.yml | 17 ++++ .../test_root_endpoint_regression.yml | 17 ++++ apps/backend/tests/test_report.py | 20 ++-- .../{modaliy_enum.py => modality_enum.py} | 0 13 files changed, 281 insertions(+), 33 deletions(-) create mode 100644 apps/backend/app/utils/sample_nifti.nii.gz create mode 100644 apps/backend/tests/fixtures/real_sample/sub-Sub1_asl.json create mode 100644 apps/backend/tests/fixtures/real_sample/sub-Sub1_aslcontext.tsv create mode 100644 apps/backend/tests/fixtures/real_sample/sub-Sub1_m0scan.json create mode 100644 apps/backend/tests/test_fixture_regression.py create mode 100644 apps/backend/tests/test_fixture_regression/test_real_asl_fixture_regression.yml create mode 100644 apps/backend/tests/test_regression/test_bids_endpoint_regression.yml create mode 100644 apps/backend/tests/test_regression/test_root_endpoint_regression.yml rename package/src/pyaslreport/enums/{modaliy_enum.py => modality_enum.py} (100%) diff --git a/apps/backend/app/routers/reports.py b/apps/backend/app/routers/reports.py index 5b80ab8e..046f3a60 100644 --- a/apps/backend/app/routers/reports.py +++ b/apps/backend/app/routers/reports.py @@ -12,6 +12,8 @@ from weasyprint import HTML from app.utils.report_template import render_report_html from app.utils.lib import default_serializer, save_upload, remove_dir +from starlette.background import BackgroundTask +import os report_router = APIRouter(prefix="/report") @@ -122,12 +124,15 @@ async def get_report_dicom( @report_router.post("/report-pdf") async def download_pdf(report_data: dict): - print("--------------------------------") - print(report_data["report_data"]["asl_parameters"]) - print("--------------------------------") html_content = render_report_html(report_data["report_data"]) + with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp: HTML(string=html_content).write_pdf(tmp.name) tmp_path = tmp.name - return FileResponse(tmp_path, media_type="application/pdf", filename="report.pdf") + return FileResponse( + tmp_path, + media_type="application/pdf", + filename="report.pdf", + background=BackgroundTask(os.unlink, tmp_path) + ) \ No newline at end of file diff --git a/apps/backend/app/utils/sample_nifti.nii.gz b/apps/backend/app/utils/sample_nifti.nii.gz new file mode 100644 index 0000000000000000000000000000000000000000..4a8e8c65007b16a02a8c3f055600f4ba13493bf2 GIT binary patch literal 75 zcmb2|=3oE;mjB&}DGFQ$#v2+Gm6=% 1000 \ No newline at end of file diff --git a/package/src/pyaslreport/enums/modaliy_enum.py b/package/src/pyaslreport/enums/modality_enum.py similarity index 100% rename from package/src/pyaslreport/enums/modaliy_enum.py rename to package/src/pyaslreport/enums/modality_enum.py From 87e1f7c583f0035899106a67c634ac9a27b78f1b Mon Sep 17 00:00:00 2001 From: Aviral Saxena Date: Sat, 28 Feb 2026 03:17:56 +0530 Subject: [PATCH 3/3] Minor change --- apps/backend/tests/test_regression.py | 7 +------ .../test_bids_endpoint_regression.yml | 17 ----------------- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 apps/backend/tests/test_regression/test_bids_endpoint_regression.yml diff --git a/apps/backend/tests/test_regression.py b/apps/backend/tests/test_regression.py index e4cb6ed9..a897a965 100644 --- a/apps/backend/tests/test_regression.py +++ b/apps/backend/tests/test_regression.py @@ -20,18 +20,13 @@ def test_root_endpoint_regression(data_regression): data_regression.check(data) -def test_bids_endpoint_regression(data_regression): - response = client.get("/") - assert response.status_code == 200 - data_regression.check(response.json()) - def test_dicom_invalid_file_returns_500(tmp_path): file_path = tmp_path / "invalid.txt" file_path.write_text("not a dicom") with open(file_path, "rb") as f: response = client.post( - "/api/report/process/dicom", # fixed /api prefix + "/api/report/process/dicom", files={"dcm_files": ("invalid.txt", f, "text/plain")}, data={"modality": "ASL"}, ) diff --git a/apps/backend/tests/test_regression/test_bids_endpoint_regression.yml b/apps/backend/tests/test_regression/test_bids_endpoint_regression.yml deleted file mode 100644 index a887b6bb..00000000 --- a/apps/backend/tests/test_regression/test_bids_endpoint_regression.yml +++ /dev/null @@ -1,17 +0,0 @@ -Specs: - Framework: FastAPI - Operating System: OS Independent - Programming Language: Python -authors: -- Ibrahim Abdelazim: ibrahim.abdelazim@fau.de -- Hanliang Xu: hxu110@jh.edu -description: This service provides an API for generating ASL methods parameters based - on user input. It is designed to be used in conjunction with the ASL Methods Parameter - Generator frontend application. -license: MIT -name: ASL Methods Parameter Generator API Service -organization: The ISMRM Open Science Initiative for Perfusion Imaging -supervisors: -- Jan Petr -- David Thomas -version: 0.0.1