cloudflare_status_page_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  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. "automation_email": "component+g4tb35rs9yw7@notifications.statuspage.io"
  139. }
  140. ],
  141. "impact": "minor"
  142. },
  143. {
  144. "name": "Web Analytics Delays",
  145. "status": "resolved",
  146. "created_at": "2018-09-17T18:05:39.907Z",
  147. "updated_at": "2018-09-17T22:53:05.078Z",
  148. "monitoring_at": null,
  149. "resolved_at": "2018-09-17T22:53:05.057Z",
  150. "shortlink": "http://stspg.io/cb208928c",
  151. "id": "wqfk9mzs5qt1",
  152. "page_id": "yh6f0r4529hb",
  153. "incident_updates": [
  154. {
  155. "status": "resolved",
  156. "body": "Cloudflare has resolved the issue and Web Analytics have resumed normal operation.",
  157. "created_at": "2018-09-17T22:53:05.057Z",
  158. "updated_at": "2018-09-17T22:53:05.057Z",
  159. "display_at": "2018-09-17T22:53:05.057Z",
  160. "affected_components": [
  161. {
  162. "code": "4c231tkdlpcl",
  163. "name": "Cloudflare customer dashboard and APIs - Analytics",
  164. "old_status": "degraded_performance",
  165. "new_status": "operational"
  166. }
  167. ],
  168. "deliver_notifications": false,
  169. "tweet_id": null,
  170. "id": "93y1w00yqzk4",
  171. "incident_id": "wqfk9mzs5qt1",
  172. "custom_tweet": null
  173. },
  174. {
  175. "status": "investigating",
  176. "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.",
  177. "created_at": "2018-09-17T18:05:40.033Z",
  178. "updated_at": "2018-09-17T18:05:40.033Z",
  179. "display_at": "2018-09-17T18:05:40.033Z",
  180. "affected_components": [
  181. {
  182. "code": "4c231tkdlpcl",
  183. "name": "Cloudflare customer dashboard and APIs - Analytics",
  184. "old_status": "operational",
  185. "new_status": "degraded_performance"
  186. }
  187. ],
  188. "deliver_notifications": false,
  189. "tweet_id": null,
  190. "id": "362t6lv0vrpk",
  191. "incident_id": "wqfk9mzs5qt1",
  192. "custom_tweet": null
  193. }
  194. ],
  195. "components": [
  196. {
  197. "status": "operational",
  198. "name": "Analytics",
  199. "created_at": "2014-11-13T11:54:10.191Z",
  200. "updated_at": "2018-12-31T08:20:52.349Z",
  201. "position": 3,
  202. "description": "Customer data",
  203. "showcase": false,
  204. "id": "4c231tkdlpcl",
  205. "page_id": "yh6f0r4529hb",
  206. "group_id": "1km35smx8p41",
  207. "group": false,
  208. "only_show_if_degraded": false,
  209. "automation_email": "component+4c231tkdlpcl@notifications.statuspage.io"
  210. }
  211. ],
  212. "impact": "minor"
  213. }
  214. ]
  215. }`),
  216. output: &StatusPage{
  217. Incidents: []Incident{
  218. Incident{
  219. Name: "Cloudflare API service issues",
  220. ID: "q746ybtyb6q0",
  221. Updates: []IncidentUpdate{
  222. IncidentUpdate{
  223. Body: "Cloudflare has resolved the issue and the service have resumed normal operation.",
  224. },
  225. IncidentUpdate{
  226. 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.",
  227. },
  228. IncidentUpdate{
  229. Body: "We are continuing to investigate this issue.",
  230. },
  231. IncidentUpdate{
  232. 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.",
  233. },
  234. },
  235. },
  236. Incident{
  237. Name: "Web Analytics Delays",
  238. ID: "wqfk9mzs5qt1",
  239. Updates: []IncidentUpdate{
  240. IncidentUpdate{
  241. Body: "Cloudflare has resolved the issue and Web Analytics have resumed normal operation.",
  242. },
  243. IncidentUpdate{
  244. 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.",
  245. },
  246. },
  247. },
  248. },
  249. },
  250. fail: false,
  251. },
  252. }
  253. for _, testCase := range testCases {
  254. output, err := parseStatusPage(testCase.input)
  255. if testCase.fail {
  256. assert.Error(t, err)
  257. } else {
  258. assert.Nil(t, err)
  259. assert.Equal(t, testCase.output, output)
  260. }
  261. }
  262. }
  263. func TestIsArgoTunnelIncident(t *testing.T) {
  264. testCases := []struct {
  265. input Incident
  266. output bool
  267. }{
  268. {
  269. input: Incident{},
  270. output: false,
  271. },
  272. {
  273. input: Incident{Name: "An Argo Tunnel incident"},
  274. output: true,
  275. },
  276. {
  277. input: Incident{Name: "an argo tunnel incident"},
  278. output: true,
  279. },
  280. {
  281. input: Incident{Name: "an aRgO TuNnEl incident"},
  282. output: true,
  283. },
  284. {
  285. input: Incident{Name: "an argotunnel incident"},
  286. output: false,
  287. },
  288. {
  289. input: Incident{Name: "irrelevant"},
  290. output: false,
  291. },
  292. {
  293. input: Incident{
  294. Name: "irrelevant",
  295. Updates: []IncidentUpdate{
  296. IncidentUpdate{Body: "irrelevant"},
  297. IncidentUpdate{Body: "an Argo Tunnel incident"},
  298. IncidentUpdate{Body: "irrelevant"},
  299. },
  300. },
  301. output: true,
  302. },
  303. {
  304. input: Incident{
  305. Name: "an Argo Tunnel incident",
  306. Updates: []IncidentUpdate{
  307. IncidentUpdate{Body: "irrelevant"},
  308. IncidentUpdate{Body: "irrelevant"},
  309. IncidentUpdate{Body: "irrelevant"},
  310. },
  311. },
  312. output: true,
  313. },
  314. }
  315. for _, testCase := range testCases {
  316. actual := isArgoTunnelIncident(testCase.input)
  317. assert.Equal(t, testCase.output, actual, "Test case failed: %v", testCase.input)
  318. }
  319. }
  320. func TestIncidentURL(t *testing.T) {
  321. incident := Incident{
  322. ID: "s6k0dnn5347b",
  323. }
  324. assert.Equal(t, "https://www.cloudflarestatus.com/incidents/s6k0dnn5347b", incident.URL())
  325. }
  326. func TestNewCachedIncidentLookup(t *testing.T) {
  327. c := newCachedIncidentLookup(func() []Incident { return nil })
  328. assert.Equal(t, time.Minute, c.ttl)
  329. assert.Equal(t, 1, c.cache.Capacity())
  330. }
  331. func TestCachedIncidentLookup(t *testing.T) {
  332. expected := []Incident{
  333. Incident{
  334. Name: "An incident",
  335. ID: "incidentID",
  336. },
  337. }
  338. var shouldCallUncachedLookup bool
  339. c := &cachedIncidentLookup{
  340. cache: lrucache.NewLRUCache(1),
  341. ttl: 50 * time.Millisecond,
  342. uncachedLookup: func() []Incident {
  343. if !shouldCallUncachedLookup {
  344. t.Fatal("uncachedLookup shouldn't have been called")
  345. }
  346. return expected
  347. },
  348. }
  349. shouldCallUncachedLookup = true
  350. assert.Equal(t, expected, c.ActiveIncidents())
  351. shouldCallUncachedLookup = false
  352. assert.Equal(t, expected, c.ActiveIncidents())
  353. assert.Equal(t, expected, c.ActiveIncidents())
  354. time.Sleep(50 * time.Millisecond)
  355. shouldCallUncachedLookup = true
  356. assert.Equal(t, expected, c.ActiveIncidents())
  357. }
  358. func TestCachedIncidentLookupDoesntPanic(t *testing.T) {
  359. expected := []Incident{
  360. Incident{
  361. Name: "An incident",
  362. ID: "incidentID",
  363. },
  364. }
  365. c := &cachedIncidentLookup{
  366. cache: lrucache.NewLRUCache(1),
  367. ttl: 50 * time.Millisecond,
  368. uncachedLookup: func() []Incident { return expected },
  369. }
  370. c.cache.Set(cacheKey, 42, time.Now().Add(30*time.Minute))
  371. actual := c.ActiveIncidents()
  372. assert.Equal(t, expected, actual)
  373. }