Skip to main content

Testing Guide

This document maps every vibeD feature to its test coverage, whether tests are automated or manual, and what gaps remain. Use it to plan test automation and verify features during development.

Running Tests

# Unit tests (no cluster required)
make test

# Integration tests (requires Kind cluster with vibeD deployed)
make test-integration-setup # Load test images into Kind
make test-integration # Full suite, ~10 min timeout
make test-integration-short # Short mode, ~5 min timeout

# Cleanup test namespaces
make test-cleanup

CI runs go test ./... -short -count=1 on every PR and push to main.

Test Coverage Matrix

Fully Automated (Unit Tests)

These run without a cluster and are included in CI.

FeatureTest FileTestsWhat's Covered
Event Businternal/events/bus_test.go8Pub/sub, multiple subscribers, unsubscribe, context cancel, slow consumers, concurrency
Garbage Collectorinternal/gc/collector_test.go9Orphaned jobs/configmaps/deployments, active artifacts preserved, dry-run, context cancel
SQLite Storeinternal/store/sqlite_test.go12CRUD, list with filters, shared_with, versions, persistence across reopens

Fully Automated (Integration Tests)

These require a Kind cluster (make dev or make test-integration-setup). Tagged with //go:build integration.

FeatureTest FileTestsWhat's Covered
K8s Deployerinternal/deployer/kubernetes_integration_test.go7Deploy, update, delete, logs, URL, full lifecycle
Knative Deployerinternal/deployer/knative_integration_test.go5Deploy, update, delete, full lifecycle (skipped if no Knative CRDs)
Orchestratorinternal/orchestrator/orchestrator_integration_test.go11Deploy, list, filter, update, delete, targets, invalid input, duplicates, build failures, logs
HTTP APIinternal/frontend/handler_integration_test.go3List artifacts, list targets, 404 for missing artifact
Authenticationinternal/auth/auth_integration_test.go8Valid/invalid keys, missing token, skip paths, env var keys
Health Checksinternal/health/health_integration_test.go7Liveness, readiness, component details, not-ready state
Environment Detectioninternal/environment/detector_integration_test.go5K8s/Knative detection, target selection
ConfigMap Storeinternal/store/configmap_integration_test.go9CRUD, list, filter, duplicates (K8s-backed store)
Image Builderinternal/builder/builder_integration_test.go1Static site buildpack (slow, skipped with -short)
HTTP Metricsinternal/metrics/middleware_integration_test.go2Request recording, path normalization

Not Yet Automated — Manual Test Procedures

The following features were implemented in v0.1.2 and need manual verification or future test automation.


OpenTelemetry Tracing

Config: tracing.enabled: true

#TestStepsExpected
1Stdout traces (dev mode)Set tracing.enabled: true with no endpoint. Deploy an artifact.Trace JSON printed to stderr with spans: orchestrator.Deploy, builder.Build, deployer.Deploy
2OTLP exportSet tracing.endpoint: "http://jaeger:4317". Deploy an artifact. Open Jaeger UI.Trace visible with vibed service name and child spans
3Sample rateSet tracing.sampleRate: 0.0. Deploy 10 artifacts.No traces emitted
4Disabled (zero overhead)Set tracing.enabled: false. Deploy an artifact.No trace output, no performance impact
5HTTP trace propagationSend request with traceparent header.Response includes trace context, spans nested under parent

Automation opportunity: Unit test that creates a TracerProvider with an in-memory exporter, runs a mock deploy, and asserts span names/attributes.


Requires: SQLite store backend, authentication enabled

#TestStepsExpected
1Create linkPOST /api/artifacts/{id}/share-link with {"password": "test", "expires_in": "24h"}201 with token, has_password: true, expires_at set
2Access without passwordGET /api/share/{token} (password-protected link)401 "password required"
3Access with correct passwordPOST /api/share/{token} with {"password": "test"}200 with artifact name, status, URL
4Access with wrong passwordPOST /api/share/{token} with {"password": "wrong"}401 "password required"
5Access no-password linkCreate link without password. GET /api/share/{token}200 with artifact info
6Expired linkCreate link with expires_in: "1s". Wait 2s. GET /api/share/{token}404
7Revoked linkCreate link. DELETE /api/share-links/{token}. GET /api/share/{token}404
8List linksGET /api/artifacts/{id}/share-linksArray of share links
9MCP create_share_linkCall via MCP toolReturns token and share link metadata
10Auth bypassGET /api/share/{token} without Authorization header (auth enabled)200 (share paths skip auth)

Automation opportunity: Unit test with SQLite store + mock orchestrator. Test the full create/resolve/revoke cycle. Add to sqlite_test.go for store-level CRUD.


Rate Limiting

