Issues (8)

src/Controller/SuiteApiController.php (2 issues)

Severity
1
<?php
2
3
/**
4
 * Copyright (c) 2017 Francois-Xavier Soubirou.
5
 *
6
 * This file is part of ci-report.
7
 *
8
 * ci-report is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU General Public License as published by
10
 * the Free Software Foundation, either version 3 of the License, or
11
 * (at your option) any later version.
12
 *
13
 * ci-report is distributed in the hope that it will be useful,
14
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
 * GNU General Public License for more details.
17
 *
18
 * You should have received a copy of the GNU General Public License
19
 * along with ci-report. If not, see <http://www.gnu.org/licenses/>.
20
 */
21
declare(strict_types=1);
22
23
namespace App\Controller;
24
25
use App\DTO\SuiteLimitsDTO;
26
use App\DTO\SuiteLimitsFilesDTO;
27
use App\Entity\Campaign;
28
use App\Entity\Project;
29
use App\Entity\Suite;
30
use App\Entity\Test;
31
use App\Service\FileUploaderService;
32
use App\Service\JunitParserService;
33
use App\Service\RefreshService;
34
use DOMDocument;
35
use FOS\RestBundle\Controller\Annotations as Rest;
36
use FOS\RestBundle\View\View;
37
use Nelmio\ApiDocBundle\Annotation\Operation;
38
use Nelmio\ApiDocBundle\Annotation\Model;
39
use Swagger\Annotations as SWG;
40
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Entity;
41
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
42
use Symfony\Component\HttpFoundation\Request;
43
use Symfony\Component\HttpFoundation\Response;
44
45
/**
46
 * Suite API controller class.
47
 *
48
 * @category  ci-report app
49
 *
50
 * @author    Francois-Xavier Soubirou <[email protected]>
51
 * @copyright 2017 Francois-Xavier Soubirou
52
 * @license   http://www.gnu.org/licenses/   GPLv3
53
 *
54
 * @see      https://www.ci-report.io
55
 *
56
 * @Rest\Route("/api")
57
 */
