04e0df0c89
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>
46 lines
1.7 KiB
YAML
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
|