blob: 49ce91a817a5abf5b56f9b801a3c7dc66e4e3362 [file] [log] [blame]
// 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 syncbase_test
import (
"reflect"
"strings"
"testing"
"time"
"v.io/v23/context"
"v.io/v23/naming"
"v.io/v23/query/syncql"
"v.io/v23/security"
"v.io/v23/security/access"
wire "v.io/v23/services/syncbase"
"v.io/v23/services/watch"
"v.io/v23/syncbase"
"v.io/v23/verror"
"v.io/v23/vom"
_ "v.io/x/ref/runtime/factories/generic"
tu "v.io/x/ref/services/syncbase/testutil"
)
// TODO(rogulenko): Test perms checking for Glob and Exec.
// Tests various Name, FullName, and Key methods.
func TestNameAndKey(t *testing.T) {
s := syncbase.NewService("s")
a := s.App("a")
d := a.Database("d", nil)
c := d.Collection("c")
r := c.Row("r")
if s.FullName() != "s" {
t.Errorf("Wrong full name: %q", s.FullName())
}
if a.Name() != "a" {
t.Errorf("Wrong name: %q", a.Name())
}
if a.FullName() != naming.Join("s", "a") {
t.Errorf("Wrong name: %q", a.FullName())
}
if d.Name() != "d" {
t.Errorf("Wrong name: %q", d.Name())
}
if d.FullName() != naming.Join("s", "a", "d") {
t.Errorf("Wrong full name: %q", d.FullName())
}
if c.Name() != "c" {
t.Errorf("Wrong name: %q", c.Name())
}
if c.FullName() != naming.Join("s", "a", "d", "c") {
t.Errorf("Wrong full name: %q", c.FullName())
}
if r.Key() != "r" {
t.Errorf("Wrong key: %q", r.Key())
}
if r.FullName() != naming.Join("s", "a", "d", "c", "r") {
t.Errorf("Wrong full name: %q", r.FullName())
}
}
// Tests that Service.ListApps works as expected.
func TestListApps(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
s := syncbase.NewService(sName)
tu.TestListChildren(t, ctx, s, tu.OkAppNames)
}
// Tests that Service.{Set,Get}Permissions work as expected.
func TestServicePerms(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
tu.TestPerms(t, ctx, syncbase.NewService(sName))
}
// Tests that App.Create works as expected.
func TestAppCreate(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
tu.TestCreate(t, ctx, syncbase.NewService(sName))
}
// Tests that App.Create checks names as expected.
func TestAppCreateNameValidation(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
tu.TestCreateNameValidation(t, ctx, syncbase.NewService(sName), tu.OkAppNames, tu.NotOkAppNames)
}
// Tests that App.Destroy works as expected.
func TestAppDestroy(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
tu.TestDestroy(t, ctx, syncbase.NewService(sName))
}
// Tests that App.ListDatabases works as expected.
func TestListDatabases(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "app/a#%b")
tu.TestListChildren(t, ctx, a, tu.OkDbCollectionNames)
}
// Tests that App.{Set,Get}Permissions work as expected.
func TestAppPerms(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
tu.TestPerms(t, ctx, a)
}
// Tests that App.Destroy destroys all databases in the app.
func TestAppDestroyAndRecreate(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
// Create the hierarchy.
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
d2 := tu.CreateDatabase(t, ctx, a, "d2")
c := tu.CreateCollection(t, ctx, d, "c")
// Write some data.
if err := c.Put(ctx, "bar/baz", "A"); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
if err := c.Put(ctx, "foo", "B"); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
// Remove admin and write permissions from "bar" and d2.
fullPerms := tu.DefaultPerms("root:client").Normalize()
readPerms := fullPerms.Copy()
readPerms.Clear("root:client", string(access.Write), string(access.Admin))
readPerms.Normalize()
if err := c.SetPrefixPermissions(ctx, syncbase.Prefix("bar"), readPerms); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := d2.SetPermissions(ctx, readPerms, ""); err != nil {
t.Fatalf("d2.SetPermissions() failed: %v", err)
}
// Verify we have no write access to "bar" anymore.
if err := c.Put(ctx, "bar/bat", "C"); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.Put() should have failed with ErrNoAccess, got: %v", err)
}
// Verify we cannot destroy d2.
if err := d2.Destroy(ctx); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("d2.Destroy() should have failed with ErrNoAccess, got: %v", err)
}
// Destroy app. Destroy needs only admin permissions on the app, so it
// shouldn't be affected by the read-only prefix or database ACL.
if err := a.Destroy(ctx); err != nil {
t.Fatalf("a.Destroy() failed: %v", err)
}
// Recreate the hierarchy.
a = tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d = tu.CreateDatabase(t, ctx, a, "d")
d2 = tu.CreateDatabase(t, ctx, a, "d2")
c = tu.CreateCollection(t, ctx, d, "c")
// Verify collection is empty.
tu.CheckScan(t, ctx, c, syncbase.Prefix(""), []string{}, []interface{}{})
// Verify we again have write access to "bar".
if err := c.Put(ctx, "bar/bat", "C"); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
}
// Tests that Database.Create works as expected.
func TestDatabaseCreate(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
tu.TestCreate(t, ctx, a)
}
// Tests name-checking on database creation.
func TestDatabaseCreateNameValidation(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
tu.TestCreateNameValidation(t, ctx, a, tu.OkDbCollectionNames, tu.NotOkDbCollectionNames)
}
// Tests that Database.Destroy works as expected.
func TestDatabaseDestroy(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
tu.TestDestroy(t, ctx, a)
}
// Tests that Database.Exec works as expected.
// Note: The Exec method is tested more thoroughly in exec_test.go.
// Also, the query package is tested in its entirety in v23/query/...
func TestExec(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
c := tu.CreateCollection(t, ctx, d, "c")
foo := Foo{I: 4, S: "f"}
if err := c.Put(ctx, "foo", foo); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
bar := Bar{F: 0.5, S: "b"}
// NOTE: not best practice, but store bar as
// optional (by passing the address of bar to Put).
// This tests auto-dereferencing.
if err := c.Put(ctx, "bar", &bar); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
baz := Baz{Name: "John Doe", Active: true}
if err := c.Put(ctx, "baz", baz); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
tu.CheckExec(t, ctx, d, "select k, v.Name from c where Type(v) like \"%.Baz\"",
[]string{"k", "v.Name"},
[][]*vom.RawBytes{
{vom.RawBytesOf("baz"), vom.RawBytesOf(baz.Name)},
})
tu.CheckExec(t, ctx, d, "select k, v from c",
[]string{"k", "v"},
[][]*vom.RawBytes{
{vom.RawBytesOf("bar"), vom.RawBytesOf(bar)},
{vom.RawBytesOf("baz"), vom.RawBytesOf(baz)},
{vom.RawBytesOf("foo"), vom.RawBytesOf(foo)},
})
tu.CheckExec(t, ctx, d, "select k, v from c where k like \"ba%\"",
[]string{"k", "v"},
[][]*vom.RawBytes{
{vom.RawBytesOf("bar"), vom.RawBytesOf(bar)},
{vom.RawBytesOf("baz"), vom.RawBytesOf(baz)},
})
tu.CheckExec(t, ctx, d, "select k, v from c where v.Active = true",
[]string{"k", "v"},
[][]*vom.RawBytes{
{vom.RawBytesOf("baz"), vom.RawBytesOf(baz)},
})
tu.CheckExec(t, ctx, d, "select k, v from c where Type(v) like \"%.Bar\"",
[]string{"k", "v"},
[][]*vom.RawBytes{
{vom.RawBytesOf("bar"), vom.RawBytesOf(bar)},
})
tu.CheckExec(t, ctx, d, "select k, v from c where v.F = 0.5",
[]string{"k", "v"},
[][]*vom.RawBytes{
{vom.RawBytesOf("bar"), vom.RawBytesOf(bar)},
})
tu.CheckExec(t, ctx, d, "select k, v from c where Type(v) like \"%.Baz\"",
[]string{"k", "v"},
[][]*vom.RawBytes{
{vom.RawBytesOf("baz"), vom.RawBytesOf(baz)},
})
// Delete baz.
tu.CheckExec(t, ctx, d, "delete from c where Type(v) like \"%.Baz\"",
[]string{"Count"},
[][]*vom.RawBytes{
{vom.RawBytesOf(1)},
})
// Check that baz is no longer in the collection.
tu.CheckExec(t, ctx, d, "select k, v from c",
[]string{"k", "v"},
[][]*vom.RawBytes{
{vom.RawBytesOf("bar"), vom.RawBytesOf(bar)},
{vom.RawBytesOf("foo"), vom.RawBytesOf(foo)},
})
tu.CheckExecError(t, ctx, d, "select k, v from foo", syncql.ErrTableCantAccess.ID)
}
// Tests that Database.ListCollections works as expected.
func TestListCollections(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "app/a#%b")
d := tu.CreateDatabase(t, ctx, a, "d")
tu.TestListChildren(t, ctx, d, tu.OkDbCollectionNames)
}
// Tests that Database.{Set,Get}Permissions work as expected.
func TestDatabasePerms(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
tu.TestPerms(t, ctx, d)
}
// Tests that Collection.Create works as expected.
func TestCollectionCreate(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
tu.TestCreate(t, ctx, d)
}
// Tests name-checking on collection creation.
func TestCollectionCreateNameValidation(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
tu.TestCreateNameValidation(t, ctx, d, tu.OkDbCollectionNames, tu.NotOkDbCollectionNames)
}
// Tests that Collection.Destroy works as expected.
func TestCollectionDestroy(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
tu.TestDestroy(t, ctx, d)
}
// Tests that Collection.{Set,Get,Delete}Permissions methods work as expected.
func TestCollectionPerms(t *testing.T) {
ctx, clientACtx, sName, rootp, cleanup := tu.SetupOrDieCustom("clientA", "server", nil)
defer cleanup()
clientBCtx := tu.NewCtx(ctx, rootp, "clientB")
a := tu.CreateApp(t, clientACtx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, clientACtx, a, "d")
c := tu.CreateCollection(t, clientACtx, d, "c")
// Permission objects.
aAndB := tu.DefaultPerms("root:clientA", "root:clientB")
aOnly := tu.DefaultPerms("root:clientA")
bOnly := tu.DefaultPerms("root:clientB")
// Set initial permissions.
if err := c.SetPermissions(clientACtx, aAndB); err != nil {
t.Fatalf("c.SetPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix(""), aAndB); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix("prefix"), aAndB); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientBCtx, syncbase.Prefix("prefix"), aAndB); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix("prefix_a"), aOnly); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientBCtx, syncbase.Prefix("prefix_b"), bOnly); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
// Checks A has no access to 'prefix_b' and vice versa.
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix("prefix_b"), aOnly); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.SetPrefixPermissions() should have failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix("prefix_b_suffix"), aOnly); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.SetPrefixPermissions() should have failed: %v", err)
}
if err := c.SetPrefixPermissions(clientBCtx, syncbase.Prefix("prefix_a"), bOnly); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.SetPrefixPermissions() should have failed: %v", err)
}
if err := c.SetPrefixPermissions(clientBCtx, syncbase.Prefix("prefix_a_suffix"), bOnly); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.SetPrefixPermissions() should have failed: %v", err)
}
// Check GetPrefixPermissions.
wantPerms := []syncbase.PrefixPermissions{
syncbase.PrefixPermissions{Prefix: syncbase.Prefix(""), Perms: aAndB},
}
if got, _ := c.GetPrefixPermissions(clientACtx, ""); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
if got, _ := c.GetPrefixPermissions(clientACtx, "abc"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
wantPerms = []syncbase.PrefixPermissions{
syncbase.PrefixPermissions{Prefix: syncbase.Prefix("prefix"), Perms: aAndB},
syncbase.PrefixPermissions{Prefix: syncbase.Prefix(""), Perms: aAndB},
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_c"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
wantPerms = []syncbase.PrefixPermissions{
syncbase.PrefixPermissions{Prefix: syncbase.Prefix("prefix_a"), Perms: aOnly},
syncbase.PrefixPermissions{Prefix: syncbase.Prefix("prefix"), Perms: aAndB},
syncbase.PrefixPermissions{Prefix: syncbase.Prefix(""), Perms: aAndB},
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_a"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_a_suffix"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
wantPerms = []syncbase.PrefixPermissions{
syncbase.PrefixPermissions{Prefix: syncbase.Prefix("prefix_b"), Perms: bOnly},
syncbase.PrefixPermissions{Prefix: syncbase.Prefix("prefix"), Perms: aAndB},
syncbase.PrefixPermissions{Prefix: syncbase.Prefix(""), Perms: aAndB},
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_b"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_b_suffix"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
// Delete some prefix permissions and check again.
// Check that A can't delete permissions of B.
if err := c.DeletePrefixPermissions(clientACtx, syncbase.Prefix("prefix_b")); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.DeletePrefixPermissions() should have failed: %v", err)
}
if err := c.DeletePrefixPermissions(clientBCtx, syncbase.Prefix("prefix_a")); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.DeletePrefixPermissions() should have failed: %v", err)
}
// Delete 'prefix' and 'prefix_a'
if err := c.DeletePrefixPermissions(clientACtx, syncbase.Prefix("prefix")); err != nil {
t.Fatalf("c.DeletePrefixPermissions() failed: %v", err)
}
if err := c.DeletePrefixPermissions(clientACtx, syncbase.Prefix("prefix_a")); err != nil {
t.Fatalf("c.DeletePrefixPermissions() failed: %v", err)
}
// Check DeletePrefixPermissions is idempotent.
if err := c.DeletePrefixPermissions(clientACtx, syncbase.Prefix("prefix")); err != nil {
t.Fatalf("c.DeletePrefixPermissions() failed: %v", err)
}
// Check GetPrefixPermissions again.
wantPerms = []syncbase.PrefixPermissions{
syncbase.PrefixPermissions{Prefix: syncbase.Prefix(""), Perms: aAndB},
}
if got, _ := c.GetPrefixPermissions(clientACtx, ""); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_a"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_a_suffix"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
wantPerms = []syncbase.PrefixPermissions{
syncbase.PrefixPermissions{Prefix: syncbase.Prefix("prefix_b"), Perms: bOnly},
syncbase.PrefixPermissions{Prefix: syncbase.Prefix(""), Perms: aAndB},
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_b"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_b_suffix"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
// Remove B from collection-level permissions and check B has no access.
if err := c.SetPermissions(clientACtx, aOnly); err != nil {
t.Fatalf("c.SetPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix(""), aOnly); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if _, err := c.GetPrefixPermissions(clientBCtx, ""); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.GetPrefixPermissions() should have failed: %v", err)
}
if _, err := c.GetPrefixPermissions(clientBCtx, "prefix_b"); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.GetPrefixPermissions() should have failed: %v", err)
}
if err := c.SetPermissions(clientBCtx, bOnly); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.SetPermissions() should have failed: %v", err)
}
if err := c.SetPrefixPermissions(clientBCtx, syncbase.Prefix("prefix_b"), bOnly); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.SetPrefixPermissions() should have failed: %v", err)
}
}
// Tests that Collection.{Set,Get}Permissions methods work as expected.
func TestCollectionPermsDifferentOrder(t *testing.T) {
ctx, clientACtx, sName, rootp, cleanup := tu.SetupOrDieCustom("clientA", "server", nil)
defer cleanup()
clientBCtx := tu.NewCtx(ctx, rootp, "clientB")
a := tu.CreateApp(t, clientACtx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, clientACtx, a, "d")
c := tu.CreateCollection(t, clientACtx, d, "c")
// Permission objects.
aAndB := tu.DefaultPerms("root:clientA", "root:clientB")
aOnly := tu.DefaultPerms("root:clientA")
bOnly := tu.DefaultPerms("root:clientB")
// Set initial permissions.
if err := c.SetPermissions(clientACtx, aAndB); err != nil {
t.Fatalf("c.SetPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix(""), aAndB); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix("prefix"), aAndB); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientBCtx, syncbase.Prefix("prefix_b"), bOnly); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix("prefix_a"), aOnly); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
wantPerms := []syncbase.PrefixPermissions{
syncbase.PrefixPermissions{Prefix: syncbase.Prefix("prefix_a"), Perms: aOnly},
syncbase.PrefixPermissions{Prefix: syncbase.Prefix("prefix"), Perms: aAndB},
syncbase.PrefixPermissions{Prefix: syncbase.Prefix(""), Perms: aAndB},
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_a"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_a_suffix"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
wantPerms = []syncbase.PrefixPermissions{
syncbase.PrefixPermissions{Prefix: syncbase.Prefix("prefix_b"), Perms: bOnly},
syncbase.PrefixPermissions{Prefix: syncbase.Prefix("prefix"), Perms: aAndB},
syncbase.PrefixPermissions{Prefix: syncbase.Prefix(""), Perms: aAndB},
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_b"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
if got, _ := c.GetPrefixPermissions(clientACtx, "prefix_b_suffix"); !reflect.DeepEqual(got, wantPerms) {
t.Fatalf("Unexpected permissions: got %v, want %v", got, wantPerms)
}
}
// bitmaskToPrefix converts first length bits of bitmask to a string.
// Bits are converted the following way: 0 -> 'b', 1 -> 'a'.
// The lowest bit becomes the first character in the string.
// For example, (0x8, 4) -> "bbba".
func bitmaskToPrefix(bitmask, length int) string {
prefix := ""
for k := 0; k < length; k++ {
prefix += string('b' - ((bitmask >> uint32(k)) & 1))
}
return prefix
}
func checkGetPermissions(t *testing.T, ctx *context.T, c syncbase.Collection, prefix string, max, min int) {
perms := tu.DefaultPerms("root:client")
for len(prefix) < max {
prefix += "a"
}
var wantPerms []syncbase.PrefixPermissions
for k := max; k >= min; k-- {
wantPerms = append(wantPerms, syncbase.PrefixPermissions{Prefix: syncbase.Prefix(prefix[:k]), Perms: perms})
}
wantPerms = append(wantPerms, syncbase.PrefixPermissions{Prefix: syncbase.Prefix(""), Perms: perms})
if gotPerms, _ := c.GetPrefixPermissions(ctx, prefix); !reflect.DeepEqual(gotPerms, wantPerms) {
tu.Fatalf(t, "Unexpected permissions for %q: got %v, want %v", prefix, gotPerms, wantPerms)
}
}
// Tests that Collection.{Set,Get,Delete}Permissions methods work as expected
// for nested prefixes.
func TestCollectionPermsNested(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
c := tu.CreateCollection(t, ctx, d, "c")
// The permission object.
perms := tu.DefaultPerms("root:client")
depth := 4
// Set permissions in order b, a, bb, ba, ab, aa, ...
for i := 1; i <= depth; i++ {
for j := 0; j < 1<<uint32(i); j++ {
prefix := bitmaskToPrefix(j, i)
if err := c.SetPrefixPermissions(ctx, syncbase.Prefix(prefix), perms); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed for %q: %v", prefix, err)
}
checkGetPermissions(t, ctx, c, prefix, i, 1)
}
}
// Delete permissions in the reverse order.
for i := depth; i >= 1; i-- {
for j := 0; j < 1<<uint32(i); j++ {
prefix := bitmaskToPrefix(j, i)
if err := c.DeletePrefixPermissions(ctx, syncbase.Prefix(prefix)); err != nil {
t.Fatalf("c.DeletePrefixPermissions() failed for %q: %v", prefix, err)
}
checkGetPermissions(t, ctx, c, prefix, i-1, 1)
}
}
// Do again the first two steps in the reverse order.
for i := depth; i >= 1; i-- {
for j := 0; j < 1<<uint32(i); j++ {
prefix := bitmaskToPrefix(j, i)
if err := c.SetPrefixPermissions(ctx, syncbase.Prefix(prefix), perms); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed for %q: %v", prefix, err)
}
checkGetPermissions(t, ctx, c, prefix, depth, i)
}
}
for i := 1; i <= depth; i++ {
for j := 0; j < 1<<uint32(i); j++ {
prefix := bitmaskToPrefix(j, i)
if err := c.DeletePrefixPermissions(ctx, syncbase.Prefix(prefix)); err != nil {
t.Fatalf("c.DeletePrefixPermissions() failed for %q: %v", prefix, err)
}
checkGetPermissions(t, ctx, c, prefix, depth, i+1)
}
}
}
// Tests that Collection.Destroy deletes all rows and ACLs in the collection.
func TestCollectionDestroyAndRecreate(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
c := tu.CreateCollection(t, ctx, d, "c")
// Write some data.
if err := c.Put(ctx, "bar/baz", "A"); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
if err := c.Put(ctx, "foo", "B"); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
// Remove admin and write permissions from "bar".
fullPerms := tu.DefaultPerms("root:client").Normalize()
readPerms := fullPerms.Copy()
readPerms.Clear("root:client", string(access.Write), string(access.Admin))
readPerms.Normalize()
if err := c.SetPrefixPermissions(ctx, syncbase.Prefix("bar"), readPerms); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
// Verify we have no write access to "bar" anymore.
if err := c.Put(ctx, "bar/bat", "C"); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.Put() should have failed with ErrNoAccess, got: %v", err)
}
// Destroy collection. Destroy needs only admin permissions on the collection,
// so it shouldn't be affected by the read-only prefix ACL.
if err := c.Destroy(ctx); err != nil {
t.Fatalf("c.Destroy() failed: %v", err)
}
// Recreate the collection.
if err := c.Create(ctx, nil); err != nil {
t.Fatalf("c.Create() (recreate) failed: %v", err)
}
// Verify collection is empty.
tu.CheckScan(t, ctx, c, syncbase.Prefix(""), []string{}, []interface{}{})
// Verify we again have write access to "bar".
if err := c.Put(ctx, "bar/bat", "C"); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
}
////////////////////////////////////////
// Tests involving rows
type Foo struct {
I int
S string
}
type Bar struct {
F float32
S string
}
type Baz struct {
Name string
Active bool
}
// Tests that Collection.Scan works as expected.
func TestCollectionScan(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
c := tu.CreateCollection(t, ctx, d, "c")
tu.CheckScan(t, ctx, c, syncbase.Prefix(""), []string{}, []interface{}{})
fooWant := Foo{I: 4, S: "f"}
if err := c.Put(ctx, "foo", &fooWant); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
barWant := Bar{F: 0.5, S: "b"}
if err := c.Put(ctx, "bar", &barWant); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
// Match all keys.
tu.CheckScan(t, ctx, c, syncbase.Prefix(""), []string{"bar", "foo"}, []interface{}{&barWant, &fooWant})
tu.CheckScan(t, ctx, c, syncbase.Range("", ""), []string{"bar", "foo"}, []interface{}{&barWant, &fooWant})
tu.CheckScan(t, ctx, c, syncbase.Range("", "z"), []string{"bar", "foo"}, []interface{}{&barWant, &fooWant})
tu.CheckScan(t, ctx, c, syncbase.Range("a", ""), []string{"bar", "foo"}, []interface{}{&barWant, &fooWant})
tu.CheckScan(t, ctx, c, syncbase.Range("a", "z"), []string{"bar", "foo"}, []interface{}{&barWant, &fooWant})
// Match "bar" only.
tu.CheckScan(t, ctx, c, syncbase.Prefix("b"), []string{"bar"}, []interface{}{&barWant})
tu.CheckScan(t, ctx, c, syncbase.Prefix("bar"), []string{"bar"}, []interface{}{&barWant})
tu.CheckScan(t, ctx, c, syncbase.Range("bar", "baz"), []string{"bar"}, []interface{}{&barWant})
tu.CheckScan(t, ctx, c, syncbase.Range("bar", "foo"), []string{"bar"}, []interface{}{&barWant})
tu.CheckScan(t, ctx, c, syncbase.Range("", "foo"), []string{"bar"}, []interface{}{&barWant})
// Match "foo" only.
tu.CheckScan(t, ctx, c, syncbase.Prefix("f"), []string{"foo"}, []interface{}{&fooWant})
tu.CheckScan(t, ctx, c, syncbase.Prefix("foo"), []string{"foo"}, []interface{}{&fooWant})
tu.CheckScan(t, ctx, c, syncbase.Range("foo", "fox"), []string{"foo"}, []interface{}{&fooWant})
tu.CheckScan(t, ctx, c, syncbase.Range("foo", ""), []string{"foo"}, []interface{}{&fooWant})
// Match nothing.
tu.CheckScan(t, ctx, c, syncbase.Range("a", "bar"), []string{}, []interface{}{})
tu.CheckScan(t, ctx, c, syncbase.Range("bar", "bar"), []string{}, []interface{}{})
tu.CheckScan(t, ctx, c, syncbase.Prefix("z"), []string{}, []interface{}{})
}
// Tests that Collection.DeleteRange works as expected.
func TestCollectionDeleteRange(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
c := tu.CreateCollection(t, ctx, d, "c")
tu.CheckScan(t, ctx, c, syncbase.Prefix(""), []string{}, []interface{}{})
// Put foo and bar.
fooWant := Foo{I: 4, S: "f"}
if err := c.Put(ctx, "foo", &fooWant); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
barWant := Bar{F: 0.5, S: "b"}
if err := c.Put(ctx, "bar", &barWant); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
tu.CheckScan(t, ctx, c, syncbase.Prefix(""), []string{"bar", "foo"}, []interface{}{&barWant, &fooWant})
// Delete foo.
if err := c.DeleteRange(ctx, syncbase.Prefix("f")); err != nil {
t.Fatalf("c.DeleteRange() failed: %v", err)
}
tu.CheckScan(t, ctx, c, syncbase.Prefix(""), []string{"bar"}, []interface{}{&barWant})
// Restore foo.
if err := c.Put(ctx, "foo", &fooWant); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
tu.CheckScan(t, ctx, c, syncbase.Prefix(""), []string{"bar", "foo"}, []interface{}{&barWant, &fooWant})
// Delete everything.
if err := c.DeleteRange(ctx, syncbase.Prefix("")); err != nil {
t.Fatalf("c.DeleteRange() failed: %v", err)
}
tu.CheckScan(t, ctx, c, syncbase.Prefix(""), []string{}, []interface{}{})
}
// Tests that Collection.{Get,Put,Delete} work as expected.
func TestCollectionRowMethods(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
c := tu.CreateCollection(t, ctx, d, "c")
got, want := Foo{}, Foo{I: 4, S: "foo"}
if err := c.Get(ctx, "f", &got); verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("c.Get() should have failed: %v", err)
}
if err := c.Put(ctx, "f", &want); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
if err := c.Get(ctx, "f", &got); err != nil {
t.Fatalf("c.Get() failed: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("Values do not match: got %v, want %v", got, want)
}
// Overwrite value.
want.I = 6
if err := c.Put(ctx, "f", &want); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
if err := c.Get(ctx, "f", &got); err != nil {
t.Fatalf("c.Get() failed: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("Values do not match: got %v, want %v", got, want)
}
if err := c.Delete(ctx, "f"); err != nil {
t.Fatalf("c.Delete() failed: %v", err)
}
if err := c.Get(ctx, "f", &got); verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("r.Get() should have failed: %v", err)
}
}
// Tests that Row.{Get,Put,Delete} work as expected.
func TestRowMethods(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
c := tu.CreateCollection(t, ctx, d, "c")
r := c.Row("f")
got, want := Foo{}, Foo{I: 4, S: "foo"}
if err := r.Get(ctx, &got); verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("r.Get() should have failed: %v", err)
}
if err := r.Put(ctx, &want); err != nil {
t.Fatalf("r.Put() failed: %v", err)
}
if err := r.Get(ctx, &got); err != nil {
t.Fatalf("r.Get() failed: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("Values do not match: got %v, want %v", got, want)
}
// Overwrite value.
want.I = 6
if err := r.Put(ctx, &want); err != nil {
t.Fatalf("r.Put() failed: %v", err)
}
if err := r.Get(ctx, &got); err != nil {
t.Fatalf("r.Get() failed: %v", err)
}
if !reflect.DeepEqual(got, want) {
t.Fatalf("Values do not match: got %v, want %v", got, want)
}
if err := r.Delete(ctx); err != nil {
t.Fatalf("r.Delete() failed: %v", err)
}
if err := r.Get(ctx, &got); verror.ErrorID(err) != verror.ErrNoExist.ID {
t.Fatalf("r.Get() should have failed: %v", err)
}
}
// Tests name-checking on row creation.
func TestRowNameValidation(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
c := tu.CreateCollection(t, ctx, d, "c")
tu.TestCreateNameValidation(t, ctx, c, tu.OkRowNames, tu.NotOkRowNames)
}
// Test permission checking in Row.{Get,Put,Delete} and
// Collection.{Scan, DeleteRange}.
func TestRowPermissions(t *testing.T) {
ctx, clientACtx, sName, rootp, cleanup := tu.SetupOrDieCustom("clientA", "server", nil)
defer cleanup()
clientBCtx := tu.NewCtx(ctx, rootp, "clientB")
a := tu.CreateApp(t, clientACtx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, clientACtx, a, "d")
c := tu.CreateCollection(t, clientACtx, d, "c")
// Permission objects.
aAndB := tu.DefaultPerms("root:clientA", "root:clientB")
aOnly := tu.DefaultPerms("root:clientA")
bOnly := tu.DefaultPerms("root:clientB")
// Set initial permissions.
if err := c.SetPermissions(clientACtx, aAndB); err != nil {
t.Fatalf("c.SetPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix(""), aAndB); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix("a"), aOnly); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientBCtx, syncbase.Prefix("b"), bOnly); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
// Add some key-value pairs.
ra := c.Row("afoo")
rb := c.Row("bfoo")
if err := ra.Put(clientACtx, Foo{}); err != nil {
t.Fatalf("ra.Put() failed: %v", err)
}
if err := rb.Put(clientBCtx, Foo{}); err != nil {
t.Fatalf("rb.Put() failed: %v", err)
}
// Check A doesn't have access to 'b'.
if err := rb.Get(clientACtx, &Foo{}); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("rb.Get() should have failed: %v", err)
}
if err := rb.Put(clientACtx, Foo{}); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("rb.Put() should have failed: %v", err)
}
if err := rb.Delete(clientACtx); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("rb.Delete() should have failed: %v", err)
}
// Test Collection.DeleteRange and Scan.
if err := c.DeleteRange(clientACtx, syncbase.Prefix("")); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("c.DeleteRange should have failed: %v", err)
}
s := c.Scan(clientACtx, syncbase.Prefix(""))
if !s.Advance() {
t.Fatalf("Stream should have advanced: %v", s.Err())
}
if s.Key() != "afoo" {
t.Fatalf("Unexpected key: got %q, want %q", s.Key(), "afoo")
}
if s.Advance() {
t.Fatalf("Stream advanced unexpectedly")
}
if err := s.Err(); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("Unexpected stream error: %v", err)
}
}
// Tests prefix perms where get is allowed but put is not.
func TestMixedPrefixPerms(t *testing.T) {
ctx, clientACtx, sName, rootp, cleanup := tu.SetupOrDieCustom("clientA", "server", nil)
defer cleanup()
clientBCtx := tu.NewCtx(ctx, rootp, "clientB")
a := tu.CreateApp(t, clientACtx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, clientACtx, a, "d")
c := tu.CreateCollection(t, clientACtx, d, "c")
// Permission objects.
aAndB := tu.DefaultPerms("root:clientA", "root:clientB")
aAllBRead := tu.DefaultPerms("root:clientA")
aAllBRead.Add(security.BlessingPattern("root:clientB"), string(access.Read))
// Set initial permissions.
if err := c.SetPermissions(clientACtx, aAndB); err != nil {
t.Fatalf("c.SetPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix(""), aAndB); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix("a"), aAllBRead); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
// Both A and B can read and write row key "z".
r := c.Row("z")
if err := r.Put(clientACtx, Foo{}); err != nil {
t.Fatalf("client A r.Put() failed: %v", err)
}
if err := r.Put(clientBCtx, Foo{}); err != nil {
t.Fatalf("client B r.Put() failed: %v", err)
}
if err := r.Get(clientACtx, &Foo{}); err != nil {
t.Fatalf("client A r.Get() failed: %v", err)
}
if err := r.Get(clientBCtx, &Foo{}); err != nil {
t.Fatalf("client B r.Get() failed: %v", err)
}
// Both A and B can read row key "a", but only A can write it.
r = c.Row("a")
if err := r.Put(clientACtx, Foo{}); err != nil {
t.Fatalf("client A r.Put() failed: %v", err)
}
if err := r.Put(clientBCtx, Foo{}); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("client B r.Put() should have failed: %v", err)
}
if err := r.Get(clientACtx, &Foo{}); err != nil {
t.Fatalf("client A r.Get() failed: %v", err)
}
if err := r.Get(clientBCtx, &Foo{}); err != nil {
t.Fatalf("client B r.Get() failed: %v", err)
}
// Same for a:foo.
r = c.Row("a:foo")
if err := r.Put(clientACtx, Foo{}); err != nil {
t.Fatalf("client A r.Put() failed: %v", err)
}
if err := r.Put(clientBCtx, Foo{}); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("client B r.Put() should have failed: %v", err)
}
if err := r.Get(clientACtx, &Foo{}); err != nil {
t.Fatalf("client A r.Get() failed: %v", err)
}
if err := r.Get(clientBCtx, &Foo{}); err != nil {
t.Fatalf("client B r.Get() failed: %v", err)
}
}
// Tests prefix perms where client A has full access to everything except for a
// particular prefix, while client B can only read/write that prefix.
// Originally inspired by syncslides example app.
func TestNestedMixedPrefixPerms(t *testing.T) {
ctx, clientACtx, sName, rootp, cleanup := tu.SetupOrDieCustom("clientA", "server", nil)
defer cleanup()
clientBCtx := tu.NewCtx(ctx, rootp, "clientB")
a := tu.CreateApp(t, clientACtx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, clientACtx, a, "d")
c := tu.CreateCollection(t, clientACtx, d, "c")
// On "", resolve should be open, and only A should have read/write/admin.
// On "b", read/resolve should be open, and only B should have write/admin.
prefixEmpty, err := access.ReadPermissions(strings.NewReader(`{"Read":{"In":["root:clientA"],"NotIn":[]},"Admin":{"In":["root:clientA"],"NotIn":[]},"Resolve":{"In":["..."],"NotIn":[]},"Write":{"In":["root:clientA"],"NotIn":[]}}`))
if err != nil {
t.Fatalf("ReadPermissions failed: %v", err)
}
prefixB, err := access.ReadPermissions(strings.NewReader(`{"Admin":{"In":["root:clientB"],"NotIn":[]},"Read":{"In":["..."],"NotIn":[]},"Write":{"In":["root:clientB"],"NotIn":[]},"Resolve":{"In":["..."],"NotIn":[]}}}`))
if err != nil {
t.Fatalf("ReadPermissions failed: %v", err)
}
// Set initial permissions.
aAndB := tu.DefaultPerms("root:clientA", "root:clientB")
if err := c.SetPermissions(clientACtx, aAndB); err != nil {
t.Fatalf("c.SetPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix(""), prefixEmpty); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
// Note that with the current perms, A must write these perms on B's behalf.
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix("b"), prefixB); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
// A should be able to read/write "a".
// B should be able to to read/write "b".
// B should not be able to read/write "a".
// A should not be able to write "b", but should be able to read it.
r := c.Row("a")
if err := r.Put(clientACtx, Foo{}); err != nil {
t.Fatalf("client A r.Put() failed: %v", err)
}
if err := r.Get(clientACtx, &Foo{}); err != nil {
t.Fatalf("client A r.Get() failed: %v", err)
}
r = c.Row("b")
if err := r.Put(clientBCtx, Foo{}); err != nil {
t.Fatalf("client B r.Put() failed: %v", err)
}
if err := r.Get(clientBCtx, &Foo{}); err != nil {
t.Fatalf("client B r.Get() failed: %v", err)
}
r = c.Row("a")
if err := r.Put(clientBCtx, Foo{}); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("client B r.Put() should have failed: %v", err)
}
if err := r.Get(clientBCtx, &Foo{}); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("client B r.Get() should have failed: %v", err)
}
r = c.Row("b")
if err := r.Put(clientACtx, Foo{}); verror.ErrorID(err) != verror.ErrNoAccess.ID {
t.Fatalf("client A r.Put() should have failed: %v", err)
}
if err := r.Get(clientACtx, &Foo{}); err != nil {
t.Fatalf("client A r.Get() failed: %v", err)
}
}
// TestWatchBasic test the basic client watch functionality: no perms,
// no batches.
func TestWatchBasic(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
c := tu.CreateCollection(t, ctx, d, "c")
var resumeMarkers []watch.ResumeMarker
// Generate the data and resume markers.
// Initial state.
resumeMarker, err := d.GetResumeMarker(ctx)
if err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
resumeMarkers = append(resumeMarkers, resumeMarker)
// Put "abc".
r := c.Row("abc")
if err := r.Put(ctx, "value"); err != nil {
t.Fatalf("r.Put() failed: %v", err)
}
if resumeMarker, err = d.GetResumeMarker(ctx); err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
resumeMarkers = append(resumeMarkers, resumeMarker)
// Delete "abc".
if err := r.Delete(ctx); err != nil {
t.Fatalf("r.Delete() failed: %v", err)
}
if resumeMarker, err = d.GetResumeMarker(ctx); err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
resumeMarkers = append(resumeMarkers, resumeMarker)
// Put "a".
r = c.Row("a")
if err := r.Put(ctx, "value"); err != nil {
t.Fatalf("r.Put() failed: %v", err)
}
if resumeMarker, err = d.GetResumeMarker(ctx); err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
resumeMarkers = append(resumeMarkers, resumeMarker)
vomValue, _ := vom.Encode("value")
allChanges := []syncbase.WatchChange{
syncbase.WatchChange{
Collection: "c",
Row: "abc",
ChangeType: syncbase.PutChange,
ValueBytes: vomValue,
ResumeMarker: resumeMarkers[1],
},
syncbase.WatchChange{
Collection: "c",
Row: "abc",
ChangeType: syncbase.DeleteChange,
ResumeMarker: resumeMarkers[2],
},
syncbase.WatchChange{
Collection: "c",
Row: "a",
ChangeType: syncbase.PutChange,
ValueBytes: vomValue,
ResumeMarker: resumeMarkers[3],
},
}
ctxWithTimeout, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
wstream, _ := d.Watch(ctxWithTimeout, "c", "a", resumeMarkers[0])
tu.CheckWatch(t, wstream, allChanges)
wstream, _ = d.Watch(ctxWithTimeout, "c", "a", resumeMarkers[1])
tu.CheckWatch(t, wstream, allChanges[1:])
wstream, _ = d.Watch(ctxWithTimeout, "c", "a", resumeMarkers[2])
tu.CheckWatch(t, wstream, allChanges[2:])
wstream, _ = d.Watch(ctxWithTimeout, "c", "abc", resumeMarkers[0])
tu.CheckWatch(t, wstream, allChanges[:2])
wstream, _ = d.Watch(ctxWithTimeout, "c", "abc", resumeMarkers[1])
tu.CheckWatch(t, wstream, allChanges[1:2])
}
// TestWatchWithBatchAndPerms test that the client watch correctly handles
// batches and prefix perms.
func TestWatchWithBatchAndPerms(t *testing.T) {
ctx, clientACtx, sName, rootp, cleanup := tu.SetupOrDieCustom("clientA", "server", nil)
defer cleanup()
clientBCtx := tu.NewCtx(ctx, rootp, "clientB")
a := tu.CreateApp(t, clientACtx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, clientACtx, a, "d")
c := tu.CreateCollection(t, clientACtx, d, "c")
// Set initial permissions.
aAndB := tu.DefaultPerms("root:clientA", "root:clientB")
aOnly := tu.DefaultPerms("root:clientA")
if err := c.SetPermissions(clientACtx, aAndB); err != nil {
t.Fatalf("c.SetPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix(""), aAndB); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(clientACtx, syncbase.Prefix("a"), aOnly); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
// Get the initial resume marker.
resumeMarker, err := d.GetResumeMarker(clientACtx)
if err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
initMarker := resumeMarker
// Do two puts in a batch.
if err := syncbase.RunInBatch(clientACtx, d, wire.BatchOptions{}, func(b syncbase.BatchDatabase) error {
c := b.Collection("c")
if err := c.Put(clientACtx, "a", "value"); err != nil {
return err
}
return c.Put(clientACtx, "b", "value")
}); err != nil {
t.Fatalf("RunInBatch failed: %v", err)
}
if resumeMarker, err = d.GetResumeMarker(clientACtx); err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
vomValue, _ := vom.Encode("value")
allChanges := []syncbase.WatchChange{
syncbase.WatchChange{
Collection: "c",
Row: "a",
ChangeType: syncbase.PutChange,
ValueBytes: vomValue,
ResumeMarker: nil,
Continued: true,
},
syncbase.WatchChange{
Collection: "c",
Row: "b",
ChangeType: syncbase.PutChange,
ValueBytes: vomValue,
ResumeMarker: resumeMarker,
},
}
ctxAWithTimeout, cancelA := context.WithTimeout(clientACtx, 10*time.Second)
defer cancelA()
ctxBWithTimeout, cancelB := context.WithTimeout(clientBCtx, 10*time.Second)
defer cancelB()
// ClientA should see both changes as one batch.
wstream, _ := d.Watch(ctxAWithTimeout, "c", "", initMarker)
tu.CheckWatch(t, wstream, allChanges)
// ClientB should see only one change.
wstream, _ = d.Watch(ctxBWithTimeout, "c", "", initMarker)
tu.CheckWatch(t, wstream, allChanges[1:])
}
// TestWatchWithInitialState test that the client watch correctly handles
// fetching initial state on empty resume marker, including batches and
// prefix perms.
func TestWatchWithInitialState(t *testing.T) {
ctx, adminCtx, sName, rootp, cleanup := tu.SetupOrDieCustom("admin", "server", nil)
defer cleanup()
clientCtx := tu.NewCtx(ctx, rootp, "client")
a := tu.CreateApp(t, adminCtx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, adminCtx, a, "d")
c := tu.CreateCollection(t, adminCtx, d, "c")
// Set permissions. Lock client out of "b".
openAcl := tu.DefaultPerms("root:admin", "root:client")
adminAcl := tu.DefaultPerms("root:admin")
if err := c.SetPermissions(adminCtx, openAcl); err != nil {
t.Fatalf("c.SetPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(adminCtx, syncbase.Prefix(""), openAcl); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
if err := c.SetPrefixPermissions(adminCtx, syncbase.Prefix("b"), adminAcl); err != nil {
t.Fatalf("c.SetPrefixPermissions() failed: %v", err)
}
// Put "a/1" and "b/1" in a batch.
if err := syncbase.RunInBatch(adminCtx, d, wire.BatchOptions{}, func(b syncbase.BatchDatabase) error {
c := b.Collection("c")
if err := c.Put(adminCtx, "a/1", "value"); err != nil {
return err
}
return c.Put(adminCtx, "b/1", "value")
}); err != nil {
t.Fatalf("RunInBatch failed: %v", err)
}
// Put "c/1".
if err := c.Put(adminCtx, "c/1", "value"); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
ctxWithTimeout, cancel := context.WithTimeout(clientCtx, 10*time.Second)
defer cancel()
// Start watches with empty resume marker.
wstreamAll, _ := d.Watch(ctxWithTimeout, "c", "", nil)
wstreamD, _ := d.Watch(ctxWithTimeout, "c", "d", nil)
resumeMarkerInitial, err := d.GetResumeMarker(clientCtx)
if err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
vomValue, _ := vom.Encode("value")
initialChanges := []syncbase.WatchChange{
syncbase.WatchChange{
Collection: "c",
Row: "a/1",
ChangeType: syncbase.PutChange,
ValueBytes: vomValue,
ResumeMarker: nil,
Continued: true,
},
syncbase.WatchChange{
Collection: "c",
Row: "c/1",
ChangeType: syncbase.PutChange,
ValueBytes: vomValue,
ResumeMarker: resumeMarkerInitial,
},
}
// Watch with empty prefix should have seen the initial state as one batch,
// omitting the inaccessible row.
tu.CheckWatch(t, wstreamAll, initialChanges)
// More writes.
// Put "a/2" and "b/2" in a batch.
if err := syncbase.RunInBatch(adminCtx, d, wire.BatchOptions{}, func(b syncbase.BatchDatabase) error {
c := b.Collection("c")
if err := c.Put(adminCtx, "a/2", "value"); err != nil {
return err
}
return c.Put(adminCtx, "b/2", "value")
}); err != nil {
t.Fatalf("RunInBatch failed: %v", err)
}
resumeMarkerAfterA2B2, err := d.GetResumeMarker(clientCtx)
if err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
// Put "d/1".
if err := c.Put(adminCtx, "d/1", "value"); err != nil {
t.Fatalf("c.Put() failed: %v", err)
}
resumeMarkerAfterD1, err := d.GetResumeMarker(clientCtx)
if err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
continuedChanges := []syncbase.WatchChange{
syncbase.WatchChange{
Collection: "c",
Row: "a/2",
ChangeType: syncbase.PutChange,
ValueBytes: vomValue,
ResumeMarker: resumeMarkerAfterA2B2,
},
syncbase.WatchChange{
Collection: "c",
Row: "d/1",
ChangeType: syncbase.PutChange,
ValueBytes: vomValue,
ResumeMarker: resumeMarkerAfterD1,
},
}
// Watch with empty prefix should have seen the continued changes as separate
// batches, omitting inaccessible rows.
tu.CheckWatch(t, wstreamAll, continuedChanges)
// Watch with prefix "d" should have seen only the last change; its initial
// state was empty.
tu.CheckWatch(t, wstreamD, continuedChanges[1:])
}
// TestBlockingWatch tests that the server side of the client watch correctly
// blocks until new updates to the database arrive.
func TestBlockingWatch(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
c := tu.CreateCollection(t, ctx, d, "c")
resumeMarker, err := d.GetResumeMarker(ctx)
if err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
ctxWithTimeout, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
wstream, _ := d.Watch(ctxWithTimeout, "c", "a", resumeMarker)
vomValue, _ := vom.Encode("value")
for i := 0; i < 10; i++ {
// Put "abc".
r := c.Row("abc")
if err := r.Put(ctx, "value"); err != nil {
t.Fatalf("r.Put() failed: %v", err)
}
if resumeMarker, err = d.GetResumeMarker(ctx); err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
if !wstream.Advance() {
t.Fatalf("wstream.Advance() reached the end: %v", wstream.Err())
}
want := syncbase.WatchChange{
Collection: "c",
Row: "abc",
ChangeType: syncbase.PutChange,
ValueBytes: vomValue,
ResumeMarker: resumeMarker,
}
if got := wstream.Change(); !reflect.DeepEqual(got, want) {
t.Fatalf("unexpected watch change: got %v, want %v", got, want)
}
}
}
// TestBlockedWatchCancel tests that the watch call blocked on the server side
// can be successfully canceled from the client.
func TestBlockedWatchCancel(t *testing.T) {
ctx, sName, cleanup := tu.SetupOrDie(nil)
defer cleanup()
a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a")
d := tu.CreateDatabase(t, ctx, a, "d")
resumeMarker, err := d.GetResumeMarker(ctx)
if err != nil {
t.Fatalf("d.GetResumeMarker() failed: %v", err)
}
ctxCancel, cancel := context.WithCancel(ctx)
wstream, err := d.Watch(ctxCancel, "c", "a", resumeMarker)
if err != nil {
t.Fatalf("d.Watch() failed: %v", err)
}
time.AfterFunc(500*time.Millisecond, cancel)
if wstream.Advance() {
t.Fatal("wstream should not have advanced")
}
if got, want := verror.ErrorID(wstream.Err()), verror.ErrCanceled.ID; got != want {
t.Errorf("unexpected wstream error ID: got %v, want %v", got, want)
}
}