diff --git a/lib/Feed.php b/lib/Feed.php index af43f22..ce4ab4c 100644 --- a/lib/Feed.php +++ b/lib/Feed.php @@ -39,7 +39,7 @@ class Feed { if (!$links) { // work around a PicoFeed memory leak libxml_use_internal_errors(false); - throw new Feed\Exception($url, new \PicoFeed\Reader\SubscriptionNotFoundException('Unable to find a subscription')); + throw new Feed\Exception("", ['url' => $url], new \PicoFeed\Reader\SubscriptionNotFoundException('Unable to find a subscription')); } else { $out = $links[0]; } @@ -119,9 +119,9 @@ class Feed { $client->reader = $reader; return $client; } catch (PicoFeedException $e) { - throw new Feed\Exception($url, $e); // @codeCoverageIgnore + throw new Feed\Exception("", ['url' => $url], $e); // @codeCoverageIgnore } catch (\GuzzleHttp\Exception\GuzzleException $e) { - throw new Feed\Exception($url, $e); + throw new Feed\Exception("", ['url' => $url], $e); } } @@ -133,9 +133,9 @@ class Feed { $this->resource->getEncoding() )->execute(); } catch (PicoFeedException $e) { - throw new Feed\Exception($this->resource->getUrl(), $e); + throw new Feed\Exception("", ['url' => $this->resource->getUrl()], $e); } catch (\GuzzleHttp\Exception\GuzzleException $e) { // @codeCoverageIgnore - throw new Feed\Exception($this->resource->getUrl(), $e); // @codeCoverageIgnore + throw new Feed\Exception("", ['url' => $this->resource->getUrl()], $e); // @codeCoverageIgnore } // Grab the favicon for the feed, or null if no valid icon is found diff --git a/lib/Feed/Exception.php b/lib/Feed/Exception.php index 2bf181e..1a8e68f 100644 --- a/lib/Feed/Exception.php +++ b/lib/Feed/Exception.php @@ -15,30 +15,33 @@ class Exception extends \JKingWeb\Arsse\AbstractException { protected const CURL_ERROR_MAP = [1 => "invalidUrl",3 => "invalidUrl",5 => "transmissionError","connectionFailed","connectionFailed","transmissionError","forbidden","unauthorized","transmissionError","transmissionError","transmissionError","transmissionError","connectionFailed","connectionFailed","transmissionError","transmissionError","transmissionError","transmissionError","transmissionError","invalidUrl","transmissionError","transmissionError","transmissionError","transmissionError",28 => "timeout","transmissionError","transmissionError","transmissionError","transmissionError","transmissionError",35 => "invalidCertificate","transmissionError","transmissionError","transmissionError","transmissionError",45 => "transmissionError","unauthorized","maxRedirect",52 => "transmissionError","invalidCertificate","invalidCertificate","transmissionError","transmissionError",58 => "invalidCertificate","invalidCertificate","invalidCertificate","transmissionError","invalidUrl","transmissionError","invalidCertificate","transmissionError","invalidCertificate","forbidden","invalidUrl","forbidden","transmissionError",73 => "transmissionError","transmissionError",77 => "invalidCertificate","invalidUrl",90 => "invalidCertificate","invalidCertificate","transmissionError",94 => "unauthorized","transmissionError","connectionFailed"]; protected const HTTP_ERROR_MAP = [401 => "unauthorized",403 => "forbidden",404 => "invalidUrl",408 => "timeout",410 => "invalidUrl",414 => "invalidUrl",451 => "invalidUrl"]; - public function __construct($url, \Throwable $e) { - if ($e instanceof BadResponseException) { - $msgID = self::HTTP_ERROR_MAP[$e->getCode()] ?? "transmissionError"; - } elseif ($e instanceof TooManyRedirectsException) { - $msgID = "maxRedirect"; - } elseif ($e instanceof GuzzleException) { - $msg = $e->getMessage(); - if (preg_match("/^Error creating resource:/", $msg)) { - // PHP stream error; the class of error is ambiguous - $msgID = "transmissionError"; - } elseif (preg_match("/^cURL error (\d+):/", $msg, $match)) { - $msgID = self::CURL_ERROR_MAP[(int) $match[1]] ?? "internalError"; + public function __construct(string $msgID = "", $vars, \Throwable $e) { + if ($msgID === "") { + assert($e !== null, new \Exception("Expecting Picofeed or Guzzle exception when no message specified.")); + if ($e instanceof BadResponseException) { + $msgID = self::HTTP_ERROR_MAP[$e->getCode()] ?? "transmissionError"; + } elseif ($e instanceof TooManyRedirectsException) { + $msgID = "maxRedirect"; + } elseif ($e instanceof GuzzleException) { + $msg = $e->getMessage(); + if (preg_match("/^Error creating resource:/", $msg)) { + // PHP stream error; the class of error is ambiguous + $msgID = "transmissionError"; + } elseif (preg_match("/^cURL error (\d+):/", $msg, $match)) { + $msgID = self::CURL_ERROR_MAP[(int) $match[1]] ?? "internalError"; + } else { + $msgID = "internalError"; + } + } elseif ($e instanceof PicoFeedException) { + $className = get_class($e); + // Convert the exception thrown by PicoFeed to the one to be thrown here. + $msgID = preg_replace('/^PicoFeed\\\(?:Client|Parser|Reader)\\\([A-Za-z]+)Exception$/', '$1', $className); + // If the message ID doesn't change then it's unknown. + $msgID = ($msgID !== $className) ? lcfirst($msgID) : "internalError"; } else { $msgID = "internalError"; } - } elseif ($e instanceof PicoFeedException) { - $className = get_class($e); - // Convert the exception thrown by PicoFeed to the one to be thrown here. - $msgID = preg_replace('/^PicoFeed\\\(?:Client|Parser|Reader)\\\([A-Za-z]+)Exception$/', '$1', $className); - // If the message ID doesn't change then it's unknown. - $msgID = ($msgID !== $className) ? lcfirst($msgID) : "internalError"; - } else { - $msgID = "internalError"; } - parent::__construct($msgID, ['url' => $url], $e); + parent::__construct($msgID, $vars, $e); } } diff --git a/tests/cases/CLI/TestCLI.php b/tests/cases/CLI/TestCLI.php index 671fbb9..3d0d6f1 100644 --- a/tests/cases/CLI/TestCLI.php +++ b/tests/cases/CLI/TestCLI.php @@ -82,7 +82,7 @@ class TestCLI extends \JKingWeb\Arsse\Test\AbstractTest { public function testRefreshAFeed(string $cmd, int $exitStatus, string $output): void { Arsse::$db = \Phake::mock(Database::class); \Phake::when(Arsse::$db)->feedUpdate(1, true)->thenReturn(true); - \Phake::when(Arsse::$db)->feedUpdate(2, true)->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/", $this->mockGuzzleException(ClientException::class, "", 404))); + \Phake::when(Arsse::$db)->feedUpdate(2, true)->thenThrow(new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/"], $this->mockGuzzleException(ClientException::class, "", 404))); $this->assertConsole($this->cli, $cmd, $exitStatus, $output); \Phake::verify($this->cli)->loadConf; \Phake::verify(Arsse::$db)->feedUpdate; diff --git a/tests/cases/Database/SeriesSubscription.php b/tests/cases/Database/SeriesSubscription.php index 3cdeeea..c009b60 100644 --- a/tests/cases/Database/SeriesSubscription.php +++ b/tests/cases/Database/SeriesSubscription.php @@ -251,7 +251,7 @@ trait SeriesSubscription { public function testAddASubscriptionToAnInvalidFeed(): void { $url = "http://example.org/feed1"; $feedID = $this->nextID("arsse_feeds"); - \Phake::when(Arsse::$db)->feedUpdate->thenThrow(new FeedException($url, $this->mockGuzzleException(ClientException::class, "", 404))); + \Phake::when(Arsse::$db)->feedUpdate->thenThrow(new FeedException("", ['url' => $url], $this->mockGuzzleException(ClientException::class, "", 404))); $this->assertException("invalidUrl", "Feed"); try { Arsse::$db->subscriptionAdd($this->user, $url, "", "", false); diff --git a/tests/cases/Feed/TestException.php b/tests/cases/Feed/TestException.php index 95adde1..b28d0d1 100644 --- a/tests/cases/Feed/TestException.php +++ b/tests/cases/Feed/TestException.php @@ -20,7 +20,7 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest { public function testHandleCurlErrors(int $code, string $message): void { $e = $this->mockGuzzleException(TransferException::class, "cURL error $code: Some message", 0); $this->assertException($message, "Feed"); - throw new FeedException("https://example.com/", $e); + throw new FeedException("", ['url' => "https://example.com/"], $e); } public function provideCurlErrors() { @@ -119,7 +119,7 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest { public function testHandleHttpErrors(int $code, string $message): void { $e = $this->mockGuzzleException(BadResponseException::class, "Irrelevant message", $code); $this->assertException($message, "Feed"); - throw new FeedException("https://example.com/", $e); + throw new FeedException("", ['url' => "https://example.com/"], $e); } public function provideHTTPErrors() { @@ -145,7 +145,7 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest { /** @dataProvider providePicoFeedException */ public function testHandlePicofeedException(PicoFeedException $e, string $message) { $this->assertException($message, "Feed"); - throw new FeedException("https://example.com/", $e); + throw new FeedException("", ['url' => "https://example.com/"], $e); } public function providePicoFeedException() { @@ -160,18 +160,18 @@ class TestException extends \JKingWeb\Arsse\Test\AbstractTest { public function testHandleExcessRedirections() { $e = $this->mockGuzzleException(TooManyRedirectsException::class, "Irrelevant message", 404); $this->assertException("maxRedirect", "Feed"); - throw new FeedException("https://example.com/", $e); + throw new FeedException("", ['url' => "https://example.com/"], $e); } public function testHandleGenericStreamErrors() { $e = $this->mockGuzzleException(TransferException::class, "Error creating resource: Irrelevant message", 403); $this->assertException("transmissionError", "Feed"); - throw new FeedException("https://example.com/", $e); + throw new FeedException("", ['url' => "https://example.com/"], $e); } public function testHandleUnexpectedError() { $e = new \Exception; $this->assertException("internalError", "Feed"); - throw new FeedException("https://example.com/", $e); + throw new FeedException("", ['url' => "https://example.com/"], $e); } } diff --git a/tests/cases/REST/NextcloudNews/TestV1_2.php b/tests/cases/REST/NextcloudNews/TestV1_2.php index eccc0cc..7b4ce32 100644 --- a/tests/cases/REST/NextcloudNews/TestV1_2.php +++ b/tests/cases/REST/NextcloudNews/TestV1_2.php @@ -534,7 +534,7 @@ class TestV1_2 extends \JKingWeb\Arsse\Test\AbstractTest { } public function provideNewSubscriptions(): array { - $feedException = new \JKingWeb\Arsse\Feed\Exception("", new \PicoFeed\Reader\SubscriptionNotFoundException); + $feedException = new \JKingWeb\Arsse\Feed\Exception("", [], new \PicoFeed\Reader\SubscriptionNotFoundException); return [ [['url' => "http://example.com/news.atom", 'folderId' => 3], 2112, 0, $this->feeds['db'][0], new ExceptionInput("idMissing"), new Response(['feeds' => [$this->feeds['rest'][0]]])], [['url' => "http://example.org/news.atom", 'folderId' => 8], 42, 4758915, $this->feeds['db'][1], true, new Response(['feeds' => [$this->feeds['rest'][1]], 'newestItemId' => 4758915])], diff --git a/tests/cases/REST/TinyTinyRSS/TestAPI.php b/tests/cases/REST/TinyTinyRSS/TestAPI.php index ff6d895..6191d84 100644 --- a/tests/cases/REST/TinyTinyRSS/TestAPI.php +++ b/tests/cases/REST/TinyTinyRSS/TestAPI.php @@ -787,13 +787,13 @@ LONG_STRING; ]; $out = [ ['code' => 1, 'feed_id' => 2], - ['code' => 5, 'message' => (new \JKingWeb\Arsse\Feed\Exception("http://example.com/1", $this->mockGuzzleException(ClientException::class, "", 401)))->getMessage()], + ['code' => 5, 'message' => (new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/1"], $this->mockGuzzleException(ClientException::class, "", 401)))->getMessage()], ['code' => 1, 'feed_id' => 0], ['code' => 0, 'feed_id' => 3], ['code' => 0, 'feed_id' => 1], - ['code' => 3, 'message' => (new \JKingWeb\Arsse\Feed\Exception("http://localhost:8000/Feed/Discovery/Invalid", new \PicoFeed\Reader\SubscriptionNotFoundException()))->getMessage()], - ['code' => 2, 'message' => (new \JKingWeb\Arsse\Feed\Exception("http://example.com/6", $this->mockGuzzleException(ClientException::class, "", 404)))->getMessage()], - ['code' => 6, 'message' => (new \JKingWeb\Arsse\Feed\Exception("http://example.com/7", new \PicoFeed\Parser\MalformedXmlException()))->getMessage()], + ['code' => 3, 'message' => (new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://localhost:8000/Feed/Discovery/Invalid"], new \PicoFeed\Reader\SubscriptionNotFoundException()))->getMessage()], + ['code' => 2, 'message' => (new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/6"], $this->mockGuzzleException(ClientException::class, "", 404)))->getMessage()], + ['code' => 6, 'message' => (new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/7"], new \PicoFeed\Parser\MalformedXmlException()))->getMessage()], ['code' => 1, 'feed_id' => 4], ['code' => 0, 'feed_id' => 4], ]; @@ -804,13 +804,13 @@ LONG_STRING; ['id' => 4, 'url' => "http://example.com/9"], ]; \Phake::when(Arsse::$db)->subscriptionAdd(...$db[0])->thenReturn(2); - \Phake::when(Arsse::$db)->subscriptionAdd(...$db[1])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/1", $this->mockGuzzleException(ClientException::class, "", 401))); + \Phake::when(Arsse::$db)->subscriptionAdd(...$db[1])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/1"], $this->mockGuzzleException(ClientException::class, "", 401))); \Phake::when(Arsse::$db)->subscriptionAdd(...$db[2])->thenReturn(2); \Phake::when(Arsse::$db)->subscriptionAdd(...$db[3])->thenThrow(new ExceptionInput("constraintViolation")); \Phake::when(Arsse::$db)->subscriptionAdd(...$db[4])->thenThrow(new ExceptionInput("constraintViolation")); \Phake::when(Arsse::$db)->subscriptionAdd(...$db[5])->thenThrow(new ExceptionInput("constraintViolation")); - \Phake::when(Arsse::$db)->subscriptionAdd(...$db[6])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/6", $this->mockGuzzleException(ClientException::class, "", 404))); - \Phake::when(Arsse::$db)->subscriptionAdd(...$db[7])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("http://example.com/7", new \PicoFeed\Parser\MalformedXmlException())); + \Phake::when(Arsse::$db)->subscriptionAdd(...$db[6])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/6"], $this->mockGuzzleException(ClientException::class, "", 404))); + \Phake::when(Arsse::$db)->subscriptionAdd(...$db[7])->thenThrow(new \JKingWeb\Arsse\Feed\Exception("", ['url' => "http://example.com/7"], new \PicoFeed\Parser\MalformedXmlException())); \Phake::when(Arsse::$db)->subscriptionAdd(...$db[8])->thenReturn(4); \Phake::when(Arsse::$db)->subscriptionAdd(...$db[9])->thenThrow(new ExceptionInput("constraintViolation")); \Phake::when(Arsse::$db)->folderPropertiesGet(Arsse::$user->id, 42)->thenReturn($this->v(['id' => 42])); diff --git a/tests/lib/FeedException.php b/tests/lib/FeedException.php new file mode 100644 index 0000000..414dbe4 --- /dev/null +++ b/tests/lib/FeedException.php @@ -0,0 +1,15 @@ +