| // 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. |
| |
| // TODO(sadovsky): Once the storage engine layer is made public, implement a |
| // Syncbase-based storage engine so that we can run all the storage engine tests |
| // against Syncbase itself. |
| |
| package nosql_test |
| |
| import ( |
| "testing" |
| |
| wire "v.io/syncbase/v23/services/syncbase/nosql" |
| "v.io/syncbase/v23/syncbase" |
| "v.io/syncbase/v23/syncbase/nosql" |
| "v.io/syncbase/v23/syncbase/nosql/syncql" |
| tu "v.io/syncbase/v23/syncbase/testutil" |
| "v.io/v23/naming" |
| "v.io/v23/vdl" |
| "v.io/v23/verror" |
| _ "v.io/x/ref/runtime/factories/generic" |
| ) |
| |
| // Tests various Name and FullName methods. |
| func TestName(t *testing.T) { |
| ctx, sName, cleanup := tu.SetupOrDie(nil) |
| defer cleanup() |
| a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a") |
| d := tu.CreateNoSQLDatabase(t, ctx, a, "d") |
| |
| b, err := d.BeginBatch(ctx, wire.BatchOptions{}) |
| if err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| |
| if d.Name() != "d" { |
| t.Errorf("Wrong name: %q", d.Name()) |
| } |
| if d.FullName() != naming.Join(sName, "a", "d") { |
| t.Errorf("Wrong full name: %q", d.FullName()) |
| } |
| if b.Name() == d.Name() { |
| t.Errorf("Names should not match: %q", b.Name()) |
| } |
| if b.FullName() == d.FullName() { |
| t.Errorf("Full names should not match: %q", b.FullName()) |
| } |
| } |
| |
| // Tests basic functionality of BatchDatabase. |
| func TestBatchBasics(t *testing.T) { |
| ctx, sName, cleanup := tu.SetupOrDie(nil) |
| defer cleanup() |
| a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a") |
| d := tu.CreateNoSQLDatabase(t, ctx, a, "d") |
| tb := tu.CreateTable(t, ctx, d, "tb") |
| |
| tu.CheckScan(t, ctx, tb, nosql.Prefix(""), []string{}, []interface{}{}) |
| |
| var b1, b2 nosql.BatchDatabase |
| var b1tb, b2tb nosql.Table |
| var err error |
| |
| // Test that the effects of a transaction are not visible until commit. |
| b1, err = d.BeginBatch(ctx, wire.BatchOptions{}) |
| if err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| b1tb = b1.Table("tb") |
| |
| if err := b1tb.Put(ctx, "fooKey", "fooValue"); err != nil { |
| t.Fatalf("Put() failed: %v", err) |
| } |
| |
| // Check that foo is visible inside of this transaction. |
| tu.CheckScan(t, ctx, b1tb, nosql.Prefix(""), []string{"fooKey"}, []interface{}{"fooValue"}) |
| |
| // Check that foo is not yet visible outside of this transaction. |
| tu.CheckScan(t, ctx, tb, nosql.Prefix(""), []string{}, []interface{}{}) |
| |
| // Start a scan in b1, advance the scan one row, put a new value that would |
| // occur later in the scan (if it were visible) and then advance the scan to see |
| // that it doesn't show (since we snapshot uncommiteed changes at the start). |
| // Ditto for Exec. |
| // start the scan and exec |
| scanIt := b1tb.Scan(ctx, nosql.Prefix("")) |
| if !scanIt.Advance() { |
| t.Fatal("scanIt.Advance() returned false") |
| } |
| _, execIt, err := b1.Exec(ctx, "select k from tb") |
| if err != nil { |
| t.Fatalf("b1.Exec() failed: %v", err) |
| } |
| if !execIt.Advance() { |
| t.Fatal("execIt.Advance() returned false") |
| } |
| // put "zzzKey" |
| if err := b1tb.Put(ctx, "zzzKey", "zzzValue"); err != nil { |
| t.Fatalf("Put() failed: %v", err) |
| } |
| // make sure Scan's Advance doesn't return a "zzzKey" |
| for scanIt.Advance() { |
| if string(scanIt.Key()) == "zzzKey" { |
| t.Fatal("scanIt.Advance() found zzzKey") |
| } |
| } |
| if scanIt.Err() != nil { |
| t.Fatalf("scanIt.Advance() failed: %v", err) |
| } |
| // make sure Exec's Advance doesn't return a "zzzKey" |
| for execIt.Advance() { |
| if string(execIt.Result()[0].String()) == "zzzKey" { |
| t.Fatal("execIt.Advance() found zzzKey") |
| } |
| } |
| if execIt.Err() != nil { |
| t.Fatalf("execIt.Advance() failed: %v", err) |
| } |
| |
| if err := b1.Commit(ctx); err != nil { |
| t.Fatalf("b1.Commit() failed: %v", err) |
| } |
| |
| // Check that foo is now visible. |
| tu.CheckScan(t, ctx, tb, nosql.Prefix(""), []string{"fooKey", "zzzKey"}, []interface{}{"fooValue", "zzzValue"}) |
| |
| // Test that concurrent transactions are isolated. |
| if b1, err = d.BeginBatch(ctx, wire.BatchOptions{}); err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| if b2, err = d.BeginBatch(ctx, wire.BatchOptions{}); err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| b1tb, b2tb = b1.Table("tb"), b2.Table("tb") |
| |
| if err := b1tb.Put(ctx, "barKey", "barValue"); err != nil { |
| t.Fatalf("Put() failed: %v", err) |
| } |
| if err := b1tb.Put(ctx, "bazKey", "bazValue"); err != nil { |
| t.Fatalf("Put() failed: %v", err) |
| } |
| |
| var got string |
| if err := b2tb.Get(ctx, "barKey", &got); verror.ErrorID(err) != verror.ErrNoExist.ID { |
| t.Fatalf("Get() should have failed: %v", err) |
| } |
| if err := b2tb.Put(ctx, "rabKey", "rabValue"); err != nil { |
| t.Fatalf("Put() failed: %v", err) |
| } |
| |
| if err := b1.Commit(ctx); err != nil { |
| t.Fatalf("b1.Commit() failed: %v", err) |
| } |
| if err := b2.Commit(ctx); err == nil { |
| t.Fatalf("b2.Commit() should have failed: %v", err) |
| } |
| |
| // Check that foo, bar, baz and zzz (but not rab) are now visible. |
| tu.CheckScan(t, ctx, tb, nosql.Prefix(""), []string{"barKey", "bazKey", "fooKey", "zzzKey"}, []interface{}{"barValue", "bazValue", "fooValue", "zzzValue"}) |
| } |
| |
| // Tests that BatchDatabase.Exec doesn't see changes committed outside the batch. |
| // 1. Create a read only batch. |
| // 2. query all rows in the table |
| // 3. commit a new row outside of the batch |
| // 4. confirm new row not seen when querying all rows in the table |
| // 5. abort the batch and create a new readonly batch |
| // 6. confirm new row NOW seen when querying all rows in the table |
| func TestBatchExecIsolation(t *testing.T) { |
| ctx, sName, cleanup := tu.SetupOrDie(nil) |
| defer cleanup() |
| a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a") |
| d := tu.CreateNoSQLDatabase(t, ctx, a, "d") |
| tb := tu.CreateTable(t, ctx, d, "tb") |
| |
| foo := Foo{I: 4, S: "f"} |
| if err := tb.Put(ctx, "foo", foo); err != nil { |
| t.Fatalf("tb.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 := tb.Put(ctx, "bar", &bar); err != nil { |
| t.Fatalf("tb.Put() failed: %v", err) |
| } |
| |
| baz := Baz{Name: "John Doe", Active: true} |
| if err := tb.Put(ctx, "baz", baz); err != nil { |
| t.Fatalf("tb.Put() failed: %v", err) |
| } |
| |
| // Begin a readonly batch. |
| roBatch, err := d.BeginBatch(ctx, wire.BatchOptions{ReadOnly: true}) |
| if err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| |
| // fetch all rows |
| tu.CheckExec(t, ctx, roBatch, "select k, v from tb", |
| []string{"k", "v"}, |
| [][]*vdl.Value{ |
| []*vdl.Value{vdl.ValueOf("bar"), vdl.ValueOf(bar)}, |
| []*vdl.Value{vdl.ValueOf("baz"), vdl.ValueOf(baz)}, |
| []*vdl.Value{vdl.ValueOf("foo"), vdl.ValueOf(foo)}, |
| }) |
| |
| // Add a row outside this batch |
| newRow := Baz{Name: "Alice Wonderland", Active: false} |
| if err := tb.Put(ctx, "newRow", newRow); err != nil { |
| t.Fatalf("tb.Put() failed: %v", err) |
| } |
| |
| // confirm fetching all rows doesn't get the new row |
| tu.CheckExec(t, ctx, roBatch, "select k, v from tb", |
| []string{"k", "v"}, |
| [][]*vdl.Value{ |
| []*vdl.Value{vdl.ValueOf("bar"), vdl.ValueOf(bar)}, |
| []*vdl.Value{vdl.ValueOf("baz"), vdl.ValueOf(baz)}, |
| []*vdl.Value{vdl.ValueOf("foo"), vdl.ValueOf(foo)}, |
| }) |
| |
| // start a new batch |
| roBatch.Abort(ctx) |
| roBatch, err = d.BeginBatch(ctx, wire.BatchOptions{ReadOnly: true}) |
| if err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| defer roBatch.Abort(ctx) |
| |
| // confirm fetching all rows NOW gets the new row |
| tu.CheckExec(t, ctx, roBatch, "select k, v from tb", |
| []string{"k", "v"}, |
| [][]*vdl.Value{ |
| []*vdl.Value{vdl.ValueOf("bar"), vdl.ValueOf(bar)}, |
| []*vdl.Value{vdl.ValueOf("baz"), vdl.ValueOf(baz)}, |
| []*vdl.Value{vdl.ValueOf("foo"), vdl.ValueOf(foo)}, |
| []*vdl.Value{vdl.ValueOf("newRow"), vdl.ValueOf(newRow)}, |
| }) |
| |
| // test error condition on batch |
| tu.CheckExecError(t, ctx, roBatch, "select k, v from foo", syncql.ErrTableCantAccess.ID) |
| } |
| |
| // Tests that BatchDatabase.Exec DOES see changes made inside the transaction but before |
| // Exec is called. |
| func TestBatchExec(t *testing.T) { |
| ctx, sName, cleanup := tu.SetupOrDie(nil) |
| defer cleanup() |
| a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a") |
| d := tu.CreateNoSQLDatabase(t, ctx, a, "d") |
| tb := tu.CreateTable(t, ctx, d, "tb") |
| |
| foo := Foo{I: 4, S: "f"} |
| if err := tb.Put(ctx, "foo", foo); err != nil { |
| t.Fatalf("tb.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 := tb.Put(ctx, "bar", &bar); err != nil { |
| t.Fatalf("tb.Put() failed: %v", err) |
| } |
| |
| baz := Baz{Name: "John Doe", Active: true} |
| if err := tb.Put(ctx, "baz", baz); err != nil { |
| t.Fatalf("tb.Put() failed: %v", err) |
| } |
| |
| // Begin a readwrite batch. |
| rwBatch, err := d.BeginBatch(ctx, wire.BatchOptions{ReadOnly: false}) |
| if err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| |
| // fetch all rows |
| tu.CheckExec(t, ctx, rwBatch, "select k, v from tb", |
| []string{"k", "v"}, |
| [][]*vdl.Value{ |
| []*vdl.Value{vdl.ValueOf("bar"), vdl.ValueOf(bar)}, |
| []*vdl.Value{vdl.ValueOf("baz"), vdl.ValueOf(baz)}, |
| []*vdl.Value{vdl.ValueOf("foo"), vdl.ValueOf(foo)}, |
| }) |
| |
| rwBatchTb := rwBatch.Table("tb") |
| |
| // Add a row in this batch |
| newRow := Baz{Name: "Snow White", Active: true} |
| if err := rwBatchTb.Put(ctx, "newRow", newRow); err != nil { |
| t.Fatalf("rwBatchTb.Put() failed: %v", err) |
| } |
| |
| // confirm fetching all rows DOES get the new row |
| tu.CheckExec(t, ctx, rwBatch, "select k, v from tb", |
| []string{"k", "v"}, |
| [][]*vdl.Value{ |
| []*vdl.Value{vdl.ValueOf("bar"), vdl.ValueOf(bar)}, |
| []*vdl.Value{vdl.ValueOf("baz"), vdl.ValueOf(baz)}, |
| []*vdl.Value{vdl.ValueOf("foo"), vdl.ValueOf(foo)}, |
| []*vdl.Value{vdl.ValueOf("newRow"), vdl.ValueOf(newRow)}, |
| }) |
| |
| // Delete the first row (bar) and the last row (newRow). |
| // Change the baz row. Confirm these rows are no longer fetched and that |
| // the change to baz is seen. |
| if err := rwBatchTb.Delete(ctx, nosql.SingleRow("bar")); err != nil { |
| t.Fatalf("rwBatchTb.Delete(bar) failed: %v", err) |
| } |
| if err := rwBatchTb.Delete(ctx, nosql.SingleRow("newRow")); err != nil { |
| t.Fatalf("rwBatchTb.Delete(newRow) failed: %v", err) |
| } |
| baz2 := Baz{Name: "Batman", Active: false} |
| if err := rwBatchTb.Put(ctx, "baz", baz2); err != nil { |
| t.Fatalf("tb.Put() failed: %v", err) |
| } |
| tu.CheckExec(t, ctx, rwBatch, "select k, v from tb", |
| []string{"k", "v"}, |
| [][]*vdl.Value{ |
| []*vdl.Value{vdl.ValueOf("baz"), vdl.ValueOf(baz2)}, |
| []*vdl.Value{vdl.ValueOf("foo"), vdl.ValueOf(foo)}, |
| }) |
| |
| // Add the 2 rows (we just deleted) back again. |
| // Delete the other two rows (baz, foo). |
| // Confirm we just see the three rows we added back. |
| // Add a row in this batch |
| bar2 := Baz{Name: "Tom Thumb", Active: true} |
| if err := rwBatchTb.Put(ctx, "bar", bar2); err != nil { |
| t.Fatalf("rwBatchTb.Put() failed: %v", err) |
| } |
| newRow2 := Baz{Name: "Snow White", Active: false} |
| if err := rwBatchTb.Put(ctx, "newRow", newRow2); err != nil { |
| t.Fatalf("rwBatchTb.Put() failed: %v", err) |
| } |
| if err := rwBatchTb.Delete(ctx, nosql.SingleRow("baz")); err != nil { |
| t.Fatalf("rwBatchTb.Delete(baz) failed: %v", err) |
| } |
| if err := rwBatchTb.Delete(ctx, nosql.SingleRow("foo")); err != nil { |
| t.Fatalf("rwBatchTb.Delete(foo) failed: %v", err) |
| } |
| tu.CheckExec(t, ctx, rwBatch, "select k, v from tb", |
| []string{"k", "v"}, |
| [][]*vdl.Value{ |
| []*vdl.Value{vdl.ValueOf("bar"), vdl.ValueOf(bar2)}, |
| []*vdl.Value{vdl.ValueOf("newRow"), vdl.ValueOf(newRow2)}, |
| }) |
| |
| // commit rw batch |
| rwBatch.Commit(ctx) |
| |
| // start a new (ro) batch |
| roBatch, err := d.BeginBatch(ctx, wire.BatchOptions{ReadOnly: false}) |
| if err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| defer roBatch.Abort(ctx) |
| |
| // confirm fetching all rows gets the rows committed above |
| // as it was never committed |
| tu.CheckExec(t, ctx, roBatch, "select k, v from tb", |
| []string{"k", "v"}, |
| [][]*vdl.Value{ |
| []*vdl.Value{vdl.ValueOf("bar"), vdl.ValueOf(bar2)}, |
| []*vdl.Value{vdl.ValueOf("newRow"), vdl.ValueOf(newRow2)}, |
| }) |
| } |
| |
| // Tests enforcement of BatchOptions.ReadOnly. |
| func TestReadOnlyBatch(t *testing.T) { |
| ctx, sName, cleanup := tu.SetupOrDie(nil) |
| defer cleanup() |
| a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a") |
| d := tu.CreateNoSQLDatabase(t, ctx, a, "d") |
| tb := tu.CreateTable(t, ctx, d, "tb") |
| |
| if err := tb.Put(ctx, "fooKey", "fooValue"); err != nil { |
| t.Fatalf("tb.Put() failed: %v", err) |
| } |
| |
| b1, err := d.BeginBatch(ctx, wire.BatchOptions{ReadOnly: true}) |
| if err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| b1tb := b1.Table("tb") |
| |
| if err := b1tb.Put(ctx, "barKey", "barValue"); verror.ErrorID(err) != wire.ErrReadOnlyBatch.ID { |
| t.Fatalf("Put() should have failed: %v", err) |
| } |
| if err := b1tb.Delete(ctx, nosql.Prefix("fooKey")); verror.ErrorID(err) != wire.ErrReadOnlyBatch.ID { |
| t.Fatalf("Table.Delete() should have failed: %v", err) |
| } |
| if err := b1tb.Row("fooKey").Delete(ctx); verror.ErrorID(err) != wire.ErrReadOnlyBatch.ID { |
| t.Fatalf("Row.Delete() should have failed: %v", err) |
| } |
| } |
| |
| // Tests that all ops fail after attempted commit or abort. |
| func TestOpAfterFinalize(t *testing.T) { |
| ctx, sName, cleanup := tu.SetupOrDie(nil) |
| defer cleanup() |
| a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a") |
| d := tu.CreateNoSQLDatabase(t, ctx, a, "d") |
| tb := tu.CreateTable(t, ctx, d, "tb") |
| |
| // TODO(sadovsky): Add some sort of "op after finalize" error type and check |
| // for it specifically below. |
| checkOpsFail := func(b nosql.BatchDatabase) { |
| btb := b.Table("tb") |
| var got string |
| if err := btb.Get(ctx, "fooKey", &got); err == nil { |
| tu.Fatal(t, "Get() should have failed") |
| } |
| it := btb.Scan(ctx, nosql.Prefix("")) |
| it.Advance() |
| if it.Err() == nil { |
| tu.Fatal(t, "Scan() should have failed") |
| } |
| if err := btb.Put(ctx, "barKey", "barValue"); err == nil { |
| tu.Fatal(t, "Put() should have failed") |
| } |
| if err := btb.Delete(ctx, nosql.Prefix("fooKey")); err == nil { |
| tu.Fatal(t, "Table.Delete() should have failed: %v", err) |
| } |
| if err := btb.Row("fooKey").Delete(ctx); err == nil { |
| tu.Fatal(t, "Row.Delete() should have failed: %v", err) |
| } |
| if err := b.Commit(ctx); err == nil { |
| tu.Fatal(t, "Commit() should have failed") |
| } |
| } |
| |
| // Commit a transaction, check that subsequent ops fail. |
| b1, err := d.BeginBatch(ctx, wire.BatchOptions{}) |
| if err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| b1tb := b1.Table("tb") |
| |
| if err := b1tb.Put(ctx, "fooKey", "fooValue"); err != nil { |
| t.Fatalf("Put() failed: %v", err) |
| } |
| if err := b1.Commit(ctx); err != nil { |
| t.Fatalf("b1.Commit() failed: %v", err) |
| } |
| checkOpsFail(b1) |
| |
| // Create a transaction with a conflict, check that the commit fails, then |
| // check that subsequent ops fail. |
| if b1, err = d.BeginBatch(ctx, wire.BatchOptions{}); err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| b1tb = b1.Table("tb") |
| |
| // Conflicts with future b1tb.Get(). |
| if err := tb.Put(ctx, "fooKey", "v2"); err != nil { |
| t.Fatalf("tb.Put() failed: %v", err) |
| } |
| |
| var got string |
| if err := b1tb.Get(ctx, "fooKey", &got); err != nil { |
| t.Fatalf("Get() failed: %v", err) |
| } |
| want := "fooValue" |
| if got != want { |
| t.Fatalf("Get() returned unexpected value: got %q, want %q", got, want) |
| } |
| if err := b1.Commit(ctx); err == nil { |
| t.Fatalf("b1.Commit() should have failed: %v", err) |
| } |
| checkOpsFail(b1) |
| |
| // Create a transaction and immediately abort it, then check that subsequent |
| // ops fail. |
| if b1, err = d.BeginBatch(ctx, wire.BatchOptions{}); err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| b1tb = b1.Table("tb") |
| b1.Abort(ctx) |
| checkOpsFail(b1) |
| } |
| |
| // Tests that batch methods called on non-batch return ErrNotBoundToBatch and |
| // that non-batch methods called on batch return ErrBoundToBatch. |
| func TestDisallowedMethods(t *testing.T) { |
| ctx, sName, cleanup := tu.SetupOrDie(nil) |
| defer cleanup() |
| a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a") |
| d := tu.CreateNoSQLDatabase(t, ctx, a, "d") |
| b, err := d.BeginBatch(ctx, wire.BatchOptions{}) |
| if err != nil { |
| t.Fatalf("d.BeginBatch() failed: %v", err) |
| } |
| |
| // Batch methods on non-batch. |
| dc := wire.DatabaseClient(d.FullName()) |
| if err := dc.Commit(ctx, -1); verror.ErrorID(err) != wire.ErrNotBoundToBatch.ID { |
| t.Fatalf("dc.Commit() should have failed: %v", err) |
| } |
| if err := dc.Abort(ctx, -1); verror.ErrorID(err) != wire.ErrNotBoundToBatch.ID { |
| t.Fatalf("dc.Abort() should have failed: %v", err) |
| } |
| |
| // Non-batch methods on batch. |
| bc := wire.DatabaseClient(b.FullName()) |
| // For bc.Create(), we check that err is not nil instead of checking for |
| // ErrBoundToBatch specifically since in practice bc.Create() will return |
| // either ErrExist or "invalid name" depending on whether the database and |
| // batch exist. |
| if err := bc.Create(ctx, nil, nil); err == nil { |
| t.Fatalf("bc.Create() should have failed: %v", err) |
| } |
| if err := bc.Delete(ctx, -1); verror.ErrorID(err) != wire.ErrBoundToBatch.ID { |
| t.Fatalf("bc.Delete() should have failed: %v", err) |
| } |
| if _, err := bc.BeginBatch(ctx, -1, wire.BatchOptions{}); verror.ErrorID(err) != wire.ErrBoundToBatch.ID { |
| t.Fatalf("bc.BeginBatch() should have failed: %v", err) |
| } |
| if _, _, err := bc.GetPermissions(ctx); verror.ErrorID(err) != wire.ErrBoundToBatch.ID { |
| t.Fatalf("bc.GetPermissions() should have failed: %v", err) |
| } |
| if err := bc.SetPermissions(ctx, nil, ""); verror.ErrorID(err) != wire.ErrBoundToBatch.ID { |
| t.Fatalf("bc.SetPermissions() should have failed: %v", err) |
| } |
| // TODO(sadovsky): Test all other SyncGroupManager methods. |
| if _, err := bc.GetSyncGroupNames(ctx); verror.ErrorID(err) != wire.ErrBoundToBatch.ID { |
| t.Fatalf("bc.GetSyncGroupNames() should have failed: %v", err) |
| } |
| |
| // Test that Table.{Create,Delete} fail with ErrBoundToBatch. |
| tc := wire.TableClient(naming.Join(b.FullName(), "tb")) |
| if err := tc.Create(ctx, -1, nil); verror.ErrorID(err) != wire.ErrBoundToBatch.ID { |
| t.Fatalf("tc.Create() should have failed: %v", err) |
| } |
| if err := tc.Delete(ctx, -1); verror.ErrorID(err) != wire.ErrBoundToBatch.ID { |
| t.Fatalf("tc.Delete() should have failed: %v", err) |
| } |
| } |
| |
| // Tests that d.BeginBatch() fails gracefully if the database does not exist. |
| func TestBeginBatchWithNonexistentDatabase(t *testing.T) { |
| ctx, sName, cleanup := tu.SetupOrDie(nil) |
| defer cleanup() |
| a := tu.CreateApp(t, ctx, syncbase.NewService(sName), "a") |
| d := a.NoSQLDatabase("d", nil) |
| if _, err := d.BeginBatch(ctx, wire.BatchOptions{}); verror.ErrorID(err) != verror.ErrNoExist.ID { |
| t.Fatalf("d.BeginBatch() should have failed: %v", err) |
| } |
| } |