enclosure_test.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package model
  4. import (
  5. "net/http"
  6. "os"
  7. "testing"
  8. "github.com/gorilla/mux"
  9. "miniflux.app/v2/internal/config"
  10. )
  11. func TestEnclosure_Html5MimeTypeGivesOriginalMimeType(t *testing.T) {
  12. enclosure := Enclosure{MimeType: "thing/thisMimeTypeIsNotExpectedToBeReplaced"}
  13. if enclosure.Html5MimeType() != enclosure.MimeType {
  14. t.Fatalf(
  15. "HTML5 MimeType must provide original MimeType if not explicitly Replaced. Got %s ,expected '%s' ",
  16. enclosure.Html5MimeType(),
  17. enclosure.MimeType,
  18. )
  19. }
  20. }
  21. func TestEnclosure_Html5MimeTypeReplaceStandardM4vByAppleSpecificMimeType(t *testing.T) {
  22. enclosure := Enclosure{MimeType: "video/m4v"}
  23. if enclosure.Html5MimeType() != "video/x-m4v" {
  24. // Solution from this stackoverflow discussion:
  25. // https://stackoverflow.com/questions/15277147/m4v-mimetype-video-mp4-or-video-m4v/66945470#66945470
  26. // tested at the time of this commit (06/2023) on latest Firefox & Vivaldi on this feed
  27. // https://www.florenceporcel.com/podcast/lfhdu.xml
  28. t.Fatalf(
  29. "HTML5 MimeType must be replaced by 'video/x-m4v' when originally video/m4v to ensure playbacks in browsers. Got '%s'",
  30. enclosure.Html5MimeType(),
  31. )
  32. }
  33. }
  34. func TestEnclosure_IsAudio(t *testing.T) {
  35. testCases := []struct {
  36. name string
  37. mimeType string
  38. expected bool
  39. }{
  40. {"MP3 audio", "audio/mpeg", true},
  41. {"WAV audio", "audio/wav", true},
  42. {"OGG audio", "audio/ogg", true},
  43. {"Mixed case audio", "Audio/MP3", true},
  44. {"Video file", "video/mp4", false},
  45. {"Image file", "image/jpeg", false},
  46. {"Text file", "text/plain", false},
  47. {"Empty mime type", "", false},
  48. {"Audio with extra info", "audio/mpeg; charset=utf-8", true},
  49. }
  50. for _, tc := range testCases {
  51. t.Run(tc.name, func(t *testing.T) {
  52. enclosure := &Enclosure{MimeType: tc.mimeType}
  53. if got := enclosure.IsAudio(); got != tc.expected {
  54. t.Errorf("IsAudio() = %v, want %v for mime type %s", got, tc.expected, tc.mimeType)
  55. }
  56. })
  57. }
  58. }
  59. func TestEnclosure_IsVideo(t *testing.T) {
  60. testCases := []struct {
  61. name string
  62. mimeType string
  63. expected bool
  64. }{
  65. {"MP4 video", "video/mp4", true},
  66. {"AVI video", "video/avi", true},
  67. {"WebM video", "video/webm", true},
  68. {"M4V video", "video/m4v", true},
  69. {"Mixed case video", "Video/MP4", true},
  70. {"Audio file", "audio/mpeg", false},
  71. {"Image file", "image/jpeg", false},
  72. {"Text file", "text/plain", false},
  73. {"Empty mime type", "", false},
  74. {"Video with extra info", "video/mp4; codecs=\"avc1.42E01E\"", true},
  75. }
  76. for _, tc := range testCases {
  77. t.Run(tc.name, func(t *testing.T) {
  78. enclosure := &Enclosure{MimeType: tc.mimeType}
  79. if got := enclosure.IsVideo(); got != tc.expected {
  80. t.Errorf("IsVideo() = %v, want %v for mime type %s", got, tc.expected, tc.mimeType)
  81. }
  82. })
  83. }
  84. }
  85. func TestEnclosure_IsImage(t *testing.T) {
  86. testCases := []struct {
  87. name string
  88. mimeType string
  89. url string
  90. expected bool
  91. }{
  92. {"JPEG image by mime", "image/jpeg", "http://example.com/file", true},
  93. {"PNG image by mime", "image/png", "http://example.com/file", true},
  94. {"GIF image by mime", "image/gif", "http://example.com/file", true},
  95. {"Mixed case image mime", "Image/JPEG", "http://example.com/file", true},
  96. {"JPG file extension", "application/octet-stream", "http://example.com/photo.jpg", true},
  97. {"JPEG file extension", "text/plain", "http://example.com/photo.jpeg", true},
  98. {"PNG file extension", "unknown/type", "http://example.com/photo.png", true},
  99. {"GIF file extension", "binary/data", "http://example.com/photo.gif", true},
  100. {"Mixed case extension", "text/plain", "http://example.com/photo.JPG", true},
  101. {"Image mime and extension", "image/jpeg", "http://example.com/photo.jpg", true},
  102. {"Video file", "video/mp4", "http://example.com/video.mp4", false},
  103. {"Audio file", "audio/mpeg", "http://example.com/audio.mp3", false},
  104. {"Text file", "text/plain", "http://example.com/file.txt", false},
  105. {"No extension", "text/plain", "http://example.com/file", false},
  106. {"Other extension", "text/plain", "http://example.com/file.pdf", false},
  107. {"Empty values", "", "", false},
  108. }
  109. for _, tc := range testCases {
  110. t.Run(tc.name, func(t *testing.T) {
  111. enclosure := &Enclosure{MimeType: tc.mimeType, URL: tc.url}
  112. if got := enclosure.IsImage(); got != tc.expected {
  113. t.Errorf("IsImage() = %v, want %v for mime type %s and URL %s", got, tc.expected, tc.mimeType, tc.url)
  114. }
  115. })
  116. }
  117. }
  118. func TestEnclosureList_FindMediaPlayerEnclosure(t *testing.T) {
  119. testCases := []struct {
  120. name string
  121. enclosures EnclosureList
  122. expectedNil bool
  123. }{
  124. {
  125. name: "Returns first audio enclosure",
  126. enclosures: EnclosureList{
  127. &Enclosure{URL: "http://example.com/audio.mp3", MimeType: "audio/mpeg"},
  128. &Enclosure{URL: "http://example.com/video.mp4", MimeType: "video/mp4"},
  129. },
  130. expectedNil: false,
  131. },
  132. {
  133. name: "Returns first video enclosure",
  134. enclosures: EnclosureList{
  135. &Enclosure{URL: "http://example.com/video.mp4", MimeType: "video/mp4"},
  136. &Enclosure{URL: "http://example.com/audio.mp3", MimeType: "audio/mpeg"},
  137. },
  138. expectedNil: false,
  139. },
  140. {
  141. name: "Skips image enclosure and returns audio",
  142. enclosures: EnclosureList{
  143. &Enclosure{URL: "http://example.com/image.jpg", MimeType: "image/jpeg"},
  144. &Enclosure{URL: "http://example.com/audio.mp3", MimeType: "audio/mpeg"},
  145. },
  146. expectedNil: false,
  147. },
  148. {
  149. name: "Skips enclosure with empty URL",
  150. enclosures: EnclosureList{
  151. &Enclosure{URL: "", MimeType: "audio/mpeg"},
  152. &Enclosure{URL: "http://example.com/audio.mp3", MimeType: "audio/mpeg"},
  153. },
  154. expectedNil: false,
  155. },
  156. {
  157. name: "Returns nil for no media enclosures",
  158. enclosures: EnclosureList{
  159. &Enclosure{URL: "http://example.com/image.jpg", MimeType: "image/jpeg"},
  160. &Enclosure{URL: "http://example.com/doc.pdf", MimeType: "application/pdf"},
  161. },
  162. expectedNil: true,
  163. },
  164. {
  165. name: "Returns nil for empty list",
  166. enclosures: EnclosureList{},
  167. expectedNil: true,
  168. },
  169. {
  170. name: "Returns nil for all empty URLs",
  171. enclosures: EnclosureList{
  172. &Enclosure{URL: "", MimeType: "audio/mpeg"},
  173. &Enclosure{URL: "", MimeType: "video/mp4"},
  174. },
  175. expectedNil: true,
  176. },
  177. }
  178. for _, tc := range testCases {
  179. t.Run(tc.name, func(t *testing.T) {
  180. result := tc.enclosures.FindMediaPlayerEnclosure()
  181. if tc.expectedNil {
  182. if result != nil {
  183. t.Errorf("FindMediaPlayerEnclosure() = %v, want nil", result)
  184. }
  185. } else {
  186. if result == nil {
  187. t.Errorf("FindMediaPlayerEnclosure() = nil, want non-nil")
  188. } else if !result.IsAudio() && !result.IsVideo() {
  189. t.Errorf("FindMediaPlayerEnclosure() returned non-media enclosure: %s", result.MimeType)
  190. }
  191. }
  192. })
  193. }
  194. }
  195. func TestEnclosureList_ContainsAudioOrVideo(t *testing.T) {
  196. testCases := []struct {
  197. name string
  198. enclosures EnclosureList
  199. expected bool
  200. }{
  201. {
  202. name: "Contains audio",
  203. enclosures: EnclosureList{
  204. &Enclosure{MimeType: "audio/mpeg"},
  205. &Enclosure{MimeType: "image/jpeg"},
  206. },
  207. expected: true,
  208. },
  209. {
  210. name: "Contains video",
  211. enclosures: EnclosureList{
  212. &Enclosure{MimeType: "image/jpeg"},
  213. &Enclosure{MimeType: "video/mp4"},
  214. },
  215. expected: true,
  216. },
  217. {
  218. name: "Contains both audio and video",
  219. enclosures: EnclosureList{
  220. &Enclosure{MimeType: "audio/mpeg"},
  221. &Enclosure{MimeType: "video/mp4"},
  222. },
  223. expected: true,
  224. },
  225. {
  226. name: "Contains only images",
  227. enclosures: EnclosureList{
  228. &Enclosure{MimeType: "image/jpeg"},
  229. &Enclosure{MimeType: "image/png"},
  230. },
  231. expected: false,
  232. },
  233. {
  234. name: "Contains only documents",
  235. enclosures: EnclosureList{
  236. &Enclosure{MimeType: "application/pdf"},
  237. &Enclosure{MimeType: "text/plain"},
  238. },
  239. expected: false,
  240. },
  241. {
  242. name: "Empty list",
  243. enclosures: EnclosureList{},
  244. expected: false,
  245. },
  246. {
  247. name: "Single audio enclosure",
  248. enclosures: EnclosureList{
  249. &Enclosure{MimeType: "audio/wav"},
  250. },
  251. expected: true,
  252. },
  253. {
  254. name: "Single video enclosure",
  255. enclosures: EnclosureList{
  256. &Enclosure{MimeType: "video/webm"},
  257. },
  258. expected: true,
  259. },
  260. }
  261. for _, tc := range testCases {
  262. t.Run(tc.name, func(t *testing.T) {
  263. result := tc.enclosures.ContainsAudioOrVideo()
  264. if result != tc.expected {
  265. t.Errorf("ContainsAudioOrVideo() = %v, want %v", result, tc.expected)
  266. }
  267. })
  268. }
  269. }
  270. func TestEnclosure_ProxifyEnclosureURL(t *testing.T) {
  271. // Initialize config for testing
  272. os.Clearenv()
  273. os.Setenv("BASE_URL", "http://localhost")
  274. os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test-private-key")
  275. var err error
  276. parser := config.NewConfigParser()
  277. config.Opts, err = parser.ParseEnvironmentVariables()
  278. if err != nil {
  279. t.Fatalf(`Config parsing failure: %v`, err)
  280. }
  281. router := mux.NewRouter()
  282. router.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
  283. testCases := []struct {
  284. name string
  285. url string
  286. mimeType string
  287. mediaProxyOption string
  288. mediaProxyResourceTypes []string
  289. expectedURLChanged bool
  290. }{
  291. {
  292. name: "HTTP URL with audio type - proxy mode all",
  293. url: "http://example.com/audio.mp3",
  294. mimeType: "audio/mpeg",
  295. mediaProxyOption: "all",
  296. mediaProxyResourceTypes: []string{"audio", "video"},
  297. expectedURLChanged: true,
  298. },
  299. {
  300. name: "HTTPS URL with video type - proxy mode all",
  301. url: "https://example.com/video.mp4",
  302. mimeType: "video/mp4",
  303. mediaProxyOption: "all",
  304. mediaProxyResourceTypes: []string{"audio", "video"},
  305. expectedURLChanged: true,
  306. },
  307. {
  308. name: "HTTP URL with video type - proxy mode http-only",
  309. url: "http://example.com/video.mp4",
  310. mimeType: "video/mp4",
  311. mediaProxyOption: "http-only",
  312. mediaProxyResourceTypes: []string{"audio", "video"},
  313. expectedURLChanged: true,
  314. },
  315. {
  316. name: "HTTPS URL with video type - proxy mode http-only",
  317. url: "https://example.com/video.mp4",
  318. mimeType: "video/mp4",
  319. mediaProxyOption: "http-only",
  320. mediaProxyResourceTypes: []string{"audio", "video"},
  321. expectedURLChanged: false,
  322. },
  323. {
  324. name: "HTTP URL with image type - not in resource types",
  325. url: "http://example.com/image.jpg",
  326. mimeType: "image/jpeg",
  327. mediaProxyOption: "all",
  328. mediaProxyResourceTypes: []string{"audio", "video"},
  329. expectedURLChanged: false,
  330. },
  331. {
  332. name: "HTTP URL with image type - in resource types",
  333. url: "http://example.com/image.jpg",
  334. mimeType: "image/jpeg",
  335. mediaProxyOption: "all",
  336. mediaProxyResourceTypes: []string{"audio", "video", "image"},
  337. expectedURLChanged: true,
  338. },
  339. {
  340. name: "HTTP URL - proxy mode none",
  341. url: "http://example.com/audio.mp3",
  342. mimeType: "audio/mpeg",
  343. mediaProxyOption: "none",
  344. mediaProxyResourceTypes: []string{"audio", "video"},
  345. expectedURLChanged: false,
  346. },
  347. {
  348. name: "Empty URL",
  349. url: "",
  350. mimeType: "audio/mpeg",
  351. mediaProxyOption: "all",
  352. mediaProxyResourceTypes: []string{"audio", "video"},
  353. expectedURLChanged: false,
  354. },
  355. {
  356. name: "Non-media MIME type",
  357. url: "http://example.com/doc.pdf",
  358. mimeType: "application/pdf",
  359. mediaProxyOption: "all",
  360. mediaProxyResourceTypes: []string{"audio", "video"},
  361. expectedURLChanged: false,
  362. },
  363. }
  364. for _, tc := range testCases {
  365. t.Run(tc.name, func(t *testing.T) {
  366. enclosure := &Enclosure{
  367. URL: tc.url,
  368. MimeType: tc.mimeType,
  369. }
  370. originalURL := enclosure.URL
  371. // Call the method
  372. enclosure.ProxifyEnclosureURL(router, tc.mediaProxyOption, tc.mediaProxyResourceTypes)
  373. // Check if URL changed as expected
  374. urlChanged := enclosure.URL != originalURL
  375. if urlChanged != tc.expectedURLChanged {
  376. t.Errorf("ProxifyEnclosureURL() URL changed = %v, want %v. Original: %s, New: %s",
  377. urlChanged, tc.expectedURLChanged, originalURL, enclosure.URL)
  378. }
  379. // If URL should have changed, verify it's not empty
  380. if tc.expectedURLChanged && enclosure.URL == "" {
  381. t.Error("ProxifyEnclosureURL() resulted in empty URL when proxification was expected")
  382. }
  383. // If URL shouldn't have changed, verify it's identical
  384. if !tc.expectedURLChanged && enclosure.URL != originalURL {
  385. t.Errorf("ProxifyEnclosureURL() URL changed unexpectedly from %s to %s", originalURL, enclosure.URL)
  386. }
  387. })
  388. }
  389. }
  390. func TestEnclosureList_ProxifyEnclosureURL(t *testing.T) {
  391. // Initialize config for testing
  392. os.Clearenv()
  393. os.Setenv("BASE_URL", "http://localhost")
  394. os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test-private-key")
  395. var err error
  396. parser := config.NewConfigParser()
  397. config.Opts, err = parser.ParseEnvironmentVariables()
  398. if err != nil {
  399. t.Fatalf(`Config parsing failure: %v`, err)
  400. }
  401. router := mux.NewRouter()
  402. router.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
  403. testCases := []struct {
  404. name string
  405. enclosures EnclosureList
  406. mediaProxyOption string
  407. mediaProxyResourceTypes []string
  408. expectedChangedCount int
  409. }{
  410. {
  411. name: "Mixed enclosures with all proxy mode",
  412. enclosures: EnclosureList{
  413. &Enclosure{URL: "http://example.com/audio.mp3", MimeType: "audio/mpeg"},
  414. &Enclosure{URL: "https://example.com/video.mp4", MimeType: "video/mp4"},
  415. &Enclosure{URL: "http://example.com/image.jpg", MimeType: "image/jpeg"},
  416. &Enclosure{URL: "http://example.com/doc.pdf", MimeType: "application/pdf"},
  417. },
  418. mediaProxyOption: "all",
  419. mediaProxyResourceTypes: []string{"audio", "video"},
  420. expectedChangedCount: 2, // audio and video should be proxified
  421. },
  422. {
  423. name: "Mixed enclosures with http-only proxy mode",
  424. enclosures: EnclosureList{
  425. &Enclosure{URL: "http://example.com/audio.mp3", MimeType: "audio/mpeg"},
  426. &Enclosure{URL: "https://example.com/video.mp4", MimeType: "video/mp4"},
  427. &Enclosure{URL: "http://example.com/video2.mp4", MimeType: "video/mp4"},
  428. },
  429. mediaProxyOption: "http-only",
  430. mediaProxyResourceTypes: []string{"audio", "video"},
  431. expectedChangedCount: 2, // only HTTP URLs should be proxified
  432. },
  433. {
  434. name: "No media types in resource list",
  435. enclosures: EnclosureList{
  436. &Enclosure{URL: "http://example.com/audio.mp3", MimeType: "audio/mpeg"},
  437. &Enclosure{URL: "http://example.com/video.mp4", MimeType: "video/mp4"},
  438. },
  439. mediaProxyOption: "all",
  440. mediaProxyResourceTypes: []string{"image"},
  441. expectedChangedCount: 0, // no matching resource types
  442. },
  443. {
  444. name: "Proxy mode none",
  445. enclosures: EnclosureList{
  446. &Enclosure{URL: "http://example.com/audio.mp3", MimeType: "audio/mpeg"},
  447. &Enclosure{URL: "http://example.com/video.mp4", MimeType: "video/mp4"},
  448. },
  449. mediaProxyOption: "none",
  450. mediaProxyResourceTypes: []string{"audio", "video"},
  451. expectedChangedCount: 0,
  452. },
  453. {
  454. name: "Empty enclosure list",
  455. enclosures: EnclosureList{},
  456. mediaProxyOption: "all",
  457. mediaProxyResourceTypes: []string{"audio", "video"},
  458. expectedChangedCount: 0,
  459. },
  460. {
  461. name: "Enclosures with empty URLs",
  462. enclosures: EnclosureList{
  463. &Enclosure{URL: "", MimeType: "audio/mpeg"},
  464. &Enclosure{URL: "http://example.com/video.mp4", MimeType: "video/mp4"},
  465. },
  466. mediaProxyOption: "all",
  467. mediaProxyResourceTypes: []string{"audio", "video"},
  468. expectedChangedCount: 1, // only the non-empty URL should be processed
  469. },
  470. }
  471. for _, tc := range testCases {
  472. t.Run(tc.name, func(t *testing.T) {
  473. // Store original URLs
  474. originalURLs := make([]string, len(tc.enclosures))
  475. for i, enclosure := range tc.enclosures {
  476. originalURLs[i] = enclosure.URL
  477. }
  478. // Call the method
  479. tc.enclosures.ProxifyEnclosureURL(router, tc.mediaProxyOption, tc.mediaProxyResourceTypes)
  480. // Count how many URLs actually changed
  481. changedCount := 0
  482. for i, enclosure := range tc.enclosures {
  483. if enclosure.URL != originalURLs[i] {
  484. changedCount++
  485. // Verify that changed URLs are not empty (unless they were empty originally)
  486. if originalURLs[i] != "" && enclosure.URL == "" {
  487. t.Errorf("Enclosure %d: ProxifyEnclosureURL resulted in empty URL", i)
  488. }
  489. }
  490. }
  491. if changedCount != tc.expectedChangedCount {
  492. t.Errorf("ProxifyEnclosureURL() changed %d URLs, want %d", changedCount, tc.expectedChangedCount)
  493. }
  494. })
  495. }
  496. }
  497. func TestEnclosure_ProxifyEnclosureURL_EdgeCases(t *testing.T) {
  498. // Initialize config for testing
  499. os.Clearenv()
  500. os.Setenv("BASE_URL", "http://localhost")
  501. os.Setenv("MEDIA_PROXY_PRIVATE_KEY", "test-private-key")
  502. var err error
  503. parser := config.NewConfigParser()
  504. config.Opts, err = parser.ParseEnvironmentVariables()
  505. if err != nil {
  506. t.Fatalf(`Config parsing failure: %v`, err)
  507. }
  508. router := mux.NewRouter()
  509. router.HandleFunc("/proxy/{encodedDigest}/{encodedURL}", func(w http.ResponseWriter, r *http.Request) {}).Name("proxy")
  510. t.Run("Empty resource types slice", func(t *testing.T) {
  511. enclosure := &Enclosure{
  512. URL: "http://example.com/audio.mp3",
  513. MimeType: "audio/mpeg",
  514. }
  515. originalURL := enclosure.URL
  516. enclosure.ProxifyEnclosureURL(router, "all", []string{})
  517. // With empty resource types, URL should not change
  518. if enclosure.URL != originalURL {
  519. t.Errorf("URL should not change with empty resource types. Original: %s, New: %s", originalURL, enclosure.URL)
  520. }
  521. })
  522. t.Run("Nil resource types slice", func(t *testing.T) {
  523. enclosure := &Enclosure{
  524. URL: "http://example.com/audio.mp3",
  525. MimeType: "audio/mpeg",
  526. }
  527. originalURL := enclosure.URL
  528. enclosure.ProxifyEnclosureURL(router, "all", nil)
  529. // With nil resource types, URL should not change
  530. if enclosure.URL != originalURL {
  531. t.Errorf("URL should not change with nil resource types. Original: %s, New: %s", originalURL, enclosure.URL)
  532. }
  533. })
  534. t.Run("Invalid proxy mode", func(t *testing.T) {
  535. enclosure := &Enclosure{
  536. URL: "http://example.com/audio.mp3",
  537. MimeType: "audio/mpeg",
  538. }
  539. originalURL := enclosure.URL
  540. enclosure.ProxifyEnclosureURL(router, "invalid-mode", []string{"audio"})
  541. // With invalid proxy mode, the function still proxifies non-HTTPS URLs
  542. // because shouldProxifyURL defaults to checking URL scheme
  543. if enclosure.URL == originalURL {
  544. t.Errorf("URL should change for HTTP URL even with invalid proxy mode. Original: %s, New: %s", originalURL, enclosure.URL)
  545. }
  546. })
  547. }