blob: 372d5bfec700121651e987bc74bf364083da211d [file] [log] [blame]
// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:build go1.16 && (linux || darwin)
// +build go1.16
// +build linux darwin
package main
import (
"context"
"net/http"
"net/http/httptest"
"testing"
"golang.org/x/build/cmd/coordinator/protos"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
grpcstatus "google.golang.org/grpc/status"
)
// fakeDashboard implements a fake version of the Build Dashboard API for testing.
// TODO(golang.org/issue/34744) - Remove with build dashboard API client removal.
type fakeDashboard struct {
returnBody string
returnStatus int
}
func (f *fakeDashboard) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(rw, "method must be POST", http.StatusBadRequest)
return
}
if r.URL.Path != "/clear-results" {
http.NotFound(rw, r)
return
}
if f.returnStatus != 0 && f.returnStatus != http.StatusOK {
http.Error(rw, `{"Error": "`+http.StatusText(f.returnStatus)+`"}`, f.returnStatus)
return
}
r.ParseForm()
if r.FormValue("builder") == "" || r.FormValue("hash") == "" || r.FormValue("key") == "" {
http.Error(rw, `{"Error": "missing builder, hash, or key"}`, http.StatusBadRequest)
return
}
if f.returnBody == "" {
rw.Write([]byte("{}"))
return
}
rw.Write([]byte(f.returnBody))
return
}
func TestClearResults(t *testing.T) {
req := &protos.ClearResultsRequest{Builder: "somebuilder", Hash: "somehash"}
fd := new(fakeDashboard)
s := httptest.NewServer(fd)
defer s.Close()
md := metadata.New(map[string]string{"coordinator-authorization": "builder mykey"})
ctx := metadata.NewIncomingContext(context.Background(), md)
gs := &gRPCServer{dashboardURL: s.URL}
_, err := gs.ClearResults(ctx, req)
if err != nil {
t.Errorf("cli.ClearResults(%v, %v) = _, %v, wanted no error", ctx, req, err)
}
if grpcstatus.Code(err) != codes.OK {
t.Errorf("cli.ClearResults(%v, %v) = _, %v, wanted %v", ctx, req, err, codes.OK)
}
}
func TestClearResultsErrors(t *testing.T) {
cases := []struct {
desc string
key string
req *protos.ClearResultsRequest
apiCode int
apiResp string
wantCode codes.Code
}{
{
desc: "missing key",
req: &protos.ClearResultsRequest{
Builder: "local",
Hash: "ABCDEF1234567890",
},
wantCode: codes.Unauthenticated,
},
{
desc: "missing builder",
key: "somekey",
req: &protos.ClearResultsRequest{
Hash: "ABCDEF1234567890",
},
wantCode: codes.InvalidArgument,
},
{
desc: "missing hash",
key: "somekey",
req: &protos.ClearResultsRequest{
Builder: "local",
},
wantCode: codes.InvalidArgument,
},
{
desc: "dashboard API error",
key: "somekey",
req: &protos.ClearResultsRequest{
Builder: "local",
Hash: "ABCDEF1234567890",
},
apiCode: http.StatusBadRequest,
wantCode: codes.InvalidArgument,
},
{
desc: "dashboard API unknown status",
key: "somekey",
req: &protos.ClearResultsRequest{
Builder: "local",
Hash: "ABCDEF1234567890",
},
apiCode: http.StatusPermanentRedirect,
wantCode: codes.Internal,
},
{
desc: "dashboard API retryable error",
key: "somekey",
req: &protos.ClearResultsRequest{
Builder: "local",
Hash: "ABCDEF1234567890",
},
apiCode: http.StatusOK,
apiResp: `{"Error": "datastore: concurrent transaction"}`,
wantCode: codes.Aborted,
},
{
desc: "dashboard API other error",
key: "somekey",
req: &protos.ClearResultsRequest{
Builder: "local",
Hash: "ABCDEF1234567890",
},
apiCode: http.StatusOK,
apiResp: `{"Error": "no matching builder found"}`,
wantCode: codes.FailedPrecondition,
},
}
for _, c := range cases {
t.Run(c.desc, func(t *testing.T) {
fd := &fakeDashboard{returnStatus: c.apiCode, returnBody: c.apiResp}
s := httptest.NewServer(fd)
defer s.Close()
md := metadata.New(map[string]string{"coordinator-authorization": "builder " + c.key})
ctx := metadata.NewIncomingContext(context.Background(), md)
gs := &gRPCServer{dashboardURL: s.URL}
_, err := gs.ClearResults(ctx, c.req)
if grpcstatus.Code(err) != c.wantCode {
t.Errorf("cli.ClearResults(%v, %v) = _, %v, wanted %v", ctx, c.req, err, c.wantCode)
}
})
}
}