Skip to content

Commit

Permalink
Implemented safe update.
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin Bailey committed Jun 5, 2017
1 parent 244d88a commit dc0f45d
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 15 deletions.
40 changes: 38 additions & 2 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ type Meta struct {
Plain map[string]string `json:"plain"`
Created time.Time `json:"created"`
LastModified time.Time `json:"last_modified"`
Version string `json:"version"`
Version string `json:"version,omitempty"`
}

// Record contains a plaintext 'Meta' object containing record metadata,
Expand Down Expand Up @@ -294,12 +294,19 @@ func (c *Client) Read(ctx context.Context, recordID string) (*Record, error) {
// Write writes a new encrypted record to the database. Returns the new record (with
// the original, unencrypted data)
func (c *Client) Write(ctx context.Context, recordType string, data *map[string]string, plain *map[string]string) (*Record, error) {
var plainMap map[string]string
if plain == nil {
plainMap = nil
} else {
plainMap = *plain
}

record := &Record{
Meta: Meta{
Type: recordType,
WriterID: c.Options.ClientID,
UserID: c.Options.ClientID, // for now
Plain: *plain,
Plain: plainMap,
},
Data: *data,
}
Expand Down Expand Up @@ -330,6 +337,35 @@ func (c *Client) Write(ctx context.Context, recordType string, data *map[string]
return record, nil
}

// Updates a record, if the version field matches the
// version stored by E3DB.
//
// Returns HTTP 409 (Conflict) in error if the record cannot be updated.
func (c *Client) Update(ctx context.Context, record *Record) error {
encryptedRecord, err := c.encryptRecord(ctx, record)
if err != nil {
return err
}

buf := new(bytes.Buffer)
json.NewEncoder(buf).Encode(encryptedRecord)
u := fmt.Sprintf("%s/v1/storage/records/safe/%s/%s", c.apiURL(), url.QueryEscape(record.Meta.RecordID), url.QueryEscape(record.Meta.Version))
req, err := http.NewRequest("PUT", u, buf)
if err != nil {
return err
}

resp, err := c.rawCall(ctx, req, encryptedRecord)
if err != nil {
return err
}

defer closeResp(resp)

record.Meta = encryptedRecord.Meta
return nil
}

// Delete deletes a record given a record ID.
func (c *Client) Delete(ctx context.Context, recordID string) error {
u := fmt.Sprintf("%s/v1/storage/records/%s", c.apiURL(), url.QueryEscape(recordID))
Expand Down
81 changes: 69 additions & 12 deletions client_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
"bytes"
"context"
"fmt"
"net/http"
"os"
"testing"

Expand Down Expand Up @@ -75,12 +76,13 @@ func TestGetClientInfo(t *testing.T) {
}

func TestWriteRead(t *testing.T) {
rec1 := client.NewRecord("test-data")
rec1.Data["message"] = "Hello, world!"
recordID, err := client.Write(context.Background(), rec1)
data := make(map[string]string)
data["message"] = "Hello, world!"
rec1, err := client.Write(context.Background(), "test-data", &data, nil)
if err != nil {
t.Fatal(err)
}
recordID := rec1.Meta.RecordID

rec2, err := client.Read(context.Background(), recordID)
if err != nil {
Expand All @@ -106,12 +108,13 @@ func TestWriteRead(t *testing.T) {

// TestWriteThenDelete should delete a record
func TestWriteThenDelete(t *testing.T) {
rec1 := client.NewRecord("test-data")
rec1.Data["message"] = "Hello, world!"
recordID, err := client.Write(context.Background(), rec1)
data := make(map[string]string)
data["message"] = "Hello, world!"
record, err := client.Write(context.Background(), "test-data", &data, nil)
if err != nil {
t.Fatal(err)
}
recordID := record.Meta.RecordID

err = client.Delete(context.Background(), recordID)
if err != nil {
Expand All @@ -120,9 +123,9 @@ func TestWriteThenDelete(t *testing.T) {
}

func TestShare(t *testing.T) {
rec1 := client.NewRecord("test-data")
rec1.Data["message"] = "Hello, world!"
_, err := client.Write(context.Background(), rec1)
data := make(map[string]string)
data["message"] = "Hello, world!"
_, err := client.Write(context.Background(), "test-data", &data, nil)
if err != nil {
t.Fatal(err)
}
Expand All @@ -135,9 +138,9 @@ func TestShare(t *testing.T) {

// TestShareThenUnshare should share then revoke sharing
func TestShareThenUnshare(t *testing.T) {
rec1 := client.NewRecord("test-share-data")
rec1.Data["message"] = "Hello, world!"
_, err := client.Write(context.Background(), rec1)
data := make(map[string]string)
data["message"] = "Hello, world!"
_, err := client.Write(context.Background(), "test-share-data", &data, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -186,3 +189,57 @@ func TestEvents(t *testing.T) {
for range done {
}
}

func TestCounter(t *testing.T) {
data := make(map[string]string)
data["counter"] = "1"
rec1, err := client.Write(context.Background(), "test-data", &data, nil)
if err != nil {
t.Fatal(err)
}
recordID := rec1.Meta.RecordID

// Update w/ correct version
err = client.Update(context.Background(), rec1)
if err != nil {
t.Fatal(err)
}

rec1.Data["counter"] = "X"
rec1.Meta.Version = "6bc381c7-a41d-45ae-89aa-0890ad654673"
// should not update
err = client.Update(context.Background(), rec1)
if err == nil {
t.Fatal("Should not be able to update record with wrong version.")
}

if httpErr, ok := err.(*httpError); ok {
if httpErr.StatusCode != http.StatusConflict {
t.Fatal("Version conflict not reported.")
}
}

rec2, err := client.Read(context.Background(), recordID)
if err != nil {
t.Fatal(err)
}

if rec2.Data["counter"] != "1" {
t.Fatal("Counter had wrong value.")
}

rec2.Data["counter"] = "2"
err = client.Update(context.Background(), rec2)
if err != nil {
t.Fatal(err)
}

rec3, err := client.Read(context.Background(), recordID)
if err != nil {
t.Fatal(err)
}

if rec3.Data["counter"] != "2" {
t.Fatal("Counter had wrong value")
}
}
2 changes: 1 addition & 1 deletion cmd/e3db/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -584,7 +584,7 @@ func cmdFeedback(cmd *cli.Cmd) {
func main() {
app := cli.App("e3db-cli", "E3DB Command Line Interface")

app.Version("v version", "e3db-cli 1.0.1")
app.Version("v version", "e3db-cli 2.0.0-rc1")

options.Logging = app.BoolOpt("d debug", false, "enable debug logging")
options.Profile = app.StringOpt("p profile", "", "e3db configuration profile")
Expand Down

0 comments on commit dc0f45d

Please sign in to comment.