From 340889f115f0878697aefc788467a4b192079f47 Mon Sep 17 00:00:00 2001 From: Bretton Date: Thu, 2 Oct 2025 16:25:20 -0700 Subject: [PATCH] refactor: Unify testing configuration into single .env.dev file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidate all test and development configuration into a single source of truth (.env.dev) for cleaner, more maintainable setup. Changes: - Remove obsolete .env.test and .env.test.example files - Update Makefile to load .env.dev variables automatically via include - Simplify test commands (no more bash subshells or complex sourcing) - Update integration tests to read config from environment variables - Rewrite TESTING_SUMMARY.md with current unified approach - Update LOCAL_DEVELOPMENT.md to reference single config file Benefits: - Single source of truth for all configuration - Simpler test execution: just `make test` - Isolated test DB (port 5434) separate from dev (port 5433) - Better documentation and developer experience 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .env.test.example | 12 - Makefile | 10 +- TESTING_SUMMARY.md | 512 +++++++++++++++++++------- docs/LOCAL_DEVELOPMENT.md | 27 +- tests/integration/integration_test.go | 26 +- 5 files changed, 430 insertions(+), 157 deletions(-) delete mode 100644 .env.test.example diff --git a/.env.test.example b/.env.test.example deleted file mode 100644 index 361e18e..0000000 --- a/.env.test.example +++ /dev/null @@ -1,12 +0,0 @@ -# Test Environment Configuration -# This file contains environment variables for running tests -# Copy this file to .env.test and update with your actual values - -# Test Database Configuration -TEST_DATABASE_URL=postgres://your_test_user:your_test_password@localhost:5434/coves_test?sslmode=disable - -# Test Server Configuration (if needed) -TEST_PORT=8081 - -# Test CAR Storage Directory -TEST_CAR_STORAGE_DIR=/tmp/coves_test_carstore \ No newline at end of file diff --git a/Makefile b/Makefile index 3383f90..afac678 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,10 @@ RESET := \033[0m GREEN := \033[32m YELLOW := \033[33m +# Load test database configuration from .env.dev +include .env.dev +export + ##@ General help: ## Show this help message @@ -92,9 +96,9 @@ test: ## Run all tests with test database @echo "Waiting for test database to be ready..." @sleep 3 @echo "$(GREEN)Running migrations on test database...$(RESET)" - @. .env.dev && goose -dir internal/db/migrations postgres "postgresql://$$POSTGRES_TEST_USER:$$POSTGRES_TEST_PASSWORD@localhost:$$POSTGRES_TEST_PORT/$$POSTGRES_TEST_DB?sslmode=disable" up || true + @goose -dir internal/db/migrations postgres "postgresql://$(POSTGRES_TEST_USER):$(POSTGRES_TEST_PASSWORD)@localhost:$(POSTGRES_TEST_PORT)/$(POSTGRES_TEST_DB)?sslmode=disable" up || true @echo "$(GREEN)Running tests...$(RESET)" - @. .env.dev && TEST_DATABASE_URL="postgresql://$$POSTGRES_TEST_USER:$$POSTGRES_TEST_PASSWORD@localhost:$$POSTGRES_TEST_PORT/$$POSTGRES_TEST_DB?sslmode=disable" go test ./... -v + @go test ./... -v @echo "$(GREEN)✓ Tests complete$(RESET)" test-db-reset: ## Reset test database @@ -104,7 +108,7 @@ test-db-reset: ## Reset test database @docker-compose -f docker-compose.dev.yml --env-file .env.dev --profile test up -d postgres-test @echo "Waiting for PostgreSQL to be ready..." @sleep 3 - @. .env.dev && goose -dir internal/db/migrations postgres "postgresql://$$POSTGRES_TEST_USER:$$POSTGRES_TEST_PASSWORD@localhost:$$POSTGRES_TEST_PORT/$$POSTGRES_TEST_DB?sslmode=disable" up || true + @goose -dir internal/db/migrations postgres "postgresql://$(POSTGRES_TEST_USER):$(POSTGRES_TEST_PASSWORD)@localhost:$(POSTGRES_TEST_PORT)/$(POSTGRES_TEST_DB)?sslmode=disable" up || true @echo "$(GREEN)✓ Test database reset$(RESET)" test-db-stop: ## Stop test database diff --git a/TESTING_SUMMARY.md b/TESTING_SUMMARY.md index eee5712..de43b91 100644 --- a/TESTING_SUMMARY.md +++ b/TESTING_SUMMARY.md @@ -1,153 +1,407 @@ -# Repository Testing Summary - -## Lexicon Schema Validation - -We use Indigo's lexicon validator to ensure all AT Protocol schemas are valid and properly structured. - -### Validation Components: -1. **Schema Validation** (`cmd/validate-lexicon/main.go`) - - Validates all 57 lexicon schema files are valid JSON - - Checks cross-references between schemas - - Validates test data files against their schemas - -2. **Test Data** (`tests/lexicon-test-data/`) - - Contains example records for validation testing - - Files use naming convention: - - `*-valid*.json` - Should pass validation - - `*-invalid-*.json` - Should fail validation (tests error detection) - - Currently covers 5 record types: - - social.coves.actor.profile - - social.coves.community.profile - - social.coves.post.record - - social.coves.interaction.vote - - social.coves.moderation.ban - -3. **Validation Library** (`internal/validation/lexicon.go`) - - Wrapper around Indigo's ValidateRecord - - Provides type-specific validation methods - - Supports multiple input formats - -### Running Lexicon Validation +# Coves Testing Guide + +This document explains how testing works in Coves, including setup, running tests, and understanding the test infrastructure. + +## Overview + +Coves uses a unified testing approach with: +- **Single configuration file**: [.env.dev](.env.dev) for all environments (dev + test) +- **Isolated test database**: PostgreSQL on port 5434 (separate from dev on 5433) +- **Makefile commands**: Simple `make test` command handles everything +- **Docker Compose profiles**: Test database spins up automatically + +## Quick Start + +```bash +# Run all tests (starts test DB, runs migrations, executes tests) +make test + +# Reset test database (clean slate) +make test-db-reset + +# Stop test database +make test-db-stop +``` + +## Test Infrastructure + +### Configuration (.env.dev) + +All test configuration lives in [.env.dev](.env.dev): + +```bash +# Test Database Configuration +POSTGRES_TEST_DB=coves_test +POSTGRES_TEST_USER=test_user +POSTGRES_TEST_PASSWORD=test_password +POSTGRES_TEST_PORT=5434 +``` + +**No separate `.env.test` file needed!** Everything is in `.env.dev`. + +### Test Database + +The test database runs in Docker via [docker-compose.dev.yml](docker-compose.dev.yml): + +- **Service**: `postgres-test` (profile: `test`) +- **Port**: 5434 (separate from dev database on 5433) +- **Automatic startup**: The Makefile handles starting/stopping +- **Isolated data**: Completely separate from development database + +### Running Tests Manually + +If you need to run tests without the Makefile: + +```bash +# 1. Load environment variables +set -a && source .env.dev && set +a + +# 2. Start test database +docker-compose -f docker-compose.dev.yml --env-file .env.dev --profile test up -d postgres-test + +# 3. Wait for it to be ready +sleep 3 + +# 4. Run migrations +goose -dir internal/db/migrations postgres \ + "postgresql://$POSTGRES_TEST_USER:$POSTGRES_TEST_PASSWORD@localhost:$POSTGRES_TEST_PORT/$POSTGRES_TEST_DB?sslmode=disable" up + +# 5. Run tests +go test ./... -v + +# 6. Stop test database (optional) +docker-compose -f docker-compose.dev.yml --env-file .env.dev --profile test stop postgres-test +``` + +**Note**: The Makefile automatically loads `.env.dev` variables, so `make test` is simpler than running manually. + +## Test Types + +### 1. Unit Tests + +Test individual components in isolation. + +**Example**: [internal/core/users/service_test.go](internal/core/users/service_test.go) + +```bash +# Run unit tests for a specific package +go test -v ./internal/core/users/... +``` + +### 2. Integration Tests + +Test full request/response flows with a real database. + +**Location**: [tests/integration/](tests/integration/) + +**How they work**: +- Start test database +- Run migrations +- Execute HTTP requests against real handlers +- Verify responses + +**Example**: [tests/integration/integration_test.go](tests/integration/integration_test.go) + +```bash +# Run integration tests +make test +# or +go test -v ./tests/integration/... +``` + +### 3. Lexicon Validation Tests + +Validate AT Protocol Lexicon schemas and test data. + +**Components**: +- **Schemas**: [internal/atproto/lexicon/](internal/atproto/lexicon/) - 57 lexicon schema files +- **Test Data**: [tests/lexicon-test-data/](tests/lexicon-test-data/) - Example records for validation +- **Validator**: [cmd/validate-lexicon/](cmd/validate-lexicon/) - Validation tool +- **Library**: [internal/validation/](internal/validation/) - Validation helpers + +**Running validation**: + ```bash -# Full validation (schemas + test data) - DEFAULT +# Full validation (schemas + test data) go run cmd/validate-lexicon/main.go -# Schemas only (skip test data validation) +# Schemas only (skip test data) go run cmd/validate-lexicon/main.go --schemas-only # Verbose output go run cmd/validate-lexicon/main.go -v -# Strict validation mode +# Strict mode go run cmd/validate-lexicon/main.go --strict ``` -### Test Coverage Warning -The validator explicitly outputs which record types have test data and which don't. This prevents false confidence from passing tests when schemas lack test coverage. +**Test data naming convention**: +- `*-valid*.json` - Should pass validation +- `*-invalid-*.json` - Should fail validation (tests error detection) -Example output: +**Current coverage** (as of last update): +- ✅ social.coves.actor.profile +- ✅ social.coves.community.profile +- ✅ social.coves.post.record +- ✅ social.coves.interaction.vote +- ✅ social.coves.moderation.ban + +## Database Migrations + +Migrations are managed with [goose](https://github.com/pressly/goose) and stored in [internal/db/migrations/](internal/db/migrations/). + +### Running Migrations + +```bash +# Development database +make db-migrate + +# Test database (automatically run by `make test`) +make test-db-reset ``` -📋 Validation Summary: - Valid test files: 5/5 passed - Invalid test files: 2/2 correctly rejected - ✅ All test files behaved as expected! +### Creating Migrations + +```bash +# Create a new migration +goose -dir internal/db/migrations create migration_name sql -📊 Test Data Coverage Summary: - - Records with test data: 5 types - - Valid test files: 5 - - Invalid test files: 2 (for error validation) +# This creates: +# internal/db/migrations/YYYYMMDDHHMMSS_migration_name.sql +``` - Tested record types: - ✓ social.coves.actor.profile - ✓ social.coves.community.profile - ✓ social.coves.post.record - ✓ social.coves.interaction.vote - ✓ social.coves.moderation.ban +### Migration Best Practices - ⚠️ Record types without test data: - - social.coves.actor.membership - - social.coves.actor.subscription - - social.coves.community.rules - ... (8 more) +- **Always test migrations** on test database first +- **Write both Up and Down** migrations +- **Keep migrations atomic** - one logical change per migration +- **Test rollback** - verify the Down migration works +- **Don't modify old migrations** - create new ones instead - Coverage: 5/16 record types have test data (31.2%) +## Test Database Management + +### Fresh Start + +```bash +# Complete reset (deletes all data) +make test-db-reset ``` -### Running Tests +### Connecting to Test Database + ```bash -# Run lexicon validation tests -go test -v ./tests/lexicon_validation_test.go - -# Run validation library tests -go test -v ./internal/validation/... -``` - -## Test Infrastructure Setup -- Created Docker Compose configuration for isolated test database on port 5434 -- Test database is completely separate from development (5433) and production (5432) -- Configuration location: `/internal/db/test_db_compose/docker-compose.yml` - -## Repository Service Implementation -Successfully integrated Indigo's carstore for ATProto repository management: - -### Key Components: -1. **CarStore Wrapper** (`/internal/atproto/carstore/carstore.go`) - - Wraps Indigo's carstore implementation - - Manages CAR file storage with PostgreSQL metadata - -2. **RepoStore** (`/internal/atproto/carstore/repo_store.go`) - - Combines CarStore with UserMapping for DID-based access - - Handles DID to UID conversions transparently - -3. **UserMapping** (`/internal/atproto/carstore/user_mapping.go`) - - Maps ATProto DIDs to numeric UIDs (required by Indigo) - - Auto-creates user_maps table via GORM - -4. **Repository Service** (`/internal/core/repository/service.go`) - - Updated to use Indigo's carstore instead of custom implementation - - Handles empty repositories gracefully - - Placeholder CID for empty repos until records are added - -## Test Results -All repository tests passing: -- ✅ CreateRepository - Creates user mapping and repository record -- ✅ ImportExport - Handles empty CAR data correctly -- ✅ DeleteRepository - Removes repository and carstore data -- ✅ CompactRepository - Runs garbage collection -- ✅ UserMapping - DID to UID mapping works correctly - -## Implementation Notes -1. **Empty Repositories**: Since Indigo's carstore expects actual CAR data, we handle empty repositories by: - - Creating user mapping only - - Using placeholder CID - - Returning empty byte array on export - - Actual CAR data will be created when records are added - -2. **Database Tables**: Indigo's carstore auto-creates: - - `user_maps` (DID ↔ UID mapping) - - `car_shards` (CAR file metadata) - - `block_refs` (IPLD block references) - -3. **Migration**: Created migration to drop our custom block_refs table to avoid conflicts - -## Next Steps -To fully utilize the carstore, implement: -1. Record CRUD operations using carstore's DeltaSession -2. Proper CAR file generation when adding records -3. Commit tracking with proper signatures -4. Repository versioning and history - -## Running Tests +# Using psql +PGPASSWORD=test_password psql -h localhost -p 5434 -U test_user -d coves_test + +# Using docker exec +docker exec -it coves-test-postgres psql -U test_user -d coves_test +``` + +### Inspecting Test Data + +```sql +-- List all tables +\dt + +-- View table schema +\d table_name + +-- Query data +SELECT * FROM users; + +-- Check migrations +SELECT * FROM goose_db_version; +``` + +## Writing Tests + +### Integration Test Template + +```go +package integration + +import ( + "testing" + // ... imports +) + +func TestYourFeature(t *testing.T) { + db := setupTestDB(t) + defer db.Close() + + // Wire up dependencies + repo := postgres.NewYourRepository(db) + service := yourpackage.NewYourService(repo) + + // Create test router + r := chi.NewRouter() + r.Mount("/api/path", routes.YourRoutes(service)) + + // Make request + req := httptest.NewRequest("GET", "/api/path", nil) + w := httptest.NewRecorder() + r.ServeHTTP(w, req) + + // Assert + if w.Code != http.StatusOK { + t.Errorf("Expected 200, got %d", w.Code) + } +} +``` + +### Test Helper: setupTestDB + +The `setupTestDB` function (in [tests/integration/integration_test.go](tests/integration/integration_test.go)): +- Reads config from environment variables (set by `.env.dev`) +- Connects to test database +- Runs migrations +- Cleans up test data +- Returns ready-to-use `*sql.DB` + +## Common Test Commands + ```bash -# Start test database -cd internal/db/test_db_compose -docker-compose up -d +# Run all tests +make test -# Run repository tests -TEST_DATABASE_URL="postgres://test_user:test_password@localhost:5434/coves_test?sslmode=disable" \ - go test -v ./internal/core/repository/... +# Run specific test package +go test -v ./internal/core/users/... -# Stop test database -docker-compose down -``` \ No newline at end of file +# Run specific test +go test -v ./tests/integration/... -run TestCreateUser + +# Run with coverage +go test -v -cover ./... + +# Run with race detector +go test -v -race ./... + +# Verbose output +go test -v ./... + +# Clean and run +make test-db-reset && make test +``` + +## Troubleshooting + +### Test database won't start + +```bash +# Check if port 5434 is in use +lsof -i :5434 + +# Check container logs +docker logs coves-test-postgres + +# Nuclear reset +docker-compose -f docker-compose.dev.yml --profile test down -v +make test-db-reset +``` + +### Migrations failing + +```bash +# Load environment and check migration status +set -a && source .env.dev && set +a +goose -dir internal/db/migrations postgres \ + "postgresql://$POSTGRES_TEST_USER:$POSTGRES_TEST_PASSWORD@localhost:$POSTGRES_TEST_PORT/$POSTGRES_TEST_DB?sslmode=disable" status + +# Reset and retry +make test-db-reset +``` + +### Tests can't connect to database + +```bash +# Verify test database is running +docker ps | grep coves-test-postgres + +# Verify environment variables +set -a && source .env.dev && set +a && echo "Test DB Port: $POSTGRES_TEST_PORT" + +# Test connection manually +PGPASSWORD=test_password psql -h localhost -p 5434 -U test_user -d coves_test -c "SELECT 1" +``` + +### Lexicon validation errors + +```bash +# Check schema syntax +cat internal/atproto/lexicon/path/to/schema.json | jq . + +# Validate specific schema +go run cmd/validate-lexicon/main.go -v + +# Check test data format +cat tests/lexicon-test-data/your-test.json | jq . +``` + +## Best Practices + +### Test Organization +- ✅ Unit tests live next to the code they test (`*_test.go`) +- ✅ Integration tests live in `tests/integration/` +- ✅ Test data lives in `tests/lexicon-test-data/` +- ✅ One test file per feature/endpoint + +### Test Data +- ✅ Use `@example.com` emails for test users (auto-cleaned by setupTestDB) +- ✅ Clean up data in tests (or rely on setupTestDB cleanup) +- ✅ Don't rely on specific test execution order +- ✅ Each test should be independent + +### Database Tests +- ✅ Always use the test database (port 5434) +- ✅ Never connect to development database (port 5433) in tests +- ✅ Use transactions for fast test cleanup (where applicable) +- ✅ Test with realistic data sizes + +### Lexicon Tests +- ✅ Create both valid and invalid test cases +- ✅ Cover all required fields +- ✅ Test edge cases (empty strings, max lengths, etc.) +- ✅ Update tests when schemas change + +## CI/CD Integration + +When setting up CI/CD, the test pipeline should: + +```yaml +# Example GitHub Actions workflow +steps: + - name: Start test database + run: | + docker-compose -f docker-compose.dev.yml --env-file .env.dev --profile test up -d postgres-test + sleep 5 + + - name: Run migrations + run: make test-db-migrate # (add this to Makefile if needed) + + - name: Run tests + run: make test + + - name: Cleanup + run: docker-compose -f docker-compose.dev.yml --profile test down -v +``` + +## Related Documentation + +- [Makefile](Makefile) - All test commands +- [.env.dev](.env.dev) - Test configuration +- [docker-compose.dev.yml](docker-compose.dev.yml) - Test database setup +- [LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md) - Full development setup +- [CLAUDE.md](CLAUDE.md) - Build guidelines + +## Getting Help + +If tests are failing: +1. Check [Troubleshooting](#troubleshooting) section above +2. Verify `.env.dev` has correct test database config +3. Run `make test-db-reset` for a clean slate +4. Check test database logs: `docker logs coves-test-postgres` + +For questions about: +- **Test infrastructure**: This document +- **AT Protocol testing**: [ATPROTO_GUIDE.md](ATPROTO_GUIDE.md) +- **Development setup**: [LOCAL_DEVELOPMENT.md](docs/LOCAL_DEVELOPMENT.md) diff --git a/docs/LOCAL_DEVELOPMENT.md b/docs/LOCAL_DEVELOPMENT.md index 68c13a7..4a6dc7c 100644 --- a/docs/LOCAL_DEVELOPMENT.md +++ b/docs/LOCAL_DEVELOPMENT.md @@ -190,10 +190,13 @@ make db-shell # Open psql shell to the database ### Testing Commands ```bash -make test # Run all tests with test database -make test-db-reset # Reset test database +make test # Run all tests (starts test DB, runs migrations, executes tests) +make test-db-reset # Reset test database (clean slate) +make test-db-stop # Stop test database ``` +**See [TESTING_SUMMARY.md](../TESTING_SUMMARY.md) for complete testing documentation.** + ### Workflow Commands ```bash @@ -385,9 +388,9 @@ curl http://localhost:3001/xrpc/_health ## Environment Variables -All configuration is in `.env.dev`: +All configuration is in [.env.dev](../.env.dev) - a single file for both development and testing: -### Database Configuration +### Development Database (Port 5433) ```bash POSTGRES_HOST=localhost POSTGRES_PORT=5433 @@ -396,6 +399,14 @@ POSTGRES_USER=dev_user POSTGRES_PASSWORD=dev_password ``` +### Test Database (Port 5434) +```bash +POSTGRES_TEST_DB=coves_test +POSTGRES_TEST_USER=test_user +POSTGRES_TEST_PASSWORD=test_password +POSTGRES_TEST_PORT=5434 +``` + ### PDS Configuration ```bash PDS_HOSTNAME=localhost @@ -405,12 +416,6 @@ PDS_ADMIN_PASSWORD=admin PDS_SERVICE_HANDLE_DOMAINS=.local.coves.dev ``` -### Relay Configuration -```bash -BGS_PORT=2471 -BGS_ADMIN_KEY=dev-admin-key -``` - ### AppView Configuration ```bash APPVIEW_PORT=8081 @@ -424,6 +429,8 @@ ENV=development LOG_LEVEL=debug ``` +**No separate `.env.test` file needed!** All configuration (dev + test) is in `.env.dev`. + ## Next Steps 1. **Build the Firehose Subscriber** - Create the AppView component that subscribes to the relay diff --git a/tests/integration/integration_test.go b/tests/integration/integration_test.go index 92bc816..3357b94 100644 --- a/tests/integration/integration_test.go +++ b/tests/integration/integration_test.go @@ -6,6 +6,7 @@ import ( "bytes" "database/sql" "encoding/json" + "fmt" "net/http" "net/http/httptest" "os" @@ -19,10 +20,29 @@ import ( ) func setupTestDB(t *testing.T) *sql.DB { - dbURL := os.Getenv("TEST_DATABASE_URL") - if dbURL == "" { - dbURL = "postgres://test_user:test_password@localhost:5434/coves_test?sslmode=disable" + // Build connection string from environment variables (set by .env.dev) + // These are loaded by the Makefile when running tests + testUser := os.Getenv("POSTGRES_TEST_USER") + testPassword := os.Getenv("POSTGRES_TEST_PASSWORD") + testPort := os.Getenv("POSTGRES_TEST_PORT") + testDB := os.Getenv("POSTGRES_TEST_DB") + + // Fallback to defaults if not set + if testUser == "" { + testUser = "test_user" } + if testPassword == "" { + testPassword = "test_password" + } + if testPort == "" { + testPort = "5434" + } + if testDB == "" { + testDB = "coves_test" + } + + dbURL := fmt.Sprintf("postgres://%s:%s@localhost:%s/%s?sslmode=disable", + testUser, testPassword, testPort, testDB) db, err := sql.Open("postgres", dbURL) if err != nil { -- 2.51.2