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  }