From 93bc6c6b696b65d746170a9ea324e9cc2e2acbb6 Mon Sep 17 00:00:00 2001 From: "Ware, Joseph (DLSLtd,RAL,LSCI)" <53935796+DiamondJoseph@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:19:30 +0100 Subject: [PATCH 1/4] test: Add system tests that use NumTracker --- .github/workflows/_system_test.yml | 14 ++++++++------ dev-requirements.txt | 2 +- docs/tutorials/quickstart.md | 6 ------ docs/tutorials/run-bus.md | 6 +++--- pyproject.toml | 2 +- src/script/start_rabbitmq.sh | 16 ---------------- tests/system_tests/compose.yaml | 18 ++++++++++++++++++ tests/system_tests/config.yaml | 4 ++++ .../system_tests/services/rabbitmq_plugins | 0 tests/system_tests/test_blueapi_system.py | 17 +++++++++++------ 10 files changed, 46 insertions(+), 39 deletions(-) delete mode 100755 src/script/start_rabbitmq.sh create mode 100644 tests/system_tests/compose.yaml rename src/script/rabbitmq_setup/enabled_plugins => tests/system_tests/services/rabbitmq_plugins (100%) diff --git a/.github/workflows/_system_test.yml b/.github/workflows/_system_test.yml index e3c123ed0..e6a37b0fc 100644 --- a/.github/workflows/_system_test.yml +++ b/.github/workflows/_system_test.yml @@ -25,20 +25,22 @@ jobs: repository: epics-containers/example-services path: example-services - - name: Run docker compose + - name: Compose devices uses: hoverkraft-tech/compose-action@40041ff1b97dbf152cd2361138c2b03fa29139df # v2.3.0 with: - compose-file: "./example-services/compose.yaml" + compose-file: "example-services/compose.yaml" services: | bl01t-di-cam-01 bl01t-mo-sim-01 ca-gateway - - name: Start RabbitMQ - uses: namoshek/rabbitmq-github-action@d1d4455f4a8f72db66111c24cb0dc5654047a975 # v1 + - name: Compose services + uses: hoverkraft-tech/compose-action@40041ff1b97dbf152cd2361138c2b03fa29139df # v2.3.0 with: - ports: "61613:61613" - plugins: rabbitmq_stomp + compose-file: "tests/system_tests/compose.yaml" + services: | + rabbitmq + numtracker - name: Start Blueapi Server env: diff --git a/dev-requirements.txt b/dev-requirements.txt index ec31eca59..4ea2cc703 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -32,7 +32,7 @@ deepdiff==8.5.0 deepmerge==2.0 Deprecated==1.2.18 distlib==0.3.9 -dls-dodal==1.51.0 +dls-dodal==1.53.0 dnspython==2.7.0 docopt==0.6.2 docutils==0.21.2 diff --git a/docs/tutorials/quickstart.md b/docs/tutorials/quickstart.md index ffa3617f4..c4d783a21 100644 --- a/docs/tutorials/quickstart.md +++ b/docs/tutorials/quickstart.md @@ -18,12 +18,6 @@ The worker can also be started using a custom config file: blueapi --config path/to/file serve ``` -An example of a config file that starts STOMP with default values can be found in: - -``` - src/script/stomp_config.yml -``` - ## Test that the Worker is Running Blueapi comes with a CLI so that you can query and control the worker from the terminal. diff --git a/docs/tutorials/run-bus.md b/docs/tutorials/run-bus.md index 85510ced2..f86189b37 100644 --- a/docs/tutorials/run-bus.md +++ b/docs/tutorials/run-bus.md @@ -5,10 +5,10 @@ Blueapi can publish updates to a message bus asynchronously, the CLI can then vi ## Start RabbitMQ The worker requires a running instance of RabbitMQ. The easiest way to start it is - to execute the provided script: + to `compose` the services in `tests/system_tests/compose.yaml` -``` - src/script/start_rabbitmq.sh +```sh + docker compose run --detach -f tests/system_tests/compose.yaml rabbitmq ``` ## Config File diff --git a/pyproject.toml b/pyproject.toml index b8f06c238..d7f9cfd38 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ dependencies = [ "fastapi>=0.112.0", "uvicorn", "requests", - "dls-dodal>=1.51.0", + "dls-dodal>=1.53.0", "super-state-machine", # https://github.com/DiamondLightSource/blueapi/issues/553 "GitPython", "event-model==1.23", # https://github.com/DiamondLightSource/blueapi/issues/684 diff --git a/src/script/start_rabbitmq.sh b/src/script/start_rabbitmq.sh deleted file mode 100755 index 03136f4c6..000000000 --- a/src/script/start_rabbitmq.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -SCRIPT_DIR=$( cd -- "$( dirname -- "$BASH_SOURCE[0]" )" &> /dev/null && pwd ) -RABBITMQ_VERSION="docker.io/rabbitmq:4.0-management" -cmd1='run -it --rm --name rabbitmq -v '\ -$SCRIPT_DIR'/rabbitmq_setup/enabled_plugins:/etc/rabbitmq/enabled_plugins:z'\ -' -p 5672:5672 -p 15672:15672 -p 61613:61613 '$RABBITMQ_VERSION - -echo "Checking docker/podman installation" -if command -v docker &> /dev/null; then - docker $cmd1 -elif command -v podman &> /dev/null; then - podman $cmd1 -else - echo "Docker/Podman installation not found. Please install docker/podman." - exit 1 -fi diff --git a/tests/system_tests/compose.yaml b/tests/system_tests/compose.yaml new file mode 100644 index 000000000..170564334 --- /dev/null +++ b/tests/system_tests/compose.yaml @@ -0,0 +1,18 @@ +services: + numtracker: + image: ghcr.io/diamondlightsource/numtracker:1.0.1 + ports: + - "8001:8000" + post_start: + - command: /app/numtracker client configure adsim --directory '/tmp/' --scan '{instrument}-{scan_number}' --detector '{instrument}-{scan_number}-{detector}' + + rabbitmq: + image: docker.io/rabbitmq:4.0-management + ports: + - "5672:5672" + - "15672:15672" + - "61613:61613" + volumes: + - type: bind + source: ./services/rabbitmq_plugins + target: /etc/rabbitmq/enabled_plugins diff --git a/tests/system_tests/config.yaml b/tests/system_tests/config.yaml index d99358bb3..5f49a1e46 100644 --- a/tests/system_tests/config.yaml +++ b/tests/system_tests/config.yaml @@ -1,6 +1,8 @@ api: url: http://0.0.0.0:8000 env: + metadata: + instrument: adsim sources: - kind: dodal module: dodal.beamlines.adsim @@ -11,3 +13,5 @@ env: stomp: enabled: true url: tcp://localhost:61613/ +numtracker: + url: http://localhost:8001/graphql diff --git a/src/script/rabbitmq_setup/enabled_plugins b/tests/system_tests/services/rabbitmq_plugins similarity index 100% rename from src/script/rabbitmq_setup/enabled_plugins rename to tests/system_tests/services/rabbitmq_plugins diff --git a/tests/system_tests/test_blueapi_system.py b/tests/system_tests/test_blueapi_system.py index bb1fbfa28..ce04bfbdf 100644 --- a/tests/system_tests/test_blueapi_system.py +++ b/tests/system_tests/test_blueapi_system.py @@ -51,13 +51,18 @@ To enable and execute these tests, set `REQUIRES_AUTH=1` and provide valid credentials. """ -# Step 1: Ensure a message bus that supports stomp is running and available: -# src/script/start_rabbitmq.sh +# Start devices +# 1. clone https://github.com/epics-containers/example-services +# 2. cd example-services +# 2. docker compose up bl01t-di-cam-01 bl01t-mo-sim-01 ca-gateway # -# Step 2: Start the BlueAPI server with valid configuration: -# blueapi -c tests/system_tests/config.yaml serve +# Start services +# docker compose up # -# Step 3: Run the system tests using tox: +# Start blueapi server +# blueapi -c config.yaml serve +# +# Run the system tests using tox: # tox -e system-test @@ -329,7 +334,7 @@ def test_progress_with_stomp(client_with_stomp: BlueapiClient): def on_event(event: AnyEvent): all_events.append(event) - client_with_stomp.run_task(_SIMPLE_TASK, on_event=on_event) + client_with_stomp.run_task(_SIMPLE_TASK, on_event=on_event, timeout=10) assert isinstance(all_events[0], WorkerEvent) and all_events[0].task_status task_id = all_events[0].task_status.task_id assert all_events == [ From b87d95c35ef0276e93386ad8cf007dbc78c6b1d1 Mon Sep 17 00:00:00 2001 From: "Ware, Joseph (DLSLtd,RAL,LSCI)" <53935796+DiamondJoseph@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:31:16 +0100 Subject: [PATCH 2/4] Add docs --- tests/system_tests/test_blueapi_system.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/tests/system_tests/test_blueapi_system.py b/tests/system_tests/test_blueapi_system.py index ce04bfbdf..ddfd1220a 100644 --- a/tests/system_tests/test_blueapi_system.py +++ b/tests/system_tests/test_blueapi_system.py @@ -52,18 +52,26 @@ """ # Start devices -# 1. clone https://github.com/epics-containers/example-services -# 2. cd example-services -# 2. docker compose up bl01t-di-cam-01 bl01t-mo-sim-01 ca-gateway +# 1. $ git clone https://github.com/epics-containers/example-services +# 2. $ docker compose -f example-services/compose.yaml up \ +# bl01t-di-cam-01 bl01t-mo-sim-01 ca-gateway --detach # # Start services -# docker compose up +# in this directory (i.e. blueapi/tests/system_tests) +# $ docker compose up --detach # -# Start blueapi server -# blueapi -c config.yaml serve +# Start blueapi server configured to talk via the ca-gateway +# $ EPICS_CA_NAME_SERVERS=127.0.0.1:5094 EPICS_PVA_NAME_SERVERS=127.0.0.1:5095 \ +# EPICS_CA_ADDR_LIST=127.0.0.1:5094 blueapi -c config.yaml serve # # Run the system tests using tox: -# tox -e system-test +# $ tox -e system-test +# +# Tear down +# Tear down blueapi by passing SIGINT in the console where it was started (ctrl+c) +# Remove the containers and networking configured by the compose files: +# $ docker compose -f example-services/compose.yaml down +# $ docker compose down @pytest.fixture From 25aa229215d56ea3ebab759c6cc7a2f8b8c77d08 Mon Sep 17 00:00:00 2001 From: "Ware, Joseph (DLSLtd,RAL,LSCI)" <53935796+DiamondJoseph@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:52:40 +0100 Subject: [PATCH 3/4] tests: Add tests that use authentication --- tests/system_tests/compose.yaml | 29 ++++++++++++++++ .../system_tests/services/clients-config.json | 13 ++++++++ tests/system_tests/services/rabbitmq.conf | 2 ++ .../system_tests/services/server-config.json | 10 ++++++ tests/system_tests/services/users-config.json | 19 +++++++++++ tests/system_tests/test_blueapi_system.py | 33 +++++-------------- 6 files changed, 82 insertions(+), 24 deletions(-) create mode 100644 tests/system_tests/services/clients-config.json create mode 100644 tests/system_tests/services/rabbitmq.conf create mode 100644 tests/system_tests/services/server-config.json create mode 100644 tests/system_tests/services/users-config.json diff --git a/tests/system_tests/compose.yaml b/tests/system_tests/compose.yaml index 170564334..dcc2d05b0 100644 --- a/tests/system_tests/compose.yaml +++ b/tests/system_tests/compose.yaml @@ -16,3 +16,32 @@ services: - type: bind source: ./services/rabbitmq_plugins target: /etc/rabbitmq/enabled_plugins + - type: bind + source: ./services/rabbitmq.conf + target: /etc/rabbitmq/rabbitmq.conf + + oidc-server-mock: + image: ghcr.io/soluto/oidc-server-mock:latest + ports: + - '4011:8080' + environment: + ASPNETCORE_ENVIRONMENT: Development + SERVER_OPTIONS_PATH: /tmp/config/server-config.json + USERS_CONFIGURATION_PATH: /tmp/config/users-config.json + CLIENTS_CONFIGURATION_PATH: /tmp/config/clients-config.json + ASPNET_SERVICES_OPTIONS_INLINE: | + { + "ForwardedHeadersOptions": { + "ForwardedHeaders" : "All" + } + } + volumes: + - type: bind + source: ./services/clients-config.json + target: /tmp/config/clients-config.json + - type: bind + source: ./services/users-config.json + target: /tmp/config/users-config.json + - type: bind + source: ./services/server-config.json + target: /tmp/config/server-config.json diff --git a/tests/system_tests/services/clients-config.json b/tests/system_tests/services/clients-config.json new file mode 100644 index 000000000..02915d5b2 --- /dev/null +++ b/tests/system_tests/services/clients-config.json @@ -0,0 +1,13 @@ +[ + { + "ClientId": "blueapi-cli", + "ClientSecrets": [ + "blueapi-cli-secret" + ], + "Description": "Client for command-line access", + "AllowedScopes": [ + "fedid", + "email" + ] + } +] diff --git a/tests/system_tests/services/rabbitmq.conf b/tests/system_tests/services/rabbitmq.conf new file mode 100644 index 000000000..5a143379a --- /dev/null +++ b/tests/system_tests/services/rabbitmq.conf @@ -0,0 +1,2 @@ +stomp.default_user = guest +stomp.default_pass = guest diff --git a/tests/system_tests/services/server-config.json b/tests/system_tests/services/server-config.json new file mode 100644 index 000000000..111433e93 --- /dev/null +++ b/tests/system_tests/services/server-config.json @@ -0,0 +1,10 @@ +{ + "AccessTokenJwtType": "JWT", + "Discovery": { + "ShowKeySet": true + }, + "Authentication": { + "CookieSameSiteMode": "Lax", + "CheckSessionCookieSameSiteMode": "Lax" + } +} diff --git a/tests/system_tests/services/users-config.json b/tests/system_tests/services/users-config.json new file mode 100644 index 000000000..f8ba938f4 --- /dev/null +++ b/tests/system_tests/services/users-config.json @@ -0,0 +1,19 @@ +[ + { + "SubjectId": "1", + "Username": "aa1", + "Password": "alice", + "Claims": [ + { + "Type": "email", + "Value": "alice@example.com", + "ValueType": "string" + }, + { + "Type": "fedid", + "Value": "aa1", + "ValueType": "string" + } + ] + } +] diff --git a/tests/system_tests/test_blueapi_system.py b/tests/system_tests/test_blueapi_system.py index ddfd1220a..0c7f95291 100644 --- a/tests/system_tests/test_blueapi_system.py +++ b/tests/system_tests/test_blueapi_system.py @@ -3,7 +3,6 @@ from pathlib import Path import pytest -from bluesky_stomp.models import BasicAuthentication from pydantic import TypeAdapter from requests.exceptions import ConnectionError from scanspec.specs import Line @@ -44,13 +43,6 @@ _DATA_PATH = Path(__file__).parent -_REQUIRES_AUTH_MESSAGE = """ -Authentication credentials are required to run this test. -The test has been skipped because authentication is currently disabled. -For more details, see: https://github.com/DiamondLightSource/blueapi/issues/676. -To enable and execute these tests, set `REQUIRES_AUTH=1` and provide valid credentials. -""" - # Start devices # 1. $ git clone https://github.com/epics-containers/example-services # 2. $ docker compose -f example-services/compose.yaml up \ @@ -76,17 +68,12 @@ @pytest.fixture def client_without_auth(tmp_path: Path) -> BlueapiClient: - return BlueapiClient.from_config(config=ApplicationConfig(auth_token_path=tmp_path)) - - -@pytest.fixture -def client_with_stomp() -> BlueapiClient: return BlueapiClient.from_config( config=ApplicationConfig( + auth_token_path=tmp_path, stomp=StompConfig( enabled=True, - auth=BasicAuthentication(username="guest", password="guest"), # type: ignore - ) + ), ) ) @@ -105,10 +92,10 @@ def wait_for_server(): raise TimeoutError("No connection to the blueapi server") -# This client will have auth enabled if it finds cached valid token @pytest.fixture -def client() -> BlueapiClient: - return BlueapiClient.from_config(config=ApplicationConfig()) +def client(client_without_auth: BlueapiClient) -> BlueapiClient: + # TODO: Authenticate! + return client_without_auth @pytest.fixture @@ -148,7 +135,6 @@ def clean_existing_tasks(client: BlueapiClient): yield -@pytest.mark.xfail(reason=_REQUIRES_AUTH_MESSAGE) def test_cannot_access_endpoints( client_without_auth: BlueapiClient, blueapi_client_get_methods: list[str] ): @@ -160,7 +146,6 @@ def test_cannot_access_endpoints( getattr(client_without_auth, get_method)() -@pytest.mark.xfail(reason=_REQUIRES_AUTH_MESSAGE) def test_can_get_oidc_config_without_auth(client_without_auth: BlueapiClient): assert client_without_auth.get_oidc_config() == OIDCConfig( well_known_url="https://example.com/realms/master/.well-known/openid-configuration", @@ -336,13 +321,13 @@ def test_get_task_by_status(client: BlueapiClient): client.clear_task(task_id=task_2.task_id) -def test_progress_with_stomp(client_with_stomp: BlueapiClient): +def test_progress_with_stomp(client: BlueapiClient): all_events: list[AnyEvent] = [] def on_event(event: AnyEvent): all_events.append(event) - client_with_stomp.run_task(_SIMPLE_TASK, on_event=on_event, timeout=10) + client.run_task(_SIMPLE_TASK, on_event=on_event, timeout=10) assert isinstance(all_events[0], WorkerEvent) and all_events[0].task_status task_id = all_events[0].task_status.task_id assert all_events == [ @@ -420,7 +405,7 @@ def test_delete_current_environment(client: BlueapiClient): ), ], ) -def test_plan_runs(client_with_stomp: BlueapiClient, task: TaskRequest): - final_event = client_with_stomp.run_task(task) +def test_plan_runs(client: BlueapiClient, task: TaskRequest): + final_event = client.run_task(task) assert final_event.is_complete() and not final_event.is_error() assert final_event.state is WorkerState.IDLE From f4cce04ee9924a329ea1e1dbf2f99e00479d7d5c Mon Sep 17 00:00:00 2001 From: "Ware, Joseph (DLSLtd,RAL,LSCI)" <53935796+DiamondJoseph@users.noreply.github.com> Date: Tue, 15 Jul 2025 13:59:46 +0100 Subject: [PATCH 4/4] Configure server to use OIDC provider --- src/blueapi/config.py | 4 ++-- tests/system_tests/config.yaml | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/blueapi/config.py b/src/blueapi/config.py index 977722cff..bc4f1b294 100644 --- a/src/blueapi/config.py +++ b/src/blueapi/config.py @@ -157,7 +157,7 @@ class ScratchConfig(BlueapiBaseModel): class OIDCConfig(BlueapiBaseModel): - well_known_url: str = Field( + well_known_url: HttpUrl = Field( description="URL to fetch OIDC config from the provider" ) client_id: str = Field(description="Client ID") @@ -165,7 +165,7 @@ class OIDCConfig(BlueapiBaseModel): @cached_property def _config_from_oidc_url(self) -> dict[str, Any]: - response: requests.Response = requests.get(self.well_known_url) + response: requests.Response = requests.get(str(self.well_known_url)) response.raise_for_status() return response.json() diff --git a/tests/system_tests/config.yaml b/tests/system_tests/config.yaml index 5f49a1e46..85dfaf2ed 100644 --- a/tests/system_tests/config.yaml +++ b/tests/system_tests/config.yaml @@ -15,3 +15,6 @@ stomp: url: tcp://localhost:61613/ numtracker: url: http://localhost:8001/graphql +oidc: + well_known_url: http://localhost:4011/.well-known/openid-configuration + client_id: blueapi-cli