github.com/m-lab/locate@v0.17.6/handler/monitoring_test.go (about)

     1  package handler
     2  
     3  import (
     4  	"encoding/json"
     5  	"net/http"
     6  	"net/http/httptest"
     7  	"strings"
     8  	"testing"
     9  	"time"
    10  
    11  	"gopkg.in/square/go-jose.v2/jwt"
    12  
    13  	"github.com/go-test/deep"
    14  	"github.com/m-lab/access/controller"
    15  	"github.com/m-lab/go/rtx"
    16  	v2 "github.com/m-lab/locate/api/v2"
    17  	"github.com/m-lab/locate/clientgeo"
    18  	"github.com/m-lab/locate/static"
    19  	prom "github.com/prometheus/client_golang/api/prometheus/v1"
    20  )
    21  
    22  func TestClient_Monitoring(t *testing.T) {
    23  	tests := []struct {
    24  		name            string
    25  		claim           *jwt.Claims
    26  		signer          Signer
    27  		locator         LocatorV2
    28  		path            string
    29  		wantTokenPrefix string
    30  		wantKey         string
    31  		wantErr         *v2.Error
    32  	}{
    33  		{
    34  			name: "success-machine",
    35  			claim: &jwt.Claims{
    36  				Issuer:   static.IssuerMonitoring,
    37  				Subject:  "mlab1-lga0t.mlab-oti.measurement-lab.org",
    38  				Audience: jwt.Audience{static.AudienceLocate},
    39  				Expiry:   jwt.NewNumericDate(time.Now().Add(time.Minute)),
    40  			},
    41  			signer: &fakeSigner{},
    42  			locator: &fakeLocatorV2{
    43  				targets: []v2.Target{{Machine: "mlab1-lga0t.measurement-lab.org"}},
    44  			},
    45  			path:    "ndt/ndt5",
    46  			wantKey: "wss://:3010/ndt_protocol",
    47  			// The fakeSigner generates synthetic access tokens based on the claim constructed by the handler.
    48  			// The audience (machine), the subject (monitoring), and issuer (locate). The suffix is the timestamp, which varies.
    49  			wantTokenPrefix: "mlab1-lga0t.mlab-oti.measurement-lab.org--monitoring--locate--",
    50  		},
    51  		{
    52  			name:  "error-no-claim",
    53  			claim: nil,
    54  			path:  "ndt/ndt5",
    55  			wantErr: &v2.Error{
    56  				Type:   "claim",
    57  				Title:  "Must provide access_token",
    58  				Status: http.StatusBadRequest,
    59  			},
    60  		},
    61  		{
    62  			name: "error-bad-subject",
    63  			claim: &jwt.Claims{
    64  				Issuer:   static.IssuerMonitoring,
    65  				Subject:  "this-is-an-invalid-hostname",
    66  				Audience: jwt.Audience{static.AudienceLocate},
    67  				Expiry:   jwt.NewNumericDate(time.Now().Add(time.Minute)),
    68  			},
    69  			path: "ndt/ndt5",
    70  			wantErr: &v2.Error{
    71  				Type:   "subject",
    72  				Title:  "Subject must be specified",
    73  				Status: http.StatusBadRequest,
    74  			},
    75  		},
    76  		{
    77  			name: "error-invalid-service-path",
    78  			claim: &jwt.Claims{
    79  				Issuer:   static.IssuerMonitoring,
    80  				Subject:  "mlab1-lga0t.mlab-oti.measurement-lab.org",
    81  				Audience: jwt.Audience{static.AudienceLocate},
    82  				Expiry:   jwt.NewNumericDate(time.Now().Add(time.Minute)),
    83  			},
    84  			path: "ndt/this-is-an-invalid-service-name",
    85  			wantErr: &v2.Error{
    86  				Type:   "config",
    87  				Title:  "Unknown service: ndt/this-is-an-invalid-service-name",
    88  				Status: http.StatusBadRequest,
    89  			},
    90  		},
    91  	}
    92  	for _, tt := range tests {
    93  		t.Run(tt.name, func(t *testing.T) {
    94  			cl := clientgeo.NewAppEngineLocator()
    95  			c := NewClient("mlab-sandbox", tt.signer, tt.locator, cl, prom.NewAPI(nil), nil, nil, nil)
    96  			rw := httptest.NewRecorder()
    97  			req := httptest.NewRequest(http.MethodGet, "/v2/platform/monitoring/"+tt.path, nil)
    98  			req = req.Clone(controller.SetClaim(req.Context(), tt.claim))
    99  
   100  			c.Monitoring(rw, req)
   101  
   102  			q := v2.MonitoringResult{}
   103  			err := json.Unmarshal(rw.Body.Bytes(), &q)
   104  			rtx.Must(err, "Failed to unmarshal")
   105  
   106  			if tt.wantErr != nil {
   107  				if q.Error == nil {
   108  					t.Fatal("Monitoring() expected error, got nil")
   109  				}
   110  				if diff := deep.Equal(q.Error, tt.wantErr); diff != nil {
   111  					t.Errorf("Monitoring() expected error: got: %#v", diff)
   112  				}
   113  				return
   114  			}
   115  			if q.Target == nil {
   116  				t.Fatalf("Monitoring() returned nil Target")
   117  			}
   118  			if q.Target.Machine != tt.claim.Subject {
   119  				t.Errorf("Monitoring() returned different machine than claim subject; got %s, want %s",
   120  					q.Target.Machine, tt.claim.Subject)
   121  			}
   122  			if len(q.Target.URLs) != len(static.Configs[tt.path]) {
   123  				t.Errorf("Monitoring() returned incomplete urls; got %d, want %d",
   124  					len(q.Target.URLs), len(static.Configs[tt.path]))
   125  			}
   126  			if q.AccessToken == "" {
   127  				t.Errorf("Monitoring() expected AccessToken, got empty string")
   128  			}
   129  			if strings.Contains(tt.wantTokenPrefix, q.AccessToken) {
   130  				t.Errorf("Monitoring() did not get access token;\ngot %s,\nwant %s", q.AccessToken, tt.wantTokenPrefix)
   131  			}
   132  			if _, ok := q.Target.URLs[tt.wantKey]; !ok {
   133  				t.Errorf("Monitoring() result missing URLs key; want %q", tt.wantKey)
   134  			}
   135  		})
   136  	}
   137  }