response_handler_test.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. // SPDX-FileCopyrightText: Copyright The Miniflux Authors. All rights reserved.
  2. // SPDX-License-Identifier: Apache-2.0
  3. package fetcher // import "miniflux.app/v2/internal/reader/fetcher"
  4. import (
  5. "net/http"
  6. "testing"
  7. "time"
  8. )
  9. func TestIsModified(t *testing.T) {
  10. var cachedEtag = "abc123"
  11. var cachedLastModified = "Wed, 21 Oct 2015 07:28:00 GMT"
  12. var testCases = map[string]struct {
  13. Status int
  14. LastModified string
  15. ETag string
  16. IsModified bool
  17. }{
  18. "Unmodified 304": {
  19. Status: 304,
  20. LastModified: cachedLastModified,
  21. ETag: cachedEtag,
  22. IsModified: false,
  23. },
  24. "Unmodified 200": {
  25. Status: 200,
  26. LastModified: cachedLastModified,
  27. ETag: cachedEtag,
  28. IsModified: false,
  29. },
  30. // ETag takes precedence per RFC9110 8.8.1.
  31. "Last-Modified changed only": {
  32. Status: 200,
  33. LastModified: "Thu, 22 Oct 2015 07:28:00 GMT",
  34. ETag: cachedEtag,
  35. IsModified: false,
  36. },
  37. "ETag changed only": {
  38. Status: 200,
  39. LastModified: cachedLastModified,
  40. ETag: "xyz789",
  41. IsModified: true,
  42. },
  43. "ETag and Last-Modified changed": {
  44. Status: 200,
  45. LastModified: "Thu, 22 Oct 2015 07:28:00 GMT",
  46. ETag: "xyz789",
  47. IsModified: true,
  48. },
  49. }
  50. for name, tc := range testCases {
  51. t.Run(name, func(tt *testing.T) {
  52. header := http.Header{}
  53. header.Add("Last-Modified", tc.LastModified)
  54. header.Add("ETag", tc.ETag)
  55. rh := ResponseHandler{
  56. httpResponse: &http.Response{
  57. StatusCode: tc.Status,
  58. Header: header,
  59. },
  60. }
  61. if tc.IsModified != rh.IsModified(cachedEtag, cachedLastModified) {
  62. tt.Error(name)
  63. }
  64. })
  65. }
  66. }
  67. func TestRetryDelay(t *testing.T) {
  68. var testCases = map[string]struct {
  69. RetryAfterHeader string
  70. ExpectedDelay time.Duration
  71. }{
  72. "Empty header": {
  73. RetryAfterHeader: "",
  74. ExpectedDelay: 0,
  75. },
  76. "Integer value": {
  77. RetryAfterHeader: "42",
  78. ExpectedDelay: 42 * time.Second,
  79. },
  80. "HTTP-date": {
  81. RetryAfterHeader: time.Now().Add(42 * time.Second).Format(time.RFC1123),
  82. ExpectedDelay: 41 * time.Second,
  83. },
  84. }
  85. for name, tc := range testCases {
  86. t.Run(name, func(tt *testing.T) {
  87. header := http.Header{}
  88. header.Add("Retry-After", tc.RetryAfterHeader)
  89. rh := ResponseHandler{
  90. httpResponse: &http.Response{
  91. Header: header,
  92. },
  93. }
  94. if tc.ExpectedDelay != rh.ParseRetryDelay() {
  95. tt.Errorf("Expected %d, got %d for scenario %q", tc.ExpectedDelay, rh.ParseRetryDelay(), name)
  96. }
  97. })
  98. }
  99. }
  100. func TestExpiresInMinutes(t *testing.T) {
  101. var testCases = map[string]struct {
  102. ExpiresHeader string
  103. Expected time.Duration
  104. }{
  105. "Empty header": {
  106. ExpiresHeader: "",
  107. Expected: 0,
  108. },
  109. "Valid Expires header": {
  110. ExpiresHeader: time.Now().Add(10 * time.Minute).Format(time.RFC1123),
  111. Expected: 10 * time.Minute,
  112. },
  113. "Invalid Expires header": {
  114. ExpiresHeader: "invalid-date",
  115. Expected: 0,
  116. },
  117. }
  118. for name, tc := range testCases {
  119. t.Run(name, func(tt *testing.T) {
  120. header := http.Header{}
  121. header.Add("Expires", tc.ExpiresHeader)
  122. rh := ResponseHandler{
  123. httpResponse: &http.Response{
  124. Header: header,
  125. },
  126. }
  127. if tc.Expected != rh.Expires() {
  128. t.Errorf("Expected %d, got %d for scenario %q", tc.Expected, rh.Expires(), name)
  129. }
  130. })
  131. }
  132. }
  133. func TestCacheControlMaxAgeInMinutes(t *testing.T) {
  134. var testCases = map[string]struct {
  135. CacheControlHeader string
  136. Expected time.Duration
  137. }{
  138. "Empty header": {
  139. CacheControlHeader: "",
  140. Expected: 0,
  141. },
  142. "Valid max-age": {
  143. CacheControlHeader: "max-age=600",
  144. Expected: 10 * time.Minute,
  145. },
  146. "Invalid max-age": {
  147. CacheControlHeader: "max-age=invalid",
  148. Expected: 0,
  149. },
  150. "Multiple directives": {
  151. CacheControlHeader: "no-cache, max-age=300",
  152. Expected: 5 * time.Minute,
  153. },
  154. }
  155. for name, tc := range testCases {
  156. t.Run(name, func(tt *testing.T) {
  157. header := http.Header{}
  158. header.Add("Cache-Control", tc.CacheControlHeader)
  159. rh := ResponseHandler{
  160. httpResponse: &http.Response{
  161. Header: header,
  162. },
  163. }
  164. if tc.Expected != rh.CacheControlMaxAge() {
  165. t.Errorf("Expected %d, got %d for scenario %q", tc.Expected, rh.CacheControlMaxAge(), name)
  166. }
  167. })
  168. }
  169. }