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  }