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 }