Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
37 changes: 37 additions & 0 deletions .github/workflows/integration-auth.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
#
# SPDX-License-Identifier: BSL-1.0

name: Integration auth

on:
push:
branches: [main, develop]
pull_request:
branches: [main, develop]

jobs:
integration-auth:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# actions/checkout v6.0.2
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false

# actions/setup-python v6.2.0
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: '3.12'

- name: Run integration auth tests
run: bash scripts/integration-auth.sh

- name: Upload logs on failure
if: failure()
# actions/upload-artifact v4.6.2
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
with:
name: integration-auth-logs
path: /tmp/compose-logs.txt
45 changes: 45 additions & 0 deletions scripts/integration-auth.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
# SPDX-License-Identifier: BSL-1.0

# Integration auth test entrypoint.
# Builds the stack, waits for health, creates a token, runs auth tests.
# On exit (success or failure): collects logs and tears down the stack.

set -euo pipefail

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=lib/weblate-stack.sh
source "${SCRIPT_DIR}/lib/weblate-stack.sh"

cleanup() {
local exit_code=$?
set +e
echo "--- Collecting logs ---"
stack_logs /tmp/compose-logs.txt
echo "--- Tearing down stack ---"
stack_down
exit "$exit_code"
}
trap cleanup EXIT

echo "=== Building stack ==="
stack_build

echo "=== Starting stack ==="
stack_up

echo "=== Waiting for Weblate ==="
stack_wait_healthy "${HEALTH_TIMEOUT:-120}"

echo "=== Creating API token ==="
WEBLATE_API_TOKEN="$(stack_create_token admin)"
export WEBLATE_API_TOKEN
export WEBLATE_LIVE_BASE_URL="${WEBLATE_LIVE_BASE_URL:-http://localhost:${WEBLATE_PORT:-8080}}"
export WEBLATE_COMPOSE_FILE="${COMPOSE_FILE}"
export WEBLATE_COMPOSE_PROJECT="${COMPOSE_PROJECT_NAME}"

echo "=== Running auth tests ==="
pip install --quiet pytest
Comment thread
whisper67265 marked this conversation as resolved.
Outdated
python -m pytest --confcutdir=tests/integration --override-ini addopts= \
tests/integration/test_auth.py -v
80 changes: 80 additions & 0 deletions tests/integration/test_auth.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# SPDX-FileCopyrightText: 2026 Andrew Zhang <whisper67265@outlook.com>
#
# SPDX-License-Identifier: BSL-1.0

"""P2 integration auth tests.

Verifies authentication and authorization behavior across all
Boost endpoint routes:
- Valid token grants access to protected endpoints
- Invalid/missing tokens are rejected
- Unauthenticated endpoints remain accessible without a token
"""

from __future__ import annotations

import pytest

from tests.integration.lib.http import http_get, http_json

pytestmark = pytest.mark.integration

_VALID_ADD_OR_UPDATE_BODY = {
"organization": "test-org",
"version": "test-1.0.0",
"add_or_update": {"zh_Hans": ["test-submodule"]},
}

_FAKE_TOKEN = "wlu_this_token_does_not_exist_in_weblate"


class TestBoostEndpointAuth:
"""Authentication and authorization across all Boost endpoint routes."""

def test_valid_token_on_info(self, api_token: str) -> None:
code, body = http_get("/boost-endpoint/info/", token=api_token)
assert code == 200, f"expected 200: {code} {body}"
assert isinstance(body, dict)
assert "module" in body

def test_valid_token_on_add_or_update(self, api_token: str) -> None:
code, body = http_json(
"POST",
"/boost-endpoint/add-or-update/",
token=api_token,
body=_VALID_ADD_OR_UPDATE_BODY,
)
assert code == 202, f"expected 202: {code} {body}"
assert isinstance(body, dict)
assert body.get("status") == "accepted"
assert body.get("task_id")

def test_invalid_token_rejected(self) -> None:
code, _ = http_get("/boost-endpoint/info/", token=_FAKE_TOKEN)
assert code in (401, 403), f"expected 401/403: {code}"

def test_no_token_rejected(self) -> None:
code, _ = http_get("/boost-endpoint/info/")
assert code in (401, 403), f"expected 401/403: {code}"

def test_invalid_token_on_add_or_update(self) -> None:
code, _ = http_json(
"POST",
"/boost-endpoint/add-or-update/",
token=_FAKE_TOKEN,
body=_VALID_ADD_OR_UPDATE_BODY,
)
assert code in (401, 403), f"expected 401/403: {code}"

def test_no_token_on_add_or_update(self) -> None:
code, _ = http_json(
"POST",
"/boost-endpoint/add-or-update/",
body=_VALID_ADD_OR_UPDATE_BODY,
)
assert code in (401, 403), f"expected 401/403: {code}"

def test_ping_no_auth_required(self) -> None:
code, body = http_get("/boost-endpoint/plugin-ping/")
assert code == 200
assert body == "ok" or body == b"ok"
Loading