Browse Source

add junie guidelines

Olivier Massot 3 months ago
parent
commit
54df7f42b2
1 changed files with 293 additions and 0 deletions
  1. 293 0
      .junie/guidelines.md

+ 293 - 0
.junie/guidelines.md

@@ -0,0 +1,293 @@
+# AP2I Project Guidelines
+
+## Project Overview
+
+AP2I is a Symfony 7.3-based API application that is part of the OpenTalent ecosystem. 
+It's a comprehensive API platform built with modern PHP (8.2+) and leverages API Platform 4.1 for REST/GraphQL API capabilities.
+
+### Key Technologies
+- **Framework**: Symfony 7.3
+- **API Platform**: 4.1 (REST/GraphQL APIs)
+- **PHP**: 8.2+ (strict requirement)
+- **ORM**: Doctrine 3.3 with migrations
+- **Authentication**: JWT (Lexik JWT Authentication Bundle)
+- **Database**: Uses Doctrine DBAL 3.9 and a mariadb 10.4.26 DB
+- **Messaging**: Symfony Messenger for async processing
+- **Testing**: PHPUnit 9.6
+- **Code Quality**: PHPStan, PHP CS Fixer
+
+### Business Domains
+The application handles multiple business domains including:
+- Organization management
+- Education systems
+- Billing and payments
+- User profiles and access control
+- Network management
+- Booking systems
+- Export functionality
+- Shop/e-commerce features
+
+## Project Structure
+
+```
+src/
+├── Message/           # Symfony Messenger messages and handlers
+├── DataFixtures/      # Database fixtures for testing/development
+├── Validator/         # Custom validation logic
+├── Enum/              # Domain-specific enumerations
+├── Service/           # Business logic services
+└── ...               # Other domain-specific directories
+
+tests/
+├── Fixture/          # Test data fixtures and factories
+├── Application/      # Application-level integration tests
+├── Unit/            # Unit tests organized by domain
+└── ...
+
+config/               # Symfony configuration
+public/              # Web root directory
+templates/           # Twig templates
+migrations/          # Doctrine database migrations
+```
+
+## Development Guidelines
+
+### Testing
+- **Always run tests** before submitting changes to ensure correctness
+- The project uses PHPUnit for testing with comprehensive unit and application tests
+- Tests are organized by domain to mirror the application structure
+- Do not modify the SUT, except for converting private methods into protected ones, or because there is an obvious error in it
+- Take `tests/Unit/Service/Typo3/Typo3ServiceTest.php` as the reference example for writing service tests
+
+#### Test Class Structure
+Each test class should follow this structure:
+
+```php
+<?php
+
+namespace App\Tests\Unit\Service\MyDomain;
+
+use App\Service\MyDomain\MyService;
+use PHPUnit\Framework\TestCase;
+use SomeDependency\Interface;
+use PHPUnit\Framework\MockObject\MockObject;
+
+// Create testable class for protected methods access
+class TestableMyService extends MyService
+{
+    public function protectedMethod(): mixed
+    {
+        return parent::protectedMethod();
+    }
+}
+
+class MyServiceTest extends TestCase
+{
+    private Interface|MockObject $dependency;
+
+    public function setUp(): void
+    {
+        // Mock all dependencies in setUp
+        $this->dependency = $this->getMockBuilder(Interface::class)
+            ->disableOriginalConstructor()
+            ->getMock();
+    }
+
+    private function getMyServiceMockFor(string $methodName): TestableMyService|MockObject
+    {
+        return $this->getMockBuilder(TestableMyService::class)
+            ->setConstructorArgs([$this->dependency])
+            ->setMethodsExcept([$methodName])
+            ->getMock();   
+    }
+}
+```
+
+#### Mocking Strategy
+- **Use `setMethodsExcept()`** to mock all methods except the one being tested (ignore deprecation warnings)
+- **Create a dedicated mock factory method** following the pattern `getMyClassMockFor(string $methodName)`
+- **Mock factory method should:**
+  - Create the SUT mock using `getMockBuilder()`
+  - Inject dependency mocks (created in `setUp`)
+  - Define methods to exclude from mocking via `setMethodsExcept()`
+  - Return the configured mock object
+
+#### Method Mocking Rules
+- **Only mock methods that are expected to be called** during test execution
+- **Exception:** Use `expects(self::never())` when verifying a method should NOT be called
+- **Mock each method called** in the currently tested method, including SUT methods
+- **Do not mock methods** that are not involved in the test scenario
+
+Example:
+```php
+public function testClearSiteCache(): void
+{
+    $typo3Service = $this->getTypo3ServiceMockFor('clearSiteCache');
+    
+    $response = $this->getMockBuilder(ResponseInterface::class)
+        ->disableOriginalConstructor()
+        ->getMock();
+        
+    $typo3Service->expects(self::once())
+        ->method('sendCommand')
+        ->with('/otadmin/site/clear-cache', ['organization-id' => 1])
+        ->willReturn($response);
+
+    $typo3Service->clearSiteCache(1);
+}
+```
+
+#### Visibility Issues
+- **For protected methods:** Create an intermediate testable class (see `TestableTypo3Service` example)
+- **For private methods in SUT:** Change visibility to protected to enable testing
+- **Testable class pattern:**
+
+```php
+class TestableMyService extends MyService
+{
+    public function protectedMethodToTest(string $param): ResponseInterface
+    {
+        return parent::protectedMethodToTest($param);
+    }
+}
+```
+
+#### Test Naming Convention
+- **Primary test** covering most common usage: `testMethod` (where "Method" is the actual method name)
+- **Variant tests** append specific situation:
+  - `testMethodWhenNoInput`
+  - `testMethodWithInvalidContext`
+  - `testMethodWithSpecialCondition`
+
+#### Test Documentation
+- **Use @see annotations** to reference the tested method:
+```php
+/**
+ * @see MyService::clearSiteCache()
+ */
+public function testClearSiteCache(): void
+```
+
+#### Test Structure Best Practices
+- **One test per execution branch/path**
+- **Use assertions** when the method should return a result
+- **Use expectations** when the method should call other methods
+- **Use `expects(self::never())`** when verifying a method should NOT be called
+- **Cover edge cases and error scenarios**
+- **Test method variants** (e.g., with/without optional parameters)
+
+#### Dependencies and Isolation
+- **All dependencies must be mocked** in `setUp` method
+- **Tests must be isolated and independent**
+- **Use `disableOriginalConstructor()`** for mock builders
+- **Mock return values** using `willReturn()` for expected behaviors
+
+#### Example Test Patterns
+
+**Simple method expectation:**
+```php
+public function testCreateSite(): void
+{
+    $typo3Service = $this->getTypo3ServiceMockFor('createSite');
+    
+    $response = $this->getMockBuilder(ResponseInterface::class)
+        ->disableOriginalConstructor()->getMock();
+        
+    $typo3Service->expects(self::once())
+        ->method('sendCommand')
+        ->with('/otadmin/site/create', ['organization-id' => 1])
+        ->willReturn($response);
+
+    $typo3Service->createSite(1);
+}
+```
+
+**Method with conditional parameters:**
+```php
+public function testSetSiteDomainWithRedirection(): void
+{
+    $typo3Service = $this->getTypo3ServiceMockFor('setSiteDomain');
+    
+    $response = $this->getMockBuilder(ResponseInterface::class)
+        ->disableOriginalConstructor()->getMock();
+        
+    $typo3Service->expects(self::once())
+        ->method('sendCommand')
+        ->with('/otadmin/site/set-domain', [
+            'organization-id' => 1, 
+            'domain' => 'new-domain', 
+            'redirect' => 1
+        ])
+        ->willReturn($response);
+
+    $typo3Service->setSiteDomain(1, 'new-domain', true);
+}
+```
+
+#### Running Tests
+```bash
+# Run all unit tests
+docker exec ap2i php -d memory_limit=-1 vendor/phpunit/phpunit/phpunit --testsuite unit --configuration phpunit.xml.dist
+
+# Run application tests (adjust testsuite as needed)
+docker exec ap2i vendor/bin/phpunit --configuration phpunit.xml.dist
+
+# Run specific test class
+docker exec ap2i vendor/bin/phpunit tests/Unit/Service/MyDomain/MyServiceTest.php
+
+# Run with coverage (if configured)
+docker exec ap2i vendor/bin/phpunit --coverage-html coverage/
+```
+
+### Code Quality
+The project enforces strict code quality standards:
+
+#### PHPStan (Static Analysis)
+```bash
+# Run analysis
+docker exec ap2i vendor/bin/phpstan analyse
+
+# Clear cache if needed
+docker exec ap2i vendor/bin/phpstan clear-result-cache
+```
+
+#### PHP CS Fixer (Code Style)
+```bash
+# Check code style
+docker exec ap2i php vendor/bin/php-cs-fixer check --config=.php-cs-fixer.dist.php
+
+# Fix code style automatically
+docker exec ap2i php vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.dist.php
+```
+
+### Messenger (Async Processing)
+The application uses Symfony Messenger for async processing:
+
+```bash
+# Start consuming messages
+docker exec ap2i php bin/console messenger:consume async
+
+# Setup transports if needed
+docker exec ap2i php bin/console messenger:setup-transports
+```
+
+### Build Process
+- No special build process required beyond standard Symfony practices
+- Run `docker exec ap2i composer install` for dependencies
+- Run database migrations: `docker exec ap2i php bin/console doctrine:migrations:migrate`
+- Clear cache: `docker exec ap2i php bin/console cache:clear`
+
+### Code Style Guidelines
+- Follow PSR-12 coding standards (enforced by PHP CS Fixer)
+- Use strict typing where possible (PHP 8.2+ features encouraged)
+- Follow Symfony best practices for service organization
+- Organize code by business domain
+- Write comprehensive tests for all business logic
+- Use PHPStan level checks for type safety
+
+### Important Notes
+- This is a proprietary project (license: proprietary)
+- Uses custom OpenTalent packages and private GitLab repositories
+- Requires PHP 8.2 minimum
+- Database changes must be done via Doctrine migrations
+- Always run the full test suite before submitting changes