Refactored repository structure.
This commit is contained in:
273
subgraph_test.go
Normal file
273
subgraph_test.go
Normal file
@@ -0,0 +1,273 @@
|
||||
package heartwood
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"git.wisehodl.dev/jay/go-heartwood/graph"
|
||||
roots "git.wisehodl.dev/jay/go-roots/events"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var ids = map[string]string{
|
||||
"a": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"b": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
|
||||
"c": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc",
|
||||
"d": "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd",
|
||||
}
|
||||
|
||||
var static = roots.Event{
|
||||
CreatedAt: 1000,
|
||||
Kind: 1,
|
||||
Content: "hello",
|
||||
}
|
||||
|
||||
func newFullEventNode(id string, createdAt, kind int, content string) *graph.Node {
|
||||
n := graph.NewEventNode(id)
|
||||
n.Props["created_at"] = createdAt
|
||||
n.Props["kind"] = kind
|
||||
n.Props["content"] = content
|
||||
return n
|
||||
}
|
||||
|
||||
func baseSubgraph(eventID, pubkey string) (*graph.Subgraph, *graph.Node, *graph.Node) {
|
||||
s := graph.NewSubgraph()
|
||||
eventNode := newFullEventNode(eventID, static.CreatedAt, static.Kind, static.Content)
|
||||
userNode := graph.NewUserNode(pubkey)
|
||||
s.AddNode(eventNode)
|
||||
s.AddNode(userNode)
|
||||
s.AddRel(graph.NewSignedRel(userNode, eventNode, nil))
|
||||
return s, eventNode, userNode
|
||||
}
|
||||
|
||||
func TestEventToSubgraph(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
event roots.Event
|
||||
expected *graph.Subgraph
|
||||
}{
|
||||
{
|
||||
name: "bare event",
|
||||
event: roots.Event{
|
||||
ID: ids["a"], PubKey: ids["b"],
|
||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||
},
|
||||
expected: func() *graph.Subgraph {
|
||||
s, _, _ := baseSubgraph(ids["a"], ids["b"])
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "single generic tag",
|
||||
event: roots.Event{
|
||||
ID: ids["a"], PubKey: ids["b"],
|
||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||
Tags: []roots.Tag{{"t", "bitcoin"}},
|
||||
},
|
||||
expected: func() *graph.Subgraph {
|
||||
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
||||
tagNode := graph.NewTagNode("t", "bitcoin")
|
||||
s.AddNode(tagNode)
|
||||
s.AddRel(graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "tag with fewer than 2 elements",
|
||||
event: roots.Event{
|
||||
ID: ids["a"], PubKey: ids["b"],
|
||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||
Tags: []roots.Tag{{"t"}},
|
||||
},
|
||||
expected: func() *graph.Subgraph {
|
||||
s, _, _ := baseSubgraph(ids["a"], ids["b"])
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "e tag with valid hex64",
|
||||
event: roots.Event{
|
||||
ID: ids["a"], PubKey: ids["b"],
|
||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||
Tags: []roots.Tag{{"e", ids["c"]}},
|
||||
},
|
||||
expected: func() *graph.Subgraph {
|
||||
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
||||
tagNode := graph.NewTagNode("e", ids["c"])
|
||||
referencedEvent := graph.NewEventNode(ids["c"])
|
||||
s.AddNode(tagNode)
|
||||
s.AddNode(referencedEvent)
|
||||
s.AddRel(graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||
s.AddRel(graph.NewReferencesEventRel(tagNode, referencedEvent, nil))
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "e tag with invalid value",
|
||||
event: roots.Event{
|
||||
ID: ids["a"], PubKey: ids["b"],
|
||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||
Tags: []roots.Tag{{"e", "notvalid"}},
|
||||
},
|
||||
expected: func() *graph.Subgraph {
|
||||
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
||||
tagNode := graph.NewTagNode("e", "notvalid")
|
||||
s.AddNode(tagNode)
|
||||
s.AddRel(graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "p tag with valid hex64",
|
||||
event: roots.Event{
|
||||
ID: ids["a"], PubKey: ids["b"],
|
||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||
Tags: []roots.Tag{{"p", ids["d"]}},
|
||||
},
|
||||
expected: func() *graph.Subgraph {
|
||||
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
||||
tagNode := graph.NewTagNode("p", ids["d"])
|
||||
referencedUser := graph.NewUserNode(ids["d"])
|
||||
s.AddNode(tagNode)
|
||||
s.AddNode(referencedUser)
|
||||
s.AddRel(graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||
s.AddRel(graph.NewReferencesUserRel(tagNode, referencedUser, nil))
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
{
|
||||
name: "p tag with invalid value",
|
||||
event: roots.Event{
|
||||
ID: ids["a"], PubKey: ids["b"],
|
||||
CreatedAt: static.CreatedAt, Kind: static.Kind, Content: static.Content,
|
||||
Tags: []roots.Tag{{"p", "notvalid"}},
|
||||
},
|
||||
expected: func() *graph.Subgraph {
|
||||
s, eventNode, _ := baseSubgraph(ids["a"], ids["b"])
|
||||
tagNode := graph.NewTagNode("p", "notvalid")
|
||||
s.AddNode(tagNode)
|
||||
s.AddRel(graph.NewTaggedRel(eventNode, tagNode, nil))
|
||||
return s
|
||||
}(),
|
||||
},
|
||||
}
|
||||
|
||||
expanders := GetDefaultExpanderRegistry()
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got := EventToSubgraph(tc.event, expanders)
|
||||
assertSubgraphsEqual(t, tc.expected, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
func nodesEqual(expected, got *graph.Node) error {
|
||||
// Compare label counts
|
||||
if expected.Labels.Length() != got.Labels.Length() {
|
||||
return fmt.Errorf(
|
||||
"number of labels does not match. expected %d, got %d",
|
||||
expected.Labels.Length(), got.Labels.Length())
|
||||
}
|
||||
|
||||
// Compare label values
|
||||
for _, label := range expected.Labels.ToArray() {
|
||||
if !got.Labels.Contains(label) {
|
||||
return fmt.Errorf("missing label %q", label)
|
||||
}
|
||||
}
|
||||
|
||||
// Compare property values
|
||||
if err := propsEqual(expected.Props, got.Props); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func relsEqual(expected, got *graph.Relationship) error {
|
||||
// Compare type
|
||||
if expected.Type != got.Type {
|
||||
return fmt.Errorf("type: expected %q, got %q", expected.Type, got.Type)
|
||||
}
|
||||
|
||||
// Compare property values
|
||||
if err := propsEqual(expected.Props, got.Props); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Compare endpoints
|
||||
if err := nodesEqual(expected.Start, got.Start); err != nil {
|
||||
return fmt.Errorf("start node: %w", err)
|
||||
}
|
||||
if err := nodesEqual(expected.End, got.End); err != nil {
|
||||
return fmt.Errorf("end node: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func propsEqual(expected, got graph.Properties) error {
|
||||
if len(expected) != len(got) {
|
||||
return fmt.Errorf(
|
||||
"number of props does not match. expected %d, got %d",
|
||||
len(expected), len(got))
|
||||
}
|
||||
|
||||
for key, expectedVal := range expected {
|
||||
gotVal, exists := got[key]
|
||||
if !exists {
|
||||
return fmt.Errorf("missing prop %q", key)
|
||||
}
|
||||
if !reflect.DeepEqual(expectedVal, gotVal) {
|
||||
return fmt.Errorf("prop %q: expected %v, got %v", key, expectedVal, gotVal)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func assertSubgraphsEqual(t *testing.T, expected, got *graph.Subgraph) {
|
||||
t.Helper()
|
||||
|
||||
gotNodes := make([]*graph.Node, len(got.Nodes()))
|
||||
copy(gotNodes, got.Nodes())
|
||||
|
||||
gotRels := make([]*graph.Relationship, len(got.Rels()))
|
||||
copy(gotRels, got.Rels())
|
||||
|
||||
for _, expectedNode := range expected.Nodes() {
|
||||
index := findInList(expectedNode, gotNodes, nodesEqual)
|
||||
if index == -1 {
|
||||
assert.Fail(t, fmt.Sprintf("missing expected node: %+v", expectedNode))
|
||||
continue
|
||||
}
|
||||
gotNodes = removeFromList(index, gotNodes)
|
||||
}
|
||||
|
||||
for _, expectedRel := range expected.Rels() {
|
||||
index := findInList(expectedRel, gotRels, relsEqual)
|
||||
if index == -1 {
|
||||
assert.Fail(t, fmt.Sprintf("missing expected rel: %+v", expectedRel))
|
||||
continue
|
||||
}
|
||||
gotRels = removeFromList(index, gotRels)
|
||||
}
|
||||
|
||||
assert.Empty(t, gotNodes, "unexpected nodes in subgraph")
|
||||
assert.Empty(t, gotRels, "unexpected rels in subgraph")
|
||||
}
|
||||
|
||||
func findInList[T any](item *T, list []*T, equal func(*T, *T) error) int {
|
||||
for i, candidate := range list {
|
||||
if equal(item, candidate) == nil {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func removeFromList[T any](i int, list []*T) []*T {
|
||||
return append(list[:i], list[i+1:]...)
|
||||
}
|
||||
Reference in New Issue
Block a user