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  }