cloudflare_status_page_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. package origin
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/cloudflare/golibs/lrucache"
  6. "github.com/stretchr/testify/assert"
  7. )
  8. func TestParseStatusPage(t *testing.T) {
  9. testCases := []struct {
  10. input []byte
  11. output *StatusPage
  12. fail bool
  13. }{
  14. {
  15. input: []byte(`<html>
  16. <head><title>504 Gateway Time-out</title></head>
  17. <body><center><h1>504 Gateway Time-out</h1></center></body>
  18. </html>`),
  19. output: nil,
  20. fail: true,
  21. },
  22. {
  23. input: []byte(`{
  24. "page": {
  25. "id": "yh6f0r4529hb",
  26. "name": "Cloudflare",
  27. "url": "https://www.cloudflarestatus.com",
  28. "time_zone": "Etc/UTC",
  29. "updated_at": "2019-01-10T20:11:38.750Z"
  30. },
  31. "incidents": [
  32. {
  33. "name": "Cloudflare API service issues",
  34. "status": "resolved",
  35. "created_at": "2018-09-17T19:29:21.132Z",
  36. "updated_at": "2018-09-18T07:45:41.313Z",
  37. "monitoring_at": "2018-09-17T21:35:06.492Z",
  38. "resolved_at": "2018-09-18T07:45:41.290Z",
  39. "shortlink": "http://stspg.io/7f079791e",
  40. "id": "q746ybtyb6q0",
  41. "page_id": "yh6f0r4529hb",
  42. "incident_updates": [
  43. {
  44. "status": "resolved",
  45. "body": "Cloudflare has resolved the issue and the service have resumed normal operation.",
  46. "created_at": "2018-09-18T07:45:41.290Z",
  47. "updated_at": "2018-09-18T07:45:41.290Z",
  48. "display_at": "2018-09-18T07:45:41.290Z",
  49. "affected_components": [
  50. {
  51. "code": "g4tb35rs9yw7",
  52. "name": "Cloudflare customer dashboard and APIs - Cloudflare APIs",
  53. "old_status": "operational",
  54. "new_status": "operational"
  55. }
  56. ],
  57. "deliver_notifications": true,
  58. "tweet_id": null,
  59. "id": "zl5g2pl5zhfs",
  60. "incident_id": "q746ybtyb6q0",
  61. "custom_tweet": null
  62. },
  63. {
  64. "status": "monitoring",
  65. "body": "Cloudflare has implemented a fix for this issue and is currently monitoring the results.\r\n\r\nWe will update the status once the issue is resolved.",
  66. "created_at": "2018-09-17T21:35:06.492Z",
  67. "updated_at": "2018-09-17T21:35:06.492Z",
  68. "display_at": "2018-09-17T21:35:06.492Z",
  69. "affected_components": [
  70. {
  71. "code": "g4tb35rs9yw7",
  72. "name": "Cloudflare customer dashboard and APIs - Cloudflare APIs",
  73. "old_status": "degraded_performance",
  74. "new_status": "operational"
  75. }
  76. ],
  77. "deliver_notifications": false,
  78. "tweet_id": null,
  79. "id": "0001sv3chdnx",
  80. "incident_id": "q746ybtyb6q0",
  81. "custom_tweet": null
  82. },
  83. {
  84. "status": "investigating",
  85. "body": "We are continuing to investigate this issue.",
  86. "created_at": "2018-09-17T19:30:08.049Z",
  87. "updated_at": "2018-09-17T19:30:08.049Z",
  88. "display_at": "2018-09-17T19:30:08.049Z",
  89. "affected_components": [
  90. {
  91. "code": "g4tb35rs9yw7",
  92. "name": "Cloudflare customer dashboard and APIs - Cloudflare APIs",
  93. "old_status": "operational",
  94. "new_status": "degraded_performance"
  95. }
  96. ],
  97. "deliver_notifications": false,
  98. "tweet_id": null,
  99. "id": "qdr164tfpq7m",
  100. "incident_id": "q746ybtyb6q0",
  101. "custom_tweet": null
  102. },
  103. {
  104. "status": "investigating",
  105. "body": "Cloudflare is investigating issues with APIs and Page Rule delays for Page Rule updates. Cloudflare Page Rule service delivery is unaffected and is operating normally. Also, these issues do not affect the Cloudflare CDN and therefore, do not impact customer websites.",
  106. "created_at": "2018-09-17T19:29:21.201Z",
  107. "updated_at": "2018-09-17T19:29:21.201Z",
  108. "display_at": "2018-09-17T19:29:21.201Z",
  109. "affected_components": [
  110. {
  111. "code": "g4tb35rs9yw7",
  112. "name": "Cloudflare customer dashboard and APIs - Cloudflare APIs",
  113. "old_status": "operational",
  114. "new_status": "operational"
  115. }
  116. ],
  117. "deliver_notifications": false,
  118. "tweet_id": null,
  119. "id": "qzl2n0q8tskg",
  120. "incident_id": "q746ybtyb6q0",
  121. "custom_tweet": null
  122. }
  123. ],
  124. "components": [
  125. {
  126. "status": "operational",
  127. "name": "Cloudflare APIs",
  128. "created_at": "2014-10-09T03:32:07.158Z",
  129. "updated_at": "2019-01-01T22:58:30.846Z",
  130. "position": 2,
  131. "description": null,
  132. "showcase": false,
  133. "id": "g4tb35rs9yw7",
  134. "page_id": "yh6f0r4529hb",
  135. "group_id": "1km35smx8p41",
  136. "group": false,
  137. "only_show_if_degraded": false
  138. }
  139. ],
  140. "impact": "minor"
  141. },
  142. {
  143. "name": "Web Analytics Delays",
  144. "status": "resolved",
  145. "created_at": "2018-09-17T18:05:39.907Z",
  146. "updated_at": "2018-09-17T22:53:05.078Z",
  147. "monitoring_at": null,
  148. "resolved_at": "2018-09-17T22:53:05.057Z",
  149. "shortlink": "http://stspg.io/cb208928c",
  150. "id": "wqfk9mzs5qt1",
  151. "page_id": "yh6f0r4529hb",
  152. "incident_updates": [
  153. {
  154. "status": "resolved",
  155. "body": "Cloudflare has resolved the issue and Web Analytics have resumed normal operation.",
  156. "created_at": "2018-09-17T22:53:05.057Z",
  157. "updated_at": "2018-09-17T22:53:05.057Z",
  158. "display_at": "2018-09-17T22:53:05.057Z",
  159. "affected_components": [
  160. {
  161. "code": "4c231tkdlpcl",
  162. "name": "Cloudflare customer dashboard and APIs - Analytics",
  163. "old_status": "degraded_performance",
  164. "new_status": "operational"
  165. }
  166. ],
  167. "deliver_notifications": false,
  168. "tweet_id": null,
  169. "id": "93y1w00yqzk4",
  170. "incident_id": "wqfk9mzs5qt1",
  171. "custom_tweet": null
  172. },
  173. {
  174. "status": "investigating",
  175. "body": "There is a delay in processing Cloudflare Web Analytics. This affects timely delivery of customer data.\n\nThese delays do not impact analytics for DNS and Rate Limiting.",
  176. "created_at": "2018-09-17T18:05:40.033Z",
  177. "updated_at": "2018-09-17T18:05:40.033Z",
  178. "display_at": "2018-09-17T18:05:40.033Z",
  179. "affected_components": [
  180. {
  181. "code": "4c231tkdlpcl",
  182. "name": "Cloudflare customer dashboard and APIs - Analytics",
  183. "old_status": "operational",
  184. "new_status": "degraded_performance"
  185. }
  186. ],
  187. "deliver_notifications": false,
  188. "tweet_id": null,
  189. "id": "362t6lv0vrpk",
  190. "incident_id": "wqfk9mzs5qt1",
  191. "custom_tweet": null
  192. }
  193. ],
  194. "components": [
  195. {
  196. "status": "operational",
  197. "name": "Analytics",
  198. "created_at": "2014-11-13T11:54:10.191Z",
  199. "updated_at": "2018-12-31T08:20:52.349Z",
  200. "position": 3,
  201. "description": "Customer data",
  202. "showcase": false,
  203. "id": "4c231tkdlpcl",
  204. "page_id": "yh6f0r4529hb",
  205. "group_id": "1km35smx8p41",
  206. "group": false,
  207. "only_show_if_degraded": false
  208. }
  209. ],
  210. "impact": "minor"
  211. }
  212. ]
  213. }`),
  214. output: &StatusPage{
  215. Incidents: []Incident{
  216. Incident{
  217. Name: "Cloudflare API service issues",
  218. ID: "q746ybtyb6q0",
  219. Updates: []IncidentUpdate{
  220. IncidentUpdate{
  221. Body: "Cloudflare has resolved the issue and the service have resumed normal operation.",
  222. },
  223. IncidentUpdate{
  224. Body: "Cloudflare has implemented a fix for this issue and is currently monitoring the results.\r\n\r\nWe will update the status once the issue is resolved.",
  225. },
  226. IncidentUpdate{
  227. Body: "We are continuing to investigate this issue.",
  228. },
  229. IncidentUpdate{
  230. Body: "Cloudflare is investigating issues with APIs and Page Rule delays for Page Rule updates. Cloudflare Page Rule service delivery is unaffected and is operating normally. Also, these issues do not affect the Cloudflare CDN and therefore, do not impact customer websites.",
  231. },
  232. },
  233. },
  234. Incident{
  235. Name: "Web Analytics Delays",
  236. ID: "wqfk9mzs5qt1",
  237. Updates: []IncidentUpdate{
  238. IncidentUpdate{
  239. Body: "Cloudflare has resolved the issue and Web Analytics have resumed normal operation.",
  240. },
  241. IncidentUpdate{
  242. Body: "There is a delay in processing Cloudflare Web Analytics. This affects timely delivery of customer data.\n\nThese delays do not impact analytics for DNS and Rate Limiting.",
  243. },
  244. },
  245. },
  246. },
  247. },
  248. fail: false,
  249. },
  250. }
  251. for _, testCase := range testCases {
  252. output, err := parseStatusPage(testCase.input)
  253. if testCase.fail {
  254. assert.Error(t, err)
  255. } else {
  256. assert.Nil(t, err)
  257. assert.Equal(t, testCase.output, output)
  258. }
  259. }
  260. }
  261. func TestIsArgoTunnelIncident(t *testing.T) {
  262. testCases := []struct {
  263. input Incident
  264. output bool
  265. }{
  266. {
  267. input: Incident{},
  268. output: false,
  269. },
  270. {
  271. input: Incident{Name: "An Argo Tunnel incident"},
  272. output: true,
  273. },
  274. {
  275. input: Incident{Name: "an argo tunnel incident"},
  276. output: true,
  277. },
  278. {
  279. input: Incident{Name: "an aRgO TuNnEl incident"},
  280. output: true,
  281. },
  282. {
  283. input: Incident{Name: "an argotunnel incident"},
  284. output: false,
  285. },
  286. {
  287. input: Incident{Name: "irrelevant"},
  288. output: false,
  289. },
  290. {
  291. input: Incident{
  292. Name: "irrelevant",
  293. Updates: []IncidentUpdate{
  294. IncidentUpdate{Body: "irrelevant"},
  295. IncidentUpdate{Body: "an Argo Tunnel incident"},
  296. IncidentUpdate{Body: "irrelevant"},
  297. },
  298. },
  299. output: true,
  300. },
  301. {
  302. input: Incident{
  303. Name: "an Argo Tunnel incident",
  304. Updates: []IncidentUpdate{
  305. IncidentUpdate{Body: "irrelevant"},
  306. IncidentUpdate{Body: "irrelevant"},
  307. IncidentUpdate{Body: "irrelevant"},
  308. },
  309. },
  310. output: true,
  311. },
  312. }
  313. for _, testCase := range testCases {
  314. actual := isArgoTunnelIncident(testCase.input)
  315. assert.Equal(t, testCase.output, actual, "Test case failed: %v", testCase.input)
  316. }
  317. }
  318. func TestIncidentURL(t *testing.T) {
  319. incident := Incident{
  320. ID: "s6k0dnn5347b",
  321. }
  322. assert.Equal(t, "https://www.cloudflarestatus.com/incidents/s6k0dnn5347b", incident.URL())
  323. }
  324. func TestNewCachedIncidentLookup(t *testing.T) {
  325. c := newCachedIncidentLookup(func() []Incident { return nil })
  326. assert.Equal(t, time.Minute, c.ttl)
  327. assert.Equal(t, 1, c.cache.Capacity())
  328. }
  329. func TestCachedIncidentLookup(t *testing.T) {
  330. expected := []Incident{
  331. Incident{
  332. Name: "An incident",
  333. ID: "incidentID",
  334. },
  335. }
  336. var shouldCallUncachedLookup bool
  337. c := &cachedIncidentLookup{
  338. cache: lrucache.NewLRUCache(1),
  339. ttl: 50 * time.Millisecond,
  340. uncachedLookup: func() []Incident {
  341. if !shouldCallUncachedLookup {
  342. t.Fatal("uncachedLookup shouldn't have been called")
  343. }
  344. return expected
  345. },
  346. }
  347. shouldCallUncachedLookup = true
  348. assert.Equal(t, expected, c.ActiveIncidents())
  349. shouldCallUncachedLookup = false
  350. assert.Equal(t, expected, c.ActiveIncidents())
  351. assert.Equal(t, expected, c.ActiveIncidents())
  352. time.Sleep(50 * time.Millisecond)
  353. shouldCallUncachedLookup = true
  354. assert.Equal(t, expected, c.ActiveIncidents())
  355. }
  356. func TestCachedIncidentLookupDoesntPanic(t *testing.T) {
  357. expected := []Incident{
  358. Incident{
  359. Name: "An incident",
  360. ID: "incidentID",
  361. },
  362. }
  363. c := &cachedIncidentLookup{
  364. cache: lrucache.NewLRUCache(1),
  365. ttl: 50 * time.Millisecond,
  366. uncachedLookup: func() []Incident { return expected },
  367. }
  368. c.cache.Set(cacheKey, 42, time.Now().Add(30*time.Minute))
  369. actual := c.ActiveIncidents()
  370. assert.Equal(t, expected, actual)
  371. }