Skip to main content

Testing and verification

Related: development, conventions

Complete environment setup before running checks. Taskfile.yaml defines the commands and their outputs.

Choose a test suite​

While editing, run the matching focused checks and tests for the affected behavior. Check Go packages together, including callers when interfaces change. Follow the shared Go conventions linked from Project conventions.

CommandCoverage and prerequisites
task test:unitFull unit suite with race detection and coverage; excludes generated code, mocks, and external test trees
task test:unit -- ./internal/metricsSelected packages with race detection; no coverage report
task test:fuzzAll fuzz targets; 30 seconds per target by default
task test:integrationFull integration suite with coverage; loads .env and needs configured services
task test:resilienceRecovery tests with an instrumented app and combined coverage; needs Toxiproxy and backing services
task test:loadHTTP and gRPC k6 suites against a running application by default

Coverage reports are written under .build/. Resilience tests are build-tagged and excluded from other test tasks. Use the resilience suite and CI service configuration to identify its required backends.

Adjust fuzz and load runs​

Fuzz tests accept FUZZ_TIME as a Go duration or iteration count. Load tests accept K6_TEST_PATH and K6_SUMMARY_PATH together to select an existing script and its summary output. These values can be Task variables or environment variables:

task test:fuzz FUZZ_TIME=5m
task test:load K6_TEST_PATH=tests/load/http/v1/auth_test.js \
K6_SUMMARY_PATH=.build/k6-summary-http-v1.json

Without load-test selectors, both protocols run, even if the first fails, and write .build/k6-summary-{http,grpc}-v1.json. Each invocation removes previous summaries for its selected scope before running k6 and reports failures through its exit status.

Integration test environment​

task test:integration runs the full suite; it does not start the application. Use a dedicated test application because the HTTP tests create, update, and delete user records. Repository tests create isolated databases through the test helper.

Test process settingRequired setup
HTTP_SERVER_ADDRESSApplication base URL, default http://localhost:8080; omit the /api/v1 suffix
GRPC_SERVER_ADDRESSApplication address, default localhost:50051; the current client uses plaintext gRPC
HTTP_API_KEYHTTP integration requires a key with users:list, users:read_others, users:create, users:update, and users:delete
GRPC_API_KEYDefaults to the seeded development key; override through the test process environment when needed
DATABASE_*Select the engine and test database service; MySQL/PostgreSQL credentials need create/drop database privileges

Start the application with its own configuration and verify readiness. Keep SERVER_GRPC_AUTHORIZATION_ENABLED=true on the test application: the suite tests denied requests too. The app and test runner are separate processes, so changing the runner's settings does not reconfigure the app.

Keep .env for application configuration. The integration task loads it for settings such as DATABASE_*; file values override inherited application settings. It does not load .env.example. Pass test-client addresses and keys through the process environment or CI job settings.

The gRPC integration helper defaults to the public development key from the seed migration. HTTP integration requires an explicit HTTP_API_KEY. The load suites use GRPC_API_KEY for both protocols and default to the same development key. CI supplies the keys explicitly. Override them through the environment when the application's test credentials differ.

SQLite repository tests use temporary files and need no database service.

The integration CI workflow is a complete example of application, backend, address, and test-credential setup. It disables the application's authentication rate limiter for its test load. Keep such overrides scoped to the test application.

HTTP schemas and boundary checks​

The Go integration suites cover authentication, authorization, credential changes, token boundaries, UUID handling, and pagination. The authentication harness uses the real service and repository with an isolated database. It checks that rejected and no-op updates preserve credentials and existing sessions, and that normalized duplicate emails return 409. These tests run in the existing SQLite, MySQL, and PostgreSQL integration matrix.

The harness validates HTTP responses against the generated OpenAPI document through its shared HTTP helper. Validation checks response bodies, required fields, content types, and declared status codes. Behavioral assertions still check the expected status, user identity, and session transitions. The HTTP client suite exercises the running application with both generated SDKs, and the probe checks verify health, readiness, and application metrics.

User responses require a UUID, email, creation time, roles, and permissions; empty role and permission arrays are valid. Creation time retains the existing YYYY-MM-DD HH:MM:SS format. Token responses require nonempty access and refresh tokens and a nonnegative integer lifetime in whole seconds. Subsecond lifetimes round down to zero.

These checks complement oasdiff, which compares API compatibility between revisions. Schema validation applies to the responses exercised by the tests; it does not generate additional requests or establish business correctness by itself.

Choosing checks before review​

  • Run applicable task test:* commands for the changed behavior, using the suites and backends relevant to the change.
  • Use task lint for full code and project validation, changes spanning several areas, or shared tooling and dependency changes. It includes Go analysis, security, licenses, capabilities, commit history, API compatibility, and source checks. Individual checks remain available through task help.
  • Use task docs:check for documentation or website changes. It runs Markdown and prose checks, installs locked website dependencies, then tests the tooling, validates documents, builds the site, and checks TypeScript.
  • For a focused change, select the relevant checks above. A workflow edit can use its YAML and GitHub Actions checks. Repeat checks after relevant edits or failures, and report material checks left unrun.

task lint and task docs:check run their checks in sequence and stop at the first failed command. Findings from NilAway, Capslock, and OpenAPI compatibility checks are advisory in task lint. A failed invocation does not mean every check ran. Neither command selects checks automatically from changed files or applies formatting fixes. History and compatibility comparisons require origin/master by default. task lint:openapi-breaking OPENAPI_BASE_REF=REVISION checks another base. CI supplies the pull request base or the previous push commit and runs the same Task command. OpenAPI compatibility findings are reported for review without failing the command or CI; no ignore file is applied. Tool errors and invalid comparison inputs still fail the command. OpenAPI schema lint and integration tests remain blocking.

task lint:capabilities reports capability additions and removals against origin/master for review. CI uses the same advisory task on non-master branches. Capability differences do not fail the task; setup and analysis errors still do.

For a documentation preview, use task docs:serve. For a focused metadata and source-link check after installing website dependencies, use npm --prefix pages run validate-docs with the pinned tool environment. The full documentation build also checks published links and anchors. Follow the documentation policy for publishing and application-specific evidence. CI runs its configured jobs independently of the local checks selected here.


Source: docs/handbook/testing.md. Revision: 3841815ceb9e.