go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/configs/prjcfg/loader_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 prjcfg
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	"go.chromium.org/luci/gae/service/datastore"
    22  
    23  	cfgpb "go.chromium.org/luci/cv/api/config/v2"
    24  	"go.chromium.org/luci/cv/internal/cvtesting"
    25  
    26  	. "github.com/smartystreets/goconvey/convey"
    27  	. "go.chromium.org/luci/common/testing/assertions"
    28  )
    29  
    30  func mockUpdateProjectConfig(ctx context.Context, project string, cgs []*cfgpb.ConfigGroup) error {
    31  	const cfgHash = "sha256:deadbeef"
    32  	var cfgGroups []*ConfigGroup
    33  	var cgNames []string
    34  	for _, cg := range cgs {
    35  		cfgGroups = append(cfgGroups, &ConfigGroup{
    36  			ID:      MakeConfigGroupID(cfgHash, cg.Name),
    37  			Project: datastore.MakeKey(ctx, projectConfigKind, project),
    38  			Content: cg,
    39  		})
    40  		cgNames = append(cgNames, cg.Name)
    41  	}
    42  	pc := &ProjectConfig{
    43  		Project:          project,
    44  		Enabled:          true,
    45  		Hash:             cfgHash,
    46  		ExternalHash:     cfgHash + "external",
    47  		ConfigGroupNames: cgNames,
    48  		SchemaVersion:    SchemaVersion,
    49  	}
    50  	cfgHashInfo := &ConfigHashInfo{
    51  		Hash:             cfgHash,
    52  		Project:          datastore.MakeKey(ctx, projectConfigKind, project),
    53  		ConfigGroupNames: cgNames,
    54  		GitRevision:      "abcdef123456",
    55  	}
    56  	return datastore.Put(ctx, pc, cfgHashInfo, cfgGroups)
    57  }
    58  
    59  func mockDisableProjectConfig(ctx context.Context, project string, cgs []*cfgpb.ConfigGroup) error {
    60  	const cfgHash = "sha256:deadbeef"
    61  	var cgNames []string
    62  	for _, cg := range cgs {
    63  		cgNames = append(cgNames, cg.Name)
    64  	}
    65  	pc := &ProjectConfig{
    66  		Project:          project,
    67  		Enabled:          false,
    68  		Hash:             cfgHash,
    69  		ExternalHash:     cfgHash + "external",
    70  		ConfigGroupNames: cgNames,
    71  		SchemaVersion:    SchemaVersion,
    72  	}
    73  	return datastore.Put(ctx, pc)
    74  }
    75  
    76  func TestLoadingConfigs(t *testing.T) {
    77  	t.Parallel()
    78  	Convey("Load project config works", t, func() {
    79  		ct := cvtesting.Test{}
    80  		ctx, cancel := ct.SetUp(t)
    81  		defer cancel()
    82  
    83  		const project = "chromium"
    84  		cfgGroups := []*cfgpb.ConfigGroup{
    85  			{
    86  				Name: "branch_m100",
    87  				Gerrit: []*cfgpb.ConfigGroup_Gerrit{
    88  					{
    89  						Url: "https://chromium-review.googlesource.com/",
    90  						Projects: []*cfgpb.ConfigGroup_Gerrit_Project{
    91  							{
    92  								Name:      "chromium/src",
    93  								RefRegexp: []string{"refs/heads/branch_m100"},
    94  							},
    95  						},
    96  					},
    97  				},
    98  			},
    99  			{
   100  				Name: "main",
   101  				Gerrit: []*cfgpb.ConfigGroup_Gerrit{
   102  					{
   103  						Url: "https://chromium-review.googlesource.com/",
   104  						Projects: []*cfgpb.ConfigGroup_Gerrit_Project{
   105  							{
   106  								Name:      "chromium/src",
   107  								RefRegexp: []string{"refs/heads/main"},
   108  							},
   109  						},
   110  					},
   111  				},
   112  			},
   113  		}
   114  
   115  		So(mockUpdateProjectConfig(ctx, project, cfgGroups), ShouldBeNil)
   116  
   117  		Convey("Not existing project", func() {
   118  			m, err := GetLatestMeta(ctx, "not_chromium")
   119  			So(err, ShouldBeNil)
   120  			So(m.Exists(), ShouldBeFalse)
   121  			So(func() { m.Hash() }, ShouldPanic)
   122  		})
   123  
   124  		Convey("Enabled project", func() {
   125  			m, err := GetLatestMeta(ctx, project)
   126  			So(err, ShouldBeNil)
   127  			So(m.Exists(), ShouldBeTrue)
   128  			So(m.Status, ShouldEqual, StatusEnabled)
   129  			So(m.ConfigGroupNames, ShouldResemble, []string{cfgGroups[0].Name, cfgGroups[1].Name})
   130  			So(m.ConfigGroupIDs, ShouldResemble, []ConfigGroupID{
   131  				ConfigGroupID(m.Hash() + "/" + cfgGroups[0].Name),
   132  				ConfigGroupID(m.Hash() + "/" + cfgGroups[1].Name),
   133  			})
   134  
   135  			m2, err := GetHashMeta(ctx, project, m.Hash())
   136  			So(err, ShouldBeNil)
   137  			So(m2, ShouldResemble, m)
   138  
   139  			cgs, err := m.GetConfigGroups(ctx)
   140  			So(err, ShouldBeNil)
   141  			So(len(cgs), ShouldEqual, 2)
   142  			So(cgs[0].Project.StringID(), ShouldEqual, project)
   143  			So(cgs[0].Content, ShouldResembleProto, cfgGroups[0])
   144  			So(cgs[1].Project.StringID(), ShouldEqual, project)
   145  			So(cgs[1].Content, ShouldResembleProto, cfgGroups[1])
   146  		})
   147  
   148  		cfgGroups = append(cfgGroups, &cfgpb.ConfigGroup{
   149  			Name: "branch_m200",
   150  			Gerrit: []*cfgpb.ConfigGroup_Gerrit{
   151  				{
   152  					Url: "https://chromium-review.googlesource.com/",
   153  					Projects: []*cfgpb.ConfigGroup_Gerrit_Project{
   154  						{
   155  							Name:      "chromium/src",
   156  							RefRegexp: []string{"refs/heads/branch_m200"},
   157  						},
   158  					},
   159  				},
   160  			},
   161  		})
   162  		So(mockUpdateProjectConfig(ctx, project, cfgGroups), ShouldBeNil)
   163  
   164  		Convey("Updated project", func() {
   165  			m, err := GetLatestMeta(ctx, project)
   166  			So(err, ShouldBeNil)
   167  			So(m.Exists(), ShouldBeTrue)
   168  			So(m.Status, ShouldEqual, StatusEnabled)
   169  			h := m.Hash()
   170  			So(h, ShouldStartWith, "sha256:")
   171  			So(m.ConfigGroupIDs, ShouldResemble, []ConfigGroupID{
   172  				ConfigGroupID(h + "/" + cfgGroups[0].Name),
   173  				ConfigGroupID(h + "/" + cfgGroups[1].Name),
   174  				ConfigGroupID(h + "/" + cfgGroups[2].Name),
   175  			})
   176  			cgs, err := m.GetConfigGroups(ctx)
   177  			So(err, ShouldBeNil)
   178  			So(len(cgs), ShouldEqual, 3)
   179  
   180  			Convey("reading ConfigGroup directly works", func() {
   181  				cg, err := GetConfigGroup(ctx, project, m.ConfigGroupIDs[2])
   182  				So(err, ShouldBeNil)
   183  				So(cg.Content, ShouldResembleProto, cfgGroups[2])
   184  			})
   185  		})
   186  
   187  		So(mockDisableProjectConfig(ctx, project, cfgGroups), ShouldBeNil)
   188  
   189  		Convey("Disabled project", func() {
   190  			m, err := GetLatestMeta(ctx, project)
   191  			So(err, ShouldBeNil)
   192  			So(m.Exists(), ShouldBeTrue)
   193  			So(m.Status, ShouldEqual, StatusDisabled)
   194  			So(len(m.ConfigGroupIDs), ShouldEqual, 3)
   195  			cgs, err := m.GetConfigGroups(ctx)
   196  			So(err, ShouldBeNil)
   197  			So(len(cgs), ShouldEqual, 3)
   198  		})
   199  
   200  		// Re-enable the project.
   201  		So(mockUpdateProjectConfig(ctx, project, cfgGroups), ShouldBeNil)
   202  
   203  		Convey("Re-enabled project", func() {
   204  			m, err := GetLatestMeta(ctx, project)
   205  			So(err, ShouldBeNil)
   206  			So(m.Exists(), ShouldBeTrue)
   207  			So(m.Status, ShouldEqual, StatusEnabled)
   208  		})
   209  
   210  		m, err := GetLatestMeta(ctx, project)
   211  		So(err, ShouldBeNil)
   212  		cgs, err := m.GetConfigGroups(ctx)
   213  		So(err, ShouldBeNil)
   214  
   215  		Convey("Deleted project", func() {
   216  			So(datastore.Delete(ctx, &ProjectConfig{Project: project}, cgs), ShouldBeNil)
   217  
   218  			m, err = GetLatestMeta(ctx, project)
   219  			So(err, ShouldBeNil)
   220  			So(m.Exists(), ShouldBeFalse)
   221  		})
   222  
   223  		Convey("reading partially deleted project", func() {
   224  			So(datastore.Delete(ctx, cgs[1]), ShouldBeNil)
   225  			_, err = m.GetConfigGroups(ctx)
   226  			So(err, ShouldErrLike, "ConfigGroups for")
   227  			So(err, ShouldErrLike, "not found")
   228  			So(datastore.IsErrNoSuchEntity(err), ShouldBeTrue)
   229  
   230  			// Can still read individual ConfigGroups.
   231  			cg, err := GetConfigGroup(ctx, project, m.ConfigGroupIDs[0])
   232  			So(err, ShouldBeNil)
   233  			So(cg.Content, ShouldResembleProto, cfgGroups[0])
   234  			cg, err = GetConfigGroup(ctx, project, m.ConfigGroupIDs[2])
   235  			So(err, ShouldBeNil)
   236  			So(cg.Content, ShouldResembleProto, cfgGroups[2])
   237  			// ... except the deleted one.
   238  			_, err = GetConfigGroup(ctx, project, m.ConfigGroupIDs[1])
   239  			So(datastore.IsErrNoSuchEntity(err), ShouldBeTrue)
   240  		})
   241  	})
   242  }