58
class SuiteApiController extends AbstractApiController
59
{
60
    /**
61
     * Get list of suites for a project and a campaign.
62
     *
63
     * @param Campaign $campaign Campaign
64
     *
65
     * @return array
66
     *
67
     * @Rest\Get("/projects/{prefid}/campaigns/{crefid}/suites",
68
     *    requirements={"crefid" = "\d+"}
69
     * )
70
     * @Rest\View(serializerGroups={"public"})
71
     *
72
     * @Entity("campaign", expr="repository.findCampaignByProjectRefidAndRefid(prefid, crefid)")
73
     *
74
     * @Operation(
75
     *     tags={"Suites"},
76
     *     summary="Get the list of all suites for a campaign.",
77
     *     description="Example: </br><pre><code>curl https://www.ci-report.io/api/projects/project-one/campaigns/1/suites -X GET</code></pre>",
78
     *     @SWG\Parameter(
79
     *         name="prefid",
80
     *         in="path",
81
     *         description="Unique short name of project defined on project creation.",
82
     *         type="string"
83
     *     ),
84
     *     @SWG\Parameter(
85
     *         name="crefid",
86
     *         in="path",
87
     *         description="Reference id of the campaign.",
88
     *         type="integer"
89
     *     ),
90
     *     @SWG\Response(
91
     *         response="200",
92
     *         description="Returned when successful array of public data of campaigns",
93
     *         @SWG\Schema(
94
     *            type="array",
95
     *            @Model(type=Suite::class, groups={"public"})
96
     *         )
97
     *     ),
98
     *     @SWG\Response(
99
     *         response="404",
100
     *         description="Returned when project or campaign not found",
101
     *         @SWG\Schema(ref="#/definitions/ErrorModel")
102
     *     )
103
     * )
104
     */
105
    public function getSuitesAction(Campaign $campaign): array
106
    {
107
        $suites = $this->getDoctrine()
108
            ->getRepository(Suite::class)
109
            ->findSuitesByCampaign($campaign);
110
111
        return $suites;
112
    }
113
114
    /**
115
     * Get suite data.
116
     *
117
     * @param Suite $suite Suite
118
     *
119
     * @return Suite
120
     *
121
     * @Rest\Get(
122
     *    "/projects/{prefid}/campaigns/{crefid}/suites/{srefid}",
123
     *    requirements={"crefid" = "\d+", "srefid" = "\d+"}
124
     * )
125
     * @Rest\View(serializerGroups={"public"})
126
     *
127
     * @Entity("suite", expr="repository.findSuiteByProjectRefidCampaignRefidAndRefid(prefid, crefid, srefid)")
128
     *
129
     * @Operation(
130
     *     tags={"Suites"},
131
     *     summary="Get suite data.",
132
     *     description="Example: </br><pre><code>curl https://www.ci-report.io/api/projects/project-one/campaigns/1/suites/1 -X GET</code></pre>",
133
     *     @SWG\Parameter(
134
     *         name="prefid",
135
     *         in="path",
136
     *         description="Unique short name of project defined on project creation.",
137
     *         type="string"
138
     *     ),
139
     *     @SWG\Parameter(
140
     *         name="crefid",
141
     *         in="path",
142
     *         description="Reference id of the campaign.",
143
     *         type="integer"
144
     *     ),
145
     *     @SWG\Parameter(
146
     *         name="srefid",
147
     *         in="path",
148
     *         description="Reference id of the suite.",
149
     *         type="integer"
150
     *     ),
151
     *     @SWG\Response(
152
     *         response="200",
153
     *         description="Returned when successful",
154
     *         @Model(type=Suite::class, groups={"public"})
155
     *     ),
156
     *     @SWG\Response(
157
     *         response="404",
158
     *         description="Returned when project or campaign not found",
159
     *         @SWG\Schema(ref="#/definitions/ErrorModel")
160
     *     )
161
     * )
162
     */
163
    public function getSuiteAction(Suite $suite): Suite
164
    {
165
        return $suite;
166
    }
167
168
    /**
169
     * Create suites for a campaign by uploading a junit file.
170
     *
171
     * @param Project  $project  Project
172
     * @param Campaign $campaign Campaign
173
     * @param Request  $request  The request
174
     *
175
     * @return array|View
176
     *
177
     * @Rest\Post("/projects/{prefid}/campaigns/{crefid}/suites/junit",
178
     *    requirements={"crefid" = "\d+"}
179
     * )
180
     * @Rest\View(statusCode=Response::HTTP_CREATED, serializerGroups={"public"})
181
     *
182
     * @ParamConverter("project", options={"mapping": {"prefid": "refid"}})
183
     * @Entity("campaign", expr="repository.findCampaignByProjectRefidAndRefid(prefid, crefid)")
184
     *
185
     * @Operation(
186
     *     tags={"Suites"},
187
     *     summary="Create suites by uploading a junit file.",
188
     *     description="Example: </br><pre><code>curl https://www.ci-report.io/api/projects/project-one/campaigns/1/suites/junit -H &quot;X-CIR-TKN: 1f4ffb19e4b9-02278af07b7d-4e370a76f001&quot; -X POST -F warning=80 -F success=95 -F 'junitfile=@/path/to/junit.xml'</code></pre><p>(@ symbol is mandatory at the beginning of the file path)</p>",
189
     *     @SWG\Parameter(
190
     *         name="prefid",
191
     *         in="path",
192
     *         description="Unique short name of project defined on project creation.",
193
     *         type="string"
194
     *     ),
195
     *     @SWG\Parameter(
196
     *         name="crefid",
197
     *         in="path",
198
     *         description="Reference id of the campaign.",
199
     *         type="integer"
200
     *     ),
201
     *     @SWG\Parameter(
202
     *         name="junitfile",
203
     *         in="formData",
204
     *         description="XML junit file.",
205
     *         required=true,
206
     *         type="file"
207
     *     ),
208
     *     @SWG\Parameter(
209
     *         name="warning",
210
     *         in="formData",
211
     *         description="Tests warning limit. Integer between 0 and 100 %. Limit defined on project by default.",
212
     *         required=false,
213
     *         type="integer"
214
     *     ),
215
     *     @SWG\Parameter(
216
     *         name="success",
217
     *         in="formData",
218
     *         description="Tests success limit. Integer between 0 and 100 %. Limit defined on project by default.",
219
     *         required=false,
220
     *         type="integer"
221
     *     ),
222
     *     @SWG\Response(
223
     *         response="201",
224
     *         description="Returned when created",
225
     *         @Model(type=Suite::class, groups={"public"})
226
     *     ),
227
     *     @SWG\Response(
228
     *         response="400",
229
     *         description="Returned when a violation is raised by validation",
230
     *         @SWG\Schema(
231
     *            type="array",
232
     *            @SWG\Items(ref="#/definitions/ErrorModel")
233
     *         )
234
     *     ),
235
     *     @SWG\Response(
236
     *         response="401",
237
     *         description="Returned when X-CIR-TKN private token value is invalid",
238
     *         @SWG\Schema(ref="#/definitions/ErrorModel")
239
     *     ),
240
     *     @SWG\Response(
241
     *         response="404",
242
     *         description="Returned when project or campaign not found",
243
     *         @SWG\Schema(ref="#/definitions/ErrorModel")
244
     *     )
245
     * )
246
     */
247
    public function postSuiteAction(Project $project, Campaign $campaign, Request $request)
248
    {
249
        if ($this->isInvalidToken($request, $project->getToken())) {
250
            return $this->getInvalidTokenView();
251
        }
252
253
        $fileUploaderService = $this->get(FileUploaderService::class);
254
        $junitParserService = $this->get(JunitParserService::class);
255
        $validator = $this->get('validator');
256
257
        $suiteLimitsFilesDTO = new SuiteLimitsFilesDTO($project, $request);
258
259
        $violations = $validator->validate($suiteLimitsFilesDTO);
260
        if (count($violations) > 0) {
261
            return $this->view($violations, Response::HTTP_BAD_REQUEST);
262
        }
263
264
        $fileNameUId = $fileUploaderService->upload(
265
            $suiteLimitsFilesDTO->getJunitfile()
266
        );
267
268
        $doc = new DOMDocument('1.0', 'UTF-8');
269
        $doc->load($fileUploaderService->getFullPath($fileNameUId));
270
        $errors = $junitParserService->validate($doc);
271
        if (count($errors) > 0) {
272
            $fileUploaderService->remove($fileNameUId);
273
274
            return $this->view($errors, Response::HTTP_BAD_REQUEST);
275
        }
276
        $suitesArray = $junitParserService->parse($doc);
277
        $fileUploaderService->remove($fileNameUId);
278
279
        $em = $this->getDoctrine()->getManager();
280
281
        $suitesEntity = array();
282
283
        foreach ($suitesArray as $suiteTests) {
284
            $suiteDTO = $suiteTests->getSuite();
285
            $suiteDTO->setWarning($suiteLimitsFilesDTO->getWarning());
286
            $suiteDTO->setSuccess($suiteLimitsFilesDTO->getSuccess());
287
288
            $suite = new Suite($project, $campaign);
289
            $suite->setFromDTO($suiteDTO);
290
291
            $violations = $validator->validate($suite);
292
            if (count($violations) > 0) {
293
                return $this->view($violations, Response::HTTP_BAD_REQUEST);
294
            }
295
            $em->persist($suite);
296
297
            foreach ($suiteTests->getTests() as $testDTO) {
298
                $test = new Test($suite);
299
                $test->setFromDTO($testDTO);
300
301
                $violations = $validator->validate($test);
302
                if (count($violations) > 0) {
303
                    return $this->view($violations, Response::HTTP_BAD_REQUEST);
304
                }
305
                $em->persist($test);
306
            }
307
            $em->flush();
308
            $this->get(RefreshService::class)->refreshCampaign(
309
                $suite->getCampaign(),
310
                true
311
            );
312
            $suitesEntity[] = $suite;
313
        }
314
315
        return $suitesEntity;
316
    }
317
318
    /**
319
     * Update suite warning and success limits.
320
     *
321
     * @param Project        $project        Project
322
     * @param Suite          $suiteDB        Suite to update
323
     * @param SuiteLimitsDTO $suiteLimitsDTO Object containing input data
324
     * @param Request        $request        The request
325
     *
326
     * @return Suite|View
327
     *
328
     * @Rest\Put(
329
     *    "/projects/{prefid}/campaigns/{crefid}/suites/{srefid}/limits",
330
     *    requirements={"crefid" = "\d+", "srefid" = "\d+"}
331
     * )
332
     * @Rest\View(serializerGroups={"public"})
333
     *
334
     * @ParamConverter("project", options={"mapping": {"prefid": "refid"}})
335
     * @Entity("suiteDB", expr="repository.findSuiteByProjectRefidCampaignRefidAndRefid(prefid, crefid, srefid)")
336
     * @ParamConverter("suiteLimitsDTO", converter="fos_rest.request_body")
337
     *
338
     * @Operation(
339
     *     tags={"Suites"},
340
     *     summary="Update suite warning and success limits.",
341
     *     description="Example: </br><pre><code>curl https://www.ci-report.io/api/projects/project-one/campaigns/1/suites/1/limits -H &quot;Content-Type: application/json&quot; -H &quot;X-CIR-TKN: 1f4ffb19e4b9-02278af07b7d-4e370a76f001&quot; -X PUT --data '{&quot;warning&quot;:80, &quot;success&quot;:95}'</code></pre>",
342
     *     @SWG\Parameter(
343
     *         name="prefid",
344
     *         in="path",
345
     *         description="Unique short name of project defined on project creation.",
346
     *         type="string"
347
     *     ),
348
     *     @SWG\Parameter(
349
     *         name="crefid",
350
     *         in="path",
351
     *         description="Reference id of the campaign.",
352
     *         type="integer"
353
     *     ),
354
     *     @SWG\Parameter(
355
     *         name="srefid",
356
     *         in="path",
357
     *         description="Reference id of the suite.",
358
     *         type="integer"
359
     *     ),
360
     *     @SWG\Parameter(
361
     *         name="data",
362
     *         in="body",
363
     *         @SWG\Schema(
364
     *            type="object",
365
     *            @SWG\Property(
366
     *                property="warning",
367
     *                type="integer",
368
     *                description="Tests warning limit. Integer between 0 and 100 %. Limit defined on project by default."
369
     *            ),
370
     *            @SWG\Property(
371
     *                property="success",
372
     *                type="integer",
373
     *                description="Tests success limit. Integer between 0 and 100 %. Limit defined on project by default."
374
     *            )
375
     *         )
376
     *     ),
377
     *     @SWG\Response(
378
     *         response="200",
379
     *         description="Returned when successful",
380
     *         @Model(type=Suite::class, groups={"public"})
381
     *     ),
382
     *     @SWG\Response(
383
     *         response="400",
384
     *         description="Returned when a violation is raised by validation",
385
     *         @SWG\Schema(
386
     *            type="array",
387
     *            @SWG\Items(ref="#/definitions/ErrorModel")
388
     *         )
389
     *     ),
390
     *     @SWG\Response(
391
     *         response="401",
392
     *         description="Returned when X-CIR-TKN private token value is invalid",
393
     *         @SWG\Schema(ref="#/definitions/ErrorModel")
394
     *     ),
395
     *     @SWG\Response(
396
     *         response="404",
397
     *         description="Returned when suite not found",
398
     *         @SWG\Schema(ref="#/definitions/ErrorModel")
399
     *     )
400
     * )
401
     */
402
    public function putSuiteLimitsAction(Project $project, Suite $suiteDB, SuiteLimitsDTO $suiteLimitsDTO, Request $request)
403
    {
404
        if ($this->isInvalidToken($request, $project->getToken())) {
405
            return $this->getInvalidTokenView();
406
        }
407
        // If limit not defined get limit from suite
408
        if (null === $suiteLimitsDTO->getWarning()) {
0 ignored issues
show
The condition null === $suiteLimitsDTO->getWarning() is always false.
Loading history...
409
            $suiteLimitsDTO->setWarning($suiteDB->getWarning());
410
        }
411
        if (null === $suiteLimitsDTO->getSuccess()) {
0 ignored issues
show
The condition null === $suiteLimitsDTO->getSuccess() is always false.
Loading history...
412
            $suiteLimitsDTO->setSuccess($suiteDB->getSuccess());
413
        }
414
415
        $validator = $this->get('validator');
416
        $violationsDTO = $validator->validate($suiteLimitsDTO);
417
418
        if (count($violationsDTO) > 0) {
419
            return $this->view($violationsDTO, Response::HTTP_BAD_REQUEST);
420
        }
421
        $suiteDB->setFromLimitsDTO($suiteLimitsDTO);
422
423
        $violations = $validator->validate($suiteDB);
424
        if (count($violations) > 0) {
425
            return $this->view($violations, Response::HTTP_BAD_REQUEST);
426
        }
427
        $this->getDoctrine()->getManager()->flush();
428
        $this->get(RefreshService::class)->refreshCampaign(
429
            $suiteDB->getCampaign()
430
        );
431
432
        return $suiteDB;
433
    }
434
435
    /**
436
     * Delete a suite.
437
     *
438
     * @param Project $project Project
439
     * @param Suite   $suite   Suite to delete
440
     * @param Request $request The request
441
     *
442
     * @return void|View
443
     *
444
     * @Rest\Delete(
445
     *    "/projects/{prefid}/campaigns/{crefid}/suites/{srefid}",
446
     *    requirements={"crefid" = "\d+", "srefid" = "\d+"}
447
     * )
448
     * @Rest\View(statusCode=Response::HTTP_NO_CONTENT)
449
     *
450
     * @ParamConverter("project", options={"mapping": {"prefid": "refid"}})
451
     * @Entity("suite", expr="repository.findSuiteByProjectRefidCampaignRefidAndRefid(prefid, crefid, srefid)")
452
     *
453
     * @Operation(
454
     *     tags={"Suites"},
455
     *     summary="Delete a suite.",
456
     *     description="Example: </br><pre><code>curl https://www.ci-report.io/api/projects/project-one/campaigns/1/suites/1 -H &quot;X-CIR-TKN: 1f4ffb19e4b9-02278af07b7d-4e370a76f001&quot; -X DELETE</code></pre>",
457
     *     @SWG\Parameter(
458
     *         name="prefid",
459
     *         in="path",
460
     *         description="Unique short name of project defined on project creation.",
461
     *         type="string"
462
     *     ),
463
     *     @SWG\Parameter(
464
     *         name="crefid",
465
     *         in="path",
466
     *         description="Reference id of the campaign.",
467
     *         type="integer"
468
     *     ),
469
     *     @SWG\Parameter(
470
     *         name="srefid",
471
     *         in="path",
472
     *         description="Reference id of the suite.",
473
     *         type="integer"
474
     *     ),
475
     *     @SWG\Response(
476
     *         response="204",
477
     *         description="Returned when successful"
478
     *     ),
479
     *     @SWG\Response(
480
     *         response="401",
481
     *         description="Returned when X-CIR-TKN private token value is invalid",
482
     *         @SWG\Schema(ref="#/definitions/ErrorModel")
483
     *     ),
484
     *     @SWG\Response(
485
     *         response="404",
486
     *         description="Returned when suite not found",
487
     *         @SWG\Schema(ref="#/definitions/ErrorModel")
488
     *     ),
489
     *     @SWG\Response(
490
     *         response="405",
491
     *         description="Returned when suite refid is not set in URL",
492
     *         @SWG\Schema(ref="#/definitions/ErrorModel")
493
     *     )
494
     * )
495
     */
496
    public function deleteSuiteAction(Project $project, Suite $suite, Request $request)
497
    {
498
        if ($this->isInvalidToken($request, $project->getToken())) {
499
            return $this->getInvalidTokenView();
500
        }
501
        $em = $this->getDoctrine()->getManager();
502
        $em->remove($suite);
503
        $em->flush();
504
        $this->get(RefreshService::class)->refreshCampaign(
505
            $suite->getCampaign()
506
        );
507
    }
508
}
509