diff --git a/.cursor/rules/testing-patterns.mdc b/.cursor/rules/testing-patterns.mdc index a0e64dbae..38bebc700 100644 --- a/.cursor/rules/testing-patterns.mdc +++ b/.cursor/rules/testing-patterns.mdc @@ -9,7 +9,50 @@ alwaysApply: false Coolify employs **comprehensive testing strategies** using modern PHP testing frameworks to ensure reliability of deployment operations, infrastructure management, and user interactions. -!Important: Always run tests inside `coolify` container. +### Test Execution Rules + +**CRITICAL**: Tests are categorized by database dependency: + +#### Unit Tests (`tests/Unit/`) +- **MUST NOT** use database connections +- **MUST** use mocking for models and external dependencies +- **CAN** run outside Docker: `./vendor/bin/pest tests/Unit` +- Purpose: Test isolated logic, helper functions, and business rules + +#### Feature Tests (`tests/Feature/`) +- **MAY** use database connections (factories, migrations, models) +- **MUST** run inside Docker container: `docker exec -it coolify php artisan test` +- **MUST** use `RefreshDatabase` trait if touching database +- Purpose: Test API endpoints, workflows, and integration scenarios + +**Rule of thumb**: If your test needs `Server::factory()->create()` or any database operation, it's a Feature test and MUST run in Docker. + +### Prefer Mocking Over Database + +When writing tests, always prefer mocking over real database operations: + +```php +// ❌ BAD: Unit test using database +it('extracts custom commands', function () { + $server = Server::factory()->create(['ip' => '1.2.3.4']); + $commands = extract_custom_proxy_commands($server, $yaml); + expect($commands)->toBeArray(); +}); + +// ✅ GOOD: Unit test using mocking +it('extracts custom commands', function () { + $server = Mockery::mock('App\Models\Server'); + $server->shouldReceive('proxyType')->andReturn('traefik'); + $commands = extract_custom_proxy_commands($server, $yaml); + expect($commands)->toBeArray(); +}); +``` + +**Design principles for testable code:** +- Use dependency injection instead of global state +- Create interfaces for external dependencies (SSH, Docker, etc.) +- Separate business logic from data persistence +- Make functions accept interfaces instead of concrete models when possible ## Testing Framework Stack diff --git a/CLAUDE.md b/CLAUDE.md index 22e762182..eafcd97f6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -23,7 +23,14 @@ ### Backend Development ### Code Quality - `./vendor/bin/pint` - Run Laravel Pint for code formatting - `./vendor/bin/phpstan` - Run PHPStan for static analysis -- `./vendor/bin/pest` - Run Pest tests +- `./vendor/bin/pest` - Run Pest tests (unit tests only, without database) + +### Running Tests +**IMPORTANT**: Tests that require database connections MUST be run inside the Docker container: +- **Inside Docker**: `docker exec -it coolify php artisan test` (for feature tests requiring database) +- **Outside Docker**: `./vendor/bin/pest tests/Unit` (for pure unit tests without database dependencies) +- Unit tests should use mocking and avoid database connections +- Feature tests that require database must be run in the `coolify` container ## Architecture Overview @@ -173,6 +180,21 @@ ### Testing Strategy - **Mocking**: Use Laravel's built-in mocking for external services - **Database**: Use RefreshDatabase trait for test isolation +#### Test Execution Environment +**CRITICAL**: Database-dependent tests MUST run inside Docker container: +- **Unit Tests** (`tests/Unit/`): Should NOT use database. Use mocking. Run with `./vendor/bin/pest tests/Unit` +- **Feature Tests** (`tests/Feature/`): May use database. MUST run inside Docker with `docker exec -it coolify php artisan test` +- If a test needs database (factories, migrations, etc.), it belongs in `tests/Feature/` +- Always mock external services and SSH connections in tests + +#### Test Design Philosophy +**PREFER MOCKING**: When designing features and writing tests: +- **Design for testability**: Structure code so it can be tested without database (use dependency injection, interfaces) +- **Mock by default**: Unit tests should mock models and external dependencies using Mockery +- **Avoid database when possible**: If you can test the logic without database, write it as a Unit test +- **Only use database when necessary**: Feature tests should test integration points, not isolated logic +- **Example**: Instead of `Server::factory()->create()`, use `Mockery::mock('App\Models\Server')` in unit tests + ### Routing Conventions - Group routes by middleware and prefix - Use route model binding for cleaner controllers @@ -543,6 +565,10 @@ ### Pest Tests - You must not remove any tests or test files from the tests directory without approval. These are not temporary or helper files - these are core to the application. - Tests should test all of the happy paths, failure paths, and weird paths. - Tests live in the `tests/Feature` and `tests/Unit` directories. +- **Unit tests** MUST use mocking and avoid database. They can run outside Docker. +- **Feature tests** can use database but MUST run inside Docker container. +- **Design for testability**: Structure code to be testable without database when possible. Use dependency injection and interfaces. +- **Mock by default**: Prefer `Mockery::mock()` over `Model::factory()->create()` in unit tests. - Pest tests look and behave like this: it('is true', function () { @@ -551,11 +577,23 @@ ### Pest Tests ### Running Tests -- Run the minimal number of tests using an appropriate filter before finalizing code edits. -- To run all tests: `php artisan test`. -- To run all tests in a file: `php artisan test tests/Feature/ExampleTest.php`. -- To filter on a particular test name: `php artisan test --filter=testName` (recommended after making a change to a related file). -- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite to ensure everything is still passing. +**IMPORTANT**: Always run tests in the correct environment based on database dependencies: + +**Unit Tests (no database):** +- Run outside Docker: `./vendor/bin/pest tests/Unit` +- Run specific file: `./vendor/bin/pest tests/Unit/ProxyCustomCommandsTest.php` +- These tests use mocking and don't require PostgreSQL + +**Feature Tests (with database):** +- Run inside Docker: `docker exec -it coolify php artisan test` +- Run specific file: `docker exec -it coolify php artisan test tests/Feature/ExampleTest.php` +- Filter by name: `docker exec -it coolify php artisan test --filter=testName` +- These tests require PostgreSQL and use factories/migrations + +**General Guidelines:** +- Run the minimal number of tests using an appropriate filter before finalizing code edits +- When the tests relating to your changes are passing, ask the user if they would like to run the entire test suite +- If you get database connection errors, you're running a Feature test outside Docker - move it inside ### Pest Assertions - When asserting status codes on a response, use the specific method like `assertForbidden` and `assertNotFound` instead of using `assertStatus(403)` or similar, e.g.: @@ -650,7 +688,12 @@ ### Replaced Utilities ## Test Enforcement - Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass. -- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test` with a specific filename or filter. +- Run the minimum number of tests needed to ensure code quality and speed. +- **For Unit tests**: Use `./vendor/bin/pest tests/Unit/YourTest.php` (runs outside Docker) +- **For Feature tests**: Use `docker exec -it coolify php artisan test --filter=YourTest` (runs inside Docker) +- Choose the correct test type based on database dependency: + - No database needed? → Unit test with mocking + - Database needed? → Feature test in Docker