Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.19% covered (success)
94.19%
81 / 86
69.23% covered (warning)
69.23%
9 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
WebhookTaskReports
94.19% covered (success)
94.19%
81 / 86
69.23% covered (warning)
69.23%
9 / 13
24.11
0.00% covered (danger)
0.00%
0 / 1
 reportInternalException
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 reportConnectException
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 reportRequestException
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
2
 reportBadResponseException
88.89% covered (warning)
88.89%
8 / 9
0.00% covered (danger)
0.00%
0 / 1
2.01
 reportInvalidStatusCode
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 reportInvalidBodyFormat
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 reportInvalidAcknowledgement
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
1
 reportSuccess
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 logErrorWithContext
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 reportError
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
5.01
 getExceptionMessage
86.67% covered (warning)
86.67%
13 / 15
0.00% covered (danger)
0.00%
0 / 1
5.06
 getEventId
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 getWebhookEventLog
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 GuzzleHttp\Exception\BadResponseException;
24use GuzzleHttp\Exception\ConnectException;
25use GuzzleHttp\Exception\RequestException;
26use oat\oatbox\service\ConfigurableService;
27use oat\tao\model\webhooks\log\WebhookEventLogInterface;
28use Psr\Http\Message\ResponseInterface;
29
30class WebhookTaskReports extends ConfigurableService
31{
32    /**
33     * @param WebhookTaskContext $taskContext
34     * @param \Exception $exception
35     * @return \common_report_Report
36     */
37    public function reportInternalException(WebhookTaskContext $taskContext, \Exception $exception)
38    {
39        $message = $this->getExceptionMessage($exception, true);
40        $this->getWebhookEventLog()->storeInternalErrorLog($taskContext, $message);
41        return $this->reportError($taskContext, 'Internal error: ' . $message);
42    }
43
44    /**
45     * @param WebhookTaskContext $taskContext
46     * @param ConnectException $exception
47     * @return \common_report_Report
48     */
49    public function reportConnectException(WebhookTaskContext $taskContext, ConnectException $exception)
50    {
51        $this->getWebhookEventLog()->storeNetworkErrorLog($taskContext, $exception->getMessage());
52        return $this->reportError($taskContext, 'Connection exception: ' . $exception->getMessage());
53    }
54
55    /**
56     * @param WebhookTaskContext $taskContext
57     * @param RequestException $exception
58     * @return \common_report_Report
59     */
60    public function reportRequestException(WebhookTaskContext $taskContext, RequestException $exception)
61    {
62        $message = $exception->getMessage();
63        if ($response = $exception->getResponse()) {
64            $this->getWebhookEventLog()->storeInvalidHttpStatusLog(
65                $taskContext,
66                $response->getStatusCode(),
67                $response->getBody()
68            );
69            return $this->reportError($taskContext, 'Request exception: ' . $exception->getMessage(), $response);
70        }
71
72        $this->getWebhookEventLog()->storeNetworkErrorLog($taskContext, $message);
73        return $this->reportError($taskContext, 'Request exception: ' . $exception->getMessage());
74    }
75
76    /**
77     * @param WebhookTaskContext $taskContext
78     * @param BadResponseException $badResponseException
79     * @return \common_report_Report
80     */
81    public function reportBadResponseException(
82        WebhookTaskContext $taskContext,
83        BadResponseException $badResponseException
84    ) {
85        $statusCode = $badResponseException->getResponse()
86            ? $badResponseException->getResponse()->getStatusCode()
87            : 0;
88        $this->getWebhookEventLog()->storeInvalidHttpStatusLog($taskContext, $statusCode);
89
90        return $this->reportError(
91            $taskContext,
92            'Bad response: ' . $badResponseException->getMessage(),
93            $badResponseException->getResponse()
94        );
95    }
96
97    /**
98     * @param WebhookTaskContext $taskContext
99     * @param ResponseInterface $response
100     * @return \common_report_Report
101     */
102    public function reportInvalidStatusCode(WebhookTaskContext $taskContext, ResponseInterface $response)
103    {
104        $statusCode = $response->getStatusCode();
105        $this->getWebhookEventLog()->storeInvalidHttpStatusLog($taskContext, $statusCode);
106        return $this->reportError($taskContext, "Response status code is $statusCode", $response);
107    }
108
109    /**
110     * @param WebhookTaskContext $taskContext
111     * @param ResponseInterface $response
112     * @return \common_report_Report
113     */
114    public function reportInvalidBodyFormat(WebhookTaskContext $taskContext, ResponseInterface $response)
115    {
116        $this->getWebhookEventLog()->storeInvalidBodyFormat($taskContext, $response->getBody());
117        return $this->reportError(
118            $taskContext,
119            "Event '" . $this->getEventId($taskContext) . "' wasn't delivered.",
120            $response
121        );
122    }
123
124    /**
125     * @param WebhookTaskContext $taskContext
126     * @param ResponseInterface $response
127     * @param WebhookResponse $parsedResponse
128     * @return \common_report_Report
129     */
130    public function reportInvalidAcknowledgement(WebhookTaskContext $taskContext, $response, $parsedResponse)
131    {
132        $eventId = $this->getEventId($taskContext);
133
134        $this->getWebhookEventLog()->storeInvalidAcknowledgementLog(
135            $taskContext,
136            $parsedResponse->getStatus($eventId)
137        );
138
139        return $this->reportError(
140            $taskContext,
141            "Event '" . $eventId . "' wasn't delivered.",
142            $response,
143            $parsedResponse
144        );
145    }
146
147    /**
148     * @param WebhookTaskContext $taskContext
149     * @param ResponseInterface $response
150     * @param string $eventStatus
151     * @return \common_report_Report
152     */
153    public function reportSuccess(WebhookTaskContext $taskContext, $response, $eventStatus)
154    {
155        $this->getWebhookEventLog()->storeSuccessfulLog($taskContext, $response->getBody(), $eventStatus);
156        return \common_report_Report::createSuccess("Event delivered with '$eventStatus' status");
157    }
158
159    /**
160     * @param WebhookTaskContext $taskContext
161     * @param string $message
162     * @param array $context
163     */
164    private function logErrorWithContext(WebhookTaskContext $taskContext, $message, $context = [])
165    {
166        $context['taskId'] = $taskContext->getTaskId();
167        $context['eventId'] = $this->getEventId($taskContext);
168        $this->logError($message, $context);
169    }
170
171    /**
172     * Adds error to log and returns report
173     * @param WebhookTaskContext $taskContext
174     * @param string $message
175     * @param ResponseInterface|null $response
176     * @param WebhookResponse|null $parsedResponse
177     * @return \common_report_Report
178     */
179    private function reportError(
180        WebhookTaskContext $taskContext,
181        $message,
182        ResponseInterface $response = null,
183        WebhookResponse $parsedResponse = null
184    ) {
185        $errors = [
186            'message' => $message
187        ];
188        $context = [];
189
190        if ($parsedResponse) {
191            if ($parsedResponse->getParseError()) {
192                $errors['parse'] = 'Parse error: ' . $parsedResponse->getParseError();
193            }
194            $status = $parsedResponse->getStatus($this->getEventId($taskContext));
195            $errors['status'] = $status !== null
196                ? "Event status: '$status'"
197                : 'eventId not found in response';
198        }
199
200        if ($response) {
201            $context['httpStatus'] = $response->getStatusCode();
202            $context['responseBody'] = (string)$response->getBody();
203        }
204
205        $this->logErrorWithContext($taskContext, implode('; ', $errors), $context);
206
207        unset($errors['parse']);
208        return \common_report_Report::createFailure(implode(PHP_EOL, $errors));
209    }
210
211    /**
212     * Get exception message, append UserMessage if exception is common_exception_ClientException
213     * @param \Exception|\common_exception_ClientException $exception
214     * @param bool $includeSourceInfo Add information about file, line and exception type
215     * @return string|null
216     */
217    private function getExceptionMessage(\Exception $exception, $includeSourceInfo = false)
218    {
219        $messages = [];
220        if ($includeSourceInfo) {
221            $messages[] = sprintf(
222                '%s in %s:%d',
223                get_class($exception),
224                $exception->getFile(),
225                $exception->getLine()
226            );
227        }
228        if ($exception->getMessage() !== null) {
229            $messages[] = $exception->getMessage();
230        }
231        if ($exception instanceof \common_exception_ClientException) {
232            $messages[] = 'User message: ' . $exception->getUserMessage();
233        }
234        if (count($messages) > 0) {
235            return implode('. ', $messages);
236        }
237        return null;
238    }
239
240    /**
241     * @param WebhookTaskContext $taskContext
242     * @return string
243     */
244    private function getEventId(WebhookTaskContext $taskContext)
245    {
246        $taskParams = $taskContext->getWebhookTaskParams();
247        if (!$taskParams) {
248            throw new \InvalidArgumentException("Task context doesn't contain task params");
249        }
250        return $taskParams->getEventId();
251    }
252
253    /**
254     * @return WebhookEventLogInterface
255     */
256    private function getWebhookEventLog()
257    {
258        /** @noinspection PhpIncompatibleReturnTypeInspection */
259        return $this->getServiceLocator()->get(WebhookEventLogInterface::SERVICE_ID);
260    }
261}