Files
FreshRSS/tests/app/Utils/dotNotationUtilTest.php
Shaurya Kesarwani 931fb41640 Keep JSON feed attachments as enclosures (#9510)
convertJsonToRss() collected item attachments into
$rssItem['attachments'], but FreshRSS_Entry::fromArray() ignores that
key, so attachments from JSON Feed and JSON dot notation feeds never
reached the generated RSS and were lost. Store them in the entry's
`enclosures` attribute instead, HTML-escaped like the other fields.

- Attachments of JSON Feed and JSON dot notation feeds (`itemAttachment`, `itemAttachmentUrl`, `itemAttachmentType`, `itemAttachmentLength`) were collected by `convertJsonToRss()` into `$rssItem['attachments']`, but `FreshRSS_Entry::fromArray()` never reads that key. So the generated RSS had no `<media:content>` for them and the enclosures were silently dropped. For example, a JSON Feed podcast item with `"attachments": [{"url": "https://example.net/1.mp3", "mime_type": "audio/mpeg"}]` showed up without its audio file.
- The attachments are now stored in the entry's `enclosures` attribute, which is what `rss.phtml` renders. The URL and type are HTML-escaped like the other fields, the length is kept only when numeric, and attachments without a URL are skipped.
- Unit test converting a JSON item with an attachment whose URL contains `&`, asserting the `<media:content url="…&amp;…" type="audio/mpeg" length="1234">` element and valid XML. It fails without the fix.
2026-10-26 08:47:23 +01:00

109 lines
3.3 KiB
PHP

<?php
declare(strict_types=1);
use PHPUnit\Framework\Attributes\DataProvider;
final class dotNotationUtilTest extends \PHPUnit\Framework\TestCase {
/**
* @return Traversable<array{array<string,mixed>,string,string}>
*/
public static function provideJsonDots(): Traversable {
$json = <<<json
{
"hello": "world",
"deeper": {
"hello": "again"
},
"items": [
{
"meta": {"title": "first"}
},
{
"meta": {"title": "second"}
}
]
}
json;
$array = json_decode($json, true);
yield [$array, 'hello', 'world'];
yield [$array, 'deeper.hello', 'again'];
yield [$array, 'items.0.meta.title', 'first'];
yield [$array, 'items[0].meta.title', 'first'];
yield [$array, 'items.1.meta.title', 'second'];
yield [$array, 'items[1].meta.title', 'second'];
yield [$array, '"Hello " & hello & \'!\'', 'Hello world!'];
yield [$array, '"Hello & goodbye " & hello & \'!\'', 'Hello & goodbye world!'];
yield [$array, '"Hello " & hello & deeper.hello & "!"', 'Hello worldagain!'];
}
/**
* @param array<string,mixed> $array
*/
#[DataProvider('provideJsonDots')]
public static function testJsonDots(array $array, string $key, string $expected): void {
$value = FreshRSS_dotNotation_Util::get($array, $key);
self::assertSame($expected, $value);
}
public static function testConvertJsonToRssEscapesCategories(): void {
FreshRSS_Context::initSystem();
Minz_Translate::init('en');
$json = <<<json
{
"items": [
{"title": "First", "url": "https://example.net/1", "categories": ["News & Politics", "Arts & Culture"]},
{"title": "Second", "url": "https://example.net/2", "categories": "Science & Tech"}
]
}
json;
$jf = json_decode($json, true);
self::assertIsArray($jf);
$rss = FreshRSS_dotNotation_Util::convertJsonToRss($jf, 'https://example.net/', [
'item' => 'items',
'itemTitle' => 'title',
'itemUri' => 'url',
'itemCategories' => 'categories',
], 'Test');
self::assertIsString($rss);
self::assertStringContainsString('<category>News &amp; Politics</category>', $rss);
self::assertStringContainsString('<category>Arts &amp; Culture</category>', $rss);
self::assertStringContainsString('<category>Science &amp; Tech</category>', $rss);
self::assertNotFalse(simplexml_load_string($rss), 'Output must be valid XML');
}
public static function testConvertJsonToRssKeepsAttachments(): void {
FreshRSS_Context::initSystem();
Minz_Translate::init('en');
$json = <<<json
{
"items": [
{
"title": "Episode 1",
"url": "https://example.net/1",
"attachments": [
{"url": "https://example.net/1.mp3?a=1&b=2", "mime_type": "audio/mpeg", "size_in_bytes": 1234}
]
}
]
}
json;
$jf = json_decode($json, true);
self::assertIsArray($jf);
$rss = FreshRSS_dotNotation_Util::convertJsonToRss($jf, 'https://example.net/', [
'item' => 'items',
'itemTitle' => 'title',
'itemUri' => 'url',
'itemAttachment' => 'attachments',
'itemAttachmentUrl' => 'url',
'itemAttachmentType' => 'mime_type',
'itemAttachmentLength' => 'size_in_bytes',
], 'Test');
self::assertIsString($rss);
self::assertStringContainsString(
'<media:content url="https://example.net/1.mp3?a=1&amp;b=2" type="audio/mpeg" length="1234">', $rss);
self::assertNotFalse(simplexml_load_string($rss), 'Output must be valid XML');
}
}