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 }