go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cipd/appengine/impl/testutil/metadata_test.go (about)

     1  // Copyright 2018 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
    16  
    17  import (
    18  	"context"
    19  	"testing"
    20  
    21  	api "go.chromium.org/luci/cipd/api/cipd/v1"
    22  
    23  	. "github.com/smartystreets/goconvey/convey"
    24  	. "go.chromium.org/luci/common/testing/assertions"
    25  )
    26  
    27  func TestMetadataStore(t *testing.T) {
    28  	t.Parallel()
    29  
    30  	ctx := context.Background()
    31  
    32  	populateMD := func(s *MetadataStore, prefixes []string) {
    33  		for _, p := range prefixes {
    34  			s.Populate(p, &api.PrefixMetadata{UpdateUser: "user:someone@example.com"})
    35  		}
    36  	}
    37  
    38  	getMD := func(s *MetadataStore, pfx string) []string {
    39  		md, err := s.GetMetadata(ctx, pfx)
    40  		So(err, ShouldBeNil)
    41  		out := make([]string, len(md))
    42  		for i, m := range md {
    43  			out[i] = m.Prefix
    44  		}
    45  		return out
    46  	}
    47  
    48  	visitAll := func(s *MetadataStore, pfx string) (visited []string) {
    49  		err := s.VisitMetadata(ctx, pfx, func(p string, md []*api.PrefixMetadata) (bool, error) {
    50  			visited = append(visited, p)
    51  			return true, nil
    52  		})
    53  		So(err, ShouldBeNil)
    54  		return
    55  	}
    56  
    57  	Convey("Works", t, func() {
    58  		s := MetadataStore{}
    59  
    60  		// Empty at the start.
    61  		metas, err := s.GetMetadata(ctx, "a/b/c")
    62  		So(err, ShouldBeNil)
    63  		So(metas, ShouldBeNil)
    64  
    65  		// Start creating metadata for 'a', but don't actually touch it.
    66  		meta, err := s.UpdateMetadata(ctx, "a/", func(_ context.Context, m *api.PrefixMetadata) error {
    67  			return nil
    68  		})
    69  		So(err, ShouldBeNil)
    70  		So(meta, ShouldBeNil) // it is missing
    71  
    72  		// Still missing.
    73  		metas, err = s.GetMetadata(ctx, "a/b/c")
    74  		So(err, ShouldBeNil)
    75  		So(metas, ShouldBeNil)
    76  
    77  		// Create metadata for 'a' for real this time.
    78  		meta, err = s.UpdateMetadata(ctx, "a/", func(_ context.Context, m *api.PrefixMetadata) error {
    79  			So(m.Prefix, ShouldEqual, "a")
    80  			So(m.Fingerprint, ShouldEqual, "")
    81  			m.UpdateUser = "user:a@example.com"
    82  			return nil
    83  		})
    84  		So(err, ShouldBeNil)
    85  
    86  		expected_a := &api.PrefixMetadata{
    87  			Prefix:      "a",
    88  			Fingerprint: "ccAI44xVAoO3SUzK2x6b0wZMD00",
    89  			UpdateUser:  "user:a@example.com",
    90  		}
    91  		So(meta, ShouldResembleProto, expected_a)
    92  
    93  		// Again, sees the updated metadata now.
    94  		meta, err = s.UpdateMetadata(ctx, "a/", func(_ context.Context, m *api.PrefixMetadata) error {
    95  			So(m, ShouldResembleProto, expected_a)
    96  			return nil
    97  		})
    98  		So(err, ShouldBeNil)
    99  		So(meta, ShouldResembleProto, expected_a)
   100  
   101  		// Create metadata for 'a/b/c'.
   102  		meta, err = s.UpdateMetadata(ctx, "a/b/c", func(_ context.Context, m *api.PrefixMetadata) error {
   103  			m.UpdateUser = "user:abc@example.com"
   104  			return nil
   105  		})
   106  		So(err, ShouldBeNil)
   107  
   108  		expected_abc := &api.PrefixMetadata{
   109  			Prefix:      "a/b/c",
   110  			Fingerprint: "HZozZp-6ZMi8lZp11-w54xJBjhA",
   111  			UpdateUser:  "user:abc@example.com",
   112  		}
   113  		So(meta, ShouldResembleProto, expected_abc)
   114  
   115  		// Create metadata for 'a/b/d' (sibling), to make sure it will not appear
   116  		// in responses below.
   117  		_, err = s.UpdateMetadata(ctx, "a/b/d", func(_ context.Context, m *api.PrefixMetadata) error {
   118  			m.UpdateUser = "user:abd@example.com"
   119  			return nil
   120  		})
   121  		So(err, ShouldBeNil)
   122  
   123  		// Fetching 'a' returns only 'a'.
   124  		metas, err = s.GetMetadata(ctx, "a")
   125  		So(err, ShouldBeNil)
   126  		So(metas, ShouldResembleProto, []*api.PrefixMetadata{expected_a})
   127  
   128  		// Prefix matches respects '/'.
   129  		metas, err = s.GetMetadata(ctx, "ab")
   130  		So(err, ShouldBeNil)
   131  		So(metas, ShouldBeNil)
   132  
   133  		// Still only 'a'.
   134  		metas, err = s.GetMetadata(ctx, "a/b")
   135  		So(err, ShouldBeNil)
   136  		So(metas, ShouldResembleProto, []*api.PrefixMetadata{expected_a})
   137  
   138  		// And now we also see 'a/b/c'.
   139  		metas, err = s.GetMetadata(ctx, "a/b/c")
   140  		So(err, ShouldBeNil)
   141  		So(metas, ShouldResembleProto, []*api.PrefixMetadata{expected_a, expected_abc})
   142  
   143  		// And that's all we can ever see, even if we do deeper.
   144  		metas, err = s.GetMetadata(ctx, "a/b/c/d/e/f")
   145  		So(err, ShouldBeNil)
   146  		So(metas, ShouldResembleProto, []*api.PrefixMetadata{expected_a, expected_abc})
   147  	})
   148  
   149  	Convey("Root metadata", t, func() {
   150  		s := MetadataStore{}
   151  
   152  		// Create the metadata for the root.
   153  		rootMeta, err := s.UpdateMetadata(ctx, "", func(_ context.Context, m *api.PrefixMetadata) error {
   154  			m.UpdateUser = "user:root@example.com"
   155  			return nil
   156  		})
   157  		So(err, ShouldBeNil)
   158  
   159  		So(rootMeta, ShouldResembleProto, &api.PrefixMetadata{
   160  			Fingerprint: "a7QYP7C3AXksn_pfotXl2OwBevc",
   161  			UpdateUser:  "user:root@example.com",
   162  		})
   163  
   164  		// Fetchable now.
   165  		metas, err := s.GetMetadata(ctx, "")
   166  		So(err, ShouldBeNil)
   167  		So(metas, ShouldResembleProto, []*api.PrefixMetadata{rootMeta})
   168  
   169  		// "/" is also accepted.
   170  		metas, err = s.GetMetadata(ctx, "/")
   171  		So(err, ShouldBeNil)
   172  		So(metas, ShouldResembleProto, []*api.PrefixMetadata{rootMeta})
   173  
   174  		// Make sure UpdateMetadata see the root metadata too.
   175  		_, err = s.UpdateMetadata(ctx, "", func(_ context.Context, m *api.PrefixMetadata) error {
   176  			So(m, ShouldResembleProto, rootMeta)
   177  			return nil
   178  		})
   179  		So(err, ShouldBeNil)
   180  
   181  		// Create metadata for some prefix.
   182  		abMeta, err := s.UpdateMetadata(ctx, "a/b", func(_ context.Context, m *api.PrefixMetadata) error {
   183  			m.UpdateUser = "user:ab@example.com"
   184  			return nil
   185  		})
   186  		So(err, ShouldBeNil)
   187  
   188  		// Fetching meta for prefixes picks up root metadata too.
   189  		metas, err = s.GetMetadata(ctx, "a")
   190  		So(err, ShouldBeNil)
   191  		So(metas, ShouldResembleProto, []*api.PrefixMetadata{rootMeta})
   192  		metas, err = s.GetMetadata(ctx, "a/b/c")
   193  		So(err, ShouldBeNil)
   194  		So(metas, ShouldResembleProto, []*api.PrefixMetadata{rootMeta, abMeta})
   195  	})
   196  
   197  	Convey("GetMetadata filters by prefix correctly", t, func() {
   198  		s := MetadataStore{}
   199  		populateMD(&s, []string{"", "a", "ab", "a/b", "b", "a/b/c"})
   200  
   201  		So(getMD(&s, ""), ShouldResemble, []string{""})
   202  		So(getMD(&s, "a"), ShouldResemble, []string{"", "a"})
   203  		So(getMD(&s, "a/b"), ShouldResemble, []string{"", "a", "a/b"})
   204  		So(getMD(&s, "a/b/c"), ShouldResemble, []string{"", "a", "a/b", "a/b/c"})
   205  	})
   206  
   207  	Convey("Purge works", t, func() {
   208  		s := MetadataStore{}
   209  		populateMD(&s, []string{"", "a", "a/b", "a/b/c"})
   210  
   211  		s.Purge("a/b")
   212  		So(getMD(&s, "a/b/c"), ShouldResemble, []string{"", "a", "a/b/c"})
   213  
   214  		s.Purge("")
   215  		So(getMD(&s, "a/b/c"), ShouldResemble, []string{"a", "a/b/c"})
   216  
   217  		s.Purge("a/b/c")
   218  		So(getMD(&s, "a/b/c"), ShouldResemble, []string{"a"})
   219  
   220  		s.Purge("a")
   221  		So(getMD(&s, "a/b/c"), ShouldResemble, []string{})
   222  	})
   223  
   224  	Convey("VisitMetadata visits", t, func() {
   225  		s := MetadataStore{}
   226  		populateMD(&s, []string{
   227  			"", "a", "ab", "a/b", "a/b/c",
   228  			"b", "b/c/d/e", "b/c/d/f",
   229  		})
   230  
   231  		So(visitAll(&s, ""), ShouldResemble, []string{
   232  			"", "a", "a/b", "a/b/c", "ab", "b", "b/c/d/e", "b/c/d/f",
   233  		})
   234  		So(visitAll(&s, "a"), ShouldResemble, []string{"a", "a/b", "a/b/c"})
   235  		So(visitAll(&s, "a/b"), ShouldResemble, []string{"a/b", "a/b/c"})
   236  	})
   237  
   238  	Convey("VisitMetadata always visits pfx even if it has no metadata", t, func() {
   239  		s := MetadataStore{}
   240  		populateMD(&s, []string{"a", "ab", "a/b", "a/b/c", "a/c"})
   241  
   242  		So(visitAll(&s, ""), ShouldResemble, []string{
   243  			"", "a", "a/b", "a/b/c", "a/c", "ab",
   244  		})
   245  		So(visitAll(&s, "a/b/c/d"), ShouldResemble, []string{"a/b/c/d"})
   246  		So(visitAll(&s, "some"), ShouldResemble, []string{"some"})
   247  	})
   248  
   249  	Convey("VisitMetadata respects callback return value", t, func() {
   250  		s := MetadataStore{}
   251  		populateMD(&s, []string{
   252  			"1", "1/a", "1/a/b", "1/a/b/c",
   253  			"2", "2/a", "2/a/b", "2/a/b/c",
   254  		})
   255  
   256  		var visited []string
   257  		s.VisitMetadata(ctx, "", func(p string, md []*api.PrefixMetadata) (bool, error) {
   258  			visited = append(visited, p)
   259  			return len(md) <= 2, nil // explore no deeper than 3 levels
   260  		})
   261  		So(visited, ShouldResemble, []string{
   262  			"",
   263  			"1", "1/a", "1/a/b",
   264  			"2", "2/a", "2/a/b",
   265  		})
   266  	})
   267  }