From 85a42d26589e210dce51981e39ce3c2610ffde69 Mon Sep 17 00:00:00 2001 From: Vargha Csongor Date: Sun, 13 Apr 2025 20:15:51 +0200 Subject: [PATCH] implement family tree queries --- .../internal/memgraph/family_tree.go | 45 ++++++ .../internal/memgraph/family_tree_test.go | 145 ++++++++++++++++++ apps/db-adapter/internal/memgraph/queries.go | 8 +- .../queries/get_blood_relations_by_id.cypher | 2 +- .../memgraph/queries/get_relationship.cypher | 2 +- .../queries/update_relationship.cypher | 4 + 6 files changed, 201 insertions(+), 5 deletions(-) create mode 100644 apps/db-adapter/internal/memgraph/family_tree.go create mode 100644 apps/db-adapter/internal/memgraph/family_tree_test.go create mode 100644 apps/db-adapter/internal/memgraph/queries/update_relationship.cypher diff --git a/apps/db-adapter/internal/memgraph/family_tree.go b/apps/db-adapter/internal/memgraph/family_tree.go new file mode 100644 index 0000000..9516ec1 --- /dev/null +++ b/apps/db-adapter/internal/memgraph/family_tree.go @@ -0,0 +1,45 @@ +package memgraph + +import ( + "context" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" +) + +// returned map has "people" which is a slice of OptimizedPersonNode and relationships wich a slice of Relatioship type. +func GetFamilyTreeById(ctx context.Context, userId int) neo4j.ManagedTransactionWork { + return func(tx neo4j.ManagedTransaction) (any, error) { + result, err := tx.Run(ctx, GetBloodRelativesCypherQuery, map[string]any{ + "id": userId, + }) + if err != nil { + return nil, err + } + + record, err := result.Single(ctx) + if err != nil { + return nil, err + } + + return record.AsMap(), nil + } +} + +// returned map has "people" which is a slice of OptimizedPersonNode and relationships wich a slice of Relatioship type. +func GetFamilyTreeWithSpousesById(ctx context.Context, userId int) neo4j.ManagedTransactionWork { + return func(tx neo4j.ManagedTransaction) (any, error) { + result, err := tx.Run(ctx, GetFamilyTreeWithSpousesCypherQuery, map[string]any{ + "id": userId, + }) + if err != nil { + return nil, err + } + + record, err := result.Single(ctx) + if err != nil { + return nil, err + } + + return record.AsMap(), nil + } +} diff --git a/apps/db-adapter/internal/memgraph/family_tree_test.go b/apps/db-adapter/internal/memgraph/family_tree_test.go new file mode 100644 index 0000000..70c509b --- /dev/null +++ b/apps/db-adapter/internal/memgraph/family_tree_test.go @@ -0,0 +1,145 @@ +package memgraph + +import ( + "context" + "errors" + "testing" + + "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/stretchr/testify/assert" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/memgraph/mock" +) + +func TestGetFamilyTreeById(t *testing.T) { + testCases := []struct { + name string + mockTxSetup func() *mock.Transaction + expectedResult map[string]any + expectedError error + }{ + { + name: "Successful case", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockRecord := &neo4j.Record{ + Values: []any{"value"}, + Keys: []string{"key"}, + } + mockResult.On("Single", context.Background()).Return(mockRecord, nil) + mockTx.On("Run", context.Background(), GetBloodRelativesCypherQuery, map[string]any{"id": 123}).Return(mockResult, nil) + return mockTx + }, + expectedResult: map[string]any{"key": "value"}, + expectedError: nil, + }, + { + name: "Error during Run", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockTx.On("Run", context.Background(), GetBloodRelativesCypherQuery, map[string]any{"id": 123}).Return(nil, errors.New("run error")) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("run error"), + }, + { + name: "Error during Single", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockResult.On("Single", context.Background()).Return(nil, errors.New("single error")) + mockTx.On("Run", context.Background(), GetBloodRelativesCypherQuery, map[string]any{"id": 123}).Return(mockResult, nil) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("single error"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + userId := 123 + + mockTx := tc.mockTxSetup() + work := GetFamilyTreeById(ctx, userId) + result, err := work(mockTx) + + if tc.expectedError != nil { + assert.Error(t, err) + assert.Nil(t, result) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedResult, result) + } + }) + } +} + +func TestGetFamilyTreeWithSpousesById(t *testing.T) { + testCases := []struct { + name string + mockTxSetup func() *mock.Transaction + expectedResult map[string]any + expectedError error + }{ + { + name: "Successful case", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockRecord := &neo4j.Record{ + Values: []any{"value"}, + Keys: []string{"key"}, + } + mockResult.On("Single", context.Background()).Return(mockRecord, nil) + mockTx.On("Run", context.Background(), GetFamilyTreeWithSpousesCypherQuery, map[string]any{"id": 123}).Return(mockResult, nil) + return mockTx + }, + expectedResult: map[string]any{"key": "value"}, + expectedError: nil, + }, + { + name: "Error during Run", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockTx.On("Run", context.Background(), GetFamilyTreeWithSpousesCypherQuery, map[string]any{"id": 123}).Return(nil, errors.New("run error")) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("run error"), + }, + { + name: "Error during Single", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockResult.On("Single", context.Background()).Return(nil, errors.New("single error")) + mockTx.On("Run", context.Background(), GetFamilyTreeWithSpousesCypherQuery, map[string]any{"id": 123}).Return(mockResult, nil) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("single error"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + userId := 123 + + mockTx := tc.mockTxSetup() + work := GetFamilyTreeWithSpousesById(ctx, userId) + result, err := work(mockTx) + + if tc.expectedError != nil { + assert.Error(t, err) + assert.Nil(t, result) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedResult, result) + } + }) + } +} diff --git a/apps/db-adapter/internal/memgraph/queries.go b/apps/db-adapter/internal/memgraph/queries.go index 2b6598f..80aa97d 100644 --- a/apps/db-adapter/internal/memgraph/queries.go +++ b/apps/db-adapter/internal/memgraph/queries.go @@ -88,10 +88,12 @@ var CreateSpouseRelationshipCypherQuery string //go:embed queries/delete_relationship.cypher var DeleteRelationshipCypherQuery string -// Requires id parameter. +// Requires id1, id2, relationship parameter. // -//go:embed queries/get_family_tree_by_id.cypher -var GetFamilyTreeByIdCypherQuery string +// return relationship +// +//go:embed queries/update_relationship.cypher +var UpdateRelationshipCypherQuery string // Requires id1, id2 parameter. // diff --git a/apps/db-adapter/internal/memgraph/queries/get_blood_relations_by_id.cypher b/apps/db-adapter/internal/memgraph/queries/get_blood_relations_by_id.cypher index e771f18..30cde5b 100644 --- a/apps/db-adapter/internal/memgraph/queries/get_blood_relations_by_id.cypher +++ b/apps/db-adapter/internal/memgraph/queries/get_blood_relations_by_id.cypher @@ -8,7 +8,7 @@ WITH collections.to_set(collect(n)+collect(family)+collect(children)+collect(dir collections.to_set(collect(c) + collect(p) + collect(s) + collect(ds)) as relationships UNWIND people as ppl RETURN collect({ - id: id(ppl), + id: id(ppl), first_name: ppl.first_name, middle_name: ppl.middle_name, last_name: ppl.last_name, diff --git a/apps/db-adapter/internal/memgraph/queries/get_relationship.cypher b/apps/db-adapter/internal/memgraph/queries/get_relationship.cypher index 631741e..b85e308 100644 --- a/apps/db-adapter/internal/memgraph/queries/get_relationship.cypher +++ b/apps/db-adapter/internal/memgraph/queries/get_relationship.cypher @@ -1,3 +1,3 @@ -MATCH (n)-[r]-(o) +MATCH (n)-[r]->(o) WHERE id(n) = $id1 AND id(o) = $id2 RETURN r as relationship \ No newline at end of file diff --git a/apps/db-adapter/internal/memgraph/queries/update_relationship.cypher b/apps/db-adapter/internal/memgraph/queries/update_relationship.cypher new file mode 100644 index 0000000..3d777d1 --- /dev/null +++ b/apps/db-adapter/internal/memgraph/queries/update_relationship.cypher @@ -0,0 +1,4 @@ +MATCH (n)-[r]->(o) +WHERE id(n) = $id1 AND id(o) = $id2 +SET r += $relationship +RETURN r as relationship