Files
Oxicloud/tests/common/docker-compose.test.yml
T
Edouard Vanbelle 04e0df0c89 test(consistency): exercise the Azure backend against Azurite
Adds an Azurite service and a scenario that audits the Azure backend
through `?storage=azurite`. It is the only coverage of that code path in
the tree: `AzureBlobBackend` has unit tests for its name parser and
ordering, but nothing else speaks the protocol, and a paid account is
not an option for CI. Azurite implements the real Blob REST API, so this
exercises SharedKey signing, prefix/marker paging, and the 256-way shard
walk with its termination.

## Harness

`docker-compose.test.yml` gains an azurite service on 10000 (tmpfs, so
it dies with the stack). `spawn-db.sh` provisions the container itself,
because `AzureBlobBackend::initialize` verifies rather than creates —
signed by hand with curl + openssl rather than pulling a ~700 MB `az`
image for one PUT. Two traps are commented there: the account key is
base64 but HMAC wants raw bytes, and the canonicalized resource repeats
the account name (`/{acc}/{acc}/{container}`) because the emulator puts
in the path what real Azure puts in the host. Getting that wrong yields
403, not a hint.

The `azurite` entry is declared in `server.env` but never activated, so
the suite's active backend stays local and only this file reaches Azure.

## What it asserts, and what it cannot

A failure surfaces as `ok: false`, because an enumeration error now
fails the run rather than degrading to a per-row probe.

It deliberately asserts no finding count. The container starts empty and
the job's grace window is an hour, so a freshly-uploaded blob is skipped
in both directions by design — an audit here can only report zero, and
"zero findings" would pass whether enumeration worked or returned
nothing. The one positive assert, `scanned_count != 0`, therefore sits
on the local control, which does hold blobs; `scanned_count` accumulates
via `checkpoint`, which the empty-page early return skips.

## No cutover, deliberately

Putting real bytes in the container means `backend_migration
?storage=azurite`, which hangs on the first blob: `head_check` issues a
~40-byte ranged GET, `azure_core` 0.21 attaches
`x-ms-range-get-content-crc64` to anything under 4 MiB, Azurite 500s,
and the deterministic error is retried forever while
`migration_readonly` refuses writes app-wide. The full chain and the
rejected workaround are in the file header. The scenario is still
ordered last in `run.sh` — it is the only one needing a second service,
and the cutover comes back there once the official SDK lands.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 19:24:46 +02:00

46 lines
1.7 KiB
YAML

services:
postgres-test:
image: postgres:18.2-alpine3.23
environment:
POSTGRES_USER: oxicloud_test
POSTGRES_PASSWORD: oxicloud_test
POSTGRES_DB: oxicloud_test
ports:
- "5433:5432"
tmpfs:
- /var/lib/postgresql # in-memory: always blank on start (pg18+ uses /var/lib/postgresql/18/data)
healthcheck:
test: ["CMD-SHELL", "pg_isready -U oxicloud_test"]
interval: 2s
timeout: 5s
retries: 10
# Azure Blob Storage emulator. Speaks the real Blob REST API, so it is
# the only way to exercise the Azure backend without an account — and
# the account it does ship is the fixed, publicly-documented dev pair
# (`devstoreaccount1` + the well-known key), so nothing here is a
# credential worth protecting.
#
# Blob service only: `--blobHost` with no queue/table, since the backend
# uses blobs exclusively and the other two would just be open ports.
#
# tmpfs for the same reason postgres uses it — a run must start from an
# empty container, or a previous run's blobs read as orphans in the very
# audit this exists to test.
azurite-test:
image: mcr.microsoft.com/azure-storage/azurite:3.36.0
command: azurite-blob --blobHost 0.0.0.0 --blobPort 10000 --location /data --skipApiVersionCheck
ports:
- "10000:10000"
tmpfs:
- /data
healthcheck:
# Azurite answers 400 to an unauthenticated root GET, which is proof
# enough that the HTTP listener is up — the SDK handshake is what
# validates credentials, and that happens later in the backend's
# own `initialize`.
test: ["CMD-SHELL", "nc -z 127.0.0.1 10000 || exit 1"]
interval: 2s
timeout: 5s
retries: 15