Config: server.rateLimit.enabled: true

#TestStepsExpected
1Under limitSend 5 requests to /api/artifacts with requestsPerSecond: 10, burst: 20All return 200
2Exceed burstSend 25 rapid requests with burst: 20First 20 return 200, rest return 429
3Retry-After headerTrigger 429Response includes Retry-After: 1 header
4Per-client isolationTwo different IPs exceed limit independentlyEach has its own bucket
5Auth user keyingTwo authenticated users, one exceeds limitOnly the exceeded user gets 429
6Skip non-API pathsExceed limit, then GET /healthzHealth endpoint returns 200 (not rate-limited)
7Metric incrementedTrigger 429. GET /metricsvibed_http_rate_limited_total counter > 0

Automation opportunity: Unit test with httptest.Server, configurable rate limiter, and rapid request loop. Straightforward to automate.

# Quick manual verification:
for i in $(seq 1 25); do
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/api/artifacts
done

Pagination (list_artifacts)

#TestStepsExpected
1Default paginationGET /api/artifacts (no params)Returns up to 50 artifacts with total count
2Custom limitGET /api/artifacts?limit=2Returns exactly 2 artifacts, total shows full count
3OffsetGET /api/artifacts?offset=1&limit=1Skips first artifact, returns second
4Max limit clampedGET /api/artifacts?limit=500Returns max 200 artifacts
5MCP paginationlist_artifacts with limit: 2, offset: 0Response includes total, offset, limit fields
6Empty resultGET /api/artifacts?offset=9999Returns empty artifacts array, total still correct

Automation opportunity: Add to sqlite_test.go — create N artifacts, verify List with various offset/limit combos. Add to handler_integration_test.go for REST API pagination.


Secret References

#TestStepsExpected
1Deploy with secret refCreate K8s Secret. Deploy with secret_refs: {"DB_PASS": "my-secret:password"}Container receives DB_PASS env var from Secret
2Missing secretDeploy with ref to non-existent SecretError: "secret not found"
3Invalid formatDeploy with secret_refs: {"X": "invalid"}Error: "must be in format 'secret-name:key'"
4Store round-tripDeploy with secret refs. get_artifact_statusSecretRefs shown in status, actual values never stored

Automation opportunity: Integration test that creates a K8s Secret, deploys with secret_refs, and verifies the container env.


OIDC Authentication

#TestStepsExpected
1Valid JWTConfigure OIDC issuer. Send request with valid JWT.200, user identity extracted from claims
2Expired JWTSend expired token401
3Wrong audienceSend JWT with different audience401
4Admin role mappingJWT with roleClaim containing adminRole valueUser gets admin privileges
5Metadata endpointGET /.well-known/oauth-protected-resourceReturns JSON with authorization_servers

Automation opportunity: Use a test JWT library to generate tokens with a mock JWKS endpoint. Verify middleware accepts/rejects correctly.


Insecure Registry

#TestStepsExpected
1HTTP pushSet registry.insecure: true and buildah.insecure: true. Deploy Go/Node app.Build succeeds, image pushed to HTTP registry
2HTTPS defaultSet insecure: false with HTTP-only registryBuild fails: "http: server gave HTTP response to HTTPS client"

Automation opportunity: Integration test in Kind cluster with the in-cluster HTTP registry.


Knative Gateway Port

#TestStepsExpected
1Port 80 (default)Set knative.gatewayPort: 80. Deploy.URL has no port: http://app.default.127.0.0.1.sslip.io
2Custom portSet knative.gatewayPort: 31080. Deploy.URL includes port: http://app.default.127.0.0.1.sslip.io:31080

Test Utilities

The tests/testutil/ package provides helpers for integration tests:

HelperDescription
SkipIfNoCluster(t)Skip test if no K8s cluster available
MustGetClients(t)Create K8s clients (fatal on failure)
CreateTestNamespace(t, clientset)Create isolated namespace with auto-cleanup
WaitForDeploymentReady(...)Poll until deployment is ready
WaitForPodRunning(...)Poll until pod is running
TestConfig(ns, tmpDir)Generate test config with in-memory store
RandomName()Generate DNS-safe random artifact names

Priority Automation Roadmap

Based on risk and effort, here's the recommended order for automating manual tests:

  1. Rate Limiting (low effort, high value) — Unit test with httptest, no cluster needed
  2. Pagination (low effort) — Extend existing sqlite_test.go and handler_integration_test.go
  3. Share Links (medium effort) — SQLite CRUD unit tests + REST handler integration tests
  4. Tracing (medium effort) — In-memory exporter, assert span names/attributes
  5. Secret References (medium effort) — Integration test with K8s Secret
  6. OIDC Auth (high effort) — Requires mock JWKS server