Issues (56)

src/CRUDTestCase.php (1 issue)

Labels
Severity
1
<?php
2
3
namespace Digitonic\ApiTestSuite;
4
5
use Digitonic\ApiTestSuite\Concerns\AssertPagination;
6
use Digitonic\ApiTestSuite\Concerns\AssertsErrorFormat;
7
use Digitonic\ApiTestSuite\Concerns\AssertsOutput;
8
use Digitonic\ApiTestSuite\Concerns\DeterminesAssertions;
9
use Digitonic\ApiTestSuite\Concerns\GeneratesTestData;
10
use Digitonic\ApiTestSuite\Concerns\InteractsWithApi;
11
use Digitonic\ApiTestSuite\Contracts\AssertsOutput as AssertsOutputI;
12
use Digitonic\ApiTestSuite\Contracts\CRUDTestCase as CRUDTestCaseI;
13
use Digitonic\ApiTestSuite\Contracts\DeterminesAssertions as DeterminesAssertionsI;
14
use Digitonic\ApiTestSuite\Contracts\GeneratesTestData as GeneratesTestDataI;
15
use Digitonic\ApiTestSuite\Contracts\InteractsWithApi as InteractsWithApiI;
16
use Illuminate\Database\Eloquent\Model;
17
use Illuminate\Http\Response;
18
use Illuminate\Support\Collection;
19
use Tests\TestCase;
0 ignored issues
show
The type Tests\TestCase was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
20
21
abstract class CRUDTestCase extends TestCase implements CRUDTestCaseI, AssertsOutputI, InteractsWithApiI, DeterminesAssertionsI, GeneratesTestDataI
22
{
23
    use AssertsOutput, InteractsWithApi, AssertsErrorFormat, AssertPagination, DeterminesAssertions, GeneratesTestData;
24
25
    /**
26
     * @var \Closure
27
     */
28
    public $identifierGenerator;
29
30
    public $otherUser;
31
32
    public function setUp(): void
33
    {
34
        parent::setUp();
35
        $this->identifierGenerator = config('digitonic.api-test-suite.identifier_faker');
36
        $this->entities = new Collection();
37
        $this->user = factory(config('digitonic.api-test-suite.api_user_class'))->state('crud')->create();
38
        $this->otherUser = factory(config('digitonic.api-test-suite.api_user_class'))->state('crud')->create();
39
    }
40
41
    /**
42
     * @throws \ReflectionException
43
     */
44
    public function runBaseApiTestSuite()
45
    {
46
        $this->assertCantUseRouteWithoutAuthenticating();
47
        $numberOfEntities = $this->isListAction($this->httpAction()) ? $this->entitiesNumber() : 1;
48
        $this->generateEntities($numberOfEntities, $this->httpAction(), $this->user, $this->otherUser);
49
        $this->assertNotFound();
50
        $this->assertFailedValidationForRequiredFields();
51
        $this->assertAccessIsForbidden();
52
        $this->assertRequiredHeaders();
53
        $this->assertCreate();
54
        $this->assertUpdate();
55
        $this->assertRetrieve();
56
        $this->assertListAll();
57
        $this->assertDelete();
58
    }
59
60
    protected function assertCantUseRouteWithoutAuthenticating()
61
    {
62
        if ($this->shouldAssertAuthentication()) {
63
            /** @var TestResponse $response */
64
            $response = $this->doRequest([], [$this->identifierGenerator->call($this)]);
65
            $this->assertErrorFormat($response, Response::HTTP_UNAUTHORIZED);
66
        }
67
    }
68
69
    protected function assertNotFound()
70
    {
71
        if ($this->shouldAssertNotFound()) {
72
            $response = $this->doAuthenticatedRequest([], [$this->identifierGenerator->call($this)]);
73
            $this->assertErrorFormat($response, Response::HTTP_NOT_FOUND);
74
        }
75
    }
76
77
    protected function assertFailedValidationForRequiredFields()
78
    {
79
        if ($this->shouldAssertValidation()) {
80
            foreach ($this->requiredFields() as $key) {
81
                if (!isset($this->payload[$key])) {
82
                    $this->fail('The field ' . $key . ' is required');
83
                }
84
                $this->assertRequiredField($key, !is_array($this->payload[$key]));
85
            }
86
        }
87
    }
88
89
    /**
90
     * @param $key
91
     * @param bool $assertValidationResponse
92
     */
93
    protected function assertRequiredField($key, $assertValidationResponse)
94
    {
95
        $data = $this->payload;
96
        unset($data[$key]);
97
98
        /** @var TestResponse $response */
99
        $response = $this->doAuthenticatedRequest($data, [$this->getCurrentIdentifier()]);
100
        $response->assertStatus(Response::HTTP_UNPROCESSABLE_ENTITY);
101
        $this->checkRequiredResponseHeaders($response);
102
        if ($assertValidationResponse) {
103
            $this->assertErrorFormat(
104
                $response,
105
                Response::HTTP_UNPROCESSABLE_ENTITY,
106
                [
107
                    'fieldName' => $key,
108
                    'formattedFieldName' => str_replace('_', ' ', $key)
109
                ]
110
            );
111
        }
112
    }
113
114
    protected function assertAccessIsForbidden()
115
    {
116
        if ($this->shouldAssertForbiddenAction()) {
117
            $entity = $this->generateSingleEntity(
118
                factory(config('digitonic.api-test-suite.api_user_class'))->state('crud')->create()
119
            );
120
            /** @var TestResponse $response */
121
            $identifier = $this->identifier();
122
            $response = $this->doAuthenticatedRequest([], [$entity->$identifier]);
123
            $this->assertErrorFormat($response, Response::HTTP_FORBIDDEN);
124
        }
125
    }
126
127
    protected function assertRequiredHeaders()
128
    {
129
        foreach ($this->requiredHeaders() as $header => $value) {
130
            $headers = $this->requiredHeaders();
131
            unset($headers[$header]);
132
133
            /** If $headers is empty, defaults will be used*/
134
            if (empty($headers)) {
135
                $headers['not_empty'] = 'not_empty';
136
            }
137
            $response = $this->doAuthenticatedRequest([], [$this->getCurrentIdentifier()], $headers);
138
            $this->assertErrorFormat($response, Response::HTTP_BAD_REQUEST, []);
139
140
            if ($value) {
141
                $headers[$header] = '123456789';
142
                $response = $this->doAuthenticatedRequest([], [$this->getCurrentIdentifier()], $headers);
143
                $this->assertErrorFormat($response, Response::HTTP_BAD_REQUEST);
144
            }
145
        }
146
    }
147
148
    /**
149
     * @throws \ReflectionException
150
     */
151
    protected function assertCreate()
152
    {
153
        if ($this->shouldAssertCreation()) {
154
            /** @var TestResponse $response */
155
            $response = $this->doAuthenticatedRequest($this->payload, [$this->getCurrentIdentifier()]);
156
            $response->assertStatus(Response::HTTP_CREATED);
157
            $this->checkRequiredResponseHeaders($response);
158
            $this->checkTransformerData(
159
                $this->getResponseData($response),
160
                $this->identifier()
161
            );
162
            $this->assertCreatedOnlyOnce();
163
        }
164
    }
165
166
    /**
167
     * @throws \ReflectionException
168
     */
169
    protected function assertCreatedOnlyOnce()
170
    {
171
        $this->doAuthenticatedRequest($this->payload, [$this->getCurrentIdentifier()]);
172
        $class = $this->resourceClass();
173
        if ((new $class) instanceof Model) {
174
            if ($this->cannotBeDuplicated()) {
175
                $this->assertEquals(
176
                    1,
177
                    $this->resourceClass()::count(),
178
                    'Failed asserting that ' . $this->resourceClass() . ' was created only once.'
179
                    . ' Make sure that firstOrCreate() is used in the Controller '
180
                    . 'or change the returned value for the test case '
181
                    . 'method \'cannotBeDuplicated\' to be \'false\''
182
                );
183
            } else {
184
                $this->assertEquals(
185
                    2,
186
                    $this->resourceClass()::count(),
187
                    'Failed asserting that ' . $this->resourceClass() . ' was created twice.'
188
                    . ' Make sure that firstOrCreate() is not used in the Controller '
189
                    . 'or change the returned value for the test case '
190
                    . 'method \'cannotBeDuplicated\' to be \'true\''
191
                );
192
            }
193
        } else {
194
            dump(
195
                "Resource class {$class} does not extend " .
196
                Model::class . ". Skipping asserting that is duplicated or not."
197
            );
198
        }
199
    }
200
201
    protected function assertUpdate()
202
    {
203
        if ($this->shouldAssertUpdate()) {
204
            $data = $this->updateData = $this->generateUpdateData($this->payload, $this->user);
205
            $updatedAt = null;
206
            if ($this->expectsTimestamps()) {
207
                $updatedAt = $this->entities->first()->updated_at;
208
                sleep(1);
209
            }
210
            /** @var TestResponse $response */
211
            $response = $this->doAuthenticatedRequest($data, [$this->getCurrentIdentifier()]);
212
            $response->assertStatus(Response::HTTP_ACCEPTED);
213
            $this->checkRequiredResponseHeaders($response);
214
            $this->checkTransformerData(
215
                $this->getResponseData($response),
216
                $this->identifier(),
217
                $updatedAt
218
            );
219
220
            $class = $this->resourceClass();
221
            if ((new $class) instanceof Model) {
222
                $this->assertCount(
223
                    1,
224
                    $this->resourceClass()::where(
225
                        [
226
                            $this->identifier() => $this->getCurrentIdentifier()
227
                        ]
228
                    )->get()
229
                );
230
            } else {
231
                dump(
232
                    "Resource class {$class} does not extend " .
233
                    Model::class . ". Skipping asserting that is not duplicated on update."
234
                );
235
            }
236
        }
237
    }
238
239
    protected function assertRetrieve()
240
    {
241
        if ($this->shouldAssertRetrieve($this->httpAction())) {
242
            $response = $this->doAuthenticatedRequest([], [$this->getCurrentIdentifier()]);
243
            $response->assertStatus(Response::HTTP_OK);
244
            $this->checkRequiredResponseHeaders($response);
245
            $this->checkTransformerData(
246
                $this->getResponseData($response),
247
                $this->identifier()
248
            );
249
        }
250
    }
251
252
    protected function assertListAll()
253
    {
254
        if ($this->shouldAssertListAll($this->httpAction())) {
255
            $entitiesNumber = $this->entitiesNumber();
256
            if ($this->shouldAssertPaginate()) {
257
                $entitiesPerPage = $this->entitiesPerPage();
258
                foreach ([1 => $entitiesPerPage, 2 => ($entitiesNumber - $entitiesPerPage)] as $page => $count) {
259
                    /** @var TestResponse $response */
260
                    $response = $this->doAuthenticatedRequest([], ['page' => $page, 'per_page' => $entitiesPerPage]);
261
                    $this->assertPaginationFormat($response, $count, $entitiesNumber);
262
                    $response->assertStatus(Response::HTTP_OK);
263
                    $this->checkRequiredResponseHeaders($response);
264
                    $this->checkTransformerData(
265
                        $this->getResponseData($response),
266
                        $this->identifier()
267
                    );
268
                }
269
            } else {
270
                $response = $this->doAuthenticatedRequest([]);
271
                $this->assertCount(
272
                    $entitiesNumber,
273
                    $this->getResponseData($response),
274
                    'The number of entities in the returned list does not match the number of entities that '
275
                    .'have been created (no pagination required)'
276
                );
277
                $response->assertStatus(Response::HTTP_OK);
278
                $this->checkRequiredResponseHeaders($response);
279
                $this->checkTransformerData(
280
                    $this->getResponseData($response),
281
                    $this->identifier()
282
                );
283
            }
284
        }
285
    }
286
287
    protected function assertDelete()
288
    {
289
        if ($this->shouldAssertDeletion()) {
290
            $response = $this->doAuthenticatedRequest([], [$this->getCurrentIdentifier()]);
291
            $response->assertStatus(Response::HTTP_NO_CONTENT);
292
            $this->checkRequiredResponseHeaders($response);
293
            $this->assertEmpty($response->getContent(), 'The content returned on deletion of an entity should be empty');
294
295
            $class = $this->resourceClass();
296
            if ((new $class) instanceof Model) {
297
                $this->assertNull($this->resourceClass()::find($this->entities->first()->id), 'The entity destroyed can still be found in the database');
298
            } else {
299
                dump(
300
                    "Resource class {$class} does not extend " .
301
                    Model::class . ". Skipping asserting that has been removed from database."
302
                );
303
            }
304
        }
305
    }
306
307
    /**
308
     * Create the test response instance from the given response.
309
     *
310
     * @param  \Illuminate\Http\Response $response
311
     * @return TestResponse
312
     */
313
    protected function createTestResponse($response)
314
    {
315
        return TestResponse::fromBaseResponse($response);
316
    }
317
}
318