From 4d667e7b1104d72b54f7e9513fb611609160ec75 Mon Sep 17 00:00:00 2001 From: Bel LaPointe Date: Wed, 22 Jul 2020 20:11:52 -0600 Subject: [PATCH] Update storage to new object format --- storage/entity/one.go | 34 ++++++++++++++++ storage/entity/one_test.go | 17 ++++++++ storage/entity/peer.go | 6 +++ storage/graph.go | 14 ++++--- storage/graph_test.go | 66 ++++++++++++++++++++------------ storage/one.go | 20 ---------- storage/{ => operator}/filter.go | 10 ++--- storage/operator/filter_test.go | 40 +++++++++++++++++++ storage/{ => operator}/modify.go | 18 ++++++--- storage/operator/modify_test.go | 65 +++++++++++++++++++++++++++++++ view/html.go | 4 +- 11 files changed, 232 insertions(+), 62 deletions(-) create mode 100644 storage/entity/one.go create mode 100644 storage/entity/one_test.go create mode 100644 storage/entity/peer.go delete mode 100644 storage/one.go rename storage/{ => operator}/filter.go (85%) create mode 100644 storage/operator/filter_test.go rename storage/{ => operator}/modify.go (69%) create mode 100644 storage/operator/modify_test.go diff --git a/storage/entity/one.go b/storage/entity/one.go new file mode 100644 index 0000000..2673932 --- /dev/null +++ b/storage/entity/one.go @@ -0,0 +1,34 @@ +package entity + +const ( + Name = "_id" + Relationship = "relationship" + Type = "type" + Title = "title" + Image = "image" + Text = "text" + Modified = "modified" + Connections = "connections" +) + +type One struct { + Name string `bson:"_id,omitempty"` + Type string `bson:"type,omitempty"` + Title string `bson:"title,omitempty"` + Image string `bson:"image,omitempty"` + Text string `bson:"text,omitempty"` + Modified int64 `bson:"modified,omitempty"` + Connections []Peer `bson:"connections,omitempty"` +} + +func (o One) Query() One { + return One{Name: o.Name} +} + +func (o One) Peers() []string { + names := make([]string, len(o.Connections)) + for i := range o.Connections { + names[i] = o.Connections[i].Name + } + return names +} diff --git a/storage/entity/one_test.go b/storage/entity/one_test.go new file mode 100644 index 0000000..a323d6c --- /dev/null +++ b/storage/entity/one_test.go @@ -0,0 +1,17 @@ +package entity + +import ( + "fmt" + "testing" +) + +func TestOne(t *testing.T) { + one := One{ + Name: "myname", + Type: "mytype", + } + q := one.Query() + if want := fmt.Sprint(One{Name: one.Name}); want != fmt.Sprint(q) { + t.Error(want, q) + } +} diff --git a/storage/entity/peer.go b/storage/entity/peer.go new file mode 100644 index 0000000..98106cc --- /dev/null +++ b/storage/entity/peer.go @@ -0,0 +1,6 @@ +package entity + +type Peer struct { + Name string `bson:"_id,omitempty"` + Relationship string `bson:"relationship,omitempty"` +} diff --git a/storage/graph.go b/storage/graph.go index 4646282..5b29268 100644 --- a/storage/graph.go +++ b/storage/graph.go @@ -2,6 +2,8 @@ package storage import ( "context" + "local/whodunit/storage/entity" + "local/whodunit/storage/operator" "go.mongodb.org/mongo-driver/bson" ) @@ -17,15 +19,15 @@ func NewGraph() Graph { } } -func (g Graph) List(ctx context.Context, from ...string) ([]One, error) { - filter := newFilterIn("_id", from) +func (g Graph) List(ctx context.Context, from ...string) ([]entity.One, error) { + filter := operator.NewFilterIn("_id", from) ch, err := g.mongo.Find(ctx, filter) if err != nil { return nil, err } - var ones []One + var ones []entity.One for one := range ch { - var o One + var o entity.One if err := bson.Unmarshal(one, &o); err != nil { return nil, err } @@ -34,10 +36,10 @@ func (g Graph) List(ctx context.Context, from ...string) ([]One, error) { return ones, nil } -func (g Graph) Insert(ctx context.Context, one One) error { +func (g Graph) Insert(ctx context.Context, one entity.One) error { return g.mongo.Insert(ctx, one) } -func (g Graph) Update(ctx context.Context, one One, modify interface{}) error { +func (g Graph) Update(ctx context.Context, one entity.One, modify interface{}) error { return g.mongo.Update(ctx, one, modify) } diff --git a/storage/graph_test.go b/storage/graph_test.go index 00fdbc8..c4d57af 100644 --- a/storage/graph_test.go +++ b/storage/graph_test.go @@ -2,12 +2,17 @@ package storage import ( "context" + "local/whodunit/storage/entity" + "local/whodunit/storage/operator" "os" "testing" + "time" + + "github.com/google/uuid" ) func TestIntegration(t *testing.T) { - if len(os.Getenv("INTEGRATION")) > 0 { + if len(os.Getenv("INTEGRATION")) == 0 { t.Logf("skipping because $INTEGRATION unset") return } @@ -24,13 +29,12 @@ func TestIntegration(t *testing.T) { clean() defer clean() - ones := []One{ - One{ID: "A", Relation: ":)"}, - One{ID: "B", Relation: ":("}, - One{ID: "C", Relation: ":/"}, + ones := []entity.One{ + randomOne(), + randomOne(), + randomOne(), } - ones[0].Know = []One{ones[len(ones)-1]} - ones[0].Know[0].Relation = ":D" + ones[0].Connections = []entity.Peer{entity.Peer{Name: ones[2].Name, Relationship: ":("}} t.Run("graph.Insert(...)", func(t *testing.T) { for _, one := range ones { @@ -48,12 +52,12 @@ func TestIntegration(t *testing.T) { } t.Logf("\nall = %+v", all) if len(all) != 3 { - t.Fatalf("%+v: %+v", len(all), all) + t.Fatalf("%v: %+v", len(all), all) } }) t.Run("graph.List(foo => *)", func(t *testing.T) { - some, err := graph.List(ctx, ones[0].Knows()...) + some, err := graph.List(ctx, ones[0].Peers()...) if err != nil { t.Fatal(err) } @@ -64,12 +68,12 @@ func TestIntegration(t *testing.T) { }) t.Run("graph.Update(foo, --bar)", func(t *testing.T) { - err := graph.Update(ctx, ones[0].Min(), Set{"know", []interface{}{}}) + err := graph.Update(ctx, ones[0].Query(), operator.Set{entity.Connections, []interface{}{}}) if err != nil { t.Fatal(err) } - some, err := graph.List(ctx, ones[0].ID) + some, err := graph.List(ctx, ones[0].Name) if err != nil { t.Fatal(err) } @@ -78,21 +82,21 @@ func TestIntegration(t *testing.T) { if len(some) != 1 { t.Fatal(len(some)) } - if some[0].ID != ones[0].ID { - t.Fatal(some[0].ID) + if some[0].Name != ones[0].Name { + t.Fatal(some[0].Name) } - if len(some[0].Knows()) > 0 { - t.Fatal(some[0].Knows()) + if len(some[0].Peers()) > 0 { + t.Fatal(some[0].Peers()) } }) t.Run("graph.Update(foo, ++...); graph.Update(foo, --if :()", func(t *testing.T) { - err := graph.Update(ctx, ones[0].Min(), Set{"know", ones}) + err := graph.Update(ctx, ones[0].Query(), operator.Set{entity.Connections, []entity.Peer{entity.Peer{Name: "hello", Relationship: ":("}}}) if err != nil { t.Fatal(err) } - some1, err := graph.List(ctx, ones[0].ID) + some1, err := graph.List(ctx, ones[0].Name) if err != nil { t.Fatal(err) } @@ -100,16 +104,16 @@ func TestIntegration(t *testing.T) { if len(some1) != 1 { t.Fatal(len(some1)) } - if len(some1[0].Knows()) != len(ones) { - t.Fatal(some1[0].Knows()) + if len(some1[0].Peers()) != 1 { + t.Fatal(some1[0].Peers()) } - err = graph.Update(ctx, ones[0].Min(), PopIf{"know", map[string]string{"relation": ":("}}) + err = graph.Update(ctx, ones[0].Query(), operator.PopIf{entity.Connections, map[string]string{entity.Relationship: ":("}}) if err != nil { t.Fatal(err) } - some2, err := graph.List(ctx, ones[0].ID) + some2, err := graph.List(ctx, ones[0].Name) if err != nil { t.Fatal(err) } @@ -118,11 +122,23 @@ func TestIntegration(t *testing.T) { if len(some1) != len(some2) { t.Fatal(len(some2)) } - if len(some1[0].Knows()) == len(some2[0].Knows()) { - t.Fatal(len(some2[0].Knows())) + if len(some1[0].Connections) == len(some2[0].Connections) { + t.Fatal(len(some2[0].Connections)) } - if len(some2[0].Knows()) == len(ones) { - t.Fatal(len(some2[0].Knows())) + if len(some2[0].Connections) == len(ones) { + t.Fatal(len(some2[0].Connections)) } }) } + +func randomOne() entity.One { + return entity.One{ + Name: uuid.New().String()[:5], + Type: "Humman", + Title: "Biggus", + Image: "/path/to.jpg", + Text: "tee hee xd", + Modified: time.Now().UnixNano(), + Connections: []entity.Peer{}, + } +} diff --git a/storage/one.go b/storage/one.go deleted file mode 100644 index 28316ad..0000000 --- a/storage/one.go +++ /dev/null @@ -1,20 +0,0 @@ -package storage - -type One struct { - ID string `bson:"_id,omitempty"` - Relation string `bson:"relation,omitempty"` - Meta map[string]interface{} `bson:"meta,omitempty"` - Know []One `bson:"know,omitempty"` -} - -func (o One) Knows() []string { - knows := make([]string, len(o.Know)) - for i := range o.Know { - knows[i] = o.Know[i].ID - } - return knows -} - -func (o One) Min() One { - return One{ID: o.ID} -} diff --git a/storage/filter.go b/storage/operator/filter.go similarity index 85% rename from storage/filter.go rename to storage/operator/filter.go index 3391fd0..507ad8d 100644 --- a/storage/filter.go +++ b/storage/operator/filter.go @@ -1,4 +1,4 @@ -package storage +package operator import ( "fmt" @@ -11,13 +11,13 @@ type FilterIn struct { Values []interface{} } -func newFilterIn(key string, values interface{}) FilterIn { +func NewFilterIn(key string, values interface{}) FilterIn { fi := FilterIn{Key: key} switch values.(type) { case []interface{}: fi.Values = values.([]interface{}) if len(fi.Values) == 0 { - return newFilterIn(key, nil) + return NewFilterIn(key, nil) } case []string: value := values.([]string) @@ -26,7 +26,7 @@ func newFilterIn(key string, values interface{}) FilterIn { fi.Values[i] = value[i] } if len(fi.Values) == 0 { - return newFilterIn(key, nil) + return NewFilterIn(key, nil) } case []int: value := values.([]int) @@ -35,7 +35,7 @@ func newFilterIn(key string, values interface{}) FilterIn { fi.Values[i] = value[i] } if len(fi.Values) == 0 { - return newFilterIn(key, nil) + return NewFilterIn(key, nil) } case nil: fi.Key = "" diff --git a/storage/operator/filter_test.go b/storage/operator/filter_test.go new file mode 100644 index 0000000..f4902d1 --- /dev/null +++ b/storage/operator/filter_test.go @@ -0,0 +1,40 @@ +package operator + +import ( + "fmt" + "testing" +) + +func TestFilterIn(t *testing.T) { + cases := map[string]struct { + input interface{} + output interface{} + }{ + "[]int{5} => []interface{5}": { + input: []int{5}, + output: []interface{}{5}, + }, + "[]string{} => nil": { + input: []string{}, + output: []interface{}{}, + }, + "[]interface{}{} => nil": { + input: []interface{}{}, + output: []interface{}{}, + }, + "[]interface{}{string} => []interface{string}": { + input: []interface{}{"hi"}, + output: []interface{}{"hi"}, + }, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + filterIn := fmt.Sprint(NewFilterIn("key", c.input).Values) + if want := fmt.Sprint(c.output); want != filterIn { + t.Error(want, filterIn) + } + }) + } +} diff --git a/storage/modify.go b/storage/operator/modify.go similarity index 69% rename from storage/modify.go rename to storage/operator/modify.go index c909b15..9f160e7 100644 --- a/storage/modify.go +++ b/storage/operator/modify.go @@ -1,4 +1,4 @@ -package storage +package operator import ( "fmt" @@ -40,12 +40,20 @@ func (p Push) MarshalBSON() ([]byte, error) { } func opMarshal(op, key string, value interface{}) ([]byte, error) { - if len(key) == 0 { - return nil, fmt.Errorf("no key to %s", op) + marshalable := opMarshalable(op, key, value) + if len(marshalable) == 0 { + return nil, fmt.Errorf("failed marshalling op") } - return bson.Marshal(map[string]interface{}{ + return bson.Marshal(marshalable) +} + +func opMarshalable(op, key string, value interface{}) map[string]map[string]interface{} { + if len(key) == 0 { + return nil + } + return map[string]map[string]interface{}{ op: map[string]interface{}{ key: value, }, - }) + } } diff --git a/storage/operator/modify_test.go b/storage/operator/modify_test.go new file mode 100644 index 0000000..95b9ef0 --- /dev/null +++ b/storage/operator/modify_test.go @@ -0,0 +1,65 @@ +package operator + +import ( + "encoding/json" + "fmt" + "testing" + + "go.mongodb.org/mongo-driver/bson" +) + +func TestModify(t *testing.T) { + cases := map[string]struct { + marshalme interface{} + key1 string + key2 string + want interface{} + }{ + "unset": { + marshalme: Unset("field"), + key1: "field", + key2: "$unset", + want: nil, + }, + "popif": { + marshalme: PopIf{Key: "field", Filter: "world"}, + key1: "$pull", + key2: "field", + want: "world", + }, + "popif map": { + marshalme: PopIf{Key: "field", Filter: map[string]string{"world": "jk"}}, + key1: "$pull", + key2: "field", + want: map[string]string{"world": "jk"}, + }, + "set": { + marshalme: Set{Key: "field", Value: "value"}, + key1: "$set", + key2: "field", + want: "value", + }, + "push": { + marshalme: Push{Key: "field", Value: "value"}, + key1: "$push", + key2: "field", + want: "value", + }, + } + + for name, d := range cases { + c := d + t.Run(name, func(t *testing.T) { + var v map[string]map[string]interface{} + if b, err := bson.Marshal(c.marshalme); err != nil { + t.Error(err) + } else if err := bson.Unmarshal(b, &v); err != nil { + t.Error(err) + } else if got := v[c.key1][c.key2]; fmt.Sprint(got) != fmt.Sprint(c.want) { + a, _ := json.Marshal(got) + b, _ := json.Marshal(v) + t.Errorf("want [%s][%s] = %v, got %s from %s", c.key1, c.key2, c.want, string(a), string(b)) + } + }) + } +} diff --git a/view/html.go b/view/html.go index e6dce1f..ebba75e 100644 --- a/view/html.go +++ b/view/html.go @@ -10,7 +10,9 @@ import ( ) func Html(g storage.Graph) error { - err := http.ListenAndServe(fmt.Sprintf(":%d", config.New().Port), foo(g)) + port := config.New().Port + log.Println("listening on", port) + err := http.ListenAndServe(fmt.Sprintf(":%d", port), foo(g)) return err }