go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/casviewer/handlers_test.go (about)

     1  // Copyright 2020 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package casviewer
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"io"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"testing"
    24  
    25  	"github.com/bazelbuild/remote-apis-sdks/go/pkg/digest"
    26  	repb "github.com/bazelbuild/remote-apis/build/bazel/remote/execution/v2"
    27  	. "github.com/smartystreets/goconvey/convey"
    28  	"google.golang.org/protobuf/proto"
    29  
    30  	"go.chromium.org/luci/server/auth"
    31  	"go.chromium.org/luci/server/auth/authtest"
    32  	"go.chromium.org/luci/server/router"
    33  	"go.chromium.org/luci/server/templates"
    34  )
    35  
    36  const testInstance = "projects/test-proj/instances/default_instance"
    37  
    38  func TestHandlers(t *testing.T) {
    39  	t.Parallel()
    40  
    41  	ctx := context.Background()
    42  
    43  	// Basic template rendering tests.
    44  	Convey("Templates", t, func() {
    45  		ctx = auth.WithState(ctx, fakeAuthState())
    46  		c := &router.Context{
    47  			Request: (&http.Request{}).WithContext(ctx),
    48  		}
    49  		templateBundleMW := templates.WithTemplates(getTemplateBundle("test-version-1"))
    50  		templateBundleMW(c, func(c *router.Context) {
    51  			top, err := templates.Render(c.Request.Context(), "pages/index.html", nil)
    52  			So(err, ShouldBeNil)
    53  			So(string(top), ShouldContainSubstring, "user@example.com")
    54  			So(string(top), ShouldContainSubstring, "test-version-1")
    55  		})
    56  	})
    57  
    58  	Convey("InstallHandlers", t, func() {
    59  		// Install handlers with fake auth state.
    60  		r := router.New()
    61  		r.Use(router.NewMiddlewareChain(func(c *router.Context, next router.Handler) {
    62  			c.Request = c.Request.WithContext(auth.WithState(c.Request.Context(), fakeAuthState()))
    63  			next(c)
    64  		}))
    65  
    66  		// Inject fake CAS client to cache.
    67  		cl := fakeClient(ctx, t)
    68  		cc := NewClientCache(ctx)
    69  		t.Cleanup(cc.Clear)
    70  		cc.clients[testInstance] = cl
    71  
    72  		InstallHandlers(r, cc, "test-version-1")
    73  
    74  		srv := httptest.NewServer(r)
    75  		t.Cleanup(srv.Close)
    76  
    77  		// Simple blob.
    78  		bd, err := cl.WriteBlob(ctx, []byte{1})
    79  		So(err, ShouldBeNil)
    80  		rSimple := fmt.Sprintf("/%s/blobs/%s/%d", testInstance, bd.Hash, bd.Size)
    81  
    82  		// Directory.
    83  		d := &repb.Directory{
    84  			Files: []*repb.FileNode{
    85  				{
    86  					Name:   "foo",
    87  					Digest: digest.NewFromBlob([]byte{1}).ToProto(),
    88  				},
    89  			},
    90  		}
    91  		b, err := proto.Marshal(d)
    92  		So(err, ShouldBeNil)
    93  		bd, err = cl.WriteBlob(context.Background(), b)
    94  		So(err, ShouldBeNil)
    95  		rDict := fmt.Sprintf("/%s/blobs/%s/%d", testInstance, bd.Hash, bd.Size)
    96  
    97  		// Unknown blob.
    98  		rUnknown := fmt.Sprintf("/%s/blobs/12345/6", testInstance)
    99  
   100  		// Invalid digest size.
   101  		rInvalidDigest := fmt.Sprintf("/%s/blobs/12345/a", testInstance)
   102  
   103  		Convey("rootHanlder", func() {
   104  			resp, err := http.Get(srv.URL)
   105  			So(err, ShouldBeNil)
   106  			defer resp.Body.Close()
   107  
   108  			So(resp.StatusCode, ShouldEqual, http.StatusOK)
   109  			_, err = io.ReadAll(resp.Body)
   110  			So(err, ShouldBeNil)
   111  		})
   112  
   113  		Convey("treeHandler", func() {
   114  			// Not found.
   115  			resp, err := http.Get(srv.URL + rUnknown + "/tree")
   116  			So(err, ShouldBeNil)
   117  			resp.Body.Close()
   118  			So(resp.StatusCode, ShouldEqual, http.StatusNotFound)
   119  
   120  			// Bad Request - Must be Directory.
   121  			resp, err = http.Get(srv.URL + rSimple + "/tree")
   122  			So(err, ShouldBeNil)
   123  			resp.Body.Close()
   124  			So(resp.StatusCode, ShouldEqual, http.StatusBadRequest)
   125  
   126  			// Bad Request - Digest size must be number.
   127  			resp, err = http.Get(srv.URL + rInvalidDigest + "/tree")
   128  			So(err, ShouldBeNil)
   129  			resp.Body.Close()
   130  			So(resp.StatusCode, ShouldEqual, http.StatusBadRequest)
   131  
   132  			// OK.
   133  			resp, err = http.Get(srv.URL + rDict + "/tree")
   134  			So(err, ShouldBeNil)
   135  			resp.Body.Close()
   136  			So(resp.StatusCode, ShouldEqual, http.StatusOK)
   137  		})
   138  
   139  		Convey("getHandler", func() {
   140  			// Not found.
   141  			resp, err := http.Get(srv.URL + rUnknown)
   142  			So(err, ShouldBeNil)
   143  			resp.Body.Close()
   144  			So(resp.StatusCode, ShouldEqual, http.StatusNotFound)
   145  
   146  			// Bad Request - Digest size must be number.
   147  			resp, err = http.Get(srv.URL + rInvalidDigest)
   148  			So(err, ShouldBeNil)
   149  			resp.Body.Close()
   150  			So(resp.StatusCode, ShouldEqual, http.StatusBadRequest)
   151  
   152  			// OK.
   153  			resp, err = http.Get(srv.URL + rDict + "?filename=text.txt")
   154  			So(err, ShouldBeNil)
   155  			resp.Body.Close()
   156  			So(resp.StatusCode, ShouldEqual, http.StatusOK)
   157  		})
   158  
   159  		Convey("checkPermission", func() {
   160  			resp, err := http.Get(
   161  				srv.URL + "/projects/test-proj-no-perm/instances/default_instance/blobs/12345/6/tree")
   162  			So(err, ShouldBeNil)
   163  			defer resp.Body.Close()
   164  			So(resp.StatusCode, ShouldEqual, http.StatusForbidden)
   165  		})
   166  	})
   167  }
   168  
   169  // fakeAuthState returns fake state that has identity and realm permission.
   170  func fakeAuthState() *authtest.FakeState {
   171  	return &authtest.FakeState{
   172  		Identity: "user:user@example.com",
   173  		IdentityPermissions: []authtest.RealmPermission{
   174  			{
   175  				Realm:      "@internal:test-proj/cas-read-only",
   176  				Permission: permMintToken,
   177  			},
   178  		},
   179  	}
   180  }