go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/config_service/testutil/testing.go (about) 1 // Copyright 2023 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 testutil reduces the boilerplate for testing in LUCI Config. 16 package testutil 17 18 import ( 19 "bytes" 20 "context" 21 "crypto/sha256" 22 "encoding/hex" 23 "errors" 24 "fmt" 25 "io" 26 "os" 27 "path/filepath" 28 "strconv" 29 "testing" 30 "time" 31 32 "github.com/klauspost/compress/gzip" 33 "google.golang.org/protobuf/encoding/prototext" 34 "google.golang.org/protobuf/proto" 35 36 "go.chromium.org/luci/common/clock" 37 "go.chromium.org/luci/common/clock/testclock" 38 "go.chromium.org/luci/common/gcloud/gs" 39 "go.chromium.org/luci/common/logging" 40 "go.chromium.org/luci/common/logging/gologger" 41 "go.chromium.org/luci/config" 42 "go.chromium.org/luci/gae/impl/memory" 43 "go.chromium.org/luci/gae/service/datastore" 44 "go.chromium.org/luci/server/auth" 45 "go.chromium.org/luci/server/auth/signing" 46 "go.chromium.org/luci/server/auth/signing/signingtest" 47 "go.chromium.org/luci/server/caching" 48 49 "go.chromium.org/luci/config_service/internal/model" 50 51 . "github.com/smartystreets/goconvey/convey" 52 ) 53 54 const AppID = "luci-config-dev" 55 const ServiceAccount = "luci-config@luci-config-dev.iam.gserviceaccount.com" 56 const TestGsBucket = "test-bucket" 57 58 // SetupContext sets up testing common context for LUCI Config tests. 59 func SetupContext() context.Context { 60 ctx := context.Background() 61 utc := time.Date(2023, time.March, 4, 17, 30, 00, 0, time.UTC) 62 ctx, _ = testclock.UseTime(ctx, utc) 63 if testing.Verbose() { 64 ctx = logging.SetLevel(gologger.StdConfig.Use(ctx), logging.Debug) 65 } else { 66 ctx = logging.SetLevel(gologger.StdConfig.Use(ctx), logging.Info) 67 } 68 69 ctx = memory.UseWithAppID(ctx, "dev~"+AppID) 70 datastore.GetTestable(ctx).Consistent(true) 71 // Intentionally not enabling AutoIndex to ensure the composite index must 72 // be explicitly added to index.yaml. 73 datastore.GetTestable(ctx).AutoIndex(false) 74 path, err := os.Getwd() 75 if err != nil { 76 panic(fmt.Errorf("can not get the cwd: %w", err)) 77 } 78 for filepath.Base(path) != "config_service" { 79 path = filepath.Dir(path) 80 } 81 if path == "." { 82 panic(errors.New("can not find the root of config_service; may be the package is renamed?")) 83 } 84 85 file, err := os.Open(filepath.Join(path, "cmd", "config_server", "index.yaml")) 86 if err != nil { 87 panic(fmt.Errorf("failed to open index.yaml file: %w", err)) 88 } 89 indexDefs, err := datastore.ParseIndexYAML(file) 90 if err != nil { 91 panic(fmt.Errorf("failed to parse index.yaml file: %w", err)) 92 } 93 datastore.GetTestable(ctx).AddIndexes(indexDefs...) 94 95 ctx = caching.WithEmptyProcessCache(ctx) 96 ctx = auth.ModifyConfig(ctx, func(cfg auth.Config) auth.Config { 97 cfg.Signer = signingtest.NewSigner(&signing.ServiceInfo{ 98 AppID: AppID, 99 ServiceAccountName: ServiceAccount, 100 }) 101 return cfg 102 }) 103 return ctx 104 } 105 106 // InjectConfigSet writes a new revision for the provided config set. 107 // 108 // The revision ID is a monotonically increasing integer. 109 func InjectConfigSet(ctx context.Context, cfgSet config.Set, configs map[string]proto.Message) { 110 cs := &model.ConfigSet{ 111 ID: cfgSet, 112 } 113 switch err := datastore.Get(ctx, cs); err { 114 case datastore.ErrNoSuchEntity: 115 cs.LatestRevision.ID = "1" 116 case nil: 117 prevID, err := strconv.Atoi(cs.LatestRevision.ID) 118 So(err, ShouldBeNil) 119 cs.LatestRevision.ID = strconv.Itoa(prevID + 1) 120 default: 121 So(err, ShouldBeNil) 122 } 123 cs.LatestRevision.CommitTime = clock.Now(ctx) 124 125 var files []*model.File 126 for filepath, msg := range configs { 127 content, err := prototext.Marshal(msg) 128 So(err, ShouldBeNil) 129 var compressed bytes.Buffer 130 gw := gzip.NewWriter(&compressed) 131 sha := sha256.New() 132 mw := io.MultiWriter(sha, gw) 133 _, err = mw.Write(content) 134 So(err, ShouldBeNil) 135 So(gw.Close(), ShouldBeNil) 136 files = append(files, &model.File{ 137 Path: filepath, 138 Revision: datastore.MakeKey(ctx, model.ConfigSetKind, string(cfgSet), model.RevisionKind, cs.LatestRevision.ID), 139 Content: compressed.Bytes(), 140 ContentSHA256: hex.EncodeToString(sha.Sum(nil)), 141 Size: int64(len(content)), 142 GcsURI: gs.MakePath(TestGsBucket, filepath), 143 }) 144 } 145 So(datastore.Put(ctx, cs, files), ShouldBeNil) 146 } 147 148 // InjectSelfConfigs is a shorthand for updating LUCI Config self config. 149 func InjectSelfConfigs(ctx context.Context, configs map[string]proto.Message) { 150 InjectConfigSet(ctx, config.MustServiceSet(AppID), configs) 151 }