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  }