diff --git a/CLAUDE.md b/CLAUDE.md index cc37637..dfd2976 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -62,9 +62,10 @@ composer cs-fix # Fix code style issues ### WireMock Server ```bash -docker-compose up -d # Start WireMock server on port 8080 -docker-compose down # Stop WireMock server -docker-compose logs wiremock # View WireMock logs +docker run -d -p 8080:8080 wiremock/wiremock:latest # Start WireMock server on port 8080 +docker ps | grep wiremock # Check if WireMock is running +docker logs # View WireMock logs +docker stop # Stop WireMock server ``` ## Development Workflow & Conventions @@ -249,13 +250,13 @@ modules: 1. Start WireMock server: ```bash - docker-compose up -d + docker run -d -p 8080:8080 wiremock/wiremock:latest ``` -2. Wait for WireMock to be healthy: +2. Wait for WireMock to be ready: ```bash - docker-compose ps - # Should show "healthy" status + curl http://localhost:8080/__admin/health + # Should return: {"status":"OK"} ``` 3. Run unit tests (don't require WireMock): @@ -277,7 +278,7 @@ modules: - Use `grabAllRequests()` to see all requests made - Use `grabUnmatchedRequests()` to see requests that didn't match any stub -- Check WireMock logs: `docker-compose logs wiremock` +- Check WireMock logs: `docker logs ` - Near-miss analysis is automatically included in verification error messages ## Common Patterns diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7752b2a..0130a18 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -53,7 +53,7 @@ composer install ### Start WireMock Server ```bash -docker-compose up -d +docker run -d -p 8080:8080 wiremock/wiremock:latest ``` Verify WireMock is running: @@ -93,7 +93,7 @@ composer test Functional tests require WireMock to be running: ```bash -docker-compose up -d +docker run -d -p 8080:8080 wiremock/wiremock:latest composer test:functional ``` diff --git a/README.md b/README.md index 70347eb..88b1447 100644 --- a/README.md +++ b/README.md @@ -473,22 +473,22 @@ class ShoppingCartCest ## Local Development -### Using Docker Compose +### Using Docker -The project includes a `docker-compose.yml` for easy local development: +You can run WireMock using Docker: ```bash # Start WireMock -docker-compose up -d +docker run -d -p 8080:8080 wiremock/wiremock:latest # Check status -docker-compose ps +docker ps | grep wiremock # View logs -docker-compose logs -f wiremock +docker logs # Stop WireMock -docker-compose down +docker stop ``` ### Running Tests @@ -501,7 +501,7 @@ composer install composer test # Start WireMock -docker-compose up -d +docker run -d -p 8080:8080 wiremock/wiremock:latest # Build Codeception support classes vendor/bin/codecept build diff --git a/src/JasonBenett/CodeceptionModuleWiremock/Module/Wiremock.php b/src/JasonBenett/CodeceptionModuleWiremock/Module/Wiremock.php index ac95d7f..b2f91ab 100644 --- a/src/JasonBenett/CodeceptionModuleWiremock/Module/Wiremock.php +++ b/src/JasonBenett/CodeceptionModuleWiremock/Module/Wiremock.php @@ -138,10 +138,12 @@ public function haveHttpStubFor( array $headers = [], array $requestMatchers = [], ): string { + $urlKey = $this->determineUrlKey($requestMatchers); + $mapping = [ 'request' => array_merge([ 'method' => strtoupper($method), - 'url' => $url, + $urlKey => $url, ], $requestMatchers), 'response' => [ 'status' => $status, @@ -187,9 +189,11 @@ public function seeHttpRequest( string $url, array $additionalMatchers = [], ): void { + $urlKey = $this->determineUrlKey($additionalMatchers); + $pattern = array_merge([ 'method' => strtoupper($method), - 'url' => $url, + $urlKey => $url, ], $additionalMatchers); $count = $this->grabRequestCount($pattern); @@ -227,9 +231,11 @@ public function dontSeeHttpRequest( string $url, array $additionalMatchers = [], ): void { + $urlKey = $this->determineUrlKey($additionalMatchers); + $pattern = array_merge([ 'method' => strtoupper($method), - 'url' => $url, + $urlKey => $url, ], $additionalMatchers); $count = $this->grabRequestCount($pattern); @@ -608,4 +614,30 @@ private function initStreamFactory(): void $this->streamFactory = $streamFactory; } + + /** + * Determine whether to use 'url' or 'urlPath' based on request matchers + * + * When queryParameters are specified in request matchers, WireMock requires + * 'urlPath' instead of 'url' to match the path separately from query params. + * + * @param array $matchers Request matcher array + * + * @return string Either 'url' or 'urlPath' + */ + private function determineUrlKey(array $matchers): string + { + // If queryParameters are present and user hasn't explicitly set urlPath/urlPattern, + // use 'urlPath' for path-only matching (allowing separate query param matching) + if ( + isset($matchers['queryParameters']) + && !isset($matchers['urlPath']) + && !isset($matchers['urlPattern']) + ) { + return 'urlPath'; + } + + // Default to 'url' for exact URL matching + return 'url'; + } } diff --git a/tests/unit/Codeception/Module/WiremockTest.php b/tests/unit/Codeception/Module/WiremockTest.php index f078371..3cd96c5 100644 --- a/tests/unit/Codeception/Module/WiremockTest.php +++ b/tests/unit/Codeception/Module/WiremockTest.php @@ -6,6 +6,7 @@ use Codeception\Lib\ModuleContainer; use Codeception\Test\Unit; +use Generator; use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; use GuzzleHttp\HandlerStack; @@ -270,4 +271,82 @@ public function testSendClearRequestsCallsClearEndpoint(): void // Should not throw exception $this->assertTrue(true); } + + /** + * Test determineUrlKey method for proper url vs urlPath selection + * + * @dataProvider urlKeyDataProvider + */ + public function testDetermineUrlKey(array $matchers, string $expectedKey): void + { + $reflection = new ReflectionClass($this->module); + $method = $reflection->getMethod('determineUrlKey'); + + $result = $method->invoke($this->module, $matchers); + + $this->assertSame($expectedKey, $result); + } + + /** + * Data provider for testDetermineUrlKey + * + * @return Generator, expectedKey: string}> + */ + public static function urlKeyDataProvider(): Generator + { + yield 'no matchers - uses url' => [ + 'matchers' => [], + 'expectedKey' => 'url', + ]; + + yield 'only method matcher - uses url' => [ + 'matchers' => ['method' => 'GET'], + 'expectedKey' => 'url', + ]; + + yield 'with queryParameters - uses urlPath' => [ + 'matchers' => [ + 'queryParameters' => ['q' => ['equalTo' => 'London']], + ], + 'expectedKey' => 'urlPath', + ]; + + yield 'with queryParameters and other matchers - uses urlPath' => [ + 'matchers' => [ + 'queryParameters' => ['q' => ['equalTo' => 'London']], + 'headers' => ['Content-Type' => ['equalTo' => 'application/json']], + ], + 'expectedKey' => 'urlPath', + ]; + + yield 'explicit urlPath overrides - uses url (respects user choice)' => [ + 'matchers' => [ + 'queryParameters' => ['q' => ['equalTo' => 'London']], + 'urlPath' => '/api/weather', + ], + 'expectedKey' => 'url', + ]; + + yield 'explicit urlPattern overrides - uses url (respects user choice)' => [ + 'matchers' => [ + 'queryParameters' => ['q' => ['equalTo' => 'London']], + 'urlPattern' => '/api/.*', + ], + 'expectedKey' => 'url', + ]; + + yield 'only bodyPatterns - uses url' => [ + 'matchers' => [ + 'bodyPatterns' => [['equalToJson' => '{"name":"test"}']], + ], + 'expectedKey' => 'url', + ]; + + yield 'only headers - uses url' => [ + 'matchers' => [ + 'headers' => ['Authorization' => ['matches' => 'Bearer .*']], + ], + 'expectedKey' => 'url', + ]; + } }