| OLD | NEW |
| (Empty) |
| 1 // Copyright 2015 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 hierarchy | |
| 16 | |
| 17 import ( | |
| 18 "fmt" | |
| 19 "testing" | |
| 20 | |
| 21 ds "github.com/luci/gae/service/datastore" | |
| 22 "github.com/luci/gae/service/info" | |
| 23 "github.com/luci/luci-go/logdog/appengine/coordinator" | |
| 24 ct "github.com/luci/luci-go/logdog/appengine/coordinator/coordinatorTest
" | |
| 25 "github.com/luci/luci-go/logdog/common/types" | |
| 26 "github.com/luci/luci-go/luci_config/common/cfgtypes" | |
| 27 | |
| 28 . "github.com/luci/luci-go/common/testing/assertions" | |
| 29 . "github.com/smartystreets/goconvey/convey" | |
| 30 ) | |
| 31 | |
| 32 func TestHierarchy(t *testing.T) { | |
| 33 t.Parallel() | |
| 34 | |
| 35 FocusConvey(`With a testing configuration`, t, func() { | |
| 36 c, env := ct.Install() | |
| 37 | |
| 38 var r Request | |
| 39 get := func() *List { | |
| 40 l, err := Get(c, r) | |
| 41 if err != nil { | |
| 42 panic(err) | |
| 43 } | |
| 44 return l | |
| 45 } | |
| 46 | |
| 47 var lv listValidator | |
| 48 | |
| 49 FocusConvey(`When requesting Project-level list`, func() { | |
| 50 r.Project = "" | |
| 51 | |
| 52 FocusConvey(`An anonymous user will see all public-acces
s projects.`, func() { | |
| 53 So(get(), lv.shouldHaveComponents, "proj-bar", "
proj-foo") | |
| 54 }) | |
| 55 | |
| 56 Convey(`An authenticated user will see all projects.`, f
unc() { | |
| 57 env.LogIn() | |
| 58 env.JoinGroup("auth") | |
| 59 | |
| 60 allProjects := []interface{}{"proj-bar", "proj-e
xclusive", "proj-foo"} | |
| 61 | |
| 62 Convey(`Will see all projects.`, func() { | |
| 63 So(get(), lv.shouldHaveComponents, allPr
ojects...) | |
| 64 }) | |
| 65 | |
| 66 Convey(`Cursor and limit work.`, func() { | |
| 67 r.Limit = 1 | |
| 68 | |
| 69 var l *List | |
| 70 for len(allProjects) > 0 { | |
| 71 l = get() | |
| 72 So(l, lv.shouldHaveComponents, a
llProjects[0]) | |
| 73 So(l.Next, ShouldNotEqual, "") | |
| 74 allProjects = allProjects[1:] | |
| 75 | |
| 76 r.Next = l.Next | |
| 77 } | |
| 78 | |
| 79 So(len(allProjects), ShouldEqual, 0) | |
| 80 | |
| 81 // End of iteration. | |
| 82 l = get() | |
| 83 So(l, lv.shouldHaveComponents) | |
| 84 So(l.Next, ShouldEqual, "") | |
| 85 }) | |
| 86 | |
| 87 Convey(`Offset and limit work.`, func() { | |
| 88 r.Limit = 1 | |
| 89 | |
| 90 for i, proj := range allProjects { | |
| 91 r.Skip = i | |
| 92 | |
| 93 l := get() | |
| 94 So(l, lv.shouldHaveComponents, p
roj) | |
| 95 So(l.Next, ShouldNotEqual, "") | |
| 96 } | |
| 97 | |
| 98 // End of iteration. | |
| 99 r.Skip = len(allProjects) | |
| 100 l := get() | |
| 101 So(l, lv.shouldHaveComponents) | |
| 102 So(l.Next, ShouldEqual, "") | |
| 103 }) | |
| 104 }) | |
| 105 }) | |
| 106 | |
| 107 Convey(`If the user is logged in`, func() { | |
| 108 env.LogIn() | |
| 109 | |
| 110 Convey(`When accessing a restricted project`, func() { | |
| 111 r.Project = "proj-exclusive" | |
| 112 | |
| 113 Convey(`Will succeed if the user can access the
project.`, func() { | |
| 114 env.JoinGroup("auth") | |
| 115 | |
| 116 _, err := Get(c, r) | |
| 117 So(err, ShouldBeRPCOK) | |
| 118 }) | |
| 119 | |
| 120 Convey(`Will fail with PermissionDenied if the u
ser can't access the project.`, func() { | |
| 121 _, err := Get(c, r) | |
| 122 So(err, ShouldBeRPCPermissionDenied) | |
| 123 }) | |
| 124 }) | |
| 125 | |
| 126 Convey(`Will fail with PermissionDenied if the project d
oes not exist.`, func() { | |
| 127 r.Project = "does-not-exist" | |
| 128 | |
| 129 _, err := Get(c, r) | |
| 130 So(err, ShouldBeRPCPermissionDenied) | |
| 131 }) | |
| 132 }) | |
| 133 | |
| 134 Convey(`Get within a project that the user cannot access will re
turn Unauthenticated.`, func() { | |
| 135 r.Project = "proj-exclusive" | |
| 136 | |
| 137 _, err := Get(c, r) | |
| 138 So(err, ShouldBeRPCUnauthenticated) | |
| 139 }) | |
| 140 | |
| 141 Convey(`Get within a project that does not exist will return Una
uthenticated.`, func() { | |
| 142 r.Project = "proj-does-not-exist" | |
| 143 | |
| 144 _, err := Get(c, r) | |
| 145 So(err, ShouldBeRPCUnauthenticated) | |
| 146 }) | |
| 147 | |
| 148 Convey(`Get will return nothing when no components are registere
d.`, func() { | |
| 149 r.Project = "proj-foo" | |
| 150 lv.project = "proj-foo" | |
| 151 | |
| 152 So(get(), lv.shouldHaveComponents) | |
| 153 | |
| 154 r.PathBase = "foo" | |
| 155 lv.pathBase = "foo" | |
| 156 So(get(), lv.shouldHaveComponents) | |
| 157 | |
| 158 r.PathBase = "foo/+/bar" | |
| 159 lv.pathBase = "foo/+/bar" | |
| 160 So(get(), lv.shouldHaveComponents) | |
| 161 }) | |
| 162 | |
| 163 Convey(`Can register a hierarchy of name components in multiple
namespaces.`, func() { | |
| 164 for _, proj := range []cfgtypes.ProjectName{ | |
| 165 "proj-foo", "proj-bar", "proj-exclusive", | |
| 166 } { | |
| 167 // Bypass access check. | |
| 168 ic := info.MustNamespace(c, coordinator.ProjectN
amespace(proj)) | |
| 169 | |
| 170 for _, p := range []types.StreamPath{ | |
| 171 "foo/+/baz", | |
| 172 "foo/+/qux", | |
| 173 "foo/+/qux", | |
| 174 "foo/+/qux/2468", | |
| 175 "foo/+/qux/0002468", | |
| 176 "foo/+/14", | |
| 177 "foo/+/001337", | |
| 178 "foo/+/bar", | |
| 179 "foo/+/bar/baz", | |
| 180 "foo/bar/+/baz", | |
| 181 "bar/+/baz", | |
| 182 "bar/+/baz/qux", | |
| 183 } { | |
| 184 comps, err := Missing(ic, Components(p,
true)) | |
| 185 if err != nil { | |
| 186 panic(err) | |
| 187 } | |
| 188 | |
| 189 for _, c := range comps { | |
| 190 if err := c.Put(ic); err != nil
{ | |
| 191 panic(err) | |
| 192 } | |
| 193 } | |
| 194 } | |
| 195 } | |
| 196 ds.GetTestable(c).CatchupIndexes() | |
| 197 | |
| 198 Convey(`Can list the hierarchy immediate paths (discrete
).`, func() { | |
| 199 r.Project = "proj-foo" | |
| 200 lv.project = cfgtypes.ProjectName(r.Project) | |
| 201 | |
| 202 list := func(b string) *List { | |
| 203 r.Project = "proj-foo" | |
| 204 r.PathBase = b | |
| 205 | |
| 206 // Set up our validator for these query
results. | |
| 207 lv.pathBase = types.StreamPath(r.PathBas
e) | |
| 208 return get() | |
| 209 } | |
| 210 | |
| 211 So(list(""), lv.shouldHaveComponents, "bar", "fo
o") | |
| 212 So(list("foo"), lv.shouldHaveComponents, "+", "b
ar") | |
| 213 So(list("foo/+"), lv.shouldHaveComponents, "14$"
, "001337$", "bar$", "baz$", "qux$", "bar", "qux") | |
| 214 So(list("foo/+/bar"), lv.shouldHaveComponents, "
baz$") | |
| 215 So(list("foo/bar"), lv.shouldHaveComponents, "+"
) | |
| 216 So(list("foo/bar/+"), lv.shouldHaveComponents, "
baz$") | |
| 217 So(list("bar"), lv.shouldHaveComponents, "+") | |
| 218 So(list("bar/+"), lv.shouldHaveComponents, "baz$
", "baz") | |
| 219 So(list("baz"), lv.shouldHaveComponents) | |
| 220 }) | |
| 221 | |
| 222 Convey(`Performing discrete queries`, func() { | |
| 223 r.Project = "proj-foo" | |
| 224 lv.project = cfgtypes.ProjectName(r.Project) | |
| 225 | |
| 226 Convey(`When listing "proj-foo/foo/+"`, func() { | |
| 227 r.PathBase = "foo/+" | |
| 228 lv.pathBase = types.StreamPath(r.PathBas
e) | |
| 229 | |
| 230 Convey(`Can list the first 2 elements.`,
func() { | |
| 231 r.Limit = 2 | |
| 232 So(get(), lv.shouldHaveComponent
s, "14$", "001337$") | |
| 233 }) | |
| 234 | |
| 235 Convey(`Can list 3 elements, skipping th
e first four.`, func() { | |
| 236 r.Limit = 2 | |
| 237 r.Skip = 4 | |
| 238 So(get(), lv.shouldHaveComponent
s, "qux$", "bar") | |
| 239 }) | |
| 240 }) | |
| 241 | |
| 242 Convey(`Can list the immediate hierarchy iterati
vely.`, func() { | |
| 243 r.PathBase = "foo/+" | |
| 244 lv.pathBase = types.StreamPath(r.PathBas
e) | |
| 245 | |
| 246 var all []interface{} | |
| 247 for _, s := range norm(get().Comp) { | |
| 248 all = append(all, s) | |
| 249 } | |
| 250 | |
| 251 r.Limit = 2 | |
| 252 for len(all) > 0 { | |
| 253 l := get() | |
| 254 | |
| 255 count := r.Limit | |
| 256 if count > len(all) { | |
| 257 count = len(all) | |
| 258 } | |
| 259 | |
| 260 So(l, lv.shouldHaveComponents, a
ll[:count]...) | |
| 261 all = all[count:] | |
| 262 | |
| 263 if len(all) > 0 { | |
| 264 So(l.Next, ShouldNotEqua
l, "") | |
| 265 } | |
| 266 r.Next = l.Next | |
| 267 } | |
| 268 }) | |
| 269 }) | |
| 270 }) | |
| 271 }) | |
| 272 } | |
| 273 | |
| 274 type listValidator struct { | |
| 275 project cfgtypes.ProjectName | |
| 276 pathBase types.StreamPath | |
| 277 } | |
| 278 | |
| 279 func (lv *listValidator) shouldHaveComponents(actual interface{}, expected ...in
terface{}) string { | |
| 280 a, ok := actual.(*List) | |
| 281 if !ok { | |
| 282 return fmt.Sprintf("Actual value must be a *List, not %T", actua
l) | |
| 283 } | |
| 284 | |
| 285 // The project and path base components should match. | |
| 286 if a.Project != lv.project { | |
| 287 return fmt.Sprintf("Actual project %q doesn't match expected %q.
", a.Project, lv.project) | |
| 288 } | |
| 289 if a.PathBase != lv.pathBase { | |
| 290 return fmt.Sprintf("Actual path base %q doesn't match expected %
q.", a.PathBase, lv.pathBase) | |
| 291 } | |
| 292 | |
| 293 for i, c := range a.Comp { | |
| 294 expPath := types.StreamPath(types.Construct(string(lv.pathBase),
c.Name)) | |
| 295 if p := a.Path(c); p != expPath { | |
| 296 return fmt.Sprintf("Component %d doesn't have expected p
ath (%q != %q)", i, p, expPath) | |
| 297 } | |
| 298 } | |
| 299 | |
| 300 // Check normalized component values. | |
| 301 comps := make([]string, len(expected)) | |
| 302 for i, exp := range expected { | |
| 303 comps[i], ok = exp.(string) | |
| 304 if !ok { | |
| 305 return fmt.Sprintf("Expected values must be strings, %d
is %T.", i, exp) | |
| 306 } | |
| 307 } | |
| 308 if err := ShouldResemble(norm(a.Comp), comps); err != "" { | |
| 309 return err | |
| 310 } | |
| 311 return "" | |
| 312 } | |
| 313 | |
| 314 func norm(c []*ListComponent) []string { | |
| 315 result := make([]string, len(c)) | |
| 316 for i, e := range c { | |
| 317 result[i] = e.Name | |
| 318 if e.Stream { | |
| 319 result[i] += "$" | |
| 320 } | |
| 321 } | |
| 322 return result | |
| 323 } | |
| OLD | NEW |