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/tests/conftest.py b/backend/tests/conftest.py index 359b88870..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 @@ -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() -> Iterator[None]: + """ + 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: diff --git a/docker-compose.yml b/docker-compose.yml index 15ae4c043..887637ae6 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,7 +49,7 @@ services: image: test-observer-frontend build: context: ./frontend - dockerfile: Dockerfile + dockerfile: Dockerfile.dev ports: - "30001:80" # Adjust the port mapping as needed depends_on: @@ -56,6 +57,26 @@ services: 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..7a61d5f1e --- /dev/null +++ b/frontend/Dockerfile.dev @@ -0,0 +1,54 @@ +# 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 --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 + +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 + +# 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 ["bash", "/app/dev_entrypoint.sh"] diff --git a/frontend/dev_entrypoint.sh b/frontend/dev_entrypoint.sh new file mode 100755 index 000000000..ed9357fa0 --- /dev/null +++ b/frontend/dev_entrypoint.sh @@ -0,0 +1,55 @@ +#!/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. +exec flutter run \ + -d web-server \ + --web-hostname 0.0.0.0 \ + --web-port 80 \ + <&3