# Run tests against real services

URL: https://briq.cloud/docs/guides/run-tests-against-real-services

> Replace mocks with a real Postgres and Redis for the duration of a test run.

Recipes:

- [PostgreSQL](https://briq.cloud/cookbook/postgres.md): A PostgreSQL database with a persistent volume. Optional pgvector.
- [Redis](https://briq.cloud/cookbook/redis.md): A Redis server (Valkey-compatible) for caches and queues.

Mocks drift, and Testcontainers needs Docker, which cloud sandboxes and CI runners often lack. briq
gives a test run real services for a few cents and tears them down after.

Recipe outputs are private addresses that only briqs of your team can reach, so the tests run in a
briq too, in the same stack as the services.

## From an agent

> Run the integration tests against a real Postgres and Redis instead of the mocks.

The agent starts the `postgres` and `redis` recipes and a runner in the same stack, runs the tests
there with the outputs as env, destroys the stack's briqs, and can tell you what the run cost.

## Run in briq

```text
briq_run({ recipe: "postgres", stack: "ci" })
briq_run({ recipe: "redis", stack: "ci" })
briq_run({ image: "node:22", stack: "ci", cmd: ["sleep", "infinity"],
           env: { DATABASE_URL: { secret: "DATABASE_URL", value: "<from postgres>" },
                  REDIS_URL: "<from redis>" } })
briq_files_put({ briq: "bq_…", path: "/app.tgz", contentBase64: "…" })
briq_exec({ briq: "bq_…", cmd: ["sh", "-c", "tar xzf /app.tgz -C / && cd /app && npm ci && npm test"], timeoutMs: 600000 })
```

`DATABASE_URL` holds a password, so it goes in as a secret: plain env values show in status and on
the dashboard. `briq_exec` shows the first 32 KB of output: send long output to a file and read it
with `briq_files_get`.

## From CI

The same calls over [REST](https://briq.cloud/docs/api/overview). `sleep: "30m"` puts everything to sleep 30 minutes
after the job last used it, even if the job dies before its cleanup step. A sleeping briq is not
billed, but its disk counts toward the plan's allowance until it is destroyed.

```bash title="ci/integration.sh"
api() { local p=$1; shift; curl -sf "https://api.briq.cloud/v1$p" \
  -H "Authorization: Bearer $BRIQ_API_KEY" -H "Content-Type: application/json" "$@"; }

DB=$(api /briqs -d '{ "recipe": "postgres", "stack": "ci", "sleep": "30m" }')
CACHE=$(api /briqs -d '{ "recipe": "redis", "stack": "ci", "sleep": "30m" }')
RUNNER=$(api /briqs -d "$(jq -n --argjson db "$DB" --argjson c "$CACHE" '{ stack: "ci", spec: {
  name: "runner", image: "node:22", cmd: ["sleep", "infinity"], sleep: "30m",
  env: { DATABASE_URL: { secret: "DATABASE_URL", value: $db.outputs.DATABASE_URL },
         REDIS_URL: $c.outputs.REDIS_URL } } }')")
ID=$(jq -r '.briqs[0].id' <<<"$RUNNER")

git archive HEAD | gzip | base64 | jq -Rs '{ path: "/app.tgz", contentBase64: . }' \
  | api "/briqs/$ID/files" -d @- >/dev/null
api "/briqs/$ID/exec" -d '{ "timeoutMs": 600000, "cmd": ["sh", "-c",
  "mkdir -p /app && tar xzf /app.tgz -C /app && cd /app && npm ci && npm test"] }' \
  | tee result.json | jq -r .stdout
STATUS=$(jq .exitCode result.json)

for id in $(jq -r '.briqs[].id' <<<"$DB $CACHE $RUNNER"); do api "/briqs/$id" -X DELETE >/dev/null; done
exit "$STATUS"
```

Uploads are capped at 1 MB a file; for a repo, have the runner `git clone` it instead.

`nano` is enough for Redis and most test databases.
