rdev/internal/testutil/testutil.go
jordan 538ea57ed4 feat: Add claude-config API, security hardening, and testing infrastructure
Claude Config API (v0.6):
- Add CRUD endpoints for commands, skills, and agents
- Commands/skills/agents stored in /workspace/.claude/ (per-project, in git)
- Credentials shared via PVC at /root/.claude/ (shared across pods)
- Use base64 encoding for file writes (prevents shell injection)
- Add content size limits (1MB max)

Security Hardening:
- Add sanitize package for command/prompt validation
- Add rate limiting middleware (token bucket algorithm)
- Add concurrent command limiting
- Add input sanitization to all command handlers
- Gitignore secrets.yaml and credentials.yaml
- Add *.example templates for secrets

Testing Infrastructure:
- Add testutil package with mocks and fixtures
- Add unit tests for auth package (63% coverage)
- Add unit tests for executor (47% coverage)
- Add handler integration tests (40% coverage)
- Add 100% coverage for sanitize, cmdlimit packages
- Add 96% coverage for ratelimit package

Infrastructure:
- Shared Claude credentials PVC (ReadWriteMany)
- Reduced workspace PVC size from 20Gi to 5Gi
- Add init container cleanup before git clone
- Document Longhorn RWX requirements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-25 01:29:13 -07:00

67 lines
1.4 KiB
Go

// Package testutil provides testing utilities for rdev-api.
package testutil
import (
"context"
"database/sql"
"os"
"testing"
"time"
_ "github.com/lib/pq" // PostgreSQL driver
)
// TestDB returns a database connection for testing.
// Uses TEST_DATABASE_URL or falls back to the standard local dev connection.
func TestDB(t *testing.T) *sql.DB {
t.Helper()
dsn := os.Getenv("TEST_DATABASE_URL")
if dsn == "" {
dsn = "postgres://appuser:localdev@localhost:5433/rdev?sslmode=disable"
}
db, err := sql.Open("postgres", dsn)
if err != nil {
t.Fatalf("open database: %v", err)
}
// Verify connection
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := db.PingContext(ctx); err != nil {
t.Skipf("database not available: %v", err)
}
t.Cleanup(func() {
db.Close()
})
return db
}
// CleanupTestKeys removes all test keys from the database.
func CleanupTestKeys(t *testing.T, db *sql.DB) {
t.Helper()
_, err := db.Exec("DELETE FROM api_keys WHERE name LIKE 'test-%'")
if err != nil {
t.Fatalf("cleanup test keys: %v", err)
}
}
// TimePtr returns a pointer to a time.Time.
func TimePtr(t time.Time) *time.Time {
return &t
}
// MustParseTime parses a time string or panics.
func MustParseTime(layout, value string) time.Time {
t, err := time.Parse(layout, value)
if err != nil {
panic(err)
}
return t
}