golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/coordinator/results_test.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build linux || darwin
     6  
     7  package main
     8  
     9  import (
    10  	"context"
    11  	"net/http"
    12  	"net/http/httptest"
    13  	"testing"
    14  
    15  	"golang.org/x/build/cmd/coordinator/protos"
    16  	"google.golang.org/grpc/codes"
    17  	"google.golang.org/grpc/metadata"
    18  	grpcstatus "google.golang.org/grpc/status"
    19  )
    20  
    21  // fakeDashboard implements a fake version of the Build Dashboard API for testing.
    22  // TODO(golang.org/issue/34744) - Remove with build dashboard API client removal.
    23  type fakeDashboard struct {
    24  	returnBody   string
    25  	returnStatus int
    26  }
    27  
    28  func (f *fakeDashboard) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
    29  	if r.Method != "POST" {
    30  		http.Error(rw, "method must be POST", http.StatusBadRequest)
    31  		return
    32  	}
    33  	if r.URL.Path != "/clear-results" {
    34  		http.NotFound(rw, r)
    35  		return
    36  	}
    37  	if f.returnStatus != 0 && f.returnStatus != http.StatusOK {
    38  		http.Error(rw, `{"Error": "`+http.StatusText(f.returnStatus)+`"}`, f.returnStatus)
    39  		return
    40  	}
    41  	r.ParseForm()
    42  	if r.FormValue("builder") == "" || r.FormValue("hash") == "" || r.FormValue("key") == "" {
    43  		http.Error(rw, `{"Error": "missing builder, hash, or key"}`, http.StatusBadRequest)
    44  		return
    45  	}
    46  	if f.returnBody == "" {
    47  		rw.Write([]byte("{}"))
    48  		return
    49  	}
    50  	rw.Write([]byte(f.returnBody))
    51  	return
    52  }
    53  
    54  func TestClearResults(t *testing.T) {
    55  	req := &protos.ClearResultsRequest{Builder: "somebuilder", Hash: "somehash"}
    56  	fd := new(fakeDashboard)
    57  	s := httptest.NewServer(fd)
    58  	defer s.Close()
    59  
    60  	md := metadata.New(map[string]string{"coordinator-authorization": "builder mykey"})
    61  	ctx := metadata.NewIncomingContext(context.Background(), md)
    62  	gs := &gRPCServer{dashboardURL: s.URL}
    63  	_, err := gs.ClearResults(ctx, req)
    64  	if err != nil {
    65  		t.Errorf("cli.ClearResults(%v, %v) = _, %v, wanted no error", ctx, req, err)
    66  	}
    67  
    68  	if grpcstatus.Code(err) != codes.OK {
    69  		t.Errorf("cli.ClearResults(%v, %v) = _, %v, wanted %v", ctx, req, err, codes.OK)
    70  	}
    71  }
    72  
    73  func TestClearResultsErrors(t *testing.T) {
    74  	cases := []struct {
    75  		desc     string
    76  		key      string
    77  		req      *protos.ClearResultsRequest
    78  		apiCode  int
    79  		apiResp  string
    80  		wantCode codes.Code
    81  	}{
    82  		{
    83  			desc: "missing key",
    84  			req: &protos.ClearResultsRequest{
    85  				Builder: "local",
    86  				Hash:    "ABCDEF1234567890",
    87  			},
    88  			wantCode: codes.Unauthenticated,
    89  		},
    90  		{
    91  			desc: "missing builder",
    92  			key:  "somekey",
    93  			req: &protos.ClearResultsRequest{
    94  				Hash: "ABCDEF1234567890",
    95  			},
    96  			wantCode: codes.InvalidArgument,
    97  		},
    98  		{
    99  			desc: "missing hash",
   100  			key:  "somekey",
   101  			req: &protos.ClearResultsRequest{
   102  				Builder: "local",
   103  			},
   104  			wantCode: codes.InvalidArgument,
   105  		},
   106  		{
   107  			desc: "dashboard API error",
   108  			key:  "somekey",
   109  			req: &protos.ClearResultsRequest{
   110  				Builder: "local",
   111  				Hash:    "ABCDEF1234567890",
   112  			},
   113  			apiCode:  http.StatusBadRequest,
   114  			wantCode: codes.InvalidArgument,
   115  		},
   116  		{
   117  			desc: "dashboard API unknown status",
   118  			key:  "somekey",
   119  			req: &protos.ClearResultsRequest{
   120  				Builder: "local",
   121  				Hash:    "ABCDEF1234567890",
   122  			},
   123  			apiCode:  http.StatusPermanentRedirect,
   124  			wantCode: codes.Internal,
   125  		},
   126  		{
   127  			desc: "dashboard API retryable error",
   128  			key:  "somekey",
   129  			req: &protos.ClearResultsRequest{
   130  				Builder: "local",
   131  				Hash:    "ABCDEF1234567890",
   132  			},
   133  			apiCode:  http.StatusOK,
   134  			apiResp:  `{"Error": "datastore: concurrent transaction"}`,
   135  			wantCode: codes.Aborted,
   136  		},
   137  		{
   138  			desc: "dashboard API other error",
   139  			key:  "somekey",
   140  			req: &protos.ClearResultsRequest{
   141  				Builder: "local",
   142  				Hash:    "ABCDEF1234567890",
   143  			},
   144  			apiCode:  http.StatusOK,
   145  			apiResp:  `{"Error": "no matching builder found"}`,
   146  			wantCode: codes.FailedPrecondition,
   147  		},
   148  	}
   149  	for _, c := range cases {
   150  		t.Run(c.desc, func(t *testing.T) {
   151  			fd := &fakeDashboard{returnStatus: c.apiCode, returnBody: c.apiResp}
   152  			s := httptest.NewServer(fd)
   153  			defer s.Close()
   154  
   155  			md := metadata.New(map[string]string{"coordinator-authorization": "builder " + c.key})
   156  			ctx := metadata.NewIncomingContext(context.Background(), md)
   157  			gs := &gRPCServer{dashboardURL: s.URL}
   158  			_, err := gs.ClearResults(ctx, c.req)
   159  
   160  			if grpcstatus.Code(err) != c.wantCode {
   161  				t.Errorf("cli.ClearResults(%v, %v) = _, %v, wanted %v", ctx, c.req, err, c.wantCode)
   162  			}
   163  		})
   164  	}
   165  }