| OLD | NEW |
| (Empty) | |
| 1 // Copyright 2016 The LUCI Authors. All rights reserved. |
| 2 // Use of this source code is governed under the Apache License, Version 2.0 |
| 3 // that can be found in the LICENSE file. |
| 4 |
| 5 package datastore |
| 6 |
| 7 import ( |
| 8 "fmt" |
| 9 "sort" |
| 10 "strings" |
| 11 "testing" |
| 12 "time" |
| 13 |
| 14 "github.com/luci/luci-go/appengine/datastorecache" |
| 15 memConfig "github.com/luci/luci-go/common/config/impl/memory" |
| 16 "github.com/luci/luci-go/common/errors" |
| 17 configPB "github.com/luci/luci-go/common/proto/config" |
| 18 gaeformat "github.com/luci/luci-go/luci_config/appengine/format" |
| 19 "github.com/luci/luci-go/luci_config/common/cfgtypes" |
| 20 "github.com/luci/luci-go/luci_config/server/cfgclient" |
| 21 "github.com/luci/luci-go/luci_config/server/cfgclient/backend" |
| 22 "github.com/luci/luci-go/luci_config/server/cfgclient/backend/caching" |
| 23 "github.com/luci/luci-go/luci_config/server/cfgclient/backend/client" |
| 24 "github.com/luci/luci-go/luci_config/server/cfgclient/backend/format" |
| 25 "github.com/luci/luci-go/luci_config/server/cfgclient/backend/testconfig
" |
| 26 "github.com/luci/luci-go/luci_config/server/cfgclient/textproto" |
| 27 "github.com/luci/luci-go/server/auth" |
| 28 "github.com/luci/luci-go/server/auth/authtest" |
| 29 |
| 30 "github.com/luci/gae/impl/memory" |
| 31 |
| 32 "github.com/golang/protobuf/proto" |
| 33 "golang.org/x/net/context" |
| 34 |
| 35 . "github.com/luci/luci-go/common/testing/assertions" |
| 36 . "github.com/smartystreets/goconvey/convey" |
| 37 ) |
| 38 |
| 39 // testCache is a generic Cache testing layer. |
| 40 type testCache interface { |
| 41 dsCacheBackend |
| 42 |
| 43 setCacheErr(err error) |
| 44 setProjectDNE(project string) |
| 45 addConfig(configSet cfgtypes.ConfigSet, path, content string) *backend.I
tem |
| 46 addProjectConfig(name cfgtypes.ProjectName, access string) |
| 47 addConfigSets(path string, configSets ...cfgtypes.ConfigSet) []string |
| 48 addConfigSetURL(configSet cfgtypes.ConfigSet) string |
| 49 } |
| 50 |
| 51 func projectConfigWithAccess(name cfgtypes.ProjectName, access ...string) *confi
gPB.ProjectCfg { |
| 52 return &configPB.ProjectCfg{ |
| 53 Name: proto.String(string(name)), |
| 54 Access: access, |
| 55 } |
| 56 } |
| 57 |
| 58 // fakeCache is a pure in-memory testCache implementation. It is very simple, |
| 59 // storing only raw cache key/value pairs. |
| 60 type fakeCache struct { |
| 61 d map[string]datastorecache.Value |
| 62 err error |
| 63 } |
| 64 |
| 65 func mkFakeCache() *fakeCache { |
| 66 return &fakeCache{ |
| 67 d: make(map[string]datastorecache.Value), |
| 68 } |
| 69 } |
| 70 |
| 71 func (fc *fakeCache) Get(c context.Context, key []byte) (v datastorecache.Value,
err error) { |
| 72 if err = fc.err; err != nil { |
| 73 return |
| 74 } |
| 75 |
| 76 var k caching.Key |
| 77 caching.Decode(key, &k) |
| 78 |
| 79 var ok bool |
| 80 if v, ok = fc.d[string(key)]; ok { |
| 81 return |
| 82 } |
| 83 |
| 84 err = datastorecache.ErrCacheExpired |
| 85 return |
| 86 } |
| 87 |
| 88 func (fc *fakeCache) setCacheData(key caching.Key, d []byte) { |
| 89 } |
| 90 |
| 91 func (fc *fakeCache) set(key caching.Key, v *caching.Value) { |
| 92 encKey, err := caching.Encode(&key) |
| 93 if err != nil { |
| 94 panic(fmt.Errorf("failed to encode key: %s", err)) |
| 95 } |
| 96 |
| 97 if v == nil { |
| 98 delete(fc.d, string(encKey)) |
| 99 return |
| 100 } |
| 101 |
| 102 encValue, err := v.Encode() |
| 103 if err != nil { |
| 104 panic(fmt.Errorf("failed to encode cache value: %s", err)) |
| 105 } |
| 106 |
| 107 fc.d[string(encKey)] = datastorecache.Value{ |
| 108 Schema: dsCacheSchema, |
| 109 Data: encValue, |
| 110 Description: key.String(), |
| 111 } |
| 112 } |
| 113 |
| 114 func (fc *fakeCache) setCacheErr(err error) { fc.err = err } |
| 115 |
| 116 func (fc *fakeCache) setProjectDNE(project string) { |
| 117 // Get for this project config will fail. |
| 118 fc.set(caching.Key{ |
| 119 Schema: caching.Schema, |
| 120 Op: caching.OpGet, |
| 121 ConfigSet: string(cfgtypes.ProjectConfigSet(cfgtypes.ProjectName
(project))), |
| 122 Path: cfgclient.ProjectConfigPath, |
| 123 }, nil) |
| 124 } |
| 125 |
| 126 func (fc *fakeCache) addConfigImpl(cs cfgtypes.ConfigSet, path, formatter, forma
tData, content string) *backend.Item { |
| 127 var ( |
| 128 item *backend.Item |
| 129 cv caching.Value |
| 130 ) |
| 131 if content != "" { |
| 132 item = &backend.Item{ |
| 133 Meta: backend.Meta{ |
| 134 ConfigSet: string(cs), |
| 135 Path: path, |
| 136 ContentHash: "hash", |
| 137 }, |
| 138 Content: content, |
| 139 FormatSpec: backend.FormatSpec{formatter, formatData}, |
| 140 } |
| 141 cv.LoadItems(item) |
| 142 } |
| 143 |
| 144 fc.set(caching.Key{ |
| 145 Schema: caching.Schema, |
| 146 Authority: backend.AsService, |
| 147 Op: caching.OpGet, |
| 148 ConfigSet: string(cs), |
| 149 Path: path, |
| 150 Content: true, |
| 151 Formatter: formatter, |
| 152 FormatData: formatData, |
| 153 }, &cv) |
| 154 |
| 155 return item |
| 156 } |
| 157 |
| 158 func (fc *fakeCache) addConfig(cs cfgtypes.ConfigSet, path, content string) *bac
kend.Item { |
| 159 return fc.addConfigImpl(cs, path, "", "", content) |
| 160 } |
| 161 |
| 162 // addProjectConfig caches a "project.cfg" file for the specified project with |
| 163 // the specified access string. |
| 164 func (fc *fakeCache) addProjectConfig(name cfgtypes.ProjectName, access string)
{ |
| 165 // We're loading the resolved version of this cache item. |
| 166 pcfg := projectConfigWithAccess(name, access) |
| 167 pcfgName := proto.MessageName(pcfg) |
| 168 |
| 169 f := textproto.Formatter{} |
| 170 formattedData, err := f.FormatItem(proto.MarshalTextString(pcfg), pcfgNa
me) |
| 171 if err != nil { |
| 172 panic(err) |
| 173 } |
| 174 |
| 175 fc.addConfigImpl(cfgtypes.ProjectConfigSet(name), cfgclient.ProjectConfi
gPath, |
| 176 textproto.BinaryFormat, pcfgName, formattedData) |
| 177 } |
| 178 |
| 179 func (fc *fakeCache) addConfigSets(path string, configSets ...cfgtypes.ConfigSet
) []string { |
| 180 items := make([]*backend.Item, len(configSets)) |
| 181 contents := make([]string, len(configSets)) |
| 182 for i, cs := range configSets { |
| 183 contents[i] = string(cs) |
| 184 items[i] = &backend.Item{ |
| 185 Meta: backend.Meta{ |
| 186 ConfigSet: string(cs), |
| 187 Path: path, |
| 188 ContentHash: "hash", |
| 189 }, |
| 190 Content: contents[i], |
| 191 } |
| 192 } |
| 193 |
| 194 for _, t := range []backend.GetAllTarget{backend.GetAllProject, backend.
GetAllRef} { |
| 195 var cv caching.Value |
| 196 cv.LoadItems(items...) |
| 197 |
| 198 fc.set(caching.Key{ |
| 199 Schema: caching.Schema, |
| 200 Authority: backend.AsService, |
| 201 Op: caching.OpGetAll, |
| 202 Content: true, |
| 203 Path: path, |
| 204 GetAllTarget: t, |
| 205 }, &cv) |
| 206 } |
| 207 return contents |
| 208 } |
| 209 |
| 210 func (fc *fakeCache) addConfigSetURL(configSet cfgtypes.ConfigSet) string { |
| 211 u := fmt.Sprintf("https://exmaple.com/config-sets/%s", configSet) |
| 212 fc.set(caching.Key{ |
| 213 Schema: caching.Schema, |
| 214 Authority: backend.AsService, |
| 215 Op: caching.OpConfigSetURL, |
| 216 ConfigSet: string(configSet), |
| 217 }, &caching.Value{ |
| 218 URL: u, |
| 219 }) |
| 220 return u |
| 221 } |
| 222 |
| 223 // fullStackCache is a testCache implementation built on top of an in-memory |
| 224 // base backend.B with the datastore Cache layer on top of it. |
| 225 type fullStackCache struct { |
| 226 cache *datastorecache.Cache |
| 227 err error |
| 228 |
| 229 data map[string]memConfig.ConfigSet |
| 230 backend backend.B |
| 231 junkIdx int |
| 232 } |
| 233 |
| 234 func (fsc *fullStackCache) Get(c context.Context, key []byte) (datastorecache.Va
lue, error) { |
| 235 if err := fsc.err; err != nil { |
| 236 return datastorecache.Value{}, err |
| 237 } |
| 238 return fsc.cache.Get(c, key) |
| 239 } |
| 240 |
| 241 func (fsc *fullStackCache) setCacheErr(err error) { fsc.err = err } |
| 242 |
| 243 func (fsc *fullStackCache) setProjectDNE(project string) { |
| 244 key := "projects/" + project |
| 245 for k := range fsc.data { |
| 246 if k == key || strings.HasPrefix(k, key+"/") { |
| 247 delete(fsc.data, k) |
| 248 } |
| 249 } |
| 250 } |
| 251 |
| 252 func (fsc *fullStackCache) addConfig(cs cfgtypes.ConfigSet, path, content string
) *backend.Item { |
| 253 cset := fsc.data[string(cs)] |
| 254 if cset == nil { |
| 255 cset = memConfig.ConfigSet{} |
| 256 fsc.data[string(cs)] = cset |
| 257 } |
| 258 if content == "" { |
| 259 delete(cset, path) |
| 260 return nil |
| 261 } |
| 262 cset[path] = content |
| 263 |
| 264 // Pull the config right back out of the base service. |
| 265 item, err := fsc.backend.Get(context.Background(), string(cs), path, bac
kend.Params{ |
| 266 Authority: backend.AsService, |
| 267 }) |
| 268 if err != nil { |
| 269 panic(err) |
| 270 } |
| 271 return item |
| 272 } |
| 273 |
| 274 // addProjectConfig caches a "project.cfg" file for the specified project with |
| 275 // the specified access string. |
| 276 func (fsc *fullStackCache) addProjectConfig(name cfgtypes.ProjectName, access st
ring) { |
| 277 fsc.addConfig(cfgtypes.ProjectConfigSet(name), cfgclient.ProjectConfigPa
th, |
| 278 proto.MarshalTextString(projectConfigWithAccess(name, access))) |
| 279 } |
| 280 |
| 281 func (fsc *fullStackCache) addConfigSets(path string, configSets ...cfgtypes.Con
figSet) []string { |
| 282 // Sort the config sets list, then put it back. |
| 283 cstr := make([]string, len(configSets)) |
| 284 for i, cs := range configSets { |
| 285 cstr[i] = string(cs) |
| 286 } |
| 287 sort.Strings(cstr) |
| 288 for i, cs := range cstr { |
| 289 configSets[i] = cfgtypes.ConfigSet(cs) |
| 290 } |
| 291 |
| 292 items := make([]*backend.Item, len(configSets)) |
| 293 for i, cs := range configSets { |
| 294 items[i] = fsc.addConfig(cs, path, string(cs)) |
| 295 } |
| 296 return cstr |
| 297 } |
| 298 |
| 299 func (fsc *fullStackCache) addConfigSetURL(configSet cfgtypes.ConfigSet) string
{ |
| 300 if _, ok := fsc.data[string(configSet)]; !ok { |
| 301 fsc.data[string(configSet)] = memConfig.ConfigSet{} |
| 302 } |
| 303 |
| 304 // We're pretty rigid here. Whatever our backend returns is all we can |
| 305 // return. We will just assert that anything more flexible has to confor
m to |
| 306 // this. |
| 307 v, err := fsc.backend.ConfigSetURL(context.Background(), string(configSe
t), |
| 308 backend.Params{Authority: backend.AsService}) |
| 309 if err != nil { |
| 310 panic(err) |
| 311 } |
| 312 return v.String() |
| 313 } |
| 314 |
| 315 // stripMeta strips cache-specific identifying information from a set of Metas. |
| 316 func stripMeta(metas []*cfgclient.Meta) []*cfgclient.Meta { |
| 317 for _, meta := range metas { |
| 318 meta.ContentHash = "" |
| 319 meta.Revision = "" |
| 320 } |
| 321 return metas |
| 322 } |
| 323 |
| 324 func testDatastoreCacheImpl(c context.Context, be backend.B, cache testCache) { |
| 325 // Install fake auth state. |
| 326 var authState authtest.FakeState |
| 327 c = auth.WithState(c, &authState) |
| 328 authState.Identity = "user:person@example.com" |
| 329 authState.IdentityGroups = []string{"users"} |
| 330 |
| 331 dsc := Config{ |
| 332 RefreshInterval: 1 * time.Hour, |
| 333 FailOpen: false, |
| 334 cache: cache, |
| 335 } |
| 336 c = backend.WithBackend(c, dsc.Backend(be)) |
| 337 |
| 338 testErr := errors.New("test error") |
| 339 |
| 340 Convey(`Test Get`, func() { |
| 341 var v string |
| 342 |
| 343 Convey(`Config missing`, func() { |
| 344 cache.addConfig("projects/test", "foo", "") |
| 345 |
| 346 So(cfgclient.Get(c, cfgclient.AsService, "projects/test"
, "foo", cfgclient.String(&v), nil), |
| 347 ShouldEqual, cfgclient.ErrNoConfig) |
| 348 }) |
| 349 |
| 350 Convey(`Config is present`, func() { |
| 351 cache.addConfig("projects/test", "foo", "bar") |
| 352 cache.addProjectConfig("test", "group:privileged") |
| 353 |
| 354 Convey(`As service`, func() { |
| 355 So(cfgclient.Get(c, cfgclient.AsService, "projec
ts/test", "foo", cfgclient.String(&v), nil), ShouldBeNil) |
| 356 So(v, ShouldEqual, "bar") |
| 357 }) |
| 358 |
| 359 Convey(`As user, when not a project group member, fails
with ErrNoConfig`, func() { |
| 360 So(cfgclient.Get(c, cfgclient.AsUser, "projects/
test", "foo", cfgclient.String(&v), nil), |
| 361 ShouldEqual, cfgclient.ErrNoConfig) |
| 362 }) |
| 363 |
| 364 Convey(`As user, when a project group member, succeeds.`
, func() { |
| 365 authState.IdentityGroups = append(authState.Iden
tityGroups, "privileged") |
| 366 So(cfgclient.Get(c, cfgclient.AsUser, "projects/
test", "foo", cfgclient.String(&v), nil), ShouldBeNil) |
| 367 So(v, ShouldEqual, "bar") |
| 368 }) |
| 369 |
| 370 Convey(`As anonymous, fails with ErrNoConfig`, func() { |
| 371 So(cfgclient.Get(c, cfgclient.AsAnonymous, "proj
ects/test", "foo", cfgclient.String(&v), nil), |
| 372 ShouldEqual, cfgclient.ErrNoConfig) |
| 373 }) |
| 374 }) |
| 375 }) |
| 376 |
| 377 Convey(`Test Projects`, func() { |
| 378 var v []string |
| 379 var meta []*cfgclient.Meta |
| 380 |
| 381 Convey(`When cache returns an error`, func() { |
| 382 cache.setCacheErr(testErr) |
| 383 |
| 384 So(cfgclient.Projects(c, cfgclient.AsService, "test.cfg"
, cfgclient.StringSlice(&v), nil), |
| 385 ShouldUnwrapTo, testErr) |
| 386 So(cfgclient.Projects(c, cfgclient.AsUser, "test.cfg", c
fgclient.StringSlice(&v), nil), |
| 387 ShouldUnwrapTo, testErr) |
| 388 So(cfgclient.Projects(c, cfgclient.AsAnonymous, "test.cf
g", cfgclient.StringSlice(&v), nil), |
| 389 ShouldUnwrapTo, testErr) |
| 390 }) |
| 391 |
| 392 Convey(`With project configs installed`, func() { |
| 393 allConfigs := cache.addConfigSets("test.cfg", |
| 394 "projects/bar", |
| 395 "projects/baz", |
| 396 "projects/foo") |
| 397 |
| 398 Convey(`As service, retrieves all configs.`, func() { |
| 399 So(cfgclient.Projects(c, cfgclient.AsService, "t
est.cfg", cfgclient.StringSlice(&v), &meta), ShouldBeNil) |
| 400 So(v, ShouldResemble, allConfigs) |
| 401 So(stripMeta(meta), ShouldResemble, []*cfgclient
.Meta{ |
| 402 {ConfigSet: "projects/bar", Path: "test.
cfg"}, |
| 403 {ConfigSet: "projects/baz", Path: "test.
cfg"}, |
| 404 {ConfigSet: "projects/foo", Path: "test.
cfg"}, |
| 405 }) |
| 406 }) |
| 407 |
| 408 Convey(`As user`, func() { |
| 409 Convey(`Not a member of any projects, receives e
mpty slice.`, func() { |
| 410 cache.addProjectConfig("foo", "group:som
eone") |
| 411 |
| 412 So(cfgclient.Projects(c, cfgclient.AsUse
r, "test.cfg", cfgclient.StringSlice(&v), &meta), ShouldBeNil) |
| 413 So(v, ShouldResemble, []string(nil)) |
| 414 So(stripMeta(meta), ShouldResemble, []*c
fgclient.Meta{}) |
| 415 }) |
| 416 |
| 417 Convey(`Member of "foo", gets only "foo".`, func
() { |
| 418 cache.addProjectConfig("foo", "group:use
rs") |
| 419 |
| 420 So(cfgclient.Projects(c, cfgclient.AsUse
r, "test.cfg", cfgclient.StringSlice(&v), &meta), ShouldBeNil) |
| 421 So(v, ShouldResemble, allConfigs[2:3]) |
| 422 So(stripMeta(meta), ShouldResemble, []*c
fgclient.Meta{ |
| 423 {ConfigSet: "projects/foo", Path
: "test.cfg"}, |
| 424 }) |
| 425 }) |
| 426 |
| 427 Convey(`Member of all projects, gets all project
s.`, func() { |
| 428 cache.addProjectConfig("foo", "group:use
rs") |
| 429 cache.addProjectConfig("bar", "group:use
rs") |
| 430 cache.addProjectConfig("baz", "group:use
rs") |
| 431 |
| 432 So(cfgclient.Projects(c, cfgclient.AsUse
r, "test.cfg", cfgclient.StringSlice(&v), &meta), ShouldBeNil) |
| 433 So(v, ShouldResemble, allConfigs) |
| 434 So(stripMeta(meta), ShouldResemble, []*c
fgclient.Meta{ |
| 435 {ConfigSet: "projects/bar", Path
: "test.cfg"}, |
| 436 {ConfigSet: "projects/baz", Path
: "test.cfg"}, |
| 437 {ConfigSet: "projects/foo", Path
: "test.cfg"}, |
| 438 }) |
| 439 }) |
| 440 }) |
| 441 }) |
| 442 }) |
| 443 |
| 444 Convey(`Test Refs`, func() { |
| 445 var v []string |
| 446 var meta []*cfgclient.Meta |
| 447 |
| 448 Convey(`When cache returns an error`, func() { |
| 449 cache.setCacheErr(testErr) |
| 450 |
| 451 So(cfgclient.Refs(c, cfgclient.AsService, "test.cfg", cf
gclient.StringSlice(&v), nil), |
| 452 ShouldUnwrapTo, testErr) |
| 453 So(cfgclient.Refs(c, cfgclient.AsUser, "test.cfg", cfgcl
ient.StringSlice(&v), nil), |
| 454 ShouldUnwrapTo, testErr) |
| 455 So(cfgclient.Refs(c, cfgclient.AsAnonymous, "test.cfg",
cfgclient.StringSlice(&v), nil), |
| 456 ShouldUnwrapTo, testErr) |
| 457 }) |
| 458 |
| 459 Convey(`With ref configs installed`, func() { |
| 460 allConfigs := cache.addConfigSets("test.cfg", |
| 461 "projects/bar/refs/branches/mybranch", |
| 462 "projects/bar/refs/heads/master", |
| 463 "projects/foo/refs/branches/mybranch", |
| 464 "projects/foo/refs/heads/master") |
| 465 |
| 466 Convey(`As service, retrieves all configs.`, func() { |
| 467 So(cfgclient.Refs(c, cfgclient.AsService, "test.
cfg", cfgclient.StringSlice(&v), &meta), ShouldBeNil) |
| 468 So(v, ShouldResemble, allConfigs) |
| 469 So(stripMeta(meta), ShouldResemble, []*cfgclient
.Meta{ |
| 470 {ConfigSet: "projects/bar/refs/branches/
mybranch", Path: "test.cfg"}, |
| 471 {ConfigSet: "projects/bar/refs/heads/mas
ter", Path: "test.cfg"}, |
| 472 {ConfigSet: "projects/foo/refs/branches/
mybranch", Path: "test.cfg"}, |
| 473 {ConfigSet: "projects/foo/refs/heads/mas
ter", Path: "test.cfg"}, |
| 474 }) |
| 475 }) |
| 476 |
| 477 Convey(`As user`, func() { |
| 478 Convey(`Not a member of any projects, receives e
mpty slice.`, func() { |
| 479 cache.addProjectConfig("foo", "group:som
eone") |
| 480 |
| 481 So(cfgclient.Refs(c, cfgclient.AsUser, "
test.cfg", cfgclient.StringSlice(&v), &meta), ShouldBeNil) |
| 482 So(v, ShouldResemble, []string(nil)) |
| 483 So(stripMeta(meta), ShouldResemble, []*c
fgclient.Meta{}) |
| 484 }) |
| 485 |
| 486 Convey(`Member of "foo", gets only "foo".`, func
() { |
| 487 cache.addProjectConfig("foo", "group:use
rs") |
| 488 |
| 489 So(cfgclient.Refs(c, cfgclient.AsUser, "
test.cfg", cfgclient.StringSlice(&v), &meta), ShouldBeNil) |
| 490 So(v, ShouldResemble, allConfigs[2:4]) |
| 491 So(stripMeta(meta), ShouldResemble, []*c
fgclient.Meta{ |
| 492 {ConfigSet: "projects/foo/refs/b
ranches/mybranch", Path: "test.cfg"}, |
| 493 {ConfigSet: "projects/foo/refs/h
eads/master", Path: "test.cfg"}, |
| 494 }) |
| 495 }) |
| 496 |
| 497 Convey(`Member of all projects, gets all project
s.`, func() { |
| 498 cache.addProjectConfig("foo", "group:use
rs") |
| 499 cache.addProjectConfig("bar", "group:use
rs") |
| 500 |
| 501 So(cfgclient.Refs(c, cfgclient.AsUser, "
test.cfg", cfgclient.StringSlice(&v), &meta), ShouldBeNil) |
| 502 So(v, ShouldResemble, allConfigs) |
| 503 So(stripMeta(meta), ShouldResemble, []*c
fgclient.Meta{ |
| 504 {ConfigSet: "projects/bar/refs/b
ranches/mybranch", Path: "test.cfg"}, |
| 505 {ConfigSet: "projects/bar/refs/h
eads/master", Path: "test.cfg"}, |
| 506 {ConfigSet: "projects/foo/refs/b
ranches/mybranch", Path: "test.cfg"}, |
| 507 {ConfigSet: "projects/foo/refs/h
eads/master", Path: "test.cfg"}, |
| 508 }) |
| 509 }) |
| 510 }) |
| 511 }) |
| 512 }) |
| 513 |
| 514 Convey(`Test ConfigSetURL`, func() { |
| 515 cache.addProjectConfig("foo", "group:someone") |
| 516 csURL := cache.addConfigSetURL("projects/foo") |
| 517 |
| 518 Convey(`AsService`, func() { |
| 519 u, err := cfgclient.GetConfigSetURL(c, cfgclient.AsServi
ce, "projects/foo") |
| 520 So(err, ShouldBeNil) |
| 521 So(u.String(), ShouldEqual, csURL) |
| 522 }) |
| 523 |
| 524 Convey(`AsUser`, func() { |
| 525 |
| 526 Convey(`When not a member of the group.`, func() { |
| 527 _, err := cfgclient.GetConfigSetURL(c, cfgclient
.AsUser, "projects/foo") |
| 528 So(err, ShouldEqual, cfgclient.ErrNoConfig) |
| 529 }) |
| 530 |
| 531 Convey(`When a member of the group.`, func() { |
| 532 authState.IdentityGroups = append(authState.Iden
tityGroups, "someone") |
| 533 |
| 534 u, err := cfgclient.GetConfigSetURL(c, cfgclient
.AsUser, "projects/foo") |
| 535 So(err, ShouldBeNil) |
| 536 So(u.String(), ShouldEqual, csURL) |
| 537 }) |
| 538 }) |
| 539 |
| 540 Convey(`AsAnonymous`, func() { |
| 541 _, err := cfgclient.GetConfigSetURL(c, cfgclient.AsAnony
mous, "projects/foo") |
| 542 So(err, ShouldEqual, cfgclient.ErrNoConfig) |
| 543 |
| 544 // With credentials, can access. |
| 545 authState.IdentityGroups = append(authState.IdentityGrou
ps, "someone") |
| 546 _, err = cfgclient.GetConfigSetURL(c, cfgclient.AsAnonym
ous, "projects/foo") |
| 547 So(err, ShouldBeNil) |
| 548 }) |
| 549 }) |
| 550 } |
| 551 |
| 552 func TestDatastoreCache(t *testing.T) { |
| 553 t.Parallel() |
| 554 |
| 555 Convey(`Testing with in-memory stub cache`, t, func() { |
| 556 c := context.Background() |
| 557 fc := mkFakeCache() |
| 558 |
| 559 var be backend.B |
| 560 be = &client.Backend{ |
| 561 Provider: &testconfig.Provider{ |
| 562 Base: memConfig.New(nil), |
| 563 }, |
| 564 } |
| 565 |
| 566 fr := gaeformat.Default() |
| 567 be = &format.Backend{ |
| 568 B: be, |
| 569 GetRegistry: func(context.Context) *cfgclient.FormatterR
egistry { return fr }, |
| 570 } |
| 571 |
| 572 Convey(`Standard datastore tests`, func() { |
| 573 testDatastoreCacheImpl(c, be, fc) |
| 574 }) |
| 575 |
| 576 Convey(`A testing setup built around the fake cache`, func() { |
| 577 dsc := Config{ |
| 578 RefreshInterval: 1 * time.Hour, |
| 579 FailOpen: false, |
| 580 cache: fc, |
| 581 } |
| 582 c = backend.WithBackend(c, dsc.Backend(be)) |
| 583 |
| 584 Convey(`Errors with different schema.`, func() { |
| 585 fc.addConfig("foo", "bar", "value") |
| 586 for k, v := range fc.d { |
| 587 v.Schema = "unknown" |
| 588 fc.d[k] = v |
| 589 } |
| 590 |
| 591 var v string |
| 592 So(cfgclient.Get(c, cfgclient.AsService, "foo",
"bar", cfgclient.String(&v), nil), |
| 593 ShouldErrLike, `response schema ("unknow
n") doesn't match current`) |
| 594 }) |
| 595 }) |
| 596 }) |
| 597 } |
| 598 |
| 599 func TestDatastoreCacheFullStack(t *testing.T) { |
| 600 t.Parallel() |
| 601 |
| 602 Convey(`Testing full-stack datastore cache`, t, func() { |
| 603 c := memory.Use(context.Background()) |
| 604 |
| 605 data := map[string]memConfig.ConfigSet{} |
| 606 |
| 607 var be backend.B |
| 608 be = &client.Backend{ |
| 609 Provider: &testconfig.Provider{ |
| 610 Base: memConfig.New(data), |
| 611 }, |
| 612 } |
| 613 |
| 614 fr := gaeformat.Default() |
| 615 be = &format.Backend{ |
| 616 B: be, |
| 617 GetRegistry: func(context.Context) *cfgclient.FormatterR
egistry { return fr }, |
| 618 } |
| 619 |
| 620 fsc := fullStackCache{ |
| 621 cache: &Cache, |
| 622 data: data, |
| 623 backend: be, |
| 624 } |
| 625 testDatastoreCacheImpl(c, be, &fsc) |
| 626 }) |
| 627 } |
| OLD | NEW |