FeedTest.php 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. <?php
  2. declare(strict_types=1);
  3. use PHPUnit\Framework\Attributes\DataProvider;
  4. final class FeedTest extends \PHPUnit\Framework\TestCase {
  5. #[\Override]
  6. protected function setUp(): void {
  7. FreshRSS_Context::initSystem(reload: true);
  8. }
  9. #[\Override]
  10. protected function tearDown(): void {
  11. FreshRSS_Context::initSystem(reload: true); // Restore the default curl_options
  12. }
  13. #[DataProvider('provideStoredAndRequestedUrls')]
  14. public function test_load_obeysRetryAfter(string $storedUrl, string $requestedUrl, string $systemProxy = ''): void {
  15. if ($systemProxy !== '') {
  16. FreshRSS_Context::systemConf()->_attribute('curl_options', [CURLOPT_PROXY => $systemProxy]);
  17. }
  18. $getRetryAfterFile = new ReflectionMethod(FreshRSS_http_Util::class, 'getRetryAfterFile');
  19. $file = $getRetryAfterFile->invoke(null, $requestedUrl, $systemProxy);
  20. self::assertIsString($file);
  21. self::assertTrue(touch($file, time() + 600));
  22. try {
  23. (new FreshRSS_Feed($storedUrl, validate: false))->load();
  24. self::fail('The feed was loaded during its Retry-After');
  25. } catch (FreshRSS_Feed_Exception $e) {
  26. self::assertStringContainsString('will first retry after', $e->getMessage());
  27. } finally {
  28. unlink($file);
  29. }
  30. }
  31. /** @return array<string,array{0:string,1:string,2?:string}> */
  32. public static function provideStoredAndRequestedUrls(): array {
  33. // On a local network, where Retry-After is per URL
  34. return [
  35. 'HTML-encoded' => ['http://192.168.1.2/?action=display&amp;bridge=Example', 'http://192.168.1.2/?action=display&bridge=Example'],
  36. 'force_feed' => ['http://192.168.1.2/feed#force_feed', 'http://192.168.1.2/feed'],
  37. 'system proxy' => ['http://192.168.1.2/feed', 'http://192.168.1.2/feed', 'proxy.example.test:3128'],
  38. ];
  39. }
  40. public function test_hashFavicon_isKeyedBySalt(): void {
  41. FreshRSS_Context::initSystem();
  42. $systemConf = FreshRSS_Context::systemConf();
  43. $originalSalt = $systemConf->salt;
  44. $systemConf->_attribute('salt', 'test-instance-salt');
  45. try {
  46. $feed = new FreshRSS_Feed('http://example.net/feed.xml', validate: false);
  47. $feed->_website('https://example.com/', validate: false);
  48. $hash = $feed->hashFavicon();
  49. self::assertMatchesRegularExpression('/^[A-Za-z0-9_-]{11}$/', $hash);
  50. $hmac = substr(hash_hmac('sha256', 'https://example.com/' . $feed->proxyParam(), 'test-instance-salt', true), 0, 8);
  51. self::assertSame(rtrim(strtr(base64_encode($hmac), '+/', '-_'), '='), $hash);
  52. $feed->_website('https://example.org/', validate: false);
  53. self::assertNotSame($hash, $feed->hashFavicon());
  54. $hash = $feed->hashFavicon();
  55. $systemConf->_attribute('salt', 'another-instance-salt');
  56. self::assertNotSame($hash, $feed->hashFavicon(skipCache: true));
  57. } finally {
  58. $systemConf->_attribute('salt', $originalSalt);
  59. }
  60. }
  61. public function test_favicon_migratesLegacyCustomIconWithoutDeletingSharedSource(): void {
  62. FreshRSS_Context::initSystem();
  63. require_once LIB_PATH . '/favicons.php';
  64. $feed = new FreshRSS_Feed('http://example.net/feed.xml', validate: false);
  65. $feed->_id(987654);
  66. $contents = file_get_contents(DEFAULT_FAVICON);
  67. self::assertIsString($contents);
  68. $newPath = $feed->setCustomFavicon($contents, updateFeed: false);
  69. self::assertIsString($newPath);
  70. unlink($newPath);
  71. $legacyPath = FAVICONS_DIR . hash('crc32b', FreshRSS_Context::systemConf()->salt . $feed->id() . Minz_User::name()) . '.ico';
  72. file_put_contents($legacyPath, $contents);
  73. $feed->favicon();
  74. self::assertSame($contents, file_get_contents($newPath));
  75. self::assertSame($contents, file_get_contents($legacyPath));
  76. unlink($legacyPath);
  77. unlink($newPath);
  78. }
  79. public function test_favicon_migrationDoesNotOverwriteDestination(): void {
  80. FreshRSS_Context::initSystem();
  81. require_once LIB_PATH . '/favicons.php';
  82. $feed = new FreshRSS_Feed('http://example.net/feed.xml', validate: false);
  83. $feed->_id(987655);
  84. $contents = file_get_contents(DEFAULT_FAVICON);
  85. self::assertIsString($contents);
  86. $newPath = $feed->setCustomFavicon($contents, updateFeed: false);
  87. self::assertIsString($newPath);
  88. $destination = 'destination-icon';
  89. file_put_contents($newPath, $destination);
  90. $legacyPath = FAVICONS_DIR . hash('crc32b', FreshRSS_Context::systemConf()->salt . $feed->id() . Minz_User::name()) . '.ico';
  91. file_put_contents($legacyPath, $contents);
  92. $feed->favicon();
  93. self::assertSame($destination, file_get_contents($newPath));
  94. self::assertSame($contents, file_get_contents($legacyPath));
  95. unlink($legacyPath);
  96. unlink($newPath);
  97. }
  98. }