# pytest-allure-host

![CI](https://github.com/darrenrabbs/allurehosting/actions/workflows/ci.yml/badge.svg)
![CodeQL](https://github.com/darrenrabbs/allurehosting/actions/workflows/codeql.yml/badge.svg)
![PyPI - Version](https://img.shields.io/pypi/v/pytest-allure-host.svg)
![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)
[![Docs](https://img.shields.io/badge/docs-site-blue)](https://darrenrabbs.github.io/allurehosting/)

Publish Allure static reports to private S3 behind CloudFront with history preservation and SPA-friendly routing.

See `docs/architecture.md` and `.github/copilot-instructions.md` for architecture and design constraints.

## Documentation

Full documentation (quickstart, AWS setup, IAM least-privilege, CLI usage, changelog) is published at:

https://darrenrabbs.github.io/allurehosting/

The README intentionally stays lean—refer to the site for detailed guidance.

## Features

- Generate Allure static report from `allure-results`
- Preserve history by pulling `latest/history` before generation
- Upload to S3 under `<project>/<branch>/<run_id>/` and update `<project>/<branch>/latest/`
- Two-phase latest update: upload to `latest_tmp/`, swap into `latest/`, write `LATEST_READY` marker
- Optional retention: keep only N newest runs (`--max-keep-runs`)
- Correct caching headers: `index.html` and `widgets/` → `no-cache`; assets → immutable
- Optional TTL tagging on objects (`--ttl-days`) for S3 lifecycle policies
- Optional summary JSON output for CI
- Best-effort manifest at `runs/index.json` with new run metadata
- Lightweight pointer file `latest.json` (branch root)
- Human-friendly HTML index at `runs/index.html` for navigating past runs
  - Columns: Run ID, raw epoch, UTC Time (human readable), Size (pretty units), P/F/B (passed/failed/broken counts), links to the immutable run and the moving latest
  - Newest run highlighted with a star (★) and soft background

## Requirements

- Python 3.9+
- AWS credentials with S3 access to the target bucket
- Allure commandline available on PATH (e.g., via Allure CLI or allure-pytest)

## S3 layout and caching

- Keys:
  - `s3://<bucket>/<prefix>/<project>/<branch>/<run_id>/...`
  - `s3://<bucket>/<prefix>/<project>/<branch>/latest/...`
  - `s3://<bucket>/<prefix>/<project>/<branch>/runs/index.json` (manifest)
  - `s3://<bucket>/<prefix>/<project>/<branch>/runs/index.html` (HTML index)
  - `s3://<bucket>/<prefix>/<project>/<branch>/latest.json` (pointer)
- Two-phase swap writes a `LATEST_READY` marker file under `latest/` when ready.
- Cache-Control:
  - `index.html`: `no-cache`
  - files under `widgets/`: `no-cache`
  - everything else: `public, max-age=31536000, immutable`

## CLI usage

Install locally for development:

```bash
pip install -e .[dev]
```

Run the publisher after tests generate `allure-results`:

```bash
publish-allure \
  --bucket your-bucket \
  --prefix reports \
  --project demo \
  --branch main \
  --cloudfront https://reports.example.com \
  --ttl-days 30 \
  --max-keep-runs 10
```

Flags (CLI):

- `--bucket` (required): S3 bucket name
- `--prefix` (default: `reports`): Root prefix
- `--project` (required): Project name
- `--branch` (default: `$GIT_BRANCH` or `main`)
- `--run-id` (default: `$ALLURE_RUN_ID` or `YYYYMMDD-HHMMSS`)
- `--cloudfront` (optional; default: `$ALLURE_CLOUDFRONT`)
- `--results` (default: `allure-results`): Input directory
- `--report` (default: `allure-report`): Output directory
- `--ttl-days` (optional): Add `ttl-days=<N>` object tag
- `--max-keep-runs` (optional): Keep N newest run prefixes, delete older
- `--summary-json <path>`: Write machine-readable summary
- `--check`: Preflight validation (AWS access, allure, inputs)
- `--dry-run`: Print planned prefixes and sample headers, no upload
- `--s3-endpoint`: Custom S3 endpoint (e.g. `http://localhost:4566` for LocalStack)

Environment fallbacks:

- `GIT_BRANCH` → `--branch` default
- `ALLURE_RUN_ID` → `--run-id` default
- `ALLURE_CLOUDFRONT` → `--cloudfront` default
- `ALLURE_S3_ENDPOINT` → `--s3-endpoint` default (LocalStack / custom S3)

## Pytest plugin usage

Run tests and publish during terminal summary:

```bash
pytest \
  --allure-bucket your-bucket \
  --allure-prefix reports \
  --allure-project demo \
  --allure-branch main \
  --allure-cloudfront https://reports.example.com \
  --allure-ttl-days 30 \
  --allure-max-keep-runs 10
```

Flags (pytest):

- `--allure-bucket` (required)
- `--allure-prefix` (default: `reports`)
- `--allure-project` (required)
- `--allure-branch` (default: `$GIT_BRANCH` or `main`)
- `--allure-run-id` (default: `$ALLURE_RUN_ID` or `YYYYMMDD-HHMMSS`)
- `--allure-cloudfront` (optional; default: `$ALLURE_CLOUDFRONT`)
- `--allure-ttl-days` (optional)
- `--allure-max-keep-runs` (optional)
- `--allure-summary-json <path>` (optional)
- `--allure-check` / `--allure-dry-run` (optional)

## Preflight and dry-run

- `--check`/`--allure-check` verifies:
  - S3 bucket reachability (HeadBucket/List)
  - `allure-results` exists and is non-empty
  - Allure CLI exists on PATH
- `--dry-run`/`--allure-dry-run` prints planned prefixes and sample headers; no uploads occur.

## Outputs

- S3 prefixes: run and latest
- Optional CDN URLs (if `--cloudfront` provided)
- `runs/index.json` manifest updated with new run entry
- `runs/index.html` HTML table of recent runs (newest first) with columns: Run ID, Epoch, UTC Time, Size, P/F/B, Run, Latest (newest row highlighted with ★)
- `latest.json` pointer to current run (simple machine-readable metadata)
- Optional `--summary-json` with sizes, file counts, and destination URLs
- `latest/LATEST_READY` marker indicates the swap is complete

## Security

See `SECURITY.md` for how to report vulnerabilities. Never open a public issue containing sensitive details.

## Badges / Status

- CI: multi-version test matrix + lint/security
- CodeQL: static code analysis

## Contributing

See `CONTRIBUTING.md` and follow the pre-commit hooks (`pre-commit install`).

## Release

Tagged versions (`vX.Y.Z`) are published to PyPI automatically via GitHub OIDC.

## CI examples

GitHub Actions (CLI):

```yaml
jobs:
  tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with: { python-version: "3.11" }
      - run: pip install .[dev]
      - run: pytest -q
      - name: Publish Allure
        env:
          AWS_REGION: us-east-1
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          GIT_BRANCH: ${{ github.ref_name }}
        run: >-
          publish-allure --bucket $BUCKET --prefix reports --project demo
          --branch "$GIT_BRANCH" --cloudfront https://reports.example.com
          --ttl-days 30 --max-keep-runs 10
```

Pytest-driven (plugin):

```yaml
- run: pytest -q \
    --allure-bucket $BUCKET \
    --allure-prefix reports \
    --allure-project demo \
    --allure-branch "$GIT_BRANCH" \
    --allure-cloudfront https://reports.example.com \
    --allure-ttl-days 30 \
    --allure-max-keep-runs 10
```

## Troubleshooting

- Missing Allure binary: ensure the Allure CLI is installed and on PATH.
- Access denied: verify AWS credentials and bucket IAM for Put/Get/List/Delete.
- SPA routing 403/404: configure CloudFront error mapping to `/index.html`.

## Development

- Install with Poetry: `poetry install`
- Run tests: `poetry run pytest -q`
- Lint (security quick): `poetry run bandit -r pytest_allure_host`
- Unified lint helper (mirrors CI):
  ```bash
  scripts/lint.sh           # check mode (ruff lint+format check, bandit, pip-audit)
  scripts/lint.sh --fix     # apply ruff fixes + format
  scripts/lint.sh pre-commit  # also run pre-commit hooks on all files
  ```

## Quick local trial (macOS)

This section walks you through a minimal end-to-end run locally.

1. Prereqs

- AWS credentials configured (via `AWS_PROFILE` or access keys); set `AWS_REGION`.
- Allure CLI installed on PATH:
  ```bash
  brew install allure
  ```
- Python deps installed:
  ```bash
  poetry install
  # or
  pip install -e .[dev]
  ```

2. Generate Allure results

- Create a tiny test (optional example):
  ```bash
  mkdir -p tests
  cat > tests/test_sample.py <<'PY'
  def test_ok():
      assert True
  PY
  ```
- Run pytest to emit results:
  ```bash
  poetry run pytest --alluredir=allure-results
  ```

3. Preflight and dry-run

```bash
poetry run publish-allure \
  --bucket <bucket> \
  --prefix reports \
  --project demo \
  --branch $(git rev-parse --abbrev-ref HEAD) \
  --cloudfront https://<cloudfront_domain> \
  --check \
  --dry-run
```

4. Publish

```bash
poetry run publish-allure \
  --bucket <bucket> \
  --prefix reports \
  --project demo \
  --branch $(git rev-parse --abbrev-ref HEAD) \
  --cloudfront https://<cloudfront_domain> \
  --ttl-days 30 \
  --max-keep-runs 5
```

5. Verify

- S3: `reports/demo/<branch>/<run_id>/...` and `reports/demo/<branch>/latest/` with `LATEST_READY`.
- CDN: open printed `run_url` / `latest_url`.
