From c8f102a707dd4bf0727f3559b28bc499ab721e11 Mon Sep 17 00:00:00 2001 From: Raul Almeida Date: Thu, 16 Jul 2026 12:50:04 -0300 Subject: [PATCH 1/5] enh: add IGNORE_PERMISSIONS and hot reload to local development docker (frontend) --- .github/workflows/test_docker_compose.yml | 5 +- backend/test_observer/common/config.py | 2 +- docker-compose.yml | 27 ++++++++++- frontend/Dockerfile.dev | 49 +++++++++++++++++++ frontend/dev_entrypoint.sh | 57 +++++++++++++++++++++++ 5 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 frontend/Dockerfile.dev create mode 100755 frontend/dev_entrypoint.sh diff --git a/.github/workflows/test_docker_compose.yml b/.github/workflows/test_docker_compose.yml index 4f6f99631..94f58fc3f 100644 --- a/.github/workflows/test_docker_compose.yml +++ b/.github/workflows/test_docker_compose.yml @@ -69,7 +69,10 @@ jobs: - name: Test Frontend run: | echo "Testing frontend HTTP response..." - curl -f -I http://localhost:30001 || (echo "Frontend test failed" && exit 1) + # Use a GET (not HEAD/-I): the Flutter dev web-server used by + # docker-compose responds 404 to HEAD but 200 to GET. -o /dev/null + # discards the body so this stays a lightweight smoke test. + curl -f -s -o /dev/null http://localhost:30001 || (echo "Frontend test failed" && exit 1) - name: Test Database connectivity run: | diff --git a/backend/test_observer/common/config.py b/backend/test_observer/common/config.py index a199188ea..9a48a1b48 100644 --- a/backend/test_observer/common/config.py +++ b/backend/test_observer/common/config.py @@ -20,7 +20,7 @@ SAML_SP_BASE_URL = os.getenv("SAML_SP_BASE_URL", "http://localhost:30000") SAML_IDP_METADATA_URL = os.getenv( "SAML_IDP_METADATA_URL", - "http://localhost:8080/simplesaml/saml2/idp/metadata.php", + "http://localhost:8081/simplesaml/saml2/idp/metadata.php", ) SAML_SP_X509_CERT = os.getenv("SAML_SP_X509_CERT", "") SAML_SP_KEY = os.getenv("SAML_SP_KEY", "") diff --git a/docker-compose.yml b/docker-compose.yml index 15ae4c043..fab69b003 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -37,6 +37,7 @@ services: PYTHONDONTWRITEBYTECODE: "1" # Prevent .pyc files in development SESSIONS_HTTPS_ONLY: "false" REQUIRE_AUTHENTICATION: "${REQUIRE_AUTHENTICATION:-false}" + IGNORE_PERMISSIONS: "${IGNORE_PERMISSIONS:-view_user,change_user,view_team,change_team,add_application,change_application,view_application,view_permission,view_issue,change_issue,change_issue_attachment,change_issue_attachment_bulk,change_attachment_rule,change_auto_rerun,view_test,change_test,view_rerun,change_rerun,change_rerun_bulk,view_artefact,change_artefact,view_environment_review,change_environment_review,view_report,view_test_case_reported_issue,change_test_case_reported_issue,view_environment_reported_issue,change_environment_reported_issue,view_notification,change_notification}" USE_LOCAL_LOGIN: "${USE_LOCAL_LOGIN:-true}" volumes: # Mount source code from host for development @@ -48,14 +49,38 @@ services: image: test-observer-frontend build: context: ./frontend - dockerfile: Dockerfile + dockerfile: Dockerfile.dev ports: - "30001:80" # Adjust the port mapping as needed + # Keep stdin/tty open so you can `docker attach test-observer-frontend` + # and press "r" (hot reload) / "R" (hot restart) in the Flutter dev server. + stdin_open: true + tty: true depends_on: test-observer-api: condition: service_healthy saml-idp: condition: service_healthy + volumes: + # Mount source code from host for development (enables live code changes) + - ./frontend:/app + # Preserve container-managed build artifacts / package config so they + # don't clash with the host's (potentially different-platform) versions. + - /app/.dart_tool + - /app/build + # The dev container starts instantly but Flutter needs time to compile + # before it serves on :80. This healthcheck lets `docker compose up --wait` + # (used locally and in the test_docker_compose CI workflow) block until the + # app is actually being served, rather than merely "running". + healthcheck: + test: [ "CMD-SHELL", "curl -f http://localhost:80/ || exit 1" ] + interval: 10s + timeout: 5s + retries: 5 + # Generous start_period: the very first `flutter run` compilation can take + # ~30-60s (longer on a cold pub cache). Failures during this window don't + # count against the retry budget. + start_period: 180s test-observer-db: image: docker.io/postgres:14 diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev new file mode 100644 index 000000000..2d86090da --- /dev/null +++ b/frontend/Dockerfile.dev @@ -0,0 +1,49 @@ +# Copyright 2025 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# SPDX-FileCopyrightText: Copyright 2025 Canonical Ltd. +# SPDX-License-Identifier: GPL-3.0-only + +# Development image for the Test Observer frontend. +# +# Unlike the production Dockerfile, this image does NOT build a static release +# bundle. Instead the source is bind-mounted at runtime (see docker-compose.yml) +# and the container runs `flutter run` on a web server so that code changes are +# picked up without rebuilding the image. + +FROM ubuntu:24.04 + +ENV DEBIAN_FRONTEND=noninteractive +RUN apt-get update && \ + apt-get install -y \ + curl git wget unzip gdb libstdc++6 libglu1-mesa fonts-droid-fallback \ + python3 inotify-tools && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN git clone https://github.com/flutter/flutter.git /opt/flutter && \ + cd /opt/flutter && git checkout '3.29.3' && \ + /opt/flutter/bin/flutter doctor && \ + /opt/flutter/bin/flutter config --enable-web + +ENV PATH="$PATH:/opt/flutter/bin:/root/.pub-cache/bin" + +WORKDIR /app + +# Warm the pub cache so the first `flutter run` is fast. The source itself is +# bind-mounted at runtime, so only dependency manifests are copied here. +COPY pubspec.* /app/ +RUN flutter pub get + +EXPOSE 80 + +CMD ["./dev_entrypoint.sh"] diff --git a/frontend/dev_entrypoint.sh b/frontend/dev_entrypoint.sh new file mode 100755 index 000000000..b69847cc8 --- /dev/null +++ b/frontend/dev_entrypoint.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Copyright 2025 Canonical Ltd. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 3, as +# published by the Free Software Foundation. +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# SPDX-FileCopyrightText: Copyright 2025 Canonical Ltd. +# SPDX-License-Identifier: GPL-3.0-only + +set -e + +echo "Fetching Flutter dependencies..." +flutter pub get + +echo "Generating code (blocking, so *.g.dart / *.freezed.dart are fresh before first compile)..." +dart run build_runner build --delete-conflicting-outputs + +echo "Starting build_runner in watch mode (regenerates code on subsequent changes)..." +dart run build_runner watch --delete-conflicting-outputs & + +# Feed the Flutter dev server's stdin through a FIFO so we can drive it +# non-interactively. A file watcher writes "R" (hot restart) to the FIFO +# whenever a source file changes, giving automatic reload on save without +# needing to attach a TTY to the container. +FIFO=/tmp/flutter-stdin +rm -f "$FIFO" +mkfifo "$FIFO" +# Hold the FIFO open for writing so it never receives EOF. +exec 3<>"$FIFO" + +echo "Watching lib/ and web/ for changes to trigger automatic hot restart..." +( + while inotifywait -q -r -e modify,create,delete,move \ + --include '\.(dart|yaml|json|html)$' lib web >/dev/null 2>&1; do + # Debounce bursts of file events (e.g. build_runner regenerating files). + sleep 1 + echo "R" >&3 + done +) & + +echo "Starting Flutter web dev server (hot restart on save)..." +# --web-hostname 0.0.0.0 so the server is reachable from outside the container. +# You can also drive it manually: 'docker attach test-observer-frontend' and +# press "r"/"R" (hot restart) or "q" (quit). +exec flutter run \ + -d web-server \ + --web-hostname 0.0.0.0 \ + --web-port 80 \ + <&3 From ee55730b8711d6e0100a595468d3a473069e70e6 Mon Sep 17 00:00:00 2001 From: Raul Almeida Date: Thu, 16 Jul 2026 14:12:34 -0300 Subject: [PATCH 2/5] enh: copilot review --- docker-compose.yml | 4 ---- frontend/Dockerfile.dev | 11 ++++++++--- frontend/dev_entrypoint.sh | 2 -- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index fab69b003..887637ae6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -52,10 +52,6 @@ services: dockerfile: Dockerfile.dev ports: - "30001:80" # Adjust the port mapping as needed - # Keep stdin/tty open so you can `docker attach test-observer-frontend` - # and press "r" (hot reload) / "R" (hot restart) in the Flutter dev server. - stdin_open: true - tty: true depends_on: test-observer-api: condition: service_healthy diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev index 2d86090da..7a61d5f1e 100644 --- a/frontend/Dockerfile.dev +++ b/frontend/Dockerfile.dev @@ -30,8 +30,7 @@ RUN apt-get update && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* -RUN git clone https://github.com/flutter/flutter.git /opt/flutter && \ - cd /opt/flutter && git checkout '3.29.3' && \ +RUN git clone --depth 1 --branch '3.29.3' https://github.com/flutter/flutter.git /opt/flutter && \ /opt/flutter/bin/flutter doctor && \ /opt/flutter/bin/flutter config --enable-web @@ -44,6 +43,12 @@ WORKDIR /app COPY pubspec.* /app/ RUN flutter pub get +# Copy the entrypoint into the image so it works even without the runtime +# bind-mount, and invoke it via `bash` so it doesn't depend on the executable +# bit being preserved. In docker-compose the bind-mount overrides this copy +# with the live host version. +COPY dev_entrypoint.sh /app/dev_entrypoint.sh + EXPOSE 80 -CMD ["./dev_entrypoint.sh"] +CMD ["bash", "/app/dev_entrypoint.sh"] diff --git a/frontend/dev_entrypoint.sh b/frontend/dev_entrypoint.sh index b69847cc8..ed9357fa0 100755 --- a/frontend/dev_entrypoint.sh +++ b/frontend/dev_entrypoint.sh @@ -48,8 +48,6 @@ echo "Watching lib/ and web/ for changes to trigger automatic hot restart..." echo "Starting Flutter web dev server (hot restart on save)..." # --web-hostname 0.0.0.0 so the server is reachable from outside the container. -# You can also drive it manually: 'docker attach test-observer-frontend' and -# press "r"/"R" (hot restart) or "q" (quit). exec flutter run \ -d web-server \ --web-hostname 0.0.0.0 \ From de4f124b7f97f968bfdb9e517e83d60c54c13d9c Mon Sep 17 00:00:00 2001 From: Raul Almeida Date: Thu, 16 Jul 2026 15:29:35 -0300 Subject: [PATCH 3/5] fix: commited by accident config.py --- backend/test_observer/common/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/test_observer/common/config.py b/backend/test_observer/common/config.py index 9a48a1b48..a199188ea 100644 --- a/backend/test_observer/common/config.py +++ b/backend/test_observer/common/config.py @@ -20,7 +20,7 @@ SAML_SP_BASE_URL = os.getenv("SAML_SP_BASE_URL", "http://localhost:30000") SAML_IDP_METADATA_URL = os.getenv( "SAML_IDP_METADATA_URL", - "http://localhost:8081/simplesaml/saml2/idp/metadata.php", + "http://localhost:8080/simplesaml/saml2/idp/metadata.php", ) SAML_SP_X509_CERT = os.getenv("SAML_SP_X509_CERT", "") SAML_SP_KEY = os.getenv("SAML_SP_KEY", "") From b2ac7becbb2bc128654b7539fc35731a0ab06512 Mon Sep 17 00:00:00 2001 From: Raul Almeida Date: Tue, 21 Jul 2026 09:02:38 -0300 Subject: [PATCH 4/5] fix: IGNORE_PERMISSIONS for tests --- backend/tests/conftest.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index 359b88870..ac72e5427 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -34,6 +34,7 @@ drop_database, ) +from test_observer.common import config from test_observer.common.config import SESSIONS_SECRET from test_observer.common.enums import Permission from test_observer.controllers.applications.application_injection import ( @@ -46,6 +47,23 @@ from tests.data_generator import DataGenerator +@pytest.fixture(autouse=True) +def _clear_ignore_permissions(): + """ + Ensure permission checks are enforced during tests. + + The local development Docker environment sets IGNORE_PERMISSIONS to bypass + permission checks for convenience, but tests run inside that same container + and must exercise the real authorization logic. Clear the set in place (both + config and permissions modules reference the same object) and restore it + afterwards. + """ + original = set(config.IGNORE_PERMISSIONS) + config.IGNORE_PERMISSIONS.clear() + yield + config.IGNORE_PERMISSIONS.update(original) + + def _check_postgres_connection(host: str, port: int, user: str, password: str) -> bool: """Check if we can connect to PostgreSQL at the given host.""" try: From 076548a4db96b00e81018841ae498f6998b6062c Mon Sep 17 00:00:00 2001 From: Raul Almeida Date: Tue, 21 Jul 2026 09:32:50 -0300 Subject: [PATCH 5/5] fix: mypy --- backend/tests/conftest.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py index ac72e5427..efc8f413e 100644 --- a/backend/tests/conftest.py +++ b/backend/tests/conftest.py @@ -17,7 +17,7 @@ import json from base64 import b64encode -from collections.abc import Callable +from collections.abc import Callable, Iterator from contextlib import contextmanager from os import environ @@ -48,7 +48,7 @@ @pytest.fixture(autouse=True) -def _clear_ignore_permissions(): +def _clear_ignore_permissions() -> Iterator[None]: """ Ensure permission checks are enforced during tests.