Repository: hotmeteor/spectator (php)

Issue:
[Feature] Expected empty response body
I'm writing some guides for a few similar tools and have set up similar test cases for each, so I've noticed a few missing things.

https://github.com/mkon/openapi_contracts/ will let you know if you've expected an empty response but a response is provided,

```
1) widgets POST /widgets responds with 201 when valid
    Failure/Error: expect(response).to match_openapi_doc(OPENAPI_DOC)
      * Expected empty response body
    # ./spec/requests/widgets_spec.rb:17:in `block (3 levels) in <top (required)>'
```

Basically if OpenAPI says this:

```

post:
  summary: Create Widget
  operationId: create-widget
  requestBody:
    description: Widget to create
    required: true
    content:
      application/json:
        schema:
          $ref: "../components/schema/widget.yaml"
  responses:
    '201':
      description: Created
```

and i've not actually defined a body, then if Spectator notices a body come back at all it should fail the test. I can then chose to define it in OpenAPI, or change the code.

Pull request:
Expected empty response body
From now, a non empty response body will be considered invalid against a specification not defining a response content

Fixes #165 

Patch:
diff --git a/src/Validation/ResponseValidator.php b/src/Validation/ResponseValidator.php
index ae7360a..0f038bb 100644
--- a/src/Validation/ResponseValidator.php
+++ b/src/Validation/ResponseValidator.php
@@ -44,6 +44,8 @@ class ResponseValidator extends AbstractValidator
 
         if ($responseObject->content) {
             $this->parseResponse($responseObject);
+        } elseif ($this->responseContent() !== '') {
+            throw new ResponseValidationException('Response body is expected to be empty.');
         }
     }
 
@@ -164,7 +166,7 @@ class ResponseValidator extends AbstractValidator
      */
     protected function body($contentType, $schemaType)
     {
-        $body = $this->response instanceof StreamedResponse ? $this->streamedContent() : $this->response->getContent();
+        $body = $this->responseContent();
 
         if (in_array($schemaType, ['object', 'array', 'allOf', 'anyOf', 'oneOf'], true)) {
             if (in_array($contentType, ['application/json', 'application/vnd.api+json', 'application/problem+json'])) {
@@ -177,6 +179,11 @@ class ResponseValidator extends AbstractValidator
         return $body;
     }
 
+    protected function responseContent(): string
+    {
+        return $this->response instanceof StreamedResponse ? $this->streamedContent() : $this->response->getContent();
+    }
+
     protected function streamedContent(): string
     {
         $content = '';


Test patch:
diff --git a/tests/Fixtures/EmptyResponse.yml b/tests/Fixtures/EmptyResponse.yml
new file mode 100644
index 0000000..f43db19
--- /dev/null
+++ b/tests/Fixtures/EmptyResponse.yml
@@ -0,0 +1,17 @@
+openapi: 3.0.0
+info:
+  title: EmptyResponse
+  version: '1.0'
+servers:
+  - url: 'https://api.protect.earth/'
+    description: Production
+paths:
+  /empty:
+    get:
+      summary: Get empty response
+      tags: []
+      responses:
+        '200':
+          description: Ok
+components:
+  schemas: {}
diff --git a/tests/ResponseValidatorTest.php b/tests/ResponseValidatorTest.php
index 7b7cfc9..8e6eb5c 100644
--- a/tests/ResponseValidatorTest.php
+++ b/tests/ResponseValidatorTest.php
@@ -88,6 +88,33 @@ class ResponseValidatorTest extends TestCase
             ->assertValidResponse();
     }
 
+    public function test_validates_valid_empty_response(): void
+    {
+        Spectator::using('EmptyResponse.yml');
+
+        Route::get('/empty', static function () {
+            return response('');
+        })->middleware(Middleware::class);
+
+        $this->getJson('/empty')
+            ->assertValidRequest()
+            ->assertValidResponse(200);
+    }
+
+    public function test_validates_invalid_empty_response(): void
+    {
+        Spectator::using('EmptyResponse.yml');
+
+        Route::get('/empty', static function () {
+            return response('Hello world!');
+        })->middleware(Middleware::class);
+
+        $this->getJson('/empty')
+            ->assertValidRequest()
+            ->assertInvalidResponse(200)
+            ->assertValidationMessage('Response body is expected to be empty.');
+    }
+
     public function test_validates_valid_problem_json_response()
     {
         Route::get('/users', function () {