Skip to content

Commit 8ca9cdf

Browse files
authored
Add integration-smoke workflow. (#72)
* Add integration-smoke test flow * fix CI check-manifest fail * fix integration workflow fail * fix token generation fail * fix django load fail * fix smoke tests fail * fix coderabbitai review
1 parent 13312b4 commit 8ca9cdf

15 files changed

Lines changed: 607 additions & 0 deletions
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
2+
#
3+
# SPDX-License-Identifier: BSL-1.0
4+
5+
name: Integration smoke
6+
7+
on:
8+
push:
9+
branches: [main, develop]
10+
pull_request:
11+
branches: [main, develop]
12+
13+
jobs:
14+
integration-smoke:
15+
runs-on: ubuntu-latest
16+
timeout-minutes: 15
17+
steps:
18+
# actions/checkout v6.0.2
19+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
20+
with:
21+
persist-credentials: false
22+
23+
# actions/setup-python v6.2.0
24+
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
25+
with:
26+
python-version: '3.12'
27+
28+
- name: Run integration smoke tests
29+
run: bash scripts/integration-smoke.sh
30+
31+
- name: Upload logs on failure
32+
if: failure()
33+
# actions/upload-artifact v4.6.2
34+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
35+
with:
36+
name: integration-smoke-logs
37+
path: /tmp/compose-logs.txt

docker/Dockerfile.weblate-plugin

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
2+
#
3+
# SPDX-License-Identifier: BSL-1.0
4+
5+
# Overlay image: stock Weblate + cppa-weblate-plugin installed into /app/venv.
6+
# CI builds with repo root as context (installs checked-out branch).
7+
# CD builds on the deploy server where the target branch is already checked out.
8+
9+
FROM weblate/weblate:latest
10+
11+
# Base image ends with USER 1000; installing into /app/venv requires root.
12+
USER root
13+
14+
ARG PLUGIN_GIT_URL=https://github.com/cppalliance/cppa-weblate-plugin.git
15+
ARG PLUGIN_GIT_REF=
16+
17+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
18+
19+
COPY src/boost_weblate/settings_override.py /app/data/settings-override.py
20+
21+
COPY . /tmp/plugin-src/
22+
RUN set -eux; \
23+
if [ ! -f /tmp/plugin-src/pyproject.toml ]; then \
24+
if [ -z "${PLUGIN_GIT_REF}" ]; then \
25+
echo "ERROR: No pyproject.toml in build context and PLUGIN_GIT_REF is unset"; exit 1; \
26+
fi; \
27+
rm -rf /tmp/plugin-src; \
28+
git clone --depth 1 --branch "${PLUGIN_GIT_REF}" "${PLUGIN_GIT_URL}" /tmp/plugin-src; \
29+
fi; \
30+
uv pip install --python /app/venv/bin/python /tmp/plugin-src; \
31+
rm -rf /tmp/plugin-src
32+
33+
USER 1000

docker/README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# docker/
2+
3+
Shared Docker assets for CI and CD.
4+
5+
- **Dockerfile.weblate-plugin** — Overlay on `weblate/weblate:latest`; installs the plugin via `uv pip install` and copies `settings-override.py`.
6+
- **docker-compose.yml** — PostgreSQL + Redis + Weblate stack. Override defaults via `.env` or environment variables.
7+
8+
## Usage
9+
10+
```bash
11+
# From repo root:
12+
docker compose -f docker/docker-compose.yml build
13+
docker compose -f docker/docker-compose.yml up -d
14+
```

docker/docker-compose.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
2+
#
3+
# SPDX-License-Identifier: BSL-1.0
4+
5+
# Shared Docker Compose stack for integration tests and CD deployments.
6+
# CI: docker compose -f docker/docker-compose.yml build && up
7+
# CD: same file, overridden via .env on the deploy server.
8+
9+
services:
10+
postgresql:
11+
image: postgres:16-alpine
12+
environment:
13+
POSTGRES_USER: weblate
14+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-weblate}
15+
POSTGRES_DB: weblate
16+
healthcheck:
17+
test: [CMD, pg_isready, -U, weblate]
18+
interval: 5s
19+
timeout: 3s
20+
retries: 10
21+
tmpfs:
22+
- /var/lib/postgresql/data
23+
24+
redis:
25+
image: redis:7-alpine
26+
healthcheck:
27+
test: [CMD, redis-cli, ping]
28+
interval: 5s
29+
timeout: 3s
30+
retries: 10
31+
32+
weblate:
33+
build:
34+
context: ..
35+
dockerfile: docker/Dockerfile.weblate-plugin
36+
ports:
37+
- ${WEBLATE_PORT:-8080}:8080
38+
depends_on:
39+
postgresql:
40+
condition: service_healthy
41+
redis:
42+
condition: service_healthy
43+
environment:
44+
WEBLATE_SITE_DOMAIN: ${WEBLATE_SITE_DOMAIN:-localhost:8080}
45+
WEBLATE_ADMIN_PASSWORD: ${WEBLATE_ADMIN_PASSWORD:-admin}
46+
WEBLATE_DEBUG: ${WEBLATE_DEBUG:-1}
47+
POSTGRES_HOST: postgresql
48+
POSTGRES_PORT: '5432'
49+
POSTGRES_USER: weblate
50+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-weblate}
51+
POSTGRES_DATABASE: weblate
52+
REDIS_HOST: redis
53+
REDIS_PORT: '6379'
54+
CELERY_SINGLE_PROCESS: '1'
55+
healthcheck:
56+
test: [CMD, curl, -f, http://localhost:8080/healthz/]
57+
interval: 10s
58+
timeout: 5s
59+
retries: 12
60+
start_period: 60s

pyproject.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ level = "cautious"
125125
unauthorized_licenses = []
126126

127127
[tool.pytest.ini_options]
128+
addopts = ["-m", "not integration"]
129+
markers = [
130+
"integration: requires live Weblate stack (Docker Compose) and optional WEBLATE_API_TOKEN"
131+
]
128132
python_classes = ["Test*"]
129133
python_files = ["test_*.py", "*_test.py"]
130134
pythonpath = ["src", "."]
@@ -141,9 +145,11 @@ select = ["E", "F", "I", "UP"]
141145
module-name = "boost_weblate"
142146
module-root = "src"
143147
source-include = [
148+
"docker/**",
144149
"docs/**",
145150
"LICENSES/**",
146151
"REUSE.toml",
152+
"scripts/**",
147153
"uv.lock",
148154
"tests/**"
149155
]

scripts/README.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# scripts/
2+
3+
Reusable shell scripts for CI and CD.
4+
5+
- **lib/compose.sh** — Sets `COMPOSE_FILE`, `COMPOSE_PROJECT_NAME`, exports `compose()` wrapper.
6+
- **lib/weblate-stack.sh** — Stack lifecycle functions: `stack_build`, `stack_up`, `stack_wait_healthy`, `stack_create_token`, `stack_logs`, `stack_down`.
7+
- **integration-smoke.sh** — CI entrypoint for P0 smoke tests (build, start, health-check, test, teardown).
8+
9+
## Usage
10+
11+
```bash
12+
# Run smoke tests locally:
13+
bash scripts/integration-smoke.sh
14+
15+
# Source the library for custom workflows:
16+
source scripts/lib/weblate-stack.sh
17+
stack_build
18+
stack_up
19+
stack_wait_healthy 120
20+
```

scripts/integration-smoke.sh

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#!/usr/bin/env bash
2+
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
3+
# SPDX-License-Identifier: BSL-1.0
4+
5+
# Integration smoke test entrypoint.
6+
# Builds the stack, waits for health, creates a token, runs smoke tests.
7+
# On exit (success or failure): collects logs and tears down the stack.
8+
9+
set -euo pipefail
10+
11+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
12+
# shellcheck source=lib/weblate-stack.sh
13+
source "${SCRIPT_DIR}/lib/weblate-stack.sh"
14+
15+
cleanup() {
16+
local exit_code=$?
17+
set +e
18+
echo "--- Collecting logs ---"
19+
stack_logs /tmp/compose-logs.txt
20+
echo "--- Tearing down stack ---"
21+
stack_down
22+
exit "$exit_code"
23+
}
24+
trap cleanup EXIT
25+
26+
echo "=== Building stack ==="
27+
stack_build
28+
29+
echo "=== Starting stack ==="
30+
stack_up
31+
32+
echo "=== Waiting for Weblate ==="
33+
stack_wait_healthy "${HEALTH_TIMEOUT:-120}"
34+
35+
echo "=== Creating API token ==="
36+
WEBLATE_API_TOKEN="$(stack_create_token admin)"
37+
export WEBLATE_API_TOKEN
38+
export WEBLATE_LIVE_BASE_URL="${WEBLATE_LIVE_BASE_URL:-http://localhost:${WEBLATE_PORT:-8080}}"
39+
export WEBLATE_COMPOSE_FILE="${COMPOSE_FILE}"
40+
export WEBLATE_COMPOSE_PROJECT="${COMPOSE_PROJECT_NAME}"
41+
42+
echo "=== Running smoke tests ==="
43+
pip install --quiet pytest
44+
# Do not load tests/conftest.py (Django host setup); integration tests only need pytest + stdlib.
45+
python -m pytest --confcutdir=tests/integration --override-ini addopts= tests/integration/test_smoke.py -v

scripts/lib/compose.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
#!/usr/bin/env bash
2+
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
3+
# SPDX-License-Identifier: BSL-1.0
4+
5+
# Shared compose wrapper sourced by other scripts.
6+
# Sets REPO_ROOT, COMPOSE_FILE, COMPOSE_PROJECT_NAME and exports compose().
7+
8+
set -euo pipefail
9+
10+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
11+
export REPO_ROOT
12+
13+
COMPOSE_FILE="${COMPOSE_FILE:-${REPO_ROOT}/docker/docker-compose.yml}"
14+
export COMPOSE_FILE
15+
16+
COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-cppa-weblate-plugin}"
17+
export COMPOSE_PROJECT_NAME
18+
19+
compose() {
20+
docker compose -f "$COMPOSE_FILE" -p "$COMPOSE_PROJECT_NAME" "$@"
21+
}

