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 }