Ctrl K
ComunidadGuías…

TESTING.md

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
# Testing Guide

This document covers the testing infrastructure for Artifact Keeper, including unit tests, integration tests, and end-to-end (E2E) tests.

## Quick Start

### Run All Tests Locally

```bash
# Backend unit tests (needs a migrated database; see "Pre-push Quality Checklist" in CLAUDE.md)
cargo nextest run --workspace --lib --test-threads 8

# E2E tests with Docker (fully automated, no human in the loop)
./scripts/run-e2e-tests.sh
```

### Run Tests in CI/CD

Tests run automatically on push/PR via GitHub Actions. See `.github/workflows/ci.yml`.

## Test Architecture

```
┌─────────────────────────────────────────────────────────────────┐
│                        Test Pyramid                              │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│                      ┌───────────────┐                           │
│                      │  E2E Tests    │  Native client tests      │
│                      │  (Docker)     │  (PyPI, NPM, Cargo, etc)  │
│                     ┌┴───────────────┴┐                          │
│                    ┌┴─────────────────┴┐                         │
│                   ┌┴───────────────────┴┐                        │
│                   │  Integration Tests   │  Cargo test            │
│                   │  (PostgreSQL)        │  (API + DB)            │
│                  ┌┴─────────────────────┴┐                       │
│                 ┌┴───────────────────────┴┐                      │
│                ┌┴─────────────────────────┴┐                     │
│                │       Unit Tests          │  Cargo test          │
│                │    (Functions, logic)      │  (Isolated)         │
│                └───────────────────────────┘                     │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
```

## Backend Tests

### Running Backend Tests

```bash
# Run the backend unit tests (use nextest, not plain `cargo test`; see CLAUDE.md)
cargo nextest run --workspace --lib --test-threads 8

# Run with test output shown
cargo nextest run --workspace --lib --no-capture

# Run specific test
cargo nextest run --workspace --lib test_create_repository

# Run one integration suite (#[ignore]d; requires PostgreSQL and DATABASE_URL)
cargo nextest run --workspace --run-ignored ignored-only --test integration_tests
```

### Test Location

- `backend/tests/integration_tests.rs` - API integration tests
- `backend/src/**/*.rs` - Unit tests (inline `#[cfg(test)]` modules)

## Automated E2E Testing with Docker

Run fully automated E2E tests without any manual setup:

```bash
# Run all E2E tests in containers
./scripts/run-e2e-tests.sh

# Force rebuild containers
./scripts/run-e2e-tests.sh --build

# Clean up after tests
./scripts/run-e2e-tests.sh --clean
```

### How It Works

```
┌─────────────────────────────────────────────────────────────────┐
│                    docker-compose.test.yml                       │
├─────────────────────────────────────────────────────────────────┤
│                                                                  │
│  ┌─────────────┐     ┌─────────────┐     ┌─────────────┐        │
│  │  PostgreSQL │────▶│   Backend   │◀────│  Native     │        │
│  │   (tmpfs)   │     │   (Rust)    │     │  Clients    │        │
│  └─────────────┘     └─────────────┘     └─────────────┘        │
│                                                                  │
└─────────────────────────────────────────────────────────────────┘
```

### Container Details

| Service | Image | Purpose |
|---------|-------|---------|
| `postgres` | postgres:18-alpine | Test database (tmpfs for speed) |
| `backend` | Custom (Rust) | API server |
| `pypi-test` | python:3.14-slim | PyPI native client test |
| `npm-test` | node:26-slim | NPM native client test |
| `cargo-test` | rust:1.98-slim | Cargo native client test |

### The e2e admin credential

The e2e stacks (`docker-compose.test.yml`, `docker-compose.concurrency-e2e.yml`,
`docker-compose.mesh-e2e.yml`, `scripts/*/docker-compose.yml`, `proof/compose.*.yml`)
and the scripts that log in to them all take the value from ONE file,
[`.env.test`](.env.test) at the repository root, so the stack and the scripts
cannot disagree and the password is written down in exactly one place:

