| // Copyright 2015 The Vanadium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package testutil |
| |
| import ( |
| "fmt" |
| "reflect" |
| "testing" |
| |
| "v.io/v23/context" |
| "v.io/v23/security" |
| "v.io/v23/security/access" |
| wire "v.io/v23/services/syncbase" |
| "v.io/v23/syncbase" |
| "v.io/v23/syncbase/nosql" |
| "v.io/v23/syncbase/util" |
| "v.io/v23/verror" |
| "v.io/x/lib/vlog" |
| "v.io/x/ref/test/testutil" |
| ) |
| |
| // TestCreate tests that object creation works as expected. |
| func TestCreate(t *testing.T, ctx *context.T, i interface{}) { |
| parent := makeLayer(i) |
| self := parent.Child("self") |
| child := self.Child("child") |
| |
| // child.Create should fail since self does not exist. |
| if err := child.Create(ctx, nil); verror.ErrorID(err) != verror.ErrNoExist.ID { |
| t.Fatalf("child.Create() should have failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self, "self", false) |
| // TODO(ivanpi): Exists on child when parent does not exist currently fails |
| // with an error instead of returning false. |
| //assertExists(t, ctx, child, "child", false) |
| |
| // Create self. |
| if err := self.Create(ctx, nil); err != nil { |
| t.Fatalf("self.Create() failed: %v", err) |
| } |
| if gotPerms, wantPerms := getPermsOrDie(t, ctx, self), DefaultPerms("root/client"); !reflect.DeepEqual(gotPerms, wantPerms) { |
| t.Errorf("Perms do not match: got %v, want %v", gotPerms, wantPerms) |
| } |
| |
| assertExists(t, ctx, self, "self", true) |
| assertExists(t, ctx, child, "child", false) |
| |
| // child.Create should now succeed. |
| if err := child.Create(ctx, nil); err != nil { |
| t.Fatalf("child.Create() failed: %v", err) |
| } |
| |
| assertExists(t, ctx, child, "child", true) |
| |
| // self.Create should fail since self already exists. |
| if err := self.Create(ctx, nil); verror.ErrorID(err) != verror.ErrExist.ID { |
| t.Fatalf("self.Create() should have failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self, "self", true) |
| |
| // Test create with non-default perms. |
| self2 := parent.Child("self2") |
| perms := access.Permissions{} |
| perms.Add(security.BlessingPattern("root/client"), string(access.Admin)) |
| if err := self2.Create(ctx, perms); err != nil { |
| t.Fatalf("self2.Create() failed: %v", err) |
| } |
| if gotPerms, wantPerms := getPermsOrDie(t, ctx, self2), perms; !reflect.DeepEqual(gotPerms, wantPerms) { |
| t.Errorf("Perms do not match: got %v, want %v", gotPerms, wantPerms) |
| } |
| |
| // Even though self2 exists, Exists returns false because Read access is |
| // needed. |
| assertExists(t, ctx, self2, "self2", false) |
| |
| // Test that create fails if the parent perms disallow access. |
| perms = DefaultPerms("root/client") |
| perms.Blacklist("root/client", string(access.Write)) |
| if err := parent.SetPermissions(ctx, perms, ""); err != nil { |
| t.Fatalf("parent.SetPermissions() failed: %v", err) |
| } |
| self3 := parent.Child("self3") |
| if err := self3.Create(ctx, nil); verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Fatalf("self3.Create() should have failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self, "self", true) |
| assertExists(t, ctx, self3, "self3", false) |
| } |
| |
| // Tests that non-ASCII UTF-8 chars are supported at all layers as long as they |
| // satisfy util.ValidName. |
| // This test only requires layer.Create to be implemented and thus works for |
| // rows. |
| func TestCreateNameValidation(t *testing.T, ctx *context.T, i interface{}) { |
| parent := makeLayer(i) |
| |
| // Invalid names. |
| // TODO(sadovsky): Add names with slashes to this list once we implement |
| // client-side name validation. As it stands, some names with slashes result |
| // in RPCs against objects at the next layer of hierarchy, and naming.Join |
| // drops some leading and trailing slashes before they reach the server-side |
| // name-checking code. |
| for _, name := range []string{"a\x00", "\x00a", "@@", "a@@", "@@a", "@@a", "$", "a/$", "$/a"} { |
| if err := parent.Child(name).Create(ctx, nil); verror.ErrorID(err) != wire.ErrInvalidName.ID { |
| t.Fatalf("Create(%q) should have failed: %v", name, err) |
| } |
| } |
| |
| // Valid names. |
| for _, name := range []string{"a", "aa", "*", "a*", "*a", "a*b", "a/b", "a/$$", "$$/a", "a/$$/b", "dev.v.io/a/admin@myapp.com", "alice/bob", "안녕하세요"} { |
| if err := parent.Child(name).Create(ctx, nil); err != nil { |
| t.Fatalf("Create(%q) failed: %v", name, err) |
| } |
| } |
| } |
| |
| // TestDestroy tests that object destruction works as expected. |
| func TestDestroy(t *testing.T, ctx *context.T, i interface{}) { |
| parent := makeLayer(i) |
| self := parent.Child("self") |
| child := self.Child("child") |
| |
| // Create self. |
| if err := self.Create(ctx, nil); err != nil { |
| t.Fatalf("self.Create() failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self, "self", true) |
| |
| // self.Create should fail, since self already exists. |
| if err := self.Create(ctx, nil); verror.ErrorID(err) != verror.ErrExist.ID { |
| t.Fatalf("self.Create() should have failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self, "self", true) |
| |
| // By default, self perms are copied from parent, so self.Destroy should |
| // succeed. |
| if err := self.Destroy(ctx); err != nil { |
| t.Fatalf("self.Destroy() failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self, "self", false) |
| |
| // child.Create should fail, since self does not exist. |
| if err := child.Create(ctx, nil); verror.ErrorID(err) != verror.ErrNoExist.ID { |
| t.Fatalf("child.Create() should have failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self, "self", false) |
| // TODO(ivanpi): Exists on child when parent does not exist currently fails |
| // with an error instead of returning false. |
| //assertExists(t, ctx, child, "child", false) |
| |
| // self.Create should succeed, since self was destroyed. |
| if err := self.Create(ctx, nil); err != nil { |
| t.Fatalf("self.Create() failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self, "self", true) |
| assertExists(t, ctx, child, "child", false) |
| |
| // Test that destroy fails if the perms disallow access. |
| self2 := parent.Child("self2") |
| if err := self2.Create(ctx, nil); err != nil { |
| t.Fatalf("self2.Create() failed: %v", err) |
| } |
| perms := DefaultPerms("root/client") |
| perms.Blacklist("root/client", string(access.Write)) |
| if err := self2.SetPermissions(ctx, perms, ""); err != nil { |
| t.Fatalf("self2.SetPermissions() failed: %v", err) |
| } |
| if err := self2.Destroy(ctx); verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Fatalf("self2.Destroy() should have failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self2, "self2", true) |
| |
| // Test that destroy succeeds even if the parent perms disallow access. |
| perms = DefaultPerms("root/client") |
| perms.Blacklist("root/client", string(access.Write)) |
| if err := parent.SetPermissions(ctx, perms, ""); err != nil { |
| t.Fatalf("parent.SetPermissions() failed: %v", err) |
| } |
| if err := self.Destroy(ctx); err != nil { |
| t.Fatalf("self.Destroy() failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self, "self", false) |
| |
| // Test that destroy is idempotent. |
| if err := self.Destroy(ctx); err != nil { |
| t.Fatalf("self.Destroy() failed: %v", err) |
| } |
| |
| assertExists(t, ctx, self, "self", false) |
| } |
| |
| func TestListChildren(t *testing.T, ctx *context.T, i interface{}) { |
| self := makeLayer(i) |
| |
| var got, want []string |
| var err error |
| |
| got, err = self.ListChildren(ctx) |
| want = []string(nil) |
| if err != nil { |
| t.Fatalf("self.ListChildren() failed: %v", err) |
| } |
| if !reflect.DeepEqual(got, want) { |
| t.Fatalf("Lists do not match: got %v, want %v", got, want) |
| } |
| |
| if err := self.Child("y").Create(ctx, nil); err != nil { |
| t.Fatalf("y.Create() failed: %v", err) |
| } |
| got, err = self.ListChildren(ctx) |
| want = []string{"y"} |
| if err != nil { |
| t.Fatalf("self.ListChildren() failed: %v", err) |
| } |
| if !reflect.DeepEqual(got, want) { |
| t.Fatalf("Lists do not match: got %v, want %v", got, want) |
| } |
| |
| if err := self.Child("x").Create(ctx, nil); err != nil { |
| t.Fatalf("x.Create() failed: %v", err) |
| } |
| got, err = self.ListChildren(ctx) |
| want = []string{"x", "y"} |
| if err != nil { |
| t.Fatalf("self.ListChildren() failed: %v", err) |
| } |
| if !reflect.DeepEqual(got, want) { |
| t.Fatalf("Lists do not match: got %v, want %v", got, want) |
| } |
| |
| hello := "안녕하세요" |
| if err := self.Child(hello).Create(ctx, nil); err != nil { |
| t.Fatalf("hello.Create() failed: %v", err) |
| } |
| got, err = self.ListChildren(ctx) |
| want = []string{"x", "y", hello} |
| if err != nil { |
| t.Fatalf("self.ListChildren() failed: %v", err) |
| } |
| if !reflect.DeepEqual(got, want) { |
| t.Fatalf("Lists do not match: got %v, want %v", got, want) |
| } |
| } |
| |
| // TestPerms tests that {Set,Get}Permissions work as expected. |
| // TODO(sadovsky): All Vanadium {Set,Get}Permissions tests ought to share this |
| // test implementation. :) |
| // Mirrors v.io/groups/x/ref/services/groups/internal/server/server_test.go. |
| func TestPerms(t *testing.T, ctx *context.T, ac util.AccessController) { |
| myperms := access.Permissions{} |
| myperms.Add(security.BlessingPattern("root/client"), string(access.Admin)) |
| // Demonstrate that myperms differs from the current perms. |
| if reflect.DeepEqual(myperms, getPermsOrDie(t, ctx, ac)) { |
| t.Fatalf("Permissions should not match: %v", myperms) |
| } |
| |
| var permsBefore, permsAfter access.Permissions |
| var versionBefore, versionAfter string |
| |
| getPermsAndVersionOrDie := func() (access.Permissions, string) { |
| perms, version, err := ac.GetPermissions(ctx) |
| if err != nil { |
| // Use Fatalf rather than t.Fatalf so we get a stack trace. |
| Fatalf(t, "GetPermissions failed: %v", err) |
| } |
| return perms, version |
| } |
| |
| // SetPermissions with bad version should fail. |
| permsBefore, versionBefore = getPermsAndVersionOrDie() |
| if err := ac.SetPermissions(ctx, myperms, "20"); verror.ErrorID(err) != verror.ErrBadVersion.ID { |
| t.Fatal("SetPermissions should have failed with version error") |
| } |
| // Since SetPermissions failed, perms and version should not have changed. |
| permsAfter, versionAfter = getPermsAndVersionOrDie() |
| if !reflect.DeepEqual(permsAfter, permsBefore) { |
| t.Errorf("Perms do not match: got %v, want %v", permsAfter, permsBefore) |
| } |
| if versionAfter != versionBefore { |
| t.Errorf("Versions do not match: got %v, want %v", versionAfter, versionBefore) |
| } |
| |
| // SetPermissions with correct version should succeed. |
| permsBefore, versionBefore = permsAfter, versionAfter |
| if err := ac.SetPermissions(ctx, myperms, versionBefore); err != nil { |
| t.Fatalf("SetPermissions failed: %v", err) |
| } |
| // Check that perms and version actually changed. |
| permsAfter, versionAfter = getPermsAndVersionOrDie() |
| if !reflect.DeepEqual(permsAfter, myperms) { |
| t.Errorf("Perms do not match: got %v, want %v", permsAfter, myperms) |
| } |
| if versionBefore == versionAfter { |
| t.Errorf("Versions should not match: %v", versionBefore) |
| } |
| |
| // SetPermissions with empty version should succeed. |
| permsBefore, versionBefore = permsAfter, versionAfter |
| myperms.Add(security.BlessingPattern("root/client"), string(access.Read)) |
| if err := ac.SetPermissions(ctx, myperms, ""); err != nil { |
| t.Fatalf("SetPermissions failed: %v", err) |
| } |
| // Check that perms and version actually changed. |
| permsAfter, versionAfter = getPermsAndVersionOrDie() |
| if !reflect.DeepEqual(permsAfter, myperms) { |
| t.Errorf("Perms do not match: got %v, want %v", permsAfter, myperms) |
| } |
| if versionBefore == versionAfter { |
| t.Errorf("Versions should not match: %v", versionBefore) |
| } |
| |
| // SetPermissions with unchanged perms should succeed, and version should |
| // still change. |
| permsBefore, versionBefore = permsAfter, versionAfter |
| if err := ac.SetPermissions(ctx, myperms, ""); err != nil { |
| t.Fatalf("SetPermissions failed: %v", err) |
| } |
| // Check that perms did not change and version did change. |
| permsAfter, versionAfter = getPermsAndVersionOrDie() |
| if !reflect.DeepEqual(permsAfter, permsBefore) { |
| t.Errorf("Perms do not match: got %v, want %v", permsAfter, permsBefore) |
| } |
| if versionBefore == versionAfter { |
| t.Errorf("Versions should not match: %v", versionBefore) |
| } |
| |
| // Take away our access. SetPermissions and GetPermissions should fail. |
| if err := ac.SetPermissions(ctx, access.Permissions{}, ""); err != nil { |
| t.Fatalf("SetPermissions failed: %v", err) |
| } |
| if _, _, err := ac.GetPermissions(ctx); verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Fatal("GetPermissions should have failed with access error") |
| } |
| if err := ac.SetPermissions(ctx, myperms, ""); verror.ErrorID(err) != verror.ErrNoAccess.ID { |
| t.Fatal("SetPermissions should have failed with access error") |
| } |
| } |
| |
| //////////////////////////////////////// |
| // Internal helpers |
| |
| const notAvailable = "not available" |
| |
| type layer interface { |
| util.AccessController |
| Create(ctx *context.T, perms access.Permissions) error |
| Destroy(ctx *context.T) error |
| Exists(ctx *context.T) (bool, error) |
| ListChildren(ctx *context.T) ([]string, error) |
| Child(childName string) layer |
| } |
| |
| type service struct { |
| syncbase.Service |
| } |
| |
| func (s *service) Create(ctx *context.T, perms access.Permissions) error { |
| panic(notAvailable) |
| } |
| func (s *service) Destroy(ctx *context.T) error { |
| panic(notAvailable) |
| } |
| func (s *service) Exists(ctx *context.T) (bool, error) { |
| panic(notAvailable) |
| } |
| func (s *service) ListChildren(ctx *context.T) ([]string, error) { |
| return s.ListApps(ctx) |
| } |
| func (s *service) Child(childName string) layer { |
| return makeLayer(s.App(childName)) |
| } |
| |
| type app struct { |
| syncbase.App |
| } |
| |
| func (a *app) ListChildren(ctx *context.T) ([]string, error) { |
| return a.ListDatabases(ctx) |
| } |
| func (a *app) Child(childName string) layer { |
| return makeLayer(a.NoSQLDatabase(childName, nil)) |
| } |
| |
| type database struct { |
| nosql.Database |
| } |
| |
| func (d *database) ListChildren(ctx *context.T) ([]string, error) { |
| return d.ListTables(ctx) |
| } |
| func (d *database) Child(childName string) layer { |
| return makeLayer(d.Table(childName)) |
| } |
| |
| type table struct { |
| nosql.Table |
| } |
| |
| func (t *table) SetPermissions(ctx *context.T, perms access.Permissions, version string) error { |
| return t.Table.SetPermissions(ctx, perms) |
| } |
| func (t *table) GetPermissions(ctx *context.T) (perms access.Permissions, version string, err error) { |
| perms, err = t.Table.GetPermissions(ctx) |
| return perms, "", err |
| } |
| func (t *table) ListChildren(ctx *context.T) ([]string, error) { |
| panic(notAvailable) |
| } |
| func (t *table) Child(childName string) layer { |
| return &row{t.Row(childName)} |
| } |
| |
| type row struct { |
| nosql.Row |
| } |
| |
| func (r *row) Create(ctx *context.T, perms access.Permissions) error { |
| if perms != nil { |
| panic(fmt.Sprintf("bad perms: %v", perms)) |
| } |
| return r.Put(ctx, true) |
| } |
| func (r *row) Destroy(ctx *context.T) error { |
| return r.Delete(ctx) |
| } |
| func (r *row) SetPermissions(ctx *context.T, perms access.Permissions, version string) error { |
| panic(notAvailable) |
| } |
| func (r *row) GetPermissions(ctx *context.T) (perms access.Permissions, version string, err error) { |
| panic(notAvailable) |
| } |
| func (r *row) ListChildren(ctx *context.T) ([]string, error) { |
| panic(notAvailable) |
| } |
| func (r *row) Child(childName string) layer { |
| panic(notAvailable) |
| } |
| |
| func makeLayer(i interface{}) layer { |
| switch t := i.(type) { |
| case syncbase.Service: |
| return &service{t} |
| case syncbase.App: |
| return &app{t} |
| case nosql.Database: |
| return &database{t} |
| case nosql.Table: |
| return &table{t} |
| default: |
| vlog.Fatalf("unexpected type: %T", t) |
| } |
| return nil |
| } |
| |
| func assertExists(t *testing.T, ctx *context.T, l layer, name string, want bool) { |
| if got, err := l.Exists(ctx); err != nil { |
| t.Fatal(testutil.FormatLogLine(2, "%s.Exists() failed: %v", name, err)) |
| } else if got != want { |
| t.Error(testutil.FormatLogLine(2, "%s.Exists() got %v, want %v", name, got, want)) |
| } |
| } |