diff --git a/RelationshipAnalysis.Integration.Test/Resources.Designer.cs b/RelationshipAnalysis.Integration.Test/Resources.Designer.cs deleted file mode 100644 index 434b002..0000000 --- a/RelationshipAnalysis.Integration.Test/Resources.Designer.cs +++ /dev/null @@ -1,204 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace RelationshipAnalysis.Integration.Test { - using System; - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] - [System.Diagnostics.DebuggerNonUserCodeAttribute()] - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static System.Resources.ResourceManager resourceMan; - - private static System.Globalization.CultureInfo resourceCulture; - - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Resources.ResourceManager ResourceManager { - get { - if (object.Equals(null, resourceMan)) { - System.Resources.ResourceManager temp = new System.Resources.ResourceManager("RelationshipAnalysis.Integration.Test.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - internal static string ValidFileMessage { - get { - return ResourceManager.GetString("ValidFileMessage", resourceCulture); - } - } - - internal static string FailedAddRecordsMessage { - get { - return ResourceManager.GetString("FailedAddRecordsMessage", resourceCulture); - } - } - - internal static string InvalidHeaderAttribute { - get { - return ResourceManager.GetString("InvalidHeaderAttribute", resourceCulture); - } - } - - internal static string TwoSameHeadersMessage { - get { - return ResourceManager.GetString("TwoSameHeadersMessage", resourceCulture); - } - } - - internal static string InvalidNodeCategory { - get { - return ResourceManager.GetString("InvalidNodeCategory", resourceCulture); - } - } - - internal static string SuccessfulNodeAdditionMessage { - get { - return ResourceManager.GetString("SuccessfulNodeAdditionMessage", resourceCulture); - } - } - - internal static string NoFileUploadedMessage { - get { - return ResourceManager.GetString("NoFileUploadedMessage", resourceCulture); - } - } - - internal static string EmailExistsMessage { - get { - return ResourceManager.GetString("EmailExistsMessage", resourceCulture); - } - } - - internal static string SuccessfulLogoutMessage { - get { - return ResourceManager.GetString("SuccessfulLogoutMessage", resourceCulture); - } - } - - internal static string InvalidPasswordMessage { - get { - return ResourceManager.GetString("InvalidPasswordMessage", resourceCulture); - } - } - - internal static string UserNotFoundMessage { - get { - return ResourceManager.GetString("UserNotFoundMessage", resourceCulture); - } - } - - internal static string WrongOldPasswordMessage { - get { - return ResourceManager.GetString("WrongOldPasswordMessage", resourceCulture); - } - } - - internal static string OldPasswordRequired { - get { - return ResourceManager.GetString("OldPasswordRequired", resourceCulture); - } - } - - internal static string NewPasswordRequired { - get { - return ResourceManager.GetString("NewPasswordRequired", resourceCulture); - } - } - - internal static string UsernameRequired { - get { - return ResourceManager.GetString("UsernameRequired", resourceCulture); - } - } - - internal static string PasswordRequired { - get { - return ResourceManager.GetString("PasswordRequired", resourceCulture); - } - } - - internal static string SuccessfulUpdateUserMessage { - get { - return ResourceManager.GetString("SuccessfulUpdateUserMessage", resourceCulture); - } - } - - internal static string SuccessfulDeleteUserMessage { - get { - return ResourceManager.GetString("SuccessfulDeleteUserMessage", resourceCulture); - } - } - - internal static string UsernameExistsMessage { - get { - return ResourceManager.GetString("UsernameExistsMessage", resourceCulture); - } - } - - internal static string SucceddfulCreateUser { - get { - return ResourceManager.GetString("SucceddfulCreateUser", resourceCulture); - } - } - - internal static string EmptyRolesMessage { - get { - return ResourceManager.GetString("EmptyRolesMessage", resourceCulture); - } - } - - internal static string InvalidRolesListMessage { - get { - return ResourceManager.GetString("InvalidRolesListMessage", resourceCulture); - } - } - - internal static string SuccessfulUpdateRolesMessage { - get { - return ResourceManager.GetString("SuccessfulUpdateRolesMessage", resourceCulture); - } - } - - internal static string LoginFailedMessage { - get { - return ResourceManager.GetString("LoginFailedMessage", resourceCulture); - } - } - - internal static string SuccessfulLoginMessage { - get { - return ResourceManager.GetString("SuccessfulLoginMessage", resourceCulture); - } - } - - internal static string DeleteAccountAccessErrorMessage { - get { - return ResourceManager.GetString("DeleteAccountAccessErrorMessage", resourceCulture); - } - } - } -} diff --git a/RelationshipAnalysis.Integration.Test/Resources.resx b/RelationshipAnalysis.Integration.Test/Resources.resx deleted file mode 100644 index 6f03822..0000000 --- a/RelationshipAnalysis.Integration.Test/Resources.resx +++ /dev/null @@ -1,108 +0,0 @@ - - - - - - File is valid! - - - Add records failed! - - - Entered header is invalid - - - Two headers have the same title! - - - Entered node category is invalid - - - Nodes added successfully - - - No file uploaded! - - - Chosen email already exists - - - Logout was successful! - - - Password must contain one digit from 1 to 9, one lowercase letter, one uppercase letter, one special - character, no space, and it must be 8-16 characters long. - - - - Could not find user! - - - Entered old password is wrong - - - Old password is required - - - New password is required - - - Username is required! - - - Password is required! - - - User updated successfully! - - - User Deleted successfully! - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, - PublicKeyToken=b77a5c561934e089 - - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, - PublicKeyToken=b77a5c561934e089 - - - - Chosen username already exists - - - User Created Successfuly! - - - Roles cant be empty! - - - Roles are invalid! - - - User roles updated successfuly! - - - Login Failed! - - - Login was successful! - - - You cant delete your account! - - \ No newline at end of file diff --git a/RelationshipAnalysis.Test/Resources.Designer.cs b/RelationshipAnalysis.Test/Resources.Designer.cs deleted file mode 100644 index 7b293ca..0000000 --- a/RelationshipAnalysis.Test/Resources.Designer.cs +++ /dev/null @@ -1,246 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace RelationshipAnalysis.Test { - using System; - - - [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [System.Diagnostics.DebuggerNonUserCodeAttribute()] - [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static System.Resources.ResourceManager resourceMan; - - private static System.Globalization.CultureInfo resourceCulture; - - [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Resources.ResourceManager ResourceManager { - get { - if (object.Equals(null, resourceMan)) { - System.Resources.ResourceManager temp = new System.Resources.ResourceManager("RelationshipAnalysis.Test.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - internal static string InvalidTargetNodeCategory { - get { - return ResourceManager.GetString("InvalidTargetNodeCategory", resourceCulture); - } - } - - internal static string InvalidSourceNodeCategory { - get { - return ResourceManager.GetString("InvalidSourceNodeCategory", resourceCulture); - } - } - - internal static string InvalidEdgeCategory { - get { - return ResourceManager.GetString("InvalidEdgeCategory", resourceCulture); - } - } - - internal static string SuccessfulEdgeAdditionMessage { - get { - return ResourceManager.GetString("SuccessfulEdgeAdditionMessage", resourceCulture); - } - } - - internal static string ValidFileMessage { - get { - return ResourceManager.GetString("ValidFileMessage", resourceCulture); - } - } - - internal static string FailedAddRecordsMessage { - get { - return ResourceManager.GetString("FailedAddRecordsMessage", resourceCulture); - } - } - - internal static string InvalidHeaderAttribute { - get { - return ResourceManager.GetString("InvalidHeaderAttribute", resourceCulture); - } - } - - internal static string TwoSameHeadersMessage { - get { - return ResourceManager.GetString("TwoSameHeadersMessage", resourceCulture); - } - } - - internal static string InvalidNodeCategory { - get { - return ResourceManager.GetString("InvalidNodeCategory", resourceCulture); - } - } - - internal static string SuccessfulNodeAdditionMessage { - get { - return ResourceManager.GetString("SuccessfulNodeAdditionMessage", resourceCulture); - } - } - - internal static string NoFileUploadedMessage { - get { - return ResourceManager.GetString("NoFileUploadedMessage", resourceCulture); - } - } - - internal static string EmailExistsMessage { - get { - return ResourceManager.GetString("EmailExistsMessage", resourceCulture); - } - } - - internal static string SuccessfulLogoutMessage { - get { - return ResourceManager.GetString("SuccessfulLogoutMessage", resourceCulture); - } - } - - internal static string InvalidPasswordMessage { - get { - return ResourceManager.GetString("InvalidPasswordMessage", resourceCulture); - } - } - - internal static string UserNotFoundMessage { - get { - return ResourceManager.GetString("UserNotFoundMessage", resourceCulture); - } - } - - internal static string WrongOldPasswordMessage { - get { - return ResourceManager.GetString("WrongOldPasswordMessage", resourceCulture); - } - } - - internal static string OldPasswordRequired { - get { - return ResourceManager.GetString("OldPasswordRequired", resourceCulture); - } - } - - internal static string NewPasswordRequired { - get { - return ResourceManager.GetString("NewPasswordRequired", resourceCulture); - } - } - - internal static string UsernameRequired { - get { - return ResourceManager.GetString("UsernameRequired", resourceCulture); - } - } - - internal static string PasswordRequired { - get { - return ResourceManager.GetString("PasswordRequired", resourceCulture); - } - } - - internal static string SuccessfulUpdateUserMessage { - get { - return ResourceManager.GetString("SuccessfulUpdateUserMessage", resourceCulture); - } - } - - internal static string SuccessfulDeleteUserMessage { - get { - return ResourceManager.GetString("SuccessfulDeleteUserMessage", resourceCulture); - } - } - - internal static string UsernameExistsMessage { - get { - return ResourceManager.GetString("UsernameExistsMessage", resourceCulture); - } - } - - internal static string SucceddfulCreateUser { - get { - return ResourceManager.GetString("SucceddfulCreateUser", resourceCulture); - } - } - - internal static string EmptyRolesMessage { - get { - return ResourceManager.GetString("EmptyRolesMessage", resourceCulture); - } - } - - internal static string InvalidRolesListMessage { - get { - return ResourceManager.GetString("InvalidRolesListMessage", resourceCulture); - } - } - - internal static string SuccessfulUpdateRolesMessage { - get { - return ResourceManager.GetString("SuccessfulUpdateRolesMessage", resourceCulture); - } - } - - internal static string LoginFailedMessage { - get { - return ResourceManager.GetString("LoginFailedMessage", resourceCulture); - } - } - - internal static string SuccessfulLoginMessage { - get { - return ResourceManager.GetString("SuccessfulLoginMessage", resourceCulture); - } - } - - internal static string DeleteAccountAccessErrorMessage { - get { - return ResourceManager.GetString("DeleteAccountAccessErrorMessage", resourceCulture); - } - } - - internal static string NullDtoErrorMessage { - get { - return ResourceManager.GetString("NullDtoErrorMessage", resourceCulture); - } - } - - internal static string NotUniqueCategoryNameErrorMessage { - get { - return ResourceManager.GetString("NotUniqueCategoryNameErrorMessage", resourceCulture); - } - } - - internal static string SuccessfulCreateCategory { - get { - return ResourceManager.GetString("SuccessfulCreateCategory", resourceCulture); - } - } - } -} diff --git a/RelationshipAnalysis.Test/Resources.resx b/RelationshipAnalysis.Test/Resources.resx deleted file mode 100644 index a3c6f68..0000000 --- a/RelationshipAnalysis.Test/Resources.resx +++ /dev/null @@ -1,130 +0,0 @@ - - - - - Target Node category is invalid! - - - Source Node category is invalid! - - - Edge category is invalid! - - - Edges added successfully! - - - - - File is valid! - - - Add records failed! - - - Entered header is invalid - - - Two headers have the same title! - - - Entered node category is invalid - - - Nodes added successfully - - - No file uploaded! - - - Chosen email already exists - - - Logout was successful! - - - Password must contain one digit from 1 to 9, one lowercase letter, one uppercase letter, one special - character, no space, and it must be 8-16 characters long. - - - - Could not find user! - - - Entered old password is wrong - - - Old password is required - - - New password is required - - - Username is required! - - - Password is required! - - - User updated successfully! - - - User Deleted successfully! - - - - - - - - text/microsoft-resx - - - 1.3 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, - PublicKeyToken=b77a5c561934e089 - - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, - PublicKeyToken=b77a5c561934e089 - - - - Chosen username already exists - - - User Created Successfuly! - - - Roles cant be empty! - - - Roles are invalid! - - - User roles updated successfuly! - - - Login Failed! - - - Login was successful! - - - You cant delete your account! - - - Input Dto can not be null! - - - Category Name is not unique! - - - Category Created successfylly! - - \ No newline at end of file diff --git a/RelationshipAnalysis.Test/Services/GraphServices/ClauseValidatorServiceTests.cs b/RelationshipAnalysis.Test/Services/GraphServices/ClauseValidatorServiceTests.cs new file mode 100644 index 0000000..c07f75c --- /dev/null +++ b/RelationshipAnalysis.Test/Services/GraphServices/ClauseValidatorServiceTests.cs @@ -0,0 +1,108 @@ + +using RelationshipAnalysis.Dto.Graph; +using RelationshipAnalysis.Enums; +using RelationshipAnalysis.Services.GraphServices; + +namespace RelationshipAnalysis.Test.Services.GraphServices; + +public class ClauseValidatorServiceTests +{ + private readonly ClauseValidatorService _sut; + + public ClauseValidatorServiceTests() + { + _sut = new ClauseValidatorService(); + } + + [Fact] + public async Task AreClausesValid_ShouldReturnSuccess_WhenAllClausesAreValid() + { + // Arrange + var searchGraphDto = new SearchGraphDto + { + SourceCategoryClauses = new Dictionary { { "validSource", "value" } }, + TargetCategoryClauses = new Dictionary { { "validTarget", "value" } }, + EdgeCategoryClauses = new Dictionary { { "validEdge", "value" } } + }; + + var sourceAttributes = new List { "validSource" }; + var targetAttributes = new List { "validTarget" }; + var edgeAttributes = new List { "validEdge" }; + + // Act + var result = await _sut.AreClausesValid(searchGraphDto, sourceAttributes, targetAttributes, edgeAttributes); + + // Assert + Assert.Equal(StatusCodeType.Success, result.StatusCode); + Assert.Null(result.Data); + } + + [Fact] + public async Task AreClausesValid_ShouldReturnNotFound_WhenSourceCategoryClausesAreInvalid() + { + // Arrange + var searchGraphDto = new SearchGraphDto + { + SourceCategoryClauses = new Dictionary { { "invalidSource", "value" } }, + TargetCategoryClauses = new Dictionary { { "validTarget", "value" } }, + EdgeCategoryClauses = new Dictionary { { "validEdge", "value" } } + }; + + var sourceAttributes = new List { "validSource" }; + var targetAttributes = new List { "validTarget" }; + var edgeAttributes = new List { "validEdge" }; + + // Act + var result = await _sut.AreClausesValid(searchGraphDto, sourceAttributes, targetAttributes, edgeAttributes); + + // Assert + Assert.Equal(StatusCodeType.NotFound, result.StatusCode); + Assert.Equal(Resources.InvalidClauseInSourceCategory, result.Data.Message); + } + + [Fact] + public async Task AreClausesValid_ShouldReturnNotFound_WhenTargetCategoryClausesAreInvalid() + { + // Arrange + var searchGraphDto = new SearchGraphDto + { + SourceCategoryClauses = new Dictionary { { "validSource", "value" } }, + TargetCategoryClauses = new Dictionary { { "invalidTarget", "value" } }, + EdgeCategoryClauses = new Dictionary { { "validEdge", "value" } } + }; + + var sourceAttributes = new List { "validSource" }; + var targetAttributes = new List { "validTarget" }; + var edgeAttributes = new List { "validEdge" }; + + // Act + var result = await _sut.AreClausesValid(searchGraphDto, sourceAttributes, targetAttributes, edgeAttributes); + + // Assert + Assert.Equal(StatusCodeType.NotFound, result.StatusCode); + Assert.Equal(Resources.InvalidClauseInDestinationCategory, result.Data.Message); + } + + [Fact] + public async Task AreClausesValid_ShouldReturnNotFound_WhenEdgeCategoryClausesAreInvalid() + { + // Arrange + var searchGraphDto = new SearchGraphDto + { + SourceCategoryClauses = new Dictionary { { "validSource", "value" } }, + TargetCategoryClauses = new Dictionary { { "validTarget", "value" } }, + EdgeCategoryClauses = new Dictionary { { "invalidEdge", "value" } } + }; + + var sourceAttributes = new List { "validSource" }; + var targetAttributes = new List { "validTarget" }; + var edgeAttributes = new List { "validEdge" }; + + // Act + var result = await _sut.AreClausesValid(searchGraphDto, sourceAttributes, targetAttributes, edgeAttributes); + + // Assert + Assert.Equal(StatusCodeType.NotFound, result.StatusCode); + Assert.Equal(Resources.InvalidClauseInDestinationCategory, result.Data.Message); + } +} diff --git a/RelationshipAnalysis.Test/Services/GraphServices/Edge/EdgeSearchValidatorTests.cs b/RelationshipAnalysis.Test/Services/GraphServices/Edge/EdgeSearchValidatorTests.cs new file mode 100644 index 0000000..5c86903 --- /dev/null +++ b/RelationshipAnalysis.Test/Services/GraphServices/Edge/EdgeSearchValidatorTests.cs @@ -0,0 +1,137 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using RelationshipAnalysis.Context; +using RelationshipAnalysis.Models.Graph.Edge; +using RelationshipAnalysis.Services.GraphServices.Edge; + +namespace RelationshipAnalysis.Test.Services.GraphServices.Edge +{ + public class EdgeSearchValidatorTests + { + private readonly IServiceProvider _serviceProvider; + private readonly EdgeSearchValidator _sut; + + private readonly Models.Graph.Node.Node _sourceNode1 = new() + { + NodeId = 1, + NodeCategoryId = 1, + NodeUniqueString = "1" + }; + + private readonly Models.Graph.Node.Node _targetNode1 = new() + { + NodeId = 2, + NodeCategoryId = 2, + NodeUniqueString = "2" + }; + + private readonly EdgeCategory _edgeCategory1 = new() { EdgeCategoryId = 1, EdgeCategoryName = "Category1" }; + + private readonly Models.Graph.Edge.Edge _edge1 = new() + { + EdgeUniqueString = "E", + EdgeCategoryId = 1, + EdgeSourceNodeId = 1, + EdgeDestinationNodeId = 2, + EdgeValues = new List + { + new() { EdgeAttribute = new EdgeAttribute { EdgeAttributeName = "Relationship" }, ValueData = "Friend" } + } + }; + + public EdgeSearchValidatorTests() + { + var serviceCollection = new ServiceCollection(); + + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .UseLazyLoadingProxies() + .Options; + + serviceCollection.AddScoped(_ => new ApplicationDbContext(options)); + + _serviceProvider = serviceCollection.BuildServiceProvider(); + + _sut = new EdgeSearchValidator(_serviceProvider); + } + + private void SeedDatabase() + { + using var scope = _serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + context.Nodes.AddRange(_sourceNode1, _targetNode1); + context.EdgeCategories.Add(_edgeCategory1); + context.Edges.Add(_edge1); + context.SaveChanges(); + } + + [Fact] + public async Task GetValidEdges_ShouldReturnEmptyList_WhenNoEdgesMatch() + { + // Arrange + SeedDatabase(); + var sourceNodes = new List { _sourceNode1 }; + var targetNodes = new List { _targetNode1 }; + var clauses = new Dictionary { { "Relationship", "Colleague" } }; + + // Act + var result = await _sut.GetValidEdges(sourceNodes, targetNodes, "Category1", clauses); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task GetValidEdges_ShouldReturnEdges_WhenClauseMatches() + { + // Arrange + SeedDatabase(); + var sourceNodes = new List { _sourceNode1 }; + var targetNodes = new List { _targetNode1 }; + var clauses = new Dictionary { { "Relationship", "Friend" } }; // "Friend" exists in Edge1 + + // Act + var result = await _sut.GetValidEdges(sourceNodes, targetNodes, "Category1", clauses); + + // Assert + Assert.Single(result); + Assert.Contains(result, edge => edge.EdgeValues.Any(ev => ev.ValueData == "Friend")); + } + + [Fact] + public async Task GetValidEdges_ShouldReturnEmptyList_WhenEdgeCategoryNameDoesNotMatch() + { + // Arrange + SeedDatabase(); + var sourceNodes = new List { _sourceNode1 }; + var targetNodes = new List { _targetNode1 }; + var clauses = new Dictionary { { "Relationship", "Friend" } }; + + // Act + var result = await _sut.GetValidEdges(sourceNodes, targetNodes, "Category2", clauses); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task GetValidEdges_ShouldReturnCorrectEdges_WhenMultipleClausesMatch() + { + // Arrange + SeedDatabase(); + var sourceNodes = new List { _sourceNode1 }; + var targetNodes = new List { _targetNode1 }; + var clauses = new Dictionary + { + { "Relationship", "Friend" } + }; + + // Act + var result = await _sut.GetValidEdges(sourceNodes, targetNodes, "Category1", clauses); + + // Assert + Assert.Single(result); + Assert.Contains(result, edge => edge.EdgeValues.Any(ev => ev.ValueData == "Friend")); + } + } +} diff --git a/RelationshipAnalysis.Test/Services/GraphServices/Graph/GraphSearcherServiceTests.cs b/RelationshipAnalysis.Test/Services/GraphServices/Graph/GraphSearcherServiceTests.cs new file mode 100644 index 0000000..940d85c --- /dev/null +++ b/RelationshipAnalysis.Test/Services/GraphServices/Graph/GraphSearcherServiceTests.cs @@ -0,0 +1,199 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using RelationshipAnalysis.Context; +using RelationshipAnalysis.Dto; +using RelationshipAnalysis.Dto.Graph; +using RelationshipAnalysis.Enums; +using RelationshipAnalysis.Services.GraphServices.Graph; +using Moq; +using RelationshipAnalysis.Dto.Graph.Edge; +using RelationshipAnalysis.Dto.Graph.Node; +using RelationshipAnalysis.Services.GraphServices.Abstraction; +using RelationshipAnalysis.Services.GraphServices.Edge.Abstraction; +using RelationshipAnalysis.Services.GraphServices.Graph.Abstraction; + +namespace RelationshipAnalysis.Test.Services.GraphServices.Graph +{ + public class GraphSearcherServiceTests + { + private readonly IServiceProvider _serviceProvider; + private readonly Mock _mockGraphDtoCreator; + private readonly Mock _mockNodeCategoryReceiver; + private readonly Mock _mockEdgeCategoryReceiver; + private readonly Mock _mockNodeValidator; + private readonly Mock _mockEdgeValidator; + private readonly Mock _mockClauseValidatorService; + private readonly GraphSearcherService _sut; + + public GraphSearcherServiceTests() + { + var serviceCollection = new ServiceCollection(); + + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .Options; + + serviceCollection.AddScoped(_ => new ApplicationDbContext(options)); + + _serviceProvider = serviceCollection.BuildServiceProvider(); + + _mockGraphDtoCreator = new Mock(); + _mockNodeCategoryReceiver = new Mock(); + _mockEdgeCategoryReceiver = new Mock(); + _mockNodeValidator = new Mock(); + _mockEdgeValidator = new Mock(); + _mockClauseValidatorService = new Mock(); + + _sut = new GraphSearcherService( + _mockGraphDtoCreator.Object, + _mockNodeCategoryReceiver.Object, + _mockEdgeCategoryReceiver.Object, + _mockNodeValidator.Object, + _mockEdgeValidator.Object, + _mockClauseValidatorService.Object + ); + } + + [Fact] + public async Task Search_ShouldReturnValidationError_WhenClauseValidationFails() + { + // Arrange + var searchGraphDto = new SearchGraphDto + { + SourceCategoryName = "Category1", + TargetCategoryName = "Category2", + EdgeCategoryName = "EdgeCategory" + }; + + _mockClauseValidatorService + .Setup(s => s.AreClausesValid(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny>())) + .ReturnsAsync(new ActionResponse { StatusCode = StatusCodeType.BadRequest }); + + // Act + var result = await _sut.Search(searchGraphDto); + + // Assert + Assert.Equal(StatusCodeType.BadRequest, result.StatusCode); + } + + [Fact] + public async Task Search_ShouldReturnEmptyGraph_WhenNoValidNodesOrEdges() + { + // Arrange + + _mockGraphDtoCreator + .Setup(gdc => gdc.CreateResultGraphDto(It.IsAny>(), It.IsAny>())) + .Returns(new GraphDto + { + Nodes = new List(), + Edges = new List() + }); + + var searchGraphDto = new SearchGraphDto + { + SourceCategoryName = "Category1", + TargetCategoryName = "Category2", + EdgeCategoryName = "EdgeCategory", + EdgeCategoryClauses = new Dictionary(), + SourceCategoryClauses = new Dictionary(), + TargetCategoryClauses = new Dictionary() + }; + + _mockClauseValidatorService + .Setup(s => s.AreClausesValid(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny>())) + .ReturnsAsync(new ActionResponse { StatusCode = StatusCodeType.Success }); + + _mockNodeValidator + .Setup(nv => nv.GetValidNodes(It.IsAny>(), It.IsAny())) + .ReturnsAsync(new List()); + + _mockEdgeValidator + .Setup(ev => ev.GetValidEdges(It.IsAny>(), It.IsAny>(), It.IsAny(), It.IsAny>())) + .ReturnsAsync(new List()); + + // Act + var result = await _sut.Search(searchGraphDto); + + // Assert + Assert.NotNull(result.Data); + Assert.Empty(result.Data.Nodes); + Assert.Empty(result.Data.Edges); + } + + [Fact] + public async Task Search_ShouldReturnValidGraph_WhenValidNodesAndEdgesAreFound() + { + // Arrange + var searchGraphDto = new SearchGraphDto + { + SourceCategoryName = "Category1", + TargetCategoryName = "Category2", + EdgeCategoryName = "EdgeCategory", + EdgeCategoryClauses = new Dictionary(), + SourceCategoryClauses = new Dictionary(), + TargetCategoryClauses = new Dictionary() + }; + + var sourceNodes = new List + { + new Models.Graph.Node.Node { NodeId = 1, NodeCategoryId = 1, NodeUniqueString = "Node1" } + }; + + var targetNodes = new List + { + new Models.Graph.Node.Node { NodeId = 2, NodeCategoryId = 2, NodeUniqueString = "Node2" } + }; + + var edges = new List + { + new Models.Graph.Edge.Edge { EdgeUniqueString = "Edge1" } + }; + + _mockClauseValidatorService + .Setup(s => s.AreClausesValid(It.IsAny(), It.IsAny>(), It.IsAny>(), It.IsAny>())) + .ReturnsAsync(new ActionResponse { StatusCode = StatusCodeType.Success }); + + _mockNodeValidator + .Setup(nv => nv.GetValidNodes(It.IsAny>(), It.IsAny())) + .ReturnsAsync(sourceNodes); + + _mockEdgeValidator + .Setup(ev => ev.GetValidEdges(sourceNodes, targetNodes, "EdgeCategory", It.IsAny>())) + .ReturnsAsync(edges); + + _mockGraphDtoCreator + .Setup(gdc => gdc.CreateResultGraphDto(It.IsAny>(), It.IsAny>())) + .Returns(new GraphDto + { + Nodes = new List() + { + new NodeDto() + { + id = "1", label = "Category1/Node1" + }, + new NodeDto() + { + id = "2", label = "Category2/Node2" + } + }, + Edges = new List() + { + new EdgeDto() + { + id = "1", + source = "1", + target = "2" + } + } + }); + + // Act + var result = await _sut.Search(searchGraphDto); + + // Assert + Assert.Equal(StatusCodeType.Success, result.StatusCode); + Assert.Equal(2, result.Data.Nodes.Count); + Assert.Single(result.Data.Edges); + } + } +} diff --git a/RelationshipAnalysis.Test/Services/GraphServices/Node/NodeSearchValidatorTests.cs b/RelationshipAnalysis.Test/Services/GraphServices/Node/NodeSearchValidatorTests.cs new file mode 100644 index 0000000..3c5c7f5 --- /dev/null +++ b/RelationshipAnalysis.Test/Services/GraphServices/Node/NodeSearchValidatorTests.cs @@ -0,0 +1,127 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using RelationshipAnalysis.Context; +using RelationshipAnalysis.Models.Graph.Node; +using RelationshipAnalysis.Services.GraphServices.Node; +using Xunit; + +namespace RelationshipAnalysis.Test.Services.GraphServices.Node +{ + public class NodeSearchValidatorTests + { + private readonly IServiceProvider _serviceProvider; + private readonly NodeSearchValidator _sut; + + private readonly NodeCategory _nodeCategory1 = new() { NodeCategoryId = 1, NodeCategoryName = "Category1" }; + private readonly NodeCategory _nodeCategory2 = new() { NodeCategoryId = 2, NodeCategoryName = "Category2" }; + + private readonly Models.Graph.Node.Node _node1 = new() + { + NodeCategoryId = 1, + NodeUniqueString = "1", + Values = new List + { + new() { NodeAttribute = new NodeAttribute { NodeAttributeName = "Name" }, ValueData = "Alice" }, + new() { NodeAttribute = new NodeAttribute { NodeAttributeName = "Age" }, ValueData = "25" } + } + }; + + private readonly Models.Graph.Node.Node _node2 = new() + { + NodeCategoryId = 2, + NodeUniqueString = "2", + Values = new List + { + new() { NodeAttribute = new NodeAttribute { NodeAttributeName = "Name" }, ValueData = "Bob" }, + new() { NodeAttribute = new NodeAttribute { NodeAttributeName = "Age" }, ValueData = "30" } + } + }; + + public NodeSearchValidatorTests() + { + var serviceCollection = new ServiceCollection(); + + var options = new DbContextOptionsBuilder() + .UseInMemoryDatabase(Guid.NewGuid().ToString()) + .UseLazyLoadingProxies() + .Options; + + serviceCollection.AddScoped(_ => new ApplicationDbContext(options)); + + _serviceProvider = serviceCollection.BuildServiceProvider(); + + _sut = new NodeSearchValidator(_serviceProvider); + } + + private void SeedDatabase() + { + using var scope = _serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + context.NodeCategories.AddRange(_nodeCategory1, _nodeCategory2); + context.Nodes.AddRange(_node1, _node2); + context.SaveChanges(); + } + + [Fact] + public async Task GetValidNodes_ShouldReturnEmptyList_WhenNoNodesMatch() + { + // Arrange + SeedDatabase(); + var clauses = new Dictionary { { "Name", "Charlie" } }; + + // Act + var result = await _sut.GetValidNodes(clauses, "Category1"); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task GetValidNodes_ShouldReturnNodes_WhenClauseMatches() + { + // Arrange + SeedDatabase(); + var clauses = new Dictionary { { "Name", "Alice" } }; // "Alice" exists in Category1 + + // Act + var result = await _sut.GetValidNodes(clauses, "Category1"); + + // Assert + Assert.Single(result); + Assert.Contains(result, node => node.Values.Any(v => v.ValueData == "Alice")); + } + + [Fact] + public async Task GetValidNodes_ShouldReturnEmptyList_WhenCategoryNameDoesNotMatch() + { + // Arrange + SeedDatabase(); + var clauses = new Dictionary { { "Name", "Alice" } }; + + // Act + var result = await _sut.GetValidNodes(clauses, "Category2"); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task GetValidNodes_ShouldReturnCorrectNodes_WhenMultipleClausesMatch() + { + // Arrange + SeedDatabase(); + var clauses = new Dictionary + { + { "Name", "Alice" }, + { "Age", "25" } + }; + + // Act + var result = await _sut.GetValidNodes(clauses, "Category1"); + + // Assert + Assert.Single(result); + Assert.Contains(result, node => node.Values.Any(v => v.ValueData == "Alice") && node.Values.Any(v => v.ValueData == "25")); + } + } +} diff --git a/RelationshipAnalysis/Program.cs b/RelationshipAnalysis/Program.cs index 67d8dda..27cd9bb 100644 --- a/RelationshipAnalysis/Program.cs +++ b/RelationshipAnalysis/Program.cs @@ -116,6 +116,9 @@ .AddSingleton() .AddSingleton() .AddSingleton() + .AddSingleton() + .AddSingleton() + .AddSingleton() .AddKeyedSingleton("node") .AddKeyedSingleton("edge") .AddKeyedSingleton("node") diff --git a/RelationshipAnalysis/Resources.Designer.cs b/RelationshipAnalysis/Resources.Designer.cs index a73de16..2038af7 100644 --- a/RelationshipAnalysis/Resources.Designer.cs +++ b/RelationshipAnalysis/Resources.Designer.cs @@ -14,18 +14,18 @@ namespace RelationshipAnalysis { [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [System.Diagnostics.DebuggerNonUserCodeAttribute()] [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { + public class Resources { private static System.Resources.ResourceManager resourceMan; private static System.Globalization.CultureInfo resourceCulture; [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { + public Resources() { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Resources.ResourceManager ResourceManager { + public static System.Resources.ResourceManager ResourceManager { get { if (object.Equals(null, resourceMan)) { System.Resources.ResourceManager temp = new System.Resources.ResourceManager("RelationshipAnalysis.Resources", typeof(Resources).Assembly); @@ -36,7 +36,7 @@ internal static System.Resources.ResourceManager ResourceManager { } [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] - internal static System.Globalization.CultureInfo Culture { + public static System.Globalization.CultureInfo Culture { get { return resourceCulture; } @@ -45,211 +45,211 @@ internal static System.Globalization.CultureInfo Culture { } } - internal static string InvalidPasswordMessage { + public static string InvalidPasswordMessage { get { return ResourceManager.GetString("InvalidPasswordMessage", resourceCulture); } } - internal static string InvalidClauseInSourceCategory { + public static string InvalidClauseInSourceCategory { get { return ResourceManager.GetString("InvalidClauseInSourceCategory", resourceCulture); } } - internal static string InvalidClauseInDestinationCategory { + public static string InvalidClauseInDestinationCategory { get { return ResourceManager.GetString("InvalidClauseInDestinationCategory", resourceCulture); } } - internal static string InvalidClauseInEdgeCategory { + public static string InvalidClauseInEdgeCategory { get { return ResourceManager.GetString("InvalidClauseInEdgeCategory", resourceCulture); } } - internal static string SuccessfulLogoutMessage { + public static string SuccessfulLogoutMessage { get { return ResourceManager.GetString("SuccessfulLogoutMessage", resourceCulture); } } - internal static string UserNotFoundMessage { + public static string UserNotFoundMessage { get { return ResourceManager.GetString("UserNotFoundMessage", resourceCulture); } } - internal static string WrongOldPasswordMessage { + public static string WrongOldPasswordMessage { get { return ResourceManager.GetString("WrongOldPasswordMessage", resourceCulture); } } - internal static string NoFileUploadedMessage { + public static string NoFileUploadedMessage { get { return ResourceManager.GetString("NoFileUploadedMessage", resourceCulture); } } - internal static string SuccessfulNodeAdditionMessage { + public static string SuccessfulNodeAdditionMessage { get { return ResourceManager.GetString("SuccessfulNodeAdditionMessage", resourceCulture); } } - internal static string TwoSameHeadersMessage { + public static string TwoSameHeadersMessage { get { return ResourceManager.GetString("TwoSameHeadersMessage", resourceCulture); } } - internal static string InvalidNodeCategory { + public static string InvalidNodeCategory { get { return ResourceManager.GetString("InvalidNodeCategory", resourceCulture); } } - internal static string InvalidHeaderAttribute { + public static string InvalidHeaderAttribute { get { return ResourceManager.GetString("InvalidHeaderAttribute", resourceCulture); } } - internal static string FailedAddRecordsMessage { + public static string FailedAddRecordsMessage { get { return ResourceManager.GetString("FailedAddRecordsMessage", resourceCulture); } } - internal static string ValidFileMessage { + public static string ValidFileMessage { get { return ResourceManager.GetString("ValidFileMessage", resourceCulture); } } - internal static string OldPasswordRequired { + public static string OldPasswordRequired { get { return ResourceManager.GetString("OldPasswordRequired", resourceCulture); } } - internal static string NewPasswordRequired { + public static string NewPasswordRequired { get { return ResourceManager.GetString("NewPasswordRequired", resourceCulture); } } - internal static string UsernameRequired { + public static string UsernameRequired { get { return ResourceManager.GetString("UsernameRequired", resourceCulture); } } - internal static string PasswordRequired { + public static string PasswordRequired { get { return ResourceManager.GetString("PasswordRequired", resourceCulture); } } - internal static string SuccessfulUpdateUserMessage { + public static string SuccessfulUpdateUserMessage { get { return ResourceManager.GetString("SuccessfulUpdateUserMessage", resourceCulture); } } - internal static string SuccessfulDeleteUserMessage { + public static string SuccessfulDeleteUserMessage { get { return ResourceManager.GetString("SuccessfulDeleteUserMessage", resourceCulture); } } - internal static string UsernameExistsMessage { + public static string UsernameExistsMessage { get { return ResourceManager.GetString("UsernameExistsMessage", resourceCulture); } } - internal static string EmailExistsMessage { + public static string EmailExistsMessage { get { return ResourceManager.GetString("EmailExistsMessage", resourceCulture); } } - internal static string SucceddfulCreateUser { + public static string SucceddfulCreateUser { get { return ResourceManager.GetString("SucceddfulCreateUser", resourceCulture); } } - internal static string EmptyRolesMessage { + public static string EmptyRolesMessage { get { return ResourceManager.GetString("EmptyRolesMessage", resourceCulture); } } - internal static string InvalidRolesListMessage { + public static string InvalidRolesListMessage { get { return ResourceManager.GetString("InvalidRolesListMessage", resourceCulture); } } - internal static string SuccessfulUpdateRolesMessage { + public static string SuccessfulUpdateRolesMessage { get { return ResourceManager.GetString("SuccessfulUpdateRolesMessage", resourceCulture); } } - internal static string LoginFailedMessage { + public static string LoginFailedMessage { get { return ResourceManager.GetString("LoginFailedMessage", resourceCulture); } } - internal static string SuccessfulLoginMessage { + public static string SuccessfulLoginMessage { get { return ResourceManager.GetString("SuccessfulLoginMessage", resourceCulture); } } - internal static string DeleteAccountAccessErrorMessage { + public static string DeleteAccountAccessErrorMessage { get { return ResourceManager.GetString("DeleteAccountAccessErrorMessage", resourceCulture); } } - internal static string NullDtoErrorMessage { + public static string NullDtoErrorMessage { get { return ResourceManager.GetString("NullDtoErrorMessage", resourceCulture); } } - internal static string NotUniqueCategoryNameErrorMessage { + public static string NotUniqueCategoryNameErrorMessage { get { return ResourceManager.GetString("NotUniqueCategoryNameErrorMessage", resourceCulture); } } - internal static string SuccessfulCreateCategory { + public static string SuccessfulCreateCategory { get { return ResourceManager.GetString("SuccessfulCreateCategory", resourceCulture); } } - internal static string SuccessfulEdgeAdditionMessage { + public static string SuccessfulEdgeAdditionMessage { get { return ResourceManager.GetString("SuccessfulEdgeAdditionMessage", resourceCulture); } } - internal static string InvalidEdgeCategory { + public static string InvalidEdgeCategory { get { return ResourceManager.GetString("InvalidEdgeCategory", resourceCulture); } } - internal static string InvalidSourceNodeCategory { + public static string InvalidSourceNodeCategory { get { return ResourceManager.GetString("InvalidSourceNodeCategory", resourceCulture); } } - internal static string InvalidTargetNodeCategory { + public static string InvalidTargetNodeCategory { get { return ResourceManager.GetString("InvalidTargetNodeCategory", resourceCulture); } diff --git a/RelationshipAnalysis/Services/GraphServices/Abstraction/IClauseValidatorService.cs b/RelationshipAnalysis/Services/GraphServices/Abstraction/IClauseValidatorService.cs new file mode 100644 index 0000000..1f46dd2 --- /dev/null +++ b/RelationshipAnalysis/Services/GraphServices/Abstraction/IClauseValidatorService.cs @@ -0,0 +1,13 @@ +using RelationshipAnalysis.Dto; +using RelationshipAnalysis.Dto.Graph; + +namespace RelationshipAnalysis.Services.GraphServices.Abstraction; + +public interface IClauseValidatorService +{ + Task> AreClausesValid( + SearchGraphDto searchGraphDto, + List sourceAttributes, + List targetAttributes, + List edgeAttributes); +} \ No newline at end of file diff --git a/RelationshipAnalysis/Services/GraphServices/ClauseValidatorService.cs b/RelationshipAnalysis/Services/GraphServices/ClauseValidatorService.cs new file mode 100644 index 0000000..1996173 --- /dev/null +++ b/RelationshipAnalysis/Services/GraphServices/ClauseValidatorService.cs @@ -0,0 +1,49 @@ +using RelationshipAnalysis.Dto; +using RelationshipAnalysis.Dto.Graph; +using RelationshipAnalysis.Enums; +using RelationshipAnalysis.Services.GraphServices.Abstraction; + +namespace RelationshipAnalysis.Services.GraphServices +{ + public class ClauseValidatorService : IClauseValidatorService + { + public async Task> AreClausesValid( + SearchGraphDto searchGraphDto, + List sourceAttributes, + List targetAttributes, + List edgeAttributes) + { + if (!searchGraphDto.SourceCategoryClauses.Keys.All(item => sourceAttributes.Contains(item))) + return NotFoundResult(Resources.InvalidClauseInSourceCategory); + + if (!searchGraphDto.TargetCategoryClauses.Keys.All(item => targetAttributes.Contains(item))) + return NotFoundResult(Resources.InvalidClauseInDestinationCategory); + + if (!searchGraphDto.EdgeCategoryClauses.Keys.All(item => edgeAttributes.Contains(item))) + return NotFoundResult(Resources.InvalidClauseInDestinationCategory); + + return SuccessResult(); + } + + private ActionResponse SuccessResult() + { + return new ActionResponse + { + StatusCode = StatusCodeType.Success, + Data = null + }; + } + + private ActionResponse NotFoundResult(string message) + { + return new ActionResponse + { + StatusCode = StatusCodeType.NotFound, + Data = new GraphDto + { + Message = message + } + }; + } + } +} \ No newline at end of file diff --git a/RelationshipAnalysis/Services/GraphServices/Edge/Abstraction/ISearchEdgeValidator.cs b/RelationshipAnalysis/Services/GraphServices/Edge/Abstraction/ISearchEdgeValidator.cs new file mode 100644 index 0000000..1756d52 --- /dev/null +++ b/RelationshipAnalysis/Services/GraphServices/Edge/Abstraction/ISearchEdgeValidator.cs @@ -0,0 +1,8 @@ +namespace RelationshipAnalysis.Services.GraphServices.Edge.Abstraction; + +public interface ISearchEdgeValidator +{ + Task> GetValidEdges(List sourceNodes, + List targetNodes, + string edgeCategory, Dictionary clauses); +} \ No newline at end of file diff --git a/RelationshipAnalysis/Services/GraphServices/Edge/EdgeSearchValidator.cs b/RelationshipAnalysis/Services/GraphServices/Edge/EdgeSearchValidator.cs new file mode 100644 index 0000000..125f7fa --- /dev/null +++ b/RelationshipAnalysis/Services/GraphServices/Edge/EdgeSearchValidator.cs @@ -0,0 +1,45 @@ +using Microsoft.EntityFrameworkCore; +using RelationshipAnalysis.Context; +using RelationshipAnalysis.Services.GraphServices.Abstraction; +using RelationshipAnalysis.Services.GraphServices.Edge.Abstraction; + +namespace RelationshipAnalysis.Services.GraphServices.Edge; + +public class EdgeSearchValidator(IServiceProvider serviceProvider) : ISearchEdgeValidator +{ + public async Task> GetValidEdges(List sourceNodes, + List targetNodes, + string edgeCategory, Dictionary clauses) + { + var sourceNodeIds = sourceNodes.Select(n => n.NodeId).ToList(); + var targetNodeIds = targetNodes.Select(n => n.NodeId).ToList(); + + using var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + + var edges = await context.Edges + .Include(e => e.EdgeValues) + .ThenInclude(ev => ev.EdgeAttribute) + .Where(e => edgeCategory == e.EdgeCategory.EdgeCategoryName && + sourceNodeIds.Contains(e.EdgeSourceNodeId) && + targetNodeIds.Contains(e.EdgeDestinationNodeId)) + .ToListAsync(); + + var validEdgesByClauses = edges.Where(e => AreEdgeAttributesValid(e, clauses)).ToList(); + return validEdgesByClauses; + } + + private bool AreEdgeAttributesValid(Models.Graph.Edge.Edge edge, Dictionary clauses) + { + var attributeValues = new Dictionary(); + edge.EdgeValues.ToList().ForEach(ev => attributeValues.Add(ev.EdgeAttribute.EdgeAttributeName, ev.ValueData)); + + foreach (var kvp in clauses) + { + var actualValue = attributeValues[kvp.Key]; + if (!actualValue.StartsWith(kvp.Value)) return false; + } + + return true; + } +} \ No newline at end of file diff --git a/RelationshipAnalysis/Services/GraphServices/Graph/GraphSearcherService.cs b/RelationshipAnalysis/Services/GraphServices/Graph/GraphSearcherService.cs index 90b36a6..95dce57 100644 --- a/RelationshipAnalysis/Services/GraphServices/Graph/GraphSearcherService.cs +++ b/RelationshipAnalysis/Services/GraphServices/Graph/GraphSearcherService.cs @@ -3,129 +3,39 @@ using RelationshipAnalysis.Dto.Graph; using RelationshipAnalysis.Enums; using RelationshipAnalysis.Services.GraphServices.Abstraction; +using RelationshipAnalysis.Services.GraphServices.Edge.Abstraction; using RelationshipAnalysis.Services.GraphServices.Graph.Abstraction; using ApplicationDbContext = RelationshipAnalysis.Context.ApplicationDbContext; -namespace RelationshipAnalysis.Services.GraphServices.Graph; - -public class GraphSearcherService( - IGraphDtoCreator graphDtoCreator, - IServiceProvider serviceProvider, - [FromKeyedServices("node")] IAttributesReceiver nodeCategoryReceiver, - [FromKeyedServices("edge")] IAttributesReceiver edgeCategoryReceiver) : IGraphSearcherService +namespace RelationshipAnalysis.Services.GraphServices.Graph { - public async Task> Search(SearchGraphDto searchGraphDto) + public class GraphSearcherService( + IGraphDtoCreator graphDtoCreator, + [FromKeyedServices("node")] IAttributesReceiver nodeCategoryReceiver, + [FromKeyedServices("edge")] IAttributesReceiver edgeCategoryReceiver, + ISearchNodeValidator nodeValidator, + ISearchEdgeValidator edgeValidator, + IClauseValidatorService clauseValidatorService) : IGraphSearcherService { - using var scope = serviceProvider.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - - - var sourceAttributes = await nodeCategoryReceiver.GetAllAttributes(searchGraphDto.SourceCategoryName); - var targetAttributes = await nodeCategoryReceiver.GetAllAttributes(searchGraphDto.TargetCategoryName); - var edgeAttributes = await edgeCategoryReceiver.GetAllAttributes(searchGraphDto.EdgeCategoryName); - - var validation = await AreClausesValid(searchGraphDto, sourceAttributes, targetAttributes, edgeAttributes); - if (validation.StatusCode != StatusCodeType.Success) return validation; - - var sourceNodes = await context.Nodes - .Where(n => searchGraphDto.SourceCategoryName == n.NodeCategory.NodeCategoryName).ToListAsync(); - var targetNodes = await context.Nodes - .Where(n => searchGraphDto.TargetCategoryName == n.NodeCategory.NodeCategoryName).ToListAsync(); - - sourceNodes = sourceNodes.Where(sn => IsNodeValid(sn, searchGraphDto.SourceCategoryClauses)).ToList(); - targetNodes = targetNodes.Where(tn => IsNodeValid(tn, searchGraphDto.TargetCategoryClauses)).ToList(); - - var edges = await GetValidEdges(sourceNodes, targetNodes, searchGraphDto.SourceCategoryName, - searchGraphDto.TargetCategoryName, searchGraphDto.EdgeCategoryName); - - edges = edges.Where(e => IsEdgeValid(e, searchGraphDto.EdgeCategoryClauses)).ToList(); - - validation.Data = graphDtoCreator.CreateResultGraphDto(sourceNodes.Union(targetNodes).ToList(), edges); - return validation; - } - - private bool IsNodeValid(Models.Graph.Node.Node node, Dictionary clauses) - { - var attributeValues = new Dictionary(); - node.Values.ToList().ForEach(nv => attributeValues.Add(nv.NodeAttribute.NodeAttributeName, nv.ValueData)); - - foreach (var kvp in clauses) + public async Task> Search(SearchGraphDto searchGraphDto) { - var actualValue = attributeValues[kvp.Key]; - if (!actualValue.StartsWith(kvp.Value)) return false; - } - return true; - } + var sourceAttributes = await nodeCategoryReceiver.GetAllAttributes(searchGraphDto.SourceCategoryName); + var targetAttributes = await nodeCategoryReceiver.GetAllAttributes(searchGraphDto.TargetCategoryName); + var edgeAttributes = await edgeCategoryReceiver.GetAllAttributes(searchGraphDto.EdgeCategoryName); - private bool IsEdgeValid(Models.Graph.Edge.Edge edge, Dictionary clauses) - { - var attributeValues = new Dictionary(); - edge.EdgeValues.ToList().ForEach(ev => attributeValues.Add(ev.EdgeAttribute.EdgeAttributeName, ev.ValueData)); + var validation = await clauseValidatorService.AreClausesValid(searchGraphDto, sourceAttributes, targetAttributes, edgeAttributes); + if (validation.StatusCode != StatusCodeType.Success) return validation; - foreach (var kvp in clauses) - { - var actualValue = attributeValues[kvp.Key]; - if (!actualValue.StartsWith(kvp.Value)) return false; - } + var sourceNodes = await nodeValidator.GetValidNodes(searchGraphDto.SourceCategoryClauses, searchGraphDto.SourceCategoryName); + var targetNodes = await nodeValidator.GetValidNodes(searchGraphDto.TargetCategoryClauses, searchGraphDto.TargetCategoryName); - return true; - } - - private async Task> GetValidEdges(List sourceNodes, - List targetNodes, string sourceCategory, string targetCategory, - string edgeCategory) - { - var sourceNodeIds = sourceNodes.Select(n => n.NodeId).ToList(); - var targetNodeIds = targetNodes.Select(n => n.NodeId).ToList(); - - using var scope = serviceProvider.CreateScope(); - var context = scope.ServiceProvider.GetRequiredService(); - - var edges = await context.Edges.Include(e => e.EdgeValues) - .ThenInclude(ev => ev.EdgeAttribute) - .Where(e => edgeCategory == e.EdgeCategory.EdgeCategoryName && - sourceNodeIds.Contains(e.EdgeSourceNodeId) && - targetNodeIds.Contains(e.EdgeDestinationNodeId)) - .ToListAsync(); - return edges; - } - - private async Task> AreClausesValid(SearchGraphDto searchGraphDto, - List sourceAttributes, List targetAttributes, - List edgeAttributes) - { - if (!searchGraphDto.SourceCategoryClauses.Keys.All(item => sourceAttributes.Contains(item))) - return NotFoundResult(Resources.InvalidClauseInSourceCategory); - - if (!searchGraphDto.TargetCategoryClauses.Keys.All(item => targetAttributes.Contains(item))) - return NotFoundResult(Resources.InvalidClauseInDestinationCategory); - - if (!searchGraphDto.EdgeCategoryClauses.Keys.All(item => edgeAttributes.Contains(item))) - return NotFoundResult(Resources.InvalidClauseInDestinationCategory); - - return SuccessResult(); - } - - private ActionResponse SuccessResult() - { - return new ActionResponse - { - StatusCode = StatusCodeType.Success, - Data = null - }; - } + var edges = await edgeValidator.GetValidEdges(sourceNodes, targetNodes, searchGraphDto.EdgeCategoryName, searchGraphDto.EdgeCategoryClauses); + + validation.Data = graphDtoCreator.CreateResultGraphDto(sourceNodes.UnionBy(targetNodes, node => node.NodeId).ToList(), edges); + return validation; + } - private ActionResponse NotFoundResult(string message) - { - return new ActionResponse - { - StatusCode = StatusCodeType.NotFound, - Data = new GraphDto - { - Message = message - } - }; } -} \ No newline at end of file +} diff --git a/RelationshipAnalysis/Services/GraphServices/Node/Abstraction/ISearchNodeValidator.cs b/RelationshipAnalysis/Services/GraphServices/Node/Abstraction/ISearchNodeValidator.cs new file mode 100644 index 0000000..a4cc4ce --- /dev/null +++ b/RelationshipAnalysis/Services/GraphServices/Node/Abstraction/ISearchNodeValidator.cs @@ -0,0 +1,6 @@ +namespace RelationshipAnalysis.Services.GraphServices.Abstraction; + +public interface ISearchNodeValidator +{ + Task> GetValidNodes(Dictionary clauses, string categoryName); +} \ No newline at end of file diff --git a/RelationshipAnalysis/Services/GraphServices/Node/NodeSearchValidator.cs b/RelationshipAnalysis/Services/GraphServices/Node/NodeSearchValidator.cs new file mode 100644 index 0000000..4d9a419 --- /dev/null +++ b/RelationshipAnalysis/Services/GraphServices/Node/NodeSearchValidator.cs @@ -0,0 +1,33 @@ +using Microsoft.EntityFrameworkCore; +using RelationshipAnalysis.Context; +using RelationshipAnalysis.Services.GraphServices.Abstraction; + +namespace RelationshipAnalysis.Services.GraphServices.Node; + +public class NodeSearchValidator(IServiceProvider serviceProvider) : ISearchNodeValidator +{ + public async Task> GetValidNodes(Dictionary clauses, string categoryName) + { + using var scope = serviceProvider.CreateScope(); + var context = scope.ServiceProvider.GetRequiredService(); + var allNodes = await context.Nodes.ToListAsync(); + var validNodes = allNodes.Where(sn => IsNodeValid(sn, clauses, categoryName)).ToList(); + return validNodes; + } + + private bool IsNodeValid(Models.Graph.Node.Node node, Dictionary clauses, string categoryName) + { + if (node.NodeCategory.NodeCategoryName != categoryName) return false; + + var attributeValues = new Dictionary(); + node.Values.ToList().ForEach(nv => attributeValues.Add(nv.NodeAttribute.NodeAttributeName, nv.ValueData)); + + foreach (var kvp in clauses) + { + if (!attributeValues.TryGetValue(kvp.Key, out var actualValue) || !actualValue.StartsWith(kvp.Value)) + return false; + } + + return true; + } +} \ No newline at end of file