From 770b6e8c7075b6426dff8ee91df6dddc609f73df Mon Sep 17 00:00:00 2001 From: Simon Pinn Date: Mon, 20 Oct 2025 13:13:39 +1100 Subject: [PATCH 1/4] Add match tests --- .../IntegrationTest.cs | 9 +- ...o4jClient.Extension.IntegrationTest.csproj | 1 + .../Tests/CreateTests.cs | 7 +- .../Tests/MatchTests.cs | 213 ++++++++++++++++++ .../Tests/MergeTests.cs | 7 +- .../Neo/NeoConfig.cs | 7 + 6 files changed, 227 insertions(+), 17 deletions(-) create mode 100644 test/Neo4jClient.Extension.IntegrationTest/Tests/MatchTests.cs diff --git a/test/Neo4jClient.Extension.IntegrationTest/IntegrationTest.cs b/test/Neo4jClient.Extension.IntegrationTest/IntegrationTest.cs index 2af1c9f..54c01b6 100644 --- a/test/Neo4jClient.Extension.IntegrationTest/IntegrationTest.cs +++ b/test/Neo4jClient.Extension.IntegrationTest/IntegrationTest.cs @@ -1,13 +1,10 @@ using System; -using System.Collections.Generic; using System.Configuration; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Neo4jClient.Cypher; using Neo4jClient.Extension.Test.CustomConverters; using Neo4jClient.Extension.Test.Data; -using Neo4jClient.Transactions; +using Newtonsoft.Json.Serialization; using NUnit.Framework; namespace Neo4jClient.Extension.Test.Integration @@ -38,9 +35,11 @@ static IntegrationTest() var connectionString = ConfigurationManager.AppSettings["Neo4jConnectionString"] ?? "bolt://localhost:7687"; var username = ConfigurationManager.AppSettings["Neo4jUsername"] ?? "neo4j"; var password = ConfigurationManager.AppSettings["Neo4jPassword"] ?? "testpassword"; - + GraphClient = new BoltGraphClient(new Uri(connectionString), username, password); + // Use CamelCasePropertyNamesContractResolver for consistent property naming + GraphClient.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); GraphClient.JsonConverters.Add(new AreaJsonConverter()); ((BoltGraphClient)GraphClient).ConnectAsync().Wait(); diff --git a/test/Neo4jClient.Extension.IntegrationTest/Neo4jClient.Extension.IntegrationTest.csproj b/test/Neo4jClient.Extension.IntegrationTest/Neo4jClient.Extension.IntegrationTest.csproj index 4f07e3c..65b2e5d 100644 --- a/test/Neo4jClient.Extension.IntegrationTest/Neo4jClient.Extension.IntegrationTest.csproj +++ b/test/Neo4jClient.Extension.IntegrationTest/Neo4jClient.Extension.IntegrationTest.csproj @@ -10,6 +10,7 @@ + diff --git a/test/Neo4jClient.Extension.IntegrationTest/Tests/CreateTests.cs b/test/Neo4jClient.Extension.IntegrationTest/Tests/CreateTests.cs index 0c705fb..2916326 100644 --- a/test/Neo4jClient.Extension.IntegrationTest/Tests/CreateTests.cs +++ b/test/Neo4jClient.Extension.IntegrationTest/Tests/CreateTests.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Neo4jClient.Extension.Cypher; +using System.Threading.Tasks; using Neo4jClient.Extension.Test.Cypher; using NUnit.Framework; diff --git a/test/Neo4jClient.Extension.IntegrationTest/Tests/MatchTests.cs b/test/Neo4jClient.Extension.IntegrationTest/Tests/MatchTests.cs new file mode 100644 index 0000000..4f56bfa --- /dev/null +++ b/test/Neo4jClient.Extension.IntegrationTest/Tests/MatchTests.cs @@ -0,0 +1,213 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Neo4jClient.Extension.Cypher; +using Neo4jClient.Extension.Test.Cypher; +using Neo4jClient.Extension.Test.TestData.Entities; +using Neo4jClient.Extension.Test.TestEntities.Relationships; +using NUnit.Framework; +using UnitsNet; +using UnitsNet.Units; + +namespace Neo4jClient.Extension.Test.Integration.Tests +{ + public class MatchTests : IntegrationTest + { + [Test] + public async Task MatchEntity_ReturnsCreatedPerson() + { + // Arrange: Create a person using CreateEntity + var person = new Person + { + Id = 1, + Name = "James Bond", + Title = "Agent", + Sex = Gender.Male, + IsOperative = true, + SerialNumber = 7, + SpendingAuthorisation = 1000000, + DateCreated = DateTimeOffset.UtcNow + }; + + await CypherQuery + .CreateEntity(person, "p") + .ExecuteWithoutResultsAsync(); + + // Act: Match using MatchEntity + var matchPerson = new Person { Id = 1 }; + var result = await CypherQuery + .MatchEntity(matchPerson, "p") + .Return(p => p.As()) + .ResultsAsync; + + // Assert: Verify all properties were saved and retrieved correctly + var retrieved = result.Single(); + retrieved.Id.Should().Be(1); + retrieved.Name.Should().Be("James Bond"); + retrieved.Title.Should().Be("Agent"); + retrieved.Sex.Should().Be(Gender.Male); + retrieved.IsOperative.Should().BeTrue(); + retrieved.SerialNumber.Should().Be(7); + retrieved.SpendingAuthorisation.Should().Be(1000000); + } + + [Test] + public async Task MatchEntity_WithRelationship_ReturnsPersonAndAddress() + { + // Arrange: Create person with home address + var person = new Person + { + Id = 2, + Name = "Q", + Title = "Quartermaster", + DateCreated = DateTimeOffset.UtcNow + }; + + var address = new Address + { + Street = "MI6 Headquarters", + Suburb = "London" + }; + + var relationship = new HomeAddressRelationship("p", "a") + { + DateEffective = DateTimeOffset.UtcNow + }; + + await CypherQuery + .CreateEntity(person, "p") + .CreateEntity(address, "a") + .CreateRelationship(relationship) + .ExecuteWithoutResultsAsync(); + + // Act: Match person and follow relationship to address using MatchRelationship + var matchPerson = new Person { Id = 2 }; + var homeRelationship = new HomeAddressRelationship("p", "a"); + + var result = await CypherQuery + .MatchEntity(matchPerson, "p") + .MatchRelationship(homeRelationship, MatchRelationshipOptions.Create().WithNoProperties()) + .Return((p, a) => new + { + Person = p.As(), + Address = a.As
() + }) + .ResultsAsync; + + // Assert + var retrieved = result.Single(); + retrieved.Person.Id.Should().Be(2); + retrieved.Person.Name.Should().Be("Q"); + retrieved.Address.Street.Should().Be("MI6 Headquarters"); + retrieved.Address.Suburb.Should().Be("London"); + } + + [Test] + public async Task MatchEntity_MultipleResults_ReturnsAll() + { + // Arrange: Create multiple people with same title + var people = new[] + { + new Person { Id = 10, Name = "Agent 1", Title = "Field Agent", DateCreated = DateTimeOffset.UtcNow }, + new Person { Id = 11, Name = "Agent 2", Title = "Field Agent", DateCreated = DateTimeOffset.UtcNow }, + new Person { Id = 12, Name = "Agent 3", Title = "Field Agent", DateCreated = DateTimeOffset.UtcNow } + }; + + foreach (var p in people) + { + await CypherQuery + .CreateEntity(p, "p") + .ExecuteWithoutResultsAsync(); + } + + // Act: Match all people (using raw Match since we want all, not filtering by properties) + var results = await CypherQuery + .Match("(p:SecretAgent)") + .Return(p => p.As()) + .ResultsAsync; + + // Assert + results.Should().HaveCount(3); + results.Select(r => r.Name).Should().BeEquivalentTo(new[] { "Agent 1", "Agent 2", "Agent 3" }); + } + + [Test] + public async Task MatchEntity_NoResults_ReturnsEmpty() + { + // Act: Try to match a person that doesn't exist using MatchEntity + var matchPerson = new Person { Id = 999 }; + var results = await CypherQuery + .MatchEntity(matchPerson, "p") + .Return(p => p.As()) + .ResultsAsync; + + // Assert + results.Should().BeEmpty(); + } + + [Test] + public async Task OptionalMatchEntity_NoResults_ReturnsNull() + { + // Arrange: Create one person + var person = new Person + { + Id = 20, + Name = "Solo Agent", + DateCreated = DateTimeOffset.UtcNow + }; + + await CypherQuery + .CreateEntity(person, "p") + .ExecuteWithoutResultsAsync(); + + // Act: Match person and optionally match address (which doesn't exist) using OptionalMatchEntity + var matchPerson = new Person { Id = 20 }; + var result = await CypherQuery + .MatchEntity(matchPerson, "p") + .OptionalMatch("(p)-[:HOME_ADDRESS]->(a:Address)") + .Return((p, a) => new + { + Person = p.As(), + Address = a.As
() + }) + .ResultsAsync; + + // Assert + var retrieved = result.Single(); + retrieved.Person.Id.Should().Be(20); + retrieved.Person.Name.Should().Be("Solo Agent"); + retrieved.Address.Should().BeNull(); + } + + [Test] + public async Task MatchEntity_WithWeapon_ReturnsWeapon() + { + // Arrange: Create weapon + var weapon = new Weapon + { + Id = 1, + Name = "Walther PPK", + BlastRadius = new Area(12.4, AreaUnit.SquareKilometer) + }; + + await CypherQuery + .CreateEntity(weapon, "w") + .ExecuteWithoutResultsAsync(); + + // Act: Match the weapon by Id using MatchEntity + var matchWeapon = new Weapon { Id = 1 }; + var result = await CypherQuery + .MatchEntity(matchWeapon, "w") + .Return(w => w.As()) + .ResultsAsync; + + // Assert + var retrieved = result.Single(); + retrieved.Id.Should().Be(1); + retrieved.Name.Should().Be("Walther PPK"); + retrieved.BlastRadius.Should().NotBeNull(); + retrieved.BlastRadius.Value.SquareKilometers.Should().BeApproximately(12.4, 0.01); + } + } +} diff --git a/test/Neo4jClient.Extension.IntegrationTest/Tests/MergeTests.cs b/test/Neo4jClient.Extension.IntegrationTest/Tests/MergeTests.cs index a20014c..9e85e52 100644 --- a/test/Neo4jClient.Extension.IntegrationTest/Tests/MergeTests.cs +++ b/test/Neo4jClient.Extension.IntegrationTest/Tests/MergeTests.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Neo4jClient.Extension.Cypher; +using System.Threading.Tasks; using Neo4jClient.Extension.Test.Cypher; using NUnit.Framework; diff --git a/test/Neo4jClient.Extension.Test.Common/Neo/NeoConfig.cs b/test/Neo4jClient.Extension.Test.Common/Neo/NeoConfig.cs index 7aae177..7d4a10b 100644 --- a/test/Neo4jClient.Extension.Test.Common/Neo/NeoConfig.cs +++ b/test/Neo4jClient.Extension.Test.Common/Neo/NeoConfig.cs @@ -25,6 +25,10 @@ public static void ConfigureModel() FluentConfig.Config() .With
() + .Match(a => a.Street) + .Match(a => a.Suburb) + .Merge(a => a.Street) + .Merge(a => a.Suburb) .MergeOnMatchOrCreate(a => a.Street) .MergeOnMatchOrCreate(a => a.Suburb) .Set(); @@ -33,6 +37,9 @@ public static void ConfigureModel() .With() .Match(x => x.Id) .Merge(x => x.Id) + .MergeOnCreate(w => w.Id) + .MergeOnCreate(w => w.Name) + .MergeOnCreate(w => w.BlastRadius) .MergeOnMatchOrCreate(w => w.Name) .MergeOnMatchOrCreate(w => w.BlastRadius) .Set(); From 080ec5650185577a238dfc76d2ed0d6af28b06ea Mon Sep 17 00:00:00 2001 From: Simon Pinn Date: Mon, 20 Oct 2025 13:14:27 +1100 Subject: [PATCH 2/4] Remove unused --- .../Cypher/Extension/CypherExtension.CqlBuilders.cs | 5 +---- .../Cypher/Extension/CypherExtension.Dynamics.cs | 2 -- .../Cypher/Extension/CypherExtension.Entity.cs | 2 -- .../Cypher/Extension/CypherExtension.Fluent.cs | 3 --- .../Cypher/Extension/CypherExtension.Main.cs | 3 --- src/Neo4jClient.Extension/Cypher/FluentConfig.cs | 5 ----- src/Neo4jClient.Extension/Cypher/MergeOptions.cs | 6 +----- src/Neo4jClient.Extension/Options/CreateOptions.cs | 6 +----- src/Neo4jClient.Extension/Options/IOptions.cs | 8 +------- src/Neo4jClient.Extension/Options/MatchOptions.cs | 6 +----- test/Neo4jClient.Extension.Test.Common/Domain/Weapon.cs | 7 +------ .../Neo/Relationships/CheckedOutRelationship.cs | 7 +------ .../Neo/Relationships/WorkAddressRelationship.cs | 7 +------ .../Properties/AssemblyInfo.cs | 1 - .../CustomConverters/AreaConverterFixture.cs | 4 ---- .../CustomConverters/AreaJsonConverter.cs | 4 ---- .../Cypher/FluentConfigBaseTest.cs | 6 ------ .../Cypher/FluentConfigCreateTests.cs | 5 ----- .../Cypher/FluentConfigMatchTests.cs | 7 ------- .../Cypher/FluentConfigMergeTests.cs | 5 ----- .../Cypher/FluentConfigUpdateTests.cs | 4 ---- 21 files changed, 8 insertions(+), 95 deletions(-) diff --git a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.CqlBuilders.cs b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.CqlBuilders.cs index 7732d82..8de4f1e 100644 --- a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.CqlBuilders.cs +++ b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.CqlBuilders.cs @@ -1,8 +1,5 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Neo4jClient.Extension.Cypher.Attributes; using Newtonsoft.Json.Serialization; diff --git a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Dynamics.cs b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Dynamics.cs index d34a25b..bff5231 100644 --- a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Dynamics.cs +++ b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Dynamics.cs @@ -1,8 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Neo4jClient.Extension.Cypher { diff --git a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Entity.cs b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Entity.cs index 3b84baf..3aacb7b 100644 --- a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Entity.cs +++ b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Entity.cs @@ -2,8 +2,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Text; -using System.Threading.Tasks; using Neo4jClient.Extension.Cypher.Attributes; namespace Neo4jClient.Extension.Cypher diff --git a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Fluent.cs b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Fluent.cs index 66f3a26..3a1c151 100644 --- a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Fluent.cs +++ b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Fluent.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Neo4jClient.Extension.Cypher { diff --git a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Main.cs b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Main.cs index dd056d5..2eae68a 100644 --- a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Main.cs +++ b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.Main.cs @@ -1,11 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; using System.Text.RegularExpressions; using Neo4jClient.Cypher; using Neo4jClient.Extension.Cypher.Attributes; -using Newtonsoft.Json.Serialization; namespace Neo4jClient.Extension.Cypher { diff --git a/src/Neo4jClient.Extension/Cypher/FluentConfig.cs b/src/Neo4jClient.Extension/Cypher/FluentConfig.cs index 7b3f0c0..6b5de8a 100644 --- a/src/Neo4jClient.Extension/Cypher/FluentConfig.cs +++ b/src/Neo4jClient.Extension/Cypher/FluentConfig.cs @@ -3,11 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; using Neo4jClient.Extension.Cypher.Attributes; namespace Neo4jClient.Extension.Cypher diff --git a/src/Neo4jClient.Extension/Cypher/MergeOptions.cs b/src/Neo4jClient.Extension/Cypher/MergeOptions.cs index 108d6e7..f6b587a 100644 --- a/src/Neo4jClient.Extension/Cypher/MergeOptions.cs +++ b/src/Neo4jClient.Extension/Cypher/MergeOptions.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; namespace Neo4jClient.Extension.Cypher { diff --git a/src/Neo4jClient.Extension/Options/CreateOptions.cs b/src/Neo4jClient.Extension/Options/CreateOptions.cs index 62b45b7..5d9e5e5 100644 --- a/src/Neo4jClient.Extension/Options/CreateOptions.cs +++ b/src/Neo4jClient.Extension/Options/CreateOptions.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; namespace Neo4jClient.Extension.Cypher { diff --git a/src/Neo4jClient.Extension/Options/IOptions.cs b/src/Neo4jClient.Extension/Options/IOptions.cs index 32d15db..ee7d331 100644 --- a/src/Neo4jClient.Extension/Options/IOptions.cs +++ b/src/Neo4jClient.Extension/Options/IOptions.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Neo4jClient.Extension.Cypher +namespace Neo4jClient.Extension.Cypher { public interface IOptionsBase { diff --git a/src/Neo4jClient.Extension/Options/MatchOptions.cs b/src/Neo4jClient.Extension/Options/MatchOptions.cs index b27d2fb..3a7a24b 100644 --- a/src/Neo4jClient.Extension/Options/MatchOptions.cs +++ b/src/Neo4jClient.Extension/Options/MatchOptions.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Collections.Generic; namespace Neo4jClient.Extension.Cypher { diff --git a/test/Neo4jClient.Extension.Test.Common/Domain/Weapon.cs b/test/Neo4jClient.Extension.Test.Common/Domain/Weapon.cs index 3fc80a8..47a6c2b 100644 --- a/test/Neo4jClient.Extension.Test.Common/Domain/Weapon.cs +++ b/test/Neo4jClient.Extension.Test.Common/Domain/Weapon.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using UnitsNet; +using UnitsNet; namespace Neo4jClient.Extension.Test.TestData.Entities { diff --git a/test/Neo4jClient.Extension.Test.Common/Neo/Relationships/CheckedOutRelationship.cs b/test/Neo4jClient.Extension.Test.Common/Neo/Relationships/CheckedOutRelationship.cs index 0699beb..53f9a76 100644 --- a/test/Neo4jClient.Extension.Test.Common/Neo/Relationships/CheckedOutRelationship.cs +++ b/test/Neo4jClient.Extension.Test.Common/Neo/Relationships/CheckedOutRelationship.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Neo4jClient.Extension.Cypher; +using Neo4jClient.Extension.Cypher; using Neo4jClient.Extension.Cypher.Attributes; namespace Neo4jClient.Extension.Test.TestData.Relationships diff --git a/test/Neo4jClient.Extension.Test.Common/Neo/Relationships/WorkAddressRelationship.cs b/test/Neo4jClient.Extension.Test.Common/Neo/Relationships/WorkAddressRelationship.cs index 7b58c6a..1d59373 100644 --- a/test/Neo4jClient.Extension.Test.Common/Neo/Relationships/WorkAddressRelationship.cs +++ b/test/Neo4jClient.Extension.Test.Common/Neo/Relationships/WorkAddressRelationship.cs @@ -1,10 +1,5 @@ -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Neo4jClient.Extension.Cypher; +using Neo4jClient.Extension.Cypher; using Neo4jClient.Extension.Cypher.Attributes; -using Neo4jClient.Extension.Test.Cypher; namespace Neo4jClient.Extension.Test.TestEntities.Relationships { diff --git a/test/Neo4jClient.Extension.Test.Common/Properties/AssemblyInfo.cs b/test/Neo4jClient.Extension.Test.Common/Properties/AssemblyInfo.cs index 4271756..1f8586c 100644 --- a/test/Neo4jClient.Extension.Test.Common/Properties/AssemblyInfo.cs +++ b/test/Neo4jClient.Extension.Test.Common/Properties/AssemblyInfo.cs @@ -1,5 +1,4 @@ using System.Reflection; -using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/test/Neo4jClient.Extension.UnitTest/CustomConverters/AreaConverterFixture.cs b/test/Neo4jClient.Extension.UnitTest/CustomConverters/AreaConverterFixture.cs index 500525f..22e5fcc 100644 --- a/test/Neo4jClient.Extension.UnitTest/CustomConverters/AreaConverterFixture.cs +++ b/test/Neo4jClient.Extension.UnitTest/CustomConverters/AreaConverterFixture.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Newtonsoft.Json; using NUnit.Framework; using UnitsNet; diff --git a/test/Neo4jClient.Extension.UnitTest/CustomConverters/AreaJsonConverter.cs b/test/Neo4jClient.Extension.UnitTest/CustomConverters/AreaJsonConverter.cs index a9b1c1f..97b0d5a 100644 --- a/test/Neo4jClient.Extension.UnitTest/CustomConverters/AreaJsonConverter.cs +++ b/test/Neo4jClient.Extension.UnitTest/CustomConverters/AreaJsonConverter.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Newtonsoft.Json; using UnitsNet; diff --git a/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigBaseTest.cs b/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigBaseTest.cs index d1e53e1..75529fa 100644 --- a/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigBaseTest.cs +++ b/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigBaseTest.cs @@ -1,15 +1,9 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Moq; using Neo4jClient.Cypher; -using Neo4jClient.Extension.Cypher; using Neo4jClient.Extension.Test.CustomConverters; using Neo4jClient.Extension.Test.Data; -using Neo4jClient.Extension.Test.TestData.Entities; -using Neo4jClient.Extension.Test.TestEntities.Relationships; using Newtonsoft.Json; using NUnit.Framework; diff --git a/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigCreateTests.cs b/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigCreateTests.cs index 0bd307f..7817266 100644 --- a/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigCreateTests.cs +++ b/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigCreateTests.cs @@ -1,11 +1,6 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Neo4jClient.Cypher; using Neo4jClient.Extension.Cypher; -using Neo4jClient.Extension.Cypher.Attributes; using Neo4jClient.Extension.Test.TestEntities.Relationships; using NUnit.Framework; diff --git a/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigMatchTests.cs b/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigMatchTests.cs index e6b1cc9..f3a9774 100644 --- a/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigMatchTests.cs +++ b/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigMatchTests.cs @@ -1,12 +1,5 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Moq; -using Neo4jClient.Cypher; using Neo4jClient.Extension.Cypher; -using Neo4jClient.Extension.Cypher.Attributes; using Neo4jClient.Extension.Test.TestData.Relationships; using Neo4jClient.Extension.Test.TestEntities.Relationships; using NUnit.Framework; diff --git a/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigMergeTests.cs b/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigMergeTests.cs index 201f481..4df41df 100644 --- a/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigMergeTests.cs +++ b/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigMergeTests.cs @@ -1,9 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Moq; using Neo4jClient.Cypher; using Neo4jClient.Extension.Cypher; using Neo4jClient.Extension.Cypher.Attributes; diff --git a/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigUpdateTests.cs b/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigUpdateTests.cs index bea7818..f3d94d9 100644 --- a/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigUpdateTests.cs +++ b/test/Neo4jClient.Extension.UnitTest/Cypher/FluentConfigUpdateTests.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Neo4jClient.Extension.Cypher; using NUnit.Framework; From a282873b3724ba4351947210ace3957d8bcb7377 Mon Sep 17 00:00:00 2001 From: Simon Pinn Date: Mon, 20 Oct 2025 15:42:02 +1100 Subject: [PATCH 3/4] Add data contract serializer test and match test --- .../Extension/CypherExtension.CqlBuilders.cs | 25 ++- .../Cypher/ContractResolverTests.cs | 170 ++++++++++++++++++ .../Neo4jClient.Extension.UnitTest.csproj | 1 + 3 files changed, 190 insertions(+), 6 deletions(-) create mode 100644 test/Neo4jClient.Extension.UnitTest/Cypher/ContractResolverTests.cs diff --git a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.CqlBuilders.cs b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.CqlBuilders.cs index 8de4f1e..8ab7698 100644 --- a/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.CqlBuilders.cs +++ b/src/Neo4jClient.Extension/Cypher/Extension/CypherExtension.CqlBuilders.cs @@ -93,14 +93,27 @@ internal static string ToCypherString(this TEntity entity, ICyph } internal static string ApplyCasing(this string value, ICypherExtensionContext context) { - var useCamelCase = (context.JsonContractResolver is CamelCasePropertyNamesContractResolver); - if (useCamelCase) + // Use the contract resolver to determine the JSON property name + if (context.JsonContractResolver != null) { - return string.Format( - "{0}{1}" - , value.Substring(0, 1).ToLowerInvariant() - , value.Length > 1 ? value.Substring(1, value.Length - 1) : string.Empty); + // Use DefaultContractResolver's NamingStrategy if available (Newtonsoft.Json 9.0+) + if (context.JsonContractResolver is DefaultContractResolver defaultResolver && + defaultResolver.NamingStrategy != null) + { + return defaultResolver.NamingStrategy.GetPropertyName(value, false); + } + + // For CamelCasePropertyNamesContractResolver (legacy support) + if (context.JsonContractResolver is CamelCasePropertyNamesContractResolver) + { + return string.Format( + "{0}{1}", + value.Substring(0, 1).ToLowerInvariant(), + value.Length > 1 ? value.Substring(1, value.Length - 1) : string.Empty); + } } + + // Fallback to PascalCase if no resolver is configured return value; } } diff --git a/test/Neo4jClient.Extension.UnitTest/Cypher/ContractResolverTests.cs b/test/Neo4jClient.Extension.UnitTest/Cypher/ContractResolverTests.cs new file mode 100644 index 0000000..ec23c85 --- /dev/null +++ b/test/Neo4jClient.Extension.UnitTest/Cypher/ContractResolverTests.cs @@ -0,0 +1,170 @@ +using FluentAssertions; +using Moq; +using Neo4jClient.Cypher; +using Neo4jClient.Extension.Cypher; +using Newtonsoft.Json.Serialization; +using NUnit.Framework; + +namespace Neo4jClient.Extension.Test.Cypher +{ + /// + /// Tests to ensure the library respects the GraphClient's configured ContractResolver + /// + public class ContractResolverTests + { + [Test] + public void ApplyCasing_WithCamelCaseResolver_ReturnsCamelCase() + { + // Arrange + var context = new CypherExtensionContext + { + JsonContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + // Act + var result = "FirstName".ApplyCasing(context); + + // Assert + result.Should().Be("firstName"); + } + + [Test] + public void ApplyCasing_WithDefaultResolver_ReturnsPascalCase() + { + // Arrange + var context = new CypherExtensionContext + { + JsonContractResolver = new DefaultContractResolver() + }; + + // Act + var result = "FirstName".ApplyCasing(context); + + // Assert + result.Should().Be("FirstName"); + } + + [Test] + public void ApplyCasing_WithSnakeCaseNamingStrategy_ReturnsSnakeCase() + { + // Arrange + var context = new CypherExtensionContext + { + JsonContractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy() + } + }; + + // Act + var result = "FirstName".ApplyCasing(context); + + // Assert + result.Should().Be("first_name"); + } + + [Test] + public void ApplyCasing_WithCamelCaseNamingStrategy_ReturnsCamelCase() + { + // Arrange + var context = new CypherExtensionContext + { + JsonContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + } + }; + + // Act + var result = "FirstName".ApplyCasing(context); + + // Assert + result.Should().Be("firstName"); + } + + [Test] + public void ApplyCasing_WithKebabCaseNamingStrategy_ReturnsKebabCase() + { + // Arrange + var context = new CypherExtensionContext + { + JsonContractResolver = new DefaultContractResolver + { + NamingStrategy = new KebabCaseNamingStrategy() + } + }; + + // Act + var result = "FirstName".ApplyCasing(context); + + // Assert + result.Should().Be("first-name"); + } + + [Test] + public void ApplyCasing_WithNullResolver_ReturnsPascalCase() + { + // Arrange + var context = new CypherExtensionContext + { + JsonContractResolver = null + }; + + // Act + var result = "FirstName".ApplyCasing(context); + + // Assert + result.Should().Be("FirstName"); + } + + // Note: Full CreateEntity integration test with different resolvers is covered + // in integration tests. The ApplyCasing tests above prove the core functionality. + + [Test] + public void UseProperties_WithCamelCaseResolver_GeneratesCamelCaseProperties() + { + // Arrange + var context = new CypherExtensionContext + { + JsonContractResolver = new CamelCasePropertyNamesContractResolver() + }; + + var person = new Person { Id = 1, Name = "Test" }; + + // Act + var properties = person.UseProperties(context, p => p.Id, p => p.Name); + + // Assert + properties.Should().HaveCount(2); + properties[0].TypeName.Should().Be("Id"); + properties[0].JsonName.Should().Be("id"); + properties[1].TypeName.Should().Be("Name"); + properties[1].JsonName.Should().Be("name"); + } + + [Test] + public void UseProperties_WithSnakeCaseNamingStrategy_GeneratesSnakeCaseProperties() + { + // Arrange + var context = new CypherExtensionContext + { + JsonContractResolver = new DefaultContractResolver + { + NamingStrategy = new SnakeCaseNamingStrategy() + } + }; + + var person = new Person { Id = 1, Name = "Test" }; + + // Act + var properties = person.UseProperties(context, p => p.Id, p => p.Name); + + // Assert + properties.Should().HaveCount(2); + properties[0].TypeName.Should().Be("Id"); + properties[0].JsonName.Should().Be("id"); + properties[1].TypeName.Should().Be("Name"); + properties[1].JsonName.Should().Be("name"); + } + } +} diff --git a/test/Neo4jClient.Extension.UnitTest/Neo4jClient.Extension.UnitTest.csproj b/test/Neo4jClient.Extension.UnitTest/Neo4jClient.Extension.UnitTest.csproj index 6d8e4b6..6ff5a56 100644 --- a/test/Neo4jClient.Extension.UnitTest/Neo4jClient.Extension.UnitTest.csproj +++ b/test/Neo4jClient.Extension.UnitTest/Neo4jClient.Extension.UnitTest.csproj @@ -10,6 +10,7 @@ + From 431ccb6200d1e2e52b1045963d3038a5cfb74a01 Mon Sep 17 00:00:00 2001 From: Simon Pinn Date: Mon, 20 Oct 2025 15:51:51 +1100 Subject: [PATCH 4/4] Update to latest packages --- .../Neo4jClient.Extension.Attributes.csproj | 2 +- src/Neo4jClient.Extension/Neo4jClient.Extension.csproj | 2 +- .../Neo4jClient.Extension.IntegrationTest.csproj | 10 +++++----- .../Neo4jClient.Extension.Test.Common.csproj | 4 ++-- .../Cypher/CypherExtensionTests.cs | 2 +- .../Neo4jClient.Extension.UnitTest.csproj | 10 +++++----- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Neo4jClient.Extension.Attributes/Neo4jClient.Extension.Attributes.csproj b/src/Neo4jClient.Extension.Attributes/Neo4jClient.Extension.Attributes.csproj index 6503d12..ea7f741 100644 --- a/src/Neo4jClient.Extension.Attributes/Neo4jClient.Extension.Attributes.csproj +++ b/src/Neo4jClient.Extension.Attributes/Neo4jClient.Extension.Attributes.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Neo4jClient.Extension/Neo4jClient.Extension.csproj b/src/Neo4jClient.Extension/Neo4jClient.Extension.csproj index d775276..8c5fdfb 100644 --- a/src/Neo4jClient.Extension/Neo4jClient.Extension.csproj +++ b/src/Neo4jClient.Extension/Neo4jClient.Extension.csproj @@ -10,7 +10,7 @@ - + diff --git a/test/Neo4jClient.Extension.IntegrationTest/Neo4jClient.Extension.IntegrationTest.csproj b/test/Neo4jClient.Extension.IntegrationTest/Neo4jClient.Extension.IntegrationTest.csproj index 65b2e5d..775123b 100644 --- a/test/Neo4jClient.Extension.IntegrationTest/Neo4jClient.Extension.IntegrationTest.csproj +++ b/test/Neo4jClient.Extension.IntegrationTest/Neo4jClient.Extension.IntegrationTest.csproj @@ -11,12 +11,12 @@ - - - - + + + + - + diff --git a/test/Neo4jClient.Extension.Test.Common/Neo4jClient.Extension.Test.Common.csproj b/test/Neo4jClient.Extension.Test.Common/Neo4jClient.Extension.Test.Common.csproj index 6862cd0..68e78ae 100644 --- a/test/Neo4jClient.Extension.Test.Common/Neo4jClient.Extension.Test.Common.csproj +++ b/test/Neo4jClient.Extension.Test.Common/Neo4jClient.Extension.Test.Common.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -10,7 +10,7 @@ - + diff --git a/test/Neo4jClient.Extension.UnitTest/Cypher/CypherExtensionTests.cs b/test/Neo4jClient.Extension.UnitTest/Cypher/CypherExtensionTests.cs index f13f02e..6aa3c33 100644 --- a/test/Neo4jClient.Extension.UnitTest/Cypher/CypherExtensionTests.cs +++ b/test/Neo4jClient.Extension.UnitTest/Cypher/CypherExtensionTests.cs @@ -108,7 +108,7 @@ public void MatchEntityKeyTest() //assert Assert.That(q.Query.DebugQueryText, Is.EqualTo(@"MATCH (key:CypherModel {id:{ - ""id"": ""b00b7355-ce53-49f2-a421-deadb655673d"" + id: ""b00b7355-ce53-49f2-a421-deadb655673d"" }.id}) RETURN cyphermodel")); } diff --git a/test/Neo4jClient.Extension.UnitTest/Neo4jClient.Extension.UnitTest.csproj b/test/Neo4jClient.Extension.UnitTest/Neo4jClient.Extension.UnitTest.csproj index 6ff5a56..b76f180 100644 --- a/test/Neo4jClient.Extension.UnitTest/Neo4jClient.Extension.UnitTest.csproj +++ b/test/Neo4jClient.Extension.UnitTest/Neo4jClient.Extension.UnitTest.csproj @@ -1,4 +1,4 @@ - + net9.0 @@ -12,10 +12,10 @@ - - - - + + + +