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>
67 lines
1.4 KiB
Go
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
|
|
}
|