diff --git a/apps/db-adapter/internal/api/auth/read_operations.go b/apps/db-adapter/internal/api/auth/read_operations.go index a150a4c..1a17b79 100644 --- a/apps/db-adapter/internal/api/auth/read_operations.go +++ b/apps/db-adapter/internal/api/auth/read_operations.go @@ -2,8 +2,11 @@ package auth import ( "context" + "fmt" "github.com/neo4j/neo4j-go-driver/v5/neo4j" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/internal/memgraph" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api" ) func CouldSeePersonsProfile(ctx context.Context, session neo4j.SessionWithContext, userId, XUserID int) error { @@ -11,4 +14,27 @@ func CouldSeePersonsProfile(ctx context.Context, session neo4j.SessionWithContex return nil } + res, err := session.ExecuteRead(ctx, memgraph.GetFamilyTreeById(ctx, XUserID)) + if err != nil { + return err + } + + resMap, ok := res.(map[string]any) + if !ok { + return fmt.Errorf("could not convert result to map[string]any") + } + + people, ok := resMap["people"].([]api.OptimizedPersonNode) + if !ok { + return fmt.Errorf("could not convert people to []api.PersonProperties") + } + + for _, person := range people { + if *person.Id == userId { + return nil + } + } + + return fmt.Errorf("user %d does not have permission to see user %d", XUserID, userId) + } diff --git a/apps/db-adapter/internal/memgraph/person_google_test.go b/apps/db-adapter/internal/memgraph/person_google_test.go index 57718cb..e20b21f 100644 --- a/apps/db-adapter/internal/memgraph/person_google_test.go +++ b/apps/db-adapter/internal/memgraph/person_google_test.go @@ -84,7 +84,7 @@ func TestGetPersonByGoogleId(t *testing.T) { } } -func TestUpdatePerson(t *testing.T) { +func TestUpdatePersonByGoogleID(t *testing.T) { tests := []struct { name string mockRunError error diff --git a/apps/db-adapter/internal/memgraph/person_test.go b/apps/db-adapter/internal/memgraph/person_test.go new file mode 100644 index 0000000..49888d0 --- /dev/null +++ b/apps/db-adapter/internal/memgraph/person_test.go @@ -0,0 +1,298 @@ +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" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api" +) + +func TestCreatePerson(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(), CreatePersonCypherQuery, map[string]any{ + "Person": api.PersonProperties{}, + }).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(), CreatePersonCypherQuery, map[string]any{ + "Person": api.PersonProperties{}, + }).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(), CreatePersonCypherQuery, map[string]any{ + "Person": api.PersonProperties{}, + }).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() + person := &api.PersonProperties{} + + mockTx := tc.mockTxSetup() + work := CreatePerson(ctx, person) + 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 TestHardDeletePerson(t *testing.T) { + testCases := []struct { + name string + mockTxSetup func() *mock.Transaction + expectedResult any + expectedError error + }{ + { + name: "Successful case", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockResult.On("Peek", context.Background()).Return(false, nil) + mockTx.On("Run", context.Background(), HardDeletePersonCypherQuery, map[string]any{"id": 123}).Return(mockResult, nil) + return mockTx + }, + expectedResult: nil, + expectedError: nil, + }, + { + name: "Error during Run", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockTx.On("Run", context.Background(), HardDeletePersonCypherQuery, map[string]any{"id": 123}).Return(nil, errors.New("run error")) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("run error"), + }, + { + name: "Unexpected record returned", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockResult.On("Peek", context.Background()).Return(true, nil) + mockTx.On("Run", context.Background(), HardDeletePersonCypherQuery, map[string]any{"id": 123}).Return(mockResult, nil) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("record was returned when it wasn't supposed to happen"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + id := 123 + + mockTx := tc.mockTxSetup() + work := HardDeletePerson(ctx, id) + 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 TestUpdatePerson(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{"updatedValue"}, + Keys: []string{"updatedKey"}, + } + mockResult.On("Single", context.Background()).Return(mockRecord, nil) + mockTx.On("Run", context.Background(), UpdatePersonCypherQuery, map[string]any{ + "id": 123, + "props": api.PersonProperties{}, + }).Return(mockResult, nil) + return mockTx + }, + expectedResult: map[string]any{"updatedKey": "updatedValue"}, + expectedError: nil, + }, + { + name: "Error during Run", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockTx.On("Run", context.Background(), UpdatePersonCypherQuery, map[string]any{ + "id": 123, + "props": api.PersonProperties{}, + }).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(), UpdatePersonCypherQuery, map[string]any{ + "id": 123, + "props": api.PersonProperties{}, + }).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() + id := 123 + person := &api.PersonProperties{} + + mockTx := tc.mockTxSetup() + work := UpdatePerson(ctx, id, person) + 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 TestSoftDeletePerson(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{"deletedValue"}, + Keys: []string{"deletedKey"}, + } + mockResult.On("Single", context.Background()).Return(mockRecord, nil) + mockTx.On("Run", context.Background(), SoftDeletePersonCypherQuery, map[string]any{ + "id": 123, + }).Return(mockResult, nil) + return mockTx + }, + expectedResult: map[string]any{"deletedKey": "deletedValue"}, + expectedError: nil, + }, + { + name: "Error during Run", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockTx.On("Run", context.Background(), SoftDeletePersonCypherQuery, 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(), SoftDeletePersonCypherQuery, 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() + id := 123 + + mockTx := tc.mockTxSetup() + work := SoftDeletePerson(ctx, id) + 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/relationship.go b/apps/db-adapter/internal/memgraph/relationship.go index e90415d..45ec077 100644 --- a/apps/db-adapter/internal/memgraph/relationship.go +++ b/apps/db-adapter/internal/memgraph/relationship.go @@ -40,13 +40,33 @@ func DeleteRelationship(ctx context.Context, id1, id2 int) neo4j.ManagedTransact } } -func CreateChildParentRelationship(ctx context.Context, childId, parentId int) neo4j.ManagedTransactionWork { +func UpdateRelationship(ctx context.Context, id1, id2 int, relationship api.FamilyRelationship) neo4j.ManagedTransactionWork { + return func(tx neo4j.ManagedTransaction) (any, error) { + result, err := tx.Run(ctx, UpdateRelationshipCypherQuery, map[string]any{ + "id1": id1, + "id2": id2, + "relationship": relationship, + }) + if err != nil { + return nil, err + } + + record, err := result.Single(ctx) + if err != nil { + return nil, err + } + + return record.AsMap(), nil + } +} + +func CreateChildParentRelationship(ctx context.Context, childId, parentId int, relationship api.FamilyRelationship) neo4j.ManagedTransactionWork { return func(tx neo4j.ManagedTransaction) (any, error) { result, err := tx.Run(ctx, CreateChildParentRelationshipCypherQuery, map[string]any{ "childId": childId, "parentId": parentId, - "childRelationship": api.FamilyRelationship{}, - "parentRelationship": api.FamilyRelationship{}, + "childRelationship": relationship, + "parentRelationship": relationship, }) if err != nil { return nil, err @@ -56,13 +76,13 @@ func CreateChildParentRelationship(ctx context.Context, childId, parentId int) n } } -func CreateSiblingRelationship(ctx context.Context, siblingId1, siblingId2 int) neo4j.ManagedTransactionWork { +func CreateSiblingRelationship(ctx context.Context, siblingId1, siblingId2 int, relationship api.FamilyRelationship) neo4j.ManagedTransactionWork { return func(tx neo4j.ManagedTransaction) (any, error) { result, err := tx.Run(ctx, CreateSiblingRelationshipCypherQuery, map[string]any{ "id1": siblingId1, "id2": siblingId2, - "Relationship1": api.FamilyRelationship{}, - "Relationship2": api.FamilyRelationship{}, + "Relationship1": relationship, + "Relationship2": relationship, }) if err != nil { return nil, err @@ -72,13 +92,13 @@ func CreateSiblingRelationship(ctx context.Context, siblingId1, siblingId2 int) } } -func CreateSpouseRelationship(ctx context.Context, spouseId1, spouseId2 int) neo4j.ManagedTransactionWork { +func CreateSpouseRelationship(ctx context.Context, spouseId1, spouseId2 int, relationship api.FamilyRelationship) neo4j.ManagedTransactionWork { return func(tx neo4j.ManagedTransaction) (any, error) { result, err := tx.Run(ctx, CreateSpouseRelationshipCypherQuery, map[string]any{ "id1": spouseId1, "id2": spouseId2, - "Relationship1": api.FamilyRelationship{}, - "Relationship2": api.FamilyRelationship{}, + "Relationship1": relationship, + "Relationship2": relationship, }) if err != nil { return nil, err diff --git a/apps/db-adapter/internal/memgraph/relationship_test.go b/apps/db-adapter/internal/memgraph/relationship_test.go new file mode 100644 index 0000000..ffc4159 --- /dev/null +++ b/apps/db-adapter/internal/memgraph/relationship_test.go @@ -0,0 +1,194 @@ +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" + "github.com/vcscsvcscs/GenerationsHeritage/apps/db-adapter/pkg/api" +) + +func TestGetRelationship(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(), GetRelationshipCypherQuery, map[string]any{"id1": 1, "id2": 2}).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(), GetRelationshipCypherQuery, map[string]any{"id1": 1, "id2": 2}).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(), GetRelationshipCypherQuery, map[string]any{"id1": 1, "id2": 2}).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() + id1, id2 := 1, 2 + + mockTx := tc.mockTxSetup() + work := GetRelationship(ctx, id1, id2) + 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 TestDeleteRelationship(t *testing.T) { + testCases := []struct { + name string + mockTxSetup func() *mock.Transaction + expectedResult bool + expectedError error + }{ + { + name: "Successful case", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockResult := new(mock.Result) + mockResult.On("Peek", context.Background()).Return(false, nil) + mockTx.On("Run", context.Background(), DeleteRelationshipCypherQuery, map[string]any{"id1": 1, "id2": 2}).Return(mockResult, nil) + return mockTx + }, + expectedResult: true, + expectedError: nil, + }, + { + name: "Error during Run", + mockTxSetup: func() *mock.Transaction { + mockTx := new(mock.Transaction) + mockTx.On("Run", context.Background(), DeleteRelationshipCypherQuery, map[string]any{"id1": 1, "id2": 2}).Return(nil, errors.New("run error")) + return mockTx + }, + expectedResult: false, + expectedError: errors.New("run error"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + id1, id2 := 1, 2 + + mockTx := tc.mockTxSetup() + work := DeleteRelationship(ctx, id1, id2) + result, err := work(mockTx) + + if tc.expectedError != nil { + assert.Error(t, err) + assert.False(t, result.(bool)) + } else { + assert.NoError(t, err) + assert.Equal(t, tc.expectedResult, result) + } + }) + } +} + +func TestUpdateRelationship(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(), UpdateRelationshipCypherQuery, map[string]any{ + "id1": 1, + "id2": 2, + "relationship": api.FamilyRelationship{}, + }).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(), UpdateRelationshipCypherQuery, map[string]any{ + "id1": 1, + "id2": 2, + "relationship": api.FamilyRelationship{}, + }).Return(nil, errors.New("run error")) + return mockTx + }, + expectedResult: nil, + expectedError: errors.New("run error"), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + ctx := context.Background() + id1, id2 := 1, 2 + relationship := api.FamilyRelationship{} + + mockTx := tc.mockTxSetup() + work := UpdateRelationship(ctx, id1, id2, relationship) + 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) + } + }) + } +}