github.com/Financial-Times/publish-availability-monitor@v1.12.0/feeds/notificationsPullFeed_test.go (about) 1 package feeds 2 3 import ( 4 "bytes" 5 "fmt" 6 "io" 7 "net/http" 8 "net/url" 9 "strings" 10 "testing" 11 "time" 12 13 "github.com/Financial-Times/go-logger/v2" 14 "github.com/Financial-Times/publish-availability-monitor/httpcaller" 15 "github.com/google/uuid" 16 "github.com/stretchr/testify/assert" 17 ) 18 19 type mockResponse struct { 20 response *http.Response 21 query *url.Values 22 } 23 24 func buildResponse(statusCode int, content string, expectedQuery *url.Values) *mockResponse { 25 return &mockResponse{ 26 &http.Response{ 27 StatusCode: statusCode, 28 Body: nopCloser{bytes.NewBuffer([]byte(content))}, 29 }, 30 expectedQuery, 31 } 32 } 33 34 // mock httpCaller implementation 35 type testHTTPCaller struct { 36 t *testing.T 37 authUser string 38 authPass string 39 apiKey string 40 tidPrefix string 41 mockResponses []*mockResponse 42 current int 43 } 44 45 // returns the mock responses of testHTTPCaller in order 46 func (t *testHTTPCaller) DoCall(config httpcaller.Config) (*http.Response, error) { 47 if t.authUser != config.Username || t.authPass != config.Password { 48 return buildResponse(401, `{message: "Not authenticated"}`, nil).response, nil 49 } 50 51 if t.apiKey != config.APIKey { 52 return buildResponse(401, `{"message":"No api key"}`, nil).response, nil 53 } 54 55 if t.tidPrefix != "" { 56 assert.True(t.t, strings.HasPrefix(config.TID, t.tidPrefix), "transaction id should start with "+t.tidPrefix) 57 timestamp := config.TID[len(t.tidPrefix):] 58 _, err := time.Parse(time.RFC3339, timestamp) 59 assert.Nil(t.t, err, "transaction id suffix did not parse as a timestamp") 60 } 61 62 response := t.mockResponses[t.current] 63 if response.query != nil { 64 requestURL, _ := url.Parse(config.URL) 65 assert.Equal(t.t, *response.query, requestURL.Query()) 66 } 67 68 t.current = (t.current + 1) % len(t.mockResponses) 69 return response.response, nil 70 } 71 72 // builds testHTTPCaller with the given mocked responses in the provided order 73 func mockHTTPCaller(t *testing.T, tidPrefix string, responses ...*mockResponse) httpcaller.Caller { 74 return &testHTTPCaller{t: t, tidPrefix: tidPrefix, mockResponses: responses} 75 } 76 77 // builds testHTTPCaller with the given mocked responses in the provided order 78 func mockAuthenticatedHTTPCaller(t *testing.T, tidPrefix string, username string, password string, apiKey string, responses ...*mockResponse) httpcaller.Caller { 79 return &testHTTPCaller{t: t, tidPrefix: tidPrefix, authUser: username, authPass: password, apiKey: apiKey, mockResponses: responses} 80 } 81 82 // this is necessary to be able to build an http.Response 83 // the body has to be a ReadCloser 84 type nopCloser struct { 85 io.Reader 86 } 87 88 func (nopCloser) Close() error { return nil } 89 90 func mockNotificationFor(uuid string, publishRef string, lastModified time.Time) string { 91 return fmt.Sprintf(`{ 92 "type": "http://www.ft.com/thing/ThingChangeType/UPDATE", 93 "id": "http://www.ft.com/thing/%v", 94 "apiUrl": "http://api.ft.com/content/%v", 95 "publishReference": "%v", 96 "lastModified": "%v" 97 }`, uuid, uuid, publishRef, lastModified.Format(time.RFC3339)) 98 } 99 100 func mockNotificationsResponseFor(requestQueryString string, notifications string, nextLinkQueryString string) string { 101 return fmt.Sprintf(`{ 102 "requestUrl": "http://api.ft.com/content/notifications?%v", 103 "notifications": [ 104 %v 105 ], 106 "links": [ 107 { 108 "href": "http://api.ft.com/content/notifications?%v", 109 "rel": "next" 110 } 111 ] 112 }`, requestQueryString, notifications, nextLinkQueryString) 113 } 114 115 func TestNotificationsArePolled(t *testing.T) { 116 uuid := uuid.NewString() 117 publishRef := "tid_0123wxyz" 118 lastModified := time.Now() 119 notifications := mockNotificationsResponseFor("2016-10-28T15:00:00.000Z", 120 mockNotificationFor(uuid, publishRef, lastModified), 121 "2016-10-28T16:00:00.000Z") 122 123 httpCaller := mockHTTPCaller(t, "tid_pam_notifications_pull_", buildResponse(200, notifications, nil)) 124 125 baseURL, _ := url.Parse("http://www.example.org?type=all") 126 log := logger.NewUPPLogger("test", "PANIC") 127 128 f := NewNotificationsFeed("notifications", *baseURL, 10, 1, "", "", "", log) 129 130 f.(*NotificationsPullFeed).SetHTTPCaller(httpCaller) 131 f.Start() 132 defer f.Stop() 133 134 time.Sleep(time.Duration(1200) * time.Millisecond) 135 136 response := f.NotificationsFor(uuid) 137 assert.Len(t, response, 1, "notifications for item") 138 assert.Equal(t, publishRef, response[0].PublishReference, "publish ref") 139 } 140 141 func TestMultipleNotificationsAreMapped(t *testing.T) { 142 uuids := []string{uuid.NewString(), uuid.NewString()} 143 publishRefs := []string{"tid_1", "tid_2"} 144 lastModified := time.Now() 145 146 notifications := mockNotificationFor(uuids[0], publishRefs[0], lastModified) + "," + mockNotificationFor(uuids[1], publishRefs[1], lastModified) 147 148 response := mockNotificationsResponseFor("2016-10-28T15:00:00.000Z", 149 notifications, 150 "2016-10-28T16:00:00.000Z") 151 152 httpCaller := mockHTTPCaller(t, "tid_pam_notifications_pull_", buildResponse(200, response, nil)) 153 154 baseURL, _ := url.Parse("http://www.example.org?type=all") 155 log := logger.NewUPPLogger("test", "PANIC") 156 157 f := NewNotificationsFeed("notifications", *baseURL, 10, 1, "", "", "", log) 158 159 f.(*NotificationsPullFeed).SetHTTPCaller(httpCaller) 160 f.Start() 161 defer f.Stop() 162 163 time.Sleep(time.Duration(1200) * time.Millisecond) 164 165 for i := range uuids { 166 actual := f.NotificationsFor(uuids[i]) 167 assert.Len(t, actual, 1, "notifications for item") 168 assert.Equal(t, "http://www.ft.com/thing/"+uuids[i], actual[0].ID, "ID") 169 assert.Equal(t, publishRefs[i], actual[0].PublishReference, "publish ref") 170 } 171 } 172 173 func TestNotificationsForReturnsEmptyIfNotFound(t *testing.T) { 174 baseURL, _ := url.Parse("http://www.example.org") 175 log := logger.NewUPPLogger("test", "PANIC") 176 177 f := NewNotificationsFeed("notifications", *baseURL, 10, 1, "", "", "", log) 178 179 response := f.NotificationsFor(uuid.NewString()) 180 assert.Len(t, response, 0, "notifications for item") 181 } 182 183 func TestNotificationsForReturnsAllMatches(t *testing.T) { 184 uuid := uuid.NewString() 185 publishRef1 := "tid_0123wxyz" 186 lastModified1 := time.Now().Add(time.Duration(-1) * time.Second) 187 notifications1 := mockNotificationsResponseFor("2016-10-28T15:00:00.000Z", 188 mockNotificationFor(uuid, publishRef1, lastModified1), 189 "2016-10-28T15:10:00.000Z") 190 191 publishRef2 := "tid_0123abcd" 192 lastModified2 := time.Now() 193 notifications2 := mockNotificationsResponseFor("2016-10-28T15:10:00.000Z", 194 mockNotificationFor(uuid, publishRef2, lastModified2), 195 "2016-10-28T15:20:00.000Z") 196 197 httpCaller := mockHTTPCaller(t, "tid_pam_notifications_pull_", buildResponse(200, notifications1, nil), buildResponse(200, notifications2, nil)) 198 199 baseURL, _ := url.Parse("http://www.example.org") 200 log := logger.NewUPPLogger("test", "PANIC") 201 202 f := NewNotificationsFeed("notifications", *baseURL, 10, 1, "", "", "", log) 203 f.(*NotificationsPullFeed).SetHTTPCaller(httpCaller) 204 f.Start() 205 defer f.Stop() 206 time.Sleep(time.Duration(2200) * time.Millisecond) 207 208 response := f.NotificationsFor(uuid) 209 assert.Len(t, response, 2, "notifications for item") 210 assert.Equal(t, publishRef1, response[0].PublishReference, "first publish ref") 211 assert.Equal(t, publishRef2, response[1].PublishReference, "second publish ref") 212 } 213 214 func TestNotificationsPollingContinuesAfterErrorResponse(t *testing.T) { 215 uuid := uuid.NewString() 216 publishRef := "tid_0123wxyz" 217 lastModified := time.Now() 218 notifications := mockNotificationsResponseFor("2016-10-28T15:00:00.000Z", 219 mockNotificationFor(uuid, publishRef, lastModified), 220 "2016-10-28T16:00:00.000Z") 221 222 httpCaller := mockHTTPCaller(t, "tid_pam_notifications_pull_", buildResponse(500, "", nil), buildResponse(200, notifications, nil)) 223 224 baseURL, _ := url.Parse("http://www.example.org") 225 log := logger.NewUPPLogger("test", "PANIC") 226 227 f := NewNotificationsFeed("notifications", *baseURL, 10, 1, "", "", "", log) 228 f.(*NotificationsPullFeed).SetHTTPCaller(httpCaller) 229 f.Start() 230 defer f.Stop() 231 time.Sleep(time.Duration(2200) * time.Millisecond) 232 233 response := f.NotificationsFor(uuid) 234 assert.Len(t, response, 1, "notifications for item") 235 assert.Equal(t, publishRef, response[0].PublishReference, "publish ref") 236 } 237 238 func TestNotificationsArePurged(t *testing.T) { 239 uuid := uuid.NewString() 240 publishRef := "tid_0123wxyz" 241 lastModified := time.Now().Add(time.Duration(-2) * time.Second) 242 notifications := mockNotificationsResponseFor("2016-10-28T15:00:00.000Z", 243 mockNotificationFor(uuid, publishRef, lastModified), 244 "2016-10-28T16:00:00.000Z") 245 246 httpCaller := mockHTTPCaller(t, "tid_pam_notifications_pull_", buildResponse(200, notifications, nil)) 247 248 baseURL, _ := url.Parse("http://www.example.org") 249 log := logger.NewUPPLogger("test", "PANIC") 250 251 f := NewNotificationsFeed("notifications", *baseURL, 1, 1, "", "", "", log) 252 f.(*NotificationsPullFeed).SetHTTPCaller(httpCaller) 253 f.Start() 254 defer f.Stop() 255 256 time.Sleep(time.Duration(1200) * time.Millisecond) 257 258 response := f.NotificationsFor(uuid) 259 assert.Len(t, response, 1, "notifications for item") 260 assert.Equal(t, publishRef, response[0].PublishReference, "publish ref") 261 262 time.Sleep(time.Duration(1) * time.Second) 263 response = f.NotificationsFor(uuid) 264 assert.Len(t, response, 0, "notifications for item") 265 } 266 267 func TestNotificationsPollingFollowsOpaqueLink(t *testing.T) { 268 uuid1 := uuid.NewString() 269 publishRef1 := "tid_0123wxyz" 270 lastModified1 := time.Now().Add(time.Duration(-1) * time.Second) 271 bootstrapQuery := url.Values{"since": []string{"any"}} 272 nextPageQuery := url.Values{"page": []string{"12345"}} 273 274 notifications1 := mockNotificationsResponseFor(bootstrapQuery.Encode(), 275 mockNotificationFor(uuid1, publishRef1, lastModified1), 276 nextPageQuery.Encode()) 277 278 uuid2 := uuid.NewString() 279 publishRef2 := "tid_0123abcd" 280 lastModified2 := time.Now() 281 notifications2 := mockNotificationsResponseFor(nextPageQuery.Encode(), 282 mockNotificationFor(uuid2, publishRef2, lastModified2), 283 "page=xxx") 284 285 httpCaller := mockHTTPCaller(t, "tid_pam_notifications_pull_", buildResponse(200, notifications1, nil), buildResponse(200, notifications2, &nextPageQuery)) 286 287 baseURL, _ := url.Parse("http://www.example.org") 288 log := logger.NewUPPLogger("test", "PANIC") 289 290 f := NewNotificationsFeed("notifications", *baseURL, 10, 1, "", "", "", log) 291 f.(*NotificationsPullFeed).SetHTTPCaller(httpCaller) 292 f.Start() 293 defer f.Stop() 294 time.Sleep(time.Duration(2200) * time.Millisecond) 295 296 response1 := f.NotificationsFor(uuid1) 297 assert.Len(t, response1, 1, "notifications for "+uuid1) 298 assert.Equal(t, publishRef1, response1[0].PublishReference, "publish ref for "+uuid1) 299 300 response2 := f.NotificationsFor(uuid2) 301 assert.Len(t, response2, 1, "notifications for "+uuid2) 302 assert.Equal(t, publishRef2, response2[0].PublishReference, "publish ref for "+uuid2) 303 }