Skip to content

chore(test): cover real backends, patch dependencies, document security - #18

Merged
ekalinin merged 1 commit into
chore/deploy-hardeningfrom
chore/deps-and-docs
Aug 20, 2026
Merged

chore(test): cover real backends, patch dependencies, document security#18
ekalinin merged 1 commit into
chore/deploy-hardeningfrom
chore/deps-and-docs

Conversation

@ekalinin

@ekalinin ekalinin commented Aug 17, 2026

Copy link
Copy Markdown
Owner

Stacked on #17.

Integration tests

The whole suite ran on the in-memory MetaStore, the local filesystem and fake drivers, so the paths that exist only for the real backends were exercised nowhere outside miniredis and unit-level fakes: Lua scripts and WATCH transactions in Redis, lease key expiry, pgx and MySQL type mapping, the S3 multipart uploader, and the Close-waits-for-upload contract that I4 rests on. internal/state/redis_test.go covers the Redis semantics against miniredis; the gap this closes is a run against the real thing.

test/integration brings up Redis, PostgreSQL, MySQL and MinIO through testcontainers. It sits behind the integration build tag, so make test and make ci do not touch it, and runs as its own CI job so a Docker or image-pull problem is never mistaken for a code failure. make vet still compiles and vets it with the tag set, because a package whose files are all cut out by a tag is skipped by ./... without a word.

Covered: a full round trip per backend (PostgreSQL, MySQL, S3), multi-node idempotency, reading a result from the instance that did not run it, watch and stop crossing instances, reaping by expired lease, the cluster lock behind GC, a heartbeat running concurrently with a terminal write, and the statement guard against a real database (after a rejected DELETE the rows are still there).

Three of these tests did not check what their names said, and were rewritten:

  • TestRedis_HeartbeatKeepsTerminalWrites was three sequential steps, so the interleaving the lost-update bug needs could not occur; it passed on the buggy implementation. It now runs the heartbeat in its own goroutine across the terminal write, 50 rounds, under -race, and fails on the pre-fix Heartbeat at round 2.
  • TestS3_ResultRoundTrip uploaded ~60 bytes, and the AWS uploader sends a non-seekable body below its 5 MiB part size as a plain PutObject, so multipart never ran. It now also materializes ~8 MB and checks the object against what was written.
  • TestRedis_GCRunsOnce never ran GC - it called TryLock twice on one store. Renamed to TestRedis_TryLockIsExclusive, extended with a second store and with lock expiry, and the comment no longer claims otherwise.

Dependencies

govulncheck reported two reachable advisories: GO-2026-6061 in grpc (fixed in v1.82.1, symbols reachable through OTLP) and GO-2026-5970 in x/text. Only grpc is bumped here - x/text v0.40.0 already arrived in the base branch - and go.sum loses the orphaned lines. The check is wired into make ci and the CI workflow, pinned to a version rather than @latest, so a release of the tool cannot fail CI on its own.

That check also raises the floor on the toolchain, because most of what it finds lives in the standard library rather than in a dependency. CI installs exactly what go.mod declares, and go 1.26.0 carries two dozen advisories reachable from this code in net/http, crypto/tls, crypto/x509, html/template, net/url and encoding/xml. go.mod now asks for 1.26.6, the first release clear of all of them. Expect this step to fail again when a new stdlib advisory lands - that is what it is for, and the fix is another patch bump.

Specification

spec.md said nothing about authentication, authorization, TLS, request limits or result ownership - that was a gap in the specification itself rather than a divergence from it. It gains a Security section: authentication and scopes, subject binding, the statement policy, TLS and trusted proxies, request and concurrency limits, listener isolation and error sanitization.

Credentials are documented as configured rather than mandatory, which is what the code does: with no auth section the API is open and says so at startup. It also records what a reload cannot apply (including auth, so revoking a token takes a restart), that leases live outside the query record, and that query events travel the control channel.

Verification

make ci in full (fmt, vet, test, race, lint, buf lint, govulncheck) - green.

make test-containers against Docker - all 8 tests green under -race. The rewritten heartbeat test was also run against the pre-fix Heartbeat implementation and fails there, which is the point of it.

Every test ran on the in-memory MetaStore, the local filesystem and fake
drivers, so the paths that only exist for the real backends had no coverage
at all: Lua scripts and WATCH transactions in Redis, lease key expiry, pgx and
MySQL type mapping, the S3 multipart uploader and the Close-waits-for-upload
contract behind I4. test/integration runs those against Redis, PostgreSQL,
MySQL and MinIO under testcontainers, behind the `integration` build tag and a
separate CI job so a Docker problem is never mistaken for a code failure. It
covers a full round trip per backend, multi-node idempotency, a subscription
and a stop crossing instances, owner-loss reaping, the GC lock, and a
heartbeat storm that must not clobber a terminal write.

govulncheck reported two reachable advisories: GO-2026-6061 in grpc and
GO-2026-5970 in x/text. Both are patched, and the check now runs in `make ci`
and in the CI workflow.

That check also raises the floor on the toolchain, because most of what it
finds is in the standard library rather than in a dependency: go.mod asks for
1.26.6, which is the first release without any of the advisories reachable
from this code. CI installs exactly what go.mod declares, so leaving it at
1.26.0 meant shipping a binary built against a stdlib with two dozen known
issues in net/http, crypto/tls, crypto/x509 and html/template.

spec.md gains the security section it never had - authentication,
authorization and subject binding, the statement policy, TLS and trusted
proxies, request and concurrency limits, listener isolation and error
sanitization - which was a gap in the specification rather than a divergence
from it. It also states what a reload cannot apply, and records that leases
live outside the query record and that query events travel the control
channel.
@ekalinin
ekalinin force-pushed the chore/deploy-hardening branch from ef48efb to 359f8a9 Compare August 18, 2026 09:47
@ekalinin
ekalinin force-pushed the chore/deps-and-docs branch from 772c3f3 to 55d41b2 Compare August 18, 2026 09:47
@ekalinin
ekalinin merged commit 07856df into chore/deploy-hardening Aug 20, 2026
6 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant