parser_test.go 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package json // import "miniflux.app/v2/internal/reader/json"
  4. import (
  5. "bytes"
  6. "testing"
  7. "time"
  8. )
  9. func TestParseJsonFeedVersion1(t *testing.T) {
  10. data := `{
  11. "version": "https://jsonfeed.org/version/1",
  12. "title": "My Example Feed",
  13. "icon": "https://micro.blog/jsonfeed/avatar.jpg",
  14. "favicon": "https://micro.blog/jsonfeed/favicon.png",
  15. "home_page_url": "https://example.org/",
  16. "feed_url": "https://example.org/feed.json",
  17. "items": [
  18. {
  19. "id": "2",
  20. "content_text": "This is a second item.",
  21. "url": "https://example.org/second-item"
  22. },
  23. {
  24. "id": "1",
  25. "content_html": "<p>Hello, world!</p>",
  26. "url": "https://example.org/initial-post"
  27. }
  28. ]
  29. }`
  30. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  31. if err != nil {
  32. t.Fatal(err)
  33. }
  34. if feed.Title != "My Example Feed" {
  35. t.Errorf("Incorrect title, got: %s", feed.Title)
  36. }
  37. if feed.Description != "" {
  38. t.Errorf("Incorrect description, got: %s", feed.Description)
  39. }
  40. if feed.FeedURL != "https://example.org/feed.json" {
  41. t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
  42. }
  43. if feed.SiteURL != "https://example.org/" {
  44. t.Errorf("Incorrect site URL, got: %s", feed.SiteURL)
  45. }
  46. if feed.IconURL != "https://micro.blog/jsonfeed/favicon.png" {
  47. t.Errorf("Incorrect icon URL, got: %s", feed.IconURL)
  48. }
  49. if len(feed.Entries) != 2 {
  50. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  51. }
  52. if feed.Entries[0].Hash != "d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35" {
  53. t.Errorf("Incorrect entry hash, got: %s", feed.Entries[0].Hash)
  54. }
  55. if feed.Entries[0].URL != "https://example.org/second-item" {
  56. t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
  57. }
  58. if feed.Entries[0].Title != "This is a second item." {
  59. t.Errorf(`Incorrect entry title, got: "%s"`, feed.Entries[0].Title)
  60. }
  61. if feed.Entries[0].Content != "This is a second item." {
  62. t.Errorf("Incorrect entry content, got: %s", feed.Entries[0].Content)
  63. }
  64. if feed.Entries[1].Hash != "6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b" {
  65. t.Errorf("Incorrect entry hash, got: %s", feed.Entries[1].Hash)
  66. }
  67. if feed.Entries[1].URL != "https://example.org/initial-post" {
  68. t.Errorf("Incorrect entry URL, got: %s", feed.Entries[1].URL)
  69. }
  70. if feed.Entries[1].Title != "Hello, world!" {
  71. t.Errorf(`Incorrect entry title, got: "%s"`, feed.Entries[1].Title)
  72. }
  73. if feed.Entries[1].Content != "<p>Hello, world!</p>" {
  74. t.Errorf("Incorrect entry content, got: %s", feed.Entries[1].Content)
  75. }
  76. }
  77. func TestParseFeedWithDescription(t *testing.T) {
  78. data := `{
  79. "version": "https://jsonfeed.org/version/1",
  80. "title": "My Example Feed",
  81. "description": "This is a sample feed description.",
  82. "home_page_url": "https://example.org/",
  83. "feed_url": "https://example.org/feed.json",
  84. "items": []
  85. }`
  86. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  87. if err != nil {
  88. t.Fatal(err)
  89. }
  90. if feed.Description != "This is a sample feed description." {
  91. t.Errorf("Incorrect description, got: %s", feed.Description)
  92. }
  93. }
  94. func TestParsePodcast(t *testing.T) {
  95. data := `{
  96. "version": "https://jsonfeed.org/version/1",
  97. "user_comment": "This is a podcast feed. You can add this feed to your podcast client using the following URL: http://therecord.co/feed.json",
  98. "title": "The Record",
  99. "home_page_url": "http://therecord.co/",
  100. "feed_url": "http://therecord.co/feed.json",
  101. "items": [
  102. {
  103. "id": "http://therecord.co/chris-parrish",
  104. "title": "Special #1 - Chris Parrish",
  105. "url": "http://therecord.co/chris-parrish",
  106. "content_text": "Chris has worked at Adobe and as a founder of Rogue Sheep, which won an Apple Design Award for Postage. Chris’s new company is Aged & Distilled with Guy English — which shipped Napkin, a Mac app for visual collaboration. Chris is also the co-host of The Record. He lives on Bainbridge Island, a quick ferry ride from Seattle.",
  107. "content_html": "Chris has worked at <a href=\"http://adobe.com/\">Adobe</a> and as a founder of Rogue Sheep, which won an Apple Design Award for Postage. Chris’s new company is Aged & Distilled with Guy English — which shipped <a href=\"http://aged-and-distilled.com/napkin/\">Napkin</a>, a Mac app for visual collaboration. Chris is also the co-host of The Record. He lives on <a href=\"http://www.ci.bainbridge-isl.wa.us/\">Bainbridge Island</a>, a quick ferry ride from Seattle.",
  108. "summary": "Brent interviews Chris Parrish, co-host of The Record and one-half of Aged & Distilled.",
  109. "date_published": "2014-05-09T14:04:00-07:00",
  110. "attachments": [
  111. {
  112. "url": "http://therecord.co/downloads/The-Record-sp1e1-ChrisParrish.m4a",
  113. "mime_type": "audio/x-m4a",
  114. "size_in_bytes": 89970236,
  115. "duration_in_seconds": 6629
  116. }
  117. ]
  118. }
  119. ]
  120. }`
  121. feed, err := Parse("http://therecord.co/feed.json", bytes.NewBufferString(data))
  122. if err != nil {
  123. t.Fatal(err)
  124. }
  125. if feed.Title != "The Record" {
  126. t.Errorf("Incorrect title, got: %s", feed.Title)
  127. }
  128. if feed.FeedURL != "http://therecord.co/feed.json" {
  129. t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
  130. }
  131. if feed.SiteURL != "http://therecord.co/" {
  132. t.Errorf("Incorrect site URL, got: %s", feed.SiteURL)
  133. }
  134. if len(feed.Entries) != 1 {
  135. t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries))
  136. }
  137. if feed.Entries[0].Hash != "6b678e57962a1b001e4e873756563cdc08bbd06ca561e764e0baa9a382485797" {
  138. t.Errorf("Incorrect entry hash, got: %s", feed.Entries[0].Hash)
  139. }
  140. if feed.Entries[0].URL != "http://therecord.co/chris-parrish" {
  141. t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
  142. }
  143. if feed.Entries[0].Title != "Special #1 - Chris Parrish" {
  144. t.Errorf(`Incorrect entry title, got: "%s"`, feed.Entries[0].Title)
  145. }
  146. if feed.Entries[0].Content != `Chris has worked at <a href="http://adobe.com/">Adobe</a> and as a founder of Rogue Sheep, which won an Apple Design Award for Postage. Chris’s new company is Aged & Distilled with Guy English — which shipped <a href="http://aged-and-distilled.com/napkin/">Napkin</a>, a Mac app for visual collaboration. Chris is also the co-host of The Record. He lives on <a href="http://www.ci.bainbridge-isl.wa.us/">Bainbridge Island</a>, a quick ferry ride from Seattle.` {
  147. t.Errorf(`Incorrect entry content, got: "%s"`, feed.Entries[0].Content)
  148. }
  149. location, _ := time.LoadLocation("America/Vancouver")
  150. if !feed.Entries[0].Date.Equal(time.Date(2014, time.May, 9, 14, 4, 0, 0, location)) {
  151. t.Errorf("Incorrect entry date, got: %v", feed.Entries[0].Date)
  152. }
  153. if len(feed.Entries[0].Enclosures) != 1 {
  154. t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures))
  155. }
  156. if feed.Entries[0].Enclosures[0].URL != "http://therecord.co/downloads/The-Record-sp1e1-ChrisParrish.m4a" {
  157. t.Errorf("Incorrect enclosure URL, got: %s", feed.Entries[0].Enclosures[0].URL)
  158. }
  159. if feed.Entries[0].Enclosures[0].MimeType != "audio/x-m4a" {
  160. t.Errorf("Incorrect enclosure type, got: %s", feed.Entries[0].Enclosures[0].MimeType)
  161. }
  162. if feed.Entries[0].Enclosures[0].Size != 89970236 {
  163. t.Errorf("Incorrect enclosure length, got: %d", feed.Entries[0].Enclosures[0].Size)
  164. }
  165. }
  166. func TestParseFeedWithFeedURLWithTrailingSpace(t *testing.T) {
  167. data := `{
  168. "version": "https://jsonfeed.org/version/1",
  169. "title": "My Example Feed",
  170. "home_page_url": "https://example.org/",
  171. "feed_url": "https://example.org/feed.json ",
  172. "items": []
  173. }`
  174. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  175. if err != nil {
  176. t.Fatal(err)
  177. }
  178. if feed.FeedURL != "https://example.org/feed.json" {
  179. t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
  180. }
  181. }
  182. func TestParseFeedWithRelativeFeedURL(t *testing.T) {
  183. data := `{
  184. "version": "https://jsonfeed.org/version/1",
  185. "title": "My Example Feed",
  186. "home_page_url": "https://example.org/",
  187. "feed_url": "/feed.json",
  188. "items": []
  189. }`
  190. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  191. if err != nil {
  192. t.Fatal(err)
  193. }
  194. if feed.FeedURL != "https://example.org/feed.json" {
  195. t.Errorf("Incorrect feed URL, got: %s", feed.FeedURL)
  196. }
  197. }
  198. func TestParseFeedSiteURLWithTrailingSpace(t *testing.T) {
  199. data := `{
  200. "version": "https://jsonfeed.org/version/1",
  201. "title": "My Example Feed",
  202. "home_page_url": "https://example.org/ ",
  203. "feed_url": "https://example.org/feed.json",
  204. "items": []
  205. }`
  206. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  207. if err != nil {
  208. t.Fatal(err)
  209. }
  210. if feed.SiteURL != "https://example.org/" {
  211. t.Errorf("Incorrect site URL, got: %s", feed.SiteURL)
  212. }
  213. }
  214. func TestParseFeedWithRelativeSiteURL(t *testing.T) {
  215. data := `{
  216. "version": "https://jsonfeed.org/version/1",
  217. "title": "My Example Feed",
  218. "home_page_url": "/home ",
  219. "feed_url": "https://example.org/feed.json",
  220. "items": []
  221. }`
  222. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  223. if err != nil {
  224. t.Fatal(err)
  225. }
  226. if feed.SiteURL != "https://example.org/home" {
  227. t.Errorf("Incorrect site URL, got: %s", feed.SiteURL)
  228. }
  229. }
  230. func TestParseFeedWithoutTitle(t *testing.T) {
  231. data := `{
  232. "version": "https://jsonfeed.org/version/1",
  233. "home_page_url": "https://example.org/",
  234. "feed_url": "https://example.org/feed.json",
  235. "items": [
  236. {
  237. "id": "2347259",
  238. "url": "https://example.org/2347259",
  239. "content_text": "Cats are neat. \n\nhttps://example.org/cats",
  240. "date_published": "2016-02-09T14:22:00-07:00"
  241. }
  242. ]
  243. }`
  244. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  245. if err != nil {
  246. t.Fatal(err)
  247. }
  248. if feed.Title != "https://example.org/" {
  249. t.Errorf("Incorrect title, got: %s", feed.Title)
  250. }
  251. }
  252. func TestParseFeedWithoutHomePage(t *testing.T) {
  253. data := `{
  254. "version": "https://jsonfeed.org/version/1",
  255. "feed_url": "https://example.org/feed.json",
  256. "title": "Some test",
  257. "items": [
  258. {
  259. "id": "2347259",
  260. "url": "https://example.org/2347259",
  261. "content_text": "Cats are neat. \n\nhttps://example.org/cats",
  262. "date_published": "2016-02-09T14:22:00-07:00"
  263. }
  264. ]
  265. }`
  266. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  267. if err != nil {
  268. t.Fatal(err)
  269. }
  270. if feed.SiteURL != "https://example.org/feed.json" {
  271. t.Errorf("Incorrect title, got: %s", feed.Title)
  272. }
  273. }
  274. func TestParseFeedWithoutFeedURL(t *testing.T) {
  275. data := `{
  276. "version": "https://jsonfeed.org/version/1",
  277. "title": "Some test",
  278. "items": [
  279. {
  280. "id": "2347259",
  281. "url": "https://example.org/2347259",
  282. "content_text": "Cats are neat. \n\nhttps://example.org/cats",
  283. "date_published": "2016-02-09T14:22:00-07:00"
  284. }
  285. ]
  286. }`
  287. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  288. if err != nil {
  289. t.Fatal(err)
  290. }
  291. if feed.SiteURL != "https://example.org/feed.json" {
  292. t.Errorf("Incorrect title, got: %s", feed.Title)
  293. }
  294. }
  295. func TestParseItemWithoutAttachmentURL(t *testing.T) {
  296. data := `{
  297. "version": "https://jsonfeed.org/version/1",
  298. "user_comment": "This is a podcast feed. You can add this feed to your podcast client using the following URL: http://therecord.co/feed.json",
  299. "title": "The Record",
  300. "home_page_url": "http://therecord.co/",
  301. "feed_url": "http://therecord.co/feed.json",
  302. "items": [
  303. {
  304. "id": "http://therecord.co/chris-parrish",
  305. "title": "Special #1 - Chris Parrish",
  306. "url": "http://therecord.co/chris-parrish",
  307. "content_text": "Chris has worked at Adobe and as a founder of Rogue Sheep, which won an Apple Design Award for Postage. Chris’s new company is Aged & Distilled with Guy English — which shipped Napkin, a Mac app for visual collaboration. Chris is also the co-host of The Record. He lives on Bainbridge Island, a quick ferry ride from Seattle.",
  308. "date_published": "2014-05-09T14:04:00-07:00",
  309. "attachments": [
  310. {
  311. "url": "",
  312. "mime_type": "audio/x-m4a",
  313. "size_in_bytes": 0
  314. }
  315. ]
  316. }
  317. ]
  318. }`
  319. feed, err := Parse("http://therecord.co/feed.json", bytes.NewBufferString(data))
  320. if err != nil {
  321. t.Fatal(err)
  322. }
  323. if len(feed.Entries) != 1 {
  324. t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries))
  325. }
  326. if len(feed.Entries[0].Enclosures) != 0 {
  327. t.Errorf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures))
  328. }
  329. }
  330. func TestParseItemWithRelativeURL(t *testing.T) {
  331. data := `{
  332. "version": "https://jsonfeed.org/version/1",
  333. "title": "Example",
  334. "home_page_url": "https://example.org/",
  335. "feed_url": "https://example.org/feed.json",
  336. "items": [
  337. {
  338. "id": "2347259",
  339. "url": "something.html",
  340. "date_published": "2016-02-09T14:22:00-07:00"
  341. }
  342. ]
  343. }`
  344. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  345. if err != nil {
  346. t.Fatal(err)
  347. }
  348. if feed.Entries[0].URL != "https://example.org/something.html" {
  349. t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
  350. }
  351. }
  352. func TestParseItemWithExternalURLAndNoURL(t *testing.T) {
  353. data := `{
  354. "version": "https://jsonfeed.org/version/1",
  355. "title": "Example",
  356. "home_page_url": "https://example.org/",
  357. "feed_url": "https://example.org/feed.json",
  358. "items": [
  359. {
  360. "id": "1234259",
  361. "external_url": "some_page.html"
  362. }
  363. ]
  364. }`
  365. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  366. if err != nil {
  367. t.Fatal(err)
  368. }
  369. if len(feed.Entries) != 1 {
  370. t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries))
  371. }
  372. if feed.Entries[0].URL != "https://example.org/some_page.html" {
  373. t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
  374. }
  375. }
  376. func TestParseItemWithExternalURLAndURL(t *testing.T) {
  377. data := `{
  378. "version": "https://jsonfeed.org/version/1",
  379. "title": "Example",
  380. "home_page_url": "https://example.org/",
  381. "feed_url": "https://example.org/feed.json",
  382. "items": [
  383. {
  384. "id": "1234259",
  385. "url": "https://example.org/article",
  386. "external_url": "https://example.org/another-article"
  387. }
  388. ]
  389. }`
  390. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  391. if err != nil {
  392. t.Fatal(err)
  393. }
  394. if len(feed.Entries) != 1 {
  395. t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries))
  396. }
  397. if feed.Entries[0].URL != "https://example.org/article" {
  398. t.Errorf("Incorrect entry URL, got: %s", feed.Entries[0].URL)
  399. }
  400. }
  401. func TestParseItemWithLegacyAuthorField(t *testing.T) {
  402. data := `{
  403. "version": "https://jsonfeed.org/version/1",
  404. "user_comment": "This is a microblog feed. You can add this to your feed reader using the following URL: https://example.org/feed.json",
  405. "title": "Brent Simmons’s Microblog",
  406. "home_page_url": "https://example.org/",
  407. "feed_url": "https://example.org/feed.json",
  408. "author": {
  409. "name": "Brent Simmons",
  410. "url": "http://example.org/",
  411. "avatar": "https://example.org/avatar.png"
  412. },
  413. "items": [
  414. {
  415. "id": "2347259",
  416. "url": "https://example.org/2347259",
  417. "content_text": "Cats are neat. \n\nhttps://example.org/cats",
  418. "date_published": "2016-02-09T14:22:00-07:00"
  419. }
  420. ]
  421. }`
  422. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  423. if err != nil {
  424. t.Fatal(err)
  425. }
  426. if len(feed.Entries) != 1 {
  427. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  428. }
  429. if feed.Entries[0].Author != "Brent Simmons" {
  430. t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author)
  431. }
  432. }
  433. func TestParseItemWithMultipleAuthorFields(t *testing.T) {
  434. data := `{
  435. "version": "https://jsonfeed.org/version/1.1",
  436. "user_comment": "This is a microblog feed. You can add this to your feed reader using the following URL: https://example.org/feed.json",
  437. "title": "Brent Simmons’s Microblog",
  438. "home_page_url": "https://example.org/",
  439. "feed_url": "https://example.org/feed.json",
  440. "author": {
  441. "name": "Deprecated Author Field",
  442. "url": "http://example.org/",
  443. "avatar": "https://example.org/avatar.png"
  444. },
  445. "authors": [
  446. {
  447. "name": "Brent Simmons",
  448. "url": "http://example.org/",
  449. "avatar": "https://example.org/avatar.png"
  450. }
  451. ],
  452. "items": [
  453. {
  454. "id": "2347259",
  455. "url": "https://example.org/2347259",
  456. "content_text": "Cats are neat. \n\nhttps://example.org/cats",
  457. "date_published": "2016-02-09T14:22:00-07:00"
  458. }
  459. ]
  460. }`
  461. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  462. if err != nil {
  463. t.Fatal(err)
  464. }
  465. if len(feed.Entries) != 1 {
  466. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  467. }
  468. if feed.Entries[0].Author != "Brent Simmons, Deprecated Author Field" {
  469. t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author)
  470. }
  471. }
  472. func TestParseItemWithMultipleDuplicateAuthors(t *testing.T) {
  473. data := `{
  474. "version": "https://jsonfeed.org/version/1.1",
  475. "title": "Example",
  476. "home_page_url": "https://example.org/",
  477. "feed_url": "https://example.org/feed.json",
  478. "items": [
  479. {
  480. "id": "2347259",
  481. "url": "https://example.org/2347259",
  482. "content_text": "Cats are neat. \n\nhttps://example.org/cats",
  483. "date_published": "2016-02-09T14:22:00-07:00",
  484. "authors": [
  485. {
  486. "name": "Author B",
  487. "url": "http://example.org/",
  488. "avatar": "https://example.org/avatar.png"
  489. },
  490. {
  491. "name": "Author A",
  492. "url": "http://example.org/",
  493. "avatar": "https://example.org/avatar.png"
  494. },
  495. {
  496. "name": "Author B",
  497. "url": "http://example.org/",
  498. "avatar": "https://example.org/avatar.png"
  499. }
  500. ]
  501. }
  502. ]
  503. }`
  504. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  505. if err != nil {
  506. t.Fatal(err)
  507. }
  508. if len(feed.Entries) != 1 {
  509. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  510. }
  511. if feed.Entries[0].Author != "Author A, Author B" {
  512. t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author)
  513. }
  514. }
  515. func TestParseItemWithAuthorsObject(t *testing.T) {
  516. data := `{
  517. "version": "https://jsonfeed.org/version/1.1",
  518. "title": "Example",
  519. "home_page_url": "https://example.org/",
  520. "feed_url": "https://example.org/feed.json",
  521. "items": [
  522. {
  523. "id": "1",
  524. "title": "Example Item",
  525. "url": "https://example.org/item",
  526. "date_published": "2020-01-02T03:04:05Z",
  527. "authors": {
  528. "name": "Example Author"
  529. }
  530. }
  531. ]
  532. }`
  533. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  534. if err != nil {
  535. t.Fatal(err)
  536. }
  537. if len(feed.Entries) != 1 {
  538. t.Fatalf("Incorrect number of entries, got: %d", len(feed.Entries))
  539. }
  540. if feed.Entries[0].Author != "Example Author" {
  541. t.Errorf("Incorrect entry author, got: %s", feed.Entries[0].Author)
  542. }
  543. }
  544. func TestParseItemWithInvalidDate(t *testing.T) {
  545. data := `{
  546. "version": "https://jsonfeed.org/version/1",
  547. "title": "My Example Feed",
  548. "home_page_url": "https://example.org/",
  549. "feed_url": "https://example.org/feed.json",
  550. "items": [
  551. {
  552. "id": "2347259",
  553. "url": "https://example.org/2347259",
  554. "content_text": "Cats are neat. \n\nhttps://example.org/cats",
  555. "date_published": "Tomorrow"
  556. }
  557. ]
  558. }`
  559. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  560. if err != nil {
  561. t.Fatal(err)
  562. }
  563. if len(feed.Entries) != 1 {
  564. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  565. }
  566. duration := time.Since(feed.Entries[0].Date)
  567. if duration.Seconds() > 1 {
  568. t.Errorf("Incorrect entry date, got: %v", feed.Entries[0].Date)
  569. }
  570. }
  571. func TestParseItemWithMissingTitleUsesSummaryFallback(t *testing.T) {
  572. data := `{
  573. "version": "https://jsonfeed.org/version/1",
  574. "title": "My Example Feed",
  575. "home_page_url": "https://example.org/",
  576. "feed_url": "https://example.org/feed.json",
  577. "items": [
  578. {
  579. "summary": "Summary title",
  580. "content_text": "Content text title",
  581. "content_html": "<p>HTML title</p>"
  582. }
  583. ]
  584. }`
  585. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  586. if err != nil {
  587. t.Fatal(err)
  588. }
  589. if len(feed.Entries) != 1 {
  590. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  591. }
  592. if feed.Entries[0].Title != "Summary title" {
  593. t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title)
  594. }
  595. }
  596. func TestParseItemWithMissingTitleUsesContentTextFallback(t *testing.T) {
  597. data := `{
  598. "version": "https://jsonfeed.org/version/1",
  599. "title": "My Example Feed",
  600. "home_page_url": "https://example.org/",
  601. "feed_url": "https://example.org/feed.json",
  602. "items": [
  603. {
  604. "summary": " ",
  605. "content_text": "Content text title",
  606. "content_html": "<p>HTML title</p>"
  607. }
  608. ]
  609. }`
  610. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  611. if err != nil {
  612. t.Fatal(err)
  613. }
  614. if len(feed.Entries) != 1 {
  615. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  616. }
  617. if feed.Entries[0].Title != "Content text title" {
  618. t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title)
  619. }
  620. }
  621. func TestParseItemWithMissingTitleUsesHTMLFallback(t *testing.T) {
  622. data := `{
  623. "version": "https://jsonfeed.org/version/1",
  624. "title": "My Example Feed",
  625. "home_page_url": "https://example.org/",
  626. "feed_url": "https://example.org/feed.json",
  627. "items": [
  628. {
  629. "summary": "",
  630. "content_text": "",
  631. "content_html": "<p>HTML title</p>"
  632. }
  633. ]
  634. }`
  635. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  636. if err != nil {
  637. t.Fatal(err)
  638. }
  639. if len(feed.Entries) != 1 {
  640. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  641. }
  642. if feed.Entries[0].Title != "HTML title" {
  643. t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title)
  644. }
  645. }
  646. func TestParseItemWithMissingTitleUsesURLFallback(t *testing.T) {
  647. data := `{
  648. "version": "https://jsonfeed.org/version/1",
  649. "title": "My Example Feed",
  650. "home_page_url": "https://example.org/",
  651. "feed_url": "https://example.org/feed.json",
  652. "items": [
  653. {
  654. "url": "https://example.org/item"
  655. }
  656. ]
  657. }`
  658. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  659. if err != nil {
  660. t.Fatal(err)
  661. }
  662. if len(feed.Entries) != 1 {
  663. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  664. }
  665. if feed.Entries[0].Title != "https://example.org/item" {
  666. t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title)
  667. }
  668. }
  669. func TestParseItemWithTooLongUnicodeTitle(t *testing.T) {
  670. data := `{
  671. "version": "https://jsonfeed.org/version/1",
  672. "title": "My Example Feed",
  673. "home_page_url": "https://example.org/",
  674. "feed_url": "https://example.org/feed.json",
  675. "items": [
  676. {
  677. "title": "I’m riding my electric bike and came across this castle. It’s called “Schloss Richmond”. 🚴‍♂️"
  678. }
  679. ]
  680. }`
  681. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  682. if err != nil {
  683. t.Fatal(err)
  684. }
  685. if len(feed.Entries) != 1 {
  686. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  687. }
  688. if len(feed.Entries[0].Title) != 110 {
  689. t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title)
  690. }
  691. if len([]rune(feed.Entries[0].Title)) != 93 {
  692. t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title)
  693. }
  694. }
  695. func TestParseItemTitleWithXMLTags(t *testing.T) {
  696. data := `{
  697. "version": "https://jsonfeed.org/version/1",
  698. "title": "My Example Feed",
  699. "home_page_url": "https://example.org/",
  700. "feed_url": "https://example.org/feed.json",
  701. "items": [
  702. {
  703. "title": "</example>"
  704. }
  705. ]
  706. }`
  707. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  708. if err != nil {
  709. t.Fatal(err)
  710. }
  711. if len(feed.Entries) != 1 {
  712. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  713. }
  714. if feed.Entries[0].Title != "</example>" {
  715. t.Errorf("Incorrect entry title, got: %s", feed.Entries[0].Title)
  716. }
  717. }
  718. func TestParseItemWithoutID(t *testing.T) {
  719. data := `{
  720. "version": "https://jsonfeed.org/version/1",
  721. "title": "My Example Feed",
  722. "home_page_url": "https://example.org/",
  723. "feed_url": "https://example.org/feed.json",
  724. "items": [
  725. {
  726. "content_text": "Some text."
  727. }
  728. ]
  729. }`
  730. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  731. if err != nil {
  732. t.Fatal(err)
  733. }
  734. if len(feed.Entries) != 1 {
  735. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  736. }
  737. if feed.Entries[0].Hash != "13b4c5aecd1b6d749afcee968fbf9c80f1ed1bbdbe1aaf25cb34ebd01144bbe9" {
  738. t.Errorf("Incorrect entry hash, got: %s", feed.Entries[0].Hash)
  739. }
  740. }
  741. func TestParseItemTags(t *testing.T) {
  742. data := `{
  743. "version": "https://jsonfeed.org/version/1",
  744. "user_comment": "This is a microblog feed. You can add this to your feed reader using the following URL: https://example.org/feed.json",
  745. "title": "Brent Simmons’s Microblog",
  746. "home_page_url": "https://example.org/",
  747. "feed_url": "https://example.org/feed.json",
  748. "author": {
  749. "name": "Brent Simmons",
  750. "url": "http://example.org/",
  751. "avatar": "https://example.org/avatar.png"
  752. },
  753. "items": [
  754. {
  755. "id": "2347259",
  756. "url": "https://example.org/2347259",
  757. "content_text": "Cats are neat. \n\nhttps://example.org/cats",
  758. "date_published": "2016-02-09T14:22:00-07:00",
  759. "tags": [
  760. " tag 1",
  761. " ",
  762. "tag 2",
  763. "tag 2",
  764. "aaa"
  765. ]
  766. }
  767. ]
  768. }`
  769. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  770. if err != nil {
  771. t.Fatal(err)
  772. }
  773. if len(feed.Entries) != 1 {
  774. t.Errorf("Incorrect number of entries, got: %d", len(feed.Entries))
  775. }
  776. if len(feed.Entries[0].Tags) != 3 {
  777. t.Errorf("Incorrect number of Tags, got: %d", len(feed.Entries[0].Tags))
  778. }
  779. expected := []string{"aaa", "tag 1", "tag 2"}
  780. for i, tag := range feed.Entries[0].Tags {
  781. if tag != expected[i] {
  782. t.Errorf("Incorrect entry tag, got %q instead of %q", tag, expected[i])
  783. }
  784. }
  785. }
  786. func TestParseFeedFavicon(t *testing.T) {
  787. data := `{
  788. "version": "https://jsonfeed.org/version/1",
  789. "title": "My Example Feed",
  790. "favicon": "https://example.org/jsonfeed/favicon.png",
  791. "home_page_url": "https://example.org/",
  792. "feed_url": "https://example.org/feed.json",
  793. "items": [
  794. {
  795. "id": "2",
  796. "content_text": "This is a second item.",
  797. "url": "https://example.org/second-item"
  798. },
  799. {
  800. "id": "1",
  801. "content_html": "<p>Hello, world!</p>",
  802. "url": "https://example.org/initial-post"
  803. }
  804. ]
  805. }`
  806. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  807. if err != nil {
  808. t.Fatal(err)
  809. }
  810. if feed.IconURL != "https://example.org/jsonfeed/favicon.png" {
  811. t.Errorf("Incorrect icon URL, got: %s", feed.IconURL)
  812. }
  813. }
  814. func TestParseFeedIcon(t *testing.T) {
  815. data := `{
  816. "version": "https://jsonfeed.org/version/1",
  817. "title": "My Example Feed",
  818. "icon": "https://example.org/jsonfeed/icon.png",
  819. "home_page_url": "https://example.org/",
  820. "feed_url": "https://example.org/feed.json",
  821. "items": [
  822. {
  823. "id": "2",
  824. "content_text": "This is a second item.",
  825. "url": "https://example.org/second-item"
  826. },
  827. {
  828. "id": "1",
  829. "content_html": "<p>Hello, world!</p>",
  830. "url": "https://example.org/initial-post"
  831. }
  832. ]
  833. }`
  834. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  835. if err != nil {
  836. t.Fatal(err)
  837. }
  838. if feed.IconURL != "https://example.org/jsonfeed/icon.png" {
  839. t.Errorf("Incorrect icon URL, got: %s", feed.IconURL)
  840. }
  841. }
  842. func TestParseFeedWithRelativeAttachmentURL(t *testing.T) {
  843. data := `{
  844. "version": "https://jsonfeed.org/version/1",
  845. "title": "My Example Feed",
  846. "home_page_url": "https://example.org/",
  847. "feed_url": "https://example.org/feed.json",
  848. "items": [
  849. {
  850. "id": "2",
  851. "content_text": "This is a second item.",
  852. "url": "https://example.org/second-item",
  853. "attachments": [
  854. {
  855. "url": " /attachment.mp3 ",
  856. "mime_type": "audio/mpeg",
  857. "size_in_bytes": 123456
  858. }
  859. ]
  860. }
  861. ]
  862. }`
  863. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  864. if err != nil {
  865. t.Fatal(err)
  866. }
  867. if len(feed.Entries[0].Enclosures) != 1 {
  868. t.Fatalf("Incorrect number of enclosures, got: %d", len(feed.Entries[0].Enclosures))
  869. }
  870. if feed.Entries[0].Enclosures[0].URL != "https://example.org/attachment.mp3" {
  871. t.Errorf("Incorrect enclosure URL, got: %q", feed.Entries[0].Enclosures[0].URL)
  872. }
  873. }
  874. func TestParseInvalidJSON(t *testing.T) {
  875. data := `garbage`
  876. _, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  877. if err == nil {
  878. t.Error("Parse should returns an error")
  879. }
  880. }
  881. func TestParseNullJSONFeed(t *testing.T) {
  882. data := `null`
  883. feed, err := Parse("https://example.org/feed.json", bytes.NewBufferString(data))
  884. if err != nil {
  885. t.Fatalf("Unexpected error when parsing null feed: %v", err)
  886. }
  887. if feed == nil {
  888. t.Fatalf("Feed should not be nil")
  889. }
  890. }