github.com/grahambrereton-form3/tilt@v0.10.18/internal/hud/server/server_test.go (about)

     1  package server_test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"io/ioutil"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"path/filepath"
    10  	"reflect"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/grpc-ecosystem/grpc-gateway/runtime"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  	"github.com/windmilleng/wmclient/pkg/analytics"
    18  
    19  	tiltanalytics "github.com/windmilleng/tilt/internal/analytics"
    20  	"github.com/windmilleng/tilt/internal/cloud"
    21  	"github.com/windmilleng/tilt/internal/cloud/cloudurl"
    22  	"github.com/windmilleng/tilt/internal/hud/server"
    23  	"github.com/windmilleng/tilt/internal/store"
    24  	"github.com/windmilleng/tilt/pkg/assets"
    25  	"github.com/windmilleng/tilt/pkg/model"
    26  	proto_webview "github.com/windmilleng/tilt/pkg/webview"
    27  )
    28  
    29  func TestHandleAnalyticsEmptyRequest(t *testing.T) {
    30  	f := newTestFixture(t)
    31  
    32  	var jsonStr = []byte(`[]`)
    33  	req, err := http.NewRequest(http.MethodPost, "/api/analytics", bytes.NewBuffer(jsonStr))
    34  	if err != nil {
    35  		t.Fatal(err)
    36  	}
    37  	req.Header.Set("Content-Type", "application/json")
    38  
    39  	rr := httptest.NewRecorder()
    40  	handler := http.HandlerFunc(f.serv.HandleAnalytics)
    41  
    42  	handler.ServeHTTP(rr, req)
    43  
    44  	if status := rr.Code; status != http.StatusOK {
    45  		t.Errorf("handler returned wrong status code: got %v want %v",
    46  			status, http.StatusOK)
    47  	}
    48  }
    49  
    50  func TestHandleAnalyticsRecordsIncr(t *testing.T) {
    51  	f := newTestFixture(t)
    52  
    53  	var jsonStr = []byte(`[{"verb": "incr", "name": "foo", "tags": {}}]`)
    54  	req, err := http.NewRequest(http.MethodPost, "/api/analytics", bytes.NewBuffer(jsonStr))
    55  	if err != nil {
    56  		t.Fatal(err)
    57  	}
    58  	req.Header.Set("Content-Type", "application/json")
    59  
    60  	rr := httptest.NewRecorder()
    61  	handler := http.HandlerFunc(f.serv.HandleAnalytics)
    62  
    63  	handler.ServeHTTP(rr, req)
    64  
    65  	if status := rr.Code; status != http.StatusOK {
    66  		t.Errorf("handler returned wrong status code: got %v want %v",
    67  			status, http.StatusOK)
    68  	}
    69  
    70  	f.assertIncrement("foo", 1)
    71  }
    72  
    73  func TestHandleAnalyticsNonPost(t *testing.T) {
    74  	f := newTestFixture(t)
    75  
    76  	req, err := http.NewRequest(http.MethodGet, "/api/analytics", nil)
    77  	if err != nil {
    78  		t.Fatal(err)
    79  	}
    80  	req.Header.Set("Content-Type", "application/json")
    81  
    82  	rr := httptest.NewRecorder()
    83  	handler := http.HandlerFunc(f.serv.HandleAnalytics)
    84  
    85  	handler.ServeHTTP(rr, req)
    86  
    87  	if status := rr.Code; status != http.StatusBadRequest {
    88  		t.Errorf("handler returned wrong status code: got %v want %v",
    89  			status, http.StatusBadRequest)
    90  	}
    91  }
    92  
    93  func TestHandleAnalyticsMalformedPayload(t *testing.T) {
    94  	f := newTestFixture(t)
    95  
    96  	var jsonStr = []byte(`[{"Verb": ]`)
    97  	req, err := http.NewRequest(http.MethodPost, "/api/analytics", bytes.NewBuffer(jsonStr))
    98  	if err != nil {
    99  		t.Fatal(err)
   100  	}
   101  	req.Header.Set("Content-Type", "application/json")
   102  
   103  	rr := httptest.NewRecorder()
   104  	handler := http.HandlerFunc(f.serv.HandleAnalytics)
   105  
   106  	handler.ServeHTTP(rr, req)
   107  
   108  	if status := rr.Code; status != http.StatusBadRequest {
   109  		t.Errorf("handler returned wrong status code: got %v want %v",
   110  			status, http.StatusBadRequest)
   111  	}
   112  }
   113  
   114  func TestHandleAnalyticsErrorsIfNotIncr(t *testing.T) {
   115  	f := newTestFixture(t)
   116  
   117  	var jsonStr = []byte(`[{"verb": "count", "name": "foo", "tags": {}}]`)
   118  	req, err := http.NewRequest(http.MethodPost, "/api/analytics", bytes.NewBuffer(jsonStr))
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  	req.Header.Set("Content-Type", "application/json")
   123  
   124  	rr := httptest.NewRecorder()
   125  	handler := http.HandlerFunc(f.serv.HandleAnalytics)
   126  
   127  	handler.ServeHTTP(rr, req)
   128  
   129  	if status := rr.Code; status != http.StatusBadRequest {
   130  		t.Errorf("handler returned wrong status code: got %v want %v",
   131  			status, http.StatusBadRequest)
   132  	}
   133  }
   134  
   135  func TestHandleAnalyticsOptIn(t *testing.T) {
   136  	f := newTestFixture(t)
   137  
   138  	err := f.ta.SetUserOpt(analytics.OptDefault)
   139  	if err != nil {
   140  		t.Fatal(err)
   141  	}
   142  
   143  	var jsonStr = []byte(`{"opt": "opt-in"}`)
   144  	req, err := http.NewRequest(http.MethodPost, "/api/analytics_opt", bytes.NewBuffer(jsonStr))
   145  	if err != nil {
   146  		t.Fatal(err)
   147  	}
   148  	req.Header.Set("Content-Type", "application/json")
   149  
   150  	rr := httptest.NewRecorder()
   151  	handler := http.HandlerFunc(f.serv.HandleAnalyticsOpt)
   152  
   153  	handler.ServeHTTP(rr, req)
   154  
   155  	if status := rr.Code; status != http.StatusOK {
   156  		t.Errorf("handler returned wrong status code: got %v want %v",
   157  			status, http.StatusOK)
   158  	}
   159  
   160  	action := store.WaitForAction(t, reflect.TypeOf(store.AnalyticsUserOptAction{}), f.getActions)
   161  	assert.Equal(t, store.AnalyticsUserOptAction{Opt: analytics.OptIn}, action)
   162  
   163  	f.a.Flush(time.Millisecond)
   164  
   165  	assert.Equal(t, []analytics.CountEvent{{
   166  		Name: "analytics.opt.in",
   167  		Tags: map[string]string{"version": "v0.0.0"},
   168  		N:    1,
   169  	}}, f.a.Counts)
   170  }
   171  
   172  func TestHandleAnalyticsOptNonPost(t *testing.T) {
   173  	f := newTestFixture(t)
   174  
   175  	req, err := http.NewRequest(http.MethodGet, "/api/analytics_opt", nil)
   176  	if err != nil {
   177  		t.Fatal(err)
   178  	}
   179  	req.Header.Set("Content-Type", "application/json")
   180  
   181  	rr := httptest.NewRecorder()
   182  	handler := http.HandlerFunc(f.serv.HandleAnalyticsOpt)
   183  
   184  	handler.ServeHTTP(rr, req)
   185  
   186  	if status := rr.Code; status != http.StatusBadRequest {
   187  		t.Errorf("handler returned wrong status code: got %v want %v",
   188  			status, http.StatusBadRequest)
   189  	}
   190  }
   191  
   192  func TestHandleAnalyticsOptMalformedPayload(t *testing.T) {
   193  	f := newTestFixture(t)
   194  
   195  	var jsonStr = []byte(`{"opt":`)
   196  	req, err := http.NewRequest(http.MethodPost, "/api/analytics_opt", bytes.NewBuffer(jsonStr))
   197  	if err != nil {
   198  		t.Fatal(err)
   199  	}
   200  	req.Header.Set("Content-Type", "application/json")
   201  
   202  	rr := httptest.NewRecorder()
   203  	handler := http.HandlerFunc(f.serv.HandleAnalyticsOpt)
   204  
   205  	handler.ServeHTTP(rr, req)
   206  
   207  	if status := rr.Code; status != http.StatusBadRequest {
   208  		t.Errorf("handler returned wrong status code: got %v want %v",
   209  			status, http.StatusBadRequest)
   210  	}
   211  }
   212  
   213  func TestHandleTriggerReturnsError(t *testing.T) {
   214  	f := newTestFixture(t)
   215  
   216  	var jsonStr = []byte(`{"manifest_names":["foo"]}`)
   217  	req, err := http.NewRequest(http.MethodPost, "/api/trigger", bytes.NewBuffer(jsonStr))
   218  	if err != nil {
   219  		t.Fatal(err)
   220  	}
   221  	req.Header.Set("Content-Type", "application/json")
   222  
   223  	rr := httptest.NewRecorder()
   224  	handler := http.HandlerFunc(f.serv.HandleTrigger)
   225  
   226  	handler.ServeHTTP(rr, req)
   227  
   228  	// Expect SendToTriggerQueue to fail: make sure we reply to the HTTP request
   229  	// with an error when this happens
   230  	if status := rr.Code; status != http.StatusBadRequest {
   231  		t.Errorf("handler returned wrong status code: got %v want %v",
   232  			status, http.StatusBadRequest)
   233  	}
   234  	assert.Contains(t, rr.Body.String(), "no manifest found with name")
   235  }
   236  
   237  func TestHandleTriggerTooManyManifestNames(t *testing.T) {
   238  	f := newTestFixture(t)
   239  
   240  	var jsonStr = []byte(`{"manifest_names":["foo", "bar"]}`)
   241  	req, err := http.NewRequest(http.MethodPost, "/api/trigger", bytes.NewBuffer(jsonStr))
   242  	if err != nil {
   243  		t.Fatal(err)
   244  	}
   245  	req.Header.Set("Content-Type", "application/json")
   246  
   247  	rr := httptest.NewRecorder()
   248  	handler := http.HandlerFunc(f.serv.HandleTrigger)
   249  
   250  	handler.ServeHTTP(rr, req)
   251  
   252  	if status := rr.Code; status != http.StatusBadRequest {
   253  		t.Errorf("handler returned wrong status code: got %v want %v",
   254  			status, http.StatusBadRequest)
   255  	}
   256  	assert.Contains(t, rr.Body.String(), "currently supports exactly one manifest name, got 2")
   257  }
   258  
   259  func TestHandleTriggerNonPost(t *testing.T) {
   260  	f := newTestFixture(t)
   261  
   262  	req, err := http.NewRequest(http.MethodGet, "/api/trigger", nil)
   263  	if err != nil {
   264  		t.Fatal(err)
   265  	}
   266  	req.Header.Set("Content-Type", "application/json")
   267  
   268  	rr := httptest.NewRecorder()
   269  	handler := http.HandlerFunc(f.serv.HandleTrigger)
   270  
   271  	handler.ServeHTTP(rr, req)
   272  
   273  	if status := rr.Code; status != http.StatusBadRequest {
   274  		t.Errorf("handler returned wrong status code: got %v want %v",
   275  			status, http.StatusBadRequest)
   276  	}
   277  	assert.Contains(t, rr.Body.String(), "must be POST request")
   278  }
   279  
   280  func TestHandleTriggerMalformedPayload(t *testing.T) {
   281  	f := newTestFixture(t)
   282  
   283  	var jsonStr = []byte(`{"manifest_names":`)
   284  	req, err := http.NewRequest(http.MethodPost, "/api/trigger", bytes.NewBuffer(jsonStr))
   285  	if err != nil {
   286  		t.Fatal(err)
   287  	}
   288  	req.Header.Set("Content-Type", "application/json")
   289  
   290  	rr := httptest.NewRecorder()
   291  	handler := http.HandlerFunc(f.serv.HandleTrigger)
   292  
   293  	handler.ServeHTTP(rr, req)
   294  
   295  	if status := rr.Code; status != http.StatusBadRequest {
   296  		t.Errorf("handler returned wrong status code: got %v want %v",
   297  			status, http.StatusBadRequest)
   298  	}
   299  	assert.Contains(t, rr.Body.String(), "error parsing JSON")
   300  }
   301  
   302  func TestSendToTriggerQueue_manualManifest(t *testing.T) {
   303  	f := newTestFixture(t)
   304  
   305  	mt := store.ManifestTarget{
   306  		Manifest: model.Manifest{
   307  			Name:        "foobar",
   308  			TriggerMode: model.TriggerModeManualAfterInitial,
   309  		},
   310  	}
   311  	state := f.st.LockMutableStateForTesting()
   312  	state.UpsertManifestTarget(&mt)
   313  	f.st.UnlockMutableState()
   314  
   315  	err := server.SendToTriggerQueue(f.st, "foobar")
   316  	if err != nil {
   317  		t.Fatal(err)
   318  	}
   319  
   320  	a := store.WaitForAction(t, reflect.TypeOf(server.AppendToTriggerQueueAction{}), f.getActions)
   321  	action, ok := a.(server.AppendToTriggerQueueAction)
   322  	if !ok {
   323  		t.Fatalf("Action was not of type 'AppendToTriggerQueueAction': %+v", action)
   324  	}
   325  	assert.Equal(t, "foobar", action.Name.String())
   326  }
   327  
   328  func TestSendToTriggerQueue_automaticManifest(t *testing.T) {
   329  	f := newTestFixture(t)
   330  
   331  	mt := store.ManifestTarget{
   332  		Manifest: model.Manifest{
   333  			Name:        "foobar",
   334  			TriggerMode: model.TriggerModeAuto,
   335  		},
   336  	}
   337  	state := f.st.LockMutableStateForTesting()
   338  	state.UpsertManifestTarget(&mt)
   339  	f.st.UnlockMutableState()
   340  
   341  	err := server.SendToTriggerQueue(f.st, "foobar")
   342  	if err != nil {
   343  		t.Fatal(err)
   344  	}
   345  
   346  	a := store.WaitForAction(t, reflect.TypeOf(server.AppendToTriggerQueueAction{}), f.getActions)
   347  	action, ok := a.(server.AppendToTriggerQueueAction)
   348  	if !ok {
   349  		t.Fatalf("Action was not of type 'AppendToTriggerQueueAction': %+v", action)
   350  	}
   351  	assert.Equal(t, "foobar", action.Name.String())
   352  }
   353  
   354  func TestSendToTriggerQueue_noManifestWithName(t *testing.T) {
   355  	f := newTestFixture(t)
   356  
   357  	err := server.SendToTriggerQueue(f.st, "foobar")
   358  
   359  	assert.EqualError(t, err, "no manifest found with name 'foobar'")
   360  	store.AssertNoActionOfType(t, reflect.TypeOf(server.AppendToTriggerQueueAction{}), f.getActions)
   361  }
   362  
   363  func TestHandleNewSnapshot(t *testing.T) {
   364  	f := newTestFixture(t)
   365  
   366  	sp := filepath.Join("..", "webview", "testdata", "snapshot.json")
   367  	snap, err := ioutil.ReadFile(sp)
   368  	if err != nil {
   369  		t.Fatal(err)
   370  	}
   371  	req, err := http.NewRequest(http.MethodPost, "/api/snapshot/new", bytes.NewBuffer(snap))
   372  	if err != nil {
   373  		t.Fatal(err)
   374  	}
   375  
   376  	rr := httptest.NewRecorder()
   377  	handler := http.HandlerFunc(f.serv.HandleNewSnapshot)
   378  
   379  	handler.ServeHTTP(rr, req)
   380  
   381  	require.Equal(t, http.StatusOK, rr.Code,
   382  		"handler returned wrong status code: got %v want %v", rr.Code, http.StatusOK)
   383  	require.Contains(t, rr.Body.String(), "https://nonexistent.example.com/snapshot/aaaaa")
   384  
   385  	lastReq := f.snapshotHTTP.lastReq
   386  	if assert.NotNil(t, lastReq) {
   387  		var snapshot proto_webview.Snapshot
   388  		jspb := &runtime.JSONPb{OrigName: false, EmitDefaults: true}
   389  		decoder := jspb.NewDecoder(lastReq.Body)
   390  		decoder.Decode(&snapshot)
   391  		assert.Equal(t, "0.10.13", snapshot.View.RunningTiltBuild.Version)
   392  		assert.Equal(t, "43", snapshot.SnapshotHighlight.BeginningLogID)
   393  	}
   394  }
   395  
   396  type serverFixture struct {
   397  	t            *testing.T
   398  	serv         *server.HeadsUpServer
   399  	a            *analytics.MemoryAnalytics
   400  	ta           *tiltanalytics.TiltAnalytics
   401  	st           *store.Store
   402  	getActions   func() []store.Action
   403  	snapshotHTTP *fakeHTTPClient
   404  }
   405  
   406  func newTestFixture(t *testing.T) *serverFixture {
   407  	st, getActions := store.NewStoreForTesting()
   408  	go st.Loop(context.Background())
   409  	a := analytics.NewMemoryAnalytics()
   410  	opter := tiltanalytics.NewFakeOpter(analytics.OptIn)
   411  	a, ta := tiltanalytics.NewMemoryTiltAnalyticsForTest(opter)
   412  	snapshotHTTP := &fakeHTTPClient{}
   413  	addr := cloudurl.Address("nonexistent.example.com")
   414  	uploader := cloud.NewSnapshotUploader(snapshotHTTP, addr)
   415  	serv, err := server.ProvideHeadsUpServer(context.Background(), st, assets.NewFakeServer(), ta, uploader)
   416  	if err != nil {
   417  		t.Fatal(err)
   418  	}
   419  
   420  	return &serverFixture{
   421  		t:            t,
   422  		serv:         serv,
   423  		a:            a,
   424  		ta:           ta,
   425  		st:           st,
   426  		getActions:   getActions,
   427  		snapshotHTTP: snapshotHTTP,
   428  	}
   429  }
   430  
   431  type fakeHTTPClient struct {
   432  	lastReq *http.Request
   433  }
   434  
   435  func (f *fakeHTTPClient) Do(req *http.Request) (*http.Response, error) {
   436  	f.lastReq = req
   437  
   438  	return &http.Response{
   439  		StatusCode: http.StatusOK,
   440  		Body:       ioutil.NopCloser(bytes.NewReader([]byte(`{"ID":"aaaaa"}`))),
   441  	}, nil
   442  }
   443  
   444  func (f *serverFixture) assertIncrement(name string, count int) {
   445  	runningCount := 0
   446  	for _, c := range f.a.Counts {
   447  		if c.Name == name {
   448  			runningCount += c.N
   449  		}
   450  	}
   451  
   452  	assert.Equalf(f.t, count, runningCount, "Expected the total count to be %d, got %d", count, runningCount)
   453  }