Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.00% covered (success)
96.00%
24 / 25
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
JsonWebhookResponseFactory
96.00% covered (success)
96.00%
24 / 25
83.33% covered (warning)
83.33%
5 / 6
12
0.00% covered (danger)
0.00%
0 / 1
 create
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
3
 getAcceptedContentType
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepareResponse
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 decodeBody
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 validateContentType
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getJsonValidator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2019 (original work) Open Assessment Technologies SA;
19 */
20
21namespace oat\tao\model\webhooks\task;
22
23use oat\oatbox\service\ConfigurableService;
24use Psr\Http\Message\ResponseInterface;
25use Psr\Http\Message\StreamInterface;
26
27class JsonWebhookResponseFactory extends ConfigurableService implements WebhookResponseFactoryInterface
28{
29    public const ACCEPTED_CONTENT_TYPE = 'application/json';
30
31    public const SUPPORTED_STATUSES = [
32        WebhookResponse::STATUS_ACCEPTED,
33        WebhookResponse::STATUS_IGNORED,
34        WebhookResponse::STATUS_ERROR
35    ];
36
37    /**
38     * @param ResponseInterface $response
39     * @return WebhookResponse
40     */
41    public function create(ResponseInterface $response)
42    {
43        try {
44            $this->validateContentType($response);
45            $body = $this->decodeBody($response->getBody());
46            $this->getJsonValidator()->validate($body);
47            return $this->prepareResponse($body);
48        } catch (InvalidJsonException $exception) {
49            $errors = implode(', ', $exception->getValidationErrors());
50            return new WebhookResponse([], $exception->getMessage() . ': ' . $errors);
51        } catch (\Exception $exception) {
52            return new WebhookResponse([], $exception->getMessage());
53        }
54    }
55
56    /**
57     * @return string
58     */
59    public function getAcceptedContentType()
60    {
61        return 'application/json';
62    }
63
64    /**
65     * TODO: in case of eventId duplication respects last item
66     * @param \stdClass $body
67     * @return WebhookResponse
68     */
69    private function prepareResponse($body)
70    {
71        $eventStatuses = [];
72        foreach ($body->events as $eventRec) {
73            $eventStatuses[$eventRec->eventId] = $eventRec->status;
74        }
75        return new WebhookResponse($eventStatuses, null);
76    }
77
78    /**
79     * @param StreamInterface $body
80     * @return \stdClass|mixed
81     */
82    private function decodeBody(StreamInterface $body)
83    {
84        $stringBody = (string) $body;
85        $decoded = json_decode($stringBody, false);
86        if ($decoded === null) {
87            throw new \InvalidArgumentException('Body is not a valid json: ' . json_last_error_msg());
88        }
89        return $decoded;
90    }
91
92    private function validateContentType(ResponseInterface $response)
93    {
94        $headerValues = $response->getHeader('Content-Type');
95        if (count($headerValues) > 0) {
96            $contentType = reset($headerValues);
97            if (strcasecmp($contentType, $this->getAcceptedContentType()) !== 0) {
98                throw new \InvalidArgumentException("Unsupported Content-Type: $contentType");
99            }
100        }
101    }
102
103    /**
104     * @return JsonValidator
105     */
106    private function getJsonValidator()
107    {
108        /** @noinspection PhpIncompatibleReturnTypeInspection */
109        return $this->getServiceLocator()->get(JsonValidator::class);
110    }
111}