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