* the compose files read it through each service's `env_file:`, which is
  relative to the compose file — no `--env-file` flag, no change to how you
  invoke `docker compose`;
* host-side scripts source it through `scripts/lib/test-env.sh`, which walks up
  to the same file. Scripts running inside an e2e container do not need that:
  compose has already injected the same variables from the same file.

`.env.test` is written in the syntax that is both a dotenv file and a POSIX
shell script, which is what lets one file serve both. Every assignment in it
uses `:-`, so an exported value always wins:

```bash
# Give the run a credential that exists nowhere in this repository.
export AK_TEST_ADMIN_PASSWORD="$(openssl rand -base64 24)"
docker compose -f docker-compose.test.yml --profile smoke up
```

`scripts/run-e2e-tests.sh` does that for you and prints the value so you can
drive the same stack from another shell. Unset, everything falls back to the
placeholder in `.env.test`, so an unconfigured local run still works.

These stacks are torn down with `down -v` at the end of a run and hold nothing
worth protecting — but they are also the files people copy when they start a
deployment, which is why the fallback is a placeholder that announces what it
is rather than a plausible-looking password (#3490), and why there is only one
of it (#3938: sixty copies of one password string is indistinguishable from a
leaked credential to a secret scanner). A real deployment must set
`ADMIN_PASSWORD` (or `INITIAL_ADMIN_PASSWORD_FILE`) from its own secret store;
see the root `docker-compose.yml`.

Individual scripts still honour `ADMIN_PASS` / `ADMIN_USER` if you want to
point one at a registry that is not one of these stacks.

## CI/CD Integration

### GitHub Actions

Tests run automatically via `.github/workflows/ci.yml`:

### Jobs

1. **check-rust** - `cargo fmt` and `cargo clippy`
2. **test-backend-unit-shard** - the unit tests (lib + bins) as a matrix of
   test shards on GitHub-hosted runners, each leg one `cargo llvm-cov`-instrumented
   build of one shard plus its `lcov.info` (see "Test shards" below)
3. **test-backend-integration** - the PostgreSQL-backed integration suites
   (pushes and backend-touching PRs)
4. **test-backend-unit** - the required `🧪 Backend Unit Tests` check: passes
   when every shard and the integration suites passed
5. **coverage-gates** - merges the shards' reports, then the 50% floor, 70%
   new-code and duplication gates (pull requests, advisory)
6. **build-backend-image** - Container image build
7. **smoke-e2e** - Native client smoke tests
8. **security-audit** - Dependency audit

### Test shards

Every inline `#[cfg(test)]` test module in `backend/src` carries a shard
gate directly above it, e.g. `#[cfg(ak_test_shard = "services-1")]`, so CI can
compile and run the ~17.5k unit tests in several smaller pieces (one rustc
holding all of them peaks at ~16.7 GiB instrumented). A plain
`cargo nextest run` / `cargo test` / clippy enables no shard feature and
compiles every test, exactly as before. To reproduce one CI leg:

```bash
cargo nextest run --workspace --lib --features test-shard-handlers-1 \
  -E "$(python3 scripts/ci/test-shards.py filter handlers-1)"
```

Which shard a module belongs to follows from its file (and whether it builds
the whole router) — `python3 scripts/ci/test-shards.py apply` writes the
attributes and `check` (run by Check Rust) verifies them. A new test module
needs nothing but `apply`. Forgetting it does not fail CI: a module without
the gate is compiled in every leg, `filter` (the `-E` above) runs its tests in
only the leg its file maps to, and `check` leaves a warning on the pull
request naming the module and the `apply` command. A test module that calls
helpers in another test module (`super::other_tests::helper()`,
`crate::formats::pypi::tests::..`) pulls that module into its own shard;
`apply` works this out, and `check` fails if the gates as written would leave
a shard unable to compile it.

## Coverage Goals

| Test Type | Target Coverage |
|-----------|-----------------|
| Unit Tests | 80%+ |
| E2E Tests | Critical paths |

## Resources

- [Cargo Test Documentation](https://doc.rust-lang.org/cargo/commands/cargo-test.html)