package sdb_test import ( "bytes" "context" "fmt" "os" "path/filepath" "testing" "github.com/keys-pub/keys" "github.com/keys-pub/keys-ext/sdb" "github.com/keys-pub/keys/dstore" "github.com/keys-pub/keys/tsutil" "github.com/stretchr/testify/require" ) // testDB returns DB for testing. // You should defer Close() the result. func testDB(t *testing.T) (*sdb.DB, func()) { path := testPath() key := keys.Rand32() return testDBWithOpts(t, path, key) } func testDBWithOpts(t *testing.T, path string, key sdb.SecretKey) (*sdb.DB, func()) { db := sdb.New() db.SetClock(tsutil.NewTestClock()) ctx := context.TODO() err := db.OpenAtPath(ctx, path, key) require.NoError(t, err) return db, func() { db.Close() os.Remove(path) } } func testPath() string { return filepath.Join(os.TempDir(), fmt.Sprintf("%s.sdb", keys.RandFileName())) } func TestDB(t *testing.T) { // SetLogger(NewLogger(DebugLevel)) db, closeFn := testDB(t) defer closeFn() require.True(t, db.IsOpen()) ctx := context.TODO() for i := 10; i <= 30; i = i + 10 { p := dstore.Path("test1", fmt.Sprintf("key%d", i)) err := db.Create(ctx, p, dstore.Data([]byte(fmt.Sprintf("value%d", i)))) require.NoError(t, err) } for i := 10; i <= 30; i = i + 10 { p := dstore.Path("test0", fmt.Sprintf("key%d", i)) err := db.Create(ctx, p, dstore.Data([]byte(fmt.Sprintf("value%d", i)))) require.NoError(t, err) } iter, err := db.DocumentIterator(ctx, "test0") require.NoError(t, err) doc, err := iter.Next() require.NoError(t, err) require.Equal(t, "/test0/key10", doc.Path) require.Equal(t, "value10", string(doc.Data())) iter.Release() out, err := db.Documents(ctx, "test0") require.NoError(t, err) require.Equal(t, 3, len(out)) require.Equal(t, "/test0/key10", out[0].Path) require.Equal(t, "value10", string(out[0].Data())) ok, err := db.Exists(ctx, "/test0/key10") require.NoError(t, err) require.True(t, ok) doc, err = db.Get(ctx, "/test0/key10") require.NoError(t, err) require.NotNil(t, doc) require.Equal(t, "value10", string(doc.Data())) err = db.Create(ctx, "/test0/key10", dstore.Data([]byte{})) require.EqualError(t, err, "path already exists /test0/key10") err = db.Set(ctx, "/test0/key10", dstore.Data([]byte("overwrite"))) require.NoError(t, err) err = db.Create(ctx, "/test0/key10", dstore.Data([]byte("overwrite"))) require.EqualError(t, err, "path already exists /test0/key10") doc, err = db.Get(ctx, "/test0/key10") require.NoError(t, err) require.NotNil(t, doc) require.Equal(t, "overwrite", string(doc.Data())) out, err = db.GetAll(ctx, []string{"/test0/key10", "/test0/key20"}) require.NoError(t, err) require.Equal(t, 2, len(out)) require.Equal(t, "/test0/key10", out[0].Path) require.Equal(t, "/test0/key20", out[1].Path) ok, err = db.Delete(ctx, "/test1/key10") require.True(t, ok) require.NoError(t, err) ok, err = db.Delete(ctx, "/test1/key10") require.False(t, ok) require.NoError(t, err) ok, err = db.Exists(ctx, "/test1/key10") require.NoError(t, err) require.False(t, ok) expected := `/test0/key10 overwrite /test0/key20 value20 /test0/key30 value30 ` var b bytes.Buffer iter, err = db.DocumentIterator(context.TODO(), "test0") require.NoError(t, err) err = dstore.SpewOut(iter, &b) require.NoError(t, err) require.Equal(t, expected, b.String()) iter.Release() iter, err = db.DocumentIterator(context.TODO(), "test0") require.NoError(t, err) spew, err := dstore.Spew(iter) require.NoError(t, err) require.Equal(t, b.String(), spew.String()) require.Equal(t, expected, spew.String()) iter.Release() iter, err = db.DocumentIterator(context.TODO(), "test0", dstore.Prefix("key1"), dstore.NoData()) require.NoError(t, err) doc, err = iter.Next() require.NoError(t, err) require.Equal(t, "/test0/key10", doc.Path) doc, err = iter.Next() require.NoError(t, err) require.Nil(t, doc) iter.Release() err = db.Create(ctx, "", dstore.Data([]byte{})) require.EqualError(t, err, "invalid path /") err = db.Set(ctx, "", dstore.Data([]byte{})) require.EqualError(t, err, "invalid path /") cols, err := db.Collections(ctx, "") require.NoError(t, err) require.Equal(t, "/test0", cols[0].Path) require.Equal(t, "/test1", cols[1].Path) _, err = db.Collections(ctx, "/test0") require.EqualError(t, err, "only root collections supported") } func TestDocumentsPath(t *testing.T) { db, closeFn := testDB(t) defer closeFn() ctx := context.TODO() err := db.Create(ctx, "test/1", dstore.Data([]byte("value1"))) require.NoError(t, err) doc, err := db.Get(ctx, "/test/1") require.NoError(t, err) require.NotNil(t, doc) ok, err := db.Exists(ctx, "/test/1") require.NoError(t, err) require.True(t, ok) ok, err = db.Exists(ctx, "test/1") require.NoError(t, err) require.True(t, ok) err = db.Create(ctx, dstore.Path("test", "key2", "col2", "key3"), dstore.Data([]byte("value3"))) require.NoError(t, err) doc, err = db.Get(ctx, dstore.Path("test", "key2", "col2", "key3")) require.NoError(t, err) require.NotNil(t, doc) require.Equal(t, []byte("value3"), doc.Data()) cols, err := db.Collections(ctx, "") require.NoError(t, err) require.Equal(t, "/test", cols[0].Path) } func TestDBListOptions(t *testing.T) { db, closeFn := testDB(t) defer closeFn() ctx := context.TODO() err := db.Create(ctx, "/test/1", dstore.Data([]byte("val1"))) require.NoError(t, err) err = db.Create(ctx, "/test/2", dstore.Data([]byte("val2"))) require.NoError(t, err) err = db.Create(ctx, "/test/3", dstore.Data([]byte("val3"))) require.NoError(t, err) for i := 1; i < 3; i++ { err := db.Create(ctx, dstore.Path("a", fmt.Sprintf("e%d", i)), dstore.Data([]byte("🤓"))) require.NoError(t, err) } for i := 1; i < 3; i++ { err := db.Create(ctx, dstore.Path("b", fmt.Sprintf("ea%d", i)), dstore.Data([]byte("😎"))) require.NoError(t, err) } for i := 1; i < 3; i++ { err := db.Create(ctx, dstore.Path("b", fmt.Sprintf("eb%d", i)), dstore.Data([]byte("😎"))) require.NoError(t, err) } for i := 1; i < 3; i++ { err := db.Create(ctx, dstore.Path("b", fmt.Sprintf("ec%d", i)), dstore.Data([]byte("😎"))) require.NoError(t, err) } for i := 1; i < 3; i++ { err := db.Create(ctx, dstore.Path("c", fmt.Sprintf("e%d", i)), dstore.Data([]byte("😎"))) require.NoError(t, err) } iter, err := db.DocumentIterator(ctx, "test") require.NoError(t, err) paths := []string{} for { doc, err := iter.Next() require.NoError(t, err) if doc == nil { break } paths = append(paths, doc.Path) } require.Equal(t, []string{"/test/1", "/test/2", "/test/3"}, paths) iter.Release() iter, err = db.DocumentIterator(context.TODO(), "test") require.NoError(t, err) b, err := dstore.Spew(iter) require.NoError(t, err) expected := `/test/1 val1 /test/2 val2 /test/3 val3 ` require.Equal(t, expected, b.String()) iter.Release() iter, err = db.DocumentIterator(context.TODO(), "test", dstore.NoData()) require.NoError(t, err) b, err = dstore.Spew(iter) require.NoError(t, err) expected = `/test/1 /test/2 /test/3 ` require.Equal(t, expected, b.String()) iter.Release() iter, err = db.DocumentIterator(ctx, "b", dstore.Prefix("eb")) require.NoError(t, err) paths = []string{} for { doc, err := iter.Next() require.NoError(t, err) if doc == nil { break } paths = append(paths, doc.Path) } iter.Release() require.Equal(t, []string{"/b/eb1", "/b/eb2"}, paths) last, err := db.Last(ctx, "") require.NoError(t, err) require.NotNil(t, last) require.Equal(t, "/test/3", last.Path) } func TestMetadata(t *testing.T) { ctx := context.TODO() db, closeFn := testDB(t) defer closeFn() err := db.Create(ctx, "/test/key1", dstore.Data([]byte("value1"))) require.NoError(t, err) doc, err := db.Get(ctx, "/test/key1") require.NoError(t, err) require.NotNil(t, doc) require.Equal(t, int64(1234567890001), tsutil.Millis(doc.CreatedAt)) err = db.Set(ctx, "/test/key1", dstore.Data([]byte("value1b"))) require.NoError(t, err) doc, err = db.Get(ctx, "/test/key1") require.NoError(t, err) require.NotNil(t, doc) require.Equal(t, int64(1234567890001), tsutil.Millis(doc.CreatedAt)) require.Equal(t, int64(1234567890002), tsutil.Millis(doc.UpdatedAt)) } func TestDocumentSetTo(t *testing.T) { ctx := context.TODO() db, closeFn := testDB(t) defer closeFn() type Test struct { Int int `json:"n,omitempty"` String string `json:"s,omitempty"` Bytes []byte `json:"b,omitempty"` } val := &Test{ Int: 1, String: "teststring", Bytes: []byte("testbytes"), } path := dstore.Path("test", "key1") err := db.Create(ctx, path, dstore.From(val)) require.NoError(t, err) doc, err := db.Get(ctx, path) require.NoError(t, err) var out Test err = doc.To(&out) require.NoError(t, err) require.Equal(t, val, &out) } func TestDocumentMerge(t *testing.T) { ctx := context.TODO() db, closeFn := testDB(t) defer closeFn() type Test struct { Int int `json:"n,omitempty"` String string `json:"s,omitempty"` Bytes []byte `json:"b,omitempty"` } val := &Test{ Int: 1, String: "teststring", Bytes: []byte("testbytes"), } path := dstore.Path("test", "key1") err := db.Set(ctx, path, dstore.From(val)) require.NoError(t, err) val2 := &Test{String: "teststring-merge"} err = db.Set(ctx, path, dstore.From(val2), dstore.MergeAll()) require.NoError(t, err) doc, err := db.Get(ctx, path) require.NoError(t, err) var out Test err = doc.To(&out) require.NoError(t, err) expected := &Test{ Int: 1, String: "teststring-merge", Bytes: []byte("testbytes"), } require.Equal(t, expected, &out) } func TestDBGetSetLarge(t *testing.T) { // SetLogger(NewLogger(DebugLevel)) db, closeFn := testDB(t) defer closeFn() large := bytes.Repeat([]byte{0x01}, 10*1024*1024) err := db.Set(context.TODO(), "/test/key1", dstore.Data(large)) require.NoError(t, err) doc, err := db.Get(context.TODO(), "/test/key1") require.NoError(t, err) require.Equal(t, large, doc.Data()) } func TestDBGetSetEmpty(t *testing.T) { // SetLogger(NewLogger(DebugLevel)) db, closeFn := testDB(t) defer closeFn() err := db.Set(context.TODO(), "/test/key1", dstore.Data([]byte{})) require.NoError(t, err) doc, err := db.Get(context.TODO(), "/test/key1") require.NoError(t, err) require.Equal(t, []byte{}, doc.Data()) } func TestDeleteAll(t *testing.T) { // SetLogger(NewLogger(DebugLevel)) db, closeFn := testDB(t) defer closeFn() err := db.Set(context.TODO(), "/test/key1", dstore.Data([]byte("val1"))) require.NoError(t, err) err = db.Set(context.TODO(), "/test/key2", dstore.Data([]byte("val2"))) require.NoError(t, err) err = db.DeleteAll(context.TODO(), []string{"/test/key1", "/test/key2", "/test/key3"}) require.NoError(t, err) doc, err := db.Get(context.TODO(), "/test/key1") require.NoError(t, err) require.Nil(t, doc) doc, err = db.Get(context.TODO(), "/test/key2") require.NoError(t, err) require.Nil(t, doc) } func TestUpdate(t *testing.T) { db, closeFn := testDB(t) defer closeFn() ctx := context.TODO() err := db.Create(ctx, dstore.Path("test", "key1"), dstore.Data([]byte("val1"))) require.NoError(t, err) err = db.Set(ctx, dstore.Path("test", "key1"), map[string]interface{}{"index": 1, "info": "testinfo"}, dstore.MergeAll()) require.NoError(t, err) doc, err := db.Get(ctx, dstore.Path("test", "key1")) require.NoError(t, err) require.NotNil(t, doc) b := doc.Bytes("data") require.Equal(t, []byte("val1"), b) index, _ := doc.Int("index") require.Equal(t, 1, index) info, _ := doc.String("info") require.Equal(t, "testinfo", info) } func TestCreate(t *testing.T) { db, closeFn := testDB(t) defer closeFn() ctx := context.TODO() path := dstore.Path("test", "key1") err := db.Create(ctx, path, dstore.Data([]byte("value1"))) require.NoError(t, err) err = db.Create(ctx, path, dstore.Data([]byte("value1"))) require.EqualError(t, err, fmt.Sprintf("path already exists %s", path)) }