github.com/Financial-Times/publish-availability-monitor@v1.12.0/messageHandler_integration_test.go (about) 1 package main 2 3 import ( 4 "errors" 5 "fmt" 6 "net/http" 7 "net/http/httptest" 8 "net/url" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/Financial-Times/go-logger/v2" 14 "github.com/Financial-Times/kafka-client-go/v4" 15 "github.com/Financial-Times/publish-availability-monitor/checks" 16 "github.com/Financial-Times/publish-availability-monitor/config" 17 "github.com/Financial-Times/publish-availability-monitor/envs" 18 "github.com/Financial-Times/publish-availability-monitor/feeds" 19 "github.com/Financial-Times/publish-availability-monitor/httpcaller" 20 "github.com/Financial-Times/publish-availability-monitor/metrics" 21 ) 22 23 func TestHandleMessage_ProducesMetrics(t *testing.T) { 24 log := logger.NewUPPLogger("publish-availability-monitor", "INFO") 25 testServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 26 w.WriteHeader(http.StatusOK) 27 })) 28 29 tests := map[string]struct { 30 AppConfig *config.AppConfig 31 E2ETestUUIDs []string 32 KafkaMessage kafka.FTMessage 33 NotificationsPayload string 34 IsMetricExpected bool 35 ExpectedMetric metrics.PublishMetric 36 }{ 37 "synthetic e2e test with valid content type and notification should result in publishOk=true": { 38 AppConfig: &config.AppConfig{ 39 Threshold: 5, 40 ValidationEndpoints: map[string]string{ 41 "application/vnd.ft-upp-article-internal+json": testServer.URL, 42 }, 43 MetricConf: []config.MetricConfig{ 44 { 45 Endpoint: "/whatever/", 46 Granularity: 1, 47 Alias: "notifications-push", 48 }, 49 }, 50 Capabilities: []config.Capability{ 51 { 52 MetricAlias: "notifications-push", 53 }, 54 }, 55 }, 56 E2ETestUUIDs: []string{"077f5ac2-0491-420e-a5d0-982e0f86204b"}, 57 KafkaMessage: kafka.FTMessage{ 58 Headers: map[string]string{ 59 "Origin-System-Id": "http://cmdb.ft.com/systems/cct", 60 "X-Request-Id": "SYNTHETIC-REQ-MON077f5ac2-0491-420e-a5d0-982e0f86204b", 61 "Content-Type": "application/vnd.ft-upp-article-internal+json", 62 "Message-Timestamp": time.Now().Format(checks.DateLayout), 63 }, 64 Body: `{ 65 "uuid": "077f5ac2-0491-420e-a5d0-982e0f86204b", 66 "type": "Article" 67 }`, 68 }, 69 NotificationsPayload: getNotificationsPayload("077f5ac2-0491-420e-a5d0-982e0f86204b", "SYNTHETIC-REQ-MON077f5ac2-0491-420e-a5d0-982e0f86204b"), 70 IsMetricExpected: true, 71 ExpectedMetric: metrics.PublishMetric{ 72 TID: "SYNTHETIC-REQ-MON077f5ac2-0491-420e-a5d0-982e0f86204b", 73 PublishOK: true, 74 }, 75 }, 76 "synthetic publish should not produce metric": { 77 AppConfig: &config.AppConfig{ 78 Threshold: 1, 79 ValidationEndpoints: map[string]string{ 80 "application/vnd.ft-upp-article-internal+json": testServer.URL, 81 }, 82 MetricConf: []config.MetricConfig{ 83 { 84 Endpoint: "/whatever/", 85 Granularity: 1, 86 Alias: "notifications-push", 87 }, 88 }, 89 }, 90 KafkaMessage: kafka.FTMessage{ 91 Headers: map[string]string{ 92 "Origin-System-Id": "http://cmdb.ft.com/systems/cct", 93 "X-Request-Id": "SYNTHETIC-REQ-MON077f5ac2-0491-420e-a5d0-982e0f86204b", 94 "Content-Type": "application/vnd.ft-upp-article-internal+json", 95 "Message-Timestamp": time.Now().Format(checks.DateLayout), 96 }, 97 Body: `{ 98 "uuid": "077f5ac2-0491-420e-a5d0-982e0f86204b", 99 "type": "Article" 100 }`, 101 }, 102 IsMetricExpected: false, 103 }, 104 "content with missing validator should not produce metric": { 105 AppConfig: &config.AppConfig{ 106 Threshold: 1, 107 MetricConf: []config.MetricConfig{ 108 { 109 Endpoint: "/whatever/", 110 Granularity: 1, 111 Alias: "notifications-push", 112 }, 113 }, 114 }, 115 KafkaMessage: kafka.FTMessage{ 116 Headers: map[string]string{ 117 "Origin-System-Id": "http://cmdb.ft.com/systems/cct", 118 "X-Request-Id": "tid_077f5ac2-0491-420e-a5d0-982e0f86204b", 119 "Content-Type": "application/vnd.ft-upp-article-internal+json", 120 "Message-Timestamp": time.Now().Format(checks.DateLayout), 121 }, 122 Body: `{ 123 "uuid": "077f5ac2-0491-420e-a5d0-982e0f86204b", 124 "type": "Article" 125 }`, 126 }, 127 IsMetricExpected: false, 128 }, 129 "content with validator and without metric content type should not produce metric": { 130 AppConfig: &config.AppConfig{ 131 Threshold: 1, 132 ValidationEndpoints: map[string]string{ 133 "application/vnd.ft-upp-article-internal+json": testServer.URL, 134 }, 135 MetricConf: []config.MetricConfig{ 136 { 137 Endpoint: "/whatever/", 138 Granularity: 1, 139 Alias: "notifications-push", 140 }, 141 }, 142 }, 143 KafkaMessage: kafka.FTMessage{ 144 Headers: map[string]string{ 145 "Origin-System-Id": "http://cmdb.ft.com/systems/cct", 146 "X-Request-Id": "tid_077f5ac2-0491-420e-a5d0-982e0f86204b", 147 "Content-Type": "application/vnd.ft-upp-article-internal+json", 148 "Message-Timestamp": time.Now().Format(checks.DateLayout), 149 }, 150 Body: `{ 151 "uuid": "077f5ac2-0491-420e-a5d0-982e0f86204b", 152 "type": "Article" 153 }`, 154 }, 155 IsMetricExpected: false, 156 }, 157 "content with validator without notification should result in publishOk=false": { 158 AppConfig: &config.AppConfig{ 159 Threshold: 5, 160 ValidationEndpoints: map[string]string{ 161 "application/vnd.ft-upp-article-internal+json": testServer.URL, 162 }, 163 MetricConf: []config.MetricConfig{ 164 { 165 Endpoint: "/whatever/", 166 Granularity: 1, 167 Alias: "notifications-push", 168 ContentTypes: []string{"application/vnd.ft-upp-article-internal+json"}, 169 }, 170 }, 171 }, 172 KafkaMessage: kafka.FTMessage{ 173 Headers: map[string]string{ 174 "Origin-System-Id": "http://cmdb.ft.com/systems/cct", 175 "X-Request-Id": "tid_077f5ac2-0491-420e-a5d0-982e0f86204b", 176 "Content-Type": "application/vnd.ft-upp-article-internal+json", 177 "Message-Timestamp": time.Now().Format(checks.DateLayout), 178 }, 179 Body: `{ 180 "uuid": "077f5ac2-0491-420e-a5d0-982e0f86204b", 181 "type": "Article" 182 }`, 183 }, 184 IsMetricExpected: true, 185 ExpectedMetric: metrics.PublishMetric{ 186 TID: "tid_077f5ac2-0491-420e-a5d0-982e0f86204b", 187 PublishOK: false, 188 }, 189 }, 190 "content with validator, metric content type and notification should result in publishOk=true": { 191 AppConfig: &config.AppConfig{ 192 Threshold: 5, 193 ValidationEndpoints: map[string]string{ 194 "application/vnd.ft-upp-article-internal+json": testServer.URL, 195 }, 196 MetricConf: []config.MetricConfig{ 197 { 198 Endpoint: "/whatever/", 199 Granularity: 1, 200 Alias: "notifications-push", 201 ContentTypes: []string{"application/vnd.ft-upp-article-internal+json"}, 202 }, 203 }, 204 }, 205 KafkaMessage: kafka.FTMessage{ 206 Headers: map[string]string{ 207 "Origin-System-Id": "http://cmdb.ft.com/systems/cct", 208 "X-Request-Id": "tid_077f5ac2-0491-420e-a5d0-982e0f86204b", 209 "Content-Type": "application/vnd.ft-upp-article-internal+json", 210 "Message-Timestamp": time.Now().Format(checks.DateLayout), 211 }, 212 Body: `{ 213 "uuid": "077f5ac2-0491-420e-a5d0-982e0f86204b", 214 "type": "Article" 215 }`, 216 }, 217 NotificationsPayload: getNotificationsPayload("077f5ac2-0491-420e-a5d0-982e0f86204b", "tid_077f5ac2-0491-420e-a5d0-982e0f86204b"), 218 IsMetricExpected: true, 219 ExpectedMetric: metrics.PublishMetric{ 220 TID: "tid_077f5ac2-0491-420e-a5d0-982e0f86204b", 221 PublishOK: true, 222 }, 223 }, 224 } 225 226 for name, test := range tests { 227 test := test 228 t.Run(name, func(t *testing.T) { 229 t.Parallel() 230 var testEnvs = envs.NewEnvironments() 231 testEnvs.SetEnvironment("env1", envs.Environment{ 232 Name: "env1", 233 ReadURL: "http://env1.example.org", 234 }) 235 236 httpCaller := &mockHTTPCaller{ 237 notifications: []string{test.NotificationsPayload}, 238 } 239 240 baseURL, _ := url.Parse("http://www.example.org") 241 f := feeds.NewNotificationsFeed("notifications-push", *baseURL, 10, 1, "", "", "", log) 242 f.(*feeds.NotificationsPushFeed).SetHTTPCaller(httpCaller) 243 f.Start() 244 defer f.Stop() 245 246 subscribedFeeds := map[string][]feeds.Feed{} 247 subscribedFeeds["env1"] = append(subscribedFeeds["env1"], f) 248 249 var metricsCh = make(chan metrics.PublishMetric) 250 var metricsHistory = metrics.NewHistory(make([]metrics.PublishMetric, 0)) 251 252 mh := NewKafkaMessageHandler(test.AppConfig, testEnvs, subscribedFeeds, metricsCh, metricsHistory, test.E2ETestUUIDs, log) 253 kmh := mh.(*kafkaMessageHandler) 254 255 kmh.HandleMessage(test.KafkaMessage) 256 257 timeout := time.Duration(test.AppConfig.Threshold) * time.Second 258 metric, err := waitForMetric(metricsCh, timeout) 259 if test.IsMetricExpected && err != nil { 260 t.Fatalf("expected nil error, got %v", err) 261 } 262 263 if !test.IsMetricExpected && err == nil { 264 t.Fatal("expected non nil error") 265 } 266 267 if metric.TID != test.ExpectedMetric.TID { 268 t.Fatalf("expected TID %v, got %v", test.ExpectedMetric.TID, metric.TID) 269 } 270 271 if metric.PublishOK != test.ExpectedMetric.PublishOK { 272 t.Fatalf("expected PublishOK %v, got %v", test.ExpectedMetric.PublishOK, metric.PublishOK) 273 } 274 }) 275 } 276 } 277 278 func getNotificationsPayload(uuid, tID string) string { 279 notifications := fmt.Sprintf(`{ 280 "type": "http://www.ft.com/thing/ThingChangeType/UPDATE", 281 "id": "http://www.ft.com/thing/%v", 282 "apiUrl": "http://api.ft.com/content/%v", 283 "publishReference": "%v", 284 "lastModified": "%v" 285 }`, uuid, uuid, tID, time.Now().Format(time.RFC3339)) 286 287 return strings.Replace(notifications, "\n", "", -1) 288 } 289 290 func waitForMetric(metricsCh chan metrics.PublishMetric, timeout time.Duration) (metrics.PublishMetric, error) { 291 ticker := time.NewTicker(100 * time.Millisecond) 292 defer ticker.Stop() 293 294 timer := time.NewTimer(timeout) 295 defer timer.Stop() 296 for { 297 select { 298 case <-ticker.C: 299 continue 300 case metric := <-metricsCh: 301 return metric, nil 302 case <-timer.C: 303 return metrics.PublishMetric{}, errors.New("test timed out waiting for metric") 304 } 305 } 306 } 307 308 type mockHTTPCaller struct { 309 notifications []string 310 } 311 312 func (mht *mockHTTPCaller) DoCall(config httpcaller.Config) (*http.Response, error) { 313 stream := &mockPushNotificationsStream{mht.notifications, 0} 314 return &http.Response{ 315 StatusCode: 200, 316 Body: stream, 317 }, nil 318 } 319 320 type mockPushNotificationsStream struct { 321 notifications []string 322 index int 323 } 324 325 func (resp *mockPushNotificationsStream) Read(p []byte) (n int, err error) { 326 var data []byte 327 if resp.index >= len(resp.notifications) { 328 data = []byte("data: []\n") 329 } else { 330 data = []byte("data: [" + resp.notifications[resp.index] + "]\n") 331 resp.index++ 332 } 333 actual := len(data) 334 for i := 0; i < actual; i++ { 335 p[i] = data[i] 336 } 337 338 return actual, nil 339 } 340 341 func (resp *mockPushNotificationsStream) Close() error { 342 return nil 343 }