| // 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) |
| } |
| }) |
| } |
| } |