Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/WORKFLOWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ GitHub Actions and CI/CD helpers for this repository (see [`.github/`](../.githu
| [`workflows/release.yml`](workflows/release.yml) | **Release** — manual `workflow_dispatch` only; tags `main` from `pyproject.toml` (`v<version>`) and creates a GitHub Release with Weblate compatibility metadata |
| [`workflows/ci-lint.yml`](workflows/ci-lint.yml) | Lint and format (prek) |
| [`workflows/ci-test.yml`](workflows/ci-test.yml) | Unit tests and coverage (Python **3.12**, **3.13**, **3.14** matrix; `fail-fast: false`) |
| [`workflows/ci-postgres.yml`](workflows/ci-postgres.yml) | DB-backed integration tests (`pytest -m postgres`; Python **3.12**; PostgreSQL service container) |
| [`workflows/ci-benchmark.yml`](workflows/ci-benchmark.yml) | QuickBook parser benchmarks (`pytest-benchmark`; JSON artifact; regression gate vs `.benchmarks/`) |
| [`workflows/ci-package.yml`](workflows/ci-package.yml) | Build and package checks |
| [`workflows/ci-dependencies.yml`](workflows/ci-dependencies.yml) | Dependency and license audit |
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/ci-postgres.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
#
# SPDX-License-Identifier: BSL-1.0

name: PostgreSQL tests

on:
workflow_call:

permissions:
contents: read

jobs:
postgres:
name: PostgreSQL tests (Python 3.12)
runs-on: ubuntu-latest
timeout-minutes: 15
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_USER: weblate
POSTGRES_PASSWORD: weblate
POSTGRES_DB: weblate
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 5s
--health-timeout 3s
--health-retries 10
steps:
# actions/checkout v6.0.2
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
with:
persist-credentials: false
# actions/setup-python v6.2.0
- name: Setup Python
uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1
with:
python-version: '3.12'
# astral-sh/setup-uv v8.1.0
- uses: astral-sh/setup-uv@d31148d669074a8d0a63714ba94f3201e7020bc3
with:
version: 0.11.12
- name: Install apt dependencies (Weblate venv)
run: sudo ./.github/ci/apt-install
- name: Install dependencies (incl. dev)
run: uv sync --frozen --group dev
- name: Pytest postgres tier
env:
DJANGO_SETTINGS_MODULE: tests.django_integration_settings
CI_DB_HOST: 127.0.0.1
CI_DB_USER: weblate
CI_DB_PASSWORD: weblate
CI_DB_NAME: weblate
run: >
uv run --group dev pytest -m postgres -v --tb=short --reuse-db
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ jobs:
uses: ./.github/workflows/ci-lint.yml
test:
uses: ./.github/workflows/ci-test.yml
postgres:
uses: ./.github/workflows/ci-postgres.yml
benchmark:
needs: [test]
uses: ./.github/workflows/ci-benchmark.yml
Expand Down
28 changes: 25 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ Hooks include Ruff (lint + format), YAML/TOML checks, REUSE license compliance,

## Unit tests

Default pytest excludes slow or environment-specific markers (`plugin`, `benchmark`, `fuzz`, `weblate_contract`) — see markers in [`pyproject.toml`](pyproject.toml):
Default pytest excludes slow or environment-specific markers (`plugin`, `benchmark`, `fuzz`, `weblate_contract`, `postgres`) — see markers in [`pyproject.toml`](pyproject.toml):

```bash
pytest
Expand Down Expand Up @@ -75,10 +75,32 @@ Open `htmlcov/index.html` locally to browse line coverage (`coverage.xml`, `html
|-----------|---------------|
| `tests/formats/` | Format adapters and registry |
| `tests/utils/` | QuickBook parse/serialize logic |
| `tests/endpoint/` | HTTP API, serializers, services |
| `tests/endpoint/` | HTTP API, serializers, services (mock ORM) |
| `tests/postgres/` | DB-backed endpoint and service tests (`-m postgres`) |
| `tests/plugin/` | Live Weblate Docker stack (see below) |

Add tests next to the code you touch. Keep `django.setup()`-friendly patterns; the bundled Django test settings intentionally avoid heavy DB or migration suites.
Add tests next to the code you touch. Unit tests use `tests.django_qbk_format_settings` (SQLite, no migrations). PostgreSQL tests use `tests.django_integration_settings` (PostgreSQL + migrations).

### PostgreSQL tests

The postgres tier verifies untrusted-input → serializer → ORM behavior against a migrated database without the full Weblate Docker stack. It is excluded from default `pytest` and pre-commit runs.

Start PostgreSQL locally (same credentials as CI):

```bash
docker run -d --name weblate-integration-pg -p 5432:5432 \
-e POSTGRES_USER=weblate -e POSTGRES_PASSWORD=weblate -e POSTGRES_DB=weblate \
postgres:16-alpine
```

Run PostgreSQL tests:

```bash
export CI_DB_HOST=127.0.0.1 CI_DB_USER=weblate CI_DB_PASSWORD=weblate CI_DB_NAME=weblate
DJANGO_SETTINGS_MODULE=tests.django_integration_settings pytest -m postgres -v --reuse-db
```

CI runs this tier in [`.github/workflows/ci-postgres.yml`](.github/workflows/ci-postgres.yml) (Python 3.12, PostgreSQL service container).

## Plugin and Docker tests

Expand Down
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ flowchart TB

- **`src/boost_weblate/endpoint/`** — **HTTP API** for Boost documentation project/component management. Exposes three routes under `/boost-endpoint/` (see [Boost endpoint routes](#boost-endpoint-routes)), uses Django REST Framework for auth and serialization, and hands off heavy work to a Celery task (see [Celery requirement for add-or-update](#celery-requirement-for-add-or-update)).

- **`tests/`** — **Pytest** layout mirrors `src/boost_weblate/` (`tests/formats/`, `tests/utils/`, `tests/endpoint/`). Shared fixtures live under `tests/fixtures/`. `tests/conftest.py` configures `sys.path`, sets `DJANGO_SETTINGS_MODULE` to `tests.django_qbk_format_settings`, and calls `django.setup()` so format tests can load Weblate's Django stack without requiring PostgreSQL. Docker-based plugin tests live in `tests/plugin/`.
- **`tests/`** — **Pytest** layout mirrors `src/boost_weblate/` (`tests/formats/`, `tests/utils/`, `tests/endpoint/`). Shared fixtures live under `tests/fixtures/`. `tests/conftest.py` configures `sys.path` and Django bootstrap. Default unit tests use `tests.django_qbk_format_settings` (SQLite, no migrations). DB-backed PostgreSQL tests live in `tests/postgres/` (`-m postgres`). Docker-based plugin tests live in `tests/plugin/`.

## WEBLATE_FORMATS configuration

Expand Down Expand Up @@ -284,12 +284,13 @@ The service has no plugin-owned models; it operates entirely through Weblate's D

### CI (`ci.yml`)

Triggered on push and PR to `main` and `develop`. Calls nine reusable sub-workflows:
Triggered on push and PR to `main` and `develop`. Calls ten reusable sub-workflows:

| Job | Workflow | What it checks |
|-----|----------|----------------|
| `lint` | [`.github/workflows/ci-lint.yml`](.github/workflows/ci-lint.yml) | prek (Ruff, YAML/TOML, REUSE, actionlint, pytest) |
| `test` | [`.github/workflows/ci-test.yml`](.github/workflows/ci-test.yml) | pytest + 90% coverage gate on Python **3.12**, **3.13**, and **3.14** (`--cov-fail-under=90`) |
| `postgres` | [`.github/workflows/ci-postgres.yml`](.github/workflows/ci-postgres.yml) | DB-backed tests (`pytest -m postgres`) on Python **3.12** with PostgreSQL service container |
| `benchmark` | [`.github/workflows/ci-benchmark.yml`](.github/workflows/ci-benchmark.yml) | QuickBook parser benchmarks (`pytest-benchmark`); JSON artifact; optional regression gate vs `.benchmarks/` baseline |
| `package` | [`.github/workflows/ci-package.yml`](.github/workflows/ci-package.yml) | `uv build`, twine, pydistcheck, pyroma, check-wheel-contents, check-manifest |
| `dependencies` | [`.github/workflows/ci-dependencies.yml`](.github/workflows/ci-dependencies.yml) | pip-audit, liccheck, dependency review (on PRs) |
Expand Down Expand Up @@ -324,6 +325,25 @@ Each deploy SSHes to `/opt/cppa-weblate-plugin`, pulls the branch, rebuilds with

Full deployment and promotion procedure: [docs/deployment-runbook.md](docs/deployment-runbook.md) (staging, production, `PROMOTE_PAT`, rollback, release tagging).

### Running PostgreSQL tests locally

PostgreSQL tests exercise the serializer → view → ORM path against a migrated database (no full Weblate Docker stack). Start PostgreSQL (credentials aligned with [`docker/docker-compose.ci.yml`](docker/docker-compose.ci.yml)):

```bash
docker run -d --name weblate-integration-pg -p 5432:5432 \
-e POSTGRES_USER=weblate -e POSTGRES_PASSWORD=weblate -e POSTGRES_DB=weblate \
postgres:16-alpine
```

Then run the postgres tier (first run applies Weblate migrations; use `--reuse-db` on subsequent runs):

```bash
export CI_DB_HOST=127.0.0.1 CI_DB_USER=weblate CI_DB_PASSWORD=weblate CI_DB_NAME=weblate
DJANGO_SETTINGS_MODULE=tests.django_integration_settings pytest -m postgres -v --reuse-db
```
Comment thread
whisper67265 marked this conversation as resolved.

Default `pytest` and pre-commit exclude the `postgres` marker.

### Running plugin tests locally

```bash
Expand Down Expand Up @@ -369,7 +389,7 @@ See **[CONTRIBUTING.md](CONTRIBUTING.md)** for end-to-end local setup (uv, prek,

New maintainers should also read [docs/maintainer-handoff.md](docs/maintainer-handoff.md) for Weblate coupling points and the [first verified change walkthrough](docs/maintainer-handoff.md#first-verified-change-walkthrough).

**Quick reference:** install with `uv pip install -e '.[dev]'`, run hooks with `prek run --all-files`, run unit tests with `pytest`. CI enforces 90% coverage on `boost_weblate` and runs plugin smoke/auth/functional jobs against the Docker CI stack. Parser benchmark details and baseline refresh instructions are in [CONTRIBUTING.md](CONTRIBUTING.md#parser-benchmarks).
**Quick reference:** install with `uv pip install -e '.[dev]'`, run hooks with `prek run --all-files`, run unit tests with `pytest`. CI enforces 90% coverage on `boost_weblate`, runs PostgreSQL tests (`ci-postgres`), and runs plugin smoke/auth/functional jobs against the Docker CI stack. Parser benchmark details and baseline refresh instructions are in [CONTRIBUTING.md](CONTRIBUTING.md#parser-benchmarks).

## License

Expand Down
8 changes: 6 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ dev = [
"coverage[toml]==7.14.1",
"hypothesis==6.155.3",
"pytest-benchmark==5.2.3",
"pytest-cov==7.1.0"
"pytest-cov==7.1.0",
"pytest-django==4.12.0"
]
lint = [
{include-group = "pre-commit"}
Expand Down Expand Up @@ -74,6 +75,7 @@ dev = [
"hypothesis==6.155.3",
"prek==0.4.4",
"pytest-cov==7.1.0",
"pytest-django==4.12.0",
"pytest==9.0.3"
]

Expand Down Expand Up @@ -137,9 +139,11 @@ level = "cautious"
unauthorized_licenses = []

[tool.pytest.ini_options]
addopts = ["-m", "not plugin and not benchmark and not fuzz and not weblate_contract"]
DJANGO_SETTINGS_MODULE = "tests.django_qbk_format_settings"
addopts = ["-m", "not plugin and not benchmark and not fuzz and not weblate_contract and not postgres"]
markers = [
"benchmark: parser performance benchmarks (slow; excluded from default test runs)",
"postgres: DB-backed tests requiring PostgreSQL (excluded from default runs)",
"plugin: requires live Weblate stack (Docker Compose) and optional WEBLATE_API_TOKEN",
"slow: long-running plugin integration test",
"fuzz: property-based / fuzz tests (excluded from default test runs; pytest -m fuzz)",
Expand Down
5 changes: 2 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,8 @@ def pytest_configure() -> None:
profile = os.environ.get("HYPOTHESIS_PROFILE", "ci")
hypothesis_settings.load_profile(profile)

# Always use bundled settings so a host ``DJANGO_SETTINGS_MODULE`` does not
# break collection or ``python tests/formats/test_quickbook.py``.
os.environ["DJANGO_SETTINGS_MODULE"] = "tests.django_qbk_format_settings"
# Default bundled settings for unit/format tests; integration runs override via env.
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "tests.django_qbk_format_settings")

import django
from django.conf import settings
Expand Down
31 changes: 31 additions & 0 deletions tests/django_integration_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
#
# SPDX-License-Identifier: BSL-1.0

"""Django settings for DB-backed integration tests.

Extends Weblate's ``settings_test`` (PostgreSQL, eager Celery, full migrations)
with the Boost endpoint app and throttle configuration.

Requires a PostgreSQL server. Configure via ``CI_DB_*`` environment variables
(same contract as upstream Weblate CI)::

export CI_DB_HOST=127.0.0.1
export CI_DB_USER=weblate
export CI_DB_PASSWORD=weblate
export CI_DB_NAME=weblate
DJANGO_SETTINGS_MODULE=tests.django_integration_settings pytest -m postgres
"""

from __future__ import annotations

from weblate.settings_test import * # noqa: F403

from boost_weblate.settings_override import merge_boost_endpoint_throttle_rates

_ENDPOINT_APP_CONFIG = "boost_weblate.endpoint.apps.BoostEndpointConfig"

REST_FRAMEWORK = merge_boost_endpoint_throttle_rates(REST_FRAMEWORK) # noqa: F405

if _ENDPOINT_APP_CONFIG not in INSTALLED_APPS: # noqa: F405
INSTALLED_APPS += (_ENDPOINT_APP_CONFIG,) # noqa: F405
3 changes: 3 additions & 0 deletions tests/endpoint/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,8 @@ def process_all(self, _submodules, *, user, request=None): # noqa: ANN001
def test_get_or_create_project_uses_literal_lang_in_slug(
self, monkeypatch: pytest.MonkeyPatch
) -> None:
from contextlib import nullcontext

from boost_weblate.endpoint import services as services_mod

lang_code = "zh_Hans"
Expand All @@ -927,6 +929,7 @@ def capture_get_or_create(*, slug: str, defaults: dict):
monkeypatch.setattr(
services_mod.Project.objects, "get_or_create", capture_get_or_create
)
monkeypatch.setattr(services_mod.transaction, "atomic", lambda: nullcontext())

service = services_mod.BoostComponentService(
organization="org",
Expand Down
3 changes: 3 additions & 0 deletions tests/postgres/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
#
# SPDX-License-Identifier: BSL-1.0
Loading
Loading