scripts/lib/weblate-stack.sh

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#!/usr/bin/env bash
2+
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
3+
# SPDX-License-Identifier: BSL-1.0
4+
5+
# Reusable functions for managing the Weblate Docker Compose stack.
6+
# Source this file from CI/CD scripts.
7+
8+
set -euo pipefail
9+
10+
SCRIPT_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
11+
# shellcheck source=compose.sh
12+
source "${SCRIPT_LIB_DIR}/compose.sh"
13+
14+
stack_build() {
15+
compose build "$@"
16+
}
17+
18+
stack_up() {
19+
compose up -d "$@"
20+
}
21+
22+
stack_wait_healthy() {
23+
local timeout="${1:-120}"
24+
local port="${WEBLATE_PORT:-8080}"
25+
local url="http://localhost:${port}/healthz/"
26+
local interval=5
27+
local elapsed=0
28+
29+
echo "Waiting for Weblate at ${url} (timeout: ${timeout}s)..."
30+
while [ "$elapsed" -lt "$timeout" ]; do
31+
if curl -sf "$url" > /dev/null 2>&1; then
32+
echo "Weblate is healthy (after ${elapsed}s)."
33+
return 0
34+
fi
35+
sleep "$interval"
36+
elapsed=$((elapsed + interval))
37+
done
38+
39+
echo "ERROR: Weblate did not become healthy in ${timeout}s."
40+
echo "--- weblate container logs ---"
41+
compose logs weblate | tail -80
42+
return 1
43+
}
44+
45+
stack_create_token() {
46+
local user="${1:-admin}"
47+
# Use python -c (not `weblate shell`) so stdout is only the key
48+
compose exec -T -e "WEBLATE_CI_USERNAME=${user}" weblate \
49+
/app/venv/bin/python -c \
50+
'import os
51+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "weblate.settings_docker")
52+
import django
53+
django.setup()
54+
from weblate.auth.models import User
55+
from rest_framework.authtoken.models import Token
56+
from weblate.utils.token import get_token
57+
u = User.objects.get(username=os.environ["WEBLATE_CI_USERNAME"])
58+
Token.objects.filter(user=u).delete()
59+
t = Token.objects.create(user=u, key=get_token("wlp" if u.is_bot else "wlu"))
60+
print(t.key)'
61+
}
62+
63+
stack_logs() {
64+
local file="${1:-}"
65+
if [ -n "$file" ]; then
66+
compose logs > "$file" 2>&1 || true
67+
else
68+
compose logs
69+
fi
70+
}
71+
72+
stack_down() {
73+
compose down -v --remove-orphans 2>/dev/null || true
74+
}

tests/integration/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)