go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/config/impl/remote/remote_test.go (about) 1 // Copyright 2015 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 remote 16 17 import ( 18 "bytes" 19 "compress/zlib" 20 "context" 21 "encoding/base64" 22 "encoding/json" 23 "fmt" 24 "io" 25 "net/http" 26 "net/http/httptest" 27 "net/url" 28 "testing" 29 30 "go.chromium.org/luci/config" 31 32 . "github.com/smartystreets/goconvey/convey" 33 ) 34 35 func encodeToB(s string, compress bool) string { 36 var b []byte 37 if compress { 38 buf := &bytes.Buffer{} 39 w := zlib.NewWriter(buf) 40 if _, err := io.WriteString(w, s); err != nil { 41 panic(err) 42 } 43 if err := w.Close(); err != nil { 44 panic(err) 45 } 46 b = buf.Bytes() 47 } else { 48 b = []byte(s) 49 } 50 return base64.StdEncoding.EncodeToString(b) 51 } 52 53 func testTools(code int, resp any) (*httptest.Server, config.Interface) { 54 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 55 w.WriteHeader(code) 56 w.Header().Set("Content-Type", "application/json") 57 marsh, _ := json.Marshal(resp) 58 fmt.Fprintln(w, string(marsh)) 59 })) 60 61 u, err := url.Parse(server.URL) 62 if err != nil { 63 panic(err) 64 } 65 return server, NewV1(u.Host, true, nil) 66 } 67 68 func TestRemoteCalls(t *testing.T) { 69 t.Parallel() 70 71 ctx := context.Background() 72 73 Convey("Should pass through calls to the generated API", t, func() { 74 Convey("GetConfig", func() { 75 server, remoteImpl := testTools(200, map[string]string{ 76 "content": encodeToB("hi", false), 77 "content_hash": "bar", 78 "revision": "3", 79 "url": "config_url", 80 }) 81 defer server.Close() 82 83 res, err := remoteImpl.GetConfig(ctx, "a", "b", false) 84 85 So(err, ShouldBeNil) 86 So(*res, ShouldResemble, config.Config{ 87 Meta: config.Meta{ 88 ConfigSet: "a", 89 Path: "b", 90 ContentHash: "bar", 91 Revision: "3", 92 ViewURL: "config_url", 93 }, 94 Content: "hi", 95 }) 96 }) 97 Convey("GetConfig (zlib)", func() { 98 server, remoteImpl := testTools(200, map[string]any{ 99 "content": encodeToB("hi", true), 100 "content_hash": "bar", 101 "is_zlib_compressed": true, 102 "revision": "3", 103 "url": "config_url", 104 }) 105 defer server.Close() 106 107 res, err := remoteImpl.GetConfig(ctx, "a", "b", false) 108 109 So(err, ShouldBeNil) 110 So(*res, ShouldResemble, config.Config{ 111 Meta: config.Meta{ 112 ConfigSet: "a", 113 Path: "b", 114 ContentHash: "bar", 115 Revision: "3", 116 ViewURL: "config_url", 117 }, 118 Content: "hi", 119 }) 120 }) 121 Convey("ListFiles", func() { 122 server, remoteImpl := testTools(200, 123 map[string]any{ 124 "config_sets": []any{ 125 map[string]any{ 126 "files": []any{ 127 map[string]any{ 128 "path": "first.template", 129 }, 130 map[string]any{ 131 "path": "second.template", 132 }, 133 }, 134 "config_set": "a", 135 }, 136 }, 137 }, 138 ) 139 defer server.Close() 140 141 res, err := remoteImpl.ListFiles(ctx, "a") 142 143 So(err, ShouldBeNil) 144 So(res, ShouldResemble, []string{"first.template", "second.template"}) 145 }) 146 Convey("GetProjectConfigs", func() { 147 server, remoteImpl := testTools(200, map[string]any{ 148 "configs": [...]any{map[string]string{ 149 "config_set": "a", 150 "content": encodeToB("hi", false), 151 "content_hash": "bar", 152 "revision": "3", 153 }}, 154 }) 155 defer server.Close() 156 157 res, err := remoteImpl.GetProjectConfigs(ctx, "b", false) 158 159 So(err, ShouldBeNil) 160 So(res, ShouldNotBeEmpty) 161 So(len(res), ShouldEqual, 1) 162 So(res[0], ShouldResemble, config.Config{ 163 Meta: config.Meta{ 164 ConfigSet: "a", 165 Path: "b", 166 ContentHash: "bar", 167 Revision: "3", 168 }, 169 Content: "hi", 170 }) 171 }) 172 Convey("GetProjectConfigs metaOnly", func() { 173 server, remoteImpl := testTools(200, map[string]any{ 174 "configs": [...]any{map[string]string{ 175 "config_set": "a", 176 "content_hash": "bar", 177 "revision": "3", 178 }}, 179 }) 180 defer server.Close() 181 182 res, err := remoteImpl.GetProjectConfigs(ctx, "b", true) 183 184 So(err, ShouldBeNil) 185 So(res, ShouldNotBeEmpty) 186 So(len(res), ShouldEqual, 1) 187 So(res[0], ShouldResemble, config.Config{ 188 Meta: config.Meta{ 189 ConfigSet: "a", 190 Path: "b", 191 ContentHash: "bar", 192 Revision: "3", 193 }, 194 }) 195 }) 196 Convey("GetProjects", func() { 197 id := "blink" 198 name := "Blink" 199 URL, err := url.Parse("http://example.com") 200 if err != nil { 201 panic(err) 202 } 203 204 server, remoteImpl := testTools(200, map[string]any{ 205 "projects": [...]any{map[string]string{ 206 "id": id, 207 "name": name, 208 "repo_type": "GITILES", 209 "repo_url": URL.String(), 210 }}, 211 }) 212 defer server.Close() 213 214 res, err := remoteImpl.GetProjects(ctx) 215 216 So(err, ShouldBeNil) 217 So(res, ShouldNotBeEmpty) 218 So(len(res), ShouldEqual, 1) 219 So(res[0], ShouldResemble, config.Project{ 220 ID: id, 221 Name: name, 222 RepoType: config.GitilesRepo, 223 RepoURL: URL, 224 }) 225 }) 226 }) 227 228 Convey("Should handle errors well", t, func() { 229 Convey("Should pass through HTTP errors", func() { 230 remoteImpl := NewV1("example.com", true, func(context.Context) (*http.Client, error) { 231 return &http.Client{ 232 Transport: failingRoundTripper{}, 233 }, nil 234 }) 235 236 _, err := remoteImpl.GetConfig(ctx, "a", "b", false) 237 So(err, ShouldNotBeNil) 238 _, err = remoteImpl.GetProjectConfigs(ctx, "a", false) 239 So(err, ShouldNotBeNil) 240 _, err = remoteImpl.GetProjects(ctx) 241 So(err, ShouldNotBeNil) 242 }) 243 }) 244 } 245 246 type failingRoundTripper struct{} 247 248 func (t failingRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) { 249 return nil, fmt.Errorf("IM AM ERRAR") 250 }