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>
This commit is contained in:
@@ -44,19 +44,22 @@ Authorization: Bearer {{admin_token}}
|
||||
HTTP 200
|
||||
[Asserts]
|
||||
# Two entries declared, in _ENTRIES order.
|
||||
jsonpath "$.entries" count == 2
|
||||
jsonpath "$.entries" count == 3
|
||||
jsonpath "$.entries[0].name" == "local_main"
|
||||
jsonpath "$.entries[1].name" == "s3_stub"
|
||||
jsonpath "$.entries[2].name" == "azurite"
|
||||
|
||||
# Backend types match the declarations.
|
||||
jsonpath "$.entries[0].backend" == "local"
|
||||
jsonpath "$.entries[1].backend" == "s3"
|
||||
jsonpath "$.entries[2].backend" == "azure"
|
||||
|
||||
# Active pointer: fresh DB has no `active_backend_name` row, so the
|
||||
# boot fallback picks the FIRST entry in _ENTRIES.
|
||||
jsonpath "$.active_entry_name" == "local_main"
|
||||
jsonpath "$.entries[0].is_active" == true
|
||||
jsonpath "$.entries[1].is_active" == false
|
||||
jsonpath "$.entries[2].is_active" == false
|
||||
|
||||
# Read-only mode off on a fresh boot (no in-flight migration, no
|
||||
# stale flag in DB).
|
||||
|
||||
Reference in New Issue
Block a user