diff --git a/.gitignore b/.gitignore index 8c675a670..92b062ad7 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,6 @@ Neo4jClient.*.nupkg *.docstates *.crunchsolution.cache packages -*.ncrunch* +*.ncrunchsolution *.Cache +*.vs\ \ No newline at end of file diff --git a/Test/ApiModels/GremlinTableCapResponseTests.cs b/Neo4jClient.Tests/ApiModels/GremlinTableCapResponseTests.cs similarity index 97% rename from Test/ApiModels/GremlinTableCapResponseTests.cs rename to Neo4jClient.Tests/ApiModels/GremlinTableCapResponseTests.cs index 591638774..40aa907bf 100644 --- a/Test/ApiModels/GremlinTableCapResponseTests.cs +++ b/Neo4jClient.Tests/ApiModels/GremlinTableCapResponseTests.cs @@ -1,351 +1,351 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using NUnit.Framework; -using Neo4jClient.ApiModels; -using System.Linq; -using Neo4jClient.ApiModels.Gremlin; -using Newtonsoft.Json; - -namespace Neo4jClient.Test.ApiModels -{ - [TestFixture] - public class GremlinTableCapResponseTests - { - [Test] - public void VerifyTransferTableCapResponseToResult() - { - var list = new List>(); - const string dataforfoo = "DataForFoo"; - const string dataforbar = "DataForBar"; - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "Foo", - "Bar" - }, - Data = new List> - { - new List - { - dataforfoo, - dataforbar, - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - - Assert.IsTrue(response.Any(r => r.Foo == dataforfoo)); - Assert.IsTrue(response.Any(r => r.Bar == dataforbar)); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithLongToLong() - { - var list = new List>(); - - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "Long" - }, - Data = new List> - { - new List - { - "123", - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => r.Long == 123)); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithEnumValueToEnum() - { - var list = new List>(); - - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "EnumValue" - }, - Data = new List> - { - new List - { - MyEnum.Foo.ToString(), - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => r.EnumValue == MyEnum.Foo)); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithNullableEnumValueToEnum() - { - var list = new List>(); - - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "EnumValueNullable" - }, - Data = new List> - { - new List - { - MyEnum.Foo.ToString(), - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => r.EnumValueNullable == MyEnum.Foo)); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithLongToNullableLong() - { - var list = new List>(); - - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "NullableLong" - }, - Data = new List> - { - new List - { - "123", - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => r.NullableLong.Value == 123)); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithLongNullToNullableLong() - { - var list = new List>(); - - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "NullableLong" - }, - Data = new List> - { - new List - { - "", - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => !r.NullableLong.HasValue)); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithIntNullToNullableInt() - { - var list = new List>(); - - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "NullableInt" - }, - Data = new List> - { - new List - { - "", - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => !r.NullableLong.HasValue)); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithLongNullAsStringToNullableLong() - { - var list = new List>(); - - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "NullableLong" - }, - Data = new List> - { - new List - { - "null", - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => !r.NullableLong.HasValue)); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithDateTimeOffsetToNullableDateTimeOffset() - { - var list = new List>(); - - const string expectedDate = "01 Jul 2009"; - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "DateTimeOffsetNullable" - }, - Data = new List> - { - new List - { - expectedDate, - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => r.DateTimeOffsetNullable.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == expectedDate)); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithDateTimeOffsetToDateTimeOffsetUsingNeoDate() - { - var list = new List>(); - - const string date = "/NeoDate(1322007153048+1100)/"; - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "DateTimeOffset" - }, - Data = new List> - { - new List - { - date, - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => r.DateTimeOffset.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == "23 Nov 2011")); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithDateTimeOffsetToDateTimeOffset() - { - var list = new List>(); - - const string expectedDate = "01 Jul 2009"; - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "DateTimeOffset" - }, - Data = new List> - { - new List - { - expectedDate, - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => r.DateTimeOffset.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == expectedDate)); - } - - [Test] - public void VerifyTransferTableCapResponseToResultFromStringWithDateTimeToDateTime() - { - var list = new List>(); - - const string expectedDate = "01 Jul 2009"; - list.Add(new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "DateTime" - }, - Data = new List> - { - new List - { - expectedDate, - } - } - } - }); - var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); - Assert.IsTrue(response.Any(r => r.DateTime.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == expectedDate)); - } - - internal enum MyEnum {Foo, Bar, Baz} - internal class SimpleClass - { - public string Foo { get; set; } - public string Bar { get; set; } - public long Long { get; set; } - public long? NullableLong { get; set; } - public int? NullableInt { get; set; } - public MyEnum EnumValue { get; set; } - public MyEnum? EnumValueNullable { get; set; } - public DateTimeOffset DateTimeOffset { get; set; } - public DateTime DateTime { get; set; } - public DateTimeOffset DateTimeOffsetNullable { get; set; } - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using NUnit.Framework; +using Neo4jClient.ApiModels; +using System.Linq; +using Neo4jClient.ApiModels.Gremlin; +using Newtonsoft.Json; + +namespace Neo4jClient.Test.ApiModels +{ + [TestFixture] + public class GremlinTableCapResponseTests + { + [Test] + public void VerifyTransferTableCapResponseToResult() + { + var list = new List>(); + const string dataforfoo = "DataForFoo"; + const string dataforbar = "DataForBar"; + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "Foo", + "Bar" + }, + Data = new List> + { + new List + { + dataforfoo, + dataforbar, + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + + Assert.IsTrue(response.Any(r => r.Foo == dataforfoo)); + Assert.IsTrue(response.Any(r => r.Bar == dataforbar)); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithLongToLong() + { + var list = new List>(); + + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "Long" + }, + Data = new List> + { + new List + { + "123", + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => r.Long == 123)); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithEnumValueToEnum() + { + var list = new List>(); + + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "EnumValue" + }, + Data = new List> + { + new List + { + MyEnum.Foo.ToString(), + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => r.EnumValue == MyEnum.Foo)); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithNullableEnumValueToEnum() + { + var list = new List>(); + + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "EnumValueNullable" + }, + Data = new List> + { + new List + { + MyEnum.Foo.ToString(), + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => r.EnumValueNullable == MyEnum.Foo)); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithLongToNullableLong() + { + var list = new List>(); + + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "NullableLong" + }, + Data = new List> + { + new List + { + "123", + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => r.NullableLong.Value == 123)); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithLongNullToNullableLong() + { + var list = new List>(); + + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "NullableLong" + }, + Data = new List> + { + new List + { + "", + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => !r.NullableLong.HasValue)); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithIntNullToNullableInt() + { + var list = new List>(); + + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "NullableInt" + }, + Data = new List> + { + new List + { + "", + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => !r.NullableLong.HasValue)); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithLongNullAsStringToNullableLong() + { + var list = new List>(); + + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "NullableLong" + }, + Data = new List> + { + new List + { + "null", + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => !r.NullableLong.HasValue)); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithDateTimeOffsetToNullableDateTimeOffset() + { + var list = new List>(); + + const string expectedDate = "01 Jul 2009"; + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "DateTimeOffsetNullable" + }, + Data = new List> + { + new List + { + expectedDate, + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => r.DateTimeOffsetNullable.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == expectedDate)); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithDateTimeOffsetToDateTimeOffsetUsingNeoDate() + { + var list = new List>(); + + const string date = "/NeoDate(1322007153048+1100)/"; + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "DateTimeOffset" + }, + Data = new List> + { + new List + { + date, + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => r.DateTimeOffset.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == "23 Nov 2011")); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithDateTimeOffsetToDateTimeOffset() + { + var list = new List>(); + + const string expectedDate = "01 Jul 2009"; + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "DateTimeOffset" + }, + Data = new List> + { + new List + { + expectedDate, + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => r.DateTimeOffset.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == expectedDate)); + } + + [Test] + public void VerifyTransferTableCapResponseToResultFromStringWithDateTimeToDateTime() + { + var list = new List>(); + + const string expectedDate = "01 Jul 2009"; + list.Add(new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "DateTime" + }, + Data = new List> + { + new List + { + expectedDate, + } + } + } + }); + var response = GremlinTableCapResponse.TransferResponseToResult(list, new JsonConverter[0]).ToArray(); + Assert.IsTrue(response.Any(r => r.DateTime.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == expectedDate)); + } + + internal enum MyEnum {Foo, Bar, Baz} + internal class SimpleClass + { + public string Foo { get; set; } + public string Bar { get; set; } + public long Long { get; set; } + public long? NullableLong { get; set; } + public int? NullableInt { get; set; } + public MyEnum EnumValue { get; set; } + public MyEnum? EnumValueNullable { get; set; } + public DateTimeOffset DateTimeOffset { get; set; } + public DateTime DateTime { get; set; } + public DateTimeOffset DateTimeOffsetNullable { get; set; } + } + } +} diff --git a/Test/ApiModels/RootApiResponseTests.cs b/Neo4jClient.Tests/ApiModels/RootApiResponseTests.cs similarity index 97% rename from Test/ApiModels/RootApiResponseTests.cs rename to Neo4jClient.Tests/ApiModels/RootApiResponseTests.cs index 37b941df9..ccd30b7d3 100644 --- a/Test/ApiModels/RootApiResponseTests.cs +++ b/Neo4jClient.Tests/ApiModels/RootApiResponseTests.cs @@ -1,27 +1,27 @@ -using NUnit.Framework; -using Neo4jClient.ApiModels; - -namespace Neo4jClient.Test.ApiModels -{ - [TestFixture] - public class RootApiResponseTests - { - [Test] - [TestCase("", Result = "0.0")] - [TestCase("kgrkjkj", Result = "0.0")] - [TestCase("1.5-82-g7cb21bb1-dirty", Result = "1.5", Description = "http://docs.neo4j.org/chunked/snapshot/rest-api-service-root.html")] - [TestCase("1.5M02", Result = "1.5.0.2")] - [TestCase("1.8.RC1", Result = "1.8.0.1")] - [TestCase("1.5.M02", Result = "1.5.0.2", Description = "Retrieved via REST call from running 1.5M02 install")] - [TestCase("1.7", Result = "1.7", Description = "http://docs.neo4j.org/chunked/1.7/rest-api-service-root.html")] - [TestCase("1.7.2", Result = "1.7.2", Description = "http://docs.neo4j.org/chunked/1.7.2/rest-api-service-root.html")] - [TestCase("1.8.M07-1-g09701c5", Result = "1.8.0.7", Description = "http://docs.neo4j.org/chunked/1.8.M07/rest-api-service-root.html")] - [TestCase("1.9.RC1", Result = "1.9.0.1")] - [TestCase("1.9.RC2", Result = "1.9.0.2")] - public string Version(string versionString) - { - var response = new RootApiResponse { neo4j_version = versionString }; - return response.Version.ToString(); - } - } -} +using NUnit.Framework; +using Neo4jClient.ApiModels; + +namespace Neo4jClient.Test.ApiModels +{ + [TestFixture] + public class RootApiResponseTests + { + [Test] + [TestCase("", Result = "0.0")] + [TestCase("kgrkjkj", Result = "0.0")] + [TestCase("1.5-82-g7cb21bb1-dirty", Result = "1.5", Description = "http://docs.neo4j.org/chunked/snapshot/rest-api-service-root.html")] + [TestCase("1.5M02", Result = "1.5.0.2")] + [TestCase("1.8.RC1", Result = "1.8.0.1")] + [TestCase("1.5.M02", Result = "1.5.0.2", Description = "Retrieved via REST call from running 1.5M02 install")] + [TestCase("1.7", Result = "1.7", Description = "http://docs.neo4j.org/chunked/1.7/rest-api-service-root.html")] + [TestCase("1.7.2", Result = "1.7.2", Description = "http://docs.neo4j.org/chunked/1.7.2/rest-api-service-root.html")] + [TestCase("1.8.M07-1-g09701c5", Result = "1.8.0.7", Description = "http://docs.neo4j.org/chunked/1.8.M07/rest-api-service-root.html")] + [TestCase("1.9.RC1", Result = "1.9.0.1")] + [TestCase("1.9.RC2", Result = "1.9.0.2")] + public string Version(string versionString) + { + var response = new RootApiResponse { neo4j_version = versionString }; + return response.Version.ToString(); + } + } +} diff --git a/Test/ApiUsageIdeas.cs b/Neo4jClient.Tests/ApiUsageIdeas.cs similarity index 97% rename from Test/ApiUsageIdeas.cs rename to Neo4jClient.Tests/ApiUsageIdeas.cs index 7712af275..c6f442978 100644 --- a/Test/ApiUsageIdeas.cs +++ b/Neo4jClient.Tests/ApiUsageIdeas.cs @@ -1,62 +1,62 @@ -using System; -using Neo4jClient.Test.Domain; -using Neo4jClient.Test.Relationships; - -namespace Neo4jClient.Test -{ - // This class just documents how the API could be consumed. It was an - // initial scratching ground before any of the original signatures, - // interfaces, or functionality was put in place. Right now it has no - // other requirements than just compiling (so as to assert backwards - // compatibility with consumers). It is mainly kept for historical - // purposes. - class ApiUsageIdeas - { - void Foo() - { - IGraphClient graph = new GraphClient(new Uri("")); - - // Based on http://wiki.neo4j.org/content/Image:Warehouse.png - - // Can create nodes from POCOs - var frameStore = graph.Create( - new StorageLocation { Name = "Frame Store" }); - var mainStore = graph.Create( - new StorageLocation { Name = "Main Store" }); - - // Can create a node with outgoing relationships - var frame = graph.Create( - new Part { Name = "Frame" }, - new StoredIn(frameStore)); - - // Can create multiple outgoing relationships and relationships with payloads - graph.Create( - new Product { Name = "Trike", Weight = 2 }, - new StoredIn(mainStore), - new Requires(frame, new Requires.Payload { Count = 1 })); - - // Can create relationships in both directions - graph.Create( - new Part { Name = "Pedal" }, - new StoredIn(frameStore), - new Requires(frame, new Requires.Payload { Count = 2 }) - { Direction = RelationshipDirection.Incoming }); - - var wheel = graph.Create( - new Part { Name = "Wheel" }, - new Requires(frame, new Requires.Payload { Count = 2 }) - { Direction = RelationshipDirection.Incoming }); - - // Can create implicit incoming relationships - graph.Create( - new StorageLocation { Name = "Wheel Store" }, - new StoredIn(wheel)); - - // Can create relationships against the root node - graph.Create( - new StorageLocation {Name = "Auxillary Store"}, - new StoredIn(wheel), - new OwnedBy(graph.RootNode)); - } - } +using System; +using Neo4jClient.Test.Domain; +using Neo4jClient.Test.Relationships; + +namespace Neo4jClient.Test +{ + // This class just documents how the API could be consumed. It was an + // initial scratching ground before any of the original signatures, + // interfaces, or functionality was put in place. Right now it has no + // other requirements than just compiling (so as to assert backwards + // compatibility with consumers). It is mainly kept for historical + // purposes. + class ApiUsageIdeas + { + void Foo() + { + IGraphClient graph = new GraphClient(new Uri("")); + + // Based on http://wiki.neo4j.org/content/Image:Warehouse.png + + // Can create nodes from POCOs + var frameStore = graph.Create( + new StorageLocation { Name = "Frame Store" }); + var mainStore = graph.Create( + new StorageLocation { Name = "Main Store" }); + + // Can create a node with outgoing relationships + var frame = graph.Create( + new Part { Name = "Frame" }, + new StoredIn(frameStore)); + + // Can create multiple outgoing relationships and relationships with payloads + graph.Create( + new Product { Name = "Trike", Weight = 2 }, + new StoredIn(mainStore), + new Requires(frame, new Requires.Payload { Count = 1 })); + + // Can create relationships in both directions + graph.Create( + new Part { Name = "Pedal" }, + new StoredIn(frameStore), + new Requires(frame, new Requires.Payload { Count = 2 }) + { Direction = RelationshipDirection.Incoming }); + + var wheel = graph.Create( + new Part { Name = "Wheel" }, + new Requires(frame, new Requires.Payload { Count = 2 }) + { Direction = RelationshipDirection.Incoming }); + + // Can create implicit incoming relationships + graph.Create( + new StorageLocation { Name = "Wheel Store" }, + new StoredIn(wheel)); + + // Can create relationships against the root node + graph.Create( + new StorageLocation {Name = "Auxillary Store"}, + new StoredIn(wheel), + new OwnedBy(graph.RootNode)); + } + } } \ No newline at end of file diff --git a/Test/CultureInfoSetupFixture.cs b/Neo4jClient.Tests/CultureInfoSetupFixture.cs similarity index 97% rename from Test/CultureInfoSetupFixture.cs rename to Neo4jClient.Tests/CultureInfoSetupFixture.cs index f6890ec88..8d16b9bb3 100644 --- a/Test/CultureInfoSetupFixture.cs +++ b/Neo4jClient.Tests/CultureInfoSetupFixture.cs @@ -1,21 +1,21 @@ -using System.Globalization; -using System.Threading; -using NUnit.Framework; - -namespace Neo4jClient.Test -{ - [SetUpFixture] - public class CultureInfoSetupFixture - { - [SetUp] - public void SetCultureToSomethingNonLatinToEnsureCodeUnderTestDoesntAssumeEnAu() - { - // Issue https://bitbucket.org/Readify/neo4jclient/issue/15/take-cultureinfo-into-account-for-proper - - // The idea is to minimize developer mistake by surprising culture-info assumptions. This may not be the best setup for culture-dependent - // tests. The alternative of introducing test base class is deliberately not taken because deriving from it is another assumption by itself. - var thread = Thread.CurrentThread; - thread.CurrentCulture = thread.CurrentUICulture = new CultureInfo("zh-CN"); - } - } -} +using System.Globalization; +using System.Threading; +using NUnit.Framework; + +namespace Neo4jClient.Test +{ + [SetUpFixture] + public class CultureInfoSetupFixture + { + [SetUp] + public void SetCultureToSomethingNonLatinToEnsureCodeUnderTestDoesntAssumeEnAu() + { + // Issue https://bitbucket.org/Readify/neo4jclient/issue/15/take-cultureinfo-into-account-for-proper + + // The idea is to minimize developer mistake by surprising culture-info assumptions. This may not be the best setup for culture-dependent + // tests. The alternative of introducing test base class is deliberately not taken because deriving from it is another assumption by itself. + var thread = Thread.CurrentThread; + thread.CurrentCulture = thread.CurrentUICulture = new CultureInfo("zh-CN"); + } + } +} diff --git a/Test/Cypher/AggregateTests.cs b/Neo4jClient.Tests/Cypher/AggregateTests.cs similarity index 72% rename from Test/Cypher/AggregateTests.cs rename to Neo4jClient.Tests/Cypher/AggregateTests.cs index 442275306..b3fc7b9f0 100644 --- a/Test/Cypher/AggregateTests.cs +++ b/Neo4jClient.Tests/Cypher/AggregateTests.cs @@ -1,119 +1,144 @@ -using System.Linq; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class AggregateTests - { - [Test] - public void Length() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(n => new { Foo = n.Length() }) - .Query; - - Assert.AreEqual("RETURN length(n) AS Foo", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count()); - } - - [Test] - public void Type() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(n => new { Foo = n.Type() }) - .Query; - - Assert.AreEqual("RETURN type(n) AS Foo", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count()); - } - - [Test] - public void Id() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(n => new { Foo = n.Id() }) - .Query; - - Assert.AreEqual("RETURN id(n) AS Foo", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count()); - } - - [Test] - public void Count() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(n => new { Foo = n.Count() }) - .Query; - - Assert.AreEqual("RETURN count(n) AS Foo", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count()); - } - - - - - [Test] - public void CountDistinct() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(n => new { Foo = n.CountDistinct() }) - .Query; - - Assert.AreEqual("RETURN count(distinct n) AS Foo", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count()); - } - - [Test] - public void CountAll() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(() => new - { - Foo = All.Count() - }) - .Query; - - Assert.AreEqual("RETURN count(*) AS Foo", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count()); - } - - [Test] - public void CountUsingICypderFluentQuery() - { - var client = Substitute.For(); - ICypherFluentQuery query = new CypherFluentQuery(client); - - var resultQuery = - query.Return(() => new { Foo = All.Count() }) - .Query; - - Assert.AreEqual("RETURN count(*) AS Foo", resultQuery.QueryText); - Assert.AreEqual(0, resultQuery.QueryParameters.Count()); - } - - [Test] - public void CountAllWithOtherIdentities() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(bar => new - { - Foo = All.Count(), - Baz = bar.CollectAs() - }) - .Query; - - Assert.AreEqual("RETURN count(*) AS Foo, collect(bar) AS Baz", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count()); - } - } -} +using System.Linq; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class AggregateTests + { + [Test] + public void Length() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(n => new { Foo = n.Length() }) + .Query; + + Assert.AreEqual("RETURN length(n) AS Foo", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void Type() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(n => new { Foo = n.Type() }) + .Query; + + Assert.AreEqual("RETURN type(n) AS Foo", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void Id() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(n => new { Foo = n.Id() }) + .Query; + + Assert.AreEqual("RETURN id(n) AS Foo", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void Count() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(n => new { Foo = n.Count() }) + .Query; + + Assert.AreEqual("RETURN count(n) AS Foo", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + + + + [Test] + public void CountDistinct() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(n => new { Foo = n.CountDistinct() }) + .Query; + + Assert.AreEqual("RETURN count(distinct n) AS Foo", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void CountAll() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(() => new + { + Foo = All.Count() + }) + .Query; + + Assert.AreEqual("RETURN count(*) AS Foo", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void CountUsingICypderFluentQuery() + { + var client = Substitute.For(); + ICypherFluentQuery query = new CypherFluentQuery(client); + + var resultQuery = + query.Return(() => new { Foo = All.Count() }) + .Query; + + Assert.AreEqual("RETURN count(*) AS Foo", resultQuery.QueryText); + Assert.AreEqual(0, resultQuery.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, resultQuery.ResultFormat); + } + + [Test] + public void CountAllWithOtherIdentitiesWithNode() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(bar => new + { + Foo = All.Count(), + Baz = bar.CollectAs>() + }) + .Query; + + Assert.AreEqual("RETURN count(*) AS Foo, collect(bar) AS Baz", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.Rest, query.ResultFormat); + } + + [Test] + public void CountAllWithOtherIdentities() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(bar => new + { + Foo = All.Count(), + Baz = bar.CollectAs() + }) + .Query; + + Assert.AreEqual("RETURN count(*) AS Foo, collect(bar) AS Baz", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + } +} diff --git a/Test/Cypher/CypherFluentQueryConstraintTest.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryConstraintTest.cs similarity index 100% rename from Test/Cypher/CypherFluentQueryConstraintTest.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryConstraintTest.cs diff --git a/Test/Cypher/CypherFluentQueryCreateUniqueTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryCreateUniqueTests.cs similarity index 97% rename from Test/Cypher/CypherFluentQueryCreateUniqueTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryCreateUniqueTests.cs index 40e0fccfa..1f9d78e38 100644 --- a/Test/Cypher/CypherFluentQueryCreateUniqueTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryCreateUniqueTests.cs @@ -1,51 +1,51 @@ -using System; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - class CypherFluentQueryCreateUniqueTests - { - [Test] - public void CreateNodeWithValuesViaCreateUnique() - { - // http://docs.neo4j.org/chunked/1.8.M03/query-relate.html#relate-create-nodes-with-values - //START root=node(2) - //CREATE UNIQUE root-[:X]-(leaf {name:'D'} ) - //RETURN leaf - - var client = Substitute.For(); - client.ServerVersion.Returns(new Version(1, 8)); - var query = new CypherFluentQuery(client) - .Start("root", (NodeReference)2) - .CreateUnique("root-[:X]-(leaf {name:'D'} )") - .Return("leaf") - .Query; - - Assert.AreEqual("START root=node({p0})\r\nCREATE UNIQUE root-[:X]-(leaf {name:'D'} )\r\nRETURN leaf", query.QueryText); - Assert.AreEqual(2, query.QueryParameters["p0"]); - } - - [Test] - public void CreateNodeWithValuesViaCreateUniqueAfterMatch() - { - //START root=node(2) - //MATCH root-[:X]-foo - //CREATE UNIQUE foo-[:Y]-(leaf {name:'D'} ) - //RETURN leaf - - var client = Substitute.For(); - client.ServerVersion.Returns(new Version(1, 8)); - var query = new CypherFluentQuery(client) - .Start("root", (NodeReference)2) - .Match("root-[:X]-foo") - .CreateUnique("foo-[:Y]-(leaf {name:'D'} )") - .Return("leaf") - .Query; - - Assert.AreEqual("START root=node({p0})\r\nMATCH root-[:X]-foo\r\nCREATE UNIQUE foo-[:Y]-(leaf {name:'D'} )\r\nRETURN leaf", query.QueryText); - Assert.AreEqual(2, query.QueryParameters["p0"]); - } - } -} +using System; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + class CypherFluentQueryCreateUniqueTests + { + [Test] + public void CreateNodeWithValuesViaCreateUnique() + { + // http://docs.neo4j.org/chunked/1.8.M03/query-relate.html#relate-create-nodes-with-values + //START root=node(2) + //CREATE UNIQUE root-[:X]-(leaf {name:'D'} ) + //RETURN leaf + + var client = Substitute.For(); + client.ServerVersion.Returns(new Version(1, 8)); + var query = new CypherFluentQuery(client) + .Start("root", (NodeReference)2) + .CreateUnique("root-[:X]-(leaf {name:'D'} )") + .Return("leaf") + .Query; + + Assert.AreEqual("START root=node({p0})\r\nCREATE UNIQUE root-[:X]-(leaf {name:'D'} )\r\nRETURN leaf", query.QueryText); + Assert.AreEqual(2, query.QueryParameters["p0"]); + } + + [Test] + public void CreateNodeWithValuesViaCreateUniqueAfterMatch() + { + //START root=node(2) + //MATCH root-[:X]-foo + //CREATE UNIQUE foo-[:Y]-(leaf {name:'D'} ) + //RETURN leaf + + var client = Substitute.For(); + client.ServerVersion.Returns(new Version(1, 8)); + var query = new CypherFluentQuery(client) + .Start("root", (NodeReference)2) + .Match("root-[:X]-foo") + .CreateUnique("foo-[:Y]-(leaf {name:'D'} )") + .Return("leaf") + .Query; + + Assert.AreEqual("START root=node({p0})\r\nMATCH root-[:X]-foo\r\nCREATE UNIQUE foo-[:Y]-(leaf {name:'D'} )\r\nRETURN leaf", query.QueryText); + Assert.AreEqual(2, query.QueryParameters["p0"]); + } + } +} diff --git a/Test/Cypher/CypherFluentQueryDeleteTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryDeleteTests.cs similarity index 97% rename from Test/Cypher/CypherFluentQueryDeleteTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryDeleteTests.cs index e54eb0a91..6ac16df4d 100644 --- a/Test/Cypher/CypherFluentQueryDeleteTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryDeleteTests.cs @@ -1,99 +1,99 @@ -using System; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQueryDeleteTests - { - [Test] - public void DeleteMatchedIdentifier() - { - // http://docs.neo4j.org/chunked/milestone/query-delete.html#delete-remove-a-node-and-connected-relationships - // START n = node(3) - // MATCH n-[r]-() - // DELETE n, r - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Match("n-[r]-()") - .Delete("n, r") - .Query; - - Assert.AreEqual("START n=node({p0})\r\nMATCH n-[r]-()\r\nDELETE n, r", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void DeleteProperty() - { - // http://docs.neo4j.org/chunked/1.8.M06/query-delete.html#delete-remove-a-property - //START andres = node(3) - //DELETE andres.age - //RETURN andres - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("andres", (NodeReference)3) - .Delete("andres.age") - .Return>("andres") - .Query; - - Assert.AreEqual("START andres=node({p0})\r\nDELETE andres.age\r\nRETURN andres", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void DeleteIdentifier() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Delete("n") - .Query; - - Assert.AreEqual("START n=node({p0})\r\nDELETE n", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void DeleteWithoutReturn() - { - // Arrange - var client = Substitute.For(); - CypherQuery executedQuery = null; - client - .When(c => c.ExecuteCypher(Arg.Any())) - .Do(ci => { executedQuery = ci.Arg(); }); - - // Act - new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Delete("n") - .ExecuteWithoutResults(); - - // Assert - Assert.IsNotNull(executedQuery, "Query was not executed against graph client"); - Assert.AreEqual("START n=node({p0})\r\nDELETE n", executedQuery.QueryText); - Assert.AreEqual(3, executedQuery.QueryParameters["p0"]); - } - - [Test] - public void AllowDeleteClauseAfterWhere() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Where("(...)") - .Delete("n") - .Query; - - // Assert - Assert.AreEqual("START n=node({p0})\r\nWHERE (...)\r\nDELETE n", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - } -} +using System; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQueryDeleteTests + { + [Test] + public void DeleteMatchedIdentifier() + { + // http://docs.neo4j.org/chunked/milestone/query-delete.html#delete-remove-a-node-and-connected-relationships + // START n = node(3) + // MATCH n-[r]-() + // DELETE n, r + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Match("n-[r]-()") + .Delete("n, r") + .Query; + + Assert.AreEqual("START n=node({p0})\r\nMATCH n-[r]-()\r\nDELETE n, r", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + public void DeleteProperty() + { + // http://docs.neo4j.org/chunked/1.8.M06/query-delete.html#delete-remove-a-property + //START andres = node(3) + //DELETE andres.age + //RETURN andres + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("andres", (NodeReference)3) + .Delete("andres.age") + .Return>("andres") + .Query; + + Assert.AreEqual("START andres=node({p0})\r\nDELETE andres.age\r\nRETURN andres", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + public void DeleteIdentifier() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Delete("n") + .Query; + + Assert.AreEqual("START n=node({p0})\r\nDELETE n", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + public void DeleteWithoutReturn() + { + // Arrange + var client = Substitute.For(); + CypherQuery executedQuery = null; + client + .When(c => c.ExecuteCypher(Arg.Any())) + .Do(ci => { executedQuery = ci.Arg(); }); + + // Act + new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Delete("n") + .ExecuteWithoutResults(); + + // Assert + Assert.IsNotNull(executedQuery, "Query was not executed against graph client"); + Assert.AreEqual("START n=node({p0})\r\nDELETE n", executedQuery.QueryText); + Assert.AreEqual(3, executedQuery.QueryParameters["p0"]); + } + + [Test] + public void AllowDeleteClauseAfterWhere() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Where("(...)") + .Delete("n") + .Query; + + // Assert + Assert.AreEqual("START n=node({p0})\r\nWHERE (...)\r\nDELETE n", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + } +} diff --git a/Test/Cypher/CypherFluentQueryDropTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryDropTests.cs similarity index 97% rename from Test/Cypher/CypherFluentQueryDropTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryDropTests.cs index 2beb9a385..bc3352749 100644 --- a/Test/Cypher/CypherFluentQueryDropTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryDropTests.cs @@ -1,95 +1,95 @@ -using System; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQueryDropTests - { - [Test] - public void DropIndex() - { - // http://docs.neo4j.org/chunked/milestone/query-schema-index.html#schema-index-drop-index-on-a-label - // DROP INDEX ON :Person(name) - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Drop("INDEX ON :Person(name)") - .Query; - - Assert.AreEqual("DROP INDEX ON :Person(name)", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void DeleteProperty() - { - // http://docs.neo4j.org/chunked/1.8.M06/query-delete.html#delete-remove-a-property - //START andres = node(3) - //DELETE andres.age - //RETURN andres - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("andres", (NodeReference)3) - .Delete("andres.age") - .Return>("andres") - .Query; - - Assert.AreEqual("START andres=node({p0})\r\nDELETE andres.age\r\nRETURN andres", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void DeleteIdentifier() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Delete("n") - .Query; - - Assert.AreEqual("START n=node({p0})\r\nDELETE n", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void DeleteWithoutReturn() - { - // Arrange - var client = Substitute.For(); - CypherQuery executedQuery = null; - client - .When(c => c.ExecuteCypher(Arg.Any())) - .Do(ci => { executedQuery = ci.Arg(); }); - - // Act - new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Delete("n") - .ExecuteWithoutResults(); - - // Assert - Assert.IsNotNull(executedQuery, "Query was not executed against graph client"); - Assert.AreEqual("START n=node({p0})\r\nDELETE n", executedQuery.QueryText); - Assert.AreEqual(3, executedQuery.QueryParameters["p0"]); - } - - [Test] - public void AllowDeleteClauseAfterWhere() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Where("(...)") - .Delete("n") - .Query; - - // Assert - Assert.AreEqual("START n=node({p0})\r\nWHERE (...)\r\nDELETE n", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - } -} +using System; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQueryDropTests + { + [Test] + public void DropIndex() + { + // http://docs.neo4j.org/chunked/milestone/query-schema-index.html#schema-index-drop-index-on-a-label + // DROP INDEX ON :Person(name) + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Drop("INDEX ON :Person(name)") + .Query; + + Assert.AreEqual("DROP INDEX ON :Person(name)", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void DeleteProperty() + { + // http://docs.neo4j.org/chunked/1.8.M06/query-delete.html#delete-remove-a-property + //START andres = node(3) + //DELETE andres.age + //RETURN andres + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("andres", (NodeReference)3) + .Delete("andres.age") + .Return>("andres") + .Query; + + Assert.AreEqual("START andres=node({p0})\r\nDELETE andres.age\r\nRETURN andres", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + public void DeleteIdentifier() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Delete("n") + .Query; + + Assert.AreEqual("START n=node({p0})\r\nDELETE n", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + public void DeleteWithoutReturn() + { + // Arrange + var client = Substitute.For(); + CypherQuery executedQuery = null; + client + .When(c => c.ExecuteCypher(Arg.Any())) + .Do(ci => { executedQuery = ci.Arg(); }); + + // Act + new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Delete("n") + .ExecuteWithoutResults(); + + // Assert + Assert.IsNotNull(executedQuery, "Query was not executed against graph client"); + Assert.AreEqual("START n=node({p0})\r\nDELETE n", executedQuery.QueryText); + Assert.AreEqual(3, executedQuery.QueryParameters["p0"]); + } + + [Test] + public void AllowDeleteClauseAfterWhere() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Where("(...)") + .Delete("n") + .Query; + + // Assert + Assert.AreEqual("START n=node({p0})\r\nWHERE (...)\r\nDELETE n", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + } +} diff --git a/Test/Cypher/CypherFluentQueryForEachTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryForEachTests.cs similarity index 100% rename from Test/Cypher/CypherFluentQueryForEachTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryForEachTests.cs diff --git a/Test/Cypher/CypherFluentQueryLimitTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryLimitTests.cs similarity index 97% rename from Test/Cypher/CypherFluentQueryLimitTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryLimitTests.cs index 42e59fca0..a6f950911 100644 --- a/Test/Cypher/CypherFluentQueryLimitTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryLimitTests.cs @@ -1,103 +1,103 @@ -using NUnit.Framework; -using NSubstitute; -using Neo4jClient.Cypher; -using System; -using System.Linq; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQueryLimitTests - { - [Test] - public void LimitClause() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Return>("n") - .Limit(2) - .Query; - - // Assert - Assert.AreEqual("START n=node({p0})\r\nRETURN n\r\nLIMIT {p1}", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual(3, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] - public void LimitClauseAfterReturnClauseIsTyped() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) - .Return>("n") - .Limit(2); - - // Assert - Assert.IsInstanceOf>>(query); - } - - [Test] - public void NullLimitDoesNotWriteClause() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Return>("n") - .Limit(null) - .Query; - - // Assert - Assert.AreEqual("START n=node({p0})\r\nRETURN n", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] - public void LimitClauseAfterWithClause() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) - .With("foo") - .Limit(2) - .Query; - - - // Assert - Assert.AreEqual("START n=node({p0})\r\nWITH foo\r\nLIMIT {p1}", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual(3, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] - public void LimitClauseAfterWithClauseIsUntyped() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) - .With("foo") - .Limit(2); - - // Assert - var implementsTypedQueryInterface = query - .GetType() - .GetInterfaces() - .Where(i => i.IsGenericType) - .Select(i => i.GetGenericTypeDefinition()) - .Any(t => t == typeof(ICypherFluentQuery<>)); - Assert.IsFalse(implementsTypedQueryInterface, "Implementes ICypherFluentQuery<>"); - } - } -} +using NUnit.Framework; +using NSubstitute; +using Neo4jClient.Cypher; +using System; +using System.Linq; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQueryLimitTests + { + [Test] + public void LimitClause() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Return>("n") + .Limit(2) + .Query; + + // Assert + Assert.AreEqual("START n=node({p0})\r\nRETURN n\r\nLIMIT {p1}", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] + public void LimitClauseAfterReturnClauseIsTyped() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { n = new NodeReference(3) }) + .Return>("n") + .Limit(2); + + // Assert + Assert.IsInstanceOf>>(query); + } + + [Test] + public void NullLimitDoesNotWriteClause() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Return>("n") + .Limit(null) + .Query; + + // Assert + Assert.AreEqual("START n=node({p0})\r\nRETURN n", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] + public void LimitClauseAfterWithClause() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { n = new NodeReference(3) }) + .With("foo") + .Limit(2) + .Query; + + + // Assert + Assert.AreEqual("START n=node({p0})\r\nWITH foo\r\nLIMIT {p1}", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] + public void LimitClauseAfterWithClauseIsUntyped() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { n = new NodeReference(3) }) + .With("foo") + .Limit(2); + + // Assert + var implementsTypedQueryInterface = query + .GetType() + .GetInterfaces() + .Where(i => i.IsGenericType) + .Select(i => i.GetGenericTypeDefinition()) + .Any(t => t == typeof(ICypherFluentQuery<>)); + Assert.IsFalse(implementsTypedQueryInterface, "Implementes ICypherFluentQuery<>"); + } + } +} diff --git a/Test/Cypher/CypherFluentQueryMatchTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryMatchTests.cs similarity index 100% rename from Test/Cypher/CypherFluentQueryMatchTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryMatchTests.cs diff --git a/Test/Cypher/CypherFluentQueryMergeTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryMergeTests.cs similarity index 96% rename from Test/Cypher/CypherFluentQueryMergeTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryMergeTests.cs index 183097fdf..3e4a590ab 100644 --- a/Test/Cypher/CypherFluentQueryMergeTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryMergeTests.cs @@ -1,71 +1,71 @@ -using NUnit.Framework; -using NSubstitute; -using Neo4jClient.Cypher; -using System; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQueryMergeTests - { - [Test] - public void MergePropertyWithLabel() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Merge("(robert:Person)") - .Query; - - // Assert - Assert.AreEqual("MERGE (robert:Person)", query.QueryText); - } - - [Test] - public void MergeOnCreate() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Merge("(robert:Person)") - .OnCreate() - .Set("robert.Created = timestamp()") - .Query; - - // Assert - Assert.AreEqual("MERGE (robert:Person)\r\nON CREATE\r\nSET robert.Created = timestamp()", query.QueryText); - } - - [Test] - public void MergeOnMatch() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Merge("(robert:Person)") - .OnMatch() - .Set("robert.LastSeen = timestamp()") - .Query; - - // Assert - Assert.AreEqual("MERGE (robert:Person)\r\nON MATCH\r\nSET robert.LastSeen = timestamp()", query.QueryText); - } - - [Test] - public void MergeOnCreateOnMatch() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Merge("(robert:Person)") - .OnCreate() - .Set("robert.Created = timestamp()") - .OnMatch() - .Set("robert.LastSeen = timestamp()") - .Query; - - // Assert - Assert.AreEqual("MERGE (robert:Person)\r\nON CREATE\r\nSET robert.Created = timestamp()\r\nON MATCH\r\nSET robert.LastSeen = timestamp()", query.QueryText); - } - } -} +using NUnit.Framework; +using NSubstitute; +using Neo4jClient.Cypher; +using System; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQueryMergeTests + { + [Test] + public void MergePropertyWithLabel() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Merge("(robert:Person)") + .Query; + + // Assert + Assert.AreEqual("MERGE (robert:Person)", query.QueryText); + } + + [Test] + public void MergeOnCreate() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Merge("(robert:Person)") + .OnCreate() + .Set("robert.Created = timestamp()") + .Query; + + // Assert + Assert.AreEqual("MERGE (robert:Person)\r\nON CREATE\r\nSET robert.Created = timestamp()", query.QueryText); + } + + [Test] + public void MergeOnMatch() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Merge("(robert:Person)") + .OnMatch() + .Set("robert.LastSeen = timestamp()") + .Query; + + // Assert + Assert.AreEqual("MERGE (robert:Person)\r\nON MATCH\r\nSET robert.LastSeen = timestamp()", query.QueryText); + } + + [Test] + public void MergeOnCreateOnMatch() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Merge("(robert:Person)") + .OnCreate() + .Set("robert.Created = timestamp()") + .OnMatch() + .Set("robert.LastSeen = timestamp()") + .Query; + + // Assert + Assert.AreEqual("MERGE (robert:Person)\r\nON CREATE\r\nSET robert.Created = timestamp()\r\nON MATCH\r\nSET robert.LastSeen = timestamp()", query.QueryText); + } + } +} diff --git a/Test/Cypher/CypherFluentQueryParserVersionTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryParserVersionTests.cs similarity index 96% rename from Test/Cypher/CypherFluentQueryParserVersionTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryParserVersionTests.cs index de9f54ec1..77c0c712f 100644 --- a/Test/Cypher/CypherFluentQueryParserVersionTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryParserVersionTests.cs @@ -1,61 +1,61 @@ -using System; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - public class CypherFluentQueryParserVersionTests - { - [Test] - public void SetsVersion_WhenUsingVersionOverload() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .ParserVersion(new Version(1, 9)) - .Start(new - { - n = All.Nodes, - }) - .Return("n") - .Query; - - Assert.AreEqual("CYPHER 1.9\r\nSTART n=node(*)\r\nRETURN n", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void SetsVersion_WhenUsingIntOverload() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .ParserVersion(1, 9) - .Start(new - { - n = All.Nodes, - }) - .Return("n") - .Query; - - Assert.AreEqual("CYPHER 1.9\r\nSTART n=node(*)\r\nRETURN n", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void UsesLegacy_WhenVersionRequestedIsLessThan1_9() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .ParserVersion(1, 8) - .Start(new - { - n = All.Nodes, - }) - .Return("n") - .Query; - - Assert.AreEqual("CYPHER LEGACY\r\nSTART n=node(*)\r\nRETURN n", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - } -} +using System; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + public class CypherFluentQueryParserVersionTests + { + [Test] + public void SetsVersion_WhenUsingVersionOverload() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .ParserVersion(new Version(1, 9)) + .Start(new + { + n = All.Nodes, + }) + .Return("n") + .Query; + + Assert.AreEqual("CYPHER 1.9\r\nSTART n=node(*)\r\nRETURN n", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void SetsVersion_WhenUsingIntOverload() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .ParserVersion(1, 9) + .Start(new + { + n = All.Nodes, + }) + .Return("n") + .Query; + + Assert.AreEqual("CYPHER 1.9\r\nSTART n=node(*)\r\nRETURN n", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void UsesLegacy_WhenVersionRequestedIsLessThan1_9() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .ParserVersion(1, 8) + .Start(new + { + n = All.Nodes, + }) + .Return("n") + .Query; + + Assert.AreEqual("CYPHER LEGACY\r\nSTART n=node(*)\r\nRETURN n", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + } +} diff --git a/Test/Cypher/CypherFluentQueryRemoveTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryRemoveTests.cs similarity index 100% rename from Test/Cypher/CypherFluentQueryRemoveTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryRemoveTests.cs diff --git a/Test/Cypher/CypherFluentQueryResultsTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryResultsTests.cs similarity index 97% rename from Test/Cypher/CypherFluentQueryResultsTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryResultsTests.cs index cf0f0a1fd..20e4a54b4 100644 --- a/Test/Cypher/CypherFluentQueryResultsTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryResultsTests.cs @@ -1,159 +1,159 @@ -using System.Collections.Generic; -using System.Linq; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQueryResultsTests - { - [Test] - public void ReturnColumnAlias() - { - // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias - // START a=node(1) - // RETURN a.Age AS SomethingTotallyDifferent - - var client = Substitute.For(); - - client - .ExecuteGetCypherResults(Arg.Any()) - .Returns(Enumerable.Empty()); - - var cypher = new CypherFluentQuery(client); - var results = cypher - .Start("a", (NodeReference)1) - .Return(a => new ReturnPropertyQueryResult - { - SomethingTotallyDifferent = a.As().Age, - }) - .Results; - - Assert.IsInstanceOf>(results); - } - - [Test] - public void ReturnColumnAliasOfTypeEnum() - { - // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias - // START a=node(1) - // RETURN a.Age AS SomethingTotallyDifferent - - var client = Substitute.For(); - - client - .ExecuteGetCypherResults(Arg.Any()) - .Returns(Enumerable.Empty()); - - var cypher = new CypherFluentQuery(client); - var results = cypher - .Start("a", (NodeReference)1) - .Return(a => new FooNode - { - TheType = a.As().TheType, - }) - .Results; - - Assert.IsInstanceOf>(results); - } - - [Test] - public void ReturnNodeAsSet() - { - var client = Substitute.For(); - var set = new[] { new Node(new FooNode(), new NodeReference(123)) }; - client - .ExecuteGetCypherResults>( - Arg.Is(q => q.ResultMode == CypherResultMode.Set)) - .Returns(set); - - var cypher = new CypherFluentQuery(client); - var results = cypher - .Start("a", (NodeReference)1) - .Return>("a") - .Results; - - CollectionAssert.AreEqual(set, results); - } - - [Test] - public void ReturnRelationshipWithDataAsSet() - { - var client = Substitute.For(); - var set = new[] { new RelationshipInstance(new RelationshipReference(1), new NodeReference(0), new NodeReference(2),"Type", new FooNode()) }; - client - .ExecuteGetCypherResults>( - Arg.Is(q => q.ResultMode == CypherResultMode.Set)) - .Returns(set); - - var cypher = new CypherFluentQuery(client); - var results = cypher - .Start("a", (RelationshipReference)1) - .Return>("a") - .Results; - - CollectionAssert.AreEqual(set, results); - } - - [Test] - public void ReturnRelationshipAsSet() - { - var client = Substitute.For(); - var set = new[] { new RelationshipInstance(new RelationshipReference(1), new NodeReference(0), new NodeReference(2), "Type") }; - client - .ExecuteGetCypherResults( - Arg.Is(q => q.ResultMode == CypherResultMode.Set)) - .Returns(set); - - var cypher = new CypherFluentQuery(client); - var results = cypher - .Start("a", (RelationshipReference)1) - .Return("a") - .Results; - - CollectionAssert.AreEqual(set, results); - } - - [Test] - public void ExecutingQueryMultipleTimesShouldResetParameters() - { - var client = Substitute.For(); - - client - .ExecuteGetCypherResults(Arg.Any()) - .Returns(Enumerable.Empty()); - - var cypher = new CypherFluentQuery(client); - var query1 = cypher - .Start("a", (NodeReference)1) - .Return("a.Name") - .Query; - - Assert.AreEqual(1, query1.QueryParameters.Count()); - Assert.AreEqual(1, query1.QueryParameters["p0"]); - - var query2 = cypher - .Start("b", (NodeReference)2) - .Return("a.Name") - .Query; - - Assert.AreEqual(1, query2.QueryParameters.Count()); - Assert.AreEqual(2, query2.QueryParameters["p0"]); - } - - public enum MyType {Type1, Type2} - - public class FooNode - { - public int Age { get; set; } - public MyType TheType { get; set; } - } - - public class ReturnPropertyQueryResult - { - public int SomethingTotallyDifferent { get; set; } - } - } -} +using System.Collections.Generic; +using System.Linq; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQueryResultsTests + { + [Test] + public void ReturnColumnAlias() + { + // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias + // START a=node(1) + // RETURN a.Age AS SomethingTotallyDifferent + + var client = Substitute.For(); + + client + .ExecuteGetCypherResults(Arg.Any()) + .Returns(Enumerable.Empty()); + + var cypher = new CypherFluentQuery(client); + var results = cypher + .Start("a", (NodeReference)1) + .Return(a => new ReturnPropertyQueryResult + { + SomethingTotallyDifferent = a.As().Age, + }) + .Results; + + Assert.IsInstanceOf>(results); + } + + [Test] + public void ReturnColumnAliasOfTypeEnum() + { + // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias + // START a=node(1) + // RETURN a.Age AS SomethingTotallyDifferent + + var client = Substitute.For(); + + client + .ExecuteGetCypherResults(Arg.Any()) + .Returns(Enumerable.Empty()); + + var cypher = new CypherFluentQuery(client); + var results = cypher + .Start("a", (NodeReference)1) + .Return(a => new FooNode + { + TheType = a.As().TheType, + }) + .Results; + + Assert.IsInstanceOf>(results); + } + + [Test] + public void ReturnNodeAsSet() + { + var client = Substitute.For(); + var set = new[] { new Node(new FooNode(), new NodeReference(123)) }; + client + .ExecuteGetCypherResults>( + Arg.Is(q => q.ResultMode == CypherResultMode.Set)) + .Returns(set); + + var cypher = new CypherFluentQuery(client); + var results = cypher + .Start("a", (NodeReference)1) + .Return>("a") + .Results; + + CollectionAssert.AreEqual(set, results); + } + + [Test] + public void ReturnRelationshipWithDataAsSet() + { + var client = Substitute.For(); + var set = new[] { new RelationshipInstance(new RelationshipReference(1), new NodeReference(0), new NodeReference(2),"Type", new FooNode()) }; + client + .ExecuteGetCypherResults>( + Arg.Is(q => q.ResultMode == CypherResultMode.Set)) + .Returns(set); + + var cypher = new CypherFluentQuery(client); + var results = cypher + .Start("a", (RelationshipReference)1) + .Return>("a") + .Results; + + CollectionAssert.AreEqual(set, results); + } + + [Test] + public void ReturnRelationshipAsSet() + { + var client = Substitute.For(); + var set = new[] { new RelationshipInstance(new RelationshipReference(1), new NodeReference(0), new NodeReference(2), "Type") }; + client + .ExecuteGetCypherResults( + Arg.Is(q => q.ResultMode == CypherResultMode.Set)) + .Returns(set); + + var cypher = new CypherFluentQuery(client); + var results = cypher + .Start("a", (RelationshipReference)1) + .Return("a") + .Results; + + CollectionAssert.AreEqual(set, results); + } + + [Test] + public void ExecutingQueryMultipleTimesShouldResetParameters() + { + var client = Substitute.For(); + + client + .ExecuteGetCypherResults(Arg.Any()) + .Returns(Enumerable.Empty()); + + var cypher = new CypherFluentQuery(client); + var query1 = cypher + .Start("a", (NodeReference)1) + .Return("a.Name") + .Query; + + Assert.AreEqual(1, query1.QueryParameters.Count()); + Assert.AreEqual(1, query1.QueryParameters["p0"]); + + var query2 = cypher + .Start("b", (NodeReference)2) + .Return("a.Name") + .Query; + + Assert.AreEqual(1, query2.QueryParameters.Count()); + Assert.AreEqual(2, query2.QueryParameters["p0"]); + } + + public enum MyType {Type1, Type2} + + public class FooNode + { + public int Age { get; set; } + public MyType TheType { get; set; } + } + + public class ReturnPropertyQueryResult + { + public int SomethingTotallyDifferent { get; set; } + } + } +} diff --git a/Test/Cypher/CypherFluentQueryReturnTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs similarity index 91% rename from Test/Cypher/CypherFluentQueryReturnTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs index f75217925..003cbf13b 100644 --- a/Test/Cypher/CypherFluentQueryReturnTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs @@ -1,637 +1,655 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using Newtonsoft.Json.Serialization; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.ApiModels.Cypher; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - using System.Linq.Expressions; - using System.Text.RegularExpressions; - - [TestFixture] - public class CypherFluentQueryReturnTests - { - [Test] - public void ReturnDistinct() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .ReturnDistinct("n") - .Query; - - Assert.AreEqual("START n=node({p0})\r\nRETURN distinct n", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void ReturnDistinctWithLimit() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .ReturnDistinct("n") - .Limit(5) - .Query; - - Assert.AreEqual("START n=node({p0})\r\nRETURN distinct n\r\nLIMIT {p1}", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - Assert.AreEqual(5, query.QueryParameters["p1"]); - } - - [Test] - public void ReturnDistinctWithLimitAndOrderBy() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .ReturnDistinct("n") - .OrderBy("n.Foo") - .Limit(5) - .Query; - - Assert.AreEqual("START n=node({p0})\r\nRETURN distinct n\r\nORDER BY n.Foo\r\nLIMIT {p1}", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - Assert.AreEqual(5, query.QueryParameters["p1"]); - } - - [Test] - public void ReturnIdentity() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Return("n") - .Query; - - Assert.AreEqual("START n=node({p0})\r\nRETURN n", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void ReturnWithLimit() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Return("n") - .Limit(5) - .Query; - - Assert.AreEqual("START n=node({p0})\r\nRETURN n\r\nLIMIT {p1}", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - Assert.AreEqual(5, query.QueryParameters["p1"]); - } - - [Test] - public void ReturnWithLimitAndOrderBy() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Return("n") - .OrderBy("n.Foo") - .Limit(5) - .Query; - - Assert.AreEqual("START n=node({p0})\r\nRETURN n\r\nORDER BY n.Foo\r\nLIMIT {p1}", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - Assert.AreEqual(5, query.QueryParameters["p1"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/42")] - public void ShouldCombineWithLimitAndOrderBy() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new - { - me = (NodeReference)123, - viewer = (NodeReference)456 - }) - .Match("me-[:FRIEND]-common-[:FRIEND]-viewer") - .Return>("common") - .Limit(5) - .OrderBy("common.FirstName") - .Query; - - Assert.AreEqual(@"START me=node({p0}), viewer=node({p1}) -MATCH me-[:FRIEND]-common-[:FRIEND]-viewer -RETURN common -LIMIT {p2} -ORDER BY common.FirstName", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual(456, query.QueryParameters["p1"]); - Assert.AreEqual(5, query.QueryParameters["p2"]); - } - - [Test] - public void ShouldReturnRawFunctionCall() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("count(item)") - .Query; - - Assert.AreEqual("RETURN count(item)", query.QueryText); - } - - [Test] - public void ReturnsWhenIdentityIsACollection() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Match("(n0),(n1)") - .Return>("[n0,n1]") - .Query; - - Assert.AreEqual("MATCH (n0),(n1)\r\nRETURN [n0,n1]", query.QueryText); - } - - [Test] - public void ReturnsWhenIdentityIsACollectionRegardlessOfPadding() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Match("(n0),(n1)") - .Return>(" [n0,n1] ") - .Query; - - Assert.AreEqual("MATCH (n0),(n1)\r\nRETURN [n0,n1]", query.QueryText); - } - - [Test] - public void ShouldThrowWhenIdentityIsCollectionButResultIsNot() - { - var client = Substitute.For(); - var ex = Assert.Throws( - () => new CypherFluentQuery(client).Return("[foo,bar]") - ); - StringAssert.StartsWith(CypherFluentQuery.IdentityLooksLikeACollectionButTheResultIsNotEnumerableMessage, ex.Message); - } - - [Test] - public void ShouldThrowWhenIdentityLooksLikeAMultiColumnStatement() - { - var client = Substitute.For(); - var ex = Assert.Throws( - () => new CypherFluentQuery(client).Return("foo,bar") - ); - StringAssert.StartsWith(CypherFluentQuery.IdentityLooksLikeAMultiColumnStatementExceptionMessage, ex.Message); - } - - [Test] - public void ShouldReturnCountOnItsOwn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(item => item.Count()) - .Query; - - Assert.AreEqual("RETURN count(item)", query.QueryText); - } - - [Test] - public void ShouldReturnCountAllOnItsOwn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(() => All.Count()) - .Query; - - Assert.AreEqual("RETURN count(*)", query.QueryText); - } - - [Test] - public void ShouldReturnCustomFunctionCall() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(() => Return.As("sum(foo.bar)")) - .Query; - - Assert.AreEqual("RETURN sum(foo.bar)", query.QueryText); - } - - [Test] - public void ShouldReturnSpecificPropertyOnItsOwn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(a => a.As().Name) - .Query; - - Assert.AreEqual("RETURN a.Name", query.QueryText); - } - - [Test] - public void ShouldReturnSpecificPropertyOnItsOwnCamel() - { - var client = Substitute.For(); - client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var query = new CypherFluentQuery(client) - .Return(a => a.As().Name) - .Query; - - Assert.AreEqual("RETURN a.name", query.QueryText); - } - - [Test] - public void ShouldThrowForMemberExpressionOffMethodOtherThanAs() - { - var client = Substitute.For(); - Assert.Throws( - () => new CypherFluentQuery(client).Return(a => a.Type().Length)); - } - - [Test] - public void ShouldUseSetResultModeForIdentityBasedReturn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("foo") - .Query; - - Assert.AreEqual(CypherResultMode.Set, query.ResultMode); - } - - [Test] - public void ShouldUseSetResultModeForRawFunctionCallReturn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("count(foo)") - .Query; - - Assert.AreEqual(CypherResultMode.Set, query.ResultMode); - } - - [Test] - public void ShouldUseSetResultModeForSimpleLambdaReturn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(item => item.As()) - .Query; - - Assert.AreEqual(CypherResultMode.Set, query.ResultMode); - } - - [Test] - public void ShouldUseSetResultModeForSingleFunctionReturn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(item => item.Count()) - .Query; - - Assert.AreEqual(CypherResultMode.Set, query.ResultMode); - } - - [Test] - public void ShouldUseSetResultModeForAllFunctionReturn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(() => All.Count()) - .Query; - - Assert.AreEqual(CypherResultMode.Set, query.ResultMode); - } - - [Test] - public void ShouldUseSetResultModeForSpecificPropertyReturn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(a => a.As().Name) - .Query; - - Assert.AreEqual(CypherResultMode.Set, query.ResultMode); - } - - [Test] - public void ShouldUseSetResultModeForSpecificPropertyReturnCamel() - { - var client = Substitute.For(); - client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var query = new CypherFluentQuery(client) - .Return(a => a.As().Name) - .Query; - - Assert.AreEqual(CypherResultMode.Set, query.ResultMode); - } - - [Test] - public void ShouldUseProjectionResultModeForAnonymousObjectReturn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(a => new { Foo = a.As() }) - .Query; - - Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); - } - - [Test] - public void ShouldUseProjectionResultModeForNamedObjectReturn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return(a => new ProjectionResult { Commodity = a.As() }) - .Query; - - Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); - } - - [Test] - public void ShouldUseProjectionResultModeForNamedObjectReturnCamel() - { - var client = Substitute.For(); - client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var query = new CypherFluentQuery(client) - .Return(a => new ProjectionResult { Commodity = a.As() }) - .Query; - - Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); - } - - [Test] - public void ShouldSupportAnonymousReturnTypesEndToEnd() - { - const string queryText = "START root=node({p0})\r\nMATCH root-->other\r\nRETURN other AS Foo"; - var parameters = new Dictionary - { - {"p0", 123} - }; - - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Projection); - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Json(HttpStatusCode.OK, @"{ - 'columns' : [ 'Foo' ], - 'data' : [ [ { - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/748/relationships/out', - 'data' : { - 'Name' : 'Antimony', - 'UniqueId' : 38 - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/748/traverse/{returnType}', - 'self' : 'http://localhost:8000/db/data/node/748', - 'property' : 'http://localhost:8000/db/data/node/748/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:8000/db/data/node/748/properties', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/748/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/748/relationships', - 'paged_traverse' : 'http://localhost:8000/db/data/node/748/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:8000/db/data/node/748/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/in/{-list|&|types}' - } ], [ { - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/610/relationships/out', - 'data' : { - 'Name' : 'Bauxite', - 'UniqueId' : 24 - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/610/traverse/{returnType}', - 'self' : 'http://localhost:8000/db/data/node/610', - 'property' : 'http://localhost:8000/db/data/node/610/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:8000/db/data/node/610/properties', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/610/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/610/relationships', - 'paged_traverse' : 'http://localhost:8000/db/data/node/610/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:8000/db/data/node/610/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/in/{-list|&|types}' - } ], [ { - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/749/relationships/out', - 'data' : { - 'Name' : 'Bismuth', - 'UniqueId' : 37 - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/749/traverse/{returnType}', - 'self' : 'http://localhost:8000/db/data/node/749', - 'property' : 'http://localhost:8000/db/data/node/749/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:8000/db/data/node/749/properties', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/749/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/749/relationships', - 'paged_traverse' : 'http://localhost:8000/db/data/node/749/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:8000/db/data/node/749/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/in/{-list|&|types}' - } ] ] -}") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var results = graphClient - .Cypher - .Start("root", graphClient.RootNode) - .Match("root-->other") - .Return(other => new - { - Foo = other.As() - }) - .Results - .ToList(); - - Assert.AreEqual(3, results.Count()); - - var result = results[0]; - Assert.AreEqual("Antimony", result.Foo.Name); - Assert.AreEqual(38, result.Foo.UniqueId); - - result = results[1]; - Assert.AreEqual("Bauxite", result.Foo.Name); - Assert.AreEqual(24, result.Foo.UniqueId); - - result = results[2]; - Assert.AreEqual("Bismuth", result.Foo.Name); - Assert.AreEqual(37, result.Foo.UniqueId); - } - } - - [Test] - public void ShouldSupportAnonymousReturnTypesEndToEndCamel() - { - const string queryText = "START root=node({p0})\r\nMATCH root-->other\r\nRETURN other AS Foo"; - var parameters = new Dictionary - { - {"p0", 123} - }; - - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Projection); - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Json(HttpStatusCode.OK, @"{ - 'columns' : [ 'Foo' ], - 'data' : [ [ { - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/748/relationships/out', - 'data' : { - 'name' : 'Antimony', - 'uniqueId' : 38 - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/748/traverse/{returnType}', - 'self' : 'http://localhost:8000/db/data/node/748', - 'property' : 'http://localhost:8000/db/data/node/748/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:8000/db/data/node/748/properties', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/748/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/748/relationships', - 'paged_traverse' : 'http://localhost:8000/db/data/node/748/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:8000/db/data/node/748/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/in/{-list|&|types}' - } ], [ { - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/610/relationships/out', - 'data' : { - 'name' : 'Bauxite', - 'uniqueId' : 24 - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/610/traverse/{returnType}', - 'self' : 'http://localhost:8000/db/data/node/610', - 'property' : 'http://localhost:8000/db/data/node/610/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:8000/db/data/node/610/properties', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/610/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/610/relationships', - 'paged_traverse' : 'http://localhost:8000/db/data/node/610/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:8000/db/data/node/610/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/in/{-list|&|types}' - } ], [ { - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/749/relationships/out', - 'data' : { - 'name' : 'Bismuth', - 'uniqueId' : 37 - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/749/traverse/{returnType}', - 'self' : 'http://localhost:8000/db/data/node/749', - 'property' : 'http://localhost:8000/db/data/node/749/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:8000/db/data/node/749/properties', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/749/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/749/relationships', - 'paged_traverse' : 'http://localhost:8000/db/data/node/749/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:8000/db/data/node/749/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/in/{-list|&|types}' - } ] ] -}") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var results = graphClient - .Cypher - .Start("root", graphClient.RootNode) - .Match("root-->other") - .Return(other => new - { - Foo = other.As() - }) - .Results - .ToList(); - - Assert.AreEqual(3, results.Count()); - - var result = results[0]; - Assert.AreEqual("Antimony", result.Foo.Name); - Assert.AreEqual(38, result.Foo.UniqueId); - - result = results[1]; - Assert.AreEqual("Bauxite", result.Foo.Name); - Assert.AreEqual(24, result.Foo.UniqueId); - - result = results[2]; - Assert.AreEqual("Bismuth", result.Foo.Name); - Assert.AreEqual(37, result.Foo.UniqueId); - } - } - - [Test] - public void BinaryExpressionIsNotNull() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Match("(a)") - .Return(a => new {NotNull = a != null}) - .Query; - - Assert.AreEqual("MATCH (a)\r\nRETURN a IS NOT NULL AS NotNull", query.QueryText); - } - - [Test] - public void BinaryExpressionIsNull() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Match("(a)") - .Return(a => new { IsNull = a == null }) - .Query; - - Assert.AreEqual("MATCH (a)\r\nRETURN a IS NULL AS IsNull", query.QueryText); - } - - [Test] - public void BinaryExpressionThrowNotSupportedExceptionForUnsupportedExpressionComparison() - { - var client = Substitute.For(); - var ex = Assert.Throws(() => new CypherFluentQuery(client).Return(a => new { IsNull = a.As() == 10 })); - - StringAssert.StartsWith(CypherReturnExpressionBuilder.UnsupportedBinaryExpressionComparisonExceptionMessage, ex.Message); - } - - [Test] - public void BinaryExpressionThrowNotSupportedExceptionForUnsupportedExpressionTypes() - { - var client = Substitute.For(); - var ex = Assert.Throws(() => new CypherFluentQuery(client).Return(a => new {IsNull = a.As() > 10})); - - var message = string.Format(CypherReturnExpressionBuilder.UnsupportedBinaryExpressionExceptionMessageFormat, ExpressionType.GreaterThan); - StringAssert.StartsWith(message, ex.Message); - } - - public class Commodity - { - public string Name { get; set; } - public long UniqueId { get; set; } - } - - public class ProjectionResult - { - public Commodity Commodity { get; set; } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using Newtonsoft.Json.Serialization; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + using System.Linq.Expressions; + using System.Text.RegularExpressions; + + [TestFixture] + public class CypherFluentQueryReturnTests + { + [Test] + public void ReturnDistinct() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .ReturnDistinct("n") + .Query; + + Assert.AreEqual("START n=node({p0})\r\nRETURN distinct n", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ReturnDistinctWithLimit() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .ReturnDistinct("n") + .Limit(5) + .Query; + + Assert.AreEqual("START n=node({p0})\r\nRETURN distinct n\r\nLIMIT {p1}", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(5, query.QueryParameters["p1"]); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ReturnDistinctWithLimitAndOrderBy() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .ReturnDistinct("n") + .OrderBy("n.Foo") + .Limit(5) + .Query; + + Assert.AreEqual("START n=node({p0})\r\nRETURN distinct n\r\nORDER BY n.Foo\r\nLIMIT {p1}", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(5, query.QueryParameters["p1"]); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ReturnIdentity() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Return("n") + .Query; + + Assert.AreEqual("START n=node({p0})\r\nRETURN n", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ReturnWithLimit() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Return("n") + .Limit(5) + .Query; + + Assert.AreEqual("START n=node({p0})\r\nRETURN n\r\nLIMIT {p1}", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(5, query.QueryParameters["p1"]); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ReturnWithLimitAndOrderBy() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Return("n") + .OrderBy("n.Foo") + .Limit(5) + .Query; + + Assert.AreEqual("START n=node({p0})\r\nRETURN n\r\nORDER BY n.Foo\r\nLIMIT {p1}", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(5, query.QueryParameters["p1"]); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/42")] + public void ShouldCombineWithLimitAndOrderBy() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new + { + me = (NodeReference)123, + viewer = (NodeReference)456 + }) + .Match("me-[:FRIEND]-common-[:FRIEND]-viewer") + .Return>("common") + .Limit(5) + .OrderBy("common.FirstName") + .Query; + + Assert.AreEqual(string.Format("START me=node({{p0}}), viewer=node({{p1}}){0}MATCH me-[:FRIEND]-common-[:FRIEND]-viewer{0}RETURN common{0}LIMIT {{p2}}{0}ORDER BY common.FirstName", Environment.NewLine), query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual(456, query.QueryParameters["p1"]); + Assert.AreEqual(5, query.QueryParameters["p2"]); + Assert.AreEqual(CypherResultFormat.Rest, query.ResultFormat); + } + + [Test] + public void ShouldReturnRawFunctionCall() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("count(item)") + .Query; + + Assert.AreEqual("RETURN count(item)", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ReturnsWhenIdentityIsACollection() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Match("(n0),(n1)") + .Return>("[n0,n1]") + .Query; + + Assert.AreEqual("MATCH (n0),(n1)\r\nRETURN [n0,n1]", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ReturnsWhenIdentityIsACollectionRegardlessOfPadding() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Match("(n0),(n1)") + .Return>(" [n0,n1] ") + .Query; + + Assert.AreEqual("MATCH (n0),(n1)\r\nRETURN [n0,n1]", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldThrowWhenIdentityIsCollectionButResultIsNot() + { + var client = Substitute.For(); + var ex = Assert.Throws( + () => new CypherFluentQuery(client).Return("[foo,bar]") + ); + StringAssert.StartsWith(CypherFluentQuery.IdentityLooksLikeACollectionButTheResultIsNotEnumerableMessage, ex.Message); + } + + [Test] + public void ShouldThrowWhenIdentityLooksLikeAMultiColumnStatement() + { + var client = Substitute.For(); + var ex = Assert.Throws( + () => new CypherFluentQuery(client).Return("foo,bar") + ); + StringAssert.StartsWith(CypherFluentQuery.IdentityLooksLikeAMultiColumnStatementExceptionMessage, ex.Message); + } + + [Test] + public void ShouldReturnCountOnItsOwn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(item => item.Count()) + .Query; + + Assert.AreEqual("RETURN count(item)", query.QueryText); + } + + [Test] + public void ShouldReturnCountAllOnItsOwn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(() => All.Count()) + .Query; + + Assert.AreEqual("RETURN count(*)", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldReturnCustomFunctionCall() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(() => Return.As("sum(foo.bar)")) + .Query; + + Assert.AreEqual("RETURN sum(foo.bar)", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldReturnSpecificPropertyOnItsOwn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(a => a.As().Name) + .Query; + + Assert.AreEqual("RETURN a.Name", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldReturnSpecificPropertyOnItsOwnCamel() + { + var client = Substitute.For(); + client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var query = new CypherFluentQuery(client) + .Return(a => a.As().Name) + .Query; + + Assert.AreEqual("RETURN a.name", query.QueryText); + } + + [Test] + public void ShouldThrowForMemberExpressionOffMethodOtherThanAs() + { + var client = Substitute.For(); + Assert.Throws( + () => new CypherFluentQuery(client).Return(a => a.Type().Length)); + } + + [Test] + public void ShouldUseSetResultModeForIdentityBasedReturn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("foo") + .Query; + + Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldUseSetResultModeForRawFunctionCallReturn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("count(foo)") + .Query; + + Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldUseSetResultModeForSimpleLambdaReturn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(item => item.As()) + .Query; + + Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + } + + [Test] + public void ShouldUseSetResultModeForSingleFunctionReturn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(item => item.Count()) + .Query; + + Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldUseSetResultModeForAllFunctionReturn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(() => All.Count()) + .Query; + + Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldUseSetResultModeForSpecificPropertyReturn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(a => a.As().Name) + .Query; + + Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldUseSetResultModeForSpecificPropertyReturnCamel() + { + var client = Substitute.For(); + client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var query = new CypherFluentQuery(client) + .Return(a => a.As().Name) + .Query; + + Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + } + + [Test] + public void ShouldUseProjectionResultModeForAnonymousObjectReturn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(a => new { Foo = a.As() }) + .Query; + + Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldUseProjectionResultModeForNamedObjectReturn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return(a => new ProjectionResult { Commodity = a.As() }) + .Query; + + Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void ShouldUseProjectionResultModeForNamedObjectReturnCamel() + { + var client = Substitute.For(); + client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var query = new CypherFluentQuery(client) + .Return(a => new ProjectionResult { Commodity = a.As() }) + .Query; + + Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + } + + [Test] + public void ShouldSupportAnonymousReturnTypesEndToEnd() + { + const string queryText = "START root=node({p0})\r\nMATCH root-->other\r\nRETURN other AS Foo"; + var parameters = new Dictionary + { + {"p0", 123} + }; + + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Projection, CypherResultFormat.Rest); + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Json(HttpStatusCode.OK, @"{ + 'columns' : [ 'Foo' ], + 'data' : [ [ { + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/748/relationships/out', + 'data' : { + 'Name' : 'Antimony', + 'UniqueId' : 38 + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/748/traverse/{returnType}', + 'self' : 'http://localhost:8000/db/data/node/748', + 'property' : 'http://localhost:8000/db/data/node/748/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:8000/db/data/node/748/properties', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/748/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/748/relationships', + 'paged_traverse' : 'http://localhost:8000/db/data/node/748/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:8000/db/data/node/748/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/in/{-list|&|types}' + } ], [ { + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/610/relationships/out', + 'data' : { + 'Name' : 'Bauxite', + 'UniqueId' : 24 + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/610/traverse/{returnType}', + 'self' : 'http://localhost:8000/db/data/node/610', + 'property' : 'http://localhost:8000/db/data/node/610/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:8000/db/data/node/610/properties', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/610/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/610/relationships', + 'paged_traverse' : 'http://localhost:8000/db/data/node/610/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:8000/db/data/node/610/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/in/{-list|&|types}' + } ], [ { + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/749/relationships/out', + 'data' : { + 'Name' : 'Bismuth', + 'UniqueId' : 37 + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/749/traverse/{returnType}', + 'self' : 'http://localhost:8000/db/data/node/749', + 'property' : 'http://localhost:8000/db/data/node/749/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:8000/db/data/node/749/properties', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/749/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/749/relationships', + 'paged_traverse' : 'http://localhost:8000/db/data/node/749/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:8000/db/data/node/749/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/in/{-list|&|types}' + } ] ] +}") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var results = graphClient + .Cypher + .Start("root", graphClient.RootNode) + .Match("root-->other") + .Return(other => new + { + Foo = other.As() + }) + .Results + .ToList(); + + Assert.AreEqual(3, results.Count()); + + var result = results[0]; + Assert.AreEqual("Antimony", result.Foo.Name); + Assert.AreEqual(38, result.Foo.UniqueId); + + result = results[1]; + Assert.AreEqual("Bauxite", result.Foo.Name); + Assert.AreEqual(24, result.Foo.UniqueId); + + result = results[2]; + Assert.AreEqual("Bismuth", result.Foo.Name); + Assert.AreEqual(37, result.Foo.UniqueId); + } + } + + [Test] + public void ShouldSupportAnonymousReturnTypesEndToEndCamel() + { + const string queryText = "START root=node({p0})\r\nMATCH root-->other\r\nRETURN other AS Foo"; + var parameters = new Dictionary + { + {"p0", 123} + }; + + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Projection); + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Json(HttpStatusCode.OK, @"{ + 'columns' : [ 'Foo' ], + 'data' : [ [ { + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/748/relationships/out', + 'data' : { + 'name' : 'Antimony', + 'uniqueId' : 38 + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/748/traverse/{returnType}', + 'self' : 'http://localhost:8000/db/data/node/748', + 'property' : 'http://localhost:8000/db/data/node/748/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:8000/db/data/node/748/properties', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/748/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/748/relationships', + 'paged_traverse' : 'http://localhost:8000/db/data/node/748/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:8000/db/data/node/748/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/748/relationships/in/{-list|&|types}' + } ], [ { + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/610/relationships/out', + 'data' : { + 'name' : 'Bauxite', + 'uniqueId' : 24 + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/610/traverse/{returnType}', + 'self' : 'http://localhost:8000/db/data/node/610', + 'property' : 'http://localhost:8000/db/data/node/610/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:8000/db/data/node/610/properties', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/610/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/610/relationships', + 'paged_traverse' : 'http://localhost:8000/db/data/node/610/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:8000/db/data/node/610/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/610/relationships/in/{-list|&|types}' + } ], [ { + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/749/relationships/out', + 'data' : { + 'name' : 'Bismuth', + 'uniqueId' : 37 + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/749/traverse/{returnType}', + 'self' : 'http://localhost:8000/db/data/node/749', + 'property' : 'http://localhost:8000/db/data/node/749/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:8000/db/data/node/749/properties', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/749/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/749/relationships', + 'paged_traverse' : 'http://localhost:8000/db/data/node/749/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:8000/db/data/node/749/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/749/relationships/in/{-list|&|types}' + } ] ] +}") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + graphClient.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var results = graphClient + .Cypher + .Start("root", graphClient.RootNode) + .Match("root-->other") + .Return(other => new + { + Foo = other.As() + }) + .Results + .ToList(); + + Assert.AreEqual(3, results.Count()); + + var result = results[0]; + Assert.AreEqual("Antimony", result.Foo.Name); + Assert.AreEqual(38, result.Foo.UniqueId); + + result = results[1]; + Assert.AreEqual("Bauxite", result.Foo.Name); + Assert.AreEqual(24, result.Foo.UniqueId); + + result = results[2]; + Assert.AreEqual("Bismuth", result.Foo.Name); + Assert.AreEqual(37, result.Foo.UniqueId); + } + } + + [Test] + public void BinaryExpressionIsNotNull() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Match("(a)") + .Return(a => new {NotNull = a != null}) + .Query; + + Assert.AreEqual("MATCH (a)\r\nRETURN a IS NOT NULL AS NotNull", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void BinaryExpressionIsNull() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Match("(a)") + .Return(a => new { IsNull = a == null }) + .Query; + + Assert.AreEqual("MATCH (a)\r\nRETURN a IS NULL AS IsNull", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); + } + + [Test] + public void BinaryExpressionThrowNotSupportedExceptionForUnsupportedExpressionComparison() + { + var client = Substitute.For(); + var ex = Assert.Throws(() => new CypherFluentQuery(client).Return(a => new { IsNull = a.As() == 10 })); + + StringAssert.StartsWith(CypherReturnExpressionBuilder.UnsupportedBinaryExpressionComparisonExceptionMessage, ex.Message); + } + + [Test] + public void BinaryExpressionThrowNotSupportedExceptionForUnsupportedExpressionTypes() + { + var client = Substitute.For(); + var ex = Assert.Throws(() => new CypherFluentQuery(client).Return(a => new {IsNull = a.As() > 10})); + + var message = string.Format(CypherReturnExpressionBuilder.UnsupportedBinaryExpressionExceptionMessageFormat, ExpressionType.GreaterThan); + StringAssert.StartsWith(message, ex.Message); + } + + public class Commodity + { + public string Name { get; set; } + public long UniqueId { get; set; } + } + + public class ProjectionResult + { + public Commodity Commodity { get; set; } + } + } +} diff --git a/Test/Cypher/CypherFluentQuerySetTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQuerySetTests.cs similarity index 96% rename from Test/Cypher/CypherFluentQuerySetTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQuerySetTests.cs index 3e19580f5..1ede3a138 100644 --- a/Test/Cypher/CypherFluentQuerySetTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQuerySetTests.cs @@ -1,42 +1,42 @@ -using NUnit.Framework; -using NSubstitute; -using Neo4jClient.Cypher; -using System; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQuerySetTests - { - [Test] - public void SetProperty() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Set("n.age = 30") - .Return>("n") - .Query; - - // Assert - Assert.AreEqual("START n=node({p0})\r\nSET n.age = 30\r\nRETURN n", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void SetWithoutReturn() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference) 3) - .Set("n.name = \"Ted\"") - .Query; - - // Assert - Assert.AreEqual("START n=node({p0})\r\nSET n.name = \"Ted\"", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - } -} +using NUnit.Framework; +using NSubstitute; +using Neo4jClient.Cypher; +using System; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQuerySetTests + { + [Test] + public void SetProperty() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Set("n.age = 30") + .Return>("n") + .Query; + + // Assert + Assert.AreEqual("START n=node({p0})\r\nSET n.age = 30\r\nRETURN n", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + public void SetWithoutReturn() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference) 3) + .Set("n.name = \"Ted\"") + .Query; + + // Assert + Assert.AreEqual("START n=node({p0})\r\nSET n.name = \"Ted\"", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + } +} diff --git a/Test/Cypher/CypherFluentQuerySkipTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQuerySkipTests.cs similarity index 97% rename from Test/Cypher/CypherFluentQuerySkipTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQuerySkipTests.cs index 9c2d0040e..24f864773 100644 --- a/Test/Cypher/CypherFluentQuerySkipTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQuerySkipTests.cs @@ -1,104 +1,104 @@ -using NUnit.Framework; -using NSubstitute; -using Neo4jClient.Cypher; -using System; -using System.Linq; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQuerySkipTests - { - [Test] - public void SkipClause() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Return>("n") - .Skip(2) - .Query; - - // Assert - Assert.AreEqual("START n=node({p0})\r\nRETURN n\r\nSKIP {p1}", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual(3, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] - public void SkipClauseAfterReturnClauseIsTyped() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) - .Return>("n") - .Skip(2); - - // Assert - Assert.IsInstanceOf>>(query); - } - - [Test] - public void NullSkipDoesNotWriteClause() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .Return>("n") - .Skip(null) - .Query; - - // Assert - Assert.AreEqual("START n=node({p0})\r\nRETURN n", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] - public void SkipClauseAfterWithClause() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) - .With("foo") - .Skip(2) - .Query; - - - // Assert - Assert.AreEqual("START n=node({p0})\r\nWITH foo\r\nSKIP {p1}", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual(3, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] - public void SkipClauseAfterWithClauseIsUntyped() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) - .With("foo") - .Skip(2); - - // Assert - Assert.IsInstanceOf(query); - var implementsTypedQueryInterface = query - .GetType() - .GetInterfaces() - .Where(i => i.IsGenericType) - .Select(i => i.GetGenericTypeDefinition()) - .Any(t => t == typeof(ICypherFluentQuery<>)); - Assert.IsFalse(implementsTypedQueryInterface, "Implementes ICypherFluentQuery<>"); - } - } -} +using NUnit.Framework; +using NSubstitute; +using Neo4jClient.Cypher; +using System; +using System.Linq; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQuerySkipTests + { + [Test] + public void SkipClause() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Return>("n") + .Skip(2) + .Query; + + // Assert + Assert.AreEqual("START n=node({p0})\r\nRETURN n\r\nSKIP {p1}", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] + public void SkipClauseAfterReturnClauseIsTyped() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { n = new NodeReference(3) }) + .Return>("n") + .Skip(2); + + // Assert + Assert.IsInstanceOf>>(query); + } + + [Test] + public void NullSkipDoesNotWriteClause() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .Return>("n") + .Skip(null) + .Query; + + // Assert + Assert.AreEqual("START n=node({p0})\r\nRETURN n", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] + public void SkipClauseAfterWithClause() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { n = new NodeReference(3) }) + .With("foo") + .Skip(2) + .Query; + + + // Assert + Assert.AreEqual("START n=node({p0})\r\nWITH foo\r\nSKIP {p1}", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/140/support-multiple-limit-order-by-clauses-in")] + public void SkipClauseAfterWithClauseIsUntyped() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { n = new NodeReference(3) }) + .With("foo") + .Skip(2); + + // Assert + Assert.IsInstanceOf(query); + var implementsTypedQueryInterface = query + .GetType() + .GetInterfaces() + .Where(i => i.IsGenericType) + .Select(i => i.GetGenericTypeDefinition()) + .Any(t => t == typeof(ICypherFluentQuery<>)); + Assert.IsFalse(implementsTypedQueryInterface, "Implementes ICypherFluentQuery<>"); + } + } +} diff --git a/Test/Cypher/CypherFluentQueryStartTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryStartTests.cs similarity index 95% rename from Test/Cypher/CypherFluentQueryStartTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryStartTests.cs index 54425f59f..8bb0ffc07 100644 --- a/Test/Cypher/CypherFluentQueryStartTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryStartTests.cs @@ -1,280 +1,278 @@ -using System; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - public class CypherFluentQueryStartTests - { - [Test] - [Obsolete] - public void NodeByIndexLookup() - { - // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup - //START n=node:nodes(name = "A") - //RETURN n - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .StartWithNodeIndexLookup("n", "nodes", "name", "A") - .Return("n") - .Query; - - Assert.AreEqual("START n=node:`nodes`(name = {p0})\r\nRETURN n", query.QueryText); - Assert.AreEqual("A", query.QueryParameters["p0"]); - } - - [Test] - [Obsolete] - public void NodeByIndexLookupMultipleIndexedStartPoints() - { - // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup - //START a=node:nodes(name = "A"), b=node:nodes(name = "B") - //RETURN a - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start( - new CypherStartBitWithNodeIndexLookup("a", "nodes", "name", "A"), - new CypherStartBitWithNodeIndexLookup("b", "nodes", "name", "B") - ) - .Return("a") - .Query; - - Assert.AreEqual("START a=node:`nodes`(name = {p0}), b=node:`nodes`(name = {p1})\r\nRETURN a", query.QueryText); - Assert.AreEqual("A", query.QueryParameters["p0"]); - Assert.AreEqual("B", query.QueryParameters["p1"]); - } - - [Test] - [Obsolete] - public void NodeByIndexLookupWithAdditionalStartPoint() - { - // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup - //START a=node:nodes(name = "A"), b=node(2) - //RETURN a - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start( - new CypherStartBitWithNodeIndexLookup("a", "nodes", "name", "A"), - new CypherStartBit("b", (NodeReference)2) - ) - .Return("a") - .Query; - - Assert.AreEqual("START a=node:`nodes`(name = {p0}), b=node({p1})\r\nRETURN a", query.QueryText); - Assert.AreEqual("A", query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - [Obsolete] - public void NodeByIndexLookupWithAdditionalStartPointAndExtraIndexedStartPoint() - { - // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup - //START a=node:nodes(name = "A"), b=node(2), c=node:nodes(name = "C") - //RETURN a - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start( - new CypherStartBitWithNodeIndexLookup("a", "nodes", "name", "A"), - new CypherStartBit("b", (NodeReference)2), - new CypherStartBitWithNodeIndexLookup("c", "nodes", "name", "C") - ) - .Return("a") - .Query; - - Assert.AreEqual("START a=node:`nodes`(name = {p0}), b=node({p1}), c=node:`nodes`(name = {p2})\r\nRETURN a", query.QueryText); - Assert.AreEqual("A", query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - Assert.AreEqual("C", query.QueryParameters["p2"]); - } - - [Test] - [Obsolete] - public void StartThenNodeByIndexLookup() - { - // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup - //START a=nodes(2), b=node:nodes(name = "B") - //RETURN a - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start( - new CypherStartBit("a", (NodeReference)1), - new CypherStartBitWithNodeIndexLookup("b", "nodes", "name", "B") - ) - .Return("a") - .Query; - - Assert.AreEqual("START a=node({p0}), b=node:`nodes`(name = {p1})\r\nRETURN a", query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual("B", query.QueryParameters["p1"]); - } - - [Test] - [Obsolete] - public void NodeByIndexLookupWithSingleParameter() - { - // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup - //START n=node:nodes("*:*") - //RETURN n - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .StartWithNodeIndexLookup("n", "nodes", "*.*") - .Return("n") - .Query; - - Assert.AreEqual("START n=node:`nodes`({p0})\r\nRETURN n", query.QueryText); - Assert.AreEqual("*.*", query.QueryParameters["p0"]); - } - - [Test] - public void AllNodes() - { - // http://docs.neo4j.org/chunked/1.8/query-start.html#start-all-nodes - //START n=node(*) - //RETURN n - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { n = All.Nodes }) - .Return("n") - .Query; - - Assert.AreEqual("START n=node(*)\r\nRETURN n", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void AllNodesMultipleTimes() - { - // http://docs.neo4j.org/chunked/1.8/query-start.html#start-all-nodes - //START n=node(*), b=node(*) - //RETURN n - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new - { - n = All.Nodes, - b = All.Nodes - }) - .Return("n") - .Query; - - Assert.AreEqual("START n=node(*), b=node(*)\r\nRETURN n", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/56/cypher-fluent-api-node-auto-index-start")] - public void NodeByAutoIndexLookup() - { - // http://stackoverflow.com/questions/14882562/cypher-query-return-related-nodes-as-children/14986114 - // start s=node:node_auto_index(StartType='JobTypes') - // match s-[:starts]->t, t-[:SubTypes]->ts - // return {Id: t.Id, Name: t.Name, JobSpecialties: ts} - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { s = Node.ByIndexLookup("node_auto_index", "StartType", "JobTypes") }) - .Match("s-[:starts]->t", "t-[:SubTypes]->ts") - .Return((t, ts) => new - { - t.As().Id, - t.As().Name, - JobSpecialties = ts.CollectAs() - }) - .Query; - - Assert.AreEqual(@"START s=node:`node_auto_index`(StartType = {p0}) -MATCH s-[:starts]->t, t-[:SubTypes]->ts -RETURN t.Id AS Id, t.Name AS Name, collect(ts) AS JobSpecialties", query.QueryText); - Assert.AreEqual("JobTypes", query.QueryParameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/64/cypher-query-with-multiple-starts")] - public void MutipleNodesByReference() - { - // START n1=node(1), n2=node(2) - // MATCH n1-[:KNOWS]->n2 - // RETURN count(*) - - var referenceA = (NodeReference)1; - var referenceB = (NodeReference)2; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { - n1 = referenceA, - n2 = referenceB - }) - .Query; - - Assert.AreEqual("START n1=node({p0}), n2=node({p1})", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - [Obsolete] - public void MutipleNodesByReferenceObsolete() - { - // https://bitbucket.org/Readify/neo4jclient/issue/64/cypher-query-with-multiple-starts - // START n1=node(1), n2=node(2) - // MATCH n1-[:KNOWS]->n2 - // RETURN count(*) - - var referenceA = (NodeReference)1; - var referenceB = (NodeReference)2; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start( - new CypherStartBit("n1", referenceA), - new CypherStartBit("n2", referenceB) - ) - .Query; - - Assert.AreEqual("START n1=node({p0}), n2=node({p1})", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - public void SingleNodeByStaticReferenceInAnonymousType() - { - // START n1=node(1) - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new - { - n1 = (NodeReference)1 - }) - .Query; - - Assert.AreEqual("START n1=node({p0})", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(1, query.QueryParameters["p0"]); - } - - public class JobType - { - public string Id { get; set; } - public string Name { get; set; } - } - - public class JobSpecialty - { - } - } -} +using System; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + public class CypherFluentQueryStartTests + { + [Test] + [Obsolete] + public void NodeByIndexLookup() + { + // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup + //START n=node:nodes(name = "A") + //RETURN n + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .StartWithNodeIndexLookup("n", "nodes", "name", "A") + .Return("n") + .Query; + + Assert.AreEqual("START n=node:`nodes`(name = {p0})\r\nRETURN n", query.QueryText); + Assert.AreEqual("A", query.QueryParameters["p0"]); + } + + [Test] + [Obsolete] + public void NodeByIndexLookupMultipleIndexedStartPoints() + { + // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup + //START a=node:nodes(name = "A"), b=node:nodes(name = "B") + //RETURN a + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start( + new CypherStartBitWithNodeIndexLookup("a", "nodes", "name", "A"), + new CypherStartBitWithNodeIndexLookup("b", "nodes", "name", "B") + ) + .Return("a") + .Query; + + Assert.AreEqual("START a=node:`nodes`(name = {p0}), b=node:`nodes`(name = {p1})\r\nRETURN a", query.QueryText); + Assert.AreEqual("A", query.QueryParameters["p0"]); + Assert.AreEqual("B", query.QueryParameters["p1"]); + } + + [Test] + [Obsolete] + public void NodeByIndexLookupWithAdditionalStartPoint() + { + // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup + //START a=node:nodes(name = "A"), b=node(2) + //RETURN a + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start( + new CypherStartBitWithNodeIndexLookup("a", "nodes", "name", "A"), + new CypherStartBit("b", (NodeReference)2) + ) + .Return("a") + .Query; + + Assert.AreEqual("START a=node:`nodes`(name = {p0}), b=node({p1})\r\nRETURN a", query.QueryText); + Assert.AreEqual("A", query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + [Obsolete] + public void NodeByIndexLookupWithAdditionalStartPointAndExtraIndexedStartPoint() + { + // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup + //START a=node:nodes(name = "A"), b=node(2), c=node:nodes(name = "C") + //RETURN a + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start( + new CypherStartBitWithNodeIndexLookup("a", "nodes", "name", "A"), + new CypherStartBit("b", (NodeReference)2), + new CypherStartBitWithNodeIndexLookup("c", "nodes", "name", "C") + ) + .Return("a") + .Query; + + Assert.AreEqual("START a=node:`nodes`(name = {p0}), b=node({p1}), c=node:`nodes`(name = {p2})\r\nRETURN a", query.QueryText); + Assert.AreEqual("A", query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + Assert.AreEqual("C", query.QueryParameters["p2"]); + } + + [Test] + [Obsolete] + public void StartThenNodeByIndexLookup() + { + // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup + //START a=nodes(2), b=node:nodes(name = "B") + //RETURN a + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start( + new CypherStartBit("a", (NodeReference)1), + new CypherStartBitWithNodeIndexLookup("b", "nodes", "name", "B") + ) + .Return("a") + .Query; + + Assert.AreEqual("START a=node({p0}), b=node:`nodes`(name = {p1})\r\nRETURN a", query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual("B", query.QueryParameters["p1"]); + } + + [Test] + [Obsolete] + public void NodeByIndexLookupWithSingleParameter() + { + // http://docs.neo4j.org/chunked/1.8.M07/query-start.html#start-node-by-index-lookup + //START n=node:nodes("*:*") + //RETURN n + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .StartWithNodeIndexLookup("n", "nodes", "*.*") + .Return("n") + .Query; + + Assert.AreEqual("START n=node:`nodes`({p0})\r\nRETURN n", query.QueryText); + Assert.AreEqual("*.*", query.QueryParameters["p0"]); + } + + [Test] + public void AllNodes() + { + // http://docs.neo4j.org/chunked/1.8/query-start.html#start-all-nodes + //START n=node(*) + //RETURN n + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { n = All.Nodes }) + .Return("n") + .Query; + + Assert.AreEqual("START n=node(*)\r\nRETURN n", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void AllNodesMultipleTimes() + { + // http://docs.neo4j.org/chunked/1.8/query-start.html#start-all-nodes + //START n=node(*), b=node(*) + //RETURN n + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new + { + n = All.Nodes, + b = All.Nodes + }) + .Return("n") + .Query; + + Assert.AreEqual("START n=node(*), b=node(*)\r\nRETURN n", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/56/cypher-fluent-api-node-auto-index-start")] + public void NodeByAutoIndexLookup() + { + // http://stackoverflow.com/questions/14882562/cypher-query-return-related-nodes-as-children/14986114 + // start s=node:node_auto_index(StartType='JobTypes') + // match s-[:starts]->t, t-[:SubTypes]->ts + // return {Id: t.Id, Name: t.Name, JobSpecialties: ts} + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { s = Node.ByIndexLookup("node_auto_index", "StartType", "JobTypes") }) + .Match("s-[:starts]->t", "t-[:SubTypes]->ts") + .Return((t, ts) => new + { + t.As().Id, + t.As().Name, + JobSpecialties = ts.CollectAs() + }) + .Query; + + Assert.AreEqual(string.Format("START s=node:`node_auto_index`(StartType = {{p0}}){0}MATCH s-[:starts]->t, t-[:SubTypes]->ts{0}RETURN t.Id AS Id, t.Name AS Name, collect(ts) AS JobSpecialties", Environment.NewLine), query.QueryText); + Assert.AreEqual("JobTypes", query.QueryParameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/64/cypher-query-with-multiple-starts")] + public void MutipleNodesByReference() + { + // START n1=node(1), n2=node(2) + // MATCH n1-[:KNOWS]->n2 + // RETURN count(*) + + var referenceA = (NodeReference)1; + var referenceB = (NodeReference)2; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { + n1 = referenceA, + n2 = referenceB + }) + .Query; + + Assert.AreEqual("START n1=node({p0}), n2=node({p1})", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + [Obsolete] + public void MutipleNodesByReferenceObsolete() + { + // https://bitbucket.org/Readify/neo4jclient/issue/64/cypher-query-with-multiple-starts + // START n1=node(1), n2=node(2) + // MATCH n1-[:KNOWS]->n2 + // RETURN count(*) + + var referenceA = (NodeReference)1; + var referenceB = (NodeReference)2; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start( + new CypherStartBit("n1", referenceA), + new CypherStartBit("n2", referenceB) + ) + .Query; + + Assert.AreEqual("START n1=node({p0}), n2=node({p1})", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + public void SingleNodeByStaticReferenceInAnonymousType() + { + // START n1=node(1) + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new + { + n1 = (NodeReference)1 + }) + .Query; + + Assert.AreEqual("START n1=node({p0})", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(1, query.QueryParameters["p0"]); + } + + public class JobType + { + public string Id { get; set; } + public string Name { get; set; } + } + + public class JobSpecialty + { + } + } +} diff --git a/Test/Cypher/CypherFluentQueryTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs similarity index 95% rename from Test/Cypher/CypherFluentQueryTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs index 075e49901..6a0237a81 100644 --- a/Test/Cypher/CypherFluentQueryTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs @@ -1,1119 +1,1093 @@ -using System; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; -using Neo4jClient.Test.GraphClientTests; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQueryTests - { - [Test] - public void ExecutesQuery() - { - // Arrange - var client = Substitute.For(); - CypherQuery executedQuery = null; - client - .When(c => c.ExecuteCypher(Arg.Any())) - .Do(ci => { executedQuery = ci.Arg(); }); - - // Act - new CypherFluentQuery(client) - .Start("n", (NodeReference) 5) - .Delete("n") - .ExecuteWithoutResults(); - - // Assert - Assert.IsNotNull(executedQuery, "Query was not executed against graph client"); - Assert.AreEqual("START n=node({p0})\r\nDELETE n", executedQuery.QueryText); - Assert.AreEqual(5, executedQuery.QueryParameters["p0"]); - } - - [Test] - public void ExecutesQueryAsync() - { - // Arrange - var client = Substitute.For(); - CypherQuery executedQuery = null; - client - .When(c => c.ExecuteCypherAsync(Arg.Any())) - .Do(ci => { executedQuery = ci.Arg(); }); - - // Act - var task = new CypherFluentQuery(client) - .Start("n", (NodeReference) 5) - .Delete("n") - .ExecuteWithoutResultsAsync(); - task.Wait(); - - // Assert - Assert.IsNotNull(executedQuery, "Query was not executed against graph client"); - Assert.AreEqual("START n=node({p0})\r\nDELETE n", executedQuery.QueryText); - Assert.AreEqual(5, executedQuery.QueryParameters["p0"]); - } - - [Test] - public void ShouldBuildQueriesAsImmutableStepsInsteadOfCorruptingPreviousOnes() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)1) - .Return("n"); - - var query1 = query.Query; - query = query.OrderBy("n.Foo"); - var query2 = query.Query; - - Assert.AreEqual("START n=node({p0})\r\nRETURN n", query1.QueryText); - Assert.AreEqual(1, query1.QueryParameters["p0"]); - - Assert.AreEqual("START n=node({p0})\r\nRETURN n\r\nORDER BY n.Foo", query2.QueryText); - Assert.AreEqual(1, query2.QueryParameters["p0"]); - } - - - [Test] - public void AddingStartBitsToDifferentQueriesShouldntCorruptEachOther() - { - var client = Substitute.For(); - var cypher = new CypherFluentQuery(client); - - var query1 = cypher - .Start("a", (NodeReference)1) - .Query; - - var query2 = cypher - .Start("b", (NodeReference)2) - .Query; - - Assert.AreEqual("START a=node({p0})", query1.QueryText); - Assert.AreEqual(1, query1.QueryParameters.Count); - Assert.AreEqual(1, query1.QueryParameters["p0"]); - - Assert.AreEqual("START b=node({p0})", query2.QueryText); - Assert.AreEqual(1, query2.QueryParameters.Count); - Assert.AreEqual(2, query2.QueryParameters["p0"]); - } - - [Test] - public void StartAndReturnNodeById() - { - // http://docs.neo4j.org/chunked/1.6/query-start.html#start-node-by-id - // START n=node(1) - // RETURN n - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)1) - .Return("n") - .Query; - - Assert.AreEqual("START n=node({p0})\r\nRETURN n", query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - } - - [Test] - public void MultipleStartPoints() - { - // http://docs.neo4j.org/chunked/1.6/query-start.html#start-multiple-start-points - // START a=node(1), b=node(2) - // RETURN a,b - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new - { - a = (NodeReference)1, - b = (NodeReference)2 - }) - .Query; - - Assert.AreEqual("START a=node({p0}), b=node({p1})", query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - [Obsolete] - public void MultipleStartPointsObsolete() - { - // http://docs.neo4j.org/chunked/1.6/query-start.html#start-multiple-start-points - // START a=node(1), b=node(2) - // RETURN a,b - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start( - new CypherStartBit("a", (NodeReference)1), - new CypherStartBit("b", (NodeReference)2) - ) - .Query; - - Assert.AreEqual("START a=node({p0}), b=node({p1})", query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - public void ReturnFirstPart() - { - // http://docs.neo4j.org/chunked/1.6/query-limit.html#limit-return-first-part - // START n=node(3, 4, 5, 1, 2) - // RETURN n - // LIMIT 3 - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("n") - .Limit(3) - .Query; - - Assert.AreEqual("RETURN n\r\nLIMIT {p0}", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void SkipFirstThree() - { - // http://docs.neo4j.org/chunked/1.6/query-skip.html#skip-skip-first-three - // START n=node(3, 4, 5, 1, 2) - // RETURN n - // ORDER BY n.name - // SKIP 3 - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("n") - .OrderBy("n.name") - .Skip(3) - .Query; - - Assert.AreEqual("RETURN n\r\nORDER BY n.name\r\nSKIP {p0}", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void ReturnMiddleTwo() - { - // http://docs.neo4j.org/chunked/1.6/query-skip.html#skip-return-middle-two - // START n=node(3, 4, 5, 1, 2) - // RETURN n - // ORDER BY n.name - // SKIP 1 - // LIMIT 2 - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("n") - .OrderBy("n.name") - .Skip(1) - .Limit(2) - .Query; - - Assert.AreEqual("RETURN n\r\nORDER BY n.name\r\nSKIP {p0}\r\nLIMIT {p1}", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - public void OrderNodesByNull() - { - // http://docs.neo4j.org/chunked/stable/query-order.html#order-by-ordering-null - // RETURN n - // ORDER BY n.length? - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("n") - .OrderBy("n.length?") - .Query; - - Assert.AreEqual("RETURN n\r\nORDER BY n.length?", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void OrderNodesByProperty() - { - // http://docs.neo4j.org/chunked/stable/query-order.html#order-by-order-nodes-by-property - // START n=node(3,1,2) - // RETURN n - // ORDER BY n.name - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("n") - .OrderBy("n.name") - .Query; - - Assert.AreEqual("RETURN n\r\nORDER BY n.name", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void OrderNodesByMultipleProperties() - { - // http://docs.neo4j.org/chunked/stable/query-order.html#order-by-order-nodes-by-multiple-properties - // START n=node(3,1,2) - // RETURN n - // ORDER BY n.age, n.name - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("n") - .OrderBy("n.age", "n.name") - .Query; - - Assert.AreEqual("RETURN n\r\nORDER BY n.age, n.name", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void OrderNodesByPropertyDescending() - { - // http://docs.neo4j.org/chunked/stable/query-order.html#order-by-order-nodes-in-descending-order - // START n=node(3,1,2) - // RETURN n - // ORDER BY n.name DESC - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("n") - .OrderByDescending("n.name") - .Query; - - Assert.AreEqual("RETURN n\r\nORDER BY n.name DESC", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void OrderNodesByMultiplePropertiesDescending() - { - // http://docs.neo4j.org/chunked/stable/query-order.html#order-by-order-nodes-in-descending-order - // START n=node(3,1,2) - // RETURN n - // ORDER BY n.age, n.name DESC - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Return("n") - .OrderByDescending("n.age", "n.name") - .Query; - - Assert.AreEqual("RETURN n\r\nORDER BY n.age, n.name DESC", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void ReturnColumnAlias() - { - // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias - // START a=node(1) - // RETURN a.Age AS SomethingTotallyDifferent - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) - .Return(a => new ReturnPropertyQueryResult - { - SomethingTotallyDifferent = a.As().Age - }) - .Query; - - Assert.AreEqual("START a=node({p0})\r\nRETURN a.Age AS SomethingTotallyDifferent", query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - } - - [Test] - public void ReturnUniqueResults() - { - // http://docs.neo4j.org/chunked/1.6/query-return.html#return-unique-results - // START a=node(1) - // MATCH (a)-->(b) - // RETURN distinct b - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) - .Match("(a)-->(b)") - .ReturnDistinct("b") - .Query; - - Assert.AreEqual("START a=node({p0})\r\nMATCH (a)-->(b)\r\nRETURN distinct b", query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - } - - [Test] - public void ReturnUniqueResultsWithExpression() - { - // http://docs.neo4j.org/chunked/1.6/query-return.html#return-unique-results - // START a=node(1) - // MATCH (a)-->(b) - // RETURN distinct b - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) - .Match("(a)-->(b)") - .ReturnDistinct(b => new FooData - { - Age = b.As().Age - }) - .Query; - - Assert.AreEqual("START a=node({p0})\r\nMATCH (a)-->(b)\r\nRETURN distinct b.Age AS Age", query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - } - - [Test] - public void ReturnPropertiesIntoAnonymousType() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) - .Match("(a)-->(b)") - .Return(b => new - { - SomeAge = b.As().Age, - SomeName = b.As().Name - }) - .Query; - - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b) -RETURN b.Age AS SomeAge, b.Name AS SomeName"; - - Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); - } - - [Test] - public void ReturnPropertiesIntoAnonymousTypeWithAutoNames() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) - .Match("(a)-->(b)") - .Return(b => new - { - b.As().Age, - b.As().Name - }) - .Query; - - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b) -RETURN b.Age AS Age, b.Name AS Name"; - - Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); - } - - [Test] - public void ReturnPropertiesFromMultipleNodesIntoAnonymousTypeWithAutoNames() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) - .Match("(a)-->(b)-->(c)") - .Return((b, c) => new - { - b.As().Age, - c.As().Name - }) - .Query; - - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b)-->(c) -RETURN b.Age AS Age, c.Name AS Name"; - - Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); - } - - [Test] - public void ReturnNodeDataIntoAnonymousType() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) - .Match("(a)-->(b)") - .Return((b, c) => new - { - NodeB = b.As(), - }) - .Query; - - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b) -RETURN b AS NodeB"; - - Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); - } - - [Test] - public void ReturnEntireNodeDataAndReferenceIntoAnonymousType() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) - .Match("(a)-->(b)") - .Return((b, c) => new - { - NodeB = b.Node(), - }) - .Query; - - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b) -RETURN b AS NodeB"; - - Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); - } - - [Test] - public void ReturnEntireNodeDataAndReferenceIntoProjectionType() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) - .Match("(a)-->(b)") - .Return((b, c) => new ReturnEntireNodeDataAndReferenceIntoProjectionTypeResult - { - NodeB = b.Node(), - }) - .Query; - - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b) -RETURN b AS NodeB"; - - Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); - } - - public class ReturnEntireNodeDataAndReferenceIntoProjectionTypeResult - { - public Node NodeB { get; set; } - } - - [Test] - public void WhereBooleanOperationWithVariable() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - const string name = "Tobias"; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.Age < 30 && n.Name == name) || n.Name != "Tobias") - .Query; - - Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - Assert.AreEqual("Tobias", query.QueryParameters["p1"]); - Assert.AreEqual("Tobias", query.QueryParameters["p2"]); - } - - [Test] - public void WhereBooleanOperationWithLong() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - const string name = "Tobias"; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.AgeLong < 30 && n.Name == name) || n.Name != "Tobias") - .Query; - - Assert.AreEqual("WHERE (((n.AgeLong < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))".Replace("'", "\""), query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - Assert.AreEqual("Tobias", query.QueryParameters["p1"]); - Assert.AreEqual("Tobias", query.QueryParameters["p2"]); - } - - [Test] - public void WhereBooleanOperationWithNullableLong() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - const string name = "Tobias"; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.AgeLongNullable < 30 && n.Name == name) || n.Name != "Tobias") - .Query; - - Assert.AreEqual("WHERE (((n.AgeLongNullable < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - Assert.AreEqual("Tobias", query.QueryParameters["p1"]); - Assert.AreEqual("Tobias", query.QueryParameters["p2"]); - } - - [Test] - public void WhereBooleanOperationWithStringPropertyOnRightSide() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - var foo = new FooData - { - Name = "Tobias" - }; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.Age < 30 && n.Name == foo.Name) || n.Name != "Tobias") - .Query; - - Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - Assert.AreEqual("Tobias", query.QueryParameters["p1"]); - Assert.AreEqual("Tobias", query.QueryParameters["p2"]); - } - - [Test] - public void WhereBooleanOperationWithIntPropertyOnRightSide() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - var foo = new FooData - { - Name = "Tobias", - Id = 777 - }; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.Age < 30 && n.Id == foo.Id) || n.Name != "Tobias") - .Query; - - Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Id = {p1})) OR (n.Name <> {p2}))", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - Assert.AreEqual(777, query.QueryParameters["p1"]); - Assert.AreEqual("Tobias", query.QueryParameters["p2"]); - } - - [Test] - public void WhereBooleanOperationWithIntConstantOnRightSide() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - int theId = 777; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.Id == theId)) - .Query; - - Assert.AreEqual("WHERE (n.Id = {p0})", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(777, query.QueryParameters["p0"]); - } - - [Test] - public void WhereBooleanOperationWithLongConstantOnRightSide() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - long theId = 777; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.Id == theId)) - .Query; - - Assert.AreEqual("WHERE (n.Id = {p0})", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(777, query.QueryParameters["p0"]); - } - - [Test] - public void WhereBooleanOperationWithLongNullableConstantOnRightSide() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - long? theId = 777; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.Id == theId)) - .Query; - - Assert.AreEqual("WHERE (n.Id = {p0})", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(777, query.QueryParameters["p0"]); - } - - [Test] - public void WhereBooleanOperationWithObjectProperty() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - var fooData = new FooData {Name = "Tobias"}; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.Age < 30 && n.Name == fooData.Name) || n.Name != "Tobias") - .Query; - - Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - Assert.AreEqual("Tobias", query.QueryParameters["p1"]); - Assert.AreEqual("Tobias", query.QueryParameters["p2"]); - } - - [Test] - public void WhereBooleanOperationWithObjectNullableProperty() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - var fooData = new FooData { Id = 777 }; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.Age < 30 && n.Id == fooData.Id) || n.Name != "Tobias") - .Query; - - Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Id = {p1})) OR (n.Name <> {p2}))", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - Assert.AreEqual(777, query.QueryParameters["p1"]); - Assert.AreEqual("Tobias", query.QueryParameters["p2"]); - } - - [Test] - public void WhereBooleanOperations() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations - // START n=node(3, 1) - // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") - // RETURN n - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => (n.Age < 30 && n.Name == "Tobias") || n.Name != "Tobias") - .Query; - - Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - Assert.AreEqual("Tobias", query.QueryParameters["p1"]); - Assert.AreEqual("Tobias", query.QueryParameters["p2"]); - } - - [Test] - public void WhereFilterOnNodeProperty() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-filter-on-node-property - // START n=node(3, 1) - // WHERE n.age < 30 - // RETURN n - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => n.Age < 30 ) - .Query; - - Assert.AreEqual("WHERE (n.Age < {p0})", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - } - - [Test] - public void WhereFilterPropertyExists() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-property-exists - // START n=node(3, 1) - // WHERE has(n.Belt) - // RETURN n - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where("(has(n.Belt))") - .Query; - - Assert.AreEqual("WHERE (has(n.Belt))", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void WhereFilterCompareIfPropertyExists() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-compare-if-property-exists - // START n=node(3, 1) - // WHERE n.Belt = 'white' - // RETURN n - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => n.Id < 30) - .Query; - - Assert.AreEqual("WHERE (n.Id < {p0})", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - } - - [Test] - public void WhereFilterOnNullValues() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(r => r.Name == null && r.Id == 100) - .Query; - - Assert.AreEqual("WHERE ((not(has(r.Name))) AND (r.Id = {p0}))", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual(100, query.QueryParameters["p0"]); - } - - [Test] - public void WhereFilterOnMultipleNodesProperties() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where((n1, n2) => n1.Age < 30 && n2.Key == 11) - .Query; - - Assert.AreEqual("WHERE ((n1.Age < {p0}) AND (n2.Key = {p1}))", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual(30, query.QueryParameters["p0"]); - Assert.AreEqual(11, query.QueryParameters["p1"]); - } - - [Test] - public void WhereFilterOnRelationshipType() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html - // START n=node(3) - // MATCH (n)-[r]->() - // WHERE type(r) = "HOSTS" - // RETURN r - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where("(type(r) = {Hosts})") - .WithParam("Hosts", "HOSTS") - .Query; - - Assert.AreEqual("WHERE (type(r) = {Hosts})", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual("HOSTS", query.QueryParameters["Hosts"]); - } - - [Test] - public void WhereWithAnd() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => n.Name == "bob") - .AndWhere("(type(r) = {Hosts})") - .WithParam("Hosts", "HOSTS") - .Query; - - Assert.AreEqual("WHERE (n.Name = {p0})\r\nAND (type(r) = {Hosts})", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual("bob", query.QueryParameters["p0"]); - Assert.AreEqual("HOSTS", query.QueryParameters["Hosts"]); - } - - [Test] - public void WhereWithOr() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => n.Name == "bob") - .OrWhere("(type(r) = {Hosts})") - .WithParam("Hosts", "HOSTS") - .Query; - - Assert.AreEqual("WHERE (n.Name = {p0})\r\nOR (type(r) = {Hosts})", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual("bob", query.QueryParameters["p0"]); - Assert.AreEqual("HOSTS", query.QueryParameters["Hosts"]); - } - - [Test] - public void WhereWithOrAnd() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where(n => n.Name == "Bob") - .OrWhere("(type(r) = {Hosts})") - .WithParam("Hosts", "HOSTS") - .AndWhere(n => n.Id == 10) - .Query; - - Assert.AreEqual("WHERE (n.Name = {p0})\r\nOR (type(r) = {Hosts})\r\nAND (n.Id = {p2})", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - Assert.AreEqual("Bob", query.QueryParameters["p0"]); - Assert.AreEqual(10, query.QueryParameters["p2"]); - Assert.AreEqual("HOSTS", query.QueryParameters["Hosts"]); - } - - [Test] - public void WhereFilterOnRelationships() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-filter-on-relationships - // START a=node(1), b=node(3, 2) - // WHERE a<--b - // RETURN b - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where("(a<--b)") - .Query; - - Assert.AreEqual("WHERE (a<--b)", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void WhereFilterRegularExpressions() - { - // http://docs.neo4j.org/chunked/1.6/query-where.html#where-regular-expressions - // START n=node(3, 1) - // WHERE n.name =~ /Tob.*/ - // RETURN n - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where("(n.Name =~ /Tob.*/)") - .Query; - - Assert.AreEqual("WHERE (n.Name =~ /Tob.*/)", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void CreateRelationshipBetweenTwoNodes() - { - //http://docs.neo4j.org/chunked/1.8.M06/query-create.html#create-create-a-relationship-between-two-nodes - // START a=node(1), b=node(2) - // CREATE a-[r:REL]->b - // RETURN r - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { - a = (NodeReference)1, - b = (NodeReference)2 - }) - .Create("a-[r:REL]->b") - .Return("r") - .Query; - - Assert.AreEqual("START a=node({p0}), b=node({p1})\r\nCREATE a-[r:REL]->b\r\nRETURN r", query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - public void CreateNode() - { - //http://docs.neo4j.org/chunked/milestone/query-create.html#create-create-single-node-and-set-properties - // CREATE (a {Foo: 'foo', Bar: 'bar', Baz: 'baz'}) - // RETURN a - - var data = new CreateNodeTests.TestNode {Foo = "foo", Bar = "bar", Baz = "baz"}; - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Create("a", data) - .Return("a") - .Query; - Assert.AreEqual("CREATE (a {p0})\r\nRETURN a", query.QueryText); - Assert.AreEqual(data, query.QueryParameters["p0"]); - } - - [Test] - public void CreateAFullPath() { - //http://docs.neo4j.org/chunked/milestone/query-create.html#create-create-a-full-path - // START n=node(1) - // CREATE n-[r:REL]->(a {Foo: 'foo', Bar: 'bar', Baz: 'baz'})-[r:REL]->(b {Foo: 'foo2', Bar: 'bar2', Baz: 'baz2'}) - // RETURN a - - var data1 = new CreateNodeTests.TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - var data2 = new CreateNodeTests.TestNode { Foo = "foo2", Bar = "bar2", Baz = "baz2" }; - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)1) - .Create("n-[r:REL]->(a {0})-[r:REL]->(b {1})", data1, data2) - .Return("a") - .Query; - Assert.AreEqual("START n=node({p0})\r\nCREATE n-[r:REL]->(a {p1})-[r:REL]->(b {p2})\r\nRETURN a", query.QueryText); - Assert.AreEqual(data1, query.QueryParameters["p1"]); - Assert.AreEqual(data2, query.QueryParameters["p2"]); - } - - [Test] - public void CreateRelationshipAndSetProperties() - { - //http://docs.neo4j.org/chunked/1.8.M06/query-create.html#create-create-a-relationship-and-set-properties - //START a=node(1), b=node(2) - //CREATE a-[r:REL {name : a.name + '<->' + b.name }]->b - //RETURN r - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { - a = (NodeReference)1, - b = (NodeReference)2 - }) - .Create("a-[r:REL {name : a.name + '<->' + b.name }]->b") - .Return("r") - .Query; - - Assert.AreEqual("START a=node({p0}), b=node({p1})\r\nCREATE a-[r:REL {name : a.name + '<->' + b.name }]->b\r\nRETURN r", query.QueryText); - Assert.AreEqual(1, query.QueryParameters["p0"]); - Assert.AreEqual(2, query.QueryParameters["p1"]); - } - - [Test] - public void ComplexMatching() - { - // http://docs.neo4j.org/chunked/1.8.M03/query-match.html#match-complex-matching - // START a=node(3) - // MATCH (a)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:BLOCKS]-(d)-[:KNOWS]-(c) - // RETURN a,b,c,d - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)3) - .Match( - "(a)-[:KNOWS]->(b)-[:KNOWS]->(c)", - "(a)-[:BLOCKS]-(d)-[:KNOWS]-(c)") - .Query; - - Assert.AreEqual("START a=node({p0})\r\nMATCH (a)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:BLOCKS]-(d)-[:KNOWS]-(c)", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/45/cyper-should-allow-for-flexible-order-of")] - public void SupportsFlexibleOrderOfClauses() - { - // START me=node:node_auto_index(name='Bob') - // MATCH me-[r?:STATUS]-secondlatestupdate - // DELETE r - // WITH me, secondlatestupdate - // CREATE me-[:STATUS]->(latest_update{text:'Status',date:123}) - // WITH latest_update,secondlatestupdate - // CREATE latest_update-[:NEXT]-secondlatestupdate - // WHERE secondlatestupdate <> null - // RETURN latest_update.text as new_status - - var update = new { text = "Status", date = 123 }; - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new { me = Node.ByIndexLookup("node_auto_index", "name", "Bob") }) - .Match("me-[r?:STATUS]-secondlatestupdate") - .Delete("r") - .With("me, secondlatestupdate") - .Create("me-[:STATUS]->(latest_update {update})") - .WithParams(new { update }) - .With("latest_update,secondlatestupdate") - .Create("latest_update-[:NEXT]-secondlatestupdate") - .Where("secondlatestupdate <> null") -// ReSharper disable InconsistentNaming - .Return(latest_update => new { new_status = latest_update.As().text }) -// ReSharper restore InconsistentNaming - .Query; - - Assert.AreEqual(@"START me=node:`node_auto_index`(name = {p0}) -MATCH me-[r?:STATUS]-secondlatestupdate -DELETE r -WITH me, secondlatestupdate -CREATE me-[:STATUS]->(latest_update {update}) -WITH latest_update,secondlatestupdate -CREATE latest_update-[:NEXT]-secondlatestupdate -WHERE secondlatestupdate <> null -RETURN latest_update.text AS new_status", query.QueryText); - Assert.AreEqual("Bob", query.QueryParameters["p0"]); - Assert.AreEqual(update, query.QueryParameters["update"]); - } - - public class UpdateData - { -// ReSharper disable InconsistentNaming - public string text { get; set; } -// ReSharper restore InconsistentNaming - } - - public class FooData - { - public int Age { get; set; } - public int? Id { get; set; } - public long AgeLong { get; set; } - public long? AgeLongNullable { get; set; } - public string Name { get; set; } - } - - public class BarData - { - public int Key { get; set; } - public string Value { get; set; } - } - - public class ReturnPropertyQueryResult - { - public int SomethingTotallyDifferent { get; set; } - } - } -} +using System; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; +using Neo4jClient.Test.GraphClientTests; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQueryTests + { + [Test] + public void ExecutesQuery() + { + // Arrange + var client = Substitute.For(); + CypherQuery executedQuery = null; + client + .When(c => c.ExecuteCypher(Arg.Any())) + .Do(ci => { executedQuery = ci.Arg(); }); + + // Act + new CypherFluentQuery(client) + .Start("n", (NodeReference) 5) + .Delete("n") + .ExecuteWithoutResults(); + + // Assert + Assert.IsNotNull(executedQuery, "Query was not executed against graph client"); + Assert.AreEqual("START n=node({p0})\r\nDELETE n", executedQuery.QueryText); + Assert.AreEqual(5, executedQuery.QueryParameters["p0"]); + } + + [Test] + public void ExecutesQueryAsync() + { + // Arrange + var client = Substitute.For(); + CypherQuery executedQuery = null; + client + .When(c => c.ExecuteCypherAsync(Arg.Any())) + .Do(ci => { executedQuery = ci.Arg(); }); + + // Act + var task = new CypherFluentQuery(client) + .Start("n", (NodeReference) 5) + .Delete("n") + .ExecuteWithoutResultsAsync(); + task.Wait(); + + // Assert + Assert.IsNotNull(executedQuery, "Query was not executed against graph client"); + Assert.AreEqual("START n=node({p0})\r\nDELETE n", executedQuery.QueryText); + Assert.AreEqual(5, executedQuery.QueryParameters["p0"]); + } + + [Test] + public void ShouldBuildQueriesAsImmutableStepsInsteadOfCorruptingPreviousOnes() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)1) + .Return("n"); + + var query1 = query.Query; + query = query.OrderBy("n.Foo"); + var query2 = query.Query; + + Assert.AreEqual("START n=node({p0})\r\nRETURN n", query1.QueryText); + Assert.AreEqual(1, query1.QueryParameters["p0"]); + + Assert.AreEqual("START n=node({p0})\r\nRETURN n\r\nORDER BY n.Foo", query2.QueryText); + Assert.AreEqual(1, query2.QueryParameters["p0"]); + } + + + [Test] + public void AddingStartBitsToDifferentQueriesShouldntCorruptEachOther() + { + var client = Substitute.For(); + var cypher = new CypherFluentQuery(client); + + var query1 = cypher + .Start("a", (NodeReference)1) + .Query; + + var query2 = cypher + .Start("b", (NodeReference)2) + .Query; + + Assert.AreEqual("START a=node({p0})", query1.QueryText); + Assert.AreEqual(1, query1.QueryParameters.Count); + Assert.AreEqual(1, query1.QueryParameters["p0"]); + + Assert.AreEqual("START b=node({p0})", query2.QueryText); + Assert.AreEqual(1, query2.QueryParameters.Count); + Assert.AreEqual(2, query2.QueryParameters["p0"]); + } + + [Test] + public void StartAndReturnNodeById() + { + // http://docs.neo4j.org/chunked/1.6/query-start.html#start-node-by-id + // START n=node(1) + // RETURN n + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)1) + .Return("n") + .Query; + + Assert.AreEqual("START n=node({p0})\r\nRETURN n", query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + } + + [Test] + public void MultipleStartPoints() + { + // http://docs.neo4j.org/chunked/1.6/query-start.html#start-multiple-start-points + // START a=node(1), b=node(2) + // RETURN a,b + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new + { + a = (NodeReference)1, + b = (NodeReference)2 + }) + .Query; + + Assert.AreEqual("START a=node({p0}), b=node({p1})", query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + [Obsolete] + public void MultipleStartPointsObsolete() + { + // http://docs.neo4j.org/chunked/1.6/query-start.html#start-multiple-start-points + // START a=node(1), b=node(2) + // RETURN a,b + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start( + new CypherStartBit("a", (NodeReference)1), + new CypherStartBit("b", (NodeReference)2) + ) + .Query; + + Assert.AreEqual("START a=node({p0}), b=node({p1})", query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + public void ReturnFirstPart() + { + // http://docs.neo4j.org/chunked/1.6/query-limit.html#limit-return-first-part + // START n=node(3, 4, 5, 1, 2) + // RETURN n + // LIMIT 3 + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("n") + .Limit(3) + .Query; + + Assert.AreEqual("RETURN n\r\nLIMIT {p0}", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + public void SkipFirstThree() + { + // http://docs.neo4j.org/chunked/1.6/query-skip.html#skip-skip-first-three + // START n=node(3, 4, 5, 1, 2) + // RETURN n + // ORDER BY n.name + // SKIP 3 + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("n") + .OrderBy("n.name") + .Skip(3) + .Query; + + Assert.AreEqual("RETURN n\r\nORDER BY n.name\r\nSKIP {p0}", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + public void ReturnMiddleTwo() + { + // http://docs.neo4j.org/chunked/1.6/query-skip.html#skip-return-middle-two + // START n=node(3, 4, 5, 1, 2) + // RETURN n + // ORDER BY n.name + // SKIP 1 + // LIMIT 2 + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("n") + .OrderBy("n.name") + .Skip(1) + .Limit(2) + .Query; + + Assert.AreEqual("RETURN n\r\nORDER BY n.name\r\nSKIP {p0}\r\nLIMIT {p1}", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + public void OrderNodesByNull() + { + // http://docs.neo4j.org/chunked/stable/query-order.html#order-by-ordering-null + // RETURN n + // ORDER BY n.length? + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("n") + .OrderBy("n.length?") + .Query; + + Assert.AreEqual("RETURN n\r\nORDER BY n.length?", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void OrderNodesByProperty() + { + // http://docs.neo4j.org/chunked/stable/query-order.html#order-by-order-nodes-by-property + // START n=node(3,1,2) + // RETURN n + // ORDER BY n.name + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("n") + .OrderBy("n.name") + .Query; + + Assert.AreEqual("RETURN n\r\nORDER BY n.name", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void OrderNodesByMultipleProperties() + { + // http://docs.neo4j.org/chunked/stable/query-order.html#order-by-order-nodes-by-multiple-properties + // START n=node(3,1,2) + // RETURN n + // ORDER BY n.age, n.name + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("n") + .OrderBy("n.age", "n.name") + .Query; + + Assert.AreEqual("RETURN n\r\nORDER BY n.age, n.name", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void OrderNodesByPropertyDescending() + { + // http://docs.neo4j.org/chunked/stable/query-order.html#order-by-order-nodes-in-descending-order + // START n=node(3,1,2) + // RETURN n + // ORDER BY n.name DESC + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("n") + .OrderByDescending("n.name") + .Query; + + Assert.AreEqual("RETURN n\r\nORDER BY n.name DESC", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void OrderNodesByMultiplePropertiesDescending() + { + // http://docs.neo4j.org/chunked/stable/query-order.html#order-by-order-nodes-in-descending-order + // START n=node(3,1,2) + // RETURN n + // ORDER BY n.age, n.name DESC + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Return("n") + .OrderByDescending("n.age", "n.name") + .Query; + + Assert.AreEqual("RETURN n\r\nORDER BY n.age, n.name DESC", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void ReturnColumnAlias() + { + // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias + // START a=node(1) + // RETURN a.Age AS SomethingTotallyDifferent + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("a", (NodeReference)1) + .Return(a => new ReturnPropertyQueryResult + { + SomethingTotallyDifferent = a.As().Age + }) + .Query; + + Assert.AreEqual("START a=node({p0})\r\nRETURN a.Age AS SomethingTotallyDifferent", query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + } + + [Test] + public void ReturnUniqueResults() + { + // http://docs.neo4j.org/chunked/1.6/query-return.html#return-unique-results + // START a=node(1) + // MATCH (a)-->(b) + // RETURN distinct b + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("a", (NodeReference)1) + .Match("(a)-->(b)") + .ReturnDistinct("b") + .Query; + + Assert.AreEqual("START a=node({p0})\r\nMATCH (a)-->(b)\r\nRETURN distinct b", query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + } + + [Test] + public void ReturnUniqueResultsWithExpression() + { + // http://docs.neo4j.org/chunked/1.6/query-return.html#return-unique-results + // START a=node(1) + // MATCH (a)-->(b) + // RETURN distinct b + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("a", (NodeReference)1) + .Match("(a)-->(b)") + .ReturnDistinct(b => new FooData + { + Age = b.As().Age + }) + .Query; + + Assert.AreEqual("START a=node({p0})\r\nMATCH (a)-->(b)\r\nRETURN distinct b.Age AS Age", query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + } + + [Test] + public void ReturnPropertiesIntoAnonymousType() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("a", (NodeReference)1) + .Match("(a)-->(b)") + .Return(b => new + { + SomeAge = b.As().Age, + SomeName = b.As().Name + }) + .Query; + + string expected = string.Format("START a=node({{p0}}){0}MATCH (a)-->(b){0}RETURN b.Age AS SomeAge, b.Name AS SomeName", Environment.NewLine); + + Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + } + + [Test] + public void ReturnPropertiesIntoAnonymousTypeWithAutoNames() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("a", (NodeReference)1) + .Match("(a)-->(b)") + .Return(b => new + { + b.As().Age, + b.As().Name + }) + .Query; + + string expected = string.Format("START a=node({{p0}}){0}MATCH (a)-->(b){0}RETURN b.Age AS Age, b.Name AS Name", Environment.NewLine); + + Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + } + + [Test] + public void ReturnPropertiesFromMultipleNodesIntoAnonymousTypeWithAutoNames() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("a", (NodeReference)1) + .Match("(a)-->(b)-->(c)") + .Return((b, c) => new + { + b.As().Age, + c.As().Name + }) + .Query; + + string expected = "START a=node({p0})" + Environment.NewLine + "MATCH (a)-->(b)-->(c)" + Environment.NewLine + "RETURN b.Age AS Age, c.Name AS Name"; + + Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + } + + [Test] + public void ReturnNodeDataIntoAnonymousType() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("a", (NodeReference)1) + .Match("(a)-->(b)") + .Return((b, c) => new + { + NodeB = b.As(), + }) + .Query; + + string expected = "START a=node({p0})" + Environment.NewLine + "MATCH (a)-->(b)" + Environment.NewLine + "RETURN b AS NodeB"; + + Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + } + + [Test] + public void ReturnEntireNodeDataAndReferenceIntoAnonymousType() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("a", (NodeReference)1) + .Match("(a)-->(b)") + .Return((b, c) => new + { + NodeB = b.Node(), + }) + .Query; + + string expected = string.Format("START a=node({{p0}}){0}MATCH (a)-->(b){0}RETURN b AS NodeB", Environment.NewLine); + + Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + } + + [Test] + public void ReturnEntireNodeDataAndReferenceIntoProjectionType() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("a", (NodeReference)1) + .Match("(a)-->(b)") + .Return((b, c) => new ReturnEntireNodeDataAndReferenceIntoProjectionTypeResult + { + NodeB = b.Node(), + }) + .Query; + + string expected = string.Format("START a=node({{p0}}){0}MATCH (a)-->(b){0}RETURN b AS NodeB", Environment.NewLine); + + Assert.AreEqual(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + } + + public class ReturnEntireNodeDataAndReferenceIntoProjectionTypeResult + { + public Node NodeB { get; set; } + } + + [Test] + public void WhereBooleanOperationWithVariable() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + const string name = "Tobias"; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.Age < 30 && n.Name == name) || n.Name != "Tobias") + .Query; + + Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + Assert.AreEqual("Tobias", query.QueryParameters["p1"]); + Assert.AreEqual("Tobias", query.QueryParameters["p2"]); + } + + [Test] + public void WhereBooleanOperationWithLong() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + const string name = "Tobias"; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.AgeLong < 30 && n.Name == name) || n.Name != "Tobias") + .Query; + + Assert.AreEqual("WHERE (((n.AgeLong < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))".Replace("'", "\""), query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + Assert.AreEqual("Tobias", query.QueryParameters["p1"]); + Assert.AreEqual("Tobias", query.QueryParameters["p2"]); + } + + [Test] + public void WhereBooleanOperationWithNullableLong() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + const string name = "Tobias"; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.AgeLongNullable < 30 && n.Name == name) || n.Name != "Tobias") + .Query; + + Assert.AreEqual("WHERE (((n.AgeLongNullable < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + Assert.AreEqual("Tobias", query.QueryParameters["p1"]); + Assert.AreEqual("Tobias", query.QueryParameters["p2"]); + } + + [Test] + public void WhereBooleanOperationWithStringPropertyOnRightSide() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + var foo = new FooData + { + Name = "Tobias" + }; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.Age < 30 && n.Name == foo.Name) || n.Name != "Tobias") + .Query; + + Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + Assert.AreEqual("Tobias", query.QueryParameters["p1"]); + Assert.AreEqual("Tobias", query.QueryParameters["p2"]); + } + + [Test] + public void WhereBooleanOperationWithIntPropertyOnRightSide() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + var foo = new FooData + { + Name = "Tobias", + Id = 777 + }; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.Age < 30 && n.Id == foo.Id) || n.Name != "Tobias") + .Query; + + Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Id = {p1})) OR (n.Name <> {p2}))", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + Assert.AreEqual(777, query.QueryParameters["p1"]); + Assert.AreEqual("Tobias", query.QueryParameters["p2"]); + } + + [Test] + public void WhereBooleanOperationWithIntConstantOnRightSide() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + int theId = 777; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.Id == theId)) + .Query; + + Assert.AreEqual("WHERE (n.Id = {p0})", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(777, query.QueryParameters["p0"]); + } + + [Test] + public void WhereBooleanOperationWithLongConstantOnRightSide() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + long theId = 777; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.Id == theId)) + .Query; + + Assert.AreEqual("WHERE (n.Id = {p0})", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(777, query.QueryParameters["p0"]); + } + + [Test] + public void WhereBooleanOperationWithLongNullableConstantOnRightSide() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + long? theId = 777; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.Id == theId)) + .Query; + + Assert.AreEqual("WHERE (n.Id = {p0})", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(777, query.QueryParameters["p0"]); + } + + [Test] + public void WhereBooleanOperationWithObjectProperty() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + var fooData = new FooData {Name = "Tobias"}; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.Age < 30 && n.Name == fooData.Name) || n.Name != "Tobias") + .Query; + + Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + Assert.AreEqual("Tobias", query.QueryParameters["p1"]); + Assert.AreEqual("Tobias", query.QueryParameters["p2"]); + } + + [Test] + public void WhereBooleanOperationWithObjectNullableProperty() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + var fooData = new FooData { Id = 777 }; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.Age < 30 && n.Id == fooData.Id) || n.Name != "Tobias") + .Query; + + Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Id = {p1})) OR (n.Name <> {p2}))", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + Assert.AreEqual(777, query.QueryParameters["p1"]); + Assert.AreEqual("Tobias", query.QueryParameters["p2"]); + } + + [Test] + public void WhereBooleanOperations() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-boolean-operations + // START n=node(3, 1) + // WHERE (n.age < 30 and n.name = "Tobias") or not(n.name = "Tobias") + // RETURN n + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => (n.Age < 30 && n.Name == "Tobias") || n.Name != "Tobias") + .Query; + + Assert.AreEqual("WHERE (((n.Age < {p0}) AND (n.Name = {p1})) OR (n.Name <> {p2}))", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + Assert.AreEqual("Tobias", query.QueryParameters["p1"]); + Assert.AreEqual("Tobias", query.QueryParameters["p2"]); + } + + [Test] + public void WhereFilterOnNodeProperty() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-filter-on-node-property + // START n=node(3, 1) + // WHERE n.age < 30 + // RETURN n + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => n.Age < 30 ) + .Query; + + Assert.AreEqual("WHERE (n.Age < {p0})", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + } + + [Test] + public void WhereFilterPropertyExists() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-property-exists + // START n=node(3, 1) + // WHERE has(n.Belt) + // RETURN n + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where("(has(n.Belt))") + .Query; + + Assert.AreEqual("WHERE (has(n.Belt))", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void WhereFilterCompareIfPropertyExists() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-compare-if-property-exists + // START n=node(3, 1) + // WHERE n.Belt = 'white' + // RETURN n + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => n.Id < 30) + .Query; + + Assert.AreEqual("WHERE (n.Id < {p0})", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + } + + [Test] + public void WhereFilterOnNullValues() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(r => r.Name == null && r.Id == 100) + .Query; + + Assert.AreEqual("WHERE ((not(has(r.Name))) AND (r.Id = {p0}))", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual(100, query.QueryParameters["p0"]); + } + + [Test] + public void WhereFilterOnMultipleNodesProperties() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where((n1, n2) => n1.Age < 30 && n2.Key == 11) + .Query; + + Assert.AreEqual("WHERE ((n1.Age < {p0}) AND (n2.Key = {p1}))", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual(30, query.QueryParameters["p0"]); + Assert.AreEqual(11, query.QueryParameters["p1"]); + } + + [Test] + public void WhereFilterOnRelationshipType() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html + // START n=node(3) + // MATCH (n)-[r]->() + // WHERE type(r) = "HOSTS" + // RETURN r + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where("(type(r) = {Hosts})") + .WithParam("Hosts", "HOSTS") + .Query; + + Assert.AreEqual("WHERE (type(r) = {Hosts})", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual("HOSTS", query.QueryParameters["Hosts"]); + } + + [Test] + public void WhereWithAnd() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => n.Name == "bob") + .AndWhere("(type(r) = {Hosts})") + .WithParam("Hosts", "HOSTS") + .Query; + + Assert.AreEqual("WHERE (n.Name = {p0})\r\nAND (type(r) = {Hosts})", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual("bob", query.QueryParameters["p0"]); + Assert.AreEqual("HOSTS", query.QueryParameters["Hosts"]); + } + + [Test] + public void WhereWithOr() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => n.Name == "bob") + .OrWhere("(type(r) = {Hosts})") + .WithParam("Hosts", "HOSTS") + .Query; + + Assert.AreEqual("WHERE (n.Name = {p0})\r\nOR (type(r) = {Hosts})", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual("bob", query.QueryParameters["p0"]); + Assert.AreEqual("HOSTS", query.QueryParameters["Hosts"]); + } + + [Test] + public void WhereWithOrAnd() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where(n => n.Name == "Bob") + .OrWhere("(type(r) = {Hosts})") + .WithParam("Hosts", "HOSTS") + .AndWhere(n => n.Id == 10) + .Query; + + Assert.AreEqual("WHERE (n.Name = {p0})\r\nOR (type(r) = {Hosts})\r\nAND (n.Id = {p2})", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + Assert.AreEqual("Bob", query.QueryParameters["p0"]); + Assert.AreEqual(10, query.QueryParameters["p2"]); + Assert.AreEqual("HOSTS", query.QueryParameters["Hosts"]); + } + + [Test] + public void WhereFilterOnRelationships() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-filter-on-relationships + // START a=node(1), b=node(3, 2) + // WHERE a<--b + // RETURN b + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where("(a<--b)") + .Query; + + Assert.AreEqual("WHERE (a<--b)", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void WhereFilterRegularExpressions() + { + // http://docs.neo4j.org/chunked/1.6/query-where.html#where-regular-expressions + // START n=node(3, 1) + // WHERE n.name =~ /Tob.*/ + // RETURN n + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where("(n.Name =~ /Tob.*/)") + .Query; + + Assert.AreEqual("WHERE (n.Name =~ /Tob.*/)", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void CreateRelationshipBetweenTwoNodes() + { + //http://docs.neo4j.org/chunked/1.8.M06/query-create.html#create-create-a-relationship-between-two-nodes + // START a=node(1), b=node(2) + // CREATE a-[r:REL]->b + // RETURN r + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { + a = (NodeReference)1, + b = (NodeReference)2 + }) + .Create("a-[r:REL]->b") + .Return("r") + .Query; + + Assert.AreEqual("START a=node({p0}), b=node({p1})\r\nCREATE a-[r:REL]->b\r\nRETURN r", query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + public void CreateNode() + { + //http://docs.neo4j.org/chunked/milestone/query-create.html#create-create-single-node-and-set-properties + // CREATE (a {Foo: 'foo', Bar: 'bar', Baz: 'baz'}) + // RETURN a + + var data = new CreateNodeTests.TestNode {Foo = "foo", Bar = "bar", Baz = "baz"}; + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Create("a", data) + .Return("a") + .Query; + Assert.AreEqual("CREATE (a {p0})\r\nRETURN a", query.QueryText); + Assert.AreEqual(data, query.QueryParameters["p0"]); + } + + [Test] + public void CreateAFullPath() { + //http://docs.neo4j.org/chunked/milestone/query-create.html#create-create-a-full-path + // START n=node(1) + // CREATE n-[r:REL]->(a {Foo: 'foo', Bar: 'bar', Baz: 'baz'})-[r:REL]->(b {Foo: 'foo2', Bar: 'bar2', Baz: 'baz2'}) + // RETURN a + + var data1 = new CreateNodeTests.TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + var data2 = new CreateNodeTests.TestNode { Foo = "foo2", Bar = "bar2", Baz = "baz2" }; + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)1) + .Create("n-[r:REL]->(a {0})-[r:REL]->(b {1})", data1, data2) + .Return("a") + .Query; + Assert.AreEqual("START n=node({p0})\r\nCREATE n-[r:REL]->(a {p1})-[r:REL]->(b {p2})\r\nRETURN a", query.QueryText); + Assert.AreEqual(data1, query.QueryParameters["p1"]); + Assert.AreEqual(data2, query.QueryParameters["p2"]); + } + + [Test] + public void CreateRelationshipAndSetProperties() + { + //http://docs.neo4j.org/chunked/1.8.M06/query-create.html#create-create-a-relationship-and-set-properties + //START a=node(1), b=node(2) + //CREATE a-[r:REL {name : a.name + '<->' + b.name }]->b + //RETURN r + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { + a = (NodeReference)1, + b = (NodeReference)2 + }) + .Create("a-[r:REL {name : a.name + '<->' + b.name }]->b") + .Return("r") + .Query; + + Assert.AreEqual("START a=node({p0}), b=node({p1})\r\nCREATE a-[r:REL {name : a.name + '<->' + b.name }]->b\r\nRETURN r", query.QueryText); + Assert.AreEqual(1, query.QueryParameters["p0"]); + Assert.AreEqual(2, query.QueryParameters["p1"]); + } + + [Test] + public void ComplexMatching() + { + // http://docs.neo4j.org/chunked/1.8.M03/query-match.html#match-complex-matching + // START a=node(3) + // MATCH (a)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:BLOCKS]-(d)-[:KNOWS]-(c) + // RETURN a,b,c,d + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("a", (NodeReference)3) + .Match( + "(a)-[:KNOWS]->(b)-[:KNOWS]->(c)", + "(a)-[:BLOCKS]-(d)-[:KNOWS]-(c)") + .Query; + + Assert.AreEqual("START a=node({p0})\r\nMATCH (a)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:BLOCKS]-(d)-[:KNOWS]-(c)", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/45/cyper-should-allow-for-flexible-order-of")] + public void SupportsFlexibleOrderOfClauses() + { + // START me=node:node_auto_index(name='Bob') + // MATCH me-[r?:STATUS]-secondlatestupdate + // DELETE r + // WITH me, secondlatestupdate + // CREATE me-[:STATUS]->(latest_update{text:'Status',date:123}) + // WITH latest_update,secondlatestupdate + // CREATE latest_update-[:NEXT]-secondlatestupdate + // WHERE secondlatestupdate <> null + // RETURN latest_update.text as new_status + + var update = new { text = "Status", date = 123 }; + + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start(new { me = Node.ByIndexLookup("node_auto_index", "name", "Bob") }) + .Match("me-[r?:STATUS]-secondlatestupdate") + .Delete("r") + .With("me, secondlatestupdate") + .Create("me-[:STATUS]->(latest_update {update})") + .WithParams(new { update }) + .With("latest_update,secondlatestupdate") + .Create("latest_update-[:NEXT]-secondlatestupdate") + .Where("secondlatestupdate <> null") +// ReSharper disable InconsistentNaming + .Return(latest_update => new { new_status = latest_update.As().text }) +// ReSharper restore InconsistentNaming + .Query; + + Assert.AreEqual(string.Format("START me=node:`node_auto_index`(name = {{p0}}){0}MATCH me-[r?:STATUS]-secondlatestupdate{0}DELETE r{0}WITH me, secondlatestupdate{0}CREATE me-[:STATUS]->(latest_update {{update}}){0}WITH latest_update,secondlatestupdate{0}CREATE latest_update-[:NEXT]-secondlatestupdate{0}WHERE secondlatestupdate <> null{0}RETURN latest_update.text AS new_status", Environment.NewLine), query.QueryText); + Assert.AreEqual("Bob", query.QueryParameters["p0"]); + Assert.AreEqual(update, query.QueryParameters["update"]); + } + + public class UpdateData + { +// ReSharper disable InconsistentNaming + public string text { get; set; } +// ReSharper restore InconsistentNaming + } + + public class FooData + { + public int Age { get; set; } + public int? Id { get; set; } + public long AgeLong { get; set; } + public long? AgeLongNullable { get; set; } + public string Name { get; set; } + } + + public class BarData + { + public int Key { get; set; } + public string Value { get; set; } + } + + public class ReturnPropertyQueryResult + { + public int SomethingTotallyDifferent { get; set; } + } + } +} diff --git a/Test/Cypher/CypherFluentQueryUnwindTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryUnwindTests.cs similarity index 100% rename from Test/Cypher/CypherFluentQueryUnwindTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryUnwindTests.cs diff --git a/Test/Cypher/CypherFluentQueryUsingIndexTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryUsingIndexTests.cs similarity index 100% rename from Test/Cypher/CypherFluentQueryUsingIndexTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryUsingIndexTests.cs diff --git a/Test/Cypher/CypherFluentQueryWhereTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryWhereTests.cs similarity index 97% rename from Test/Cypher/CypherFluentQueryWhereTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryWhereTests.cs index 903b77112..f3646cfc1 100644 --- a/Test/Cypher/CypherFluentQueryWhereTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryWhereTests.cs @@ -1,109 +1,109 @@ -using Newtonsoft.Json.Serialization; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQueryWhereTests - { - // ReSharper disable ClassNeverInstantiated.Local - // ReSharper disable UnusedAutoPropertyAccessor.Local - class Foo - { - public int Bar { get; set; } - } - - class FooCamel:Foo - { - public int LongBar { get; set; } - public int a { get; set; } - public int B { get; set; } - } - // ReSharper restore ClassNeverInstantiated.Local - // ReSharper restore UnusedAutoPropertyAccessor.Local - - class MockWithNullField - { - public string NullField { get; set; } - } - - [Test] - public void ComparePropertiesAcrossEntitiesEqual() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where((a, b) => a.Bar == b.Bar) - .Query; - - Assert.AreEqual("WHERE (a.Bar = b.Bar)", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void ComparePropertiesAcrossEntitiesNotEqual() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where((a, b) => a.Bar != b.Bar) - .Query; - - Assert.AreEqual("WHERE (a.Bar <> b.Bar)", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void NestOrAndAndCorrectly() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Where((Foo a, Foo b) => a.Bar == 123 || b.Bar == 456) - .AndWhere((Foo c) => c.Bar == 789) - .Query; - - Assert.AreEqual("WHERE ((a.Bar = {p0}) OR (b.Bar = {p1}))\r\nAND (c.Bar = {p2})", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - } - - [Test] - public void ComparePropertiesAcrossEntitiesEqualCamel() - { - var client = Substitute.For(); - client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var query = new CypherFluentQuery(client) - .Where((a, b) => a.Bar == b.Bar && a.LongBar == b.LongBar && a.a == b.a && a.B == b.B) - .Query; - - Assert.AreEqual("WHERE ((((a.bar = b.bar) AND (a.longBar = b.longBar)) AND (a.a = b.a)) AND (a.b = b.b))", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void ComparePropertiesAcrossEntitiesNotEqualCamel() - { - var client = Substitute.For(); - client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var query = new CypherFluentQuery(client) - .Where((a, b) => a.Bar != b.Bar && a.LongBar != b.LongBar && a.a != b.a && a.B != b.B) - .Query; - - Assert.AreEqual("WHERE ((((a.bar <> b.bar) AND (a.longBar <> b.longBar)) AND (a.a <> b.a)) AND (a.b <> b.b))", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void NestOrAndAndCorrectlyCamel() - { - var client = Substitute.For(); - client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var query = new CypherFluentQuery(client) - .Where((FooCamel a, FooCamel b) => a.LongBar == 123 || b.Bar == 456) - .AndWhere((FooCamel c) => c.B == 789) - .Query; - - Assert.AreEqual("WHERE ((a.longBar = {p0}) OR (b.bar = {p1}))\r\nAND (c.b = {p2})", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - } - } -} +using Newtonsoft.Json.Serialization; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQueryWhereTests + { + // ReSharper disable ClassNeverInstantiated.Local + // ReSharper disable UnusedAutoPropertyAccessor.Local + class Foo + { + public int Bar { get; set; } + } + + class FooCamel:Foo + { + public int LongBar { get; set; } + public int a { get; set; } + public int B { get; set; } + } + // ReSharper restore ClassNeverInstantiated.Local + // ReSharper restore UnusedAutoPropertyAccessor.Local + + class MockWithNullField + { + public string NullField { get; set; } + } + + [Test] + public void ComparePropertiesAcrossEntitiesEqual() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where((a, b) => a.Bar == b.Bar) + .Query; + + Assert.AreEqual("WHERE (a.Bar = b.Bar)", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void ComparePropertiesAcrossEntitiesNotEqual() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where((a, b) => a.Bar != b.Bar) + .Query; + + Assert.AreEqual("WHERE (a.Bar <> b.Bar)", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void NestOrAndAndCorrectly() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Where((Foo a, Foo b) => a.Bar == 123 || b.Bar == 456) + .AndWhere((Foo c) => c.Bar == 789) + .Query; + + Assert.AreEqual("WHERE ((a.Bar = {p0}) OR (b.Bar = {p1}))\r\nAND (c.Bar = {p2})", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + } + + [Test] + public void ComparePropertiesAcrossEntitiesEqualCamel() + { + var client = Substitute.For(); + client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var query = new CypherFluentQuery(client) + .Where((a, b) => a.Bar == b.Bar && a.LongBar == b.LongBar && a.a == b.a && a.B == b.B) + .Query; + + Assert.AreEqual("WHERE ((((a.bar = b.bar) AND (a.longBar = b.longBar)) AND (a.a = b.a)) AND (a.b = b.b))", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void ComparePropertiesAcrossEntitiesNotEqualCamel() + { + var client = Substitute.For(); + client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var query = new CypherFluentQuery(client) + .Where((a, b) => a.Bar != b.Bar && a.LongBar != b.LongBar && a.a != b.a && a.B != b.B) + .Query; + + Assert.AreEqual("WHERE ((((a.bar <> b.bar) AND (a.longBar <> b.longBar)) AND (a.a <> b.a)) AND (a.b <> b.b))", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void NestOrAndAndCorrectlyCamel() + { + var client = Substitute.For(); + client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var query = new CypherFluentQuery(client) + .Where((FooCamel a, FooCamel b) => a.LongBar == 123 || b.Bar == 456) + .AndWhere((FooCamel c) => c.B == 789) + .Query; + + Assert.AreEqual("WHERE ((a.longBar = {p0}) OR (b.bar = {p1}))\r\nAND (c.b = {p2})", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + } + } +} diff --git a/Test/Cypher/CypherFluentQueryWithParamTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithParamTests.cs similarity index 97% rename from Test/Cypher/CypherFluentQueryWithParamTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryWithParamTests.cs index 3a1d4addf..a194db6b5 100644 --- a/Test/Cypher/CypherFluentQueryWithParamTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithParamTests.cs @@ -1,153 +1,153 @@ -using System.Globalization; -using Neo4jClient.Serialization; -using Newtonsoft.Json.Serialization; -using NUnit.Framework; -using NSubstitute; -using Neo4jClient.Cypher; -using System; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQueryWithParamTests - { - [Test] - public void WithParam() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .WithParam("foo", 123) - .Query; - - // Assert - Assert.AreEqual("START n=node({p0})", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual(3, query.QueryParameters["p0"]); - Assert.AreEqual(123, query.QueryParameters["foo"]); - } - - [Test(Description = "https://bitbucket.org/Readify/neo4jclient/issue/156/passing-cypher-parameters-by-anonymous")] - public void WithParams() - { - // Arrange - var client = Substitute.For(); - - // Act - const string bar = "string value"; - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .WithParams(new {foo = 123, bar}) - .Query; - - // Assert - Assert.AreEqual("START n=node({p0})", query.QueryText); - Assert.AreEqual(3, query.QueryParameters.Count); - Assert.AreEqual(3, query.QueryParameters["p0"]); - Assert.AreEqual(123, query.QueryParameters["foo"]); - Assert.AreEqual("string value", query.QueryParameters["bar"]); - } - - [Test] - public void ThrowsExceptionForDuplicateManualKey() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .WithParam("foo", 123); - - // Assert - var ex = Assert.Throws( - () => query.WithParam("foo", 456) - ); - Assert.AreEqual("key", ex.ParamName); - Assert.AreEqual("A parameter with the given key is already defined in the query.\r\nParameter name: key", ex.Message); - } - - [Test] - public void ThrowsExceptionForDuplicateOfAutoKey() - { - // Arrange - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3); - - // Assert - var ex = Assert.Throws( - () => query.WithParam("p0", 456) - ); - Assert.AreEqual("key", ex.ParamName); - Assert.AreEqual("A parameter with the given key is already defined in the query.\r\nParameter name: key", ex.Message); - } - - public class ComplexObjForWithParamTest - { - public long? Id { get; set; } - public string Name { get; set; } - public decimal Currency { get; set; } - public string CamelCaseProperty { get; set; } - } - - [Test] - public void ComplexObjectInWithParam() - { - // Arrange - var client = Substitute.For(); - - // Act - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference) 3) - .CreateUnique("n-[:X]-(leaf {obj})") - .WithParam("obj", CreateComplexObjForWithParamTest()) - .Query; - - // Assert - Assert.AreEqual("START n=node(3)" + - "\r\nCREATE UNIQUE n-[:X]-(leaf {" + - "\r\n \"Id\": 123," + - "\r\n \"Name\": \"Bar\"," + - "\r\n \"Currency\": 12.143," + - "\r\n \"CamelCaseProperty\": \"Foo\"" + - "\r\n})", query.DebugQueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - } - - private ComplexObjForWithParamTest CreateComplexObjForWithParamTest() - { - return new ComplexObjForWithParamTest - { - Id = 123, - Name = "Bar", - Currency = (decimal) 12.143, - CamelCaseProperty = "Foo" - }; - } - - [Test] - public void ComplexObjectInWithParamCamelCase() - { - // Arrange - var client = Substitute.For(); - client.JsonContractResolver.Returns(new CamelCasePropertyNamesContractResolver()); - - // Act - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .CreateUnique("n-[:X]-(leaf {obj})") - .WithParam("obj", CreateComplexObjForWithParamTest()) - .Query; - - // Assert - Assert.AreEqual("START n=node(3)" + - "\r\nCREATE UNIQUE n-[:X]-(leaf {" + - "\r\n \"id\": 123," + - "\r\n \"name\": \"Bar\"," + - "\r\n \"currency\": 12.143," + - "\r\n \"camelCaseProperty\": \"Foo\"" + - "\r\n})", query.DebugQueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - } - } -} +using System.Globalization; +using Neo4jClient.Serialization; +using Newtonsoft.Json.Serialization; +using NUnit.Framework; +using NSubstitute; +using Neo4jClient.Cypher; +using System; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQueryWithParamTests + { + [Test] + public void WithParam() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .WithParam("foo", 123) + .Query; + + // Assert + Assert.AreEqual("START n=node({p0})", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(123, query.QueryParameters["foo"]); + } + + [Test(Description = "https://bitbucket.org/Readify/neo4jclient/issue/156/passing-cypher-parameters-by-anonymous")] + public void WithParams() + { + // Arrange + var client = Substitute.For(); + + // Act + const string bar = "string value"; + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .WithParams(new {foo = 123, bar}) + .Query; + + // Assert + Assert.AreEqual("START n=node({p0})", query.QueryText); + Assert.AreEqual(3, query.QueryParameters.Count); + Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(123, query.QueryParameters["foo"]); + Assert.AreEqual("string value", query.QueryParameters["bar"]); + } + + [Test] + public void ThrowsExceptionForDuplicateManualKey() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .WithParam("foo", 123); + + // Assert + var ex = Assert.Throws( + () => query.WithParam("foo", 456) + ); + Assert.AreEqual("key", ex.ParamName); + Assert.AreEqual("A parameter with the given key is already defined in the query.\r\nParameter name: key", ex.Message); + } + + [Test] + public void ThrowsExceptionForDuplicateOfAutoKey() + { + // Arrange + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3); + + // Assert + var ex = Assert.Throws( + () => query.WithParam("p0", 456) + ); + Assert.AreEqual("key", ex.ParamName); + Assert.AreEqual("A parameter with the given key is already defined in the query.\r\nParameter name: key", ex.Message); + } + + public class ComplexObjForWithParamTest + { + public long? Id { get; set; } + public string Name { get; set; } + public decimal Currency { get; set; } + public string CamelCaseProperty { get; set; } + } + + [Test] + public void ComplexObjectInWithParam() + { + // Arrange + var client = Substitute.For(); + + // Act + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference) 3) + .CreateUnique("n-[:X]-(leaf {obj})") + .WithParam("obj", CreateComplexObjForWithParamTest()) + .Query; + + // Assert + Assert.AreEqual("START n=node(3)" + + "\r\nCREATE UNIQUE n-[:X]-(leaf {" + + "\r\n \"Id\": 123," + + "\r\n \"Name\": \"Bar\"," + + "\r\n \"Currency\": 12.143," + + "\r\n \"CamelCaseProperty\": \"Foo\"" + + "\r\n})", query.DebugQueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + } + + private ComplexObjForWithParamTest CreateComplexObjForWithParamTest() + { + return new ComplexObjForWithParamTest + { + Id = 123, + Name = "Bar", + Currency = (decimal) 12.143, + CamelCaseProperty = "Foo" + }; + } + + [Test] + public void ComplexObjectInWithParamCamelCase() + { + // Arrange + var client = Substitute.For(); + client.JsonContractResolver.Returns(new CamelCasePropertyNamesContractResolver()); + + // Act + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .CreateUnique("n-[:X]-(leaf {obj})") + .WithParam("obj", CreateComplexObjForWithParamTest()) + .Query; + + // Assert + Assert.AreEqual("START n=node(3)" + + "\r\nCREATE UNIQUE n-[:X]-(leaf {" + + "\r\n \"id\": 123," + + "\r\n \"name\": \"Bar\"," + + "\r\n \"currency\": 12.143," + + "\r\n \"camelCaseProperty\": \"Foo\"" + + "\r\n})", query.DebugQueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + } + } +} diff --git a/Test/Cypher/CypherFluentQueryWithTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs similarity index 96% rename from Test/Cypher/CypherFluentQueryWithTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs index 3761c4556..8b4054c09 100644 --- a/Test/Cypher/CypherFluentQueryWithTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs @@ -1,194 +1,194 @@ -using System; -using Newtonsoft.Json.Serialization; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherFluentQueryWithTests - { - [Test] - public void With() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) - .With("foo") - .Query; - - Assert.AreEqual("START n=node({p0})\r\nWITH foo", query.QueryText); - Assert.AreEqual(3, query.QueryParameters["p0"]); - } - - [Test] - public void ShouldReturnCountOnItsOwn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .With(item => item.Count()) - .Query; - - Assert.AreEqual("WITH count(item)", query.QueryText); - } - - [Test] - public void ShouldReturnCountAllOnItsOwn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .With(() => All.Count()) - .Query; - - Assert.AreEqual("WITH count(*)", query.QueryText); - } - - [Test] - public void ShouldReturnCustomFunctionCall() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .With(() => new { baz = "sum(foo.bar)" }) - .Query; - - Assert.AreEqual("WITH sum(foo.bar) AS baz", query.QueryText); - } - - [Test] - public void ShouldReturnSpecificPropertyOnItsOwn() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .With(a => a.As().Name) - .Query; - - Assert.AreEqual("WITH a.Name", query.QueryText); - } - - [Test] - public void ShouldReturnSpecificPropertyOnItsOwnCamelAs() - { - var client = Substitute.For(); - client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var query = new CypherFluentQuery(client) - .With(a => new Commodity(){ Name = a.As().Name}) - .Query; - - Assert.AreEqual("WITH a.name? AS Name", query.QueryText); - } - - [Test] - public void ShouldReturnSpecificPropertyOnItsOwnCamel() - { - var client = Substitute.For(); - client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var query = new CypherFluentQuery(client) - .With(a => a.As().Name) - .Query; - - Assert.AreEqual("WITH a.name", query.QueryText); - } - - [Test] - public void ShouldThrowForMemberExpressionOffMethodOtherThanAs() - { - var client = Substitute.For(); - Assert.Throws( - () => new CypherFluentQuery(client).With(a => a.Type().Length)); - } - - [Test] - public void ShouldTranslateAnonymousObjectWithExplicitPropertyNames() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .With(a => new { Foo = a }) - .Query; - - Assert.AreEqual("WITH a AS Foo", query.QueryText); - } - - [Test] - public void ShouldTranslateAnonymousObjectWithImplicitPropertyNames() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .With(a => new { a }) - .Query; - - Assert.AreEqual("WITH a", query.QueryText); - } - - [Test] - public void ShouldTranslateAnonymousObjectWithMultipleProperties() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .With((a, b) => new { a, b }) - .Query; - - Assert.AreEqual("WITH a, b", query.QueryText); - } - - [Test] - public void ShouldTranslateAnonymousObjectWithMixedProperties() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .With((a, b) => new - { - a, - foo = b.Count(), - bar = b.CollectAs() - }) - .Query; - - Assert.AreEqual("WITH a, count(b) AS foo, collect(b) AS bar", query.QueryText); - } - - [Test] - public void ShouldUseProjectionResultModeForNamedObjectReturnWithConcreteProperties() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .With(a => new ProjectionResult - { - Commodity = a.As() - }) - .Query; - - Assert.AreEqual("WITH a AS Commodity", query.QueryText); - } - - [Test] - public void ShouldUseProjectionResultModeForNamedObjectReturnWithICypherResultItems() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .With(a => new CypherProjectionResult - { - Foo = a - }) - .Query; - - Assert.AreEqual("WITH a AS Foo", query.QueryText); - } - - public class Commodity - { - public string Name { get; set; } - public long UniqueId { get; set; } - } - - public class ProjectionResult - { - public Commodity Commodity { get; set; } - } - - public class CypherProjectionResult - { - public ICypherResultItem Foo { get; set; } - } - } -} +using System; +using Newtonsoft.Json.Serialization; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherFluentQueryWithTests + { + [Test] + public void With() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Start("n", (NodeReference)3) + .With("foo") + .Query; + + Assert.AreEqual("START n=node({p0})\r\nWITH foo", query.QueryText); + Assert.AreEqual(3, query.QueryParameters["p0"]); + } + + [Test] + public void ShouldReturnCountOnItsOwn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .With(item => item.Count()) + .Query; + + Assert.AreEqual("WITH count(item)", query.QueryText); + } + + [Test] + public void ShouldReturnCountAllOnItsOwn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .With(() => All.Count()) + .Query; + + Assert.AreEqual("WITH count(*)", query.QueryText); + } + + [Test] + public void ShouldReturnCustomFunctionCall() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .With(() => new { baz = "sum(foo.bar)" }) + .Query; + + Assert.AreEqual("WITH sum(foo.bar) AS baz", query.QueryText); + } + + [Test] + public void ShouldReturnSpecificPropertyOnItsOwn() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .With(a => a.As().Name) + .Query; + + Assert.AreEqual("WITH a.Name", query.QueryText); + } + + [Test] + public void ShouldReturnSpecificPropertyOnItsOwnCamelAs() + { + var client = Substitute.For(); + client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var query = new CypherFluentQuery(client) + .With(a => new Commodity(){ Name = a.As().Name}) + .Query; + + Assert.AreEqual("WITH a.name? AS Name", query.QueryText); + } + + [Test] + public void ShouldReturnSpecificPropertyOnItsOwnCamel() + { + var client = Substitute.For(); + client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var query = new CypherFluentQuery(client) + .With(a => a.As().Name) + .Query; + + Assert.AreEqual("WITH a.name", query.QueryText); + } + + [Test] + public void ShouldThrowForMemberExpressionOffMethodOtherThanAs() + { + var client = Substitute.For(); + Assert.Throws( + () => new CypherFluentQuery(client).With(a => a.Type().Length)); + } + + [Test] + public void ShouldTranslateAnonymousObjectWithExplicitPropertyNames() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .With(a => new { Foo = a }) + .Query; + + Assert.AreEqual("WITH a AS Foo", query.QueryText); + } + + [Test] + public void ShouldTranslateAnonymousObjectWithImplicitPropertyNames() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .With(a => new { a }) + .Query; + + Assert.AreEqual("WITH a", query.QueryText); + } + + [Test] + public void ShouldTranslateAnonymousObjectWithMultipleProperties() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .With((a, b) => new { a, b }) + .Query; + + Assert.AreEqual("WITH a, b", query.QueryText); + } + + [Test] + public void ShouldTranslateAnonymousObjectWithMixedProperties() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .With((a, b) => new + { + a, + foo = b.Count(), + bar = b.CollectAs() + }) + .Query; + + Assert.AreEqual("WITH a, count(b) AS foo, collect(b) AS bar", query.QueryText); + } + + [Test] + public void ShouldUseProjectionResultModeForNamedObjectReturnWithConcreteProperties() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .With(a => new ProjectionResult + { + Commodity = a.As() + }) + .Query; + + Assert.AreEqual("WITH a AS Commodity", query.QueryText); + } + + [Test] + public void ShouldUseProjectionResultModeForNamedObjectReturnWithICypherResultItems() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .With(a => new CypherProjectionResult + { + Foo = a + }) + .Query; + + Assert.AreEqual("WITH a AS Foo", query.QueryText); + } + + public class Commodity + { + public string Name { get; set; } + public long UniqueId { get; set; } + } + + public class ProjectionResult + { + public Commodity Commodity { get; set; } + } + + public class CypherProjectionResult + { + public ICypherResultItem Foo { get; set; } + } + } +} diff --git a/Test/Cypher/CypherQueryTests.cs b/Neo4jClient.Tests/Cypher/CypherQueryTests.cs similarity index 100% rename from Test/Cypher/CypherQueryTests.cs rename to Neo4jClient.Tests/Cypher/CypherQueryTests.cs diff --git a/Test/Cypher/CypherReturnExpressionBuilderTests.cs b/Neo4jClient.Tests/Cypher/CypherReturnExpressionBuilderTests.cs similarity index 92% rename from Test/Cypher/CypherReturnExpressionBuilderTests.cs rename to Neo4jClient.Tests/Cypher/CypherReturnExpressionBuilderTests.cs index 1d0897d2d..ef9eb75e3 100644 --- a/Test/Cypher/CypherReturnExpressionBuilderTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherReturnExpressionBuilderTests.cs @@ -1,584 +1,556 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class CypherReturnExpressionBuilderTests - { - [Test] - public void ReturnProperty() - { - // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias - // START a=node(1) - // RETURN a.Age AS SomethingTotallyDifferent - - Expression> expression = - a => new ReturnPropertyQueryResult - { - SomethingTotallyDifferent = a.As().Age - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("a.Age AS SomethingTotallyDifferent", returnExpression.Text); - } - - [Test] - public void ReturnNode() - { - Expression> expression = - a => new - { - FooNode = a.As>() - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("a AS FooNode", returnExpression.Text); - } - - [Test] - public void ReturnPropertyWithNullablePropertyOnRightHandSide() - { - Expression> expression = - a => new Foo - { - Age = a.As().AgeNullable.Value - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("a.AgeNullable AS Age", returnExpression.Text); - } - - [Test] - public void ReturnMultipleProperties() - { - // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias - // START a=node(1) - // RETURN a.Age AS SomethingTotallyDifferent, a.Name as FirstName - - Expression> expression = - a => new ReturnPropertyQueryResult - { - SomethingTotallyDifferent = a.As().Age, - FirstName = a.As().Name - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("a.Age AS SomethingTotallyDifferent, a.Name AS FirstName", returnExpression.Text); - } - - [Test] - public void ReturnMultiplePropertiesInAnonymousType() - { - // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias - // START a=node(1) - // RETURN a.Age AS SomethingTotallyDifferent, a.Name as FirstName - - Expression> expression = - a => new - { - SomethingTotallyDifferent = a.As().Age, - FirstName = a.As().Name - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("a.Age AS SomethingTotallyDifferent, a.Name AS FirstName", returnExpression.Text); - } - - [Test] - public void ReturnMultiplePropertiesFromMultipleColumns() - { - // http://docs.neo4j.org/chunked/milestone/cypher-query-lang.html - // START john=node(1) - // MATCH john-[:friend]->()-[:friend]->fof - // RETURN john.Age, fof.Name - - Expression> expression = - (john, fof) => new ReturnPropertyQueryResult - { - SomethingTotallyDifferent = john.As().Age, - FirstName = fof.As().Name - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("john.Age AS SomethingTotallyDifferent, fof.Name AS FirstName", returnExpression.Text); - } - - [Test] - public void NullablePropertiesShouldBeQueriedAsCypherOptionalProperties_PreNeo4j20() - { - // http://docs.neo4j.org/chunked/1.6/query-return.html#return-optional-properties - // START n=node(1) - // RETURN n.Age AS Age, n.NumberOfCats? AS NumberOfCats - - Expression> expression = - n => new OptionalPropertiesQueryResult - { - Age = n.As().Age, - NumberOfCats = n.As().NumberOfCats - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Cypher19, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("n.Age AS Age, n.NumberOfCats? AS NumberOfCats", returnExpression.Text); - } - - [Test] - public void NullablePropertiesShouldNotGetSpecialHandling() - { - Expression> expression = - n => new OptionalPropertiesQueryResult - { - Age = n.As().Age, - NumberOfCats = n.As().NumberOfCats - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("n.Age AS Age, n.NumberOfCats AS NumberOfCats", returnExpression.Text); - } - - [Test] - public void ReturnNodeInColumn() - { - // START a=node(1) - // RETURN a AS Foo - - Expression> expression = - a => new - { - Foo = a.As() - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("a AS Foo", returnExpression.Text); - } - - [Test] - public void ReturnMultipleNodesInColumns() - { - // START a=node(1) - // MATCH a<--b - // RETURN a AS Foo, b AS Bar - - Expression> expression = - (a, b) => new - { - Foo = a.As(), - Bar = b.As() - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("a AS Foo, b AS Bar", returnExpression.Text); - } - - [Test] - public void ReturnCollectedNodesInColumn() - { - // http://docs.neo4j.org/chunked/1.8.M05/query-aggregation.html#aggregation-collect - // START a=node(1) - // MATCH a<--b - // RETURN collect(a) - - Expression> expression = - a => new - { - Foo = a.CollectAs() - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("collect(a) AS Foo", returnExpression.Text); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/118/collectas-causes-argumentnullexception")] - public void PreventDoubleNodeWrappedCollectAs() - { - Expression> expression = - a => new - { - Foo = a.CollectAs>() - }; - - var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - StringAssert.StartsWith(CypherReturnExpressionBuilder.CollectAsShouldNotBeNodeTExceptionMessage, ex.Message); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/118/collectas-causes-argumentnullexception")] - public void PreventDoubleNodeWrappedCollectAsDistinct() - { - Expression> expression = - a => new - { - Foo = a.CollectAsDistinct>() - }; - - var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - StringAssert.StartsWith(CypherReturnExpressionBuilder.CollectAsDistinctShouldNotBeNodeTExceptionMessage, ex.Message); - } - - [Test] - public void ReturnCollectedDistinctNodesInColumn() - { - // http://docs.neo4j.org/chunked/1.9.M05/query-aggregation.html#aggregation-distinct - // START a=node(1) - // MATCH a-->b - // RETURN collect(distinct b.eyes) - - Expression> expression = - a => new - { - Foo = a.CollectAsDistinct() - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("collect(distinct a) AS Foo", returnExpression.Text); - } - - [Test] - public void ReturnHeadCollectedNodesInColumn() - { - // http://docs.neo4j.org/chunked/milestone/query-functions-scalar.html#functions-head - // START a=node(1) - // MATCH a<--b - // RETURN head(collect(a)) - - Expression> expression = - a => new - { - Foo = a.Head().CollectAs() - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("head(collect(a)) AS Foo", returnExpression.Text); - } - - [Test] - public void ReturnLastCollectedNodesInColumn() - { - // http://docs.neo4j.org/chunked/milestone/query-functions-scalar.html#functions-last - // START a=node(1) - // MATCH a<--b - // RETURN last(collect(a)) - - Expression> expression = - a => new - { - Foo = a.Last().CollectAs() - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("last(collect(a)) AS Foo", returnExpression.Text); - } - - [Test] - public void ReturnCountInAnonymousType() - { - // http://docs.neo4j.org/chunked/1.8.M05/query-aggregation.html#aggregation-collect - // START a=node(1) - // MATCH a<--b - // RETURN count(b) - - Expression> expression = - b => new - { - Foo = b.Count() - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("count(b) AS Foo", returnExpression.Text); - } - - [Test] - public void ReturnCountOnItsOwn() - { - // http://docs.neo4j.org/chunked/1.8.M05/query-aggregation.html#aggregation-collect - // START a=node(1) - // MATCH a<--b - // RETURN count(b) - - Expression> expression = b => b.Count(); - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("count(b)", returnExpression.Text); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/116/bug-in-returning-single-nullable-value")] - public void ReturnCountOnItsOwnAsNullableLong() - { - Expression> expression = b => b.Count(); - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - Assert.AreEqual("count(b)", returnExpression.Text); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/165/as-throws-systemargumentexception-in")] - public void ReturnComplexAnonymousWithValueTypesAndCustomExpressions() - { - Expression> expression = (ping, reviews, reviewer) => new - { - PingId = Return.As("ping.Id"), - PingImage = Return.As("ping.Image"), - PingDescription = Return.As("ping.Description"), - Reviews = reviews.As(), - Reviewers = reviewer.CountDistinct(), - Avatars = Return.As>("collect(distinct reviewer.Avatar)[0..{maxAvatars}]") - }; - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - Assert.AreEqual("ping.Id AS PingId, ping.Image AS PingImage, ping.Description AS PingDescription, reviews AS Reviews, count(distinct reviewer) AS Reviewers, collect(distinct reviewer.Avatar)[0..{maxAvatars}] AS Avatars", returnExpression.Text); - } - - [Test] - [Description("https://github.com/Readify/Neo4jClient/pull/56#issuecomment-44158504")] - public void ReturnComplexTupleWithValueTypesAndCustomExpressions() - { - Expression> expression = (ping, reviews, reviewer) => new - Tuple>( - Return.As("ping.Id"), - Return.As("ping.Image"), - Return.As("ping.Description"), - reviews.As(), - reviewer.CountDistinct(), - Return.As>("collect(distinct reviewer.Avatar)[0..{maxAvatars}]") - ); - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - Assert.AreEqual("ping.Id AS Item1, ping.Image AS Item2, ping.Description AS Item3, reviews AS Item4, count(distinct reviewer) AS Item5, collect(distinct reviewer.Avatar)[0..{maxAvatars}] AS Item6", returnExpression.Text); - } - - [Test] - public void ReturnLabelsInAnonymousType() - { - // MATCH (a:User) - // WHERE a.Id == 123 - // RETURN labels(a) - - Expression> expression = - b => new - { - Foo = b.Labels() - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("labels(b) AS Foo", returnExpression.Text); - } - - [Test] - public void ReturnAllOnItsOwn() - { - Expression> expression = () => All.Count(); - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - Assert.AreEqual("count(*)", returnExpression.Text); - } - - [Test] - public void ReturnCustomStatementOnItsOwn() - { - Expression> expression = () => Return.As("custom statement"); - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - Assert.AreEqual("custom statement", returnExpression.Text); - } - - [Test] - public void ReturnCustomCypherTextFromConstant() - { - // START a=node(1) - // MATCH a<--b - // RETURN abs(sum(a.age) - sum(b.age)) - - Expression> expression = - b => new - { - Foo = Return.As("abs(sum(a.age) - sum(b.age))") - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("abs(sum(a.age) - sum(b.age)) AS Foo", returnExpression.Text); - } - - [Test] - public void ReturnCustomCypherTextFromDynamicCode() - { - // START a=node(1) - // MATCH a<--b - // RETURN abs(sum(a.age) - sum(b.age)) - - Expression> expression = - b => new - { - Foo = Return.As("abs(sum(a.age)" + new string(' ', 1) + "- sum(b.age))") - }; - - var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); - - Assert.AreEqual("abs(sum(a.age) - sum(b.age)) AS Foo", returnExpression.Text); - } - - [Test] - public void ThrowNiceErrorForChainedMethods() - { - Expression> expression = - a => new - { - Foo = a - .CollectAs() - .Select(f => f.Data) - .ToList() - }; - - var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - Assert.AreEqual(CypherReturnExpressionBuilder.ReturnExpressionCannotBeSerializedToCypherExceptionMessage, ex.Message); - } - - [Test] - public void ThrowNiceErrorForStructInNewExpression() - { - Expression> expression = - a => new KeyValuePair(); - - var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - StringAssert.StartsWith(CypherReturnExpressionBuilder.ReturnExpressionCannotBeStruct, ex.Message); - } - - [Test] - public void ThrowNiceErrorForStructInMemberInitExpression() - { - Expression> expression = - a => new KeyValuePair() - { - }; - - var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - StringAssert.StartsWith(CypherReturnExpressionBuilder.ReturnExpressionCannotBeStruct, ex.Message); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/47/problem-casting-cypher-query-results-to")] - public void ThrowNiceErrorForConstructorsWithArguments() - { - Expression> expression = - a => new KeyValuePair(a, a); - - var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - StringAssert.StartsWith(CypherReturnExpressionBuilder.ReturnExpressionShouldBeOneOfExceptionMessage, ex.Message); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/159/problems-with-nodebyindexlookup")] - public void ThrowNiceErrorForConstructorsWithArgumentsInReturnAs() - { - Expression> expression = - a => a.As(); - - var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - - const string expectedMessage = - "You've called As() in your return clause, where TypeWithoutDefaultConstructor is not a supported type. It must be a simple type (like int, string, or long), a class with a default constructor (so that we can deserialize into it), RelationshipInstance, RelationshipInstance, list of RelationshipInstance, or list of RelationshipInstance."; - StringAssert.StartsWith(expectedMessage, ex.Message); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] - public void AllowArrayOfRelationshipInstanceInReturnAs() - { - Expression> expression = a => a.As(); - Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] - public void AllowListOfRelationshipInstanceInReturnAs() - { - Expression> expression = a => a.As>(); - Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] - public void AllowEnumerableOfRelationshipInstanceInReturnAs() - { - Expression> expression = a => a.As>>(); - Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] - public void AllowArrayOfInt32InReturnAs() - { - Expression> expression = a => a.As(); - Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] - public void AllowEnumerableOfInt32InReturnAs() - { - Expression> expression = a => a.As>(); - Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] - public void AllowListOfInt32InReturnAs() - { - Expression> expression = a => a.As>(); - Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); - } - - public class TypeWithoutDefaultConstructor - { - readonly int c; - - public TypeWithoutDefaultConstructor(int a, int b) - { - c = a + b; - } - - public override string ToString() - { - return c.ToString(); - } - } - - public class Foo - { - public int Age { get; set; } - public int? AgeNullable { get; set; } - public string Name { get; set; } - public int NumberOfCats { get; set; } - } - - public class ReturnPropertyQueryResult - { - public int SomethingTotallyDifferent { get; set; } - public string FirstName { get; set; } - } - - public class OptionalPropertiesQueryResult - { - public int Age { get; set; } - public int? NumberOfCats { get; set; } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class CypherReturnExpressionBuilderTests + { + [Test] + public void ReturnProperty() + { + // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias + // START a=node(1) + // RETURN a.Age AS SomethingTotallyDifferent + + Expression> expression = + a => new ReturnPropertyQueryResult + { + SomethingTotallyDifferent = a.As().Age + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("a.Age AS SomethingTotallyDifferent", returnExpression.Text); + } + + [Test] + public void ReturnNode() + { + Expression> expression = + a => new + { + FooNode = a.As>() + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("a AS FooNode", returnExpression.Text); + } + + [Test] + public void ReturnPropertyWithNullablePropertyOnRightHandSide() + { + Expression> expression = + a => new Foo + { + Age = a.As().AgeNullable.Value + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("a.AgeNullable AS Age", returnExpression.Text); + } + + [Test] + public void ReturnMultipleProperties() + { + // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias + // START a=node(1) + // RETURN a.Age AS SomethingTotallyDifferent, a.Name as FirstName + + Expression> expression = + a => new ReturnPropertyQueryResult + { + SomethingTotallyDifferent = a.As().Age, + FirstName = a.As().Name + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("a.Age AS SomethingTotallyDifferent, a.Name AS FirstName", returnExpression.Text); + } + + [Test] + public void ReturnMultiplePropertiesInAnonymousType() + { + // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias + // START a=node(1) + // RETURN a.Age AS SomethingTotallyDifferent, a.Name as FirstName + + Expression> expression = + a => new + { + SomethingTotallyDifferent = a.As().Age, + FirstName = a.As().Name + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("a.Age AS SomethingTotallyDifferent, a.Name AS FirstName", returnExpression.Text); + } + + [Test] + public void ReturnMultiplePropertiesFromMultipleColumns() + { + // http://docs.neo4j.org/chunked/milestone/cypher-query-lang.html + // START john=node(1) + // MATCH john-[:friend]->()-[:friend]->fof + // RETURN john.Age, fof.Name + + Expression> expression = + (john, fof) => new ReturnPropertyQueryResult + { + SomethingTotallyDifferent = john.As().Age, + FirstName = fof.As().Name + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("john.Age AS SomethingTotallyDifferent, fof.Name AS FirstName", returnExpression.Text); + } + + [Test] + public void NullablePropertiesShouldBeQueriedAsCypherOptionalProperties_PreNeo4j20() + { + // http://docs.neo4j.org/chunked/1.6/query-return.html#return-optional-properties + // START n=node(1) + // RETURN n.Age AS Age, n.NumberOfCats? AS NumberOfCats + + Expression> expression = + n => new OptionalPropertiesQueryResult + { + Age = n.As().Age, + NumberOfCats = n.As().NumberOfCats + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Cypher19, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("n.Age AS Age, n.NumberOfCats? AS NumberOfCats", returnExpression.Text); + } + + [Test] + public void NullablePropertiesShouldNotGetSpecialHandling() + { + Expression> expression = + n => new OptionalPropertiesQueryResult + { + Age = n.As().Age, + NumberOfCats = n.As().NumberOfCats + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("n.Age AS Age, n.NumberOfCats AS NumberOfCats", returnExpression.Text); + } + + [Test] + public void ReturnNodeInColumn() + { + // START a=node(1) + // RETURN a AS Foo + + Expression> expression = + a => new + { + Foo = a.As() + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("a AS Foo", returnExpression.Text); + } + + [Test] + public void ReturnMultipleNodesInColumns() + { + // START a=node(1) + // MATCH a<--b + // RETURN a AS Foo, b AS Bar + + Expression> expression = + (a, b) => new + { + Foo = a.As(), + Bar = b.As() + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("a AS Foo, b AS Bar", returnExpression.Text); + } + + [Test] + public void ReturnCollectedNodesInColumn() + { + // http://docs.neo4j.org/chunked/1.8.M05/query-aggregation.html#aggregation-collect + // START a=node(1) + // MATCH a<--b + // RETURN collect(a) + + Expression> expression = + a => new + { + Foo = a.CollectAs() + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("collect(a) AS Foo", returnExpression.Text); + } + + + [Test] + public void ReturnCollectedDistinctNodesInColumn() + { + // http://docs.neo4j.org/chunked/1.9.M05/query-aggregation.html#aggregation-distinct + // START a=node(1) + // MATCH a-->b + // RETURN collect(distinct b.eyes) + + Expression> expression = + a => new + { + Foo = a.CollectAsDistinct() + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("collect(distinct a) AS Foo", returnExpression.Text); + } + + [Test] + public void ReturnHeadCollectedNodesInColumn() + { + // http://docs.neo4j.org/chunked/milestone/query-functions-scalar.html#functions-head + // START a=node(1) + // MATCH a<--b + // RETURN head(collect(a)) + + Expression> expression = + a => new + { + Foo = a.Head().CollectAs() + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("head(collect(a)) AS Foo", returnExpression.Text); + } + + [Test] + public void ReturnLastCollectedNodesInColumn() + { + // http://docs.neo4j.org/chunked/milestone/query-functions-scalar.html#functions-last + // START a=node(1) + // MATCH a<--b + // RETURN last(collect(a)) + + Expression> expression = + a => new + { + Foo = a.Last().CollectAs() + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("last(collect(a)) AS Foo", returnExpression.Text); + } + + [Test] + public void ReturnCountInAnonymousType() + { + // http://docs.neo4j.org/chunked/1.8.M05/query-aggregation.html#aggregation-collect + // START a=node(1) + // MATCH a<--b + // RETURN count(b) + + Expression> expression = + b => new + { + Foo = b.Count() + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("count(b) AS Foo", returnExpression.Text); + } + + [Test] + public void ReturnCountOnItsOwn() + { + // http://docs.neo4j.org/chunked/1.8.M05/query-aggregation.html#aggregation-collect + // START a=node(1) + // MATCH a<--b + // RETURN count(b) + + Expression> expression = b => b.Count(); + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("count(b)", returnExpression.Text); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/116/bug-in-returning-single-nullable-value")] + public void ReturnCountOnItsOwnAsNullableLong() + { + Expression> expression = b => b.Count(); + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + Assert.AreEqual("count(b)", returnExpression.Text); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/165/as-throws-systemargumentexception-in")] + public void ReturnComplexAnonymousWithValueTypesAndCustomExpressions() + { + Expression> expression = (ping, reviews, reviewer) => new + { + PingId = Return.As("ping.Id"), + PingImage = Return.As("ping.Image"), + PingDescription = Return.As("ping.Description"), + Reviews = reviews.As(), + Reviewers = reviewer.CountDistinct(), + Avatars = Return.As>("collect(distinct reviewer.Avatar)[0..{maxAvatars}]") + }; + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + Assert.AreEqual("ping.Id AS PingId, ping.Image AS PingImage, ping.Description AS PingDescription, reviews AS Reviews, count(distinct reviewer) AS Reviewers, collect(distinct reviewer.Avatar)[0..{maxAvatars}] AS Avatars", returnExpression.Text); + } + + [Test] + [Description("https://github.com/Readify/Neo4jClient/pull/56#issuecomment-44158504")] + public void ReturnComplexTupleWithValueTypesAndCustomExpressions() + { + Expression> expression = (ping, reviews, reviewer) => new + Tuple>( + Return.As("ping.Id"), + Return.As("ping.Image"), + Return.As("ping.Description"), + reviews.As(), + reviewer.CountDistinct(), + Return.As>("collect(distinct reviewer.Avatar)[0..{maxAvatars}]") + ); + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + Assert.AreEqual("ping.Id AS Item1, ping.Image AS Item2, ping.Description AS Item3, reviews AS Item4, count(distinct reviewer) AS Item5, collect(distinct reviewer.Avatar)[0..{maxAvatars}] AS Item6", returnExpression.Text); + } + + [Test] + public void ReturnLabelsInAnonymousType() + { + // MATCH (a:User) + // WHERE a.Id == 123 + // RETURN labels(a) + + Expression> expression = + b => new + { + Foo = b.Labels() + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("labels(b) AS Foo", returnExpression.Text); + } + + [Test] + public void ReturnAllOnItsOwn() + { + Expression> expression = () => All.Count(); + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + Assert.AreEqual("count(*)", returnExpression.Text); + } + + [Test] + public void ReturnCustomStatementOnItsOwn() + { + Expression> expression = () => Return.As("custom statement"); + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + Assert.AreEqual("custom statement", returnExpression.Text); + } + + [Test] + public void ReturnCustomCypherTextFromConstant() + { + // START a=node(1) + // MATCH a<--b + // RETURN abs(sum(a.age) - sum(b.age)) + + Expression> expression = + b => new + { + Foo = Return.As("abs(sum(a.age) - sum(b.age))") + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("abs(sum(a.age) - sum(b.age)) AS Foo", returnExpression.Text); + } + + [Test] + public void ReturnCustomCypherTextFromDynamicCode() + { + // START a=node(1) + // MATCH a<--b + // RETURN abs(sum(a.age) - sum(b.age)) + + Expression> expression = + b => new + { + Foo = Return.As("abs(sum(a.age)" + new string(' ', 1) + "- sum(b.age))") + }; + + var returnExpression = CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters); + + Assert.AreEqual("abs(sum(a.age) - sum(b.age)) AS Foo", returnExpression.Text); + } + + [Test] + public void ThrowNiceErrorForChainedMethods() + { + Expression> expression = + a => new + { + Foo = a + .CollectAs() + .ToList() + }; + + var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + Assert.AreEqual(CypherReturnExpressionBuilder.ReturnExpressionCannotBeSerializedToCypherExceptionMessage, ex.Message); + } + + [Test] + public void ThrowNiceErrorForStructInNewExpression() + { + Expression> expression = + a => new KeyValuePair(); + + var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + StringAssert.StartsWith(CypherReturnExpressionBuilder.ReturnExpressionCannotBeStruct, ex.Message); + } + + [Test] + public void ThrowNiceErrorForStructInMemberInitExpression() + { + Expression> expression = + a => new KeyValuePair() + { + }; + + var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + StringAssert.StartsWith(CypherReturnExpressionBuilder.ReturnExpressionCannotBeStruct, ex.Message); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/47/problem-casting-cypher-query-results-to")] + public void ThrowNiceErrorForConstructorsWithArguments() + { + Expression> expression = + a => new KeyValuePair(a, a); + + var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + StringAssert.StartsWith(CypherReturnExpressionBuilder.ReturnExpressionShouldBeOneOfExceptionMessage, ex.Message); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/159/problems-with-nodebyindexlookup")] + public void ThrowNiceErrorForConstructorsWithArgumentsInReturnAs() + { + Expression> expression = + a => a.As(); + + var ex = Assert.Throws(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + + const string expectedMessage = + "You've called As() in your return clause, where TypeWithoutDefaultConstructor is not a supported type. It must be a simple type (like int, string, or long), a class with a default constructor (so that we can deserialize into it), RelationshipInstance, RelationshipInstance, list of RelationshipInstance, or list of RelationshipInstance."; + StringAssert.StartsWith(expectedMessage, ex.Message); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] + public void AllowArrayOfRelationshipInstanceInReturnAs() + { + Expression> expression = a => a.As(); + Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] + public void AllowListOfRelationshipInstanceInReturnAs() + { + Expression> expression = a => a.As>(); + Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] + public void AllowEnumerableOfRelationshipInstanceInReturnAs() + { + Expression> expression = a => a.As>>(); + Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] + public void AllowArrayOfInt32InReturnAs() + { + Expression> expression = a => a.As(); + Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] + public void AllowEnumerableOfInt32InReturnAs() + { + Expression> expression = a => a.As>(); + Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/171/remove-false-protection-around-return-a")] + public void AllowListOfInt32InReturnAs() + { + Expression> expression = a => a.As>(); + Assert.DoesNotThrow(() => CypherReturnExpressionBuilder.BuildText(expression, CypherCapabilities.Default, GraphClient.DefaultJsonConverters)); + } + + public class TypeWithoutDefaultConstructor + { + readonly int c; + + public TypeWithoutDefaultConstructor(int a, int b) + { + c = a + b; + } + + public override string ToString() + { + return c.ToString(); + } + } + + public class Foo + { + public int Age { get; set; } + public int? AgeNullable { get; set; } + public string Name { get; set; } + public int NumberOfCats { get; set; } + } + + public class ReturnPropertyQueryResult + { + public int SomethingTotallyDifferent { get; set; } + public string FirstName { get; set; } + } + + public class OptionalPropertiesQueryResult + { + public int Age { get; set; } + public int? NumberOfCats { get; set; } + } + } +} diff --git a/Test/Cypher/CypherWhereExpressionBuilderTests.cs b/Neo4jClient.Tests/Cypher/CypherWhereExpressionBuilderTests.cs similarity index 97% rename from Test/Cypher/CypherWhereExpressionBuilderTests.cs rename to Neo4jClient.Tests/Cypher/CypherWhereExpressionBuilderTests.cs index ac20f53c6..82db0c698 100644 --- a/Test/Cypher/CypherWhereExpressionBuilderTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherWhereExpressionBuilderTests.cs @@ -1,639 +1,639 @@ -using NUnit.Framework; -using Neo4jClient.Cypher; -using System.Collections.Generic; -using System; -using System.Linq.Expressions; - -namespace Neo4jClient.Test.Cypher -{ - public class CypherWhereExpressionBuilderTests - { - // ReSharper disable ClassNeverInstantiated.Local - // ReSharper disable UnusedAutoPropertyAccessor.Local - class Foo - { - public int Bar { get; set; } - public int? NullableBar { get; set; } - public bool SomeBool { get; set; } - } - - class Nested - { - public Foo Foo { get; set; } - } - - class SuperNested - { - public Nested Nested { get; set; } - } - // ReSharper restore ClassNeverInstantiated.Local - // ReSharper restore UnusedAutoPropertyAccessor.Local - - // This must be a public static field, that's not a constant - public static int BazField = 123; - - // This must be a public static property - public static int BazProperty - { - get { return 456; } - } - - interface IFoo - { - int Bar { get; set; } - } - - [Test] - public void AccessStaticField() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.Bar == BazField; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.Bar = {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - public void AccessStaticProperty() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.Bar == BazProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.Bar = {p0})", result); - Assert.AreEqual(456, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/158/neo4jclient-cypher-where-clauses-using-a")] - public void ShouldCompareAgainstValueOfNullableType() - { - var bar = (long?)123; - - var parameters = new Dictionary(); - Expression> expression = foo => foo.Bar == bar.Value; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.Bar = {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyToConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar == 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar! = {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - public void ForPre20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNotConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar != 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar? <> {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/103/cypher-queries-include-nodes-with-missing")] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyGreaterThanConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar > 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar! > {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/103/cypher-queries-include-nodes-with-missing")] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyGreaterThanOrEqualToConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar >= 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar! >= {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/103/cypher-queries-include-nodes-with-missing")] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyLessThanConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar < 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar! < {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/103/cypher-queries-include-nodes-with-missing")] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyLessThanOrEqualToConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar <= 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar! <= {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - public void ForPre20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNullProperty() - { - var parameters = new Dictionary(); - var fooWithNulls = new Foo - { - NullableBar = null - }; - Expression> expression = foo => foo.NullableBar == fooWithNulls.NullableBar; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar? is null)", result); - } - - [Test] - public void For20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNullProperty() - { - var parameters = new Dictionary(); - var fooWithNulls = new Foo - { - NullableBar = null - }; - Expression> expression = foo => foo.NullableBar == fooWithNulls.NullableBar; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher20); - - Assert.AreEqual("(not(has(foo.NullableBar)))", result); - } - - [Test] - public void ForPre20VersionsEvaluateTrueWhenComparingNotMissingNullablePropertyToNullProperty() - { - var parameters = new Dictionary(); - var fooWithNulls = new Foo - { - NullableBar = null - }; - Expression> expression = foo => foo.NullableBar != fooWithNulls.NullableBar; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar? is not null)", result); - } - - [Test] - public void For20VersionsEvaluateTrueWhenComparingNotMissingNullablePropertyToNullProperty() - { - var parameters = new Dictionary(); - var fooWithNulls = new Foo - { - NullableBar = null - }; - Expression> expression = foo => foo.NullableBar != fooWithNulls.NullableBar; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher20); - - Assert.AreEqual("(has(foo.NullableBar))", result); - } - - [Test] - public void ForPre20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNull() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar == null; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar? is null)", result); - } - - [Test] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyToNotNull() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar != null; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar? is not null)", result); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyToLocalMemberValue() - { - var localObject = new {NoneCypherLocalProperty = 123}; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar == localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar! = {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] - public void ForPre20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNotLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar != localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar? <> {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyGreaterThanLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar > localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar! > {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyGreaterThanOrEqualToLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar >= localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar! >= {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyLessThanLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar < localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar! < {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] - public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyLessThanOrEqualToLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar <= localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); - - Assert.AreEqual("(foo.NullableBar! <= {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyToConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar == 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar = {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyToNotConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar != 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar <> {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyGreaterThanConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar > 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar > {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyGreaterThanOrEqualToConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar >= 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar >= {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyLessThanConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar < 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar < {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyLessThanOrEqualToConstantValue() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar <= 123; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar <= {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyToNull() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar == null; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(not(has(foo.NullableBar)))", result); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyToNotNull() - { - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar != null; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(has(foo.NullableBar))", result); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyToLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar == localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar = {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyToNotLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar != localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar <> {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyGreaterThanLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar > localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar > {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyGreaterThanOrEqualToLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar >= localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar >= {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyLessThanLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar < localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar < {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] - public void DontSuffixPropertyWhenComparingMissingNullablePropertyLessThanOrEqualToLocalMemberValue() - { - var localObject = new { NoneCypherLocalProperty = 123 }; - var parameters = new Dictionary(); - Expression> expression = foo => foo.NullableBar <= localObject.NoneCypherLocalProperty; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.NullableBar <= {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - public void ShouldComparePropertiesAcrossEntities() - { - // http://stackoverflow.com/questions/15718916/neo4jclient-where-clause-not-putting-in-parameters - // Where((otherStartNodes, startNode) => otherStartNodes.Id != startNode.Id) - - var parameters = new Dictionary(); - Expression> expression = - (p1, p2) => p1.Bar == p2.Bar; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(p1.Bar = p2.Bar)", result); - } - - [Test] - public void ShouldComparePropertiesAcrossEntitiesNotEqual() - { - // http://stackoverflow.com/questions/15718916/neo4jclient-where-clause-not-putting-in-parameters - // Where((otherStartNodes, startNode) => otherStartNodes.Id != startNode.Id) - - var parameters = new Dictionary(); - Expression> expression = - (p1, p2) => p1.Bar != p2.Bar; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(p1.Bar <> p2.Bar)", result); - } - - [Test] - public void ShouldComparePropertiesAcrossInterfaces() - { - // http://stackoverflow.com/questions/15718916/neo4jclient-where-clause-not-putting-in-parameters - // Where((otherStartNodes, startNode) => otherStartNodes.Id != startNode.Id) - - var parameters = new Dictionary(); - Expression> expression = - (p1, p2) => p1.Bar == p2.Bar; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(p1.Bar = p2.Bar)", result); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/73/where-clause-not-building-correctly-with")] - public void ShouldComparePropertiesAcrossInterfacesViaGenerics() - { - TestShouldComparePropertiesAcrossInterfacesViaGenerics(); - } - - static void TestShouldComparePropertiesAcrossInterfacesViaGenerics() where TNode : IFoo - { - var parameters = new Dictionary(); - Expression> expression = - (p1, p2) => p1.Bar == p2.Bar; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(p1.Bar = p2.Bar)", result); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/99/throw-error-when-unary-expressions-are")] - public void ThrowNotSupportedExceptionForMemberAccessExpression() - { - // Where(n => n.Bar) - - var parameters = new Dictionary(); - Expression> expression = - p1 => p1.SomeBool; - - Assert.Throws(() => - CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v))); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/99/throw-error-when-unary-expressions-are")] - public void ThrowNotSupportedExceptionForUnaryNotExpression() - { - // Where(n => !n.Bar) - - var parameters = new Dictionary(); - Expression> expression = - p1 => !p1.SomeBool; - - Assert.Throws(() => - CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v))); - } - - [Test] - public void GetsValueFromNestedProperty() - { - var comparison = new Nested {Foo = new Foo {Bar = BazField}}; - - var parameters = new Dictionary(); - Expression> expression = foo => foo.Bar == comparison.Foo.Bar; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.Bar = {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - [Test] - public void GetsValueFromSuperNestedProperty() - { - var comparison = new SuperNested {Nested= new Nested {Foo = new Foo {Bar = BazField}}}; - - var parameters = new Dictionary(); - Expression> expression = foo => foo.Bar == comparison.Nested.Foo.Bar; - - var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); - - Assert.AreEqual("(foo.Bar = {p0})", result); - Assert.AreEqual(123, parameters["p0"]); - } - - static string CreateParameter(IDictionary parameters, object paramValue) - { - var paramName = string.Format("p{0}", parameters.Count); - parameters.Add(paramName, paramValue); - return "{" + paramName + "}"; - } - } -} +using NUnit.Framework; +using Neo4jClient.Cypher; +using System.Collections.Generic; +using System; +using System.Linq.Expressions; + +namespace Neo4jClient.Test.Cypher +{ + public class CypherWhereExpressionBuilderTests + { + // ReSharper disable ClassNeverInstantiated.Local + // ReSharper disable UnusedAutoPropertyAccessor.Local + class Foo + { + public int Bar { get; set; } + public int? NullableBar { get; set; } + public bool SomeBool { get; set; } + } + + class Nested + { + public Foo Foo { get; set; } + } + + class SuperNested + { + public Nested Nested { get; set; } + } + // ReSharper restore ClassNeverInstantiated.Local + // ReSharper restore UnusedAutoPropertyAccessor.Local + + // This must be a public static field, that's not a constant + public static int BazField = 123; + + // This must be a public static property + public static int BazProperty + { + get { return 456; } + } + + interface IFoo + { + int Bar { get; set; } + } + + [Test] + public void AccessStaticField() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.Bar == BazField; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.Bar = {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + public void AccessStaticProperty() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.Bar == BazProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.Bar = {p0})", result); + Assert.AreEqual(456, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/158/neo4jclient-cypher-where-clauses-using-a")] + public void ShouldCompareAgainstValueOfNullableType() + { + var bar = (long?)123; + + var parameters = new Dictionary(); + Expression> expression = foo => foo.Bar == bar.Value; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.Bar = {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyToConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar == 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar! = {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + public void ForPre20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNotConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar != 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar? <> {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/103/cypher-queries-include-nodes-with-missing")] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyGreaterThanConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar > 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar! > {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/103/cypher-queries-include-nodes-with-missing")] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyGreaterThanOrEqualToConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar >= 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar! >= {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/103/cypher-queries-include-nodes-with-missing")] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyLessThanConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar < 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar! < {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/103/cypher-queries-include-nodes-with-missing")] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyLessThanOrEqualToConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar <= 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar! <= {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + public void ForPre20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNullProperty() + { + var parameters = new Dictionary(); + var fooWithNulls = new Foo + { + NullableBar = null + }; + Expression> expression = foo => foo.NullableBar == fooWithNulls.NullableBar; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar? is null)", result); + } + + [Test] + public void For20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNullProperty() + { + var parameters = new Dictionary(); + var fooWithNulls = new Foo + { + NullableBar = null + }; + Expression> expression = foo => foo.NullableBar == fooWithNulls.NullableBar; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher20); + + Assert.AreEqual("(not(has(foo.NullableBar)))", result); + } + + [Test] + public void ForPre20VersionsEvaluateTrueWhenComparingNotMissingNullablePropertyToNullProperty() + { + var parameters = new Dictionary(); + var fooWithNulls = new Foo + { + NullableBar = null + }; + Expression> expression = foo => foo.NullableBar != fooWithNulls.NullableBar; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar? is not null)", result); + } + + [Test] + public void For20VersionsEvaluateTrueWhenComparingNotMissingNullablePropertyToNullProperty() + { + var parameters = new Dictionary(); + var fooWithNulls = new Foo + { + NullableBar = null + }; + Expression> expression = foo => foo.NullableBar != fooWithNulls.NullableBar; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher20); + + Assert.AreEqual("(has(foo.NullableBar))", result); + } + + [Test] + public void ForPre20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNull() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar == null; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar? is null)", result); + } + + [Test] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyToNotNull() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar != null; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar? is not null)", result); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyToLocalMemberValue() + { + var localObject = new {NoneCypherLocalProperty = 123}; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar == localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar! = {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] + public void ForPre20VersionsEvaluateTrueWhenComparingMissingNullablePropertyToNotLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar != localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar? <> {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyGreaterThanLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar > localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar! > {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyGreaterThanOrEqualToLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar >= localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar! >= {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyLessThanLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar < localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar! < {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/98/where-andwhere-include-nodes-with-missing")] + public void ForPre20VersionsEvaluateFalseWhenComparingMissingNullablePropertyLessThanOrEqualToLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar <= localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v), CypherCapabilities.Cypher19); + + Assert.AreEqual("(foo.NullableBar! <= {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyToConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar == 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar = {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyToNotConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar != 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar <> {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyGreaterThanConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar > 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar > {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyGreaterThanOrEqualToConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar >= 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar >= {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyLessThanConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar < 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar < {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyLessThanOrEqualToConstantValue() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar <= 123; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar <= {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyToNull() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar == null; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(not(has(foo.NullableBar)))", result); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyToNotNull() + { + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar != null; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(has(foo.NullableBar))", result); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyToLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar == localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar = {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyToNotLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar != localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar <> {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyGreaterThanLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar > localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar > {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyGreaterThanOrEqualToLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar >= localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar >= {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyLessThanLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar < localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar < {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/163/neo4j-v2m6-client-syntax-error")] + public void DontSuffixPropertyWhenComparingMissingNullablePropertyLessThanOrEqualToLocalMemberValue() + { + var localObject = new { NoneCypherLocalProperty = 123 }; + var parameters = new Dictionary(); + Expression> expression = foo => foo.NullableBar <= localObject.NoneCypherLocalProperty; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.NullableBar <= {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + public void ShouldComparePropertiesAcrossEntities() + { + // http://stackoverflow.com/questions/15718916/neo4jclient-where-clause-not-putting-in-parameters + // Where((otherStartNodes, startNode) => otherStartNodes.Id != startNode.Id) + + var parameters = new Dictionary(); + Expression> expression = + (p1, p2) => p1.Bar == p2.Bar; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(p1.Bar = p2.Bar)", result); + } + + [Test] + public void ShouldComparePropertiesAcrossEntitiesNotEqual() + { + // http://stackoverflow.com/questions/15718916/neo4jclient-where-clause-not-putting-in-parameters + // Where((otherStartNodes, startNode) => otherStartNodes.Id != startNode.Id) + + var parameters = new Dictionary(); + Expression> expression = + (p1, p2) => p1.Bar != p2.Bar; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(p1.Bar <> p2.Bar)", result); + } + + [Test] + public void ShouldComparePropertiesAcrossInterfaces() + { + // http://stackoverflow.com/questions/15718916/neo4jclient-where-clause-not-putting-in-parameters + // Where((otherStartNodes, startNode) => otherStartNodes.Id != startNode.Id) + + var parameters = new Dictionary(); + Expression> expression = + (p1, p2) => p1.Bar == p2.Bar; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(p1.Bar = p2.Bar)", result); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/73/where-clause-not-building-correctly-with")] + public void ShouldComparePropertiesAcrossInterfacesViaGenerics() + { + TestShouldComparePropertiesAcrossInterfacesViaGenerics(); + } + + static void TestShouldComparePropertiesAcrossInterfacesViaGenerics() where TNode : IFoo + { + var parameters = new Dictionary(); + Expression> expression = + (p1, p2) => p1.Bar == p2.Bar; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(p1.Bar = p2.Bar)", result); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/99/throw-error-when-unary-expressions-are")] + public void ThrowNotSupportedExceptionForMemberAccessExpression() + { + // Where(n => n.Bar) + + var parameters = new Dictionary(); + Expression> expression = + p1 => p1.SomeBool; + + Assert.Throws(() => + CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v))); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/99/throw-error-when-unary-expressions-are")] + public void ThrowNotSupportedExceptionForUnaryNotExpression() + { + // Where(n => !n.Bar) + + var parameters = new Dictionary(); + Expression> expression = + p1 => !p1.SomeBool; + + Assert.Throws(() => + CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v))); + } + + [Test] + public void GetsValueFromNestedProperty() + { + var comparison = new Nested {Foo = new Foo {Bar = BazField}}; + + var parameters = new Dictionary(); + Expression> expression = foo => foo.Bar == comparison.Foo.Bar; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.Bar = {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + [Test] + public void GetsValueFromSuperNestedProperty() + { + var comparison = new SuperNested {Nested= new Nested {Foo = new Foo {Bar = BazField}}}; + + var parameters = new Dictionary(); + Expression> expression = foo => foo.Bar == comparison.Nested.Foo.Bar; + + var result = CypherWhereExpressionBuilder.BuildText(expression, v => CreateParameter(parameters, v)); + + Assert.AreEqual("(foo.Bar = {p0})", result); + Assert.AreEqual(123, parameters["p0"]); + } + + static string CreateParameter(IDictionary parameters, object paramValue) + { + var paramName = string.Format("p{0}", parameters.Count); + parameters.Add(paramName, paramValue); + return "{" + paramName + "}"; + } + } +} diff --git a/Test/Cypher/DocumentationExamples.cs b/Neo4jClient.Tests/Cypher/DocumentationExamples.cs similarity index 96% rename from Test/Cypher/DocumentationExamples.cs rename to Neo4jClient.Tests/Cypher/DocumentationExamples.cs index 7fe75ebb9..8f8b6c11f 100644 --- a/Test/Cypher/DocumentationExamples.cs +++ b/Neo4jClient.Tests/Cypher/DocumentationExamples.cs @@ -1,301 +1,301 @@ -using Neo4jClient.Cypher; - -// ReSharper disable ClassNeverInstantiated.Local -// ReSharper disable UnusedAutoPropertyAccessor.Local -// ReSharper disable UnusedVariable - -namespace Neo4jClient.Test.Cypher -{ - public class DocumentationExamples - { - static IGraphClient BuildClient() - { - return null; - } - - public void NodeById() - { - // ##start Cypher - // START n=node(1) - // RETURN n - // ##end Cypher - - var client = BuildClient(); - - var someNodeReferenceAlreadyLoaded = (NodeReference)1; - // ##start C# - var results = client.Cypher - .Start(new { n = someNodeReferenceAlreadyLoaded }) - .Return(n => n.Node()) - .Results; - // ##end C# - - // ##start Note - // You could use Start(new { n = (NodeReference)1 }), however we try to avoid passing around integer references like that. You should find the node via another query, or via an index, rather than remembering ids. To get your query started, you can use IGraphClient.RootNode. - // ##end Note - } - - public void RelationshipById() - { - // ##start Cypher - // START n=relationship(1) - // RETURN n - // ##end Cypher - - var client = BuildClient(); - - var someRelationshipReferenceAlreadyLoaded = (RelationshipReference)1; - // ##start C# - var results = client.Cypher - .Start(new { r = someRelationshipReferenceAlreadyLoaded }) - .Return(r => r.As()) - .Results; - // ##end C# - - // ##start Note - // You could use Start(new { r = (RelationshipReference)1 }), however we try to avoid passing around integer references like that. You should find the relationship via another query, or via an index, rather than remembering ids. - // ##end Note - } - - public void MultipleNodesById() - { - // ##start Cypher - // START n=node(1, 2, 3) - // RETURN n - // ##end Cypher - - var client = BuildClient(); - - var n1 = (NodeReference)1; - var n2 = (NodeReference)1; - var n3 = (NodeReference)1; - // ##start C# - var results = client.Cypher - .Start(new { n = new[] { n1, n2, n3 } }) - .Return(n => n.Node()) - .Results; - // ##end C# - } - - public void AllNodes() - { - // ##start Cypher - // START n=node(*) - // RETURN n - // ##end Cypher - - var client = BuildClient(); - - // ##start C# - var results = client.Cypher - .Start(new { n = All.Nodes }) - .Return(n => n.Node()) - .Results; - // ##end C# - } - - public void NodeByIndexLookup() - { - // ##start Cypher - // START n=node:people(name = 'Bob') - // RETURN n - // ##end Cypher - - var client = BuildClient(); - - // ##start C# - var results = client.Cypher - .Start(new { n = Node.ByIndexLookup("people", "name", "Bob") }) - .Return(n => n.Node()) - .Results; - // ##end C# - } - - public void Example1() - { - // ##start Cypher - // START john=node:node_auto_index(name = 'John') - // MATCH john-[:friend]->()-[:friend]->fof - // RETURN john, fof - // ##end Cypher - - var client = BuildClient(); - - // ##start C# - var results = client.Cypher - .Start(new {john = Node.ByIndexLookup("node_auto_index", "name", "John")}) - .Match("john-[:friend]->()-[:friend]->fof") - .Return((john, fof) => new - { - John = john.As(), - FriendOfFriend = fof.As() - }) - .Results; - // ##end C# - } - - public void Example2() - { - // ##start Cypher - // MATCH n - // WHERE n.Name = 'B' - // RETURN n - // ##end Cypher - - var client = BuildClient(); - - // ##start C# - var results = client.Cypher - .Match("n") - .Where(n => n.Name == "B") - .Return(n => n.As()) - .Results; - // ##end C# - } - - public void Example3() - { - // ##start Cypher - // MATCH n - // WHERE n.Name = 'B' - // RETURN n.Age - // ##end Cypher - - var client = BuildClient(); - - // ##start C# - var results = client.Cypher - .Match("n") - .Where(n => n.Name == "B") - .Return(n => n.As().Age) - .Results; - // ##end C# - } - - public void Example4() - { - // ##start Cypher - // MATCH a-->b - // WHERE a.Name = 'A' - // RETURN DISTINCT b - // ##end Cypher - - var client = BuildClient(); - - // ##start C# - var results = client.Cypher - .Match("a-->b") - .Where(a => a.Name == "A") - .ReturnDistinct(b => b.As()) - .Results; - // ##end C# - } - - public void Example5() - { - // ##start Cypher - // MATCH n - // RETURN n - // SKIP 1 - // LIMIT 2 - // ##end Cypher - - var client = BuildClient(); - - // ##start C# - var results = client.Cypher - .Match("n") - .Return(n => n.As()) - .Skip(1) - .Limit(2) - .Results; - // ##end C# - } - - public void Example6() - { - // ##start Cypher - // MATCH david--otherPerson-->() - // WHERE david.name='David' - // WITH otherPerson, count(*) AS foaf - // WHERE foaf > 1 - // RETURN otherPerson - // ##end Cypher - - var client = BuildClient(); - - // ##start C# - var results = client.Cypher - .Match("david--otherPerson-->()") - .Where(david => david.Name == "David") - .With(otherPerson => new - { - otherPerson, - foaf = "count(*)" - }) - .Where(foaf => foaf > 1) - .Return(otherPerson => otherPerson.As()) - .Results; - // ##end C# - } - - public void Example7() - { - // ##start Cypher - // MATCH n - // WITH n - // ORDER BY n.name DESC - // LIMIT 3 - // RETURN collect(n) - // ##end Cypher - - var client = BuildClient(); - - // ##start C# - var results = client.Cypher - .Match("n") - .With("n") - .OrderByDescending("n.name") - .Limit(3) - .Return(n => n.CollectAs()) - .Results; - // ##end C# - } - - public void Example8() - { - // ##start Cypher - // MATCH n:Actor - // RETURN n.Name AS Name - // UNION ALL - // MATCH n:Movie - // RETURN n.Title AS Name - // ##end Cypher - - var client = BuildClient(); - - // ##start C# - var results = client.Cypher - .Match("n:Actor") - .Return(n => n.As().Name) - .UnionAll() - .Match("n:Movie") - .Return(n => new { - Name = n.As().Title - }) - .Results; - // ##end C# - } - - class Person - { - public string Name { get; set; } - public int Age { get; set; } - } - - class Movie - { - public string Title { get; set; } - } - } -} +using Neo4jClient.Cypher; + +// ReSharper disable ClassNeverInstantiated.Local +// ReSharper disable UnusedAutoPropertyAccessor.Local +// ReSharper disable UnusedVariable + +namespace Neo4jClient.Test.Cypher +{ + public class DocumentationExamples + { + static IGraphClient BuildClient() + { + return null; + } + + public void NodeById() + { + // ##start Cypher + // START n=node(1) + // RETURN n + // ##end Cypher + + var client = BuildClient(); + + var someNodeReferenceAlreadyLoaded = (NodeReference)1; + // ##start C# + var results = client.Cypher + .Start(new { n = someNodeReferenceAlreadyLoaded }) + .Return(n => n.Node()) + .Results; + // ##end C# + + // ##start Note + // You could use Start(new { n = (NodeReference)1 }), however we try to avoid passing around integer references like that. You should find the node via another query, or via an index, rather than remembering ids. To get your query started, you can use IGraphClient.RootNode. + // ##end Note + } + + public void RelationshipById() + { + // ##start Cypher + // START n=relationship(1) + // RETURN n + // ##end Cypher + + var client = BuildClient(); + + var someRelationshipReferenceAlreadyLoaded = (RelationshipReference)1; + // ##start C# + var results = client.Cypher + .Start(new { r = someRelationshipReferenceAlreadyLoaded }) + .Return(r => r.As()) + .Results; + // ##end C# + + // ##start Note + // You could use Start(new { r = (RelationshipReference)1 }), however we try to avoid passing around integer references like that. You should find the relationship via another query, or via an index, rather than remembering ids. + // ##end Note + } + + public void MultipleNodesById() + { + // ##start Cypher + // START n=node(1, 2, 3) + // RETURN n + // ##end Cypher + + var client = BuildClient(); + + var n1 = (NodeReference)1; + var n2 = (NodeReference)1; + var n3 = (NodeReference)1; + // ##start C# + var results = client.Cypher + .Start(new { n = new[] { n1, n2, n3 } }) + .Return(n => n.Node()) + .Results; + // ##end C# + } + + public void AllNodes() + { + // ##start Cypher + // START n=node(*) + // RETURN n + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = client.Cypher + .Start(new { n = All.Nodes }) + .Return(n => n.Node()) + .Results; + // ##end C# + } + + public void NodeByIndexLookup() + { + // ##start Cypher + // START n=node:people(name = 'Bob') + // RETURN n + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = client.Cypher + .Start(new { n = Node.ByIndexLookup("people", "name", "Bob") }) + .Return(n => n.Node()) + .Results; + // ##end C# + } + + public void Example1() + { + // ##start Cypher + // START john=node:node_auto_index(name = 'John') + // MATCH john-[:friend]->()-[:friend]->fof + // RETURN john, fof + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = client.Cypher + .Start(new {john = Node.ByIndexLookup("node_auto_index", "name", "John")}) + .Match("john-[:friend]->()-[:friend]->fof") + .Return((john, fof) => new + { + John = john.As(), + FriendOfFriend = fof.As() + }) + .Results; + // ##end C# + } + + public void Example2() + { + // ##start Cypher + // MATCH n + // WHERE n.Name = 'B' + // RETURN n + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = client.Cypher + .Match("n") + .Where(n => n.Name == "B") + .Return(n => n.As()) + .Results; + // ##end C# + } + + public void Example3() + { + // ##start Cypher + // MATCH n + // WHERE n.Name = 'B' + // RETURN n.Age + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = client.Cypher + .Match("n") + .Where(n => n.Name == "B") + .Return(n => n.As().Age) + .Results; + // ##end C# + } + + public void Example4() + { + // ##start Cypher + // MATCH a-->b + // WHERE a.Name = 'A' + // RETURN DISTINCT b + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = client.Cypher + .Match("a-->b") + .Where(a => a.Name == "A") + .ReturnDistinct(b => b.As()) + .Results; + // ##end C# + } + + public void Example5() + { + // ##start Cypher + // MATCH n + // RETURN n + // SKIP 1 + // LIMIT 2 + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = client.Cypher + .Match("n") + .Return(n => n.As()) + .Skip(1) + .Limit(2) + .Results; + // ##end C# + } + + public void Example6() + { + // ##start Cypher + // MATCH david--otherPerson-->() + // WHERE david.name='David' + // WITH otherPerson, count(*) AS foaf + // WHERE foaf > 1 + // RETURN otherPerson + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = client.Cypher + .Match("david--otherPerson-->()") + .Where(david => david.Name == "David") + .With(otherPerson => new + { + otherPerson, + foaf = "count(*)" + }) + .Where(foaf => foaf > 1) + .Return(otherPerson => otherPerson.As()) + .Results; + // ##end C# + } + + public void Example7() + { + // ##start Cypher + // MATCH n + // WITH n + // ORDER BY n.name DESC + // LIMIT 3 + // RETURN collect(n) + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = client.Cypher + .Match("n") + .With("n") + .OrderByDescending("n.name") + .Limit(3) + .Return(n => n.CollectAs()) + .Results; + // ##end C# + } + + public void Example8() + { + // ##start Cypher + // MATCH n:Actor + // RETURN n.Name AS Name + // UNION ALL + // MATCH n:Movie + // RETURN n.Title AS Name + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = client.Cypher + .Match("n:Actor") + .Return(n => n.As().Name) + .UnionAll() + .Match("n:Movie") + .Return(n => new { + Name = n.As().Title + }) + .Results; + // ##end C# + } + + class Person + { + public string Name { get; set; } + public int Age { get; set; } + } + + class Movie + { + public string Title { get; set; } + } + } +} diff --git a/Test/Cypher/QueryWriterTests.cs b/Neo4jClient.Tests/Cypher/QueryWriterTests.cs similarity index 95% rename from Test/Cypher/QueryWriterTests.cs rename to Neo4jClient.Tests/Cypher/QueryWriterTests.cs index 73d7b3f81..e60ff120b 100644 --- a/Test/Cypher/QueryWriterTests.cs +++ b/Neo4jClient.Tests/Cypher/QueryWriterTests.cs @@ -1,129 +1,130 @@ -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class QueryWriterTests - { - [Test] - public void EmptyQueryForNoClauses() - { - var writer = new QueryWriter(); - - var query = writer.ToCypherQuery(); - Assert.AreEqual("", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void AppendClause() - { - var writer = new QueryWriter(); - - writer.AppendClause("foo"); - - var query = writer.ToCypherQuery(); - Assert.AreEqual("foo", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void AppendMultipleClauses() - { - var writer = new QueryWriter(); - - writer.AppendClause("foo"); - writer.AppendClause("bar"); - - var query = writer.ToCypherQuery(); - Assert.AreEqual("foo\r\nbar", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count); - } - - [Test] - public void AppendClauseWithParameter() - { - var writer = new QueryWriter(); - - writer.AppendClause("foo {0}", "bar"); - - var query = writer.ToCypherQuery(); - Assert.AreEqual("foo {p0}", query.QueryText); - Assert.AreEqual(1, query.QueryParameters.Count); - Assert.AreEqual("bar", query.QueryParameters["p0"]); - } - - [Test] - public void ToCypherQueryShouldNotIncrementParamCountsWhenGeneratedTwice() - { - var writer = new QueryWriter(); - - writer.AppendClause("foo {0}", "bar"); - - var query1 = writer.ToCypherQuery(); - Assert.AreEqual("foo {p0}", query1.QueryText); - Assert.AreEqual(1, query1.QueryParameters.Count); - Assert.AreEqual("bar", query1.QueryParameters["p0"]); - - var query2 = writer.ToCypherQuery(); - Assert.AreEqual("foo {p0}", query2.QueryText); - Assert.AreEqual(1, query2.QueryParameters.Count); - Assert.AreEqual("bar", query2.QueryParameters["p0"]); - } - - [Test] - public void ToCypherQueryShouldNotLeakNewParamsIntoPreviouslyBuiltQuery() - { - var writer = new QueryWriter(); - - writer.AppendClause("foo {0}", "bar"); - var query1 = writer.ToCypherQuery(); - - writer.AppendClause("baz {0}", "qak"); - var query2 = writer.ToCypherQuery(); - - Assert.AreEqual("foo {p0}", query1.QueryText); - Assert.AreEqual(1, query1.QueryParameters.Count); - Assert.AreEqual("bar", query1.QueryParameters["p0"]); - - Assert.AreEqual("foo {p0}\r\nbaz {p1}", query2.QueryText); - Assert.AreEqual(2, query2.QueryParameters.Count); - Assert.AreEqual("bar", query2.QueryParameters["p0"]); - Assert.AreEqual("qak", query2.QueryParameters["p1"]); - } - - [Test] - public void AppendClauseWithMultipleParameters() - { - var writer = new QueryWriter(); - - writer.AppendClause("foo {0} bar {1}", "baz", "qak"); - - var query = writer.ToCypherQuery(); - Assert.AreEqual("foo {p0} bar {p1}", query.QueryText); - Assert.AreEqual(2, query.QueryParameters.Count); - Assert.AreEqual("baz", query.QueryParameters["p0"]); - Assert.AreEqual("qak", query.QueryParameters["p1"]); - } - - [Test] - public void AppendMultipleClausesWithMultipleParameters() - { - var writer = new QueryWriter(); - - writer.AppendClause("foo {0} bar {1}", "baz", "qak"); - writer.AppendClause("{0} qoo {1} zoo", "abc", "xyz"); - - var query = writer.ToCypherQuery(); - const string expectedText = @"foo {p0} bar {p1} -{p2} qoo {p3} zoo"; - Assert.AreEqual(expectedText, query.QueryText); - Assert.AreEqual(4, query.QueryParameters.Count); - Assert.AreEqual("baz", query.QueryParameters["p0"]); - Assert.AreEqual("qak", query.QueryParameters["p1"]); - Assert.AreEqual("abc", query.QueryParameters["p2"]); - Assert.AreEqual("xyz", query.QueryParameters["p3"]); - } - } -} +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + using System; + + [TestFixture] + public class QueryWriterTests + { + [Test] + public void EmptyQueryForNoClauses() + { + var writer = new QueryWriter(); + + var query = writer.ToCypherQuery(); + Assert.AreEqual("", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void AppendClause() + { + var writer = new QueryWriter(); + + writer.AppendClause("foo"); + + var query = writer.ToCypherQuery(); + Assert.AreEqual("foo", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void AppendMultipleClauses() + { + var writer = new QueryWriter(); + + writer.AppendClause("foo"); + writer.AppendClause("bar"); + + var query = writer.ToCypherQuery(); + Assert.AreEqual("foo\r\nbar", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count); + } + + [Test] + public void AppendClauseWithParameter() + { + var writer = new QueryWriter(); + + writer.AppendClause("foo {0}", "bar"); + + var query = writer.ToCypherQuery(); + Assert.AreEqual("foo {p0}", query.QueryText); + Assert.AreEqual(1, query.QueryParameters.Count); + Assert.AreEqual("bar", query.QueryParameters["p0"]); + } + + [Test] + public void ToCypherQueryShouldNotIncrementParamCountsWhenGeneratedTwice() + { + var writer = new QueryWriter(); + + writer.AppendClause("foo {0}", "bar"); + + var query1 = writer.ToCypherQuery(); + Assert.AreEqual("foo {p0}", query1.QueryText); + Assert.AreEqual(1, query1.QueryParameters.Count); + Assert.AreEqual("bar", query1.QueryParameters["p0"]); + + var query2 = writer.ToCypherQuery(); + Assert.AreEqual("foo {p0}", query2.QueryText); + Assert.AreEqual(1, query2.QueryParameters.Count); + Assert.AreEqual("bar", query2.QueryParameters["p0"]); + } + + [Test] + public void ToCypherQueryShouldNotLeakNewParamsIntoPreviouslyBuiltQuery() + { + var writer = new QueryWriter(); + + writer.AppendClause("foo {0}", "bar"); + var query1 = writer.ToCypherQuery(); + + writer.AppendClause("baz {0}", "qak"); + var query2 = writer.ToCypherQuery(); + + Assert.AreEqual("foo {p0}", query1.QueryText); + Assert.AreEqual(1, query1.QueryParameters.Count); + Assert.AreEqual("bar", query1.QueryParameters["p0"]); + + Assert.AreEqual("foo {p0}\r\nbaz {p1}", query2.QueryText); + Assert.AreEqual(2, query2.QueryParameters.Count); + Assert.AreEqual("bar", query2.QueryParameters["p0"]); + Assert.AreEqual("qak", query2.QueryParameters["p1"]); + } + + [Test] + public void AppendClauseWithMultipleParameters() + { + var writer = new QueryWriter(); + + writer.AppendClause("foo {0} bar {1}", "baz", "qak"); + + var query = writer.ToCypherQuery(); + Assert.AreEqual("foo {p0} bar {p1}", query.QueryText); + Assert.AreEqual(2, query.QueryParameters.Count); + Assert.AreEqual("baz", query.QueryParameters["p0"]); + Assert.AreEqual("qak", query.QueryParameters["p1"]); + } + + [Test] + public void AppendMultipleClausesWithMultipleParameters() + { + var writer = new QueryWriter(); + + writer.AppendClause("foo {0} bar {1}", "baz", "qak"); + writer.AppendClause("{0} qoo {1} zoo", "abc", "xyz"); + + var query = writer.ToCypherQuery(); + string expectedText = string.Format("foo {{p0}} bar {{p1}}{0}{{p2}} qoo {{p3}} zoo", Environment.NewLine); + Assert.AreEqual(expectedText, query.QueryText); + Assert.AreEqual(4, query.QueryParameters.Count); + Assert.AreEqual("baz", query.QueryParameters["p0"]); + Assert.AreEqual("qak", query.QueryParameters["p1"]); + Assert.AreEqual("abc", query.QueryParameters["p2"]); + Assert.AreEqual("xyz", query.QueryParameters["p3"]); + } + } +} diff --git a/Test/Cypher/StartBitFormatterTests.cs b/Neo4jClient.Tests/Cypher/StartBitFormatterTests.cs similarity index 97% rename from Test/Cypher/StartBitFormatterTests.cs rename to Neo4jClient.Tests/Cypher/StartBitFormatterTests.cs index 96476adb3..474446563 100644 --- a/Test/Cypher/StartBitFormatterTests.cs +++ b/Neo4jClient.Tests/Cypher/StartBitFormatterTests.cs @@ -1,367 +1,367 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class StartBitFormatterTests - { - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-node-by-id")] - public void SingleNodeReference() - { - var cypher = ToCypher(new - { - n1 = (NodeReference) 1 - }); - - Assert.AreEqual("n1=node({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual(1, cypher.QueryParameters["p0"]); - } - - [Test] - public void SingleNode() - { - var cypher = ToCypher(new - { - n1 = new Node(new object(), 123, null) - }); - - Assert.AreEqual("n1=node({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual(123, cypher.QueryParameters["p0"]); - } - - [Test] - public void EnumerableOfNodes() - { - var cypher = ToCypher(new - { - n1 = new[] {123, 456}.Select(id => new Node(new object(), id, null)) - }); - - Assert.AreEqual("n1=node({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual(new[] {123, 456}, cypher.QueryParameters["p0"]); - } - - [Test] - public void RootNodeReference() - { - var cypher = ToCypher(new - { - n1 = new RootNode(123) - }); - - Assert.AreEqual("n1=node({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual(123, cypher.QueryParameters["p0"]); - } - - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-node-by-id")] - public void MultipleNodeReferences() - { - var cypher = ToCypher(new - { - n1 = (NodeReference)1, - n2 = (NodeReference)2 - }); - - Assert.AreEqual("n1=node({p0}), n2=node({p1})", cypher.QueryText); - Assert.AreEqual(2, cypher.QueryParameters.Count); - Assert.AreEqual(1, cypher.QueryParameters["p0"]); - Assert.AreEqual(2, cypher.QueryParameters["p1"]); - } - - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-multiple-nodes-by-id")] - public void ArrayOfNodeReferences() - { - var cypher = ToCypher(new - { - n1 = new NodeReference[] { 1, 2 } - }); - - Assert.AreEqual("n1=node({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual(new[] {1, 2}, cypher.QueryParameters["p0"]); - } - - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-multiple-nodes-by-id")] - public void EnumerableOfNodeReferences() - { - var cypher = ToCypher(new - { - n1 = new[] { 1, 2 }.Select(id => (NodeReference)id) - }); - - Assert.AreEqual("n1=node({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual(new[] { 1, 2 }, cypher.QueryParameters["p0"]); - } - - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-multiple-nodes-by-id")] - public void EnumerableOfTypedNodeReferences() - { - var cypher = ToCypher(new - { - n1 = new[] { 1, 2 }.Select(id => (NodeReference)id) - }); - - Assert.AreEqual("n1=node({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual(new[] { 1, 2 }, cypher.QueryParameters["p0"]); - } - - [Test] - public void SingleRelationshipReference() - { - var cypher = ToCypher(new - { - r1 = (RelationshipReference)1 - }); - - Assert.AreEqual("r1=relationship({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual(1, cypher.QueryParameters["p0"]); - } - - [Test] - public void ArrayOfRelationshipReferences() - { - var cypher = ToCypher(new - { - r1 = new RelationshipReference[] { 1, 2 } - }); - - Assert.AreEqual("r1=relationship({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual(new[] {1, 2}, cypher.QueryParameters["p0"]); - } - - [Test] - public void EnumerableOfRelationshipReferences() - { - var cypher = ToCypher(new - { - r1 = new[] { 1, 2 }.Select(id => (RelationshipReference)id) - }); - - Assert.AreEqual("r1=relationship({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual(new[] { 1, 2 }, cypher.QueryParameters["p0"]); - } - - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-all-nodes")] - public void AllNodes() - { - var cypher = ToCypher(new - { - n = All.Nodes - }); - - Assert.AreEqual("n=node(*)", cypher.QueryText); - Assert.AreEqual(0, cypher.QueryParameters.Count); - } - - [Test] - public void CustomString() - { - var cypher = ToCypher(new - { - n1 = "foo" - }); - - Assert.AreEqual("n1=foo", cypher.QueryText); - Assert.AreEqual(0, cypher.QueryParameters.Count); - } - - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-node-by-index-lookup")] - public void NodeByIndexLookup() - { - var cypher = ToCypher(new - { - n = Node.ByIndexLookup("someIndex", "name", "A") - }); - - Assert.AreEqual("n=node:`someIndex`(name = {p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual("A", cypher.QueryParameters["p0"]); - } - - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-node-by-index-query")] - public void NodeByIndexQuery() - { - var cypher = ToCypher(new - { - n = Node.ByIndexQuery("someIndex", "name:A") - }); - - Assert.AreEqual("n=node:`someIndex`({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual("name:A", cypher.QueryParameters["p0"]); - } - - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-relationship-by-index-lookup")] - public void RelationshipByIndexLookup() - { - var cypher = ToCypher(new - { - r = Relationship.ByIndexLookup("someIndex", "name", "A") - }); - - Assert.AreEqual("r=relationship:`someIndex`(name = {p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual("A", cypher.QueryParameters["p0"]); - } - - [Test] - public void RelationshipByIndexQuery() - { - var cypher = ToCypher(new - { - r = Relationship.ByIndexQuery("someIndex", "name:A") - }); - - Assert.AreEqual("r=relationship:`someIndex`({p0})", cypher.QueryText); - Assert.AreEqual(1, cypher.QueryParameters.Count); - Assert.AreEqual("name:A", cypher.QueryParameters["p0"]); - } - - [Test] - public void Mixed() - { - var nodeRef = (NodeReference)2; - var relRef = (RelationshipReference)3; - var relRef2 = (RelationshipReference)4; - - var cypher = ToCypher(new - { - n1 = "custom", - n2 = nodeRef, - n3 = Node.ByIndexLookup("indexName", "property", "value"), - n4 = Node.ByIndexQuery("indexName", "query"), - r1 = relRef, - moreRels = new[] { relRef, relRef2 }, - r2 = Relationship.ByIndexLookup("indexName", "property", "value"), - r3 = Relationship.ByIndexQuery("indexName", "query"), - all = All.Nodes - }); - - const string expected = - "n1=custom, " + - "n2=node({p0}), " + - "n3=node:`indexName`(property = {p1}), " + - "n4=node:`indexName`({p2}), " + - "r1=relationship({p3}), " + - "moreRels=relationship({p4}), " + - "r2=relationship:`indexName`(property = {p5}), " + - "r3=relationship:`indexName`({p6}), " + - "all=node(*)"; - - Assert.AreEqual(expected, cypher.QueryText); - Assert.AreEqual(7, cypher.QueryParameters.Count); - Assert.AreEqual(2, cypher.QueryParameters["p0"]); - Assert.AreEqual("value", cypher.QueryParameters["p1"]); - Assert.AreEqual("query", cypher.QueryParameters["p2"]); - Assert.AreEqual(3, cypher.QueryParameters["p3"]); - Assert.AreEqual(new[] {3, 4}, cypher.QueryParameters["p4"]); - Assert.AreEqual("value", cypher.QueryParameters["p5"]); - Assert.AreEqual("query", cypher.QueryParameters["p6"]); - } - - [Test] - public void ThrowArgumentExceptionForEmptyObject() - { - var emptyObject = new {}; - var ex = Assert.Throws( - () => StartBitFormatter.FormatAsCypherText(emptyObject, null) - ); - Assert.AreEqual("startBits", ex.ParamName); - } - - [Test] - public void DontThrowArgumentExceptionForEmptyDictionary() - { - var emptyDictionary = new Dictionary(); - Assert.DoesNotThrow( - () => StartBitFormatter.FormatAsCypherText(emptyDictionary, null) - ); - } - - [Test] - public void ThrowNotSupportedExceptionForUnknownType() - { - var badObject = new { n1 = new StartBitFormatterTests() }; - Assert.Throws( - () => StartBitFormatter.FormatAsCypherText(badObject, null) - ); - } - - [Test] - public void NotSupportedExceptionForUnknownTypeIncludesIdentityName() - { - var badObject = new { n1 = new StartBitFormatterTests() }; - var exception = Assert.Throws( - () => StartBitFormatter.FormatAsCypherText(badObject, null) - ); - StringAssert.Contains("n1", exception.Message); - } - - [Test] - public void NotSupportedExceptionForUnknownTypeIncludesTypeName() - { - var badObject = new { n1 = new StartBitFormatterTests() }; - var exception = Assert.Throws( - () => StartBitFormatter.FormatAsCypherText(badObject, null) - ); - StringAssert.Contains(typeof(StartBitFormatterTests).FullName, exception.Message); - } - - [Test] - public void ThrowArgumentExceptionForNullValue() - { - var startBits = new { foo = (object)null }; - Assert.Throws( - () => StartBitFormatter.FormatAsCypherText(startBits, null) - ); - } - - [Test] - public void ArgumentExceptionForNullValueIncludesPropertyName() - { - var startBits = new { foo = (object)null }; - var ex = Assert.Throws( - () => StartBitFormatter.FormatAsCypherText(startBits, null) - ); - StringAssert.Contains("foo", ex.Message); - } - - static CypherQuery ToCypher(object startBits) - { - var parameters = new Dictionary(); - Func createParameter = value => - { - var name = "p" + parameters.Count; - parameters.Add(name, value); - return string.Format("{{{0}}}", name); - }; - - var cypherText = StartBitFormatter.FormatAsCypherText(startBits, createParameter); - - var query = new CypherQuery(cypherText, parameters, CypherResultMode.Projection); - return query; - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class StartBitFormatterTests + { + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-node-by-id")] + public void SingleNodeReference() + { + var cypher = ToCypher(new + { + n1 = (NodeReference) 1 + }); + + Assert.AreEqual("n1=node({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual(1, cypher.QueryParameters["p0"]); + } + + [Test] + public void SingleNode() + { + var cypher = ToCypher(new + { + n1 = new Node(new object(), 123, null) + }); + + Assert.AreEqual("n1=node({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual(123, cypher.QueryParameters["p0"]); + } + + [Test] + public void EnumerableOfNodes() + { + var cypher = ToCypher(new + { + n1 = new[] {123, 456}.Select(id => new Node(new object(), id, null)) + }); + + Assert.AreEqual("n1=node({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual(new[] {123, 456}, cypher.QueryParameters["p0"]); + } + + [Test] + public void RootNodeReference() + { + var cypher = ToCypher(new + { + n1 = new RootNode(123) + }); + + Assert.AreEqual("n1=node({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual(123, cypher.QueryParameters["p0"]); + } + + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-node-by-id")] + public void MultipleNodeReferences() + { + var cypher = ToCypher(new + { + n1 = (NodeReference)1, + n2 = (NodeReference)2 + }); + + Assert.AreEqual("n1=node({p0}), n2=node({p1})", cypher.QueryText); + Assert.AreEqual(2, cypher.QueryParameters.Count); + Assert.AreEqual(1, cypher.QueryParameters["p0"]); + Assert.AreEqual(2, cypher.QueryParameters["p1"]); + } + + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-multiple-nodes-by-id")] + public void ArrayOfNodeReferences() + { + var cypher = ToCypher(new + { + n1 = new NodeReference[] { 1, 2 } + }); + + Assert.AreEqual("n1=node({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual(new[] {1, 2}, cypher.QueryParameters["p0"]); + } + + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-multiple-nodes-by-id")] + public void EnumerableOfNodeReferences() + { + var cypher = ToCypher(new + { + n1 = new[] { 1, 2 }.Select(id => (NodeReference)id) + }); + + Assert.AreEqual("n1=node({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual(new[] { 1, 2 }, cypher.QueryParameters["p0"]); + } + + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-multiple-nodes-by-id")] + public void EnumerableOfTypedNodeReferences() + { + var cypher = ToCypher(new + { + n1 = new[] { 1, 2 }.Select(id => (NodeReference)id) + }); + + Assert.AreEqual("n1=node({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual(new[] { 1, 2 }, cypher.QueryParameters["p0"]); + } + + [Test] + public void SingleRelationshipReference() + { + var cypher = ToCypher(new + { + r1 = (RelationshipReference)1 + }); + + Assert.AreEqual("r1=relationship({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual(1, cypher.QueryParameters["p0"]); + } + + [Test] + public void ArrayOfRelationshipReferences() + { + var cypher = ToCypher(new + { + r1 = new RelationshipReference[] { 1, 2 } + }); + + Assert.AreEqual("r1=relationship({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual(new[] {1, 2}, cypher.QueryParameters["p0"]); + } + + [Test] + public void EnumerableOfRelationshipReferences() + { + var cypher = ToCypher(new + { + r1 = new[] { 1, 2 }.Select(id => (RelationshipReference)id) + }); + + Assert.AreEqual("r1=relationship({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual(new[] { 1, 2 }, cypher.QueryParameters["p0"]); + } + + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-all-nodes")] + public void AllNodes() + { + var cypher = ToCypher(new + { + n = All.Nodes + }); + + Assert.AreEqual("n=node(*)", cypher.QueryText); + Assert.AreEqual(0, cypher.QueryParameters.Count); + } + + [Test] + public void CustomString() + { + var cypher = ToCypher(new + { + n1 = "foo" + }); + + Assert.AreEqual("n1=foo", cypher.QueryText); + Assert.AreEqual(0, cypher.QueryParameters.Count); + } + + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-node-by-index-lookup")] + public void NodeByIndexLookup() + { + var cypher = ToCypher(new + { + n = Node.ByIndexLookup("someIndex", "name", "A") + }); + + Assert.AreEqual("n=node:`someIndex`(name = {p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual("A", cypher.QueryParameters["p0"]); + } + + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-node-by-index-query")] + public void NodeByIndexQuery() + { + var cypher = ToCypher(new + { + n = Node.ByIndexQuery("someIndex", "name:A") + }); + + Assert.AreEqual("n=node:`someIndex`({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual("name:A", cypher.QueryParameters["p0"]); + } + + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-relationship-by-index-lookup")] + public void RelationshipByIndexLookup() + { + var cypher = ToCypher(new + { + r = Relationship.ByIndexLookup("someIndex", "name", "A") + }); + + Assert.AreEqual("r=relationship:`someIndex`(name = {p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual("A", cypher.QueryParameters["p0"]); + } + + [Test] + public void RelationshipByIndexQuery() + { + var cypher = ToCypher(new + { + r = Relationship.ByIndexQuery("someIndex", "name:A") + }); + + Assert.AreEqual("r=relationship:`someIndex`({p0})", cypher.QueryText); + Assert.AreEqual(1, cypher.QueryParameters.Count); + Assert.AreEqual("name:A", cypher.QueryParameters["p0"]); + } + + [Test] + public void Mixed() + { + var nodeRef = (NodeReference)2; + var relRef = (RelationshipReference)3; + var relRef2 = (RelationshipReference)4; + + var cypher = ToCypher(new + { + n1 = "custom", + n2 = nodeRef, + n3 = Node.ByIndexLookup("indexName", "property", "value"), + n4 = Node.ByIndexQuery("indexName", "query"), + r1 = relRef, + moreRels = new[] { relRef, relRef2 }, + r2 = Relationship.ByIndexLookup("indexName", "property", "value"), + r3 = Relationship.ByIndexQuery("indexName", "query"), + all = All.Nodes + }); + + const string expected = + "n1=custom, " + + "n2=node({p0}), " + + "n3=node:`indexName`(property = {p1}), " + + "n4=node:`indexName`({p2}), " + + "r1=relationship({p3}), " + + "moreRels=relationship({p4}), " + + "r2=relationship:`indexName`(property = {p5}), " + + "r3=relationship:`indexName`({p6}), " + + "all=node(*)"; + + Assert.AreEqual(expected, cypher.QueryText); + Assert.AreEqual(7, cypher.QueryParameters.Count); + Assert.AreEqual(2, cypher.QueryParameters["p0"]); + Assert.AreEqual("value", cypher.QueryParameters["p1"]); + Assert.AreEqual("query", cypher.QueryParameters["p2"]); + Assert.AreEqual(3, cypher.QueryParameters["p3"]); + Assert.AreEqual(new[] {3, 4}, cypher.QueryParameters["p4"]); + Assert.AreEqual("value", cypher.QueryParameters["p5"]); + Assert.AreEqual("query", cypher.QueryParameters["p6"]); + } + + [Test] + public void ThrowArgumentExceptionForEmptyObject() + { + var emptyObject = new {}; + var ex = Assert.Throws( + () => StartBitFormatter.FormatAsCypherText(emptyObject, null) + ); + Assert.AreEqual("startBits", ex.ParamName); + } + + [Test] + public void DontThrowArgumentExceptionForEmptyDictionary() + { + var emptyDictionary = new Dictionary(); + Assert.DoesNotThrow( + () => StartBitFormatter.FormatAsCypherText(emptyDictionary, null) + ); + } + + [Test] + public void ThrowNotSupportedExceptionForUnknownType() + { + var badObject = new { n1 = new StartBitFormatterTests() }; + Assert.Throws( + () => StartBitFormatter.FormatAsCypherText(badObject, null) + ); + } + + [Test] + public void NotSupportedExceptionForUnknownTypeIncludesIdentityName() + { + var badObject = new { n1 = new StartBitFormatterTests() }; + var exception = Assert.Throws( + () => StartBitFormatter.FormatAsCypherText(badObject, null) + ); + StringAssert.Contains("n1", exception.Message); + } + + [Test] + public void NotSupportedExceptionForUnknownTypeIncludesTypeName() + { + var badObject = new { n1 = new StartBitFormatterTests() }; + var exception = Assert.Throws( + () => StartBitFormatter.FormatAsCypherText(badObject, null) + ); + StringAssert.Contains(typeof(StartBitFormatterTests).FullName, exception.Message); + } + + [Test] + public void ThrowArgumentExceptionForNullValue() + { + var startBits = new { foo = (object)null }; + Assert.Throws( + () => StartBitFormatter.FormatAsCypherText(startBits, null) + ); + } + + [Test] + public void ArgumentExceptionForNullValueIncludesPropertyName() + { + var startBits = new { foo = (object)null }; + var ex = Assert.Throws( + () => StartBitFormatter.FormatAsCypherText(startBits, null) + ); + StringAssert.Contains("foo", ex.Message); + } + + static CypherQuery ToCypher(object startBits) + { + var parameters = new Dictionary(); + Func createParameter = value => + { + var name = "p" + parameters.Count; + parameters.Add(name, value); + return string.Format("{{{0}}}", name); + }; + + var cypherText = StartBitFormatter.FormatAsCypherText(startBits, createParameter); + + var query = new CypherQuery(cypherText, parameters, CypherResultMode.Projection, CypherResultFormat.Rest); + return query; + } + } +} diff --git a/Test/Cypher/UnionTests.cs b/Neo4jClient.Tests/Cypher/UnionTests.cs similarity index 96% rename from Test/Cypher/UnionTests.cs rename to Neo4jClient.Tests/Cypher/UnionTests.cs index 94a853397..24fac5262 100644 --- a/Test/Cypher/UnionTests.cs +++ b/Neo4jClient.Tests/Cypher/UnionTests.cs @@ -1,37 +1,37 @@ -using System.Linq; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.Cypher -{ - [TestFixture] - public class UnionTests - { - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-union.html#union-combine-two-queries-and-removing-duplicates")] - public void UnionAll() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .UnionAll() - .Query; - - Assert.AreEqual("UNION ALL", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count()); - } - - [Test] - [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-union.html#union-union-two-queries")] - public void Union() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Union() - .Query; - - Assert.AreEqual("UNION", query.QueryText); - Assert.AreEqual(0, query.QueryParameters.Count()); - } - } -} +using System.Linq; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.Cypher +{ + [TestFixture] + public class UnionTests + { + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-union.html#union-combine-two-queries-and-removing-duplicates")] + public void UnionAll() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .UnionAll() + .Query; + + Assert.AreEqual("UNION ALL", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count()); + } + + [Test] + [Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-union.html#union-union-two-queries")] + public void Union() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Union() + .Query; + + Assert.AreEqual("UNION", query.QueryText); + Assert.AreEqual(0, query.QueryParameters.Count()); + } + } +} diff --git a/Test/Domain/Product.cs b/Neo4jClient.Tests/Domain/Product.cs similarity index 95% rename from Test/Domain/Product.cs rename to Neo4jClient.Tests/Domain/Product.cs index 55fc06f1b..dd8adeb0b 100644 --- a/Test/Domain/Product.cs +++ b/Neo4jClient.Tests/Domain/Product.cs @@ -1,8 +1,8 @@ -namespace Neo4jClient.Test.Domain -{ - public class Product - { - public string Name { get; set; } - public double Weight { get; set; } - } +namespace Neo4jClient.Test.Domain +{ + public class Product + { + public string Name { get; set; } + public double Weight { get; set; } + } } \ No newline at end of file diff --git a/Test/Domain/StorageLocation.cs b/Neo4jClient.Tests/Domain/StorageLocation.cs similarity index 95% rename from Test/Domain/StorageLocation.cs rename to Neo4jClient.Tests/Domain/StorageLocation.cs index 208989359..648a52738 100644 --- a/Test/Domain/StorageLocation.cs +++ b/Neo4jClient.Tests/Domain/StorageLocation.cs @@ -1,7 +1,7 @@ -namespace Neo4jClient.Test.Domain -{ - public class StorageLocation - { - public string Name { get; set; } - } +namespace Neo4jClient.Test.Domain +{ + public class StorageLocation + { + public string Name { get; set; } + } } \ No newline at end of file diff --git a/Test/Domain/User.cs b/Neo4jClient.Tests/Domain/User.cs similarity index 95% rename from Test/Domain/User.cs rename to Neo4jClient.Tests/Domain/User.cs index c7a5b18e4..b8cc8a0f8 100644 --- a/Test/Domain/User.cs +++ b/Neo4jClient.Tests/Domain/User.cs @@ -1,7 +1,7 @@ -namespace Neo4jClient.Test.Domain -{ - public class Part - { - public string Name { get; set; } - } +namespace Neo4jClient.Test.Domain +{ + public class Part + { + public string Name { get; set; } + } } \ No newline at end of file diff --git a/Test/GraphClientTests/ConnectTests.cs b/Neo4jClient.Tests/GraphClientTests/ConnectTests.cs similarity index 91% rename from Test/GraphClientTests/ConnectTests.cs rename to Neo4jClient.Tests/GraphClientTests/ConnectTests.cs index 075aa7d65..88a7a278b 100644 --- a/Test/GraphClientTests/ConnectTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/ConnectTests.cs @@ -1,234 +1,249 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Text.RegularExpressions; -using System.Threading.Tasks; -using Neo4jClient.Cypher; -using NSubstitute; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class ConnectTests - { - [Test] - [ExpectedException(typeof(ApplicationException), ExpectedMessage = "Received an unexpected HTTP status when executing the request.\r\n\r\nThe response status was: 500 InternalServerError")] - public void ShouldThrowConnectionExceptionFor500Response() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get(""), - MockResponse.Http(500) - } - }) - { - testHarness.CreateAndConnectGraphClient(); - } - } - - [Test] - public void ShouldRetrieveApiEndpoints() - { - using (var testHarness = new RestTestHarness()) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - Assert.AreEqual("/node", graphClient.RootApiResponse.Node); - Assert.AreEqual("/index/node", graphClient.RootApiResponse.NodeIndex); - Assert.AreEqual("/index/relationship", graphClient.RootApiResponse.RelationshipIndex); - Assert.AreEqual("http://foo/db/data/node/123", graphClient.RootApiResponse.ReferenceNode); - Assert.AreEqual("/ext", graphClient.RootApiResponse.ExtensionsInfo); - } - } - - [Test] - [ExpectedException(ExpectedMessage = "The graph client is not connected to the server. Call the Connect method first.")] - public void RootNode_ShouldThrowInvalidOperationException_WhenNotConnectedYet() - { - var graphClient = new GraphClient(new Uri("http://foo/db/data"), null); -// ReSharper disable ReturnValueOfPureMethodIsNotUsed - graphClient.RootNode.ToString(); -// ReSharper restore ReturnValueOfPureMethodIsNotUsed - } - - [Test] - public void RootNode_ShouldReturnReferenceNode() - { - using (var testHarness = new RestTestHarness()) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - Assert.IsNotNull(graphClient.RootNode); - Assert.AreEqual(123, graphClient.RootNode.Id); - } - } - - [Test] - public void RootNode_ShouldReturnNullReferenceNode_WhenNoReferenceNodeDefined() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get(""), - MockResponse.Json(HttpStatusCode.OK, @"{ - 'batch' : 'http://foo/db/data/batch', - 'node' : 'http://foo/db/data/node', - 'node_index' : 'http://foo/db/data/index/node', - 'relationship_index' : 'http://foo/db/data/index/relationship', - 'extensions_info' : 'http://foo/db/data/ext', - 'extensions' : { - } - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - Assert.IsNull(graphClient.RootNode); - } - } - - [Test] - public void ShouldParse15M02Version() - { - using (var testHarness = new RestTestHarness()) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - Assert.AreEqual("1.5.0.2", graphClient.RootApiResponse.Version.ToString()); - } - } - - [Test] - public void ShouldReturnCypher19CapabilitiesForPre20Version() - { - using (var testHarness = new RestTestHarness()) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - Assert.AreEqual(CypherCapabilities.Cypher19, graphClient.CypherCapabilities); - } - } - - [Test] - public void ShouldReturnCypher19CapabilitiesForVersion20() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get(""), - MockResponse.NeoRoot20() - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - Assert.AreEqual(CypherCapabilities.Cypher20, graphClient.CypherCapabilities); - } - } - - [Test] - public void UserInfoPreservedInRootUri() - { - var graphClient = new GraphClient(new Uri("http://username:password@foo/db/data")); - - Assert.That(graphClient.RootUri.OriginalString, Is.EqualTo("http://username:password@foo/db/data")); - } - - [Test] - public void CredentialsPreservedAllTheWayThroughToHttpStack() - { - var httpClient = Substitute.For(); - httpClient - .SendAsync(Arg.Any()) - .Returns(callInfo => { throw new NotImplementedException(); }); - - var graphClient = new GraphClient(new Uri("http://username:password@foo/db/data"), httpClient); - - try - { - graphClient.Connect(); - } - // ReSharper disable EmptyGeneralCatchClause - catch (NotImplementedException) - { - // This will fail because we're not giving it the right - // HTTP response, but we only care about the request for now - } - // ReSharper restore EmptyGeneralCatchClause - - var httpCall = httpClient.ReceivedCalls().First(); - var httpRequest = (HttpRequestMessage) httpCall.GetArguments()[0]; - - StringAssert.AreEqualIgnoringCase("Basic", httpRequest.Headers.Authorization.Scheme); - StringAssert.AreEqualIgnoringCase("dXNlcm5hbWU6cGFzc3dvcmQ=", httpRequest.Headers.Authorization.Parameter); - } - - [Test] - public void ShouldParseRootApiResponseFromAuthenticatedConnection() - { - using (var testHarness = new RestTestHarness() - { - { MockRequest.Get(""), MockResponse.NeoRoot() } - }) - { - var httpClient = testHarness.GenerateHttpClient("http://foo/db/data"); - var graphClient = new GraphClient(new Uri("http://username:password@foo/db/data"), httpClient); - graphClient.Connect(); - Assert.AreEqual("/node", graphClient.RootApiResponse.Node); - } - } - - [Test] - public void ShouldSendCustomUserAgent() - { - // Arrange - var httpClient = Substitute.For(); - var graphClient = new GraphClient(new Uri("http://localhost"), httpClient); - var expectedUserAgent = graphClient.UserAgent; - httpClient - .SendAsync(Arg.Do(message => - { - // Assert - Assert.IsTrue(message.Headers.Contains("User-Agent"), "Contains User-Agent header"); - var userAgent = message.Headers.GetValues("User-Agent").Single(); - Assert.AreEqual(expectedUserAgent, userAgent, "User-Agent header value is correct"); - })) - .Returns(ci => { - var response = new HttpResponseMessage(HttpStatusCode.OK) - { - Content = new StringContent(@"{ - 'cypher' : 'http://foo/db/data/cypher', - 'batch' : 'http://foo/db/data/batch', - 'node' : 'http://foo/db/data/node', - 'node_index' : 'http://foo/db/data/index/node', - 'relationship_index' : 'http://foo/db/data/index/relationship', - 'reference_node' : 'http://foo/db/data/node/123', - 'neo4j_version' : '1.5.M02', - 'extensions_info' : 'http://foo/db/data/ext', - 'extensions' : { - 'GremlinPlugin' : { - 'execute_script' : 'http://foo/db/data/ext/GremlinPlugin/graphdb/execute_script' - } - } - }") - }; - var task = new Task(() => response); - task.Start(); - return task; - }); - - // Act - graphClient.Connect(); - } - - [Test] - public void ShouldFormatUserAgentCorrectly() - { - var graphClient = new GraphClient(new Uri("http://localhost")); - var userAgent = graphClient.UserAgent; - Assert.IsTrue(Regex.IsMatch(userAgent, @"Neo4jClient/\d+\.\d+\.\d+\.\d+"), "User agent should be in format Neo4jClient/1.2.3.4"); - } - } -} +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Neo4jClient.Cypher; +using Neo4jClient.Execution; +using NSubstitute; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class ConnectTests + { + [Test] + [ExpectedException(typeof(ApplicationException), ExpectedMessage = "Received an unexpected HTTP status when executing the request.\r\n\r\nThe response status was: 500 InternalServerError")] + public void ShouldThrowConnectionExceptionFor500Response() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get(""), + MockResponse.Http(500) + } + }) + { + testHarness.CreateAndConnectGraphClient(); + } + } + + [Test] + public void ShouldRetrieveApiEndpoints() + { + using (var testHarness = new RestTestHarness()) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + Assert.AreEqual("/node", graphClient.RootApiResponse.Node); + Assert.AreEqual("/index/node", graphClient.RootApiResponse.NodeIndex); + Assert.AreEqual("/index/relationship", graphClient.RootApiResponse.RelationshipIndex); + Assert.AreEqual("http://foo/db/data/node/123", graphClient.RootApiResponse.ReferenceNode); + Assert.AreEqual("/ext", graphClient.RootApiResponse.ExtensionsInfo); + } + } + + [Test] + [ExpectedException(ExpectedMessage = "The graph client is not connected to the server. Call the Connect method first.")] + public void RootNode_ShouldThrowInvalidOperationException_WhenNotConnectedYet() + { + var graphClient = new GraphClient(new Uri("http://foo/db/data"), null); +// ReSharper disable ReturnValueOfPureMethodIsNotUsed + graphClient.RootNode.ToString(); +// ReSharper restore ReturnValueOfPureMethodIsNotUsed + } + + [Test] + public void RootNode_ShouldReturnReferenceNode() + { + using (var testHarness = new RestTestHarness()) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + Assert.IsNotNull(graphClient.RootNode); + Assert.AreEqual(123, graphClient.RootNode.Id); + } + } + + [Test] + public void RootNode_ShouldReturnNullReferenceNode_WhenNoReferenceNodeDefined() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get(""), + MockResponse.Json(HttpStatusCode.OK, @"{ + 'batch' : 'http://foo/db/data/batch', + 'node' : 'http://foo/db/data/node', + 'node_index' : 'http://foo/db/data/index/node', + 'relationship_index' : 'http://foo/db/data/index/relationship', + 'extensions_info' : 'http://foo/db/data/ext', + 'extensions' : { + } + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + Assert.IsNull(graphClient.RootNode); + } + } + + [Test] + public void ShouldParse15M02Version() + { + using (var testHarness = new RestTestHarness()) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + Assert.AreEqual("1.5.0.2", graphClient.RootApiResponse.Version.ToString()); + } + } + + [Test] + public void ShouldReturnCypher19CapabilitiesForPre20Version() + { + using (var testHarness = new RestTestHarness()) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + Assert.AreEqual(CypherCapabilities.Cypher19, graphClient.CypherCapabilities); + } + } + + [Test] + public void ShouldReturnCypher19CapabilitiesForVersion20() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get(""), + MockResponse.NeoRoot20() + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + Assert.AreEqual(CypherCapabilities.Cypher20, graphClient.CypherCapabilities); + } + } + + [Test] + public void UserInfoPreservedInRootUri() + { + var graphClient = new GraphClient(new Uri("http://username:password@foo/db/data")); + + Assert.That(graphClient.RootUri.OriginalString, Is.EqualTo("http://username:password@foo/db/data")); + } + + [Test] + public void CredentialsPreservedAllTheWayThroughToHttpStack() + { + var httpClient = Substitute.For(); + httpClient + .SendAsync(Arg.Any()) + .Returns(callInfo => { throw new NotImplementedException(); }); + + var graphClient = new GraphClient(new Uri("http://username:password@foo/db/data"), httpClient); + + try + { + graphClient.Connect(); + } + // ReSharper disable EmptyGeneralCatchClause + catch (NotImplementedException) + { + // This will fail because we're not giving it the right + // HTTP response, but we only care about the request for now + } + // ReSharper restore EmptyGeneralCatchClause + + var httpCall = httpClient.ReceivedCalls().Last(); + var httpRequest = (HttpRequestMessage) httpCall.GetArguments()[0]; + + StringAssert.AreEqualIgnoringCase("Basic", httpRequest.Headers.Authorization.Scheme); + StringAssert.AreEqualIgnoringCase("dXNlcm5hbWU6cGFzc3dvcmQ=", httpRequest.Headers.Authorization.Parameter); + } + + [Test] + public void ShouldParseRootApiResponseFromAuthenticatedConnection() + { + using (var testHarness = new RestTestHarness() + { + { MockRequest.Get(""), MockResponse.NeoRoot() } + }) + { + var httpClient = testHarness.GenerateHttpClient("http://foo/db/data"); + var graphClient = new GraphClient(new Uri("http://username:password@foo/db/data"), httpClient); + graphClient.Connect(); + Assert.AreEqual("/node", graphClient.RootApiResponse.Node); + } + } + + [Test] + public void ShouldSendCustomUserAgent() + { + // Arrange + var httpClient = Substitute.For(); + var graphClient = new GraphClient(new Uri("http://localhost"), httpClient); + var expectedUserAgent = graphClient.ExecutionConfiguration.UserAgent; + httpClient + .SendAsync(Arg.Do(message => + { + // Assert + Assert.IsTrue(message.Headers.Contains("User-Agent"), "Contains User-Agent header"); + var userAgent = message.Headers.GetValues("User-Agent").Single(); + Assert.AreEqual(expectedUserAgent, userAgent, "User-Agent header value is correct"); + })) + .Returns(ci => { + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@"{ + 'cypher' : 'http://foo/db/data/cypher', + 'batch' : 'http://foo/db/data/batch', + 'node' : 'http://foo/db/data/node', + 'node_index' : 'http://foo/db/data/index/node', + 'relationship_index' : 'http://foo/db/data/index/relationship', + 'reference_node' : 'http://foo/db/data/node/123', + 'neo4j_version' : '1.5.M02', + 'extensions_info' : 'http://foo/db/data/ext', + 'extensions' : { + 'GremlinPlugin' : { + 'execute_script' : 'http://foo/db/data/ext/GremlinPlugin/graphdb/execute_script' + } + } + }") + }; + var task = new Task(() => response); + task.Start(); + return task; + }); + + // Act + graphClient.Connect(); + } + + [Test] + public void ShouldFormatUserAgentCorrectly() + { + var graphClient = new GraphClient(new Uri("http://localhost")); + var userAgent = graphClient.ExecutionConfiguration.UserAgent; + Assert.IsTrue(Regex.IsMatch(userAgent, @"Neo4jClient/\d+\.\d+\.\d+\.\d+"), "User agent should be in format Neo4jClient/1.2.3.4"); + } + + [Test] + public void ShouldFormatAuthorisationHeaderCorrectly() + { + const string username = "user"; + const string password = "password"; + var expectedHeader = Convert.ToBase64String(Encoding.ASCII.GetBytes(string.Format("{0}:{1}", username, password))); + + var graphClient = new GraphClient(new Uri("http://localhost"), username, password); + var httpClient = (HttpClientWrapper) graphClient.ExecutionConfiguration.HttpClient; + + Assert.AreEqual(expectedHeader, httpClient.AuthenticationHeaderValue.Parameter); + } + } +} diff --git a/Test/GraphClientTests/CreateIndexTests.cs b/Neo4jClient.Tests/GraphClientTests/CreateIndexTests.cs similarity index 96% rename from Test/GraphClientTests/CreateIndexTests.cs rename to Neo4jClient.Tests/GraphClientTests/CreateIndexTests.cs index c772f7777..bd87aa14f 100644 --- a/Test/GraphClientTests/CreateIndexTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/CreateIndexTests.cs @@ -1,138 +1,138 @@ -using System; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class CreateIndexTests - { - [Test] - [TestCase( - IndexFor.Node, - IndexProvider.lucene, - IndexType.fulltext, - "/index/node", - @"{ - 'name': 'foo', - 'config': { 'type': 'fulltext', 'provider': 'lucene' } - }")] - [TestCase( - IndexFor.Node, - IndexProvider.lucene, - IndexType.exact, - "/index/node", - @"{ - 'name': 'foo', - 'config': { 'type': 'exact', 'provider': 'lucene' } - }")] - [TestCase( - IndexFor.Relationship, - IndexProvider.lucene, - IndexType.fulltext, - "/index/relationship", - @"{ - 'name': 'foo', - 'config': { 'type': 'fulltext', 'provider': 'lucene' } - }")] - [TestCase( - IndexFor.Relationship, - IndexProvider.lucene, - IndexType.exact, - "/index/relationship", - @"{ - 'name': 'foo', - 'config': { 'type': 'exact', 'provider': 'lucene' } - }")] - public void ShouldCreateIndex( - IndexFor indexFor, - IndexProvider indexProvider, - IndexType indexType, - string createEndpoint, - string createJson) - { - //Arrange - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostJson(createEndpoint, createJson), - MockResponse.Http(201) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var indexConfiguration = new IndexConfiguration - { - Provider = indexProvider, - Type = indexType - }; - graphClient.CreateIndex("foo", indexConfiguration, indexFor); - } - } - - [Test] - [ExpectedException(typeof(ApplicationException))] - [TestCase( - IndexFor.Node, - IndexProvider.lucene, - IndexType.fulltext, - "/index/node", - @"{ - 'name': 'foo', - 'config': { 'type': 'fulltext', 'provider': 'lucene' } - }")] - [TestCase( - IndexFor.Node, - IndexProvider.lucene, - IndexType.exact, - "/index/node", - @"{ - 'name': 'foo', - 'config': { 'type': 'exact', 'provider': 'lucene' } - }")] - [TestCase( - IndexFor.Relationship, - IndexProvider.lucene, - IndexType.fulltext, - "/index/relationship", - @"{ - 'name': 'foo', - 'config': { 'type': 'fulltext', 'provider': 'lucene' } - }")] - [TestCase( - IndexFor.Relationship, - IndexProvider.lucene, - IndexType.exact, - "/index/relationship", - @"{ - 'name': 'foo', - 'config': { 'type': 'exact', 'provider': 'lucene' } - }")] - public void ShouldThrowApplicationExceptionIfHttpCodeIsNot201( - IndexFor indexFor, - IndexProvider indexProvider, - IndexType indexType, - string createEndpoint, - string createJson) - { - //Arrange - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostJson(createEndpoint, createJson), - MockResponse.Http(500) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var indexConfiguration = new IndexConfiguration - { - Provider = indexProvider, - Type = indexType - }; - graphClient.CreateIndex("foo", indexConfiguration, indexFor); - } - } - } +using System; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class CreateIndexTests + { + [Test] + [TestCase( + IndexFor.Node, + IndexProvider.lucene, + IndexType.fulltext, + "/index/node", + @"{ + 'name': 'foo', + 'config': { 'type': 'fulltext', 'provider': 'lucene' } + }")] + [TestCase( + IndexFor.Node, + IndexProvider.lucene, + IndexType.exact, + "/index/node", + @"{ + 'name': 'foo', + 'config': { 'type': 'exact', 'provider': 'lucene' } + }")] + [TestCase( + IndexFor.Relationship, + IndexProvider.lucene, + IndexType.fulltext, + "/index/relationship", + @"{ + 'name': 'foo', + 'config': { 'type': 'fulltext', 'provider': 'lucene' } + }")] + [TestCase( + IndexFor.Relationship, + IndexProvider.lucene, + IndexType.exact, + "/index/relationship", + @"{ + 'name': 'foo', + 'config': { 'type': 'exact', 'provider': 'lucene' } + }")] + public void ShouldCreateIndex( + IndexFor indexFor, + IndexProvider indexProvider, + IndexType indexType, + string createEndpoint, + string createJson) + { + //Arrange + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostJson(createEndpoint, createJson), + MockResponse.Http(201) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var indexConfiguration = new IndexConfiguration + { + Provider = indexProvider, + Type = indexType + }; + graphClient.CreateIndex("foo", indexConfiguration, indexFor); + } + } + + [Test] + [ExpectedException(typeof(ApplicationException))] + [TestCase( + IndexFor.Node, + IndexProvider.lucene, + IndexType.fulltext, + "/index/node", + @"{ + 'name': 'foo', + 'config': { 'type': 'fulltext', 'provider': 'lucene' } + }")] + [TestCase( + IndexFor.Node, + IndexProvider.lucene, + IndexType.exact, + "/index/node", + @"{ + 'name': 'foo', + 'config': { 'type': 'exact', 'provider': 'lucene' } + }")] + [TestCase( + IndexFor.Relationship, + IndexProvider.lucene, + IndexType.fulltext, + "/index/relationship", + @"{ + 'name': 'foo', + 'config': { 'type': 'fulltext', 'provider': 'lucene' } + }")] + [TestCase( + IndexFor.Relationship, + IndexProvider.lucene, + IndexType.exact, + "/index/relationship", + @"{ + 'name': 'foo', + 'config': { 'type': 'exact', 'provider': 'lucene' } + }")] + public void ShouldThrowApplicationExceptionIfHttpCodeIsNot201( + IndexFor indexFor, + IndexProvider indexProvider, + IndexType indexType, + string createEndpoint, + string createJson) + { + //Arrange + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostJson(createEndpoint, createJson), + MockResponse.Http(500) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var indexConfiguration = new IndexConfiguration + { + Provider = indexProvider, + Type = indexType + }; + graphClient.CreateIndex("foo", indexConfiguration, indexFor); + } + } + } } \ No newline at end of file diff --git a/Test/GraphClientTests/CreateNodeTests.cs b/Neo4jClient.Tests/GraphClientTests/CreateNodeTests.cs similarity index 96% rename from Test/GraphClientTests/CreateNodeTests.cs rename to Neo4jClient.Tests/GraphClientTests/CreateNodeTests.cs index c369e1735..c9789f6e2 100644 --- a/Test/GraphClientTests/CreateNodeTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/CreateNodeTests.cs @@ -1,623 +1,623 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Net; -using System.Net.Http; -using NUnit.Framework; -using Neo4jClient.ApiModels; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class CreateNodeTests - { - [Test] - [ExpectedException(typeof(ArgumentNullException))] - public void ShouldThrowArgumentNullExceptionForNullNode() - { - var client = new GraphClient(new Uri("http://foo")); - client.Create(null); - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - client.Create(new object()); - } - - [Test] - [ExpectedException(typeof(ValidationException))] - public void ShouldThrowValidationExceptionForInvalidNodes() - { - var graphClient = new GraphClient(new Uri("http://foo/db/data"), null); - - var testNode = new TestNode {Foo = "text is too long", Bar = null, Baz = "123"}; - graphClient.Create(testNode); - } - - [Test] - [ExpectedException(typeof(ArgumentException), ExpectedMessage = "You're trying to pass in a Node instance. Just pass the TestNode instance instead.\r\nParameter name: node")] - public void ShouldThrowArgumentExceptionForPreemptivelyWrappedNode() - { - var graphClient = new GraphClient(new Uri("http://foo/db/data"), null); - graphClient.Create((Node)null); - } - - [Test] - [ExpectedException(typeof(NeoException), ExpectedMessage = "PropertyValueException: Could not set property \"TestNode2\", unsupported type: {Foo=foo, Bar=bar}")] - public void ShouldThrowNeoExceptionWhenBatchCreationStepJobFails() - { - var testHarness = new RestTestHarness - { - { - MockRequest.PostJson("/batch", - @"[{ - 'method': 'POST', 'to' : '/node', - 'body': { - 'Foo': 'foo', - 'TestNode2': { 'Foo': 'foo', 'Bar': 'bar' } - }, - 'id': 0 - }]" - ), - MockResponse.Json(HttpStatusCode.OK, - @"[ { - 'id':0,'location':null, - 'body': { - 'message': 'Could not set property ""TestNode2"", unsupported type: {Foo=foo, Bar=bar}', - 'exception': 'PropertyValueException', - 'fullname': 'org.neo4j.server.rest.web.PropertyValueException', - 'stacktrace': [ - 'org.neo4j.server.rest.domain.PropertySettingStrategy.setProperty(PropertySettingStrategy.java:141)', - 'java.lang.Thread.run(Unknown Source)' - ] - }, - 'status': 400}]" - ) - } - }; - - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.Create(new NestedTestNode() - { - Foo = "foo", - TestNode2 = new TestNode2() {Bar = "bar", Foo = "foo"} - }); - - } - - [Test] - public void ShouldNotThrowANotSupportedExceptionForPre15M02DatabaseWhenThereAreNoIndexEntries() - { - var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - var batch = new List(); - batch.Add(HttpMethod.Post, "/node", testNode); - - var testHarness = new RestTestHarness - { - { - MockRequest.Get(""), - MockResponse.NeoRootPre15M02() - }, - { - MockRequest.PostObjectAsJson("/batch", batch), - MockResponse.Json(HttpStatusCode.OK, - @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ - 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', - 'data' : { - 'Foo' : 'foo', - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', - 'self' : 'http://foo/db/data/node/760', - 'property' : 'http://foo/db/data/node/760/properties/{key}', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', - 'properties' : 'http://foo/db/data/node/760/properties', - 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/760/relationships', - 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' - },'from':'/node'}]" - ) - } - }; - - var graphClient = testHarness.CreateAndConnectGraphClient(); - - graphClient.Create(testNode, null, null); - - testHarness.AssertAllRequestsWereReceived(); - } - - [Test] - public void ShouldSerializeAllProperties() - { - var testHarness = new RestTestHarness - { - { - MockRequest.PostJson("/batch", - @"[{ - 'method': 'POST', 'to' : '/node', - 'body': { - 'Foo': 'foo', - 'Bar': 'bar', - 'Baz': 'baz' - }, - 'id': 0 - }]" - ), - MockResponse.Json(HttpStatusCode.OK, - @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ - 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', - 'data' : { - 'Foo' : 'foo', - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', - 'self' : 'http://foo/db/data/node/760', - 'property' : 'http://foo/db/data/node/760/properties/{key}', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', - 'properties' : 'http://foo/db/data/node/760/properties', - 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/760/relationships', - 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' - },'from':'/node'}]" - ) - } - }; - - var graphClient = testHarness.CreateAndConnectGraphClient(); - - graphClient.Create(new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }); - - testHarness.AssertAllRequestsWereReceived(); - } - - [Test] - public void ShouldPreserveUnicodeCharactersInStringProperties() - { - var testHarness = new RestTestHarness - { - { - MockRequest.PostJson("/batch", - @"[{ - 'method': 'POST', 'to' : '/node', - 'body': { 'Foo': 'foo東京', 'Bar': 'bar', 'Baz': 'baz' }, - 'id': 0 - }]" - ), - MockResponse.Json(HttpStatusCode.OK, - @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ - 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', - 'data' : { - 'Foo' : 'foo', - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', - 'self' : 'http://foo/db/data/node/760', - 'property' : 'http://foo/db/data/node/760/properties/{key}', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', - 'properties' : 'http://foo/db/data/node/760/properties', - 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/760/relationships', - 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' - },'from':'/node'}]" - ) - } - }; - - var graphClient = testHarness.CreateAndConnectGraphClient(); - - graphClient.Create(new TestNode { Foo = "foo東京", Bar = "bar", Baz = "baz" }); - - testHarness.AssertAllRequestsWereReceived(); - } - - [Test] - public void ShouldReturnReferenceToCreatedNode() - { - var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - var batch = new List(); - batch.Add(HttpMethod.Post, "/node", testNode); - - var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/batch", batch), - MockResponse.Json(HttpStatusCode.OK, - @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ - 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', - 'data' : { - 'Foo' : 'foo', - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', - 'self' : 'http://foo/db/data/node/760', - 'property' : 'http://foo/db/data/node/760/properties/{key}', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', - 'properties' : 'http://foo/db/data/node/760/properties', - 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/760/relationships', - 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' - },'from':'/node'}]" - ) - } - }; - - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var node = graphClient.Create(testNode); - - Assert.AreEqual(760, node.Id); - testHarness.AssertAllRequestsWereReceived(); - } - - [Test] - public void ShouldReturnReferenceToCreatedNodeWithLongId() - { - var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - var batch = new List(); - batch.Add(HttpMethod.Post, "/node", testNode); - - var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/batch", batch), - MockResponse.Json(HttpStatusCode.OK, - @"[{'id':0,'location':'http://foo/db/data/node/2157483647','body':{ - 'outgoing_relationships' : 'http://foo/db/data/node/2157483647/relationships/out', - 'data' : { - 'Foo' : 'foo', - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/2157483647/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/2157483647/relationships/all/{-list|&|types}', - 'self' : 'http://foo/db/data/node/2157483647', - 'property' : 'http://foo/db/data/node/2157483647/properties/{key}', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/2157483647/relationships/out/{-list|&|types}', - 'properties' : 'http://foo/db/data/node/2157483647/properties', - 'incoming_relationships' : 'http://foo/db/data/node/2157483647/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/2157483647/relationships', - 'paged_traverse' : 'http://foo/db/data/node/2157483647/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/2157483647/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/2157483647/relationships/in/{-list|&|types}' - },'from':'/node'}]" - ) - } - }; - - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var node = graphClient.Create(testNode); - - Assert.AreEqual(2157483647, node.Id); - testHarness.AssertAllRequestsWereReceived(); - } - - [Test] - public void ShouldReturnAttachedNodeReference() - { - var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - var batch = new List(); - batch.Add(HttpMethod.Post, "/node", testNode); - - var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/batch", batch), - MockResponse.Json(HttpStatusCode.OK, - @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ - 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', - 'data' : { - 'Foo' : 'foo', - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', - 'self' : 'http://foo/db/data/node/760', - 'property' : 'http://foo/db/data/node/760/properties/{key}', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', - 'properties' : 'http://foo/db/data/node/760/properties', - 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/760/relationships', - 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' - },'from':'/node'}]" - ) - } - }; - - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var node = graphClient.Create(testNode); - - Assert.IsNotNull(((IGremlinQuery)node).Client); - } - - [Test] - public void ShouldCreateOutgoingRelationship() - { - var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - var testRelationshipPayload = new TestPayload { Foo = "123", Bar = "456", Baz = "789" }; - var batch = new List(); - batch.Add(HttpMethod.Post, "/node", testNode); - batch.Add(HttpMethod.Post, "{0}/relationships", - new RelationshipTemplate { To = "/node/789", Data = testRelationshipPayload, Type = "TEST_RELATIONSHIP" }); - - var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/batch", batch), - MockResponse.Json(HttpStatusCode.OK, - @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ - 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', - 'data' : { - 'Foo' : 'foo', - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', - 'self' : 'http://foo/db/data/node/760', - 'property' : 'http://foo/db/data/node/760/properties/{key}', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', - 'properties' : 'http://foo/db/data/node/760/properties', - 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/760/relationships', - 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' - },'from':'/node'},{'id':1,'location':'http://foo/db/data/relationship/756','body':{ - 'start' : 'http://foo/db/data/node/760', - 'data' : { - 'Foo' : 123, - 'Bar' : 456, - 'Baz' : 789 - }, - 'property' : 'http://foo/db/data/relationship/756/properties/{key}', - 'self' : 'http://foo/db/data/relationship/756', - 'properties' : 'http://foo/db/data/relationship/756/properties', - 'type' : 'TEST_RELATIONSHIP', - 'extensions' : { - }, - 'end' : 'http://foo/db/data/node/789' - },'from':'http://foo/db/data/node/761/relationships'}]" - ) - } - }; - - var graphClient = testHarness.CreateAndConnectGraphClient(); - - graphClient.Create( - testNode, - new TestRelationship(789, testRelationshipPayload)); - - testHarness.AssertAllRequestsWereReceived(); - } - - [Test] - public void ShouldCreateIndexEntries() - { - var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - var batch = new List(); - batch.Add(HttpMethod.Post, "/node", testNode); - batch.Add(HttpMethod.Post, "/index/node/my_index", new { key = "key", value = "value", uri = "{0}" }); - batch.Add(HttpMethod.Post, "/index/node/my_index", new { key = "key3", value = "value3", uri = "{0}" }); - - var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/batch", batch), - MockResponse.Json(HttpStatusCode.OK, - @"[{'id':0,'location':'http://localhost:20001/db/data/node/763','body':{ - 'outgoing_relationships' : 'http://localhost:20001/db/data/node/763/relationships/out', - 'data' : { - 'Baz' : 'baz', - 'Foo' : 'foo', - 'Bar' : 'bar' - }, - 'traverse' : 'http://localhost:20001/db/data/node/763/traverse/{returnType}', - 'all_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/all/{-list|&|types}', - 'self' : 'http://localhost:20001/db/data/node/763', - 'property' : 'http://localhost:20001/db/data/node/763/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:20001/db/data/node/763/properties', - 'incoming_relationships' : 'http://localhost:20001/db/data/node/763/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:20001/db/data/node/763/relationships', - 'paged_traverse' : 'http://localhost:20001/db/data/node/763/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:20001/db/data/node/763/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/in/{-list|&|types}' - },'from':'/node'},{'id':1,'location':'http://localhost:20001/db/data/index/node/my_index/key/value/763','body':{ - 'indexed' : 'http://localhost:20001/db/data/index/node/my_index/key/value/763', - 'outgoing_relationships' : 'http://localhost:20001/db/data/node/763/relationships/out', - 'data' : { - 'Baz' : 'baz', - 'Foo' : 'foo', - 'Bar' : 'bar' - }, - 'traverse' : 'http://localhost:20001/db/data/node/763/traverse/{returnType}', - 'all_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/all/{-list|&|types}', - 'self' : 'http://localhost:20001/db/data/node/763', - 'property' : 'http://localhost:20001/db/data/node/763/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:20001/db/data/node/763/properties', - 'incoming_relationships' : 'http://localhost:20001/db/data/node/763/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:20001/db/data/node/763/relationships', - 'paged_traverse' : 'http://localhost:20001/db/data/node/763/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:20001/db/data/node/763/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/in/{-list|&|types}' - },'from':'/index/node/my_index/key/value'}]" - ) - } - }; - - var graphClient = testHarness.CreateAndConnectGraphClient(); - - graphClient.Create( - testNode, - null, - new[] - { - new IndexEntry - { - Name = "my_index", - KeyValues = new[] - { - new KeyValuePair("key", "value"), - new KeyValuePair("key2", ""), - new KeyValuePair("key3", "value3") - } - } - }); - } - - [Test] - public void ShouldCreateIncomingRelationship() - { - var testNode = new TestNode2 { Foo = "foo", Bar = "bar" }; - var testRelationshipPayload = new TestPayload { Foo = "123", Bar = "456", Baz = "789" }; - var batch = new List(); - batch.Add(HttpMethod.Post, "/node", testNode); - batch.Add(HttpMethod.Post, "/node/789/relationships", - new RelationshipTemplate { To = "{0}", Data = testRelationshipPayload, Type = "TEST_RELATIONSHIP" }); - - var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/batch", batch), - MockResponse.Json(HttpStatusCode.OK, - @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ - 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', - 'data' : { - 'Foo' : 'foo', - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', - 'self' : 'http://foo/db/data/node/760', - 'property' : 'http://foo/db/data/node/760/properties/{key}', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', - 'properties' : 'http://foo/db/data/node/760/properties', - 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/760/relationships', - 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' - },'from':'/node'},{'id':1,'location':'http://foo/db/data/relationship/756','body':{ - 'start' : 'http://foo/db/data/node/760', - 'data' : { - 'Foo' : 123, - 'Bar' : 456, - 'Baz' : 789 - }, - 'property' : 'http://foo/db/data/relationship/756/properties/{key}', - 'self' : 'http://foo/db/data/relationship/756', - 'properties' : 'http://foo/db/data/relationship/756/properties', - 'type' : 'TEST_RELATIONSHIP', - 'extensions' : { - }, - 'end' : 'http://foo/db/data/node/789' - },'from':'http://foo/db/data/node/761/relationships'}]" - ) - } - }; - - var graphClient = testHarness.CreateAndConnectGraphClient(); - - graphClient.Create( - testNode, - new TestRelationship(789, testRelationshipPayload)); - - testHarness.AssertAllRequestsWereReceived(); - } - - public class TestNode - { - [StringLength(4)] - public string Foo { get; set; } - - [Required] - public string Bar { get; set; } - - [RegularExpression(@"\w*")] - public string Baz { get; set; } - - } - - public class TestNode2 - { - public string Foo { get; set; } - public string Bar { get; set; } - } - - public class NestedTestNode - { - public string Foo { get; set; } - public TestNode2 TestNode2 { get; set; } - } - - public class TestPayload - { - public string Foo { get; set; } - public string Bar { get; set; } - public string Baz { get; set; } - } - - public class TestRelationship : Relationship, - IRelationshipAllowingSourceNode, - IRelationshipAllowingTargetNode - { - public TestRelationship(NodeReference targetNode, TestPayload data) - : base(targetNode, data) - { - } - - public override string RelationshipTypeKey - { - get { return "TEST_RELATIONSHIP"; } - } - } - } +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Net.Http; +using NUnit.Framework; +using Neo4jClient.ApiModels; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class CreateNodeTests + { + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ShouldThrowArgumentNullExceptionForNullNode() + { + var client = new GraphClient(new Uri("http://foo")); + client.Create(null); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void ShouldThrowInvalidOperationExceptionIfNotConnected() + { + var client = new GraphClient(new Uri("http://foo")); + client.Create(new object()); + } + + [Test] + [ExpectedException(typeof(ValidationException))] + public void ShouldThrowValidationExceptionForInvalidNodes() + { + var graphClient = new GraphClient(new Uri("http://foo/db/data"), null); + + var testNode = new TestNode {Foo = "text is too long", Bar = null, Baz = "123"}; + graphClient.Create(testNode); + } + + [Test] + [ExpectedException(typeof(ArgumentException), ExpectedMessage = "You're trying to pass in a Node instance. Just pass the TestNode instance instead.\r\nParameter name: node")] + public void ShouldThrowArgumentExceptionForPreemptivelyWrappedNode() + { + var graphClient = new GraphClient(new Uri("http://foo/db/data"), null); + graphClient.Create((Node)null); + } + + [Test] + [ExpectedException(typeof(NeoException), ExpectedMessage = "PropertyValueException: Could not set property \"TestNode2\", unsupported type: {Foo=foo, Bar=bar}")] + public void ShouldThrowNeoExceptionWhenBatchCreationStepJobFails() + { + var testHarness = new RestTestHarness + { + { + MockRequest.PostJson("/batch", + @"[{ + 'method': 'POST', 'to' : '/node', + 'body': { + 'Foo': 'foo', + 'TestNode2': { 'Foo': 'foo', 'Bar': 'bar' } + }, + 'id': 0 + }]" + ), + MockResponse.Json(HttpStatusCode.OK, + @"[ { + 'id':0,'location':null, + 'body': { + 'message': 'Could not set property ""TestNode2"", unsupported type: {Foo=foo, Bar=bar}', + 'exception': 'PropertyValueException', + 'fullname': 'org.neo4j.server.rest.web.PropertyValueException', + 'stacktrace': [ + 'org.neo4j.server.rest.domain.PropertySettingStrategy.setProperty(PropertySettingStrategy.java:141)', + 'java.lang.Thread.run(Unknown Source)' + ] + }, + 'status': 400}]" + ) + } + }; + + var graphClient = testHarness.CreateAndConnectGraphClient(); + graphClient.Create(new NestedTestNode() + { + Foo = "foo", + TestNode2 = new TestNode2() {Bar = "bar", Foo = "foo"} + }); + + } + + [Test] + public void ShouldNotThrowANotSupportedExceptionForPre15M02DatabaseWhenThereAreNoIndexEntries() + { + var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + var batch = new List(); + batch.Add(HttpMethod.Post, "/node", testNode); + + var testHarness = new RestTestHarness + { + { + MockRequest.Get(""), + MockResponse.NeoRootPre15M02() + }, + { + MockRequest.PostObjectAsJson("/batch", batch), + MockResponse.Json(HttpStatusCode.OK, + @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ + 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', + 'data' : { + 'Foo' : 'foo', + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', + 'self' : 'http://foo/db/data/node/760', + 'property' : 'http://foo/db/data/node/760/properties/{key}', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', + 'properties' : 'http://foo/db/data/node/760/properties', + 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/760/relationships', + 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' + },'from':'/node'}]" + ) + } + }; + + var graphClient = testHarness.CreateAndConnectGraphClient(); + + graphClient.Create(testNode, null, null); + + testHarness.AssertRequestConstraintsAreMet(); + } + + [Test] + public void ShouldSerializeAllProperties() + { + var testHarness = new RestTestHarness + { + { + MockRequest.PostJson("/batch", + @"[{ + 'method': 'POST', 'to' : '/node', + 'body': { + 'Foo': 'foo', + 'Bar': 'bar', + 'Baz': 'baz' + }, + 'id': 0 + }]" + ), + MockResponse.Json(HttpStatusCode.OK, + @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ + 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', + 'data' : { + 'Foo' : 'foo', + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', + 'self' : 'http://foo/db/data/node/760', + 'property' : 'http://foo/db/data/node/760/properties/{key}', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', + 'properties' : 'http://foo/db/data/node/760/properties', + 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/760/relationships', + 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' + },'from':'/node'}]" + ) + } + }; + + var graphClient = testHarness.CreateAndConnectGraphClient(); + + graphClient.Create(new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }); + + testHarness.AssertRequestConstraintsAreMet(); + } + + [Test] + public void ShouldPreserveUnicodeCharactersInStringProperties() + { + var testHarness = new RestTestHarness + { + { + MockRequest.PostJson("/batch", + @"[{ + 'method': 'POST', 'to' : '/node', + 'body': { 'Foo': 'foo東京', 'Bar': 'bar', 'Baz': 'baz' }, + 'id': 0 + }]" + ), + MockResponse.Json(HttpStatusCode.OK, + @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ + 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', + 'data' : { + 'Foo' : 'foo', + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', + 'self' : 'http://foo/db/data/node/760', + 'property' : 'http://foo/db/data/node/760/properties/{key}', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', + 'properties' : 'http://foo/db/data/node/760/properties', + 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/760/relationships', + 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' + },'from':'/node'}]" + ) + } + }; + + var graphClient = testHarness.CreateAndConnectGraphClient(); + + graphClient.Create(new TestNode { Foo = "foo東京", Bar = "bar", Baz = "baz" }); + + testHarness.AssertRequestConstraintsAreMet(); + } + + [Test] + public void ShouldReturnReferenceToCreatedNode() + { + var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + var batch = new List(); + batch.Add(HttpMethod.Post, "/node", testNode); + + var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/batch", batch), + MockResponse.Json(HttpStatusCode.OK, + @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ + 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', + 'data' : { + 'Foo' : 'foo', + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', + 'self' : 'http://foo/db/data/node/760', + 'property' : 'http://foo/db/data/node/760/properties/{key}', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', + 'properties' : 'http://foo/db/data/node/760/properties', + 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/760/relationships', + 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' + },'from':'/node'}]" + ) + } + }; + + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var node = graphClient.Create(testNode); + + Assert.AreEqual(760, node.Id); + testHarness.AssertRequestConstraintsAreMet(); + } + + [Test] + public void ShouldReturnReferenceToCreatedNodeWithLongId() + { + var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + var batch = new List(); + batch.Add(HttpMethod.Post, "/node", testNode); + + var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/batch", batch), + MockResponse.Json(HttpStatusCode.OK, + @"[{'id':0,'location':'http://foo/db/data/node/2157483647','body':{ + 'outgoing_relationships' : 'http://foo/db/data/node/2157483647/relationships/out', + 'data' : { + 'Foo' : 'foo', + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/2157483647/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/2157483647/relationships/all/{-list|&|types}', + 'self' : 'http://foo/db/data/node/2157483647', + 'property' : 'http://foo/db/data/node/2157483647/properties/{key}', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/2157483647/relationships/out/{-list|&|types}', + 'properties' : 'http://foo/db/data/node/2157483647/properties', + 'incoming_relationships' : 'http://foo/db/data/node/2157483647/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/2157483647/relationships', + 'paged_traverse' : 'http://foo/db/data/node/2157483647/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/2157483647/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/2157483647/relationships/in/{-list|&|types}' + },'from':'/node'}]" + ) + } + }; + + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var node = graphClient.Create(testNode); + + Assert.AreEqual(2157483647, node.Id); + testHarness.AssertRequestConstraintsAreMet(); + } + + [Test] + public void ShouldReturnAttachedNodeReference() + { + var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + var batch = new List(); + batch.Add(HttpMethod.Post, "/node", testNode); + + var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/batch", batch), + MockResponse.Json(HttpStatusCode.OK, + @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ + 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', + 'data' : { + 'Foo' : 'foo', + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', + 'self' : 'http://foo/db/data/node/760', + 'property' : 'http://foo/db/data/node/760/properties/{key}', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', + 'properties' : 'http://foo/db/data/node/760/properties', + 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/760/relationships', + 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' + },'from':'/node'}]" + ) + } + }; + + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var node = graphClient.Create(testNode); + + Assert.IsNotNull(((IGremlinQuery)node).Client); + } + + [Test] + public void ShouldCreateOutgoingRelationship() + { + var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + var testRelationshipPayload = new TestPayload { Foo = "123", Bar = "456", Baz = "789" }; + var batch = new List(); + batch.Add(HttpMethod.Post, "/node", testNode); + batch.Add(HttpMethod.Post, "{0}/relationships", + new RelationshipTemplate { To = "/node/789", Data = testRelationshipPayload, Type = "TEST_RELATIONSHIP" }); + + var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/batch", batch), + MockResponse.Json(HttpStatusCode.OK, + @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ + 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', + 'data' : { + 'Foo' : 'foo', + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', + 'self' : 'http://foo/db/data/node/760', + 'property' : 'http://foo/db/data/node/760/properties/{key}', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', + 'properties' : 'http://foo/db/data/node/760/properties', + 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/760/relationships', + 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' + },'from':'/node'},{'id':1,'location':'http://foo/db/data/relationship/756','body':{ + 'start' : 'http://foo/db/data/node/760', + 'data' : { + 'Foo' : 123, + 'Bar' : 456, + 'Baz' : 789 + }, + 'property' : 'http://foo/db/data/relationship/756/properties/{key}', + 'self' : 'http://foo/db/data/relationship/756', + 'properties' : 'http://foo/db/data/relationship/756/properties', + 'type' : 'TEST_RELATIONSHIP', + 'extensions' : { + }, + 'end' : 'http://foo/db/data/node/789' + },'from':'http://foo/db/data/node/761/relationships'}]" + ) + } + }; + + var graphClient = testHarness.CreateAndConnectGraphClient(); + + graphClient.Create( + testNode, + new TestRelationship(789, testRelationshipPayload)); + + testHarness.AssertRequestConstraintsAreMet(); + } + + [Test] + public void ShouldCreateIndexEntries() + { + var testNode = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + var batch = new List(); + batch.Add(HttpMethod.Post, "/node", testNode); + batch.Add(HttpMethod.Post, "/index/node/my_index", new { key = "key", value = "value", uri = "{0}" }); + batch.Add(HttpMethod.Post, "/index/node/my_index", new { key = "key3", value = "value3", uri = "{0}" }); + + var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/batch", batch), + MockResponse.Json(HttpStatusCode.OK, + @"[{'id':0,'location':'http://localhost:20001/db/data/node/763','body':{ + 'outgoing_relationships' : 'http://localhost:20001/db/data/node/763/relationships/out', + 'data' : { + 'Baz' : 'baz', + 'Foo' : 'foo', + 'Bar' : 'bar' + }, + 'traverse' : 'http://localhost:20001/db/data/node/763/traverse/{returnType}', + 'all_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/all/{-list|&|types}', + 'self' : 'http://localhost:20001/db/data/node/763', + 'property' : 'http://localhost:20001/db/data/node/763/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:20001/db/data/node/763/properties', + 'incoming_relationships' : 'http://localhost:20001/db/data/node/763/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:20001/db/data/node/763/relationships', + 'paged_traverse' : 'http://localhost:20001/db/data/node/763/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:20001/db/data/node/763/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/in/{-list|&|types}' + },'from':'/node'},{'id':1,'location':'http://localhost:20001/db/data/index/node/my_index/key/value/763','body':{ + 'indexed' : 'http://localhost:20001/db/data/index/node/my_index/key/value/763', + 'outgoing_relationships' : 'http://localhost:20001/db/data/node/763/relationships/out', + 'data' : { + 'Baz' : 'baz', + 'Foo' : 'foo', + 'Bar' : 'bar' + }, + 'traverse' : 'http://localhost:20001/db/data/node/763/traverse/{returnType}', + 'all_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/all/{-list|&|types}', + 'self' : 'http://localhost:20001/db/data/node/763', + 'property' : 'http://localhost:20001/db/data/node/763/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:20001/db/data/node/763/properties', + 'incoming_relationships' : 'http://localhost:20001/db/data/node/763/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:20001/db/data/node/763/relationships', + 'paged_traverse' : 'http://localhost:20001/db/data/node/763/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:20001/db/data/node/763/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:20001/db/data/node/763/relationships/in/{-list|&|types}' + },'from':'/index/node/my_index/key/value'}]" + ) + } + }; + + var graphClient = testHarness.CreateAndConnectGraphClient(); + + graphClient.Create( + testNode, + null, + new[] + { + new IndexEntry + { + Name = "my_index", + KeyValues = new[] + { + new KeyValuePair("key", "value"), + new KeyValuePair("key2", ""), + new KeyValuePair("key3", "value3") + } + } + }); + } + + [Test] + public void ShouldCreateIncomingRelationship() + { + var testNode = new TestNode2 { Foo = "foo", Bar = "bar" }; + var testRelationshipPayload = new TestPayload { Foo = "123", Bar = "456", Baz = "789" }; + var batch = new List(); + batch.Add(HttpMethod.Post, "/node", testNode); + batch.Add(HttpMethod.Post, "/node/789/relationships", + new RelationshipTemplate { To = "{0}", Data = testRelationshipPayload, Type = "TEST_RELATIONSHIP" }); + + var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/batch", batch), + MockResponse.Json(HttpStatusCode.OK, + @"[{'id':0,'location':'http://foo/db/data/node/760','body':{ + 'outgoing_relationships' : 'http://foo/db/data/node/760/relationships/out', + 'data' : { + 'Foo' : 'foo', + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/760/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/760/relationships/all/{-list|&|types}', + 'self' : 'http://foo/db/data/node/760', + 'property' : 'http://foo/db/data/node/760/properties/{key}', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/760/relationships/out/{-list|&|types}', + 'properties' : 'http://foo/db/data/node/760/properties', + 'incoming_relationships' : 'http://foo/db/data/node/760/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/760/relationships', + 'paged_traverse' : 'http://foo/db/data/node/760/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/760/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/760/relationships/in/{-list|&|types}' + },'from':'/node'},{'id':1,'location':'http://foo/db/data/relationship/756','body':{ + 'start' : 'http://foo/db/data/node/760', + 'data' : { + 'Foo' : 123, + 'Bar' : 456, + 'Baz' : 789 + }, + 'property' : 'http://foo/db/data/relationship/756/properties/{key}', + 'self' : 'http://foo/db/data/relationship/756', + 'properties' : 'http://foo/db/data/relationship/756/properties', + 'type' : 'TEST_RELATIONSHIP', + 'extensions' : { + }, + 'end' : 'http://foo/db/data/node/789' + },'from':'http://foo/db/data/node/761/relationships'}]" + ) + } + }; + + var graphClient = testHarness.CreateAndConnectGraphClient(); + + graphClient.Create( + testNode, + new TestRelationship(789, testRelationshipPayload)); + + testHarness.AssertRequestConstraintsAreMet(); + } + + public class TestNode + { + [StringLength(4)] + public string Foo { get; set; } + + [Required] + public string Bar { get; set; } + + [RegularExpression(@"\w*")] + public string Baz { get; set; } + + } + + public class TestNode2 + { + public string Foo { get; set; } + public string Bar { get; set; } + } + + public class NestedTestNode + { + public string Foo { get; set; } + public TestNode2 TestNode2 { get; set; } + } + + public class TestPayload + { + public string Foo { get; set; } + public string Bar { get; set; } + public string Baz { get; set; } + } + + public class TestRelationship : Relationship, + IRelationshipAllowingSourceNode, + IRelationshipAllowingTargetNode + { + public TestRelationship(NodeReference targetNode, TestPayload data) + : base(targetNode, data) + { + } + + public override string RelationshipTypeKey + { + get { return "TEST_RELATIONSHIP"; } + } + } + } } \ No newline at end of file diff --git a/Test/GraphClientTests/CreateRelationshipTests.cs b/Neo4jClient.Tests/GraphClientTests/CreateRelationshipTests.cs similarity index 97% rename from Test/GraphClientTests/CreateRelationshipTests.cs rename to Neo4jClient.Tests/GraphClientTests/CreateRelationshipTests.cs index da92eac9f..cef9f68a1 100644 --- a/Test/GraphClientTests/CreateRelationshipTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/CreateRelationshipTests.cs @@ -1,134 +1,134 @@ -using System; -using System.Net; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class CreateRelationshipTests - { - [Test] - public void ShouldReturnRelationshipReference() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostJson("/node/81/relationships", - @"{ - 'to': 'http://foo/db/data/node/81', - 'type': 'TEST_RELATIONSHIP' - }"), - MockResponse.Json(HttpStatusCode.Created, - @"{ - 'extensions' : { - }, - 'start' : 'http://foo/db/data/node/81', - 'property' : 'http://foo/db/data/relationship/38/properties/{key}', - 'self' : 'http://foo/db/data/relationship/38', - 'properties' : 'http://foo/db/data/relationship/38/properties', - 'type' : 'TEST_RELATIONSHIP', - 'end' : 'http://foo/db/data/node/80', - 'data' : { - } - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var testRelationship = new TestRelationship(81); - var relationshipReference = graphClient.CreateRelationship(new NodeReference(81), testRelationship); - - Assert.IsInstanceOf(relationshipReference); - Assert.IsNotInstanceOf>(relationshipReference); - Assert.AreEqual(38, relationshipReference.Id); - } - } - - [Test] - public void ShouldReturnAttachedRelationshipReference() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostJson("/node/81/relationships", - @"{ - 'to': 'http://foo/db/data/node/81', - 'type': 'TEST_RELATIONSHIP' - }"), - MockResponse.Json(HttpStatusCode.Created, - @"{ - 'extensions' : { - }, - 'start' : 'http://foo/db/data/node/81', - 'property' : 'http://foo/db/data/relationship/38/properties/{key}', - 'self' : 'http://foo/db/data/relationship/38', - 'properties' : 'http://foo/db/data/relationship/38/properties', - 'type' : 'TEST_RELATIONSHIP', - 'end' : 'http://foo/db/data/node/80', - 'data' : { - } - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var testRelationship = new TestRelationship(81); - var relationshipReference = graphClient.CreateRelationship(new NodeReference(81), testRelationship); - - Assert.AreEqual(graphClient, ((IAttachedReference)relationshipReference).Client); - } - } - - [Test] - [ExpectedException(typeof(ArgumentNullException))] - public void ShouldThrowArgumentNullExceptionForNullNodeReference() - { - var client = new GraphClient(new Uri("http://foo")); - client.CreateRelationship((NodeReference)null, new TestRelationship(10)); - } - - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - client.CreateRelationship(new NodeReference(5), new TestRelationship(10)); - } - - [Test] - [ExpectedException(typeof(NotSupportedException))] - public void ShouldThrowNotSupportedExceptionForIncomingRelationship() - { - using (var testHarness = new RestTestHarness()) - { - var client = testHarness.CreateAndConnectGraphClient(); - client.CreateRelationship(new NodeReference(5), new TestRelationship(10) { Direction = RelationshipDirection.Incoming }); - } - } - - public class TestNode - { - } - - public class TestNode2 - { - } - - public class TestRelationship : Relationship, - IRelationshipAllowingSourceNode, - IRelationshipAllowingTargetNode - { - public TestRelationship(NodeReference targetNode) - : base(targetNode) - { - } - - public override string RelationshipTypeKey - { - get { return "TEST_RELATIONSHIP"; } - } - } - } -} +using System; +using System.Net; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class CreateRelationshipTests + { + [Test] + public void ShouldReturnRelationshipReference() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostJson("/node/81/relationships", + @"{ + 'to': 'http://foo/db/data/node/81', + 'type': 'TEST_RELATIONSHIP' + }"), + MockResponse.Json(HttpStatusCode.Created, + @"{ + 'extensions' : { + }, + 'start' : 'http://foo/db/data/node/81', + 'property' : 'http://foo/db/data/relationship/38/properties/{key}', + 'self' : 'http://foo/db/data/relationship/38', + 'properties' : 'http://foo/db/data/relationship/38/properties', + 'type' : 'TEST_RELATIONSHIP', + 'end' : 'http://foo/db/data/node/80', + 'data' : { + } + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var testRelationship = new TestRelationship(81); + var relationshipReference = graphClient.CreateRelationship(new NodeReference(81), testRelationship); + + Assert.IsInstanceOf(relationshipReference); + Assert.IsNotInstanceOf>(relationshipReference); + Assert.AreEqual(38, relationshipReference.Id); + } + } + + [Test] + public void ShouldReturnAttachedRelationshipReference() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostJson("/node/81/relationships", + @"{ + 'to': 'http://foo/db/data/node/81', + 'type': 'TEST_RELATIONSHIP' + }"), + MockResponse.Json(HttpStatusCode.Created, + @"{ + 'extensions' : { + }, + 'start' : 'http://foo/db/data/node/81', + 'property' : 'http://foo/db/data/relationship/38/properties/{key}', + 'self' : 'http://foo/db/data/relationship/38', + 'properties' : 'http://foo/db/data/relationship/38/properties', + 'type' : 'TEST_RELATIONSHIP', + 'end' : 'http://foo/db/data/node/80', + 'data' : { + } + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var testRelationship = new TestRelationship(81); + var relationshipReference = graphClient.CreateRelationship(new NodeReference(81), testRelationship); + + Assert.AreEqual(graphClient, ((IAttachedReference)relationshipReference).Client); + } + } + + [Test] + [ExpectedException(typeof(ArgumentNullException))] + public void ShouldThrowArgumentNullExceptionForNullNodeReference() + { + var client = new GraphClient(new Uri("http://foo")); + client.CreateRelationship((NodeReference)null, new TestRelationship(10)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void ShouldThrowInvalidOperationExceptionIfNotConnected() + { + var client = new GraphClient(new Uri("http://foo")); + client.CreateRelationship(new NodeReference(5), new TestRelationship(10)); + } + + [Test] + [ExpectedException(typeof(NotSupportedException))] + public void ShouldThrowNotSupportedExceptionForIncomingRelationship() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectGraphClient(); + client.CreateRelationship(new NodeReference(5), new TestRelationship(10) { Direction = RelationshipDirection.Incoming }); + } + } + + public class TestNode + { + } + + public class TestNode2 + { + } + + public class TestRelationship : Relationship, + IRelationshipAllowingSourceNode, + IRelationshipAllowingTargetNode + { + public TestRelationship(NodeReference targetNode) + : base(targetNode) + { + } + + public override string RelationshipTypeKey + { + get { return "TEST_RELATIONSHIP"; } + } + } + } +} diff --git a/Test/GraphClientTests/Cypher/ExecuteCypherTests.cs b/Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteCypherTests.cs similarity index 96% rename from Test/GraphClientTests/Cypher/ExecuteCypherTests.cs rename to Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteCypherTests.cs index 122dfd089..00bf3a965 100644 --- a/Test/GraphClientTests/Cypher/ExecuteCypherTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteCypherTests.cs @@ -1,111 +1,111 @@ -using System; -using System.Collections.Generic; -using System.Net; -using NUnit.Framework; -using Neo4jClient.ApiModels.Cypher; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.GraphClientTests.Cypher -{ - [TestFixture] - public class ExecuteCypherTests - { - [Test] - public void ShouldSendCommandAndNotCareAboutResults() - { - // Arrange - const string queryText = @"START d=node({p0}), e=node({p1}) CREATE UNIQUE d-[:foo]->e"; - var parameters = new Dictionary - { - {"p0", 215}, - {"p1", 219} - }; - - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Http((int)HttpStatusCode.OK) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - graphClient.ExecuteCypher(cypherQuery); - } - } - - [Test] - public void ShouldSendCommandAndNotCareAboutResultsAsync() - { - // Arrange - const string queryText = @"return 1"; - var parameters = new Dictionary(); - - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Http((int)HttpStatusCode.OK) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - bool raisedEvent = false; - - graphClient.OperationCompleted += (sender, e) => { raisedEvent = true; }; - - //Act - var task = graphClient.ExecuteCypherAsync(cypherQuery); - task.Wait(); - - Assert.IsTrue(raisedEvent, "Raised OperationCompleted"); - } - } - - [Test] - public void WhenAsyncCommandFails_ShouldNotRaiseCompleted() - { - // Arrange - const string queryText = @"return 1"; - var parameters = new Dictionary(); - - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Throws() - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - bool raisedEvent = false; - - graphClient.OperationCompleted += (sender, e) => { raisedEvent = true; }; - - //Act - var task = graphClient.ExecuteCypherAsync(cypherQuery) - .ContinueWith(t => - { - Assert.IsTrue(t.IsFaulted); - Assert.IsInstanceOf(t.Exception.Flatten().InnerException); - }); - task.Wait(); - - Assert.IsFalse(raisedEvent, "Raised OperationCompleted"); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Net; +using NUnit.Framework; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.GraphClientTests.Cypher +{ + [TestFixture] + public class ExecuteCypherTests + { + [Test] + public void ShouldSendCommandAndNotCareAboutResults() + { + // Arrange + const string queryText = @"START d=node({p0}), e=node({p1}) CREATE UNIQUE d-[:foo]->e"; + var parameters = new Dictionary + { + {"p0", 215}, + {"p1", 219} + }; + + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, CypherResultFormat.Rest); + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Http((int)HttpStatusCode.OK) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + graphClient.ExecuteCypher(cypherQuery); + } + } + + [Test] + public void ShouldSendCommandAndNotCareAboutResultsAsync() + { + // Arrange + const string queryText = @"return 1"; + var parameters = new Dictionary(); + + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Http((int)HttpStatusCode.OK) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + bool raisedEvent = false; + + graphClient.OperationCompleted += (sender, e) => { raisedEvent = true; }; + + //Act + var task = graphClient.ExecuteCypherAsync(cypherQuery); + task.Wait(); + + Assert.IsTrue(raisedEvent, "Raised OperationCompleted"); + } + } + + [Test] + public void WhenAsyncCommandFails_ShouldNotRaiseCompleted() + { + // Arrange + const string queryText = @"return 1"; + var parameters = new Dictionary(); + + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Throws() + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + bool raisedEvent = false; + + graphClient.OperationCompleted += (sender, e) => { raisedEvent = true; }; + + //Act + var task = graphClient.ExecuteCypherAsync(cypherQuery) + .ContinueWith(t => + { + Assert.IsTrue(t.IsFaulted); + Assert.IsInstanceOf(t.Exception.Flatten().InnerException); + }); + task.Wait(); + + Assert.IsFalse(raisedEvent, "Raised OperationCompleted"); + } + } + } +} diff --git a/Test/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs b/Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs similarity index 97% rename from Test/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs rename to Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs index 60083a0b8..49ca68763 100644 --- a/Test/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs @@ -1,690 +1,695 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using NUnit.Framework; -using Neo4jClient.ApiModels.Cypher; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Test.GraphClientTests.Cypher -{ - [TestFixture] - public class ExecuteGetCypherResultsTests - { - public class SimpleResultDto - { - public string RelationshipType { get; set; } - public string Name { get; set; } - public long? UniqueId { get; set; } - } - - [Test] public void ShouldDeserializePathsResultAsSetBased() - { - // Arrange - const string queryText = @"START d=node({p0}), e=node({p1}) - MATCH p = allShortestPaths( d-[*..15]-e ) - RETURN p"; - - var parameters = new Dictionary - { - {"p0", 215}, - {"p1", 219} - }; - - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Json(HttpStatusCode.OK, - @"{ - 'data' : [ [ { - 'start' : 'http://foo/db/data/node/215', - 'nodes' : [ 'http://foo/db/data/node/215', 'http://foo/db/data/node/0', 'http://foo/db/data/node/219' ], - 'length' : 2, - 'relationships' : [ 'http://foo/db/data/relationship/247', 'http://foo/db/data/relationship/257' ], - 'end' : 'http://foo/db/data/node/219' - } ], [ { - 'start' : 'http://foo/db/data/node/215', - 'nodes' : [ 'http://foo/db/data/node/215', 'http://foo/db/data/node/1', 'http://foo/db/data/node/219' ], - 'length' : 2, - 'relationships' : [ 'http://foo/db/data/relationship/248', 'http://foo/db/data/relationship/258' ], - 'end' : 'http://foo/db/data/node/219' - } ] ], - 'columns' : [ 'p' ] - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var results = graphClient - .ExecuteGetCypherResults(cypherQuery) - .ToArray(); - - //Assert - Assert.IsInstanceOf>(results); - Assert.AreEqual(results.First().Length, 2); - Assert.AreEqual(results.First().Start, "http://foo/db/data/node/215"); - Assert.AreEqual(results.First().End, "http://foo/db/data/node/219"); - Assert.AreEqual(results.Skip(1).First().Length, 2); - Assert.AreEqual(results.Skip(1).First().Start, "http://foo/db/data/node/215"); - Assert.AreEqual(results.Skip(1).First().End, "http://foo/db/data/node/219"); - } - } - - [Test] - public void ShouldDeserializeSimpleTableStructure() - { - // Arrange - const string queryText = @" - START x = node({p0}) - MATCH x-[r]->n - RETURN type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId - LIMIT 3"; - var cypherQuery = new CypherQuery( - queryText, - new Dictionary - { - {"p0", 123} - }, - CypherResultMode.Projection); - - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using(var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Json(HttpStatusCode.OK, @"{ - 'data' : [ [ 'HOSTS', 'foo', 44321 ], [ 'LIKES', 'bar', 44311 ], [ 'HOSTS', 'baz', 42586 ] ], - 'columns' : [ 'RelationshipType', 'Name', 'UniqueId' ] - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var results = graphClient.ExecuteGetCypherResults(cypherQuery); - - //Assert - Assert.IsInstanceOf>(results); - - var resultsArray = results.ToArray(); - Assert.AreEqual(3, resultsArray.Count()); - - var firstResult = resultsArray[0]; - Assert.AreEqual("HOSTS", firstResult.RelationshipType); - Assert.AreEqual("foo", firstResult.Name); - Assert.AreEqual(44321, firstResult.UniqueId); - - var secondResult = resultsArray[1]; - Assert.AreEqual("LIKES", secondResult.RelationshipType); - Assert.AreEqual("bar", secondResult.Name); - Assert.AreEqual(44311, secondResult.UniqueId); - - var thirdResult = resultsArray[2]; - Assert.AreEqual("HOSTS", thirdResult.RelationshipType); - Assert.AreEqual("baz", thirdResult.Name); - Assert.AreEqual(42586, thirdResult.UniqueId); - } - } - - [Test] - public void ShouldDeserializeArrayOfNodesInPropertyAsResultOfCollectFunctionInCypherQuery() - { - // Arrange - var cypherQuery = new CypherQuery( - @"START root=node(0) MATCH root-[:HAS_COMPANIES]->()-[:HAS_COMPANY]->company, company--foo RETURN company, collect(foo) as Bar", - new Dictionary(), - CypherResultMode.Projection); - - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Json(HttpStatusCode.OK, @"{ - 'columns' : [ 'ColumnA', 'ColumnBFromCollect' ], - 'data' : [ [ { - 'paged_traverse' : 'http://localhost:8000/db/data/node/358/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/358/relationships/out', - 'data' : { - 'Bar' : 'BHP', - 'Baz' : '1' - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/358/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/358/traverse/{returnType}', - 'self' : 'http://localhost:8000/db/data/node/358', - 'property' : 'http://localhost:8000/db/data/node/358/properties/{key}', - 'all_relationships' : 'http://localhost:8000/db/data/node/358/relationships/all', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/358/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:8000/db/data/node/358/properties', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/358/relationships/in', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/358/relationships/in/{-list|&|types}', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/358/relationships' - }, [ { - 'paged_traverse' : 'http://localhost:8000/db/data/node/362/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/362/relationships/out', - 'data' : { - 'OpportunityType' : 'Board', - 'Description' : 'Foo' - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/362/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/362/traverse/{returnType}', - 'self' : 'http://localhost:8000/db/data/node/362', - 'property' : 'http://localhost:8000/db/data/node/362/properties/{key}', - 'all_relationships' : 'http://localhost:8000/db/data/node/362/relationships/all', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/362/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:8000/db/data/node/362/properties', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/362/relationships/in', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/362/relationships/in/{-list|&|types}', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/362/relationships' - }, { - 'paged_traverse' : 'http://localhost:8000/db/data/node/359/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/359/relationships/out', - 'data' : { - 'OpportunityType' : 'Executive', - 'Description' : 'Bar' - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/359/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/359/traverse/{returnType}', - 'self' : 'http://localhost:8000/db/data/node/359', - 'property' : 'http://localhost:8000/db/data/node/359/properties/{key}', - 'all_relationships' : 'http://localhost:8000/db/data/node/359/relationships/all', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/359/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:8000/db/data/node/359/properties', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/359/relationships/in', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/359/relationships/in/{-list|&|types}', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/359/relationships' - } ] ] ] -}") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var results = graphClient.ExecuteGetCypherResults(cypherQuery); - - //Assert - Assert.IsInstanceOf>(results); - - var resultsArray = results.ToArray(); - Assert.AreEqual(1, resultsArray.Count()); - - var firstResult = resultsArray[0]; - Assert.AreEqual(358, firstResult.ColumnA.Reference.Id); - Assert.AreEqual("BHP", firstResult.ColumnA.Data.Bar); - Assert.AreEqual("1", firstResult.ColumnA.Data.Baz); - - var collectedResults = firstResult.ColumnBFromCollect.ToArray(); - Assert.AreEqual(2, collectedResults.Count()); - - var firstCollectedResult = collectedResults[0]; - Assert.AreEqual(362, firstCollectedResult.Reference.Id); - Assert.AreEqual("Board", firstCollectedResult.Data.OpportunityType); - Assert.AreEqual("Foo", firstCollectedResult.Data.Description); - - var secondCollectedResult = collectedResults[1]; - Assert.AreEqual(359, secondCollectedResult.Reference.Id); - Assert.AreEqual("Executive", secondCollectedResult.Data.OpportunityType); - Assert.AreEqual("Bar", secondCollectedResult.Data.Description); - } - } - - public class FooData - { - public string Bar { get; set; } - public string Baz { get; set; } - public DateTimeOffset? Date { get; set; } - } - - public class CollectResult - { - public Node ColumnA { get; set; } - public IEnumerable> ColumnBFromCollect { get; set; } - } - - public class BarData - { - public string OpportunityType { get; set; } - public string Description { get; set; } - } - - public class ResultWithNodeDto - { - public Node Fooness { get; set; } - public string RelationshipType { get; set; } - public string Name { get; set; } - public long? UniqueId { get; set; } - } - - public class ResultWithNodeDataObjectsDto - { - public FooData Fooness { get; set; } - public string RelationshipType { get; set; } - public string Name { get; set; } - public long? UniqueId { get; set; } - } - - public class ResultWithRelationshipDto - { - public RelationshipInstance Fooness { get; set; } - public string RelationshipType { get; set; } - public string Name { get; set; } - public long? UniqueId { get; set; } - } - - [Test] - public void ShouldDeserializeTableStructureWithNodes() - { - // Arrange - const string queryText = @" - START x = node({p0}) - MATCH x-[r]->n - RETURN x AS Fooness, type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId - LIMIT 3"; - var cypherQuery = new CypherQuery( - queryText, - new Dictionary - { - {"p0", 123} - }, - CypherResultMode.Projection); - - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Json(HttpStatusCode.OK, @"{ - 'data' : [ [ { - 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', - 'property' : 'http://foo/db/data/node/0/properties/{key}', - 'self' : 'http://foo/db/data/node/0', - 'properties' : 'http://foo/db/data/node/0/properties', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/0/relationships', - 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' - }, 'HOSTS', 'foo', 44321 ], [ { - 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', - 'property' : 'http://foo/db/data/node/0/properties/{key}', - 'self' : 'http://foo/db/data/node/2', - 'properties' : 'http://foo/db/data/node/0/properties', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/0/relationships', - 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' - }, 'LIKES', 'bar', 44311 ], [ { - 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', - 'property' : 'http://foo/db/data/node/0/properties/{key}', - 'self' : 'http://foo/db/data/node/12', - 'properties' : 'http://foo/db/data/node/0/properties', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/0/relationships', - 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' - }, 'HOSTS', 'baz', 42586 ] ], - 'columns' : [ 'Fooness', 'RelationshipType', 'Name', 'UniqueId' ] - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var results = graphClient.ExecuteGetCypherResults(cypherQuery); - - //Assert - Assert.IsInstanceOf>(results); - - var resultsArray = results.ToArray(); - Assert.AreEqual(3, resultsArray.Count()); - - var firstResult = resultsArray[0]; - Assert.AreEqual(0, firstResult.Fooness.Reference.Id); - Assert.AreEqual("bar", firstResult.Fooness.Data.Bar); - Assert.AreEqual("baz", firstResult.Fooness.Data.Baz); - Assert.AreEqual("HOSTS", firstResult.RelationshipType); - Assert.AreEqual("foo", firstResult.Name); - Assert.AreEqual(44321, firstResult.UniqueId); - - var secondResult = resultsArray[1]; - Assert.AreEqual(2, secondResult.Fooness.Reference.Id); - Assert.AreEqual("bar", secondResult.Fooness.Data.Bar); - Assert.AreEqual("baz", secondResult.Fooness.Data.Baz); - Assert.AreEqual("LIKES", secondResult.RelationshipType); - Assert.AreEqual("bar", secondResult.Name); - Assert.AreEqual(44311, secondResult.UniqueId); - - var thirdResult = resultsArray[2]; - Assert.AreEqual(12, thirdResult.Fooness.Reference.Id); - Assert.AreEqual("bar", thirdResult.Fooness.Data.Bar); - Assert.AreEqual("baz", thirdResult.Fooness.Data.Baz); - Assert.AreEqual("HOSTS", thirdResult.RelationshipType); - Assert.AreEqual("baz", thirdResult.Name); - Assert.AreEqual(42586, thirdResult.UniqueId); - } - } - - [Test] - public void ShouldDeserializeTableStructureWithNodeDataObjects() - { - // Arrange - const string queryText = @" - START x = node({p0}) - MATCH x-[r]->n - RETURN x AS Fooness, type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId - LIMIT 3"; - var cypherQuery = new CypherQuery( - queryText, - new Dictionary - { - {"p0", 123} - }, - CypherResultMode.Projection); - - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Json(HttpStatusCode.OK, @"{ - 'data' : [ [ { - 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', - 'property' : 'http://foo/db/data/node/0/properties/{key}', - 'self' : 'http://foo/db/data/node/0', - 'properties' : 'http://foo/db/data/node/0/properties', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/0/relationships', - 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' - }, 'HOSTS', 'foo', 44321 ], [ { - 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', - 'property' : 'http://foo/db/data/node/0/properties/{key}', - 'self' : 'http://foo/db/data/node/2', - 'properties' : 'http://foo/db/data/node/0/properties', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/0/relationships', - 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' - }, 'LIKES', 'bar', 44311 ], [ { - 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', - 'property' : 'http://foo/db/data/node/0/properties/{key}', - 'self' : 'http://foo/db/data/node/12', - 'properties' : 'http://foo/db/data/node/0/properties', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/0/relationships', - 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' - }, 'HOSTS', 'baz', 42586 ] ], - 'columns' : [ 'Fooness', 'RelationshipType', 'Name', 'UniqueId' ] - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var results = graphClient.ExecuteGetCypherResults(cypherQuery); - - //Assert - Assert.IsInstanceOf>(results); - - var resultsArray = results.ToArray(); - Assert.AreEqual(3, resultsArray.Count()); - - var firstResult = resultsArray[0]; - Assert.AreEqual("bar", firstResult.Fooness.Bar); - Assert.AreEqual("baz", firstResult.Fooness.Baz); - Assert.AreEqual("HOSTS", firstResult.RelationshipType); - Assert.AreEqual("foo", firstResult.Name); - Assert.AreEqual(44321, firstResult.UniqueId); - - var secondResult = resultsArray[1]; - Assert.AreEqual("bar", secondResult.Fooness.Bar); - Assert.AreEqual("baz", secondResult.Fooness.Baz); - Assert.AreEqual("LIKES", secondResult.RelationshipType); - Assert.AreEqual("bar", secondResult.Name); - Assert.AreEqual(44311, secondResult.UniqueId); - - var thirdResult = resultsArray[2]; - Assert.AreEqual("bar", thirdResult.Fooness.Bar); - Assert.AreEqual("baz", thirdResult.Fooness.Baz); - Assert.AreEqual("HOSTS", thirdResult.RelationshipType); - Assert.AreEqual("baz", thirdResult.Name); - Assert.AreEqual(42586, thirdResult.UniqueId); - } - } - - [Test] - public void ShouldDeserializeTableStructureWithRelationships() - { - // Arrange - const string queryText = @" - START x = node({p0}) - MATCH x-[r]->n - RETURN x AS Fooness, type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId - LIMIT 3"; - var cypherQuery = new CypherQuery( - queryText, - new Dictionary - { - {"p0", 123} - }, - CypherResultMode.Projection); - - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Json(HttpStatusCode.OK, @"{ - 'data' : [ [ { - 'start' : 'http://foo/db/data/node/0', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'property' : 'http://foo/db/data/relationship/0/properties/{key}', - 'self' : 'http://foo/db/data/relationship/0', - 'properties' : 'http://foo/db/data/relationship/0/properties', - 'type' : 'HAS_REFERENCE_DATA', - 'extensions' : { - }, - 'end' : 'http://foo/db/data/node/1' - }, 'HOSTS', 'foo', 44321 ], [ { - 'start' : 'http://foo/db/data/node/1', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'property' : 'http://foo/db/data/relationship/1/properties/{key}', - 'self' : 'http://foo/db/data/relationship/1', - 'properties' : 'http://foo/db/data/relationship/1/properties', - 'type' : 'HAS_REFERENCE_DATA', - 'extensions' : { - }, - 'end' : 'http://foo/db/data/node/1' - }, 'LIKES', 'bar', 44311 ], [ { - 'start' : 'http://foo/db/data/node/2', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'property' : 'http://foo/db/data/relationship/2/properties/{key}', - 'self' : 'http://foo/db/data/relationship/2', - 'properties' : 'http://foo/db/data/relationship/2/properties', - 'type' : 'HAS_REFERENCE_DATA', - 'extensions' : { - }, - 'end' : 'http://foo/db/data/node/1' - }, 'HOSTS', 'baz', 42586 ] ], - 'columns' : [ 'Fooness', 'RelationshipType', 'Name', 'UniqueId' ] - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - //Act - var results = graphClient.ExecuteGetCypherResults(cypherQuery); - - //Assert - Assert.IsInstanceOf>(results); - - var resultsArray = results.ToArray(); - Assert.AreEqual(3, resultsArray.Count()); - - var firstResult = resultsArray[0]; - Assert.AreEqual(0, firstResult.Fooness.Reference.Id); - Assert.AreEqual("bar", firstResult.Fooness.Data.Bar); - Assert.AreEqual("baz", firstResult.Fooness.Data.Baz); - Assert.AreEqual("HOSTS", firstResult.RelationshipType); - Assert.AreEqual("foo", firstResult.Name); - Assert.AreEqual(44321, firstResult.UniqueId); - - var secondResult = resultsArray[1]; - Assert.AreEqual(1, secondResult.Fooness.Reference.Id); - Assert.AreEqual("bar", secondResult.Fooness.Data.Bar); - Assert.AreEqual("baz", secondResult.Fooness.Data.Baz); - Assert.AreEqual("LIKES", secondResult.RelationshipType); - Assert.AreEqual("bar", secondResult.Name); - Assert.AreEqual(44311, secondResult.UniqueId); - - var thirdResult = resultsArray[2]; - Assert.AreEqual(2, thirdResult.Fooness.Reference.Id); - Assert.AreEqual("bar", thirdResult.Fooness.Data.Bar); - Assert.AreEqual("baz", thirdResult.Fooness.Data.Baz); - Assert.AreEqual("HOSTS", thirdResult.RelationshipType); - Assert.AreEqual("baz", thirdResult.Name); - Assert.AreEqual(42586, thirdResult.UniqueId); - } - } - - [Test] - public void ShouldPromoteBadQueryResponseToNiceException() - { - // Arrange - const string queryText = @"broken query"; - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection); - var cypherApiQuery = new CypherApiQuery(cypherQuery); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Json(HttpStatusCode.BadRequest, @"{ - 'message' : 'expected START or CREATE\n\'bad query\'\n ^', - 'exception' : 'SyntaxException', - 'fullname' : 'org.neo4j.cypher.SyntaxException', - 'stacktrace' : [ 'org.neo4j.cypher.internal.parser.v1_9.CypherParserImpl.parse(CypherParserImpl.scala:45)', 'org.neo4j.cypher.CypherParser.parse(CypherParser.scala:44)', 'org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:80)', 'org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:80)', 'org.neo4j.cypher.internal.LRUCache.getOrElseUpdate(LRUCache.scala:37)', 'org.neo4j.cypher.ExecutionEngine.prepare(ExecutionEngine.scala:80)', 'org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:72)', 'org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:76)', 'org.neo4j.cypher.javacompat.ExecutionEngine.execute(ExecutionEngine.java:79)', 'org.neo4j.server.rest.web.CypherService.cypher(CypherService.java:94)', 'java.lang.reflect.Method.invoke(Unknown Source)', 'org.neo4j.server.rest.security.SecurityFilter.doFilter(SecurityFilter.java:112)' ] -}") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var ex = Assert.Throws(() => graphClient.ExecuteGetCypherResults(cypherQuery)); - Assert.AreEqual("SyntaxException: expected START or CREATE\n'bad query'\n ^", ex.Message); - Assert.AreEqual("expected START or CREATE\n'bad query'\n ^", ex.NeoMessage); - Assert.AreEqual("SyntaxException", ex.NeoExceptionName); - Assert.AreEqual("org.neo4j.cypher.SyntaxException", ex.NeoFullName); - - var expectedStack = new[] - { - "org.neo4j.cypher.internal.parser.v1_9.CypherParserImpl.parse(CypherParserImpl.scala:45)", - "org.neo4j.cypher.CypherParser.parse(CypherParser.scala:44)", - "org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:80)", - "org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:80)", - "org.neo4j.cypher.internal.LRUCache.getOrElseUpdate(LRUCache.scala:37)", - "org.neo4j.cypher.ExecutionEngine.prepare(ExecutionEngine.scala:80)", - "org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:72)", - "org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:76)", - "org.neo4j.cypher.javacompat.ExecutionEngine.execute(ExecutionEngine.java:79)", - "org.neo4j.server.rest.web.CypherService.cypher(CypherService.java:94)", - "java.lang.reflect.Method.invoke(Unknown Source)", - "org.neo4j.server.rest.security.SecurityFilter.doFilter(SecurityFilter.java:112)" - }; - CollectionAssert.AreEqual(expectedStack, ex.NeoStackTrace); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using NUnit.Framework; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Test.GraphClientTests.Cypher +{ + [TestFixture] + public class ExecuteGetCypherResultsTests + { + public class SimpleResultDto + { + public string RelationshipType { get; set; } + public string Name { get; set; } + public long? UniqueId { get; set; } + } + + [Test] public void ShouldDeserializePathsResultAsSetBased() + { + // Arrange + const string queryText = @"START d=node({p0}), e=node({p1}) + MATCH p = allShortestPaths( d-[*..15]-e ) + RETURN p"; + + var parameters = new Dictionary + { + {"p0", 215}, + {"p1", 219} + }; + + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, CypherResultFormat.Rest); + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Json(HttpStatusCode.OK, + @"{ + 'data' : [ [ { + 'start' : 'http://foo/db/data/node/215', + 'nodes' : [ 'http://foo/db/data/node/215', 'http://foo/db/data/node/0', 'http://foo/db/data/node/219' ], + 'length' : 2, + 'relationships' : [ 'http://foo/db/data/relationship/247', 'http://foo/db/data/relationship/257' ], + 'end' : 'http://foo/db/data/node/219' + } ], [ { + 'start' : 'http://foo/db/data/node/215', + 'nodes' : [ 'http://foo/db/data/node/215', 'http://foo/db/data/node/1', 'http://foo/db/data/node/219' ], + 'length' : 2, + 'relationships' : [ 'http://foo/db/data/relationship/248', 'http://foo/db/data/relationship/258' ], + 'end' : 'http://foo/db/data/node/219' + } ] ], + 'columns' : [ 'p' ] + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var results = graphClient + .ExecuteGetCypherResults(cypherQuery) + .ToArray(); + + //Assert + Assert.IsInstanceOf>(results); + Assert.AreEqual(results.First().Length, 2); + Assert.AreEqual(results.First().Start, "http://foo/db/data/node/215"); + Assert.AreEqual(results.First().End, "http://foo/db/data/node/219"); + Assert.AreEqual(results.Skip(1).First().Length, 2); + Assert.AreEqual(results.Skip(1).First().Start, "http://foo/db/data/node/215"); + Assert.AreEqual(results.Skip(1).First().End, "http://foo/db/data/node/219"); + } + } + + [Test] + public void ShouldDeserializeSimpleTableStructure() + { + // Arrange + const string queryText = @" + START x = node({p0}) + MATCH x-[r]->n + RETURN type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId + LIMIT 3"; + var cypherQuery = new CypherQuery( + queryText, + new Dictionary + { + {"p0", 123} + }, + CypherResultMode.Projection, + CypherResultFormat.Rest); + + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using(var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Json(HttpStatusCode.OK, @"{ + 'data' : [ [ 'HOSTS', 'foo', 44321 ], [ 'LIKES', 'bar', 44311 ], [ 'HOSTS', 'baz', 42586 ] ], + 'columns' : [ 'RelationshipType', 'Name', 'UniqueId' ] + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var results = graphClient.ExecuteGetCypherResults(cypherQuery); + + //Assert + Assert.IsInstanceOf>(results); + + var resultsArray = results.ToArray(); + Assert.AreEqual(3, resultsArray.Count()); + + var firstResult = resultsArray[0]; + Assert.AreEqual("HOSTS", firstResult.RelationshipType); + Assert.AreEqual("foo", firstResult.Name); + Assert.AreEqual(44321, firstResult.UniqueId); + + var secondResult = resultsArray[1]; + Assert.AreEqual("LIKES", secondResult.RelationshipType); + Assert.AreEqual("bar", secondResult.Name); + Assert.AreEqual(44311, secondResult.UniqueId); + + var thirdResult = resultsArray[2]; + Assert.AreEqual("HOSTS", thirdResult.RelationshipType); + Assert.AreEqual("baz", thirdResult.Name); + Assert.AreEqual(42586, thirdResult.UniqueId); + } + } + + [Test] + public void ShouldDeserializeArrayOfNodesInPropertyAsResultOfCollectFunctionInCypherQuery() + { + // Arrange + var cypherQuery = new CypherQuery( + @"START root=node(0) MATCH root-[:HAS_COMPANIES]->()-[:HAS_COMPANY]->company, company--foo RETURN company, collect(foo) as Bar", + new Dictionary(), + CypherResultMode.Projection, + CypherResultFormat.Rest); + + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Json(HttpStatusCode.OK, @"{ + 'columns' : [ 'ColumnA', 'ColumnBFromCollect' ], + 'data' : [ [ { + 'paged_traverse' : 'http://localhost:8000/db/data/node/358/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/358/relationships/out', + 'data' : { + 'Bar' : 'BHP', + 'Baz' : '1' + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/358/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/358/traverse/{returnType}', + 'self' : 'http://localhost:8000/db/data/node/358', + 'property' : 'http://localhost:8000/db/data/node/358/properties/{key}', + 'all_relationships' : 'http://localhost:8000/db/data/node/358/relationships/all', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/358/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:8000/db/data/node/358/properties', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/358/relationships/in', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/358/relationships/in/{-list|&|types}', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/358/relationships' + }, [ { + 'paged_traverse' : 'http://localhost:8000/db/data/node/362/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/362/relationships/out', + 'data' : { + 'OpportunityType' : 'Board', + 'Description' : 'Foo' + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/362/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/362/traverse/{returnType}', + 'self' : 'http://localhost:8000/db/data/node/362', + 'property' : 'http://localhost:8000/db/data/node/362/properties/{key}', + 'all_relationships' : 'http://localhost:8000/db/data/node/362/relationships/all', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/362/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:8000/db/data/node/362/properties', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/362/relationships/in', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/362/relationships/in/{-list|&|types}', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/362/relationships' + }, { + 'paged_traverse' : 'http://localhost:8000/db/data/node/359/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/359/relationships/out', + 'data' : { + 'OpportunityType' : 'Executive', + 'Description' : 'Bar' + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/359/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/359/traverse/{returnType}', + 'self' : 'http://localhost:8000/db/data/node/359', + 'property' : 'http://localhost:8000/db/data/node/359/properties/{key}', + 'all_relationships' : 'http://localhost:8000/db/data/node/359/relationships/all', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/359/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:8000/db/data/node/359/properties', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/359/relationships/in', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/359/relationships/in/{-list|&|types}', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/359/relationships' + } ] ] ] +}") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var results = graphClient.ExecuteGetCypherResults(cypherQuery); + + //Assert + Assert.IsInstanceOf>(results); + + var resultsArray = results.ToArray(); + Assert.AreEqual(1, resultsArray.Count()); + + var firstResult = resultsArray[0]; + Assert.AreEqual(358, firstResult.ColumnA.Reference.Id); + Assert.AreEqual("BHP", firstResult.ColumnA.Data.Bar); + Assert.AreEqual("1", firstResult.ColumnA.Data.Baz); + + var collectedResults = firstResult.ColumnBFromCollect.ToArray(); + Assert.AreEqual(2, collectedResults.Count()); + + var firstCollectedResult = collectedResults[0]; + Assert.AreEqual(362, firstCollectedResult.Reference.Id); + Assert.AreEqual("Board", firstCollectedResult.Data.OpportunityType); + Assert.AreEqual("Foo", firstCollectedResult.Data.Description); + + var secondCollectedResult = collectedResults[1]; + Assert.AreEqual(359, secondCollectedResult.Reference.Id); + Assert.AreEqual("Executive", secondCollectedResult.Data.OpportunityType); + Assert.AreEqual("Bar", secondCollectedResult.Data.Description); + } + } + + public class FooData + { + public string Bar { get; set; } + public string Baz { get; set; } + public DateTimeOffset? Date { get; set; } + } + + public class CollectResult + { + public Node ColumnA { get; set; } + public IEnumerable> ColumnBFromCollect { get; set; } + } + + public class BarData + { + public string OpportunityType { get; set; } + public string Description { get; set; } + } + + public class ResultWithNodeDto + { + public Node Fooness { get; set; } + public string RelationshipType { get; set; } + public string Name { get; set; } + public long? UniqueId { get; set; } + } + + public class ResultWithNodeDataObjectsDto + { + public FooData Fooness { get; set; } + public string RelationshipType { get; set; } + public string Name { get; set; } + public long? UniqueId { get; set; } + } + + public class ResultWithRelationshipDto + { + public RelationshipInstance Fooness { get; set; } + public string RelationshipType { get; set; } + public string Name { get; set; } + public long? UniqueId { get; set; } + } + + [Test] + public void ShouldDeserializeTableStructureWithNodes() + { + // Arrange + const string queryText = @" + START x = node({p0}) + MATCH x-[r]->n + RETURN x AS Fooness, type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId + LIMIT 3"; + var cypherQuery = new CypherQuery( + queryText, + new Dictionary + { + {"p0", 123} + }, + CypherResultMode.Projection, + CypherResultFormat.Rest); + + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Json(HttpStatusCode.OK, @"{ + 'data' : [ [ { + 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', + 'property' : 'http://foo/db/data/node/0/properties/{key}', + 'self' : 'http://foo/db/data/node/0', + 'properties' : 'http://foo/db/data/node/0/properties', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/0/relationships', + 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' + }, 'HOSTS', 'foo', 44321 ], [ { + 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', + 'property' : 'http://foo/db/data/node/0/properties/{key}', + 'self' : 'http://foo/db/data/node/2', + 'properties' : 'http://foo/db/data/node/0/properties', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/0/relationships', + 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' + }, 'LIKES', 'bar', 44311 ], [ { + 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', + 'property' : 'http://foo/db/data/node/0/properties/{key}', + 'self' : 'http://foo/db/data/node/12', + 'properties' : 'http://foo/db/data/node/0/properties', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/0/relationships', + 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' + }, 'HOSTS', 'baz', 42586 ] ], + 'columns' : [ 'Fooness', 'RelationshipType', 'Name', 'UniqueId' ] + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var results = graphClient.ExecuteGetCypherResults(cypherQuery); + + //Assert + Assert.IsInstanceOf>(results); + + var resultsArray = results.ToArray(); + Assert.AreEqual(3, resultsArray.Count()); + + var firstResult = resultsArray[0]; + Assert.AreEqual(0, firstResult.Fooness.Reference.Id); + Assert.AreEqual("bar", firstResult.Fooness.Data.Bar); + Assert.AreEqual("baz", firstResult.Fooness.Data.Baz); + Assert.AreEqual("HOSTS", firstResult.RelationshipType); + Assert.AreEqual("foo", firstResult.Name); + Assert.AreEqual(44321, firstResult.UniqueId); + + var secondResult = resultsArray[1]; + Assert.AreEqual(2, secondResult.Fooness.Reference.Id); + Assert.AreEqual("bar", secondResult.Fooness.Data.Bar); + Assert.AreEqual("baz", secondResult.Fooness.Data.Baz); + Assert.AreEqual("LIKES", secondResult.RelationshipType); + Assert.AreEqual("bar", secondResult.Name); + Assert.AreEqual(44311, secondResult.UniqueId); + + var thirdResult = resultsArray[2]; + Assert.AreEqual(12, thirdResult.Fooness.Reference.Id); + Assert.AreEqual("bar", thirdResult.Fooness.Data.Bar); + Assert.AreEqual("baz", thirdResult.Fooness.Data.Baz); + Assert.AreEqual("HOSTS", thirdResult.RelationshipType); + Assert.AreEqual("baz", thirdResult.Name); + Assert.AreEqual(42586, thirdResult.UniqueId); + } + } + + [Test] + public void ShouldDeserializeTableStructureWithNodeDataObjects() + { + // Arrange + const string queryText = @" + START x = node({p0}) + MATCH x-[r]->n + RETURN x AS Fooness, type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId + LIMIT 3"; + var cypherQuery = new CypherQuery( + queryText, + new Dictionary + { + {"p0", 123} + }, + CypherResultMode.Projection, + CypherResultFormat.Rest); + + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Json(HttpStatusCode.OK, @"{ + 'data' : [ [ { + 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', + 'property' : 'http://foo/db/data/node/0/properties/{key}', + 'self' : 'http://foo/db/data/node/0', + 'properties' : 'http://foo/db/data/node/0/properties', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/0/relationships', + 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' + }, 'HOSTS', 'foo', 44321 ], [ { + 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', + 'property' : 'http://foo/db/data/node/0/properties/{key}', + 'self' : 'http://foo/db/data/node/2', + 'properties' : 'http://foo/db/data/node/0/properties', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/0/relationships', + 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' + }, 'LIKES', 'bar', 44311 ], [ { + 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/0/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/0/relationships/all/{-list|&|types}', + 'property' : 'http://foo/db/data/node/0/properties/{key}', + 'self' : 'http://foo/db/data/node/12', + 'properties' : 'http://foo/db/data/node/0/properties', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/0/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://foo/db/data/node/0/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/0/relationships', + 'paged_traverse' : 'http://foo/db/data/node/0/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/0/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/0/relationships/in/{-list|&|types}' + }, 'HOSTS', 'baz', 42586 ] ], + 'columns' : [ 'Fooness', 'RelationshipType', 'Name', 'UniqueId' ] + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var results = graphClient.ExecuteGetCypherResults(cypherQuery); + + //Assert + Assert.IsInstanceOf>(results); + + var resultsArray = results.ToArray(); + Assert.AreEqual(3, resultsArray.Count()); + + var firstResult = resultsArray[0]; + Assert.AreEqual("bar", firstResult.Fooness.Bar); + Assert.AreEqual("baz", firstResult.Fooness.Baz); + Assert.AreEqual("HOSTS", firstResult.RelationshipType); + Assert.AreEqual("foo", firstResult.Name); + Assert.AreEqual(44321, firstResult.UniqueId); + + var secondResult = resultsArray[1]; + Assert.AreEqual("bar", secondResult.Fooness.Bar); + Assert.AreEqual("baz", secondResult.Fooness.Baz); + Assert.AreEqual("LIKES", secondResult.RelationshipType); + Assert.AreEqual("bar", secondResult.Name); + Assert.AreEqual(44311, secondResult.UniqueId); + + var thirdResult = resultsArray[2]; + Assert.AreEqual("bar", thirdResult.Fooness.Bar); + Assert.AreEqual("baz", thirdResult.Fooness.Baz); + Assert.AreEqual("HOSTS", thirdResult.RelationshipType); + Assert.AreEqual("baz", thirdResult.Name); + Assert.AreEqual(42586, thirdResult.UniqueId); + } + } + + [Test] + public void ShouldDeserializeTableStructureWithRelationships() + { + // Arrange + const string queryText = @" + START x = node({p0}) + MATCH x-[r]->n + RETURN x AS Fooness, type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId + LIMIT 3"; + var cypherQuery = new CypherQuery( + queryText, + new Dictionary + { + {"p0", 123} + }, + CypherResultMode.Projection, + CypherResultFormat.Rest); + + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Json(HttpStatusCode.OK, @"{ + 'data' : [ [ { + 'start' : 'http://foo/db/data/node/0', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'property' : 'http://foo/db/data/relationship/0/properties/{key}', + 'self' : 'http://foo/db/data/relationship/0', + 'properties' : 'http://foo/db/data/relationship/0/properties', + 'type' : 'HAS_REFERENCE_DATA', + 'extensions' : { + }, + 'end' : 'http://foo/db/data/node/1' + }, 'HOSTS', 'foo', 44321 ], [ { + 'start' : 'http://foo/db/data/node/1', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'property' : 'http://foo/db/data/relationship/1/properties/{key}', + 'self' : 'http://foo/db/data/relationship/1', + 'properties' : 'http://foo/db/data/relationship/1/properties', + 'type' : 'HAS_REFERENCE_DATA', + 'extensions' : { + }, + 'end' : 'http://foo/db/data/node/1' + }, 'LIKES', 'bar', 44311 ], [ { + 'start' : 'http://foo/db/data/node/2', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'property' : 'http://foo/db/data/relationship/2/properties/{key}', + 'self' : 'http://foo/db/data/relationship/2', + 'properties' : 'http://foo/db/data/relationship/2/properties', + 'type' : 'HAS_REFERENCE_DATA', + 'extensions' : { + }, + 'end' : 'http://foo/db/data/node/1' + }, 'HOSTS', 'baz', 42586 ] ], + 'columns' : [ 'Fooness', 'RelationshipType', 'Name', 'UniqueId' ] + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + //Act + var results = graphClient.ExecuteGetCypherResults(cypherQuery); + + //Assert + Assert.IsInstanceOf>(results); + + var resultsArray = results.ToArray(); + Assert.AreEqual(3, resultsArray.Count()); + + var firstResult = resultsArray[0]; + Assert.AreEqual(0, firstResult.Fooness.Reference.Id); + Assert.AreEqual("bar", firstResult.Fooness.Data.Bar); + Assert.AreEqual("baz", firstResult.Fooness.Data.Baz); + Assert.AreEqual("HOSTS", firstResult.RelationshipType); + Assert.AreEqual("foo", firstResult.Name); + Assert.AreEqual(44321, firstResult.UniqueId); + + var secondResult = resultsArray[1]; + Assert.AreEqual(1, secondResult.Fooness.Reference.Id); + Assert.AreEqual("bar", secondResult.Fooness.Data.Bar); + Assert.AreEqual("baz", secondResult.Fooness.Data.Baz); + Assert.AreEqual("LIKES", secondResult.RelationshipType); + Assert.AreEqual("bar", secondResult.Name); + Assert.AreEqual(44311, secondResult.UniqueId); + + var thirdResult = resultsArray[2]; + Assert.AreEqual(2, thirdResult.Fooness.Reference.Id); + Assert.AreEqual("bar", thirdResult.Fooness.Data.Bar); + Assert.AreEqual("baz", thirdResult.Fooness.Data.Baz); + Assert.AreEqual("HOSTS", thirdResult.RelationshipType); + Assert.AreEqual("baz", thirdResult.Name); + Assert.AreEqual(42586, thirdResult.UniqueId); + } + } + + [Test] + public void ShouldPromoteBadQueryResponseToNiceException() + { + // Arrange + const string queryText = @"broken query"; + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Rest); + var cypherApiQuery = new CypherApiQuery(cypherQuery); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockResponse.Json(HttpStatusCode.BadRequest, @"{ + 'message' : 'expected START or CREATE\n\'bad query\'\n ^', + 'exception' : 'SyntaxException', + 'fullname' : 'org.neo4j.cypher.SyntaxException', + 'stacktrace' : [ 'org.neo4j.cypher.internal.parser.v1_9.CypherParserImpl.parse(CypherParserImpl.scala:45)', 'org.neo4j.cypher.CypherParser.parse(CypherParser.scala:44)', 'org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:80)', 'org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:80)', 'org.neo4j.cypher.internal.LRUCache.getOrElseUpdate(LRUCache.scala:37)', 'org.neo4j.cypher.ExecutionEngine.prepare(ExecutionEngine.scala:80)', 'org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:72)', 'org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:76)', 'org.neo4j.cypher.javacompat.ExecutionEngine.execute(ExecutionEngine.java:79)', 'org.neo4j.server.rest.web.CypherService.cypher(CypherService.java:94)', 'java.lang.reflect.Method.invoke(Unknown Source)', 'org.neo4j.server.rest.security.SecurityFilter.doFilter(SecurityFilter.java:112)' ] +}") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var ex = Assert.Throws(() => graphClient.ExecuteGetCypherResults(cypherQuery)); + Assert.AreEqual("SyntaxException: expected START or CREATE\n'bad query'\n ^", ex.Message); + Assert.AreEqual("expected START or CREATE\n'bad query'\n ^", ex.NeoMessage); + Assert.AreEqual("SyntaxException", ex.NeoExceptionName); + Assert.AreEqual("org.neo4j.cypher.SyntaxException", ex.NeoFullName); + + var expectedStack = new[] + { + "org.neo4j.cypher.internal.parser.v1_9.CypherParserImpl.parse(CypherParserImpl.scala:45)", + "org.neo4j.cypher.CypherParser.parse(CypherParser.scala:44)", + "org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:80)", + "org.neo4j.cypher.ExecutionEngine$$anonfun$prepare$1.apply(ExecutionEngine.scala:80)", + "org.neo4j.cypher.internal.LRUCache.getOrElseUpdate(LRUCache.scala:37)", + "org.neo4j.cypher.ExecutionEngine.prepare(ExecutionEngine.scala:80)", + "org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:72)", + "org.neo4j.cypher.ExecutionEngine.execute(ExecutionEngine.scala:76)", + "org.neo4j.cypher.javacompat.ExecutionEngine.execute(ExecutionEngine.java:79)", + "org.neo4j.server.rest.web.CypherService.cypher(CypherService.java:94)", + "java.lang.reflect.Method.invoke(Unknown Source)", + "org.neo4j.server.rest.security.SecurityFilter.doFilter(SecurityFilter.java:112)" + }; + CollectionAssert.AreEqual(expectedStack, ex.NeoStackTrace); + } + } + } +} diff --git a/Test/GraphClientTests/DeleteIndexTests.cs b/Neo4jClient.Tests/GraphClientTests/DeleteIndexTests.cs similarity index 96% rename from Test/GraphClientTests/DeleteIndexTests.cs rename to Neo4jClient.Tests/GraphClientTests/DeleteIndexTests.cs index 1038069fc..61eec4fb7 100644 --- a/Test/GraphClientTests/DeleteIndexTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/DeleteIndexTests.cs @@ -1,26 +1,26 @@ -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class DeleteIndexTests - { - [Test] - public void ShouldExecuteSilentlyForSuccessfulDelete() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Delete("/index/node/MyIndex"), - MockResponse.Http(204) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - graphClient.DeleteIndex("MyIndex", IndexFor.Node); - } - } - } -} +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class DeleteIndexTests + { + [Test] + public void ShouldExecuteSilentlyForSuccessfulDelete() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Delete("/index/node/MyIndex"), + MockResponse.Http(204) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + graphClient.DeleteIndex("MyIndex", IndexFor.Node); + } + } + } +} diff --git a/Test/GraphClientTests/DeleteNodeTests.cs b/Neo4jClient.Tests/GraphClientTests/DeleteNodeTests.cs similarity index 97% rename from Test/GraphClientTests/DeleteNodeTests.cs rename to Neo4jClient.Tests/GraphClientTests/DeleteNodeTests.cs index dc2b94395..e7394c9b2 100644 --- a/Test/GraphClientTests/DeleteNodeTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/DeleteNodeTests.cs @@ -1,97 +1,97 @@ -using System; -using System.Net; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class DeleteNodeTests - { - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - client.Delete(123, DeleteMode.NodeOnly); - } - - [Test] - public void ShouldDeleteNodeOnly() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Delete("/node/456"), - MockResponse.Http(204) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.Delete(456, DeleteMode.NodeOnly); - } - } - - [Test] - public void ShouldDeleteAllRelationshipsFirst() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/456/relationships/all"), - MockResponse.Json(HttpStatusCode.OK, - @"[ - { 'self': 'http://foo/db/data/relationship/56', - 'start': 'http://foo/db/data/node/123', - 'end': 'http://foo/db/data/node/456', - 'type': 'KNOWS', - 'properties': 'http://foo/db/data/relationship/56/properties', - 'property': 'http://foo/db/data/relationship/56/properties/{key}', - 'data': { 'date': 1270559208258 } - }, - { 'self': 'http://foo/db/data/relationship/78', - 'start': 'http://foo/db/data/node/456', - 'end': 'http://foo/db/data/node/789', - 'type': 'KNOWS', - 'properties': 'http://foo/db/data/relationship/78/properties', - 'property': 'http://foo/db/data/relationship/78/properties/{key}', - 'data': { 'date': 1270559208258 } - } - ]") - }, - { - MockRequest.Delete("/relationship/56"), - MockResponse.Http(204) - }, - { - MockRequest.Delete("/relationship/78"), - MockResponse.Http(204) - }, - { - MockRequest.Delete("/node/456"), - MockResponse.Http(204) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.Delete(456, DeleteMode.NodeAndRelationships); - } - } - - [Test] - [ExpectedException(typeof(ApplicationException), ExpectedMessage = "Unable to delete the node. The node may still have relationships. The response status was: 409 Conflict")] - public void ShouldThrowApplicationExceptionWhenDeleteFails() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Delete("/node/456"), - MockResponse.Http(409) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.Delete(456, DeleteMode.NodeOnly); - } - } - } -} +using System; +using System.Net; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class DeleteNodeTests + { + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void ShouldThrowInvalidOperationExceptionIfNotConnected() + { + var client = new GraphClient(new Uri("http://foo")); + client.Delete(123, DeleteMode.NodeOnly); + } + + [Test] + public void ShouldDeleteNodeOnly() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Delete("/node/456"), + MockResponse.Http(204) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + graphClient.Delete(456, DeleteMode.NodeOnly); + } + } + + [Test] + public void ShouldDeleteAllRelationshipsFirst() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/456/relationships/all"), + MockResponse.Json(HttpStatusCode.OK, + @"[ + { 'self': 'http://foo/db/data/relationship/56', + 'start': 'http://foo/db/data/node/123', + 'end': 'http://foo/db/data/node/456', + 'type': 'KNOWS', + 'properties': 'http://foo/db/data/relationship/56/properties', + 'property': 'http://foo/db/data/relationship/56/properties/{key}', + 'data': { 'date': 1270559208258 } + }, + { 'self': 'http://foo/db/data/relationship/78', + 'start': 'http://foo/db/data/node/456', + 'end': 'http://foo/db/data/node/789', + 'type': 'KNOWS', + 'properties': 'http://foo/db/data/relationship/78/properties', + 'property': 'http://foo/db/data/relationship/78/properties/{key}', + 'data': { 'date': 1270559208258 } + } + ]") + }, + { + MockRequest.Delete("/relationship/56"), + MockResponse.Http(204) + }, + { + MockRequest.Delete("/relationship/78"), + MockResponse.Http(204) + }, + { + MockRequest.Delete("/node/456"), + MockResponse.Http(204) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + graphClient.Delete(456, DeleteMode.NodeAndRelationships); + } + } + + [Test] + [ExpectedException(typeof(ApplicationException), ExpectedMessage = "Unable to delete the node. The node may still have relationships. The response status was: 409 Conflict")] + public void ShouldThrowApplicationExceptionWhenDeleteFails() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Delete("/node/456"), + MockResponse.Http(409) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + graphClient.Delete(456, DeleteMode.NodeOnly); + } + } + } +} diff --git a/Test/GraphClientTests/DeleteRelationshipTests.cs b/Neo4jClient.Tests/GraphClientTests/DeleteRelationshipTests.cs similarity index 96% rename from Test/GraphClientTests/DeleteRelationshipTests.cs rename to Neo4jClient.Tests/GraphClientTests/DeleteRelationshipTests.cs index 7bb8950da..838faaac6 100644 --- a/Test/GraphClientTests/DeleteRelationshipTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/DeleteRelationshipTests.cs @@ -1,50 +1,50 @@ -using System; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class DeleteRelationshipTests - { - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - client.DeleteRelationship(123); - } - - [Test] - public void ShouldDeleteRelationship() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Delete("/relationship/456"), - MockResponse.Http(204) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.DeleteRelationship(456); - } - } - - [Test] - [ExpectedException(typeof(ApplicationException), ExpectedMessage = "Unable to delete the relationship. The response status was: 404 NotFound")] - public void ShouldThrowApplicationExceptionWhenDeleteFails() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Delete("/relationship/456"), - MockResponse.Http(404) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.DeleteRelationship(456); - } - } - } -} +using System; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class DeleteRelationshipTests + { + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void ShouldThrowInvalidOperationExceptionIfNotConnected() + { + var client = new GraphClient(new Uri("http://foo")); + client.DeleteRelationship(123); + } + + [Test] + public void ShouldDeleteRelationship() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Delete("/relationship/456"), + MockResponse.Http(204) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + graphClient.DeleteRelationship(456); + } + } + + [Test] + [ExpectedException(typeof(ApplicationException), ExpectedMessage = "Unable to delete the relationship. The response status was: 404 NotFound")] + public void ShouldThrowApplicationExceptionWhenDeleteFails() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Delete("/relationship/456"), + MockResponse.Http(404) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + graphClient.DeleteRelationship(456); + } + } + } +} diff --git a/Test/GraphClientTests/GetIndexesTests.cs b/Neo4jClient.Tests/GraphClientTests/GetIndexesTests.cs similarity index 97% rename from Test/GraphClientTests/GetIndexesTests.cs rename to Neo4jClient.Tests/GraphClientTests/GetIndexesTests.cs index b5b875f74..10cf8d261 100644 --- a/Test/GraphClientTests/GetIndexesTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/GetIndexesTests.cs @@ -1,74 +1,74 @@ -using System.Linq; -using System.Net; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class GetIndexesTests - { - [Test] - public void ShouldReturnNodeIndexes() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/index/node"), - MockResponse.Json(HttpStatusCode.OK, - @"{ - 'agency24871-clients' : { - 'to_lower_case' : 'true', - 'template' : 'http://localhost:5102/db/data/index/node/agency24871-clients/{key}/{value}', - '_blueprints:type' : 'MANUAL', - 'provider' : 'lucene', - 'type' : 'fulltext' - }, - 'agency36681-clients' : { - 'to_lower_case' : 'false', - 'template' : 'http://localhost:5102/db/data/index/node/agency36681-clients/{key}/{value}', - '_blueprints:type' : 'MANUAL', - 'provider' : 'lucene', - 'type' : 'exact' - } - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var indexes = graphClient.GetIndexes(IndexFor.Node); - Assert.AreEqual(2, indexes.Count()); - - var index = indexes.ElementAt(0); - Assert.AreEqual("agency24871-clients", index.Key); - Assert.AreEqual(true, index.Value.ToLowerCase); - Assert.AreEqual("http://localhost:5102/db/data/index/node/agency24871-clients/{key}/{value}", index.Value.Template); - Assert.AreEqual("lucene", index.Value.Provider); - Assert.AreEqual("fulltext", index.Value.Type); - - index = indexes.ElementAt(1); - Assert.AreEqual("agency36681-clients", index.Key); - Assert.AreEqual(false, index.Value.ToLowerCase); - Assert.AreEqual("http://localhost:5102/db/data/index/node/agency36681-clients/{key}/{value}", index.Value.Template); - Assert.AreEqual("lucene", index.Value.Provider); - Assert.AreEqual("exact", index.Value.Type); - } - } - - [Test] - public void ShouldReturnEmptyDictionaryOfIndexesForHttpResponse204() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/index/node"), - MockResponse.Http(204) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var indexes = graphClient.GetIndexes(IndexFor.Node); - Assert.IsFalse(indexes.Any()); - } - } - } -} +using System.Linq; +using System.Net; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class GetIndexesTests + { + [Test] + public void ShouldReturnNodeIndexes() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/index/node"), + MockResponse.Json(HttpStatusCode.OK, + @"{ + 'agency24871-clients' : { + 'to_lower_case' : 'true', + 'template' : 'http://localhost:5102/db/data/index/node/agency24871-clients/{key}/{value}', + '_blueprints:type' : 'MANUAL', + 'provider' : 'lucene', + 'type' : 'fulltext' + }, + 'agency36681-clients' : { + 'to_lower_case' : 'false', + 'template' : 'http://localhost:5102/db/data/index/node/agency36681-clients/{key}/{value}', + '_blueprints:type' : 'MANUAL', + 'provider' : 'lucene', + 'type' : 'exact' + } + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + var indexes = graphClient.GetIndexes(IndexFor.Node); + Assert.AreEqual(2, indexes.Count()); + + var index = indexes.ElementAt(0); + Assert.AreEqual("agency24871-clients", index.Key); + Assert.AreEqual(true, index.Value.ToLowerCase); + Assert.AreEqual("http://localhost:5102/db/data/index/node/agency24871-clients/{key}/{value}", index.Value.Template); + Assert.AreEqual("lucene", index.Value.Provider); + Assert.AreEqual("fulltext", index.Value.Type); + + index = indexes.ElementAt(1); + Assert.AreEqual("agency36681-clients", index.Key); + Assert.AreEqual(false, index.Value.ToLowerCase); + Assert.AreEqual("http://localhost:5102/db/data/index/node/agency36681-clients/{key}/{value}", index.Value.Template); + Assert.AreEqual("lucene", index.Value.Provider); + Assert.AreEqual("exact", index.Value.Type); + } + } + + [Test] + public void ShouldReturnEmptyDictionaryOfIndexesForHttpResponse204() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/index/node"), + MockResponse.Http(204) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + var indexes = graphClient.GetIndexes(IndexFor.Node); + Assert.IsFalse(indexes.Any()); + } + } + } +} diff --git a/Test/GraphClientTests/GetNodeTests.cs b/Neo4jClient.Tests/GraphClientTests/GetNodeTests.cs similarity index 97% rename from Test/GraphClientTests/GetNodeTests.cs rename to Neo4jClient.Tests/GraphClientTests/GetNodeTests.cs index 42e64ba4d..6918c9aae 100644 --- a/Test/GraphClientTests/GetNodeTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/GetNodeTests.cs @@ -1,232 +1,232 @@ -using System; -using System.Net; -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class GetNodeTests - { - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - client.Get((NodeReference)123); - } - - [Test] - public void ShouldReturnNodeData() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/456"), - MockResponse.Json(HttpStatusCode.OK, - @"{ 'self': 'http://foo/db/data/node/456', - 'data': { 'Foo': 'foo', - 'Bar': 'bar', - 'Baz': 'baz' - }, - 'create_relationship': 'http://foo/db/data/node/456/relationships', - 'all_relationships': 'http://foo/db/data/node/456/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/456/properties', - 'property': 'http://foo/db/data/node/456/property/{key}', - 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var node = graphClient.Get((NodeReference)456); - - Assert.AreEqual(456, node.Reference.Id); - Assert.AreEqual("foo", node.Data.Foo); - Assert.AreEqual("bar", node.Data.Bar); - Assert.AreEqual("baz", node.Data.Baz); - } - } - - [Test] - public void ShouldReturnNodeDataForLongId() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/21484836470"), - MockResponse.Json(HttpStatusCode.OK, - @"{ 'self': 'http://foo/db/data/node/21484836470', - 'data': { 'Foo': 'foo', - 'Bar': 'bar', - 'Baz': 'baz' - }, - 'create_relationship': 'http://foo/db/data/node/21484836470/relationships', - 'all_relationships': 'http://foo/db/data/node/21484836470/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/21484836470/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/21484836470/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/21484836470/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/21484836470/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/21484836470/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/21484836470/properties', - 'property': 'http://foo/db/data/node/21484836470/property/{key}', - 'traverse': 'http://foo/db/data/node/21484836470/traverse/{returnType}' - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var node = graphClient.Get((NodeReference)21484836470); - - Assert.AreEqual(21484836470, node.Reference.Id); - Assert.AreEqual("foo", node.Data.Foo); - Assert.AreEqual("bar", node.Data.Bar); - Assert.AreEqual("baz", node.Data.Baz); - } - } - - [Test] - public void ShouldReturnNodeDataAndDeserializeToEnumType() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/456"), - MockResponse.Json(HttpStatusCode.OK, - @"{ 'self': 'http://foo/db/data/node/456', - 'data': { 'Foo': 'foo', - 'Status': 'Value1' - }, - 'create_relationship': 'http://foo/db/data/node/456/relationships', - 'all_relationships': 'http://foo/db/data/node/456/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/456/properties', - 'property': 'http://foo/db/data/node/456/property/{key}', - 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var node = graphClient.Get((NodeReference)456); - - Assert.AreEqual(456, node.Reference.Id); - Assert.AreEqual("foo", node.Data.Foo); - Assert.AreEqual(TestEnum.Value1, node.Data.Status); - } - } - - [Test] - public void ShouldReturnNodeWithReferenceBackToClient() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/456"), - MockResponse.Json(HttpStatusCode.OK, - @"{ 'self': 'http://foo/db/data/node/456', - 'data': { 'Foo': 'foo', - 'Bar': 'bar', - 'Baz': 'baz' - }, - 'create_relationship': 'http://foo/db/data/node/456/relationships', - 'all_relationships': 'http://foo/db/data/node/456/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/456/properties', - 'property': 'http://foo/db/data/node/456/property/{key}', - 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var node = graphClient.Get((NodeReference)456); - - Assert.AreEqual(graphClient, ((IGremlinQuery) node.Reference).Client); - } - } - - [Test] - public void ShouldReturnNullWhenNodeDoesntExist() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/456"), - MockResponse.Http(404) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var node = graphClient.Get((NodeReference)456); - - Assert.IsNull(node); - } - } - - [Test] - public void ShouldReturnNodeDataAndDeserialzedJsonDatesForDateTimeOffsetNullableType() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/456"), - MockResponse.Json(HttpStatusCode.OK, - @"{ 'self': 'http://foo/db/data/node/456', - 'data': { 'DateOffSet': '/Date(1309421746929+0000)/' }, - 'create_relationship': 'http://foo/db/data/node/456/relationships', - 'all_relationships': 'http://foo/db/data/node/456/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/456/properties', - 'property': 'http://foo/db/data/node/456/property/{key}', - 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' - }") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var node = graphClient.Get((NodeReference)456); - - Assert.IsNotNull(node.Data.DateOffSet); - Assert.AreEqual("2011-06-30 08:15:46Z", node.Data.DateOffSet.Value.ToString("u")); - } - } - - public class TestNode - { - public string Foo { get; set; } - public string Bar { get; set; } - public string Baz { get; set; } - public DateTimeOffset? DateOffSet { get; set; } - } - - public class TestNodeWithEnum - { - public string Foo { get; set; } - public TestEnum Status { get; set; } - } - - public enum TestEnum - { - Value1, - Value2 - } - } -} +using System; +using System.Net; +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class GetNodeTests + { + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void ShouldThrowInvalidOperationExceptionIfNotConnected() + { + var client = new GraphClient(new Uri("http://foo")); + client.Get((NodeReference)123); + } + + [Test] + public void ShouldReturnNodeData() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/456"), + MockResponse.Json(HttpStatusCode.OK, + @"{ 'self': 'http://foo/db/data/node/456', + 'data': { 'Foo': 'foo', + 'Bar': 'bar', + 'Baz': 'baz' + }, + 'create_relationship': 'http://foo/db/data/node/456/relationships', + 'all_relationships': 'http://foo/db/data/node/456/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/456/properties', + 'property': 'http://foo/db/data/node/456/property/{key}', + 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + var node = graphClient.Get((NodeReference)456); + + Assert.AreEqual(456, node.Reference.Id); + Assert.AreEqual("foo", node.Data.Foo); + Assert.AreEqual("bar", node.Data.Bar); + Assert.AreEqual("baz", node.Data.Baz); + } + } + + [Test] + public void ShouldReturnNodeDataForLongId() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/21484836470"), + MockResponse.Json(HttpStatusCode.OK, + @"{ 'self': 'http://foo/db/data/node/21484836470', + 'data': { 'Foo': 'foo', + 'Bar': 'bar', + 'Baz': 'baz' + }, + 'create_relationship': 'http://foo/db/data/node/21484836470/relationships', + 'all_relationships': 'http://foo/db/data/node/21484836470/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/21484836470/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/21484836470/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/21484836470/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/21484836470/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/21484836470/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/21484836470/properties', + 'property': 'http://foo/db/data/node/21484836470/property/{key}', + 'traverse': 'http://foo/db/data/node/21484836470/traverse/{returnType}' + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + var node = graphClient.Get((NodeReference)21484836470); + + Assert.AreEqual(21484836470, node.Reference.Id); + Assert.AreEqual("foo", node.Data.Foo); + Assert.AreEqual("bar", node.Data.Bar); + Assert.AreEqual("baz", node.Data.Baz); + } + } + + [Test] + public void ShouldReturnNodeDataAndDeserializeToEnumType() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/456"), + MockResponse.Json(HttpStatusCode.OK, + @"{ 'self': 'http://foo/db/data/node/456', + 'data': { 'Foo': 'foo', + 'Status': 'Value1' + }, + 'create_relationship': 'http://foo/db/data/node/456/relationships', + 'all_relationships': 'http://foo/db/data/node/456/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/456/properties', + 'property': 'http://foo/db/data/node/456/property/{key}', + 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + var node = graphClient.Get((NodeReference)456); + + Assert.AreEqual(456, node.Reference.Id); + Assert.AreEqual("foo", node.Data.Foo); + Assert.AreEqual(TestEnum.Value1, node.Data.Status); + } + } + + [Test] + public void ShouldReturnNodeWithReferenceBackToClient() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/456"), + MockResponse.Json(HttpStatusCode.OK, + @"{ 'self': 'http://foo/db/data/node/456', + 'data': { 'Foo': 'foo', + 'Bar': 'bar', + 'Baz': 'baz' + }, + 'create_relationship': 'http://foo/db/data/node/456/relationships', + 'all_relationships': 'http://foo/db/data/node/456/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/456/properties', + 'property': 'http://foo/db/data/node/456/property/{key}', + 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + var node = graphClient.Get((NodeReference)456); + + Assert.AreEqual(graphClient, ((IGremlinQuery) node.Reference).Client); + } + } + + [Test] + public void ShouldReturnNullWhenNodeDoesntExist() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/456"), + MockResponse.Http(404) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + var node = graphClient.Get((NodeReference)456); + + Assert.IsNull(node); + } + } + + [Test] + public void ShouldReturnNodeDataAndDeserialzedJsonDatesForDateTimeOffsetNullableType() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/456"), + MockResponse.Json(HttpStatusCode.OK, + @"{ 'self': 'http://foo/db/data/node/456', + 'data': { 'DateOffSet': '/Date(1309421746929+0000)/' }, + 'create_relationship': 'http://foo/db/data/node/456/relationships', + 'all_relationships': 'http://foo/db/data/node/456/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/456/properties', + 'property': 'http://foo/db/data/node/456/property/{key}', + 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' + }") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + var node = graphClient.Get((NodeReference)456); + + Assert.IsNotNull(node.Data.DateOffSet); + Assert.AreEqual("2011-06-30 08:15:46Z", node.Data.DateOffSet.Value.ToString("u")); + } + } + + public class TestNode + { + public string Foo { get; set; } + public string Bar { get; set; } + public string Baz { get; set; } + public DateTimeOffset? DateOffSet { get; set; } + } + + public class TestNodeWithEnum + { + public string Foo { get; set; } + public TestEnum Status { get; set; } + } + + public enum TestEnum + { + Value1, + Value2 + } + } +} diff --git a/Test/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs b/Neo4jClient.Tests/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs similarity index 97% rename from Test/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs rename to Neo4jClient.Tests/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs index 71a75a9c2..139aae6c6 100644 --- a/Test/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs @@ -1,149 +1,149 @@ -using System; -using System.Collections.Generic; -using System.Net; -using NUnit.Framework; -using System.Linq; - -namespace Neo4jClient.Test.GraphClientTests.Gremlin -{ - [TestFixture] - public class ExecuteGetAllNodesGremlinTests - { - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - client.ExecuteGetAllNodesGremlin("", null); - } - - public class Foo - { - public string Bar { get; set; } - public string Baz { get; set; } - } - - [Test] - public void ShouldReturnIEnumerableOfObjects() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostJson( - "/ext/GremlinPlugin/graphdb/execute_script", - @"{ - 'script': 'foo bar query', - 'params': { 'foo': 123, 'bar': 'baz' } - }" - ), - MockResponse.Json(HttpStatusCode.OK, - @"[ { - 'outgoing_relationships' : 'http://foo/db/data/node/5/relationships/out', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'traverse' : 'http://foo/db/data/node/5/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/5/relationships/all/{-list|&|types}', - 'property' : 'http://foo/db/data/node/5/properties/{key}', - 'self' : 'http://foo/db/data/node/5', - 'properties' : 'http://foo/db/data/node/5/properties', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/5/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://foo/db/data/node/5/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/5/relationships', - 'all_relationships' : 'http://foo/db/data/node/5/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/5/relationships/in/{-list|&|types}' - }, { - 'outgoing_relationships' : 'http://foo/db/data/node/6/relationships/out', - 'data' : { - 'Bar' : '123', - 'Baz' : '456' - }, - 'traverse' : 'http://foo/db/data/node/6/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/6/relationships/all/{-list|&|types}', - 'property' : 'http://foo/db/data/node/6/properties/{key}', - 'self' : 'http://foo/db/data/node/6', - 'properties' : 'http://foo/db/data/node/6/properties', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/6/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://foo/db/data/node/6/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/6/relationships', - 'all_relationships' : 'http://foo/db/data/node/6/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/6/relationships/in/{-list|&|types}' - } ]" - ) - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - //Act - var parameters = new Dictionary - { - {"foo", 123}, - {"bar", "baz"} - }; - var nodes = graphClient - .ExecuteGetAllNodesGremlin("foo bar query", parameters) - .ToList(); - - //Assert - Assert.AreEqual(2, nodes.Count()); - Assert.AreEqual(5, nodes.ElementAt(0).Reference.Id); - Assert.AreEqual("bar", nodes.ElementAt(0).Data.Bar); - Assert.AreEqual("baz", nodes.ElementAt(0).Data.Baz); - Assert.AreEqual(6, nodes.ElementAt(1).Reference.Id); - Assert.AreEqual("123", nodes.ElementAt(1).Data.Bar); - Assert.AreEqual("456", nodes.ElementAt(1).Data.Baz); - } - } - - [Test] - public void ShouldReturnEmptyEnumerableForNullResult() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostJson( - "/ext/GremlinPlugin/graphdb/execute_script", - @"{ 'script': 'foo bar query', 'params': {} }" - ), - MockResponse.Json(HttpStatusCode.OK, @"[]") - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - //Act - var nodes = graphClient - .ExecuteGetAllNodesGremlin("foo bar query", null) - .ToList(); - - //Assert - Assert.AreEqual(0, nodes.Count()); - } - } - - [Test] - public void ShouldFailGracefullyWhenGremlinIsNotAvailable() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get(""), - MockResponse.NeoRoot20() - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - var ex = Assert.Throws( - () => graphClient.ExecuteGetAllNodesGremlin("foo bar query", null)); - Assert.AreEqual(GraphClient.GremlinPluginUnavailable, ex.Message); - } - } - } +using System; +using System.Collections.Generic; +using System.Net; +using NUnit.Framework; +using System.Linq; + +namespace Neo4jClient.Test.GraphClientTests.Gremlin +{ + [TestFixture] + public class ExecuteGetAllNodesGremlinTests + { + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void ShouldThrowInvalidOperationExceptionIfNotConnected() + { + var client = new GraphClient(new Uri("http://foo")); + client.ExecuteGetAllNodesGremlin("", null); + } + + public class Foo + { + public string Bar { get; set; } + public string Baz { get; set; } + } + + [Test] + public void ShouldReturnIEnumerableOfObjects() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostJson( + "/ext/GremlinPlugin/graphdb/execute_script", + @"{ + 'script': 'foo bar query', + 'params': { 'foo': 123, 'bar': 'baz' } + }" + ), + MockResponse.Json(HttpStatusCode.OK, + @"[ { + 'outgoing_relationships' : 'http://foo/db/data/node/5/relationships/out', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'traverse' : 'http://foo/db/data/node/5/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/5/relationships/all/{-list|&|types}', + 'property' : 'http://foo/db/data/node/5/properties/{key}', + 'self' : 'http://foo/db/data/node/5', + 'properties' : 'http://foo/db/data/node/5/properties', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/5/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://foo/db/data/node/5/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/5/relationships', + 'all_relationships' : 'http://foo/db/data/node/5/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/5/relationships/in/{-list|&|types}' + }, { + 'outgoing_relationships' : 'http://foo/db/data/node/6/relationships/out', + 'data' : { + 'Bar' : '123', + 'Baz' : '456' + }, + 'traverse' : 'http://foo/db/data/node/6/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/6/relationships/all/{-list|&|types}', + 'property' : 'http://foo/db/data/node/6/properties/{key}', + 'self' : 'http://foo/db/data/node/6', + 'properties' : 'http://foo/db/data/node/6/properties', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/6/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://foo/db/data/node/6/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/6/relationships', + 'all_relationships' : 'http://foo/db/data/node/6/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/6/relationships/in/{-list|&|types}' + } ]" + ) + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + //Act + var parameters = new Dictionary + { + {"foo", 123}, + {"bar", "baz"} + }; + var nodes = graphClient + .ExecuteGetAllNodesGremlin("foo bar query", parameters) + .ToList(); + + //Assert + Assert.AreEqual(2, nodes.Count()); + Assert.AreEqual(5, nodes.ElementAt(0).Reference.Id); + Assert.AreEqual("bar", nodes.ElementAt(0).Data.Bar); + Assert.AreEqual("baz", nodes.ElementAt(0).Data.Baz); + Assert.AreEqual(6, nodes.ElementAt(1).Reference.Id); + Assert.AreEqual("123", nodes.ElementAt(1).Data.Bar); + Assert.AreEqual("456", nodes.ElementAt(1).Data.Baz); + } + } + + [Test] + public void ShouldReturnEmptyEnumerableForNullResult() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostJson( + "/ext/GremlinPlugin/graphdb/execute_script", + @"{ 'script': 'foo bar query', 'params': {} }" + ), + MockResponse.Json(HttpStatusCode.OK, @"[]") + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + //Act + var nodes = graphClient + .ExecuteGetAllNodesGremlin("foo bar query", null) + .ToList(); + + //Assert + Assert.AreEqual(0, nodes.Count()); + } + } + + [Test] + public void ShouldFailGracefullyWhenGremlinIsNotAvailable() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get(""), + MockResponse.NeoRoot20() + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + var ex = Assert.Throws( + () => graphClient.ExecuteGetAllNodesGremlin("foo bar query", null)); + Assert.AreEqual(GraphClient.GremlinPluginUnavailable, ex.Message); + } + } + } } \ No newline at end of file diff --git a/Test/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs b/Neo4jClient.Tests/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs similarity index 97% rename from Test/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs rename to Neo4jClient.Tests/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs index ee1835b0c..09b0f0801 100644 --- a/Test/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs @@ -1,287 +1,287 @@ -using System; -using System.Net; -using NUnit.Framework; -using System.Linq; -using Neo4jClient.ApiModels.Gremlin; - -namespace Neo4jClient.Test.GraphClientTests.Gremlin -{ - [TestFixture] - public class ExecuteGetAllRelationshipsGremlinTests - { - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - client.ExecuteGetAllRelationshipsGremlin("", null); - } - - [Test] - public void ShouldReturnListOfRelationshipInstances() - { - //Arrange - const string gremlinQueryExpected = "foo bar query"; - var query = new GremlinApiQuery(gremlinQueryExpected, null); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), - MockResponse.Json(HttpStatusCode.OK, @"[ { - 'start' : 'http://127.0.0.1:5118/db/data/node/123', - 'data' : { - }, - 'self' : 'http://127.0.0.1:5118/db/data/relationship/456', - 'property' : 'http://127.0.0.1:5118/db/data/relationship/456/properties/{key}', - 'properties' : 'http://127.0.0.1:5118/db/data/relationship/456/properties', - 'type' : 'KNOWS', - 'extensions' : { - }, - 'end' : 'http://127.0.0.1:5118/db/data/node/789' - } ]") - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - //Act - var relationships = graphClient - .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) - .ToList(); - - //Assert - Assert.AreEqual(1, relationships.Count()); - Assert.AreEqual(456, relationships.ElementAt(0).Reference.Id); - Assert.AreEqual(123, relationships.ElementAt(0).StartNodeReference.Id); - Assert.AreEqual(789, relationships.ElementAt(0).EndNodeReference.Id); - Assert.AreEqual("KNOWS", relationships.ElementAt(0).TypeKey); - } - } - - [Test] - public void ShouldReturnListOfRelationshipInstancesWithPayloads() - { - //Arrange - const string gremlinQueryExpected = "foo bar query"; - var query = new GremlinApiQuery(gremlinQueryExpected, null); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), - MockResponse.Json(HttpStatusCode.OK, @"[ { - 'start' : 'http://127.0.0.1:5118/db/data/node/123', - 'data' : { - 'Foo': 'Foo', - 'Bar': 'Bar' - }, - 'self' : 'http://127.0.0.1:5118/db/data/relationship/456', - 'property' : 'http://127.0.0.1:5118/db/data/relationship/456/properties/{key}', - 'properties' : 'http://127.0.0.1:5118/db/data/relationship/456/properties', - 'type' : 'KNOWS', - 'extensions' : { - }, - 'end' : 'http://127.0.0.1:5118/db/data/node/789' - } ]") - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - //Act - var relationships = graphClient - .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) - .ToList(); - - //Assert - Assert.AreEqual(1, relationships.Count()); - Assert.AreEqual(456, relationships.ElementAt(0).Reference.Id); - Assert.AreEqual(123, relationships.ElementAt(0).StartNodeReference.Id); - Assert.AreEqual(789, relationships.ElementAt(0).EndNodeReference.Id); - Assert.AreEqual("KNOWS", relationships.ElementAt(0).TypeKey); - Assert.AreEqual("Foo", relationships.ElementAt(0).Data.Foo); - Assert.AreEqual("Bar", relationships.ElementAt(0).Data.Bar); - } - } - - class TestPayload - { - public string Foo { get; set; } - public string Bar { get; set; } - } - - [Test] - public void ShouldReturnEmptyEnumerableForNullResult() - { - //Arrange - const string gremlinQueryExpected = "foo bar query"; - var query = new GremlinApiQuery(gremlinQueryExpected, null); - - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), - MockResponse.Json(HttpStatusCode.OK, @"[]") - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - //Act - var nodes = graphClient - .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) - .ToList(); - - //Assert - Assert.AreEqual(0, nodes.Count()); - } - } - - [Test] - public void ShouldReturnListOfRelationshipInstancesWithLongRelationshipId() - { - //Arrange - const string gremlinQueryExpected = "foo bar query"; - var query = new GremlinApiQuery(gremlinQueryExpected, null); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), - MockResponse.Json(HttpStatusCode.OK, @"[ { - 'start' : 'http://127.0.0.1:5118/db/data/node/123', - 'data' : { - }, - 'self' : 'http://127.0.0.1:5118/db/data/relationship/21484836470', - 'property' : 'http://127.0.0.1:5118/db/data/relationship/21484836470/properties/{key}', - 'properties' : 'http://127.0.0.1:5118/db/data/relationship/21484836470/properties', - 'type' : 'KNOWS', - 'extensions' : { - }, - 'end' : 'http://127.0.0.1:5118/db/data/node/789' - } ]") - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - //Act - var relationships = graphClient - .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) - .ToList(); - - //Assert - Assert.AreEqual(1, relationships.Count()); - Assert.AreEqual(21484836470, relationships.ElementAt(0).Reference.Id); - Assert.AreEqual(123, relationships.ElementAt(0).StartNodeReference.Id); - Assert.AreEqual(789, relationships.ElementAt(0).EndNodeReference.Id); - Assert.AreEqual("KNOWS", relationships.ElementAt(0).TypeKey); - } - } - - [Test] - public void ShouldReturnListOfRelationshipInstancesWithLongStartNodeId() - { - //Arrange - const string gremlinQueryExpected = "foo bar query"; - var query = new GremlinApiQuery(gremlinQueryExpected, null); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), - MockResponse.Json(HttpStatusCode.OK, @"[ { - 'start' : 'http://127.0.0.1:5118/db/data/node/21484836470', - 'data' : { - }, - 'self' : 'http://127.0.0.1:5118/db/data/relationship/456', - 'property' : 'http://127.0.0.1:5118/db/data/relationship/456/properties/{key}', - 'properties' : 'http://127.0.0.1:5118/db/data/relationship/456/properties', - 'type' : 'KNOWS', - 'extensions' : { - }, - 'end' : 'http://127.0.0.1:5118/db/data/node/789' - } ]") - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - //Act - var relationships = graphClient - .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) - .ToList(); - - //Assert - Assert.AreEqual(1, relationships.Count()); - Assert.AreEqual(456, relationships.ElementAt(0).Reference.Id); - Assert.AreEqual(21484836470, relationships.ElementAt(0).StartNodeReference.Id); - Assert.AreEqual(789, relationships.ElementAt(0).EndNodeReference.Id); - Assert.AreEqual("KNOWS", relationships.ElementAt(0).TypeKey); - } - } - - [Test] - public void ShouldReturnListOfRelationshipInstancesWithLongEndNodeId() - { - //Arrange - const string gremlinQueryExpected = "foo bar query"; - var query = new GremlinApiQuery(gremlinQueryExpected, null); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), - MockResponse.Json(HttpStatusCode.OK, @"[ { - 'start' : 'http://127.0.0.1:5118/db/data/node/123', - 'data' : { - }, - 'self' : 'http://127.0.0.1:5118/db/data/relationship/456', - 'property' : 'http://127.0.0.1:5118/db/data/relationship/456/properties/{key}', - 'properties' : 'http://127.0.0.1:5118/db/data/relationship/456/properties', - 'type' : 'KNOWS', - 'extensions' : { - }, - 'end' : 'http://127.0.0.1:5118/db/data/node/21484836470' - } ]") - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - //Act - var relationships = graphClient - .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) - .ToList(); - - //Assert - Assert.AreEqual(1, relationships.Count()); - Assert.AreEqual(456, relationships.ElementAt(0).Reference.Id); - Assert.AreEqual(123, relationships.ElementAt(0).StartNodeReference.Id); - Assert.AreEqual(21484836470, relationships.ElementAt(0).EndNodeReference.Id); - Assert.AreEqual("KNOWS", relationships.ElementAt(0).TypeKey); - } - } - - [Test] - public void ShouldFailGracefullyWhenGremlinIsNotAvailable() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get(""), - MockResponse.NeoRoot20() - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - var ex = Assert.Throws( - () => graphClient.ExecuteGetAllRelationshipsGremlin("foo bar query", null)); - Assert.AreEqual(GraphClient.GremlinPluginUnavailable, ex.Message); - } - } - } +using System; +using System.Net; +using NUnit.Framework; +using System.Linq; +using Neo4jClient.ApiModels.Gremlin; + +namespace Neo4jClient.Test.GraphClientTests.Gremlin +{ + [TestFixture] + public class ExecuteGetAllRelationshipsGremlinTests + { + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void ShouldThrowInvalidOperationExceptionIfNotConnected() + { + var client = new GraphClient(new Uri("http://foo")); + client.ExecuteGetAllRelationshipsGremlin("", null); + } + + [Test] + public void ShouldReturnListOfRelationshipInstances() + { + //Arrange + const string gremlinQueryExpected = "foo bar query"; + var query = new GremlinApiQuery(gremlinQueryExpected, null); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), + MockResponse.Json(HttpStatusCode.OK, @"[ { + 'start' : 'http://127.0.0.1:5118/db/data/node/123', + 'data' : { + }, + 'self' : 'http://127.0.0.1:5118/db/data/relationship/456', + 'property' : 'http://127.0.0.1:5118/db/data/relationship/456/properties/{key}', + 'properties' : 'http://127.0.0.1:5118/db/data/relationship/456/properties', + 'type' : 'KNOWS', + 'extensions' : { + }, + 'end' : 'http://127.0.0.1:5118/db/data/node/789' + } ]") + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + //Act + var relationships = graphClient + .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) + .ToList(); + + //Assert + Assert.AreEqual(1, relationships.Count()); + Assert.AreEqual(456, relationships.ElementAt(0).Reference.Id); + Assert.AreEqual(123, relationships.ElementAt(0).StartNodeReference.Id); + Assert.AreEqual(789, relationships.ElementAt(0).EndNodeReference.Id); + Assert.AreEqual("KNOWS", relationships.ElementAt(0).TypeKey); + } + } + + [Test] + public void ShouldReturnListOfRelationshipInstancesWithPayloads() + { + //Arrange + const string gremlinQueryExpected = "foo bar query"; + var query = new GremlinApiQuery(gremlinQueryExpected, null); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), + MockResponse.Json(HttpStatusCode.OK, @"[ { + 'start' : 'http://127.0.0.1:5118/db/data/node/123', + 'data' : { + 'Foo': 'Foo', + 'Bar': 'Bar' + }, + 'self' : 'http://127.0.0.1:5118/db/data/relationship/456', + 'property' : 'http://127.0.0.1:5118/db/data/relationship/456/properties/{key}', + 'properties' : 'http://127.0.0.1:5118/db/data/relationship/456/properties', + 'type' : 'KNOWS', + 'extensions' : { + }, + 'end' : 'http://127.0.0.1:5118/db/data/node/789' + } ]") + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + //Act + var relationships = graphClient + .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) + .ToList(); + + //Assert + Assert.AreEqual(1, relationships.Count()); + Assert.AreEqual(456, relationships.ElementAt(0).Reference.Id); + Assert.AreEqual(123, relationships.ElementAt(0).StartNodeReference.Id); + Assert.AreEqual(789, relationships.ElementAt(0).EndNodeReference.Id); + Assert.AreEqual("KNOWS", relationships.ElementAt(0).TypeKey); + Assert.AreEqual("Foo", relationships.ElementAt(0).Data.Foo); + Assert.AreEqual("Bar", relationships.ElementAt(0).Data.Bar); + } + } + + class TestPayload + { + public string Foo { get; set; } + public string Bar { get; set; } + } + + [Test] + public void ShouldReturnEmptyEnumerableForNullResult() + { + //Arrange + const string gremlinQueryExpected = "foo bar query"; + var query = new GremlinApiQuery(gremlinQueryExpected, null); + + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), + MockResponse.Json(HttpStatusCode.OK, @"[]") + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + //Act + var nodes = graphClient + .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) + .ToList(); + + //Assert + Assert.AreEqual(0, nodes.Count()); + } + } + + [Test] + public void ShouldReturnListOfRelationshipInstancesWithLongRelationshipId() + { + //Arrange + const string gremlinQueryExpected = "foo bar query"; + var query = new GremlinApiQuery(gremlinQueryExpected, null); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), + MockResponse.Json(HttpStatusCode.OK, @"[ { + 'start' : 'http://127.0.0.1:5118/db/data/node/123', + 'data' : { + }, + 'self' : 'http://127.0.0.1:5118/db/data/relationship/21484836470', + 'property' : 'http://127.0.0.1:5118/db/data/relationship/21484836470/properties/{key}', + 'properties' : 'http://127.0.0.1:5118/db/data/relationship/21484836470/properties', + 'type' : 'KNOWS', + 'extensions' : { + }, + 'end' : 'http://127.0.0.1:5118/db/data/node/789' + } ]") + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + //Act + var relationships = graphClient + .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) + .ToList(); + + //Assert + Assert.AreEqual(1, relationships.Count()); + Assert.AreEqual(21484836470, relationships.ElementAt(0).Reference.Id); + Assert.AreEqual(123, relationships.ElementAt(0).StartNodeReference.Id); + Assert.AreEqual(789, relationships.ElementAt(0).EndNodeReference.Id); + Assert.AreEqual("KNOWS", relationships.ElementAt(0).TypeKey); + } + } + + [Test] + public void ShouldReturnListOfRelationshipInstancesWithLongStartNodeId() + { + //Arrange + const string gremlinQueryExpected = "foo bar query"; + var query = new GremlinApiQuery(gremlinQueryExpected, null); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), + MockResponse.Json(HttpStatusCode.OK, @"[ { + 'start' : 'http://127.0.0.1:5118/db/data/node/21484836470', + 'data' : { + }, + 'self' : 'http://127.0.0.1:5118/db/data/relationship/456', + 'property' : 'http://127.0.0.1:5118/db/data/relationship/456/properties/{key}', + 'properties' : 'http://127.0.0.1:5118/db/data/relationship/456/properties', + 'type' : 'KNOWS', + 'extensions' : { + }, + 'end' : 'http://127.0.0.1:5118/db/data/node/789' + } ]") + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + //Act + var relationships = graphClient + .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) + .ToList(); + + //Assert + Assert.AreEqual(1, relationships.Count()); + Assert.AreEqual(456, relationships.ElementAt(0).Reference.Id); + Assert.AreEqual(21484836470, relationships.ElementAt(0).StartNodeReference.Id); + Assert.AreEqual(789, relationships.ElementAt(0).EndNodeReference.Id); + Assert.AreEqual("KNOWS", relationships.ElementAt(0).TypeKey); + } + } + + [Test] + public void ShouldReturnListOfRelationshipInstancesWithLongEndNodeId() + { + //Arrange + const string gremlinQueryExpected = "foo bar query"; + var query = new GremlinApiQuery(gremlinQueryExpected, null); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/ext/GremlinPlugin/graphdb/execute_script", query), + MockResponse.Json(HttpStatusCode.OK, @"[ { + 'start' : 'http://127.0.0.1:5118/db/data/node/123', + 'data' : { + }, + 'self' : 'http://127.0.0.1:5118/db/data/relationship/456', + 'property' : 'http://127.0.0.1:5118/db/data/relationship/456/properties/{key}', + 'properties' : 'http://127.0.0.1:5118/db/data/relationship/456/properties', + 'type' : 'KNOWS', + 'extensions' : { + }, + 'end' : 'http://127.0.0.1:5118/db/data/node/21484836470' + } ]") + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + //Act + var relationships = graphClient + .ExecuteGetAllRelationshipsGremlin(gremlinQueryExpected, null) + .ToList(); + + //Assert + Assert.AreEqual(1, relationships.Count()); + Assert.AreEqual(456, relationships.ElementAt(0).Reference.Id); + Assert.AreEqual(123, relationships.ElementAt(0).StartNodeReference.Id); + Assert.AreEqual(21484836470, relationships.ElementAt(0).EndNodeReference.Id); + Assert.AreEqual("KNOWS", relationships.ElementAt(0).TypeKey); + } + } + + [Test] + public void ShouldFailGracefullyWhenGremlinIsNotAvailable() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get(""), + MockResponse.NeoRoot20() + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + var ex = Assert.Throws( + () => graphClient.ExecuteGetAllRelationshipsGremlin("foo bar query", null)); + Assert.AreEqual(GraphClient.GremlinPluginUnavailable, ex.Message); + } + } + } } \ No newline at end of file diff --git a/Test/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs b/Neo4jClient.Tests/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs similarity index 96% rename from Test/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs rename to Neo4jClient.Tests/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs index 62f7e65cc..733569a8d 100644 --- a/Test/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs @@ -1,61 +1,61 @@ -using System; -using System.Net; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests.Gremlin -{ - [TestFixture] - public class ExecuteScalarGremlinTests - { - [Test] - [ExpectedException(typeof(InvalidOperationException))] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - client.ExecuteScalarGremlin("", null); - } - - [Test] - public void ShouldReturnScalarValue() - { - //Arrange - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostJson( - "/ext/GremlinPlugin/graphdb/execute_script", - @"{ 'script': 'foo bar query', 'params': {} }"), - MockResponse.Json(HttpStatusCode.OK, @"1") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var node = graphClient.ExecuteScalarGremlin("foo bar query", null); - - //Assert - Assert.AreEqual(1, int.Parse(node)); - } - } - - [Test] - public void ShouldFailGracefullyWhenGremlinIsNotAvailable() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get(""), - MockResponse.NeoRoot20() - } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - var ex = Assert.Throws( - () => graphClient.ExecuteScalarGremlin("foo bar query", null)); - Assert.AreEqual(GraphClient.GremlinPluginUnavailable, ex.Message); - } - } - } +using System; +using System.Net; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests.Gremlin +{ + [TestFixture] + public class ExecuteScalarGremlinTests + { + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void ShouldThrowInvalidOperationExceptionIfNotConnected() + { + var client = new GraphClient(new Uri("http://foo")); + client.ExecuteScalarGremlin("", null); + } + + [Test] + public void ShouldReturnScalarValue() + { + //Arrange + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostJson( + "/ext/GremlinPlugin/graphdb/execute_script", + @"{ 'script': 'foo bar query', 'params': {} }"), + MockResponse.Json(HttpStatusCode.OK, @"1") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var node = graphClient.ExecuteScalarGremlin("foo bar query", null); + + //Assert + Assert.AreEqual(1, int.Parse(node)); + } + } + + [Test] + public void ShouldFailGracefullyWhenGremlinIsNotAvailable() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get(""), + MockResponse.NeoRoot20() + } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + var ex = Assert.Throws( + () => graphClient.ExecuteScalarGremlin("foo bar query", null)); + Assert.AreEqual(GraphClient.GremlinPluginUnavailable, ex.Message); + } + } + } } \ No newline at end of file diff --git a/Test/GraphClientTests/IndexExistsTests.cs b/Neo4jClient.Tests/GraphClientTests/IndexExistsTests.cs similarity index 97% rename from Test/GraphClientTests/IndexExistsTests.cs rename to Neo4jClient.Tests/GraphClientTests/IndexExistsTests.cs index 9f27d573f..fa7af303f 100644 --- a/Test/GraphClientTests/IndexExistsTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/IndexExistsTests.cs @@ -1,32 +1,32 @@ -using System.Net; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class IndexExistsTests - { - [Test] - [TestCase(IndexFor.Node, "/index/node/MyIndex", HttpStatusCode.OK, Result = true)] - [TestCase(IndexFor.Node, "/index/node/MyIndex", HttpStatusCode.NotFound, Result = false)] - [TestCase(IndexFor.Relationship, "/index/relationship/MyIndex", HttpStatusCode.OK, Result = true)] - [TestCase(IndexFor.Relationship, "/index/relationship/MyIndex", HttpStatusCode.NotFound, Result = false)] - public bool ShouldReturnIfIndexIsFound( - IndexFor indexFor, - string indexPath, - HttpStatusCode httpStatusCode) - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get(indexPath), - MockResponse.Json(httpStatusCode, "") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - return graphClient.CheckIndexExists("MyIndex", indexFor); - } - } - } -} +using System.Net; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class IndexExistsTests + { + [Test] + [TestCase(IndexFor.Node, "/index/node/MyIndex", HttpStatusCode.OK, Result = true)] + [TestCase(IndexFor.Node, "/index/node/MyIndex", HttpStatusCode.NotFound, Result = false)] + [TestCase(IndexFor.Relationship, "/index/relationship/MyIndex", HttpStatusCode.OK, Result = true)] + [TestCase(IndexFor.Relationship, "/index/relationship/MyIndex", HttpStatusCode.NotFound, Result = false)] + public bool ShouldReturnIfIndexIsFound( + IndexFor indexFor, + string indexPath, + HttpStatusCode httpStatusCode) + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get(indexPath), + MockResponse.Json(httpStatusCode, "") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + return graphClient.CheckIndexExists("MyIndex", indexFor); + } + } + } +} diff --git a/Test/GraphClientTests/LookupIndexTests.cs b/Neo4jClient.Tests/GraphClientTests/LookupIndexTests.cs similarity index 97% rename from Test/GraphClientTests/LookupIndexTests.cs rename to Neo4jClient.Tests/GraphClientTests/LookupIndexTests.cs index aa5c5e980..6218786a9 100644 --- a/Test/GraphClientTests/LookupIndexTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/LookupIndexTests.cs @@ -1,54 +1,54 @@ -using NUnit.Framework; -using System.Linq; -using System.Net; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class LookupIndexTests - { - [Test] - public void ShouldReturnLookupIndexResult() - { - //Arrange - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/index/node/users/Id/1000"), - MockResponse.Json(HttpStatusCode.OK, - @"[{ 'self': 'http://foo/db/data/node/456', - 'data': { 'Id': '1000', 'Name': 'Foo' }, - 'create_relationship': 'http://foo/db/data/node/456/relationships', - 'all_relationships': 'http://foo/db/data/node/456/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/456/properties', - 'property': 'http://foo/db/data/node/456/property/{key}', - 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' - }]") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var results = graphClient - .LookupIndex("users", IndexFor.Node, "Id", 1000) - .ToArray(); - - Assert.AreEqual(1, results.Count()); - var result = results[0]; - Assert.AreEqual(456, result.Reference.Id); - Assert.AreEqual(1000, result.Data.Id); - } - } - - public class UserTestNode - { - public long Id { get; set; } - public string Name { get; set; } - } - } -} +using NUnit.Framework; +using System.Linq; +using System.Net; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class LookupIndexTests + { + [Test] + public void ShouldReturnLookupIndexResult() + { + //Arrange + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/index/node/users/Id/1000"), + MockResponse.Json(HttpStatusCode.OK, + @"[{ 'self': 'http://foo/db/data/node/456', + 'data': { 'Id': '1000', 'Name': 'Foo' }, + 'create_relationship': 'http://foo/db/data/node/456/relationships', + 'all_relationships': 'http://foo/db/data/node/456/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/456/properties', + 'property': 'http://foo/db/data/node/456/property/{key}', + 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' + }]") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var results = graphClient + .LookupIndex("users", IndexFor.Node, "Id", 1000) + .ToArray(); + + Assert.AreEqual(1, results.Count()); + var result = results[0]; + Assert.AreEqual(456, result.Reference.Id); + Assert.AreEqual(1000, result.Data.Id); + } + } + + public class UserTestNode + { + public long Id { get; set; } + public string Name { get; set; } + } + } +} diff --git a/Test/GraphClientTests/QueryNodeIndexTests.cs b/Neo4jClient.Tests/GraphClientTests/QueryNodeIndexTests.cs similarity index 97% rename from Test/GraphClientTests/QueryNodeIndexTests.cs rename to Neo4jClient.Tests/GraphClientTests/QueryNodeIndexTests.cs index 1d45777db..cad5f650b 100644 --- a/Test/GraphClientTests/QueryNodeIndexTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/QueryNodeIndexTests.cs @@ -1,55 +1,55 @@ -using System; -using System.Linq; -using System.Net; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class QueryNodeIndexTests - { - [Test] - [Obsolete] - public void ShouldReturnQueryResults() - { - //Arrange - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/index/node/indexName?query=name%3Afoo"), - MockResponse.Json(HttpStatusCode.OK, - @"[{ 'self': 'http://foo/db/data/node/456', - 'data': { 'Name': 'Foo' }, - 'create_relationship': 'http://foo/db/data/node/456/relationships', - 'all_relationships': 'http://foo/db/data/node/456/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/456/properties', - 'property': 'http://foo/db/data/node/456/property/{key}', - 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' - }]") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - var results = graphClient - .QueryIndex("indexName", IndexFor.Node, "name:foo") - .ToArray(); - - Assert.AreEqual(1, results.Count()); - var result = results[0]; - Assert.AreEqual(456, result.Reference.Id); - Assert.AreEqual("Foo", result.Data.Name); - } - } - - public class TestNode - { - public string Name { get; set; } - } - } -} +using System; +using System.Linq; +using System.Net; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class QueryNodeIndexTests + { + [Test] + [Obsolete] + public void ShouldReturnQueryResults() + { + //Arrange + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/index/node/indexName?query=name%3Afoo"), + MockResponse.Json(HttpStatusCode.OK, + @"[{ 'self': 'http://foo/db/data/node/456', + 'data': { 'Name': 'Foo' }, + 'create_relationship': 'http://foo/db/data/node/456/relationships', + 'all_relationships': 'http://foo/db/data/node/456/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/456/properties', + 'property': 'http://foo/db/data/node/456/property/{key}', + 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' + }]") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + var results = graphClient + .QueryIndex("indexName", IndexFor.Node, "name:foo") + .ToArray(); + + Assert.AreEqual(1, results.Count()); + var result = results[0]; + Assert.AreEqual(456, result.Reference.Id); + Assert.AreEqual("Foo", result.Data.Name); + } + } + + public class TestNode + { + public string Name { get; set; } + } + } +} diff --git a/Test/GraphClientTests/ReIndexNodesTests.cs b/Neo4jClient.Tests/GraphClientTests/ReIndexNodesTests.cs similarity index 97% rename from Test/GraphClientTests/ReIndexNodesTests.cs rename to Neo4jClient.Tests/GraphClientTests/ReIndexNodesTests.cs index da2908897..9b1bc3bce 100644 --- a/Test/GraphClientTests/ReIndexNodesTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/ReIndexNodesTests.cs @@ -1,193 +1,193 @@ -using System; -using System.Collections.Generic; -using System.Net; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class ReIndexNodesTests - { - [Test] - public void ShouldReindexNodeWithIndexEntryContainingSpace() - { - //Arrange - var indexEntries = new List - { - new IndexEntry - { - Name = "my_nodes", - KeyValues = new Dictionary - { - {"FooKey", "the_value with space"} - }, - } - }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/index/node/my_nodes", - new - { - key = "FooKey", - value = "the_value with space", - uri = "http://foo/db/data/node/123" - }), - MockResponse.Json(HttpStatusCode.Created, - @"Location: http://foo/db/data/index/node/my_nodes/FooKey/the_value%20with%20space/123") - }, - { - MockRequest.Delete("/index/node/my_nodes/123"), - MockResponse.Http((int) HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - graphClient.ReIndex((NodeReference)123, indexEntries); - - // Assert - Assert.Pass("Success."); - } - } - - [Test] - public void ShouldReindexNodeWithDateTimeOffsetIndexEntry() - { - //Arrange - var indexEntries = new List - { - new IndexEntry - { - Name = "my_nodes", - KeyValues = new Dictionary - { - {"FooKey", new DateTimeOffset(1000, new TimeSpan(0))} - }, - } - }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/index/node/my_nodes", - new - { - key = "FooKey", - value = "1000", - uri = "http://foo/db/data/node/123" - }), - MockResponse.Json(HttpStatusCode.Created, - @"Location: http://foo/db/data/index/node/my_nodes/FooKey/someDateValue/123") - }, - { - MockRequest.Delete("/index/node/my_nodes/123"), - MockResponse.Http((int) HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - graphClient.ReIndex((NodeReference)123, indexEntries); - - // Assert - Assert.Pass("Success."); - } - } - - [Test] - public void ShouldAcceptQuestionMarkInIndexValue() - { - //Arrange - var indexKeyValues = new Dictionary - { - {"FooKey", "foo?bar"} - }; - var indexEntries = new List - { - new IndexEntry - { - Name = "my_nodes", - KeyValues = indexKeyValues, - } - }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/index/node/my_nodes", - new - { - key = "FooKey", - value = "foo?bar", - uri = "http://foo/db/data/node/123" - }), - MockResponse.Json(HttpStatusCode.Created, - @"Location: http://foo/db/data/index/node/my_nodes/FooKey/%3f/123") - }, - { - MockRequest.Delete("/index/node/my_nodes/123"), - MockResponse.Http((int) HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - graphClient.ReIndex((NodeReference)123, indexEntries); - - // Assert - Assert.Pass("Success."); - } - } - - [Test] - public void ShouldPreserveSlashInIndexValue() - { - //Arrange - var indexKeyValues = new Dictionary - { - {"FooKey", "abc/def"} - }; - var indexEntries = new List - { - new IndexEntry - { - Name = "my_nodes", - KeyValues = indexKeyValues, - } - }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/index/node/my_nodes", - new - { - key = "FooKey", - value = "abc/def", - uri = "http://foo/db/data/node/123" - }), - MockResponse.Json(HttpStatusCode.Created, - @"Location: http://foo/db/data/index/node/my_nodes/FooKey/abc-def/123") - }, - { - MockRequest.Delete("/index/node/my_nodes/123"), - MockResponse.Http((int) HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - graphClient.ReIndex((NodeReference)123, indexEntries); - - // Assert - Assert.Pass("Success."); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Net; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class ReIndexNodesTests + { + [Test] + public void ShouldReindexNodeWithIndexEntryContainingSpace() + { + //Arrange + var indexEntries = new List + { + new IndexEntry + { + Name = "my_nodes", + KeyValues = new Dictionary + { + {"FooKey", "the_value with space"} + }, + } + }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/index/node/my_nodes", + new + { + key = "FooKey", + value = "the_value with space", + uri = "http://foo/db/data/node/123" + }), + MockResponse.Json(HttpStatusCode.Created, + @"Location: http://foo/db/data/index/node/my_nodes/FooKey/the_value%20with%20space/123") + }, + { + MockRequest.Delete("/index/node/my_nodes/123"), + MockResponse.Http((int) HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + graphClient.ReIndex((NodeReference)123, indexEntries); + + // Assert + Assert.Pass("Success."); + } + } + + [Test] + public void ShouldReindexNodeWithDateTimeOffsetIndexEntry() + { + //Arrange + var indexEntries = new List + { + new IndexEntry + { + Name = "my_nodes", + KeyValues = new Dictionary + { + {"FooKey", new DateTimeOffset(1000, new TimeSpan(0))} + }, + } + }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/index/node/my_nodes", + new + { + key = "FooKey", + value = "1000", + uri = "http://foo/db/data/node/123" + }), + MockResponse.Json(HttpStatusCode.Created, + @"Location: http://foo/db/data/index/node/my_nodes/FooKey/someDateValue/123") + }, + { + MockRequest.Delete("/index/node/my_nodes/123"), + MockResponse.Http((int) HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + graphClient.ReIndex((NodeReference)123, indexEntries); + + // Assert + Assert.Pass("Success."); + } + } + + [Test] + public void ShouldAcceptQuestionMarkInIndexValue() + { + //Arrange + var indexKeyValues = new Dictionary + { + {"FooKey", "foo?bar"} + }; + var indexEntries = new List + { + new IndexEntry + { + Name = "my_nodes", + KeyValues = indexKeyValues, + } + }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/index/node/my_nodes", + new + { + key = "FooKey", + value = "foo?bar", + uri = "http://foo/db/data/node/123" + }), + MockResponse.Json(HttpStatusCode.Created, + @"Location: http://foo/db/data/index/node/my_nodes/FooKey/%3f/123") + }, + { + MockRequest.Delete("/index/node/my_nodes/123"), + MockResponse.Http((int) HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + graphClient.ReIndex((NodeReference)123, indexEntries); + + // Assert + Assert.Pass("Success."); + } + } + + [Test] + public void ShouldPreserveSlashInIndexValue() + { + //Arrange + var indexKeyValues = new Dictionary + { + {"FooKey", "abc/def"} + }; + var indexEntries = new List + { + new IndexEntry + { + Name = "my_nodes", + KeyValues = indexKeyValues, + } + }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/index/node/my_nodes", + new + { + key = "FooKey", + value = "abc/def", + uri = "http://foo/db/data/node/123" + }), + MockResponse.Json(HttpStatusCode.Created, + @"Location: http://foo/db/data/index/node/my_nodes/FooKey/abc-def/123") + }, + { + MockRequest.Delete("/index/node/my_nodes/123"), + MockResponse.Http((int) HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + graphClient.ReIndex((NodeReference)123, indexEntries); + + // Assert + Assert.Pass("Success."); + } + } + } +} diff --git a/Test/GraphClientTests/ReIndexRelationshipsTests.cs b/Neo4jClient.Tests/GraphClientTests/ReIndexRelationshipsTests.cs similarity index 97% rename from Test/GraphClientTests/ReIndexRelationshipsTests.cs rename to Neo4jClient.Tests/GraphClientTests/ReIndexRelationshipsTests.cs index cc4f649dd..c87aeb27e 100644 --- a/Test/GraphClientTests/ReIndexRelationshipsTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/ReIndexRelationshipsTests.cs @@ -1,211 +1,211 @@ -using System; -using System.Collections.Generic; -using System.Net; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class ReIndexRelationshipsTests - { - [Test] - public void ShouldReindexRelationshipWithIndexEntryContainingSpace() - { - //Arrange - var indexEntries = new List - { - new IndexEntry - { - Name = "my_relationships", - KeyValues = new Dictionary - { - {"BarKey", "the_value with space"} - }, - } - }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/index/relationship/my_relationships", - new - { - key = "BarKey", - value = "the_value with space", - uri = "http://foo/db/data/relationship/1234" - }), - MockResponse.Json(HttpStatusCode.Created, - @"Location: http://foo/db/data/index/relationship/my_relationships/BarKey/the_value%20with%20space/1234") - }, - { - MockRequest.Delete("/index/relationship/my_relationships/1234"), - MockResponse.Http((int) HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var relReference = new RelationshipReference(1234); - graphClient.ReIndex(relReference, indexEntries); - - // Assert - Assert.Pass("Success."); - } - } - - [Test] - public void ShouldReindexRelationshipWithDateTimeOffsetIndexEntry() - { - //Arrange - var indexEntries = new List - { - new IndexEntry - { - Name = "my_relationships", - KeyValues = new Dictionary - { - {"BarKey", new DateTimeOffset(1000, new TimeSpan(0))} - }, - } - }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/index/relationship/my_relationships", - new - { - key = "BarKey", - value = "1000", - uri = "http://foo/db/data/relationship/1234" - }), - MockResponse.Json(HttpStatusCode.Created, - @"Location: http://foo/db/data/index/relationship/my_relationships/BarKey/someDateValue/1234") - }, - { - MockRequest.Delete("/index/relationship/my_relationships/1234"), - MockResponse.Http((int) HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var relReference = new RelationshipReference(1234); - graphClient.ReIndex(relReference, indexEntries); - - // Assert - Assert.Pass("Success."); - } - } - - [Test] - public void ShouldAcceptQuestionMarkInRelationshipIndexValue() - { - //Arrange - var indexKeyValues = new Dictionary - { - {"BarKey", "foo?bar"} - }; - var indexEntries = new List - { - new IndexEntry - { - Name = "my_relationships", - KeyValues = indexKeyValues, - } - }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/index/relationship/my_relationships", - new - { - key = "BarKey", - value = "foo?bar", - uri = "http://foo/db/data/relationship/1234" - }), - MockResponse.Json(HttpStatusCode.Created, - @"Location: http://foo/db/data/index/relationship/my_relationships/BarKey/%3f/1234") - }, - { - MockRequest.Delete("/index/relationship/my_relationships/1234"), - MockResponse.Http((int) HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var relReference = new RelationshipReference(1234); - graphClient.ReIndex(relReference, indexEntries); - - // Assert - Assert.Pass("Success."); - } - } - - [Test] - public void ShouldPreserveSlashInRelationshipIndexValue() - { - //Arrange - var indexKeyValues = new Dictionary - { - {"BarKey", "abc/def"} - }; - var indexEntries = new List - { - new IndexEntry - { - Name = "my_relationships", - KeyValues = indexKeyValues, - } - }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/index/relationship/my_relationships", - new - { - key = "BarKey", - value = "abc/def", - uri = "http://foo/db/data/relationship/123" - }), - MockResponse.Json(HttpStatusCode.Created, - @"Location: http://foo/db/data/index/relationship/my_relationships/BarKey/abc-def/1234") - }, - { - MockRequest.Delete("/index/relationship/my_relationships/123"), - MockResponse.Http((int) HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var relReference = new RelationshipReference(123); - graphClient.ReIndex(relReference, indexEntries); - - // Assert - Assert.Pass("Success."); - } - } - - public class TestRelationship : Relationship - { - public TestRelationship(NodeReference targetNode) - : base(targetNode) - { - } - - public override string RelationshipTypeKey - { - get { return "TEST_RELATIONSHIP"; } - } - } - - } +using System; +using System.Collections.Generic; +using System.Net; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class ReIndexRelationshipsTests + { + [Test] + public void ShouldReindexRelationshipWithIndexEntryContainingSpace() + { + //Arrange + var indexEntries = new List + { + new IndexEntry + { + Name = "my_relationships", + KeyValues = new Dictionary + { + {"BarKey", "the_value with space"} + }, + } + }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/index/relationship/my_relationships", + new + { + key = "BarKey", + value = "the_value with space", + uri = "http://foo/db/data/relationship/1234" + }), + MockResponse.Json(HttpStatusCode.Created, + @"Location: http://foo/db/data/index/relationship/my_relationships/BarKey/the_value%20with%20space/1234") + }, + { + MockRequest.Delete("/index/relationship/my_relationships/1234"), + MockResponse.Http((int) HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var relReference = new RelationshipReference(1234); + graphClient.ReIndex(relReference, indexEntries); + + // Assert + Assert.Pass("Success."); + } + } + + [Test] + public void ShouldReindexRelationshipWithDateTimeOffsetIndexEntry() + { + //Arrange + var indexEntries = new List + { + new IndexEntry + { + Name = "my_relationships", + KeyValues = new Dictionary + { + {"BarKey", new DateTimeOffset(1000, new TimeSpan(0))} + }, + } + }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/index/relationship/my_relationships", + new + { + key = "BarKey", + value = "1000", + uri = "http://foo/db/data/relationship/1234" + }), + MockResponse.Json(HttpStatusCode.Created, + @"Location: http://foo/db/data/index/relationship/my_relationships/BarKey/someDateValue/1234") + }, + { + MockRequest.Delete("/index/relationship/my_relationships/1234"), + MockResponse.Http((int) HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var relReference = new RelationshipReference(1234); + graphClient.ReIndex(relReference, indexEntries); + + // Assert + Assert.Pass("Success."); + } + } + + [Test] + public void ShouldAcceptQuestionMarkInRelationshipIndexValue() + { + //Arrange + var indexKeyValues = new Dictionary + { + {"BarKey", "foo?bar"} + }; + var indexEntries = new List + { + new IndexEntry + { + Name = "my_relationships", + KeyValues = indexKeyValues, + } + }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/index/relationship/my_relationships", + new + { + key = "BarKey", + value = "foo?bar", + uri = "http://foo/db/data/relationship/1234" + }), + MockResponse.Json(HttpStatusCode.Created, + @"Location: http://foo/db/data/index/relationship/my_relationships/BarKey/%3f/1234") + }, + { + MockRequest.Delete("/index/relationship/my_relationships/1234"), + MockResponse.Http((int) HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var relReference = new RelationshipReference(1234); + graphClient.ReIndex(relReference, indexEntries); + + // Assert + Assert.Pass("Success."); + } + } + + [Test] + public void ShouldPreserveSlashInRelationshipIndexValue() + { + //Arrange + var indexKeyValues = new Dictionary + { + {"BarKey", "abc/def"} + }; + var indexEntries = new List + { + new IndexEntry + { + Name = "my_relationships", + KeyValues = indexKeyValues, + } + }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/index/relationship/my_relationships", + new + { + key = "BarKey", + value = "abc/def", + uri = "http://foo/db/data/relationship/123" + }), + MockResponse.Json(HttpStatusCode.Created, + @"Location: http://foo/db/data/index/relationship/my_relationships/BarKey/abc-def/1234") + }, + { + MockRequest.Delete("/index/relationship/my_relationships/123"), + MockResponse.Http((int) HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var relReference = new RelationshipReference(123); + graphClient.ReIndex(relReference, indexEntries); + + // Assert + Assert.Pass("Success."); + } + } + + public class TestRelationship : Relationship + { + public TestRelationship(NodeReference targetNode) + : base(targetNode) + { + } + + public override string RelationshipTypeKey + { + get { return "TEST_RELATIONSHIP"; } + } + } + + } } \ No newline at end of file diff --git a/Test/GraphClientTests/RootNodeTests.cs b/Neo4jClient.Tests/GraphClientTests/RootNodeTests.cs similarity index 96% rename from Test/GraphClientTests/RootNodeTests.cs rename to Neo4jClient.Tests/GraphClientTests/RootNodeTests.cs index bc09dbe8c..3a0604a9e 100644 --- a/Test/GraphClientTests/RootNodeTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/RootNodeTests.cs @@ -1,32 +1,32 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class RootNodeTests - { - [Test] - public void RootNodeShouldHaveReferenceBackToClient() - { - using (var testHarness = new RestTestHarness()) - { - var client = testHarness.CreateAndConnectGraphClient(); - var rootNode = client.RootNode; - Assert.AreEqual(client, ((IGremlinQuery) rootNode).Client); - } - } - - [Test] - public void RootNodeShouldSupportGremlinQueries() - { - using (var testHarness = new RestTestHarness()) - { - var client = testHarness.CreateAndConnectGraphClient(); - var rootNode = client.RootNode; - Assert.AreEqual("g.v(p0)", ((IGremlinQuery) rootNode).QueryText); - Assert.AreEqual(123, ((IGremlinQuery) rootNode).QueryParameters["p0"]); - } - } - } -} +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class RootNodeTests + { + [Test] + public void RootNodeShouldHaveReferenceBackToClient() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectGraphClient(); + var rootNode = client.RootNode; + Assert.AreEqual(client, ((IGremlinQuery) rootNode).Client); + } + } + + [Test] + public void RootNodeShouldSupportGremlinQueries() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectGraphClient(); + var rootNode = client.RootNode; + Assert.AreEqual("g.v(p0)", ((IGremlinQuery) rootNode).QueryText); + Assert.AreEqual(123, ((IGremlinQuery) rootNode).QueryParameters["p0"]); + } + } + } +} diff --git a/Test/GraphClientTests/ServerVersionTests.cs b/Neo4jClient.Tests/GraphClientTests/ServerVersionTests.cs similarity index 96% rename from Test/GraphClientTests/ServerVersionTests.cs rename to Neo4jClient.Tests/GraphClientTests/ServerVersionTests.cs index 3aeca60c3..2e03f00cb 100644 --- a/Test/GraphClientTests/ServerVersionTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/ServerVersionTests.cs @@ -1,22 +1,22 @@ -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class ServerVersionTests - { - [Test] - public void ShouldParse15M02Version() - { - using (var testHarness = new RestTestHarness - { - { MockRequest.Get(""), MockResponse.NeoRoot() } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - Assert.AreEqual("1.5.0.2", graphClient.ServerVersion.ToString()); - } - } - } +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class ServerVersionTests + { + [Test] + public void ShouldParse15M02Version() + { + using (var testHarness = new RestTestHarness + { + { MockRequest.Get(""), MockResponse.NeoRoot() } + }) + { + var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + + Assert.AreEqual("1.5.0.2", graphClient.ServerVersion.ToString()); + } + } + } } \ No newline at end of file diff --git a/Test/GraphClientTests/UpdateNodeTests.cs b/Neo4jClient.Tests/GraphClientTests/UpdateNodeTests.cs similarity index 97% rename from Test/GraphClientTests/UpdateNodeTests.cs rename to Neo4jClient.Tests/GraphClientTests/UpdateNodeTests.cs index dfa7ea727..90e9b341d 100644 --- a/Test/GraphClientTests/UpdateNodeTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/UpdateNodeTests.cs @@ -1,298 +1,298 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class UpdateNodeTests - { - [Test] - public void ShouldUpdateNode() - { - var nodeToUpdate = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/456"), - MockResponse.Json(HttpStatusCode.OK, @"{ 'self': 'http://foo/db/data/node/456', - 'data': { 'Foo': 'foo', - 'Bar': 'bar', - 'Baz': 'baz' - }, - 'create_relationship': 'http://foo/db/data/node/456/relationships', - 'all_relationships': 'http://foo/db/data/node/456/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/456/properties', - 'property': 'http://foo/db/data/node/456/property/{key}', - 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' - }") - }, - { - MockRequest.PutObjectAsJson("/node/456/properties", nodeToUpdate), - MockResponse.Http((int)HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var pocoReference = new NodeReference(456); - graphClient.Update( - pocoReference, nodeFromDb => - { - nodeFromDb.Foo = "fooUpdated"; - nodeFromDb.Baz = "bazUpdated"; - nodeToUpdate = nodeFromDb; - } - ); - - Assert.AreEqual("fooUpdated", nodeToUpdate.Foo); - Assert.AreEqual("bazUpdated", nodeToUpdate.Baz); - Assert.AreEqual("bar", nodeToUpdate.Bar); - } - } - - [Test] - public void ShouldReturnNodeAfterUpdating() - { - var nodeToUpdate = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/456"), - MockResponse.Json(HttpStatusCode.OK, @"{ 'self': 'http://foo/db/data/node/456', - 'data': { 'Foo': 'foo', - 'Bar': 'bar', - 'Baz': 'baz' - }, - 'create_relationship': 'http://foo/db/data/node/456/relationships', - 'all_relationships': 'http://foo/db/data/node/456/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/456/properties', - 'property': 'http://foo/db/data/node/456/property/{key}', - 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' - }") - }, - { - MockRequest.PutObjectAsJson("/node/456/properties", nodeToUpdate), - MockResponse.Http((int)HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var pocoReference = new NodeReference(456); - var updatedNode = graphClient.Update( - pocoReference, nodeFromDb => - { - nodeFromDb.Foo = "fooUpdated"; - nodeFromDb.Baz = "bazUpdated"; - }); - - Assert.AreEqual(pocoReference, updatedNode.Reference); - Assert.AreEqual("fooUpdated", updatedNode.Data.Foo); - Assert.AreEqual("bazUpdated", updatedNode.Data.Baz); - Assert.AreEqual("bar", updatedNode.Data.Bar); - } - } - - [Test] - public void ShouldUpdateNodeWithIndexEntries() - { - var nodeToUpdate = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/456"), - MockResponse.Json(HttpStatusCode.OK, @"{ 'self': 'http://foo/db/data/node/456', - 'data': { 'Foo': 'foo', - 'Bar': 'bar', - 'Baz': 'baz' - }, - 'create_relationship': 'http://foo/db/data/node/456/relationships', - 'all_relationships': 'http://foo/db/data/node/456/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/456/properties', - 'property': 'http://foo/db/data/node/456/property/{key}', - 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' - }") - }, - { - MockRequest.PutObjectAsJson("/node/456/properties", nodeToUpdate), - MockResponse.Http((int)HttpStatusCode.NoContent) - }, - { - MockRequest.Delete("/index/node/foo/456"), - MockResponse.Http((int)HttpStatusCode.NoContent) - }, - { - MockRequest.PostObjectAsJson("/index/node/foo", new { key="foo", value="bar", uri="http://foo/db/data/node/456"}), - MockResponse.Json(HttpStatusCode.Created, "Location: http://foo/db/data/index/node/foo/bar/456") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - // Act - var pocoReference = new NodeReference(456); - graphClient.Update( - pocoReference, nodeFromDb => - { - nodeFromDb.Foo = "fooUpdated"; - nodeFromDb.Baz = "bazUpdated"; - nodeToUpdate = nodeFromDb; - }, nodeFromDb => new List - { - new IndexEntry - { - Name = "foo", - KeyValues = new Dictionary {{"foo", "bar"}}, - } - }); - - Assert.AreEqual("fooUpdated", nodeToUpdate.Foo); - Assert.AreEqual("bazUpdated", nodeToUpdate.Baz); - Assert.AreEqual("bar", nodeToUpdate.Bar); - } - } - - [Test] - public void ShouldRunDelegateForChanges() - { - var nodeToUpdate = new TestNode { Id = 1, Foo = "foo", Bar = "bar", Baz = "baz" }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/node/456"), - MockResponse.Json(HttpStatusCode.OK, @"{ 'self': 'http://foo/db/data/node/456', - 'data': { 'Foo': 'foo', - 'Bar': 'bar', - 'Baz': 'baz' - }, - 'create_relationship': 'http://foo/db/data/node/456/relationships', - 'all_relationships': 'http://foo/db/data/node/456/relationships/all', - 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', - 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', - 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', - 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', - 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', - 'properties': 'http://foo/db/data/node/456/properties', - 'property': 'http://foo/db/data/node/456/property/{key}', - 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' - }") - }, - { - MockRequest.PutObjectAsJson("/node/456/properties", nodeToUpdate), - MockResponse.Http((int)HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var hasChanged = false; - - var pocoReference = new NodeReference(456); - graphClient.Update( - pocoReference, nodeFromDb => - { - nodeFromDb.Foo = "fooUpdated"; - nodeFromDb.Baz = "bazUpdated"; - nodeToUpdate = nodeFromDb; - }, - null, - diff => { hasChanged = diff.Any(); } - ); - - Assert.IsTrue(hasChanged); - } - } - - [Test] - public void ShouldReplaceNode() - { - var newData = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PutObjectAsJson("/node/456/properties", newData), - MockResponse.Http((int)HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - //Act - var pocoReference = new NodeReference(456); - graphClient.Update(pocoReference, newData); - } - } - - [Test] - public void ShouldReplaceNodeWithIndexEntries() - { - var newData = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PutObjectAsJson("/node/456/properties", newData), - MockResponse.Http((int)HttpStatusCode.NoContent) - }, - { - MockRequest.Delete("/index/node/foo/456"), - MockResponse.Http((int)HttpStatusCode.NoContent) - }, - { - MockRequest.PostObjectAsJson("/index/node/foo", new { key="foo", value="bar", uri="http://foo/db/data/node/456"}), - MockResponse.Json(HttpStatusCode.Created, "Location: http://foo/db/data/index/node/foo/bar/456") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - // Act - var pocoReference = new NodeReference(456); - graphClient.Update( - pocoReference, - newData, - new [] - { - new IndexEntry - { - Name = "foo", - KeyValues = new Dictionary {{"foo", "bar"}}, - } - }); - } - } - - public class TestNode - { - public int Id { get; set; } - public string Foo { get; set; } - public string Bar { get; set; } - public string Baz { get; set; } - } - } +using System.Collections.Generic; +using System.Linq; +using System.Net; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class UpdateNodeTests + { + [Test] + public void ShouldUpdateNode() + { + var nodeToUpdate = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/456"), + MockResponse.Json(HttpStatusCode.OK, @"{ 'self': 'http://foo/db/data/node/456', + 'data': { 'Foo': 'foo', + 'Bar': 'bar', + 'Baz': 'baz' + }, + 'create_relationship': 'http://foo/db/data/node/456/relationships', + 'all_relationships': 'http://foo/db/data/node/456/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/456/properties', + 'property': 'http://foo/db/data/node/456/property/{key}', + 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' + }") + }, + { + MockRequest.PutObjectAsJson("/node/456/properties", nodeToUpdate), + MockResponse.Http((int)HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var pocoReference = new NodeReference(456); + graphClient.Update( + pocoReference, nodeFromDb => + { + nodeFromDb.Foo = "fooUpdated"; + nodeFromDb.Baz = "bazUpdated"; + nodeToUpdate = nodeFromDb; + } + ); + + Assert.AreEqual("fooUpdated", nodeToUpdate.Foo); + Assert.AreEqual("bazUpdated", nodeToUpdate.Baz); + Assert.AreEqual("bar", nodeToUpdate.Bar); + } + } + + [Test] + public void ShouldReturnNodeAfterUpdating() + { + var nodeToUpdate = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/456"), + MockResponse.Json(HttpStatusCode.OK, @"{ 'self': 'http://foo/db/data/node/456', + 'data': { 'Foo': 'foo', + 'Bar': 'bar', + 'Baz': 'baz' + }, + 'create_relationship': 'http://foo/db/data/node/456/relationships', + 'all_relationships': 'http://foo/db/data/node/456/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/456/properties', + 'property': 'http://foo/db/data/node/456/property/{key}', + 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' + }") + }, + { + MockRequest.PutObjectAsJson("/node/456/properties", nodeToUpdate), + MockResponse.Http((int)HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var pocoReference = new NodeReference(456); + var updatedNode = graphClient.Update( + pocoReference, nodeFromDb => + { + nodeFromDb.Foo = "fooUpdated"; + nodeFromDb.Baz = "bazUpdated"; + }); + + Assert.AreEqual(pocoReference, updatedNode.Reference); + Assert.AreEqual("fooUpdated", updatedNode.Data.Foo); + Assert.AreEqual("bazUpdated", updatedNode.Data.Baz); + Assert.AreEqual("bar", updatedNode.Data.Bar); + } + } + + [Test] + public void ShouldUpdateNodeWithIndexEntries() + { + var nodeToUpdate = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/456"), + MockResponse.Json(HttpStatusCode.OK, @"{ 'self': 'http://foo/db/data/node/456', + 'data': { 'Foo': 'foo', + 'Bar': 'bar', + 'Baz': 'baz' + }, + 'create_relationship': 'http://foo/db/data/node/456/relationships', + 'all_relationships': 'http://foo/db/data/node/456/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/456/properties', + 'property': 'http://foo/db/data/node/456/property/{key}', + 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' + }") + }, + { + MockRequest.PutObjectAsJson("/node/456/properties", nodeToUpdate), + MockResponse.Http((int)HttpStatusCode.NoContent) + }, + { + MockRequest.Delete("/index/node/foo/456"), + MockResponse.Http((int)HttpStatusCode.NoContent) + }, + { + MockRequest.PostObjectAsJson("/index/node/foo", new { key="foo", value="bar", uri="http://foo/db/data/node/456"}), + MockResponse.Json(HttpStatusCode.Created, "Location: http://foo/db/data/index/node/foo/bar/456") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + // Act + var pocoReference = new NodeReference(456); + graphClient.Update( + pocoReference, nodeFromDb => + { + nodeFromDb.Foo = "fooUpdated"; + nodeFromDb.Baz = "bazUpdated"; + nodeToUpdate = nodeFromDb; + }, nodeFromDb => new List + { + new IndexEntry + { + Name = "foo", + KeyValues = new Dictionary {{"foo", "bar"}}, + } + }); + + Assert.AreEqual("fooUpdated", nodeToUpdate.Foo); + Assert.AreEqual("bazUpdated", nodeToUpdate.Baz); + Assert.AreEqual("bar", nodeToUpdate.Bar); + } + } + + [Test] + public void ShouldRunDelegateForChanges() + { + var nodeToUpdate = new TestNode { Id = 1, Foo = "foo", Bar = "bar", Baz = "baz" }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/node/456"), + MockResponse.Json(HttpStatusCode.OK, @"{ 'self': 'http://foo/db/data/node/456', + 'data': { 'Foo': 'foo', + 'Bar': 'bar', + 'Baz': 'baz' + }, + 'create_relationship': 'http://foo/db/data/node/456/relationships', + 'all_relationships': 'http://foo/db/data/node/456/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/456/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/456/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/456/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/456/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/456/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/456/properties', + 'property': 'http://foo/db/data/node/456/property/{key}', + 'traverse': 'http://foo/db/data/node/456/traverse/{returnType}' + }") + }, + { + MockRequest.PutObjectAsJson("/node/456/properties", nodeToUpdate), + MockResponse.Http((int)HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var hasChanged = false; + + var pocoReference = new NodeReference(456); + graphClient.Update( + pocoReference, nodeFromDb => + { + nodeFromDb.Foo = "fooUpdated"; + nodeFromDb.Baz = "bazUpdated"; + nodeToUpdate = nodeFromDb; + }, + null, + diff => { hasChanged = diff.Any(); } + ); + + Assert.IsTrue(hasChanged); + } + } + + [Test] + public void ShouldReplaceNode() + { + var newData = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PutObjectAsJson("/node/456/properties", newData), + MockResponse.Http((int)HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + //Act + var pocoReference = new NodeReference(456); + graphClient.Update(pocoReference, newData); + } + } + + [Test] + public void ShouldReplaceNodeWithIndexEntries() + { + var newData = new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PutObjectAsJson("/node/456/properties", newData), + MockResponse.Http((int)HttpStatusCode.NoContent) + }, + { + MockRequest.Delete("/index/node/foo/456"), + MockResponse.Http((int)HttpStatusCode.NoContent) + }, + { + MockRequest.PostObjectAsJson("/index/node/foo", new { key="foo", value="bar", uri="http://foo/db/data/node/456"}), + MockResponse.Json(HttpStatusCode.Created, "Location: http://foo/db/data/index/node/foo/bar/456") + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + // Act + var pocoReference = new NodeReference(456); + graphClient.Update( + pocoReference, + newData, + new [] + { + new IndexEntry + { + Name = "foo", + KeyValues = new Dictionary {{"foo", "bar"}}, + } + }); + } + } + + public class TestNode + { + public int Id { get; set; } + public string Foo { get; set; } + public string Bar { get; set; } + public string Baz { get; set; } + } + } } \ No newline at end of file diff --git a/Test/GraphClientTests/UpdateRelationshipTests.cs b/Neo4jClient.Tests/GraphClientTests/UpdateRelationshipTests.cs similarity index 97% rename from Test/GraphClientTests/UpdateRelationshipTests.cs rename to Neo4jClient.Tests/GraphClientTests/UpdateRelationshipTests.cs index 493e23499..48eec0a52 100644 --- a/Test/GraphClientTests/UpdateRelationshipTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/UpdateRelationshipTests.cs @@ -1,76 +1,76 @@ -using System.Net; -using NUnit.Framework; - -namespace Neo4jClient.Test.GraphClientTests -{ - [TestFixture] - public class UpdateRelationshipTests - { - [Test] - public void ShouldUpdatePayload() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/relationship/456/properties"), - MockResponse.Json(HttpStatusCode.OK, "{ 'Foo': 'foo', 'Bar': 'bar', 'Baz': 'baz' }") - }, - { - MockRequest.PutObjectAsJson( - "/relationship/456/properties", - new TestPayload { Foo = "fooUpdated", Bar = "bar", Baz = "bazUpdated" }), - MockResponse.Http((int)HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - graphClient.Update( - new RelationshipReference(456), - payloadFromDb => - { - payloadFromDb.Foo = "fooUpdated"; - payloadFromDb.Baz = "bazUpdated"; - } - ); - } - } - - [Test] - public void ShouldInitializePayloadDuringUpdate() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get("/relationship/456/properties"), - MockResponse.Http((int)HttpStatusCode.NoContent) - }, - { - MockRequest.PutObjectAsJson( - "/relationship/456/properties", - new TestPayload { Foo = "fooUpdated", Baz = "bazUpdated" }), - MockResponse.Http((int)HttpStatusCode.NoContent) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - graphClient.Update( - new RelationshipReference(456), - payloadFromDb => - { - payloadFromDb.Foo = "fooUpdated"; - payloadFromDb.Baz = "bazUpdated"; - } - ); - } - } - - public class TestPayload - { - public string Foo { get; set; } - public string Bar { get; set; } - public string Baz { get; set; } - } - } -} +using System.Net; +using NUnit.Framework; + +namespace Neo4jClient.Test.GraphClientTests +{ + [TestFixture] + public class UpdateRelationshipTests + { + [Test] + public void ShouldUpdatePayload() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/relationship/456/properties"), + MockResponse.Json(HttpStatusCode.OK, "{ 'Foo': 'foo', 'Bar': 'bar', 'Baz': 'baz' }") + }, + { + MockRequest.PutObjectAsJson( + "/relationship/456/properties", + new TestPayload { Foo = "fooUpdated", Bar = "bar", Baz = "bazUpdated" }), + MockResponse.Http((int)HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + graphClient.Update( + new RelationshipReference(456), + payloadFromDb => + { + payloadFromDb.Foo = "fooUpdated"; + payloadFromDb.Baz = "bazUpdated"; + } + ); + } + } + + [Test] + public void ShouldInitializePayloadDuringUpdate() + { + using (var testHarness = new RestTestHarness + { + { + MockRequest.Get("/relationship/456/properties"), + MockResponse.Http((int)HttpStatusCode.NoContent) + }, + { + MockRequest.PutObjectAsJson( + "/relationship/456/properties", + new TestPayload { Foo = "fooUpdated", Baz = "bazUpdated" }), + MockResponse.Http((int)HttpStatusCode.NoContent) + } + }) + { + var graphClient = testHarness.CreateAndConnectGraphClient(); + + graphClient.Update( + new RelationshipReference(456), + payloadFromDb => + { + payloadFromDb.Foo = "fooUpdated"; + payloadFromDb.Baz = "bazUpdated"; + } + ); + } + } + + public class TestPayload + { + public string Foo { get; set; } + public string Bar { get; set; } + public string Baz { get; set; } + } + } +} diff --git a/Test/Gremlin/AggregateStepTests.cs b/Neo4jClient.Tests/Gremlin/AggregateStepTests.cs similarity index 97% rename from Test/Gremlin/AggregateStepTests.cs rename to Neo4jClient.Tests/Gremlin/AggregateStepTests.cs index 40d28be7e..f76ffc0cb 100644 --- a/Test/Gremlin/AggregateStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/AggregateStepTests.cs @@ -1,62 +1,62 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class AggregateStepTests - { - [Test] - public void AggregateVShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).AggregateV("foo"); - Assert.AreEqual("foo = [];g.v(p0).aggregate(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void AggregateVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).AggregateV("foo"); - Assert.IsInstanceOf>(query); - } - - [Test] - public void AggregateEShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).AggregateE("foo"); - Assert.AreEqual("foo = [];g.v(p0).aggregate(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void AggregateEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).AggregateE("foo"); - Assert.IsInstanceOf(query); - } - - [Test] - public void AggregateEWithTDataShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).AggregateE("foo"); - Assert.AreEqual("foo = [];g.v(p0).aggregate(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void AggregateEWithTDataShouldAppendStepAndDeclareVariables() - { - var query = new NodeReference(123).AggregateE("foo").AggregateE("bar"); - Assert.AreEqual("bar = [];foo = [];g.v(p0).aggregate(foo).aggregate(bar)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void AggregateEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).AggregateE("foo"); - Assert.IsInstanceOf>(query); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class AggregateStepTests + { + [Test] + public void AggregateVShouldAppendStepAndDeclareVariable() + { + var query = new NodeReference(123).AggregateV("foo"); + Assert.AreEqual("foo = [];g.v(p0).aggregate(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void AggregateVShouldReturnTypedNodeEnumerable() + { + var query = new NodeReference(123).AggregateV("foo"); + Assert.IsInstanceOf>(query); + } + + [Test] + public void AggregateEShouldAppendStepAndDeclareVariable() + { + var query = new NodeReference(123).AggregateE("foo"); + Assert.AreEqual("foo = [];g.v(p0).aggregate(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void AggregateEShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).AggregateE("foo"); + Assert.IsInstanceOf(query); + } + + [Test] + public void AggregateEWithTDataShouldAppendStepAndDeclareVariable() + { + var query = new NodeReference(123).AggregateE("foo"); + Assert.AreEqual("foo = [];g.v(p0).aggregate(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void AggregateEWithTDataShouldAppendStepAndDeclareVariables() + { + var query = new NodeReference(123).AggregateE("foo").AggregateE("bar"); + Assert.AreEqual("bar = [];foo = [];g.v(p0).aggregate(foo).aggregate(bar)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void AggregateEWithTDataShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).AggregateE("foo"); + Assert.IsInstanceOf>(query); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/AsStepTests.cs b/Neo4jClient.Tests/Gremlin/AsStepTests.cs similarity index 97% rename from Test/Gremlin/AsStepTests.cs rename to Neo4jClient.Tests/Gremlin/AsStepTests.cs index a60608d1e..96685eb93 100644 --- a/Test/Gremlin/AsStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/AsStepTests.cs @@ -1,39 +1,39 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class AsStepTests - { - [Test] - public void AsShouldAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().As("foo"); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outV.as(p1)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - } - - [Test] - public void AsShouldAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().As("foo"); - Assert.IsInstanceOf(query); - Assert.AreEqual("g.v(p0).outE.as(p1)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - } - - [Test] - public void AsShouldAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().As("foo"); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outE.as(p1)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class AsStepTests + { + [Test] + public void AsShouldAppendStepToNodeQuery() + { + var query = new NodeReference(123).OutV().As("foo"); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outV.as(p1)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + } + + [Test] + public void AsShouldAppendStepToRelationshipQuery() + { + var query = new NodeReference(123).OutE().As("foo"); + Assert.IsInstanceOf(query); + Assert.AreEqual("g.v(p0).outE.as(p1)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + } + + [Test] + public void AsShouldAppendStepToTypedRelationshipQuery() + { + var query = new NodeReference(123).OutE().As("foo"); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outE.as(p1)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/BackTests.cs b/Neo4jClient.Tests/Gremlin/BackTests.cs similarity index 97% rename from Test/Gremlin/BackTests.cs rename to Neo4jClient.Tests/Gremlin/BackTests.cs index f4b670f75..e40a670c4 100644 --- a/Test/Gremlin/BackTests.cs +++ b/Neo4jClient.Tests/Gremlin/BackTests.cs @@ -1,57 +1,57 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class BackTests - { - [Test] - public void BackVShouldAppendStep() - { - var query = new NodeReference(123).BackV("foo"); - Assert.AreEqual("g.v(p0).back(p1)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - } - - [Test] - public void BackVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).BackV("foo"); - Assert.IsInstanceOf>(query); - } - - [Test] - public void BackEShouldAppendStep() - { - var query = new NodeReference(123).BackE("foo"); - Assert.AreEqual("g.v(p0).back(p1)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - } - - [Test] - public void BackEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).BackE("foo"); - Assert.IsInstanceOf(query); - } - - [Test] - public void BackEWithTDataShouldAppendStep() - { - var query = new NodeReference(123).BackE("foo"); - Assert.AreEqual("g.v(p0).back(p1)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - } - - [Test] - public void BackEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).BackE("foo"); - Assert.IsInstanceOf>(query); - } - } -} +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class BackTests + { + [Test] + public void BackVShouldAppendStep() + { + var query = new NodeReference(123).BackV("foo"); + Assert.AreEqual("g.v(p0).back(p1)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + } + + [Test] + public void BackVShouldReturnTypedNodeEnumerable() + { + var query = new NodeReference(123).BackV("foo"); + Assert.IsInstanceOf>(query); + } + + [Test] + public void BackEShouldAppendStep() + { + var query = new NodeReference(123).BackE("foo"); + Assert.AreEqual("g.v(p0).back(p1)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + } + + [Test] + public void BackEShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).BackE("foo"); + Assert.IsInstanceOf(query); + } + + [Test] + public void BackEWithTDataShouldAppendStep() + { + var query = new NodeReference(123).BackE("foo"); + Assert.AreEqual("g.v(p0).back(p1)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + } + + [Test] + public void BackEWithTDataShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).BackE("foo"); + Assert.IsInstanceOf>(query); + } + } +} diff --git a/Test/Gremlin/BasicStepsTests.cs b/Neo4jClient.Tests/Gremlin/BasicStepsTests.cs similarity index 97% rename from Test/Gremlin/BasicStepsTests.cs rename to Neo4jClient.Tests/Gremlin/BasicStepsTests.cs index 3f92df582..dfdcab718 100644 --- a/Test/Gremlin/BasicStepsTests.cs +++ b/Neo4jClient.Tests/Gremlin/BasicStepsTests.cs @@ -1,382 +1,382 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class BasicStepsTests - { - [Test] - public void BothVShouldAppendStepToNodeReference() - { - var node = new NodeReference(123); - var query = node.BothV(); - Assert.AreEqual("g.v(p0).bothV", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void OutVShouldAppendStepToNodeReference() - { - var node = new NodeReference(123); - var query = node.OutV(); - Assert.AreEqual("g.v(p0).outV", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void OutVShouldAppendStepToGremlinQuery() - { - var query = new NodeReference(123).OutV().OutV(); - Assert.AreEqual("g.v(p0).outV.outV", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void OutVShouldAppendStepToGremlinQueryWithSingleEqualFilter() - { - var query = new NodeReference(123) - .OutV(new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }, StringComparison.Ordinal); - Assert.AreEqual("g.v(p0).outV.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("Foo", query.QueryParameters["p1"]); - Assert.AreEqual("Bar", query.QueryParameters["p2"]); - } - - [Test] - public void OutVShouldAppendStepToGremlinQueryWithTwoEqualFilters() - { - var query = new NodeReference(123) - .OutV(new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal }, - new Filter { PropertyName = "Baz", Value = "Qak", ExpressionType = ExpressionType.Equal }, - }, StringComparison.Ordinal); - Assert.AreEqual("g.v(p0).outV.filter{ it[p1].equals(p2) && it[p3].equals(p4) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("Foo", query.QueryParameters["p1"]); - Assert.AreEqual("Bar", query.QueryParameters["p2"]); - Assert.AreEqual("Baz", query.QueryParameters["p3"]); - Assert.AreEqual("Qak", query.QueryParameters["p4"]); - } - - [Test] - public void OutVShouldReturnTypedGremlinEnumerable() - { - var node = new NodeReference(123); - var query = node.OutV(); - Assert.IsInstanceOf>(query); - } - - [Test] - public void OutVShouldCombineWithInE() - { - var query = new NodeReference(123).InE().OutV(); - Assert.AreEqual("g.v(p0).inE.outV", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void OutShouldAppendStepToGremlinQueryWithNoFilter() - { - var query = new NodeReference(123).Out("REL"); - Assert.AreEqual("g.v(p0).out(p1)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("REL", query.QueryParameters["p1"]); - } - - [Test] - public void OutShouldAppendStepToGremlinQueryWithSingleCaseSensitiveEqualFilter() - { - var query = new NodeReference(123) - .Out( - "REL", - new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }, StringComparison.Ordinal); - Assert.AreEqual("g.v(p0).out(p1).filter{ it[p2].equals(p3) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("REL", query.QueryParameters["p1"]); - Assert.AreEqual("Foo", query.QueryParameters["p2"]); - Assert.AreEqual("Bar", query.QueryParameters["p3"]); - } - - [Test] - public void OutShouldAppendStepToGremlinQueryWithSingleCaseInsensitiveEqualFilter() - { - var query = new NodeReference(123) - .Out( - "REL", - new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }); - Assert.AreEqual("g.v(p0).out(p1).filter{ it[p2].equalsIgnoreCase(p3) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("REL", query.QueryParameters["p1"]); - Assert.AreEqual("Foo", query.QueryParameters["p2"]); - Assert.AreEqual("Bar", query.QueryParameters["p3"]); - } - - [Test] - public void OutShouldAppendStepToGremlinQueryWithEqualFilterForTextOfEnum() - { - var query = new NodeReference(123) - .Out( - "REL", - x => x.Boo == TestEnum.Bar - ); - Assert.AreEqual("g.v(p0).out(p1).filter{ it[p2].equalsIgnoreCase(p3) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("REL", query.QueryParameters["p1"]); - Assert.AreEqual("Boo", query.QueryParameters["p2"]); - Assert.AreEqual("Bar", query.QueryParameters["p3"]); - } - - [Test] - public void InShouldAppendStepToGremlinQueryWithNoFilter() - { - var query = new NodeReference(123).In("REL"); - Assert.AreEqual("g.v(p0).in(p1)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("REL", query.QueryParameters["p1"]); - } - - [Test] - public void InShouldAppendStepToGremlinQueryWithSingleCaseSensitiveEqualFilter() - { - var query = new NodeReference(123) - .In( - "REL", - new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal} - }, StringComparison.Ordinal); - Assert.AreEqual("g.v(p0).in(p1).filter{ it[p2].equals(p3) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("REL", query.QueryParameters["p1"]); - Assert.AreEqual("Foo", query.QueryParameters["p2"]); - Assert.AreEqual("Bar", query.QueryParameters["p3"]); - } - - [Test] - public void InShouldAppendStepToGremlinQueryWithSingleCaseInsensitiveEqualFilter() - { - var query = new NodeReference(123) - .In( - "REL", - new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }); - Assert.AreEqual("g.v(p0).in(p1).filter{ it[p2].equalsIgnoreCase(p3) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("REL", query.QueryParameters["p1"]); - Assert.AreEqual("Foo", query.QueryParameters["p2"]); - Assert.AreEqual("Bar", query.QueryParameters["p3"]); - } - - [Test] - public void InVShouldAppendStepToGremlinQuery() - { - var query = new NodeReference(123).InV(); - Assert.AreEqual("g.v(p0).inV", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void InVShouldAppendStepToGremlinQueryWithSingleEqualFilter() - { - var query = new NodeReference(123) - .InV(new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }, StringComparison.Ordinal); - Assert.AreEqual("g.v(p0).inV.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("Foo", query.QueryParameters["p1"]); - Assert.AreEqual("Bar", query.QueryParameters["p2"]); - } - - [Test] - public void InVShouldAppendStepToGremlinQueryWithTwoEqualFilters() - { - var query = new NodeReference(123) - .InV(new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal }, - new Filter { PropertyName = "Baz", Value = "Qak", ExpressionType = ExpressionType.Equal }, - }, StringComparison.Ordinal); - Assert.AreEqual("g.v(p0).inV.filter{ it[p1].equals(p2) && it[p3].equals(p4) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("Foo", query.QueryParameters["p1"]); - Assert.AreEqual("Bar", query.QueryParameters["p2"]); - Assert.AreEqual("Baz", query.QueryParameters["p3"]); - Assert.AreEqual("Qak", query.QueryParameters["p4"]); - } - - [Test] - public void InVShouldReturnTypedGremlinEnumerable() - { - var node = new NodeReference(123); - var query = node.InV(); - Assert.IsInstanceOf>(query); - } - - [Test] - public void InVShouldCombineWithOutE() - { - var query = new NodeReference(123).OutE().InV(); - Assert.AreEqual("g.v(p0).outE.inV", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void BothEShouldAppendStepToGremlinQuery() - { - var query = new NodeReference(123).BothE(); - Assert.AreEqual("g.v(p0).bothE", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void OutEShouldAppendStepToGremlinQuery() - { - var query = new NodeReference(123).OutE(); - Assert.AreEqual("g.v(p0).outE", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void OutEShouldAppendStepToGremlinQueryWithLabel() - { - var query = new NodeReference(123).OutE("FOO"); - Assert.AreEqual("g.v(p0).outE.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("label", query.QueryParameters["p1"]); - Assert.AreEqual("FOO", query.QueryParameters["p2"]); - } - - [Test] - public void TypedOutEShouldAppendStepToGremlinQueryWithLabel() - { - var query = new NodeReference(123).OutE("FOO"); - Assert.AreEqual("g.v(p0).outE.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("label", query.QueryParameters["p1"]); - Assert.AreEqual("FOO", query.QueryParameters["p2"]); - } - - [Test] - public void TypedOutEShouldAppendStepToGremlinQueryWithoutLabel() - { - var query = new NodeReference(123).OutE(); - Assert.AreEqual("g.v(p0).outE", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void InEShouldAppendStepToGremlinQuery() - { - var query = new NodeReference(123).InE(); - Assert.AreEqual("g.v(p0).inE", query.QueryText); - } - - [Test] - public void InEShouldAppendStepToGremlinQueryWithLabel() - { - var query = new NodeReference(123).InE("FOO"); - Assert.AreEqual("g.v(p0).inE.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("label", query.QueryParameters["p1"]); - Assert.AreEqual("FOO", query.QueryParameters["p2"]); - } - - [Test] - public void TypedInEShouldAppendStepToGremlinQueryWithLabel() - { - var query = new NodeReference(123).InE("FOO"); - Assert.AreEqual("g.v(p0).inE.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("label", query.QueryParameters["p1"]); - Assert.AreEqual("FOO", query.QueryParameters["p2"]); - } - - [Test] - public void TypedInEShouldAppendStepToGremlinQueryWithoutLabel() - { - var query = new NodeReference(123).InE(); - Assert.AreEqual("g.v(p0).inE", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void ShouldCombineMultiStepEqualQuery() - { - var query = new NodeReference(0) - .OutE("E_FOO") - .InV(new List { new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } }, StringComparison.Ordinal) - .InE("E_BAR") - .InV(); - - Assert.AreEqual("g.v(p0).outE.filter{ it[p1].equals(p2) }.inV.filter{ it[p3].equals(p4) }.inE.filter{ it[p5].equals(p6) }.inV", query.QueryText); - Assert.AreEqual(0, query.QueryParameters["p0"]); - Assert.AreEqual("label", query.QueryParameters["p1"]); - Assert.AreEqual("E_FOO", query.QueryParameters["p2"]); - Assert.AreEqual("Foo", query.QueryParameters["p3"]); - Assert.AreEqual("Bar", query.QueryParameters["p4"]); - Assert.AreEqual("label", query.QueryParameters["p5"]); - Assert.AreEqual("E_BAR", query.QueryParameters["p6"]); - } - - [Test] - public void GremlinCountShouldExecuteScalar() - { - var client = Substitute.For(); - client - .ExecuteScalarGremlin( - "g.v(p0).count()", - Arg.Is>( - d => (long)d["p0"] == 123)) - .Returns("456"); - var node = new NodeReference(123, client); - var result = node.GremlinCount(); - Assert.AreEqual(456, result); - } - - [Test] - [ExpectedException(typeof(DetachedNodeException))] - public void GremlinCountShouldThrowDetachedNodeExceptionWhenBaseReferenceClientIsNull() - { - var node = new NodeReference(123); - node.GremlinCount(); - } - - public enum TestEnum { Bar } - - public class TestNodeWithNullableEnum - { - public TestEnum? Boo { get; set; } - } - - public class Foo - { - public string Prop1 { get; set; } - public string Prop2 { get; set; } - } - - public class Bar - { - public string Prop1 { get; set; } - public string Prop2 { get; set; } - } - } +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class BasicStepsTests + { + [Test] + public void BothVShouldAppendStepToNodeReference() + { + var node = new NodeReference(123); + var query = node.BothV(); + Assert.AreEqual("g.v(p0).bothV", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void OutVShouldAppendStepToNodeReference() + { + var node = new NodeReference(123); + var query = node.OutV(); + Assert.AreEqual("g.v(p0).outV", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void OutVShouldAppendStepToGremlinQuery() + { + var query = new NodeReference(123).OutV().OutV(); + Assert.AreEqual("g.v(p0).outV.outV", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void OutVShouldAppendStepToGremlinQueryWithSingleEqualFilter() + { + var query = new NodeReference(123) + .OutV(new List + { + new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } + }, StringComparison.Ordinal); + Assert.AreEqual("g.v(p0).outV.filter{ it[p1].equals(p2) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("Foo", query.QueryParameters["p1"]); + Assert.AreEqual("Bar", query.QueryParameters["p2"]); + } + + [Test] + public void OutVShouldAppendStepToGremlinQueryWithTwoEqualFilters() + { + var query = new NodeReference(123) + .OutV(new List + { + new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal }, + new Filter { PropertyName = "Baz", Value = "Qak", ExpressionType = ExpressionType.Equal }, + }, StringComparison.Ordinal); + Assert.AreEqual("g.v(p0).outV.filter{ it[p1].equals(p2) && it[p3].equals(p4) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("Foo", query.QueryParameters["p1"]); + Assert.AreEqual("Bar", query.QueryParameters["p2"]); + Assert.AreEqual("Baz", query.QueryParameters["p3"]); + Assert.AreEqual("Qak", query.QueryParameters["p4"]); + } + + [Test] + public void OutVShouldReturnTypedGremlinEnumerable() + { + var node = new NodeReference(123); + var query = node.OutV(); + Assert.IsInstanceOf>(query); + } + + [Test] + public void OutVShouldCombineWithInE() + { + var query = new NodeReference(123).InE().OutV(); + Assert.AreEqual("g.v(p0).inE.outV", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void OutShouldAppendStepToGremlinQueryWithNoFilter() + { + var query = new NodeReference(123).Out("REL"); + Assert.AreEqual("g.v(p0).out(p1)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("REL", query.QueryParameters["p1"]); + } + + [Test] + public void OutShouldAppendStepToGremlinQueryWithSingleCaseSensitiveEqualFilter() + { + var query = new NodeReference(123) + .Out( + "REL", + new List + { + new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } + }, StringComparison.Ordinal); + Assert.AreEqual("g.v(p0).out(p1).filter{ it[p2].equals(p3) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("REL", query.QueryParameters["p1"]); + Assert.AreEqual("Foo", query.QueryParameters["p2"]); + Assert.AreEqual("Bar", query.QueryParameters["p3"]); + } + + [Test] + public void OutShouldAppendStepToGremlinQueryWithSingleCaseInsensitiveEqualFilter() + { + var query = new NodeReference(123) + .Out( + "REL", + new List + { + new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } + }); + Assert.AreEqual("g.v(p0).out(p1).filter{ it[p2].equalsIgnoreCase(p3) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("REL", query.QueryParameters["p1"]); + Assert.AreEqual("Foo", query.QueryParameters["p2"]); + Assert.AreEqual("Bar", query.QueryParameters["p3"]); + } + + [Test] + public void OutShouldAppendStepToGremlinQueryWithEqualFilterForTextOfEnum() + { + var query = new NodeReference(123) + .Out( + "REL", + x => x.Boo == TestEnum.Bar + ); + Assert.AreEqual("g.v(p0).out(p1).filter{ it[p2].equalsIgnoreCase(p3) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("REL", query.QueryParameters["p1"]); + Assert.AreEqual("Boo", query.QueryParameters["p2"]); + Assert.AreEqual("Bar", query.QueryParameters["p3"]); + } + + [Test] + public void InShouldAppendStepToGremlinQueryWithNoFilter() + { + var query = new NodeReference(123).In("REL"); + Assert.AreEqual("g.v(p0).in(p1)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("REL", query.QueryParameters["p1"]); + } + + [Test] + public void InShouldAppendStepToGremlinQueryWithSingleCaseSensitiveEqualFilter() + { + var query = new NodeReference(123) + .In( + "REL", + new List + { + new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal} + }, StringComparison.Ordinal); + Assert.AreEqual("g.v(p0).in(p1).filter{ it[p2].equals(p3) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("REL", query.QueryParameters["p1"]); + Assert.AreEqual("Foo", query.QueryParameters["p2"]); + Assert.AreEqual("Bar", query.QueryParameters["p3"]); + } + + [Test] + public void InShouldAppendStepToGremlinQueryWithSingleCaseInsensitiveEqualFilter() + { + var query = new NodeReference(123) + .In( + "REL", + new List + { + new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } + }); + Assert.AreEqual("g.v(p0).in(p1).filter{ it[p2].equalsIgnoreCase(p3) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("REL", query.QueryParameters["p1"]); + Assert.AreEqual("Foo", query.QueryParameters["p2"]); + Assert.AreEqual("Bar", query.QueryParameters["p3"]); + } + + [Test] + public void InVShouldAppendStepToGremlinQuery() + { + var query = new NodeReference(123).InV(); + Assert.AreEqual("g.v(p0).inV", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void InVShouldAppendStepToGremlinQueryWithSingleEqualFilter() + { + var query = new NodeReference(123) + .InV(new List + { + new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } + }, StringComparison.Ordinal); + Assert.AreEqual("g.v(p0).inV.filter{ it[p1].equals(p2) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("Foo", query.QueryParameters["p1"]); + Assert.AreEqual("Bar", query.QueryParameters["p2"]); + } + + [Test] + public void InVShouldAppendStepToGremlinQueryWithTwoEqualFilters() + { + var query = new NodeReference(123) + .InV(new List + { + new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal }, + new Filter { PropertyName = "Baz", Value = "Qak", ExpressionType = ExpressionType.Equal }, + }, StringComparison.Ordinal); + Assert.AreEqual("g.v(p0).inV.filter{ it[p1].equals(p2) && it[p3].equals(p4) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("Foo", query.QueryParameters["p1"]); + Assert.AreEqual("Bar", query.QueryParameters["p2"]); + Assert.AreEqual("Baz", query.QueryParameters["p3"]); + Assert.AreEqual("Qak", query.QueryParameters["p4"]); + } + + [Test] + public void InVShouldReturnTypedGremlinEnumerable() + { + var node = new NodeReference(123); + var query = node.InV(); + Assert.IsInstanceOf>(query); + } + + [Test] + public void InVShouldCombineWithOutE() + { + var query = new NodeReference(123).OutE().InV(); + Assert.AreEqual("g.v(p0).outE.inV", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void BothEShouldAppendStepToGremlinQuery() + { + var query = new NodeReference(123).BothE(); + Assert.AreEqual("g.v(p0).bothE", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void OutEShouldAppendStepToGremlinQuery() + { + var query = new NodeReference(123).OutE(); + Assert.AreEqual("g.v(p0).outE", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void OutEShouldAppendStepToGremlinQueryWithLabel() + { + var query = new NodeReference(123).OutE("FOO"); + Assert.AreEqual("g.v(p0).outE.filter{ it[p1].equals(p2) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("label", query.QueryParameters["p1"]); + Assert.AreEqual("FOO", query.QueryParameters["p2"]); + } + + [Test] + public void TypedOutEShouldAppendStepToGremlinQueryWithLabel() + { + var query = new NodeReference(123).OutE("FOO"); + Assert.AreEqual("g.v(p0).outE.filter{ it[p1].equals(p2) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("label", query.QueryParameters["p1"]); + Assert.AreEqual("FOO", query.QueryParameters["p2"]); + } + + [Test] + public void TypedOutEShouldAppendStepToGremlinQueryWithoutLabel() + { + var query = new NodeReference(123).OutE(); + Assert.AreEqual("g.v(p0).outE", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void InEShouldAppendStepToGremlinQuery() + { + var query = new NodeReference(123).InE(); + Assert.AreEqual("g.v(p0).inE", query.QueryText); + } + + [Test] + public void InEShouldAppendStepToGremlinQueryWithLabel() + { + var query = new NodeReference(123).InE("FOO"); + Assert.AreEqual("g.v(p0).inE.filter{ it[p1].equals(p2) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("label", query.QueryParameters["p1"]); + Assert.AreEqual("FOO", query.QueryParameters["p2"]); + } + + [Test] + public void TypedInEShouldAppendStepToGremlinQueryWithLabel() + { + var query = new NodeReference(123).InE("FOO"); + Assert.AreEqual("g.v(p0).inE.filter{ it[p1].equals(p2) }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("label", query.QueryParameters["p1"]); + Assert.AreEqual("FOO", query.QueryParameters["p2"]); + } + + [Test] + public void TypedInEShouldAppendStepToGremlinQueryWithoutLabel() + { + var query = new NodeReference(123).InE(); + Assert.AreEqual("g.v(p0).inE", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void ShouldCombineMultiStepEqualQuery() + { + var query = new NodeReference(0) + .OutE("E_FOO") + .InV(new List { new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } }, StringComparison.Ordinal) + .InE("E_BAR") + .InV(); + + Assert.AreEqual("g.v(p0).outE.filter{ it[p1].equals(p2) }.inV.filter{ it[p3].equals(p4) }.inE.filter{ it[p5].equals(p6) }.inV", query.QueryText); + Assert.AreEqual(0, query.QueryParameters["p0"]); + Assert.AreEqual("label", query.QueryParameters["p1"]); + Assert.AreEqual("E_FOO", query.QueryParameters["p2"]); + Assert.AreEqual("Foo", query.QueryParameters["p3"]); + Assert.AreEqual("Bar", query.QueryParameters["p4"]); + Assert.AreEqual("label", query.QueryParameters["p5"]); + Assert.AreEqual("E_BAR", query.QueryParameters["p6"]); + } + + [Test] + public void GremlinCountShouldExecuteScalar() + { + var client = Substitute.For(); + client + .ExecuteScalarGremlin( + "g.v(p0).count()", + Arg.Is>( + d => (long)d["p0"] == 123)) + .Returns("456"); + var node = new NodeReference(123, client); + var result = node.GremlinCount(); + Assert.AreEqual(456, result); + } + + [Test] + [ExpectedException(typeof(DetachedNodeException))] + public void GremlinCountShouldThrowDetachedNodeExceptionWhenBaseReferenceClientIsNull() + { + var node = new NodeReference(123); + node.GremlinCount(); + } + + public enum TestEnum { Bar } + + public class TestNodeWithNullableEnum + { + public TestEnum? Boo { get; set; } + } + + public class Foo + { + public string Prop1 { get; set; } + public string Prop2 { get; set; } + } + + public class Bar + { + public string Prop1 { get; set; } + public string Prop2 { get; set; } + } + } } \ No newline at end of file diff --git a/Test/Gremlin/CopySplitStepTests.cs b/Neo4jClient.Tests/Gremlin/CopySplitStepTests.cs similarity index 98% rename from Test/Gremlin/CopySplitStepTests.cs rename to Neo4jClient.Tests/Gremlin/CopySplitStepTests.cs index 247fcbccf..e196a33f2 100644 --- a/Test/Gremlin/CopySplitStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/CopySplitStepTests.cs @@ -1,125 +1,125 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class CopySplitStepTests - { - [Test] - public void CopySplitVShouldAppendStepForRelationships() - { - var query = new NodeReference(123).CopySplitE(new IdentityPipe().OutE(), new IdentityPipe().OutE()); - Assert.AreEqual("g.v(p0)._.copySplit(_().outE, _().outE)", query.QueryText); - } - - [Test] - public void CopySplitVShouldAppendStepForNodes() - { - var query = new NodeReference(123).CopySplitV(new IdentityPipe().OutV(), new IdentityPipe().OutV()); - Assert.AreEqual("g.v(p0)._.copySplit(_().outV, _().outV)", query.QueryText); - } - - [Test] - public void CopySplitEShouldAppendStepAndPreserveOuterQueryParametersWithAllInlineBlocksAsIndentityPipes() - { - var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo"), new IdentityPipe().Out("bar")).Out("baz"); - Assert.AreEqual("g.v(p0)._.copySplit(_().out(p1), _().out(p2)).out(p3)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - Assert.AreEqual("bar", query.QueryParameters["p2"]); - Assert.AreEqual("baz", query.QueryParameters["p3"]); - } - - [Test] - public void CopySplitVShouldAppendStepAndPreserveOuterQueryParametersWithOneInlineBlocksAsNodeReference() - { - var node = new NodeReference(456); - var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo"), node.Out("bar")).Out("baz"); - Assert.AreEqual("g.v(p0)._.copySplit(_().out(p1), g.v(p2).out(p3)).out(p4)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - Assert.AreEqual(456, query.QueryParameters["p2"]); - Assert.AreEqual("bar", query.QueryParameters["p3"]); - Assert.AreEqual("baz", query.QueryParameters["p4"]); - } - - [Test] - public void CopySplitVShouldMoveInlineBlockVariablesToTheOuterScopeInFinallyQueryUsingAggregateV() - { - var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo").AggregateV("xyz"), new IdentityPipe().Out("bar")).Out("baz"); - Assert.AreEqual("xyz = [];g.v(p0)._.copySplit(_().out(p1).aggregate(xyz), _().out(p2)).out(p3)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - Assert.AreEqual("bar", query.QueryParameters["p2"]); - Assert.AreEqual("baz", query.QueryParameters["p3"]); - } - - [Test] - public void CopySplitVShouldMoveInlineBlockVariablesToTheOuterScopeInFinallyQueryUsingStoreV() - { - var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo").StoreV("xyz"), new IdentityPipe().Out("bar")).Out("baz"); - Assert.AreEqual("xyz = [];g.v(p0)._.copySplit(_().out(p1).store(xyz), _().out(p2)).out(p3)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - Assert.AreEqual("bar", query.QueryParameters["p2"]); - Assert.AreEqual("baz", query.QueryParameters["p3"]); - } - - [Test] - public void CopySplitVShouldMoveInlineBlockVariablesToTheOuterScopeInFinallyQueryUsingStoreVAndFilters() - { - var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo", t=> t.Flag == true).StoreV("xyz"), new IdentityPipe().Out("bar")).Out("baz", t=> t.Flag == true ); - Assert.AreEqual("xyz = [];g.v(p0)._.copySplit(_().out(p1).filter{ it[p2] == p3 }.store(xyz), _().out(p4)).out(p5).filter{ it[p6] == p7 }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - Assert.AreEqual("Flag", query.QueryParameters["p2"]); - Assert.AreEqual(true, query.QueryParameters["p3"]); - Assert.AreEqual("bar", query.QueryParameters["p4"]); - Assert.AreEqual("baz", query.QueryParameters["p5"]); - Assert.AreEqual("Flag", query.QueryParameters["p6"]); - Assert.AreEqual(true, query.QueryParameters["p7"]); - } - - [Test] - public void CopySplitVShouldMoveInlineBlockVariablesToTheOuterScopeInFinallyQueryUsingStoreVAndFiltersMultipleVariables() - { - var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo", t => t.Flag == true).StoreV("xyz"), new IdentityPipe().Out("bar")).Out("baz", t => t.Flag == true).AggregateE("sad"); - Assert.AreEqual("sad = [];xyz = [];g.v(p0)._.copySplit(_().out(p1).filter{ it[p2] == p3 }.store(xyz), _().out(p4)).out(p5).filter{ it[p6] == p7 }.aggregate(sad)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - Assert.AreEqual("Flag", query.QueryParameters["p2"]); - Assert.AreEqual(true, query.QueryParameters["p3"]); - Assert.AreEqual("bar", query.QueryParameters["p4"]); - Assert.AreEqual("baz", query.QueryParameters["p5"]); - Assert.AreEqual("Flag", query.QueryParameters["p6"]); - Assert.AreEqual(true, query.QueryParameters["p7"]); - } - - [Test] - public void ShouldNumberParamtersCorrectlyInNestedQueryWithMoreThan10ParametersInTotal() - { - var query = new NodeReference(0) - .CopySplitE( - new IdentityPipe() - .Out("REL1", a => a.Text == "text 1") - .In("REL2") - .Out("REL3") - .In("REL4") - .In("REL5", r => r.Flag == false) - .StoreV("ReferralWithCentres"), - new IdentityPipe() - .Out("REL6", a => a.Text == "text 2") - ); - Assert.AreEqual( - "ReferralWithCentres = [];g.v(p0)._.copySplit(_().out(p1).filter{ it[p2].equalsIgnoreCase(p3) }.in(p4).out(p5).in(p6).in(p7).filter{ it[p8] == p9 }.store(ReferralWithCentres), _().out(p10).filter{ it[p11].equalsIgnoreCase(p12) })", - query.QueryText); - } - - public class Test - { - public bool Flag { get; set; } - public string Text { get; set; } - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class CopySplitStepTests + { + [Test] + public void CopySplitVShouldAppendStepForRelationships() + { + var query = new NodeReference(123).CopySplitE(new IdentityPipe().OutE(), new IdentityPipe().OutE()); + Assert.AreEqual("g.v(p0)._.copySplit(_().outE, _().outE)", query.QueryText); + } + + [Test] + public void CopySplitVShouldAppendStepForNodes() + { + var query = new NodeReference(123).CopySplitV(new IdentityPipe().OutV(), new IdentityPipe().OutV()); + Assert.AreEqual("g.v(p0)._.copySplit(_().outV, _().outV)", query.QueryText); + } + + [Test] + public void CopySplitEShouldAppendStepAndPreserveOuterQueryParametersWithAllInlineBlocksAsIndentityPipes() + { + var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo"), new IdentityPipe().Out("bar")).Out("baz"); + Assert.AreEqual("g.v(p0)._.copySplit(_().out(p1), _().out(p2)).out(p3)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + Assert.AreEqual("bar", query.QueryParameters["p2"]); + Assert.AreEqual("baz", query.QueryParameters["p3"]); + } + + [Test] + public void CopySplitVShouldAppendStepAndPreserveOuterQueryParametersWithOneInlineBlocksAsNodeReference() + { + var node = new NodeReference(456); + var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo"), node.Out("bar")).Out("baz"); + Assert.AreEqual("g.v(p0)._.copySplit(_().out(p1), g.v(p2).out(p3)).out(p4)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + Assert.AreEqual(456, query.QueryParameters["p2"]); + Assert.AreEqual("bar", query.QueryParameters["p3"]); + Assert.AreEqual("baz", query.QueryParameters["p4"]); + } + + [Test] + public void CopySplitVShouldMoveInlineBlockVariablesToTheOuterScopeInFinallyQueryUsingAggregateV() + { + var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo").AggregateV("xyz"), new IdentityPipe().Out("bar")).Out("baz"); + Assert.AreEqual("xyz = [];g.v(p0)._.copySplit(_().out(p1).aggregate(xyz), _().out(p2)).out(p3)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + Assert.AreEqual("bar", query.QueryParameters["p2"]); + Assert.AreEqual("baz", query.QueryParameters["p3"]); + } + + [Test] + public void CopySplitVShouldMoveInlineBlockVariablesToTheOuterScopeInFinallyQueryUsingStoreV() + { + var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo").StoreV("xyz"), new IdentityPipe().Out("bar")).Out("baz"); + Assert.AreEqual("xyz = [];g.v(p0)._.copySplit(_().out(p1).store(xyz), _().out(p2)).out(p3)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + Assert.AreEqual("bar", query.QueryParameters["p2"]); + Assert.AreEqual("baz", query.QueryParameters["p3"]); + } + + [Test] + public void CopySplitVShouldMoveInlineBlockVariablesToTheOuterScopeInFinallyQueryUsingStoreVAndFilters() + { + var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo", t=> t.Flag == true).StoreV("xyz"), new IdentityPipe().Out("bar")).Out("baz", t=> t.Flag == true ); + Assert.AreEqual("xyz = [];g.v(p0)._.copySplit(_().out(p1).filter{ it[p2] == p3 }.store(xyz), _().out(p4)).out(p5).filter{ it[p6] == p7 }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + Assert.AreEqual("Flag", query.QueryParameters["p2"]); + Assert.AreEqual(true, query.QueryParameters["p3"]); + Assert.AreEqual("bar", query.QueryParameters["p4"]); + Assert.AreEqual("baz", query.QueryParameters["p5"]); + Assert.AreEqual("Flag", query.QueryParameters["p6"]); + Assert.AreEqual(true, query.QueryParameters["p7"]); + } + + [Test] + public void CopySplitVShouldMoveInlineBlockVariablesToTheOuterScopeInFinallyQueryUsingStoreVAndFiltersMultipleVariables() + { + var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo", t => t.Flag == true).StoreV("xyz"), new IdentityPipe().Out("bar")).Out("baz", t => t.Flag == true).AggregateE("sad"); + Assert.AreEqual("sad = [];xyz = [];g.v(p0)._.copySplit(_().out(p1).filter{ it[p2] == p3 }.store(xyz), _().out(p4)).out(p5).filter{ it[p6] == p7 }.aggregate(sad)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + Assert.AreEqual("Flag", query.QueryParameters["p2"]); + Assert.AreEqual(true, query.QueryParameters["p3"]); + Assert.AreEqual("bar", query.QueryParameters["p4"]); + Assert.AreEqual("baz", query.QueryParameters["p5"]); + Assert.AreEqual("Flag", query.QueryParameters["p6"]); + Assert.AreEqual(true, query.QueryParameters["p7"]); + } + + [Test] + public void ShouldNumberParamtersCorrectlyInNestedQueryWithMoreThan10ParametersInTotal() + { + var query = new NodeReference(0) + .CopySplitE( + new IdentityPipe() + .Out("REL1", a => a.Text == "text 1") + .In("REL2") + .Out("REL3") + .In("REL4") + .In("REL5", r => r.Flag == false) + .StoreV("ReferralWithCentres"), + new IdentityPipe() + .Out("REL6", a => a.Text == "text 2") + ); + Assert.AreEqual( + "ReferralWithCentres = [];g.v(p0)._.copySplit(_().out(p1).filter{ it[p2].equalsIgnoreCase(p3) }.in(p4).out(p5).in(p6).in(p7).filter{ it[p8] == p9 }.store(ReferralWithCentres), _().out(p10).filter{ it[p11].equalsIgnoreCase(p12) })", + query.QueryText); + } + + public class Test + { + public bool Flag { get; set; } + public string Text { get; set; } + } + } } \ No newline at end of file diff --git a/Test/Gremlin/EmitPropertyStepTests.cs b/Neo4jClient.Tests/Gremlin/EmitPropertyStepTests.cs similarity index 97% rename from Test/Gremlin/EmitPropertyStepTests.cs rename to Neo4jClient.Tests/Gremlin/EmitPropertyStepTests.cs index 030e04944..7c4598a96 100644 --- a/Test/Gremlin/EmitPropertyStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/EmitPropertyStepTests.cs @@ -1,36 +1,36 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class EmitPropertyStepTests - { - [Test] - public void EmitPropertyShouldAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().EmitProperty("foo"); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outV.foo", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void EmitPropertyShouldAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().EmitProperty("foo"); - Assert.IsInstanceOf(query); - Assert.AreEqual("g.v(p0).outE.foo", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void EmitPropertyShouldAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().EmitProperty("foo"); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outE.foo", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class EmitPropertyStepTests + { + [Test] + public void EmitPropertyShouldAppendStepToNodeQuery() + { + var query = new NodeReference(123).OutV().EmitProperty("foo"); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outV.foo", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void EmitPropertyShouldAppendStepToRelationshipQuery() + { + var query = new NodeReference(123).OutE().EmitProperty("foo"); + Assert.IsInstanceOf(query); + Assert.AreEqual("g.v(p0).outE.foo", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void EmitPropertyShouldAppendStepToTypedRelationshipQuery() + { + var query = new NodeReference(123).OutE().EmitProperty("foo"); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outE.foo", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/ExceptStepTests.cs b/Neo4jClient.Tests/Gremlin/ExceptStepTests.cs similarity index 97% rename from Test/Gremlin/ExceptStepTests.cs rename to Neo4jClient.Tests/Gremlin/ExceptStepTests.cs index 7a01d4c7d..b17eb95ef 100644 --- a/Test/Gremlin/ExceptStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/ExceptStepTests.cs @@ -1,54 +1,54 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class ExceptStepTests - { - [Test] - public void ExceptVShouldAppendStep() - { - var query = new NodeReference(123).ExceptV("foo"); - Assert.AreEqual("g.v(p0).except(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void ExceptVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).ExceptV("foo"); - Assert.IsInstanceOf>(query); - } - - [Test] - public void ExceptEShouldAppendStep() - { - var query = new NodeReference(123).ExceptE("foo"); - Assert.AreEqual("g.v(p0).except(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void ExceptEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).ExceptE("foo"); - Assert.IsInstanceOf(query); - } - - [Test] - public void ExceptEWithTDataShouldAppendStep() - { - var query = new NodeReference(123).ExceptE("foo"); - Assert.AreEqual("g.v(p0).except(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void ExceptEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).ExceptE("foo"); - Assert.IsInstanceOf>(query); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class ExceptStepTests + { + [Test] + public void ExceptVShouldAppendStep() + { + var query = new NodeReference(123).ExceptV("foo"); + Assert.AreEqual("g.v(p0).except(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void ExceptVShouldReturnTypedNodeEnumerable() + { + var query = new NodeReference(123).ExceptV("foo"); + Assert.IsInstanceOf>(query); + } + + [Test] + public void ExceptEShouldAppendStep() + { + var query = new NodeReference(123).ExceptE("foo"); + Assert.AreEqual("g.v(p0).except(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void ExceptEShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).ExceptE("foo"); + Assert.IsInstanceOf(query); + } + + [Test] + public void ExceptEWithTDataShouldAppendStep() + { + var query = new NodeReference(123).ExceptE("foo"); + Assert.AreEqual("g.v(p0).except(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void ExceptEWithTDataShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).ExceptE("foo"); + Assert.IsInstanceOf>(query); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/ExhaustMergeStepTests.cs b/Neo4jClient.Tests/Gremlin/ExhaustMergeStepTests.cs similarity index 97% rename from Test/Gremlin/ExhaustMergeStepTests.cs rename to Neo4jClient.Tests/Gremlin/ExhaustMergeStepTests.cs index cc7a4651c..379fb9efb 100644 --- a/Test/Gremlin/ExhaustMergeStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/ExhaustMergeStepTests.cs @@ -1,36 +1,36 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class ExhaustMergeStepTests - { - [Test] - public void ExhaustMergeAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().ExhaustMerge(); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outV.exhaustMerge", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void ExhaustMergeAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().ExhaustMerge(); - Assert.IsInstanceOf(query); - Assert.AreEqual("g.v(p0).outE.exhaustMerge", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void ExhaustMergeAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().ExhaustMerge(); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outE.exhaustMerge", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class ExhaustMergeStepTests + { + [Test] + public void ExhaustMergeAppendStepToNodeQuery() + { + var query = new NodeReference(123).OutV().ExhaustMerge(); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outV.exhaustMerge", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void ExhaustMergeAppendStepToRelationshipQuery() + { + var query = new NodeReference(123).OutE().ExhaustMerge(); + Assert.IsInstanceOf(query); + Assert.AreEqual("g.v(p0).outE.exhaustMerge", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void ExhaustMergeAppendStepToTypedRelationshipQuery() + { + var query = new NodeReference(123).OutE().ExhaustMerge(); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outE.exhaustMerge", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/FairMergeStepTests.cs b/Neo4jClient.Tests/Gremlin/FairMergeStepTests.cs similarity index 97% rename from Test/Gremlin/FairMergeStepTests.cs rename to Neo4jClient.Tests/Gremlin/FairMergeStepTests.cs index 84ae90aeb..c899ec143 100644 --- a/Test/Gremlin/FairMergeStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/FairMergeStepTests.cs @@ -1,36 +1,36 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class FairMergeStepTests - { - [Test] - public void FairMergeShouldAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().FairMerge(); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outV.fairMerge", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void FairMergeShouldAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().FairMerge(); - Assert.IsInstanceOf(query); - Assert.AreEqual("g.v(p0).outE.fairMerge", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void FairMergeShouldAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().FairMerge(); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outE.fairMerge", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class FairMergeStepTests + { + [Test] + public void FairMergeShouldAppendStepToNodeQuery() + { + var query = new NodeReference(123).OutV().FairMerge(); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outV.fairMerge", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void FairMergeShouldAppendStepToRelationshipQuery() + { + var query = new NodeReference(123).OutE().FairMerge(); + Assert.IsInstanceOf(query); + Assert.AreEqual("g.v(p0).outE.fairMerge", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void FairMergeShouldAppendStepToTypedRelationshipQuery() + { + var query = new NodeReference(123).OutE().FairMerge(); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outE.fairMerge", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/FormatGremlinFilterTests.cs b/Neo4jClient.Tests/Gremlin/FormatGremlinFilterTests.cs similarity index 98% rename from Test/Gremlin/FormatGremlinFilterTests.cs rename to Neo4jClient.Tests/Gremlin/FormatGremlinFilterTests.cs index cede4c3de..38e67bbab 100644 --- a/Test/Gremlin/FormatGremlinFilterTests.cs +++ b/Neo4jClient.Tests/Gremlin/FormatGremlinFilterTests.cs @@ -1,703 +1,703 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class FormatGremlinFilterTests - { - [Test] - public void FormatGremlinFilterShouldSupportGuidTypeInEqualsExpression() - { - const string guidString = "1a4e451c-aa87-4388-9b53-5d00b05ac728"; - var guidValue = Guid.Parse(guidString); - - var filters = new List - { - new Filter { PropertyName= "Foo", Value = guidValue, ExpressionType = ExpressionType.Equal } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(guidString, filter.FilterParameters["p1"].ToString()); - } - - [Test] - public void FormatGremlinFilterShouldSupportGuidTypeInNotEqualsExpression() - { - const string guidString = "1a4e451c-aa87-4388-9b53-5d00b05ac728"; - var guidValue = Guid.Parse(guidString); - - var filters = new List - { - new Filter { PropertyName= "Foo", Value = guidValue, ExpressionType = ExpressionType.NotEqual } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] != p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(guidString, filter.FilterParameters["p1"].ToString()); - } - - [Test] - public void FormatGremlinFilterShouldReturnEmptyStringForNoCaseSensititiveFilters() - { - var filters = new List(); - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(string.Empty, filter.FilterText); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithStringValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0].equals(p1) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithIntValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.Equal } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEquakFilterWithLongValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = (long)123, ExpressionType = ExpressionType.Equal } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithLongMaxValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.Equal } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithEnumValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = EnumForTesting.Bar, ExpressionType = ExpressionType.Equal } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0].equals(p1) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithNullableEnumValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = (EnumForTesting?)EnumForTesting.Bar, ExpressionType = ExpressionType.Equal } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0].equals(p1) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithNullValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = null, ExpressionType = ExpressionType.Equal } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] == null }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForMultipleCaseSensititiveEqualFiltersWithStringValues() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal }, - new Filter { PropertyName= "Baz", Value = "Qak", ExpressionType = ExpressionType.Equal } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0].equals(p1) && it[p2].equals(p3) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - Assert.AreEqual("Baz", filter.FilterParameters["p2"]); - Assert.AreEqual("Qak", filter.FilterParameters["p3"]); - } - - [Test] - [ExpectedException( - typeof(NotSupportedException), - ExpectedMessage = "One or more of the supplied filters is of an unsupported type or expression. Unsupported filters were: Foo of type System.ThreadStaticAttribute, with expression Equal")] - public void FormatGremlinFilterShouldThrowNotSupportedExceptionForCaseSensitiveEqualFilterOfUnsupportedType() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = new ThreadStaticAttribute(), ExpressionType = ExpressionType.Equal }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - } - - [Test] - public void FormatGremlinFilterShouldReturnEmptyStringForNoCaseInsensitiveFilters() - { - var filters = new List(); - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(string.Empty, filter.FilterText); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithStringValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0].equalsIgnoreCase(p1) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithIntValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.Equal }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithLongValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = (long)123, ExpressionType = ExpressionType.Equal }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithLongMaxValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.Equal }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithEnumValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = EnumForTesting.Bar, ExpressionType = ExpressionType.Equal }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0].equalsIgnoreCase(p1) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithNullableEnumValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = (EnumForTesting?)EnumForTesting.Bar, ExpressionType = ExpressionType.Equal }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0].equalsIgnoreCase(p1) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForMultipleCaseInsensititiveEqualFiltersWithStringValues() - { - var filters = new List - { - new Filter {PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal }, - new Filter {PropertyName = "Baz", Value = "Qak", ExpressionType = ExpressionType.Equal }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0].equalsIgnoreCase(p1) && it[p2].equalsIgnoreCase(p3) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - Assert.AreEqual("Baz", filter.FilterParameters["p2"]); - Assert.AreEqual("Qak", filter.FilterParameters["p3"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithNullValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = null, ExpressionType = ExpressionType.Equal}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] == null }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveNotEqualFilterStringValue() - { - var filters = new List - { - new Filter {PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.NotEqual} - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ !it[p0].equalsIgnoreCase(p1) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveNotEqualFilterMaxLongValue() - { - var filters = new List - { - new Filter { PropertyName = "Foo", Value = 9223372036854775807, ExpressionType = ExpressionType.NotEqual }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] != p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveNotEqualFilterZeroLongValue() - { - var filters = new List - { - new Filter { PropertyName = "Foo", Value = 0L, ExpressionType = ExpressionType.NotEqual }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] != p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(0, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseSensititiveNotEqualFilterStringValue() - { - var filters = new List - { - new Filter {PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.NotEqual} - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ !it[p0].equals(p1) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseSensititiveNotEqualFilterMaxLongValue() - { - var filters = new List - { - new Filter { PropertyName = "Foo", Value = 9223372036854775807, ExpressionType = ExpressionType.NotEqual }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] != p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseSensititiveNotEqualFilterZeroLongValue() - { - var filters = new List - { - new Filter { PropertyName = "Foo", Value = 0L, ExpressionType = ExpressionType.NotEqual }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] != p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(0, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveNotEqualFilterWithEnumValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = EnumForTesting.Bar, ExpressionType = ExpressionType.NotEqual } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ !it[p0].equals(p1) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveNotEqualFilterWithEnumValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = EnumForTesting.Bar, ExpressionType = ExpressionType.NotEqual } - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ !it[p0].equalsIgnoreCase(p1) }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual("Bar", filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveGreaterThanFilterWithIntValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.GreaterThan}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] > p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveGreaterThanFilterWithIntValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.GreaterThan}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] > p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveLessThanFilterWithIntValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.LessThan}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] < p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveLessThanFilterWithIntValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.LessThan}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] < p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveGreaterThanFilterWithLongValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.GreaterThan}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] > p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveGreaterThanFilterWithLongValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.GreaterThan}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] > p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveLessThanFilterWithLongValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.LessThan}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] < p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveLessThanFilterWithLongValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.LessThan}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] < p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveGreaterThanOrEqualFilterWithIntValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.GreaterThanOrEqual}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] >= p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveGreaterThanOrEqualFilterWithIntValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.GreaterThanOrEqual}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] >= p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveLessThanOrEqualFilterWithIntValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.LessThanOrEqual}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] <= p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveLessThanOrEqualFilterWithIntValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.LessThanOrEqual}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] <= p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(123, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveGreaterThanOrEqualFilterWithLongValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.GreaterThanOrEqual}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] >= p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveGreaterThanOrEqualFilterWithLongValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.GreaterThanOrEqual}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] >= p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveLessThanOrEqualFilterWithLongValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.LessThanOrEqual}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - Assert.AreEqual(".filter{ it[p0] <= p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveLessThanOrEqualFilterWithLongValue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.LessThanOrEqual}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] <= p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Test] - [ExpectedException( - typeof(NotSupportedException), - ExpectedMessage = "One or more of the supplied filters is of an unsupported type or expression. Unsupported filters were: Foo of type System.ThreadStaticAttribute, with expression Equal")] - public void FormatGremlinFilterShouldThrowNotSupportedExceptionForCaseInsensitiveEqualFilterOfUnsupportedType() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = new ThreadStaticAttribute(), ExpressionType = ExpressionType.Equal }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - } - - [Test] - [ExpectedException( - typeof(NotSupportedException), - ExpectedMessage = "One or more of the supplied filters is of an unsupported type or expression. Unsupported filters were: Foo with null value and expression Divide")] - public void FormatGremlinFilterShouldThrowNotSupportedExceptionForExpressionsThatAreNotRegisteredInFilterTypesToCompareNulls() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = null, ExpressionType = ExpressionType.Divide }, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleEqualFilterWithBoolValueTrue() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = true, ExpressionType = ExpressionType.Equal}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(true, filter.FilterParameters["p1"]); - } - - [Test] - public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleEqualFilterWithBoolValueFalse() - { - var filters = new List - { - new Filter { PropertyName= "Foo", Value = false, ExpressionType = ExpressionType.Equal}, - }; - var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); - var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); - Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.AreEqual("Foo", filter.FilterParameters["p0"]); - Assert.AreEqual(false, filter.FilterParameters["p1"]); - } - - enum EnumForTesting - { - Bar - } - } -} +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class FormatGremlinFilterTests + { + [Test] + public void FormatGremlinFilterShouldSupportGuidTypeInEqualsExpression() + { + const string guidString = "1a4e451c-aa87-4388-9b53-5d00b05ac728"; + var guidValue = Guid.Parse(guidString); + + var filters = new List + { + new Filter { PropertyName= "Foo", Value = guidValue, ExpressionType = ExpressionType.Equal } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(guidString, filter.FilterParameters["p1"].ToString()); + } + + [Test] + public void FormatGremlinFilterShouldSupportGuidTypeInNotEqualsExpression() + { + const string guidString = "1a4e451c-aa87-4388-9b53-5d00b05ac728"; + var guidValue = Guid.Parse(guidString); + + var filters = new List + { + new Filter { PropertyName= "Foo", Value = guidValue, ExpressionType = ExpressionType.NotEqual } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] != p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(guidString, filter.FilterParameters["p1"].ToString()); + } + + [Test] + public void FormatGremlinFilterShouldReturnEmptyStringForNoCaseSensititiveFilters() + { + var filters = new List(); + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(string.Empty, filter.FilterText); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithStringValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0].equals(p1) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithIntValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.Equal } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEquakFilterWithLongValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = (long)123, ExpressionType = ExpressionType.Equal } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithLongMaxValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.Equal } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithEnumValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = EnumForTesting.Bar, ExpressionType = ExpressionType.Equal } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0].equals(p1) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithNullableEnumValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = (EnumForTesting?)EnumForTesting.Bar, ExpressionType = ExpressionType.Equal } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0].equals(p1) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveEqualFilterWithNullValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = null, ExpressionType = ExpressionType.Equal } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] == null }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForMultipleCaseSensititiveEqualFiltersWithStringValues() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal }, + new Filter { PropertyName= "Baz", Value = "Qak", ExpressionType = ExpressionType.Equal } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0].equals(p1) && it[p2].equals(p3) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + Assert.AreEqual("Baz", filter.FilterParameters["p2"]); + Assert.AreEqual("Qak", filter.FilterParameters["p3"]); + } + + [Test] + [ExpectedException( + typeof(NotSupportedException), + ExpectedMessage = "One or more of the supplied filters is of an unsupported type or expression. Unsupported filters were: Foo of type System.ThreadStaticAttribute, with expression Equal")] + public void FormatGremlinFilterShouldThrowNotSupportedExceptionForCaseSensitiveEqualFilterOfUnsupportedType() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = new ThreadStaticAttribute(), ExpressionType = ExpressionType.Equal }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + } + + [Test] + public void FormatGremlinFilterShouldReturnEmptyStringForNoCaseInsensitiveFilters() + { + var filters = new List(); + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(string.Empty, filter.FilterText); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithStringValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0].equalsIgnoreCase(p1) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithIntValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.Equal }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithLongValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = (long)123, ExpressionType = ExpressionType.Equal }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithLongMaxValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.Equal }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithEnumValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = EnumForTesting.Bar, ExpressionType = ExpressionType.Equal }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0].equalsIgnoreCase(p1) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithNullableEnumValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = (EnumForTesting?)EnumForTesting.Bar, ExpressionType = ExpressionType.Equal }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0].equalsIgnoreCase(p1) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForMultipleCaseInsensititiveEqualFiltersWithStringValues() + { + var filters = new List + { + new Filter {PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal }, + new Filter {PropertyName = "Baz", Value = "Qak", ExpressionType = ExpressionType.Equal }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0].equalsIgnoreCase(p1) && it[p2].equalsIgnoreCase(p3) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + Assert.AreEqual("Baz", filter.FilterParameters["p2"]); + Assert.AreEqual("Qak", filter.FilterParameters["p3"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveEqualFilterWithNullValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = null, ExpressionType = ExpressionType.Equal}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] == null }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveNotEqualFilterStringValue() + { + var filters = new List + { + new Filter {PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.NotEqual} + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ !it[p0].equalsIgnoreCase(p1) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveNotEqualFilterMaxLongValue() + { + var filters = new List + { + new Filter { PropertyName = "Foo", Value = 9223372036854775807, ExpressionType = ExpressionType.NotEqual }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] != p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseInsensititiveNotEqualFilterZeroLongValue() + { + var filters = new List + { + new Filter { PropertyName = "Foo", Value = 0L, ExpressionType = ExpressionType.NotEqual }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] != p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(0, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseSensititiveNotEqualFilterStringValue() + { + var filters = new List + { + new Filter {PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.NotEqual} + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ !it[p0].equals(p1) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseSensititiveNotEqualFilterMaxLongValue() + { + var filters = new List + { + new Filter { PropertyName = "Foo", Value = 9223372036854775807, ExpressionType = ExpressionType.NotEqual }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] != p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIteratorSyntaxForSingleCaseSensititiveNotEqualFilterZeroLongValue() + { + var filters = new List + { + new Filter { PropertyName = "Foo", Value = 0L, ExpressionType = ExpressionType.NotEqual }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] != p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(0, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveNotEqualFilterWithEnumValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = EnumForTesting.Bar, ExpressionType = ExpressionType.NotEqual } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ !it[p0].equals(p1) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveNotEqualFilterWithEnumValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = EnumForTesting.Bar, ExpressionType = ExpressionType.NotEqual } + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ !it[p0].equalsIgnoreCase(p1) }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual("Bar", filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveGreaterThanFilterWithIntValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.GreaterThan}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] > p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveGreaterThanFilterWithIntValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.GreaterThan}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] > p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveLessThanFilterWithIntValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.LessThan}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] < p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveLessThanFilterWithIntValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.LessThan}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] < p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveGreaterThanFilterWithLongValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.GreaterThan}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] > p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveGreaterThanFilterWithLongValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.GreaterThan}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] > p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveLessThanFilterWithLongValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.LessThan}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] < p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveLessThanFilterWithLongValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.LessThan}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] < p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveGreaterThanOrEqualFilterWithIntValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.GreaterThanOrEqual}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] >= p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveGreaterThanOrEqualFilterWithIntValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.GreaterThanOrEqual}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] >= p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveLessThanOrEqualFilterWithIntValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.LessThanOrEqual}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] <= p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveLessThanOrEqualFilterWithIntValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = 123, ExpressionType = ExpressionType.LessThanOrEqual}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] <= p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(123, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveGreaterThanOrEqualFilterWithLongValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.GreaterThanOrEqual}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] >= p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveGreaterThanOrEqualFilterWithLongValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.GreaterThanOrEqual}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] >= p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseInsensititiveLessThanOrEqualFilterWithLongValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.LessThanOrEqual}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + Assert.AreEqual(".filter{ it[p0] <= p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleCaseSensititiveLessThanOrEqualFilterWithLongValue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = long.MaxValue, ExpressionType = ExpressionType.LessThanOrEqual}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] <= p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(9223372036854775807, filter.FilterParameters["p1"]); + } + + [Test] + [ExpectedException( + typeof(NotSupportedException), + ExpectedMessage = "One or more of the supplied filters is of an unsupported type or expression. Unsupported filters were: Foo of type System.ThreadStaticAttribute, with expression Equal")] + public void FormatGremlinFilterShouldThrowNotSupportedExceptionForCaseInsensitiveEqualFilterOfUnsupportedType() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = new ThreadStaticAttribute(), ExpressionType = ExpressionType.Equal }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + } + + [Test] + [ExpectedException( + typeof(NotSupportedException), + ExpectedMessage = "One or more of the supplied filters is of an unsupported type or expression. Unsupported filters were: Foo with null value and expression Divide")] + public void FormatGremlinFilterShouldThrowNotSupportedExceptionForExpressionsThatAreNotRegisteredInFilterTypesToCompareNulls() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = null, ExpressionType = ExpressionType.Divide }, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleEqualFilterWithBoolValueTrue() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = true, ExpressionType = ExpressionType.Equal}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(true, filter.FilterParameters["p1"]); + } + + [Test] + public void FormatGremlinFilterShouldReturnIndexerSyntaxForSingleEqualFilterWithBoolValueFalse() + { + var filters = new List + { + new Filter { PropertyName= "Foo", Value = false, ExpressionType = ExpressionType.Equal}, + }; + var baseQuery = new GremlinQuery(null, null, new Dictionary(), new List()); + var filter = FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery); + Assert.AreEqual(".filter{ it[p0] == p1 }", filter.FilterText); + Assert.AreEqual("Foo", filter.FilterParameters["p0"]); + Assert.AreEqual(false, filter.FilterParameters["p1"]); + } + + enum EnumForTesting + { + Bar + } + } +} diff --git a/Test/Gremlin/GremlinClientTests.cs b/Neo4jClient.Tests/Gremlin/GremlinClientTests.cs similarity index 96% rename from Test/Gremlin/GremlinClientTests.cs rename to Neo4jClient.Tests/Gremlin/GremlinClientTests.cs index dfd0d45ea..30695e047 100644 --- a/Test/Gremlin/GremlinClientTests.cs +++ b/Neo4jClient.Tests/Gremlin/GremlinClientTests.cs @@ -1,59 +1,59 @@ -using System.Collections.Generic; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class GremlinClientTests - { - [Test] - public void VShouldEumerateAllVertexes() - { - var client = Substitute.For(); - var gremlinClient = new GremlinClient(client); - var query = gremlinClient.V; - Assert.AreEqual("g.V", query.QueryText); - } - - [Test] - public void VShouldCombineWithGremlinCount() - { - var client = Substitute.For(); - client - .ExecuteScalarGremlin("g.V.count()", Arg.Is((IDictionary d) => d.Count == 0)) - .Returns("123"); - - var gremlinClient = new GremlinClient(client); - client.Gremlin.Returns(gremlinClient); - - var result = gremlinClient.V.GremlinCount(); - Assert.AreEqual(123, result); - } - - [Test] - public void EShouldEumerateAllEdges() - { - var client = Substitute.For(); - var gremlinClient = new GremlinClient(client); - var query = gremlinClient.E; - Assert.AreEqual("g.E", query.QueryText); - } - - [Test] - public void EShouldCombineWithGremlinCount() - { - var client = Substitute.For(); - client - .ExecuteScalarGremlin("g.E.count()", Arg.Is((IDictionary d) => d.Count == 0)) - .Returns("123"); - - var gremlinClient = new GremlinClient(client); - client.Gremlin.Returns(gremlinClient); - - var result = gremlinClient.E.GremlinCount(); - Assert.AreEqual(123, result); - } - } -} +using System.Collections.Generic; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class GremlinClientTests + { + [Test] + public void VShouldEumerateAllVertexes() + { + var client = Substitute.For(); + var gremlinClient = new GremlinClient(client); + var query = gremlinClient.V; + Assert.AreEqual("g.V", query.QueryText); + } + + [Test] + public void VShouldCombineWithGremlinCount() + { + var client = Substitute.For(); + client + .ExecuteScalarGremlin("g.V.count()", Arg.Is((IDictionary d) => d.Count == 0)) + .Returns("123"); + + var gremlinClient = new GremlinClient(client); + client.Gremlin.Returns(gremlinClient); + + var result = gremlinClient.V.GremlinCount(); + Assert.AreEqual(123, result); + } + + [Test] + public void EShouldEumerateAllEdges() + { + var client = Substitute.For(); + var gremlinClient = new GremlinClient(client); + var query = gremlinClient.E; + Assert.AreEqual("g.E", query.QueryText); + } + + [Test] + public void EShouldCombineWithGremlinCount() + { + var client = Substitute.For(); + client + .ExecuteScalarGremlin("g.E.count()", Arg.Is((IDictionary d) => d.Count == 0)) + .Returns("123"); + + var gremlinClient = new GremlinClient(client); + client.Gremlin.Returns(gremlinClient); + + var result = gremlinClient.E.GremlinCount(); + Assert.AreEqual(123, result); + } + } +} diff --git a/Test/Gremlin/GremlinDistinctStepTests.cs b/Neo4jClient.Tests/Gremlin/GremlinDistinctStepTests.cs similarity index 97% rename from Test/Gremlin/GremlinDistinctStepTests.cs rename to Neo4jClient.Tests/Gremlin/GremlinDistinctStepTests.cs index 89370fedd..5099aa39e 100644 --- a/Test/Gremlin/GremlinDistinctStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/GremlinDistinctStepTests.cs @@ -1,36 +1,36 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class GremlinDistinctStepTests - { - [Test] - public void GremlinDistinctAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().GremlinDistinct(); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outV.dedup()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void GremlinDistinctAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().GremlinDistinct(); - Assert.IsInstanceOf(query); - Assert.AreEqual("g.v(p0).outE.dedup()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void GremlinDistinctAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().GremlinDistinct(); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outE.dedup()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class GremlinDistinctStepTests + { + [Test] + public void GremlinDistinctAppendStepToNodeQuery() + { + var query = new NodeReference(123).OutV().GremlinDistinct(); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outV.dedup()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void GremlinDistinctAppendStepToRelationshipQuery() + { + var query = new NodeReference(123).OutE().GremlinDistinct(); + Assert.IsInstanceOf(query); + Assert.AreEqual("g.v(p0).outE.dedup()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void GremlinDistinctAppendStepToTypedRelationshipQuery() + { + var query = new NodeReference(123).OutE().GremlinDistinct(); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outE.dedup()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/GremlinHasNextStepTests.cs b/Neo4jClient.Tests/Gremlin/GremlinHasNextStepTests.cs similarity index 97% rename from Test/Gremlin/GremlinHasNextStepTests.cs rename to Neo4jClient.Tests/Gremlin/GremlinHasNextStepTests.cs index 27d2a0d6b..629e555f5 100644 --- a/Test/Gremlin/GremlinHasNextStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/GremlinHasNextStepTests.cs @@ -1,36 +1,36 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class GremlinHasNextStepTests - { - [Test] - public void GremlinHasNextAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().GremlinHasNext(); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outV.hasNext()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void GremlinHasNextAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().GremlinHasNext(); - Assert.IsInstanceOf(query); - Assert.AreEqual("g.v(p0).outE.hasNext()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void GremlinHasNextAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().GremlinHasNext(); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outE.hasNext()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class GremlinHasNextStepTests + { + [Test] + public void GremlinHasNextAppendStepToNodeQuery() + { + var query = new NodeReference(123).OutV().GremlinHasNext(); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outV.hasNext()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void GremlinHasNextAppendStepToRelationshipQuery() + { + var query = new NodeReference(123).OutE().GremlinHasNext(); + Assert.IsInstanceOf(query); + Assert.AreEqual("g.v(p0).outE.hasNext()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void GremlinHasNextAppendStepToTypedRelationshipQuery() + { + var query = new NodeReference(123).OutE().GremlinHasNext(); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outE.hasNext()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/GremlinNodeEnumerableTests.cs b/Neo4jClient.Tests/Gremlin/GremlinNodeEnumerableTests.cs similarity index 97% rename from Test/Gremlin/GremlinNodeEnumerableTests.cs rename to Neo4jClient.Tests/Gremlin/GremlinNodeEnumerableTests.cs index 98fe03630..4f72ee76e 100644 --- a/Test/Gremlin/GremlinNodeEnumerableTests.cs +++ b/Neo4jClient.Tests/Gremlin/GremlinNodeEnumerableTests.cs @@ -1,74 +1,74 @@ -using System.Collections.Generic; -using System.Linq; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class GremlinNodeEnumerableTests - { - [Test] - [ExpectedException(typeof(DetachedNodeException))] - public void GetEnumeratorShouldThrowDetachedNodeExceptionWhenClientNotSet() - { - // ReSharper disable ReturnValueOfPureMethodIsNotUsed - IEnumerable> enumerable = new GremlinNodeEnumerable(new GremlinQuery(null, "abc", null, null)); - enumerable.GetEnumerator(); - // ReSharper restore ReturnValueOfPureMethodIsNotUsed - } - - [Test] - public void GetEnumeratorShouldExecuteQueryAgainstClient() - { - // Arrange - var expectedResults = new[] - { - new Node(new object(), new NodeReference(123)), - new Node(new object(), new NodeReference(456)), - new Node(new object(), new NodeReference(789)) - }; - var parameters = new Dictionary(); - var client = Substitute.For(); - client - .ExecuteGetAllNodesGremlin(Arg.Any()) - .Returns(expectedResults); - - // Act - var enumerable = new GremlinNodeEnumerable(new GremlinQuery(client, "abc", parameters, null)); - var results = enumerable.ToArray(); - - // Assert - Assert.AreEqual(expectedResults, results); - } - - [Test] - public void DebugQueryTextShouldReturnExpandedText() - { - var gremlinQuery = new GremlinQuery( - null, - "g[p0][p1][p2][p3][p4][p5][p6][p7][p8][p9][p10][p11]", - new Dictionary - { - { "p0", "val00" }, - { "p1", "val01" }, - { "p2", "val02" }, - { "p3", "val03" }, - { "p4", "val04" }, - { "p5", "val05" }, - { "p6", "val06" }, - { "p7", "val07" }, - { "p8", "val08" }, - { "p9", "val09" }, - { "p10", "val10" }, - { "p11", "val11" }, - }, - null); - var enumerable = new GremlinNodeEnumerable(gremlinQuery); - Assert.AreEqual( - "g['val00']['val01']['val02']['val03']['val04']['val05']['val06']['val07']['val08']['val09']['val10']['val11']", - enumerable.DebugQueryText); - } - } +using System.Collections.Generic; +using System.Linq; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class GremlinNodeEnumerableTests + { + [Test] + [ExpectedException(typeof(DetachedNodeException))] + public void GetEnumeratorShouldThrowDetachedNodeExceptionWhenClientNotSet() + { + // ReSharper disable ReturnValueOfPureMethodIsNotUsed + IEnumerable> enumerable = new GremlinNodeEnumerable(new GremlinQuery(null, "abc", null, null)); + enumerable.GetEnumerator(); + // ReSharper restore ReturnValueOfPureMethodIsNotUsed + } + + [Test] + public void GetEnumeratorShouldExecuteQueryAgainstClient() + { + // Arrange + var expectedResults = new[] + { + new Node(new object(), new NodeReference(123)), + new Node(new object(), new NodeReference(456)), + new Node(new object(), new NodeReference(789)) + }; + var parameters = new Dictionary(); + var client = Substitute.For(); + client + .ExecuteGetAllNodesGremlin(Arg.Any()) + .Returns(expectedResults); + + // Act + var enumerable = new GremlinNodeEnumerable(new GremlinQuery(client, "abc", parameters, null)); + var results = enumerable.ToArray(); + + // Assert + Assert.AreEqual(expectedResults, results); + } + + [Test] + public void DebugQueryTextShouldReturnExpandedText() + { + var gremlinQuery = new GremlinQuery( + null, + "g[p0][p1][p2][p3][p4][p5][p6][p7][p8][p9][p10][p11]", + new Dictionary + { + { "p0", "val00" }, + { "p1", "val01" }, + { "p2", "val02" }, + { "p3", "val03" }, + { "p4", "val04" }, + { "p5", "val05" }, + { "p6", "val06" }, + { "p7", "val07" }, + { "p8", "val08" }, + { "p9", "val09" }, + { "p10", "val10" }, + { "p11", "val11" }, + }, + null); + var enumerable = new GremlinNodeEnumerable(gremlinQuery); + Assert.AreEqual( + "g['val00']['val01']['val02']['val03']['val04']['val05']['val06']['val07']['val08']['val09']['val10']['val11']", + enumerable.DebugQueryText); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/GremlinPagedEnumeratorTests.cs b/Neo4jClient.Tests/Gremlin/GremlinPagedEnumeratorTests.cs similarity index 97% rename from Test/Gremlin/GremlinPagedEnumeratorTests.cs rename to Neo4jClient.Tests/Gremlin/GremlinPagedEnumeratorTests.cs index 1e088c108..7ccd5ad60 100644 --- a/Test/Gremlin/GremlinPagedEnumeratorTests.cs +++ b/Neo4jClient.Tests/Gremlin/GremlinPagedEnumeratorTests.cs @@ -1,211 +1,211 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class GremlinPagedEnumeratorTests - { - [Test] - public void ShouldNotLoadAnythingUntilEnumerated() - { - var loadedQueries = new List(); - Func> loadCallback = - q => { loadedQueries.Add(q); return new object[0]; }; - - var baseQuery = new GremlinQuery( - null, - "g.v(p0).outV", - new Dictionary { { "p0", 0 } }, - null); - - new GremlinPagedEnumerator(loadCallback, baseQuery); - - Assert.AreEqual(0, loadedQueries.Count()); - } - - [Test] - public void ShouldLoadFirstPageOfResultsWithFirstEnumeration() - { - var loadedQueries = new List(); - Func> loadCallback = - q => { loadedQueries.Add(q); return new object[0]; }; - - var baseQuery = new GremlinQuery( - null, - "g.v(p0).outV", - new Dictionary { { "p0", 0 } }, - null); - - var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); - enumerator.MoveNext(); - - Assert.AreEqual(1, loadedQueries.Count()); - Assert.AreEqual("g.v(p0).outV.drop(p1).take(p2)._()", loadedQueries[0].QueryText); - Assert.AreEqual(0, loadedQueries[0].QueryParameters["p1"]); - Assert.AreEqual(100, loadedQueries[0].QueryParameters["p2"]); - } - - [Test] - public void ShouldEnumerateOverFirstPageOfResults() - { - var results = Enumerable.Range(0, 100).ToArray(); - - var loadedQueries = new List(); - Func> loadCallback = - q => { loadedQueries.Add(q); return results; }; - - var baseQuery = new GremlinQuery( - null, - "g.v(p0).outV", - new Dictionary { { "p0", 0 } }, - null); - - var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); - for (var i = 0; i < 100; i++) - { - Assert.IsTrue(enumerator.MoveNext()); - Assert.AreEqual(results[i], enumerator.Current); - } - } - - [Test] - public void MoveNextShouldReturnFalseOnFirstCallIfThereAreNoResults() - { - var results = new int[0]; - - var loadedQueries = new List(); - Func> loadCallback = - q => { loadedQueries.Add(q); return results; }; - - var baseQuery = new GremlinQuery( - null, - "g.v(p0).outV", - new Dictionary { { "p0", 0 } }, - null); - - var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); - Assert.IsFalse(enumerator.MoveNext()); - } - - [Test] - public void MoveNextShouldReturnFalseAfterLastRecordOnFirstPageIfThereAreNoFurtherPages() - { - var pages = new Queue>(new[] - { - Enumerable.Range(0, 100), - Enumerable.Empty() - }); - - var loadedQueries = new List(); - Func> loadCallback = - q => { loadedQueries.Add(q); return pages.Dequeue(); }; - - var baseQuery = new GremlinQuery( - null, - "g.v(p0).outV", - new Dictionary { { "p0", 0 } }, - null); - - var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); - - for (var i = 0; i < 100; i++) - enumerator.MoveNext(); - - Assert.IsFalse(enumerator.MoveNext()); - } - - [Test] - public void MoveNextShouldReturnFalseAfterLastRecordOnPartialPage() - { - var pages = new Queue>(new[] - { - Enumerable.Range(0, 50) - }); - - var loadedQueries = new List(); - Func> loadCallback = - q => { loadedQueries.Add(q); return pages.Dequeue(); }; - - var baseQuery = new GremlinQuery( - null, - "g.v(p0).outV", - new Dictionary { { "p0", 0 } }, - null); - - var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); - - for (var i = 0; i < 50; i++) - enumerator.MoveNext(); - - Assert.IsFalse(enumerator.MoveNext()); - } - - [Test] - public void ShouldLoadSecondPageWhenCallingMoveNextAfterLastRecordOfFirstPage() - { - var results = Enumerable.Range(0, 100).ToArray(); - - var loadedQueries = new List(); - Func> loadCallback = - q => { loadedQueries.Add(q); return results; }; - - var baseQuery = new GremlinQuery( - null, - "g.v(p0).outV", - new Dictionary { { "p0", 0 } }, - null); - - var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); - for (var i = 0; i < 100; i++) - { - enumerator.MoveNext(); - } - - enumerator.MoveNext(); - - Assert.AreEqual(2, loadedQueries.Count()); - Assert.AreEqual("g.v(p0).outV.drop(p1).take(p2)._()", loadedQueries[0].QueryText); - Assert.AreEqual(0, loadedQueries[0].QueryParameters["p1"]); - Assert.AreEqual(100, loadedQueries[0].QueryParameters["p2"]); - Assert.AreEqual("g.v(p0).outV.drop(p1).take(p2)._()", loadedQueries[1].QueryText); - Assert.AreEqual(100, loadedQueries[1].QueryParameters["p1"]); - Assert.AreEqual(100, loadedQueries[1].QueryParameters["p2"]); - } - - [TestCase(1)] - [TestCase(2)] - [TestCase(3)] - [TestCase(4)] - [TestCase(5)] - [TestCase(10)] - public void ShouldEnumerateOverMultiplePagesOfResults(int pageCount) - { - var pages = new Queue>(); - for (var pageIndex = 0; pageIndex < pageCount; pageIndex++) - { - pages.Enqueue(Enumerable.Range(pageIndex * 100, 100)); - } - - var loadedQueries = new List(); - Func> loadCallback = - q => { loadedQueries.Add(q); return pages.Dequeue(); }; - - var baseQuery = new GremlinQuery( - null, - "g.v(p0).outV", - new Dictionary { { "p0", 0 } }, - null); - - var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); - for (var i = 0; i < pageCount * 100; i++) - { - Assert.IsTrue(enumerator.MoveNext()); - Assert.AreEqual(i, enumerator.Current); - } - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class GremlinPagedEnumeratorTests + { + [Test] + public void ShouldNotLoadAnythingUntilEnumerated() + { + var loadedQueries = new List(); + Func> loadCallback = + q => { loadedQueries.Add(q); return new object[0]; }; + + var baseQuery = new GremlinQuery( + null, + "g.v(p0).outV", + new Dictionary { { "p0", 0 } }, + null); + + new GremlinPagedEnumerator(loadCallback, baseQuery); + + Assert.AreEqual(0, loadedQueries.Count()); + } + + [Test] + public void ShouldLoadFirstPageOfResultsWithFirstEnumeration() + { + var loadedQueries = new List(); + Func> loadCallback = + q => { loadedQueries.Add(q); return new object[0]; }; + + var baseQuery = new GremlinQuery( + null, + "g.v(p0).outV", + new Dictionary { { "p0", 0 } }, + null); + + var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); + enumerator.MoveNext(); + + Assert.AreEqual(1, loadedQueries.Count()); + Assert.AreEqual("g.v(p0).outV.drop(p1).take(p2)._()", loadedQueries[0].QueryText); + Assert.AreEqual(0, loadedQueries[0].QueryParameters["p1"]); + Assert.AreEqual(100, loadedQueries[0].QueryParameters["p2"]); + } + + [Test] + public void ShouldEnumerateOverFirstPageOfResults() + { + var results = Enumerable.Range(0, 100).ToArray(); + + var loadedQueries = new List(); + Func> loadCallback = + q => { loadedQueries.Add(q); return results; }; + + var baseQuery = new GremlinQuery( + null, + "g.v(p0).outV", + new Dictionary { { "p0", 0 } }, + null); + + var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); + for (var i = 0; i < 100; i++) + { + Assert.IsTrue(enumerator.MoveNext()); + Assert.AreEqual(results[i], enumerator.Current); + } + } + + [Test] + public void MoveNextShouldReturnFalseOnFirstCallIfThereAreNoResults() + { + var results = new int[0]; + + var loadedQueries = new List(); + Func> loadCallback = + q => { loadedQueries.Add(q); return results; }; + + var baseQuery = new GremlinQuery( + null, + "g.v(p0).outV", + new Dictionary { { "p0", 0 } }, + null); + + var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); + Assert.IsFalse(enumerator.MoveNext()); + } + + [Test] + public void MoveNextShouldReturnFalseAfterLastRecordOnFirstPageIfThereAreNoFurtherPages() + { + var pages = new Queue>(new[] + { + Enumerable.Range(0, 100), + Enumerable.Empty() + }); + + var loadedQueries = new List(); + Func> loadCallback = + q => { loadedQueries.Add(q); return pages.Dequeue(); }; + + var baseQuery = new GremlinQuery( + null, + "g.v(p0).outV", + new Dictionary { { "p0", 0 } }, + null); + + var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); + + for (var i = 0; i < 100; i++) + enumerator.MoveNext(); + + Assert.IsFalse(enumerator.MoveNext()); + } + + [Test] + public void MoveNextShouldReturnFalseAfterLastRecordOnPartialPage() + { + var pages = new Queue>(new[] + { + Enumerable.Range(0, 50) + }); + + var loadedQueries = new List(); + Func> loadCallback = + q => { loadedQueries.Add(q); return pages.Dequeue(); }; + + var baseQuery = new GremlinQuery( + null, + "g.v(p0).outV", + new Dictionary { { "p0", 0 } }, + null); + + var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); + + for (var i = 0; i < 50; i++) + enumerator.MoveNext(); + + Assert.IsFalse(enumerator.MoveNext()); + } + + [Test] + public void ShouldLoadSecondPageWhenCallingMoveNextAfterLastRecordOfFirstPage() + { + var results = Enumerable.Range(0, 100).ToArray(); + + var loadedQueries = new List(); + Func> loadCallback = + q => { loadedQueries.Add(q); return results; }; + + var baseQuery = new GremlinQuery( + null, + "g.v(p0).outV", + new Dictionary { { "p0", 0 } }, + null); + + var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); + for (var i = 0; i < 100; i++) + { + enumerator.MoveNext(); + } + + enumerator.MoveNext(); + + Assert.AreEqual(2, loadedQueries.Count()); + Assert.AreEqual("g.v(p0).outV.drop(p1).take(p2)._()", loadedQueries[0].QueryText); + Assert.AreEqual(0, loadedQueries[0].QueryParameters["p1"]); + Assert.AreEqual(100, loadedQueries[0].QueryParameters["p2"]); + Assert.AreEqual("g.v(p0).outV.drop(p1).take(p2)._()", loadedQueries[1].QueryText); + Assert.AreEqual(100, loadedQueries[1].QueryParameters["p1"]); + Assert.AreEqual(100, loadedQueries[1].QueryParameters["p2"]); + } + + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(5)] + [TestCase(10)] + public void ShouldEnumerateOverMultiplePagesOfResults(int pageCount) + { + var pages = new Queue>(); + for (var pageIndex = 0; pageIndex < pageCount; pageIndex++) + { + pages.Enqueue(Enumerable.Range(pageIndex * 100, 100)); + } + + var loadedQueries = new List(); + Func> loadCallback = + q => { loadedQueries.Add(q); return pages.Dequeue(); }; + + var baseQuery = new GremlinQuery( + null, + "g.v(p0).outV", + new Dictionary { { "p0", 0 } }, + null); + + var enumerator = new GremlinPagedEnumerator(loadCallback, baseQuery); + for (var i = 0; i < pageCount * 100; i++) + { + Assert.IsTrue(enumerator.MoveNext()); + Assert.AreEqual(i, enumerator.Current); + } + } + } +} diff --git a/Test/Gremlin/IfThenElseTests.cs b/Neo4jClient.Tests/Gremlin/IfThenElseTests.cs similarity index 98% rename from Test/Gremlin/IfThenElseTests.cs rename to Neo4jClient.Tests/Gremlin/IfThenElseTests.cs index c4110ff28..18efb51ad 100644 --- a/Test/Gremlin/IfThenElseTests.cs +++ b/Neo4jClient.Tests/Gremlin/IfThenElseTests.cs @@ -1,86 +1,86 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class IfThenElseTests - { - [Test] - public void IfThenElseVShouldAppendSteps() - { - var query = new NodeReference(123).IfThenElse( - new GremlinIterator().OutV().GremlinHasNext(), - null, - null); - Assert.AreEqual("g.v(p0).ifThenElse{it.outV.hasNext()}{}{}", query.QueryText); - } - - [Test] - public void IfThenElseVShouldAppendStepsWithThenQueryAndElseQuery() - { - var query = new NodeReference(123).IfThenElse( - new GremlinIterator().OutV().GremlinHasNext(), - new GremlinIterator().OutV(), - new GremlinIterator().InV()); - Assert.AreEqual("g.v(p0).ifThenElse{it.outV.hasNext()}{it.outV}{it.inV}", query.QueryText); - } - - [Test] - public void IfThenElseVShouldAppendStepsWithThenQueryAndElseQueryWithParameters() - { - var query = new NodeReference(123).IfThenElse( - new GremlinIterator().OutV(t => t.Flag == true).GremlinHasNext(), - new GremlinIterator().OutV(t => t.Name == "foo"), - new GremlinIterator().InV(t => t.Name == "bar")); - Assert.AreEqual("g.v(p0).ifThenElse{it.outV.filter{ it[p1] == p2 }.hasNext()}{it.outV.filter{ it[p3].equalsIgnoreCase(p4) }}{it.inV.filter{ it[p5].equalsIgnoreCase(p6) }}", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("Flag", query.QueryParameters["p1"]); - Assert.AreEqual(true, query.QueryParameters["p2"]); - Assert.AreEqual("Name", query.QueryParameters["p3"]); - Assert.AreEqual("foo", query.QueryParameters["p4"]); - Assert.AreEqual("Name", query.QueryParameters["p5"]); - Assert.AreEqual("bar", query.QueryParameters["p6"]); - } - - [Test] - public void IfThenElseVShouldAppendStepsWithThenQueryAndElseQueryWithParametersAndDeclarations() - { - var query = new NodeReference(123).IfThenElse( - new GremlinIterator().OutV(t => t.Flag == true).GremlinHasNext(), - new GremlinIterator().AggregateV("x").OutV(t => t.Name == "foo"), - new GremlinIterator().InV(t => t.Name == "bar")); - Assert.AreEqual("x = [];g.v(p0).ifThenElse{it.outV.filter{ it[p1] == p2 }.hasNext()}{it.aggregate(x).outV.filter{ it[p3].equalsIgnoreCase(p4) }}{it.inV.filter{ it[p5].equalsIgnoreCase(p6) }}", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("Flag", query.QueryParameters["p1"]); - Assert.AreEqual(true, query.QueryParameters["p2"]); - Assert.AreEqual("Name", query.QueryParameters["p3"]); - Assert.AreEqual("foo", query.QueryParameters["p4"]); - Assert.AreEqual("Name", query.QueryParameters["p5"]); - Assert.AreEqual("bar", query.QueryParameters["p6"]); - } - - [Test] - public void IfThenElseVShouldAppendStepsWithThenQueryAndElseQueryWithParametersAndMultipleDeclarations() - { - var query = new NodeReference(123).IfThenElse( - new GremlinIterator().OutV(t => t.Flag == true).GremlinHasNext(), - new GremlinIterator().AggregateV("x").OutV(t => t.Name == "foo"), - new GremlinIterator().AggregateV("y").InV(t => t.Name == "bar")); - Assert.AreEqual("y = [];x = [];g.v(p0).ifThenElse{it.outV.filter{ it[p1] == p2 }.hasNext()}{it.aggregate(x).outV.filter{ it[p3].equalsIgnoreCase(p4) }}{it.aggregate(y).inV.filter{ it[p5].equalsIgnoreCase(p6) }}", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("Flag", query.QueryParameters["p1"]); - Assert.AreEqual(true, query.QueryParameters["p2"]); - Assert.AreEqual("Name", query.QueryParameters["p3"]); - Assert.AreEqual("foo", query.QueryParameters["p4"]); - Assert.AreEqual("Name", query.QueryParameters["p5"]); - Assert.AreEqual("bar", query.QueryParameters["p6"]); - } - - public class Test - { - public bool Flag { get; set; } - public string Name { get; set; } - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class IfThenElseTests + { + [Test] + public void IfThenElseVShouldAppendSteps() + { + var query = new NodeReference(123).IfThenElse( + new GremlinIterator().OutV().GremlinHasNext(), + null, + null); + Assert.AreEqual("g.v(p0).ifThenElse{it.outV.hasNext()}{}{}", query.QueryText); + } + + [Test] + public void IfThenElseVShouldAppendStepsWithThenQueryAndElseQuery() + { + var query = new NodeReference(123).IfThenElse( + new GremlinIterator().OutV().GremlinHasNext(), + new GremlinIterator().OutV(), + new GremlinIterator().InV()); + Assert.AreEqual("g.v(p0).ifThenElse{it.outV.hasNext()}{it.outV}{it.inV}", query.QueryText); + } + + [Test] + public void IfThenElseVShouldAppendStepsWithThenQueryAndElseQueryWithParameters() + { + var query = new NodeReference(123).IfThenElse( + new GremlinIterator().OutV(t => t.Flag == true).GremlinHasNext(), + new GremlinIterator().OutV(t => t.Name == "foo"), + new GremlinIterator().InV(t => t.Name == "bar")); + Assert.AreEqual("g.v(p0).ifThenElse{it.outV.filter{ it[p1] == p2 }.hasNext()}{it.outV.filter{ it[p3].equalsIgnoreCase(p4) }}{it.inV.filter{ it[p5].equalsIgnoreCase(p6) }}", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("Flag", query.QueryParameters["p1"]); + Assert.AreEqual(true, query.QueryParameters["p2"]); + Assert.AreEqual("Name", query.QueryParameters["p3"]); + Assert.AreEqual("foo", query.QueryParameters["p4"]); + Assert.AreEqual("Name", query.QueryParameters["p5"]); + Assert.AreEqual("bar", query.QueryParameters["p6"]); + } + + [Test] + public void IfThenElseVShouldAppendStepsWithThenQueryAndElseQueryWithParametersAndDeclarations() + { + var query = new NodeReference(123).IfThenElse( + new GremlinIterator().OutV(t => t.Flag == true).GremlinHasNext(), + new GremlinIterator().AggregateV("x").OutV(t => t.Name == "foo"), + new GremlinIterator().InV(t => t.Name == "bar")); + Assert.AreEqual("x = [];g.v(p0).ifThenElse{it.outV.filter{ it[p1] == p2 }.hasNext()}{it.aggregate(x).outV.filter{ it[p3].equalsIgnoreCase(p4) }}{it.inV.filter{ it[p5].equalsIgnoreCase(p6) }}", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("Flag", query.QueryParameters["p1"]); + Assert.AreEqual(true, query.QueryParameters["p2"]); + Assert.AreEqual("Name", query.QueryParameters["p3"]); + Assert.AreEqual("foo", query.QueryParameters["p4"]); + Assert.AreEqual("Name", query.QueryParameters["p5"]); + Assert.AreEqual("bar", query.QueryParameters["p6"]); + } + + [Test] + public void IfThenElseVShouldAppendStepsWithThenQueryAndElseQueryWithParametersAndMultipleDeclarations() + { + var query = new NodeReference(123).IfThenElse( + new GremlinIterator().OutV(t => t.Flag == true).GremlinHasNext(), + new GremlinIterator().AggregateV("x").OutV(t => t.Name == "foo"), + new GremlinIterator().AggregateV("y").InV(t => t.Name == "bar")); + Assert.AreEqual("y = [];x = [];g.v(p0).ifThenElse{it.outV.filter{ it[p1] == p2 }.hasNext()}{it.aggregate(x).outV.filter{ it[p3].equalsIgnoreCase(p4) }}{it.aggregate(y).inV.filter{ it[p5].equalsIgnoreCase(p6) }}", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("Flag", query.QueryParameters["p1"]); + Assert.AreEqual(true, query.QueryParameters["p2"]); + Assert.AreEqual("Name", query.QueryParameters["p3"]); + Assert.AreEqual("foo", query.QueryParameters["p4"]); + Assert.AreEqual("Name", query.QueryParameters["p5"]); + Assert.AreEqual("bar", query.QueryParameters["p6"]); + } + + public class Test + { + public bool Flag { get; set; } + public string Name { get; set; } + } + } } \ No newline at end of file diff --git a/Test/Gremlin/IteratorTests.cs b/Neo4jClient.Tests/Gremlin/IteratorTests.cs similarity index 97% rename from Test/Gremlin/IteratorTests.cs rename to Neo4jClient.Tests/Gremlin/IteratorTests.cs index 1523cf2be..2a9ec015c 100644 --- a/Test/Gremlin/IteratorTests.cs +++ b/Neo4jClient.Tests/Gremlin/IteratorTests.cs @@ -1,78 +1,78 @@ -using System; -using System.Collections.Generic; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class IteratorTests - { - [Test] - public void GremlinSkipVShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutV().GremlinSkip(5); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outV.drop(p1)._()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual(5, query.QueryParameters["p1"]); - } - - [Test] - public void GremlinSkipEShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutE().GremlinSkip(5); - Assert.IsInstanceOf(query); - Assert.AreEqual("g.v(p0).outE.drop(p1)._()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual(5, query.QueryParameters["p1"]); - } - - [Test] - public void GremlinSkipEWithTDataShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutE().GremlinSkip(5); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outE.drop(p1)._()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual(5, query.QueryParameters["p1"]); - } - - [Test] - public void GremlinTakeVShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutV().GremlinTake(5); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outV.take(p1)._()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual(5, query.QueryParameters["p1"]); - } - - [Test] - public void GremlinTakeEShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutE().GremlinTake(5); - Assert.IsInstanceOf(query); - Assert.AreEqual("g.v(p0).outE.take(p1)._()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual(5, query.QueryParameters["p1"]); - } - - [Test] - public void GremlinTakeEWithTDataShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutE().GremlinTake(5); - Assert.IsInstanceOf>(query); - Assert.AreEqual("g.v(p0).outE.take(p1)._()", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual(5, query.QueryParameters["p1"]); - } - } +using System; +using System.Collections.Generic; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class IteratorTests + { + [Test] + public void GremlinSkipVShouldAppendStep() + { + var node = new NodeReference(123); + var query = node.OutV().GremlinSkip(5); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outV.drop(p1)._()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual(5, query.QueryParameters["p1"]); + } + + [Test] + public void GremlinSkipEShouldAppendStep() + { + var node = new NodeReference(123); + var query = node.OutE().GremlinSkip(5); + Assert.IsInstanceOf(query); + Assert.AreEqual("g.v(p0).outE.drop(p1)._()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual(5, query.QueryParameters["p1"]); + } + + [Test] + public void GremlinSkipEWithTDataShouldAppendStep() + { + var node = new NodeReference(123); + var query = node.OutE().GremlinSkip(5); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outE.drop(p1)._()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual(5, query.QueryParameters["p1"]); + } + + [Test] + public void GremlinTakeVShouldAppendStep() + { + var node = new NodeReference(123); + var query = node.OutV().GremlinTake(5); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outV.take(p1)._()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual(5, query.QueryParameters["p1"]); + } + + [Test] + public void GremlinTakeEShouldAppendStep() + { + var node = new NodeReference(123); + var query = node.OutE().GremlinTake(5); + Assert.IsInstanceOf(query); + Assert.AreEqual("g.v(p0).outE.take(p1)._()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual(5, query.QueryParameters["p1"]); + } + + [Test] + public void GremlinTakeEWithTDataShouldAppendStep() + { + var node = new NodeReference(123); + var query = node.OutE().GremlinTake(5); + Assert.IsInstanceOf>(query); + Assert.AreEqual("g.v(p0).outE.take(p1)._()", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual(5, query.QueryParameters["p1"]); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/LoopStepTests.cs b/Neo4jClient.Tests/Gremlin/LoopStepTests.cs similarity index 97% rename from Test/Gremlin/LoopStepTests.cs rename to Neo4jClient.Tests/Gremlin/LoopStepTests.cs index 7b0be610d..7c73694fc 100644 --- a/Test/Gremlin/LoopStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/LoopStepTests.cs @@ -1,60 +1,60 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class LoopStepTests - { - [Test] - public void LoopVShouldAppendStep() - { - var query = new NodeReference(123).LoopV("foo", 6); - Assert.AreEqual("g.v(p0).loop(p1){ it.loops < p2 }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - Assert.AreEqual(6, query.QueryParameters["p2"]); - } - - [Test] - public void LoopVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).LoopV("foo", 6); - Assert.IsInstanceOf>(query); - } - - [Test] - public void LoopEShouldAppendStep() - { - var query = new NodeReference(123).LoopE("foo", 6); - Assert.AreEqual("g.v(p0).loop(p1){ it.loops < p2 }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - Assert.AreEqual(6, query.QueryParameters["p2"]); - } - - [Test] - public void LoopEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).LoopE("foo", 6); - Assert.IsInstanceOf(query); - } - - [Test] - public void LoopEWithTDataShouldAppendStep() - { - var query = new NodeReference(123).LoopE("foo", 6); - Assert.AreEqual("g.v(p0).loop(p1){ it.loops < p2 }", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - Assert.AreEqual("foo", query.QueryParameters["p1"]); - Assert.AreEqual(6, query.QueryParameters["p2"]); - } - - [Test] - public void LoopEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).LoopE("foo", 6); - Assert.IsInstanceOf>(query); - } - } -} +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class LoopStepTests + { + [Test] + public void LoopVShouldAppendStep() + { + var query = new NodeReference(123).LoopV("foo", 6); + Assert.AreEqual("g.v(p0).loop(p1){ it.loops < p2 }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + Assert.AreEqual(6, query.QueryParameters["p2"]); + } + + [Test] + public void LoopVShouldReturnTypedNodeEnumerable() + { + var query = new NodeReference(123).LoopV("foo", 6); + Assert.IsInstanceOf>(query); + } + + [Test] + public void LoopEShouldAppendStep() + { + var query = new NodeReference(123).LoopE("foo", 6); + Assert.AreEqual("g.v(p0).loop(p1){ it.loops < p2 }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + Assert.AreEqual(6, query.QueryParameters["p2"]); + } + + [Test] + public void LoopEShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).LoopE("foo", 6); + Assert.IsInstanceOf(query); + } + + [Test] + public void LoopEWithTDataShouldAppendStep() + { + var query = new NodeReference(123).LoopE("foo", 6); + Assert.AreEqual("g.v(p0).loop(p1){ it.loops < p2 }", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + Assert.AreEqual("foo", query.QueryParameters["p1"]); + Assert.AreEqual(6, query.QueryParameters["p2"]); + } + + [Test] + public void LoopEWithTDataShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).LoopE("foo", 6); + Assert.IsInstanceOf>(query); + } + } +} diff --git a/Test/Gremlin/PrintLineStatementTests.cs b/Neo4jClient.Tests/Gremlin/PrintLineStatementTests.cs similarity index 97% rename from Test/Gremlin/PrintLineStatementTests.cs rename to Neo4jClient.Tests/Gremlin/PrintLineStatementTests.cs index fd155a7d0..c1d581294 100644 --- a/Test/Gremlin/PrintLineStatementTests.cs +++ b/Neo4jClient.Tests/Gremlin/PrintLineStatementTests.cs @@ -1,20 +1,20 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class PrintLineStatementTests - { - [Test] - public void PrintLineShouldAppendStepToNodeQuery() - { - var query = new NodeReference(123).IfThenElse( - new GremlinIterator().OutV().GremlinHasNext(), - new Statement().PrintLine("\"{$it} Hello\""), - new Statement().PrintLine("\"{$it} GoodBye\"")); - Assert.AreEqual("g.v(p0).ifThenElse{it.outV.hasNext()}{println \"{$it} Hello\"}{println \"{$it} GoodBye\"}", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class PrintLineStatementTests + { + [Test] + public void PrintLineShouldAppendStepToNodeQuery() + { + var query = new NodeReference(123).IfThenElse( + new GremlinIterator().OutV().GremlinHasNext(), + new Statement().PrintLine("\"{$it} Hello\""), + new Statement().PrintLine("\"{$it} GoodBye\"")); + Assert.AreEqual("g.v(p0).ifThenElse{it.outV.hasNext()}{println \"{$it} Hello\"}{println \"{$it} GoodBye\"}", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/RetainStepTests.cs b/Neo4jClient.Tests/Gremlin/RetainStepTests.cs similarity index 97% rename from Test/Gremlin/RetainStepTests.cs rename to Neo4jClient.Tests/Gremlin/RetainStepTests.cs index 102e3b749..93d12d416 100644 --- a/Test/Gremlin/RetainStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/RetainStepTests.cs @@ -1,54 +1,54 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class RetainStepTests - { - [Test] - public void RetainVShouldAppendStep() - { - var query = new NodeReference(123).RetainV("foo"); - Assert.AreEqual("g.v(p0).retain(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void RetainVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).RetainV("foo"); - Assert.IsInstanceOf>(query); - } - - [Test] - public void RetainEShouldAppendStep() - { - var query = new NodeReference(123).RetainE("foo"); - Assert.AreEqual("g.v(p0).retain(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void RetainEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).RetainE("foo"); - Assert.IsInstanceOf(query); - } - - [Test] - public void RetainEWithTDataShouldAppendStep() - { - var query = new NodeReference(123).RetainE("foo"); - Assert.AreEqual("g.v(p0).retain(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void RetainEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).RetainE("foo"); - Assert.IsInstanceOf>(query); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class RetainStepTests + { + [Test] + public void RetainVShouldAppendStep() + { + var query = new NodeReference(123).RetainV("foo"); + Assert.AreEqual("g.v(p0).retain(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void RetainVShouldReturnTypedNodeEnumerable() + { + var query = new NodeReference(123).RetainV("foo"); + Assert.IsInstanceOf>(query); + } + + [Test] + public void RetainEShouldAppendStep() + { + var query = new NodeReference(123).RetainE("foo"); + Assert.AreEqual("g.v(p0).retain(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void RetainEShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).RetainE("foo"); + Assert.IsInstanceOf(query); + } + + [Test] + public void RetainEWithTDataShouldAppendStep() + { + var query = new NodeReference(123).RetainE("foo"); + Assert.AreEqual("g.v(p0).retain(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void RetainEWithTDataShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).RetainE("foo"); + Assert.IsInstanceOf>(query); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/StoreStepTests.cs b/Neo4jClient.Tests/Gremlin/StoreStepTests.cs similarity index 97% rename from Test/Gremlin/StoreStepTests.cs rename to Neo4jClient.Tests/Gremlin/StoreStepTests.cs index fc75ccc90..a23c081ea 100644 --- a/Test/Gremlin/StoreStepTests.cs +++ b/Neo4jClient.Tests/Gremlin/StoreStepTests.cs @@ -1,54 +1,54 @@ -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class StoreStepTests - { - [Test] - public void StoreVShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).StoreV("foo"); - Assert.AreEqual("foo = [];g.v(p0).store(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void StoreVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).StoreV("foo"); - Assert.IsInstanceOf>(query); - } - - [Test] - public void StoreEShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).StoreE("foo"); - Assert.AreEqual("foo = [];g.v(p0).store(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void StoreEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).StoreE("foo"); - Assert.IsInstanceOf(query); - } - - [Test] - public void StoreEWithTDataShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).StoreE("foo"); - Assert.AreEqual("foo = [];g.v(p0).store(foo)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void StoreEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).StoreE("foo"); - Assert.IsInstanceOf>(query); - } - } +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class StoreStepTests + { + [Test] + public void StoreVShouldAppendStepAndDeclareVariable() + { + var query = new NodeReference(123).StoreV("foo"); + Assert.AreEqual("foo = [];g.v(p0).store(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void StoreVShouldReturnTypedNodeEnumerable() + { + var query = new NodeReference(123).StoreV("foo"); + Assert.IsInstanceOf>(query); + } + + [Test] + public void StoreEShouldAppendStepAndDeclareVariable() + { + var query = new NodeReference(123).StoreE("foo"); + Assert.AreEqual("foo = [];g.v(p0).store(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void StoreEShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).StoreE("foo"); + Assert.IsInstanceOf(query); + } + + [Test] + public void StoreEWithTDataShouldAppendStepAndDeclareVariable() + { + var query = new NodeReference(123).StoreE("foo"); + Assert.AreEqual("foo = [];g.v(p0).store(foo)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void StoreEWithTDataShouldReturnRelationshipEnumerable() + { + var query = new NodeReference(123).StoreE("foo"); + Assert.IsInstanceOf>(query); + } + } } \ No newline at end of file diff --git a/Test/Gremlin/TableTests.cs b/Neo4jClient.Tests/Gremlin/TableTests.cs similarity index 97% rename from Test/Gremlin/TableTests.cs rename to Neo4jClient.Tests/Gremlin/TableTests.cs index 6454f2a73..3ec546a16 100644 --- a/Test/Gremlin/TableTests.cs +++ b/Neo4jClient.Tests/Gremlin/TableTests.cs @@ -1,104 +1,104 @@ -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; -using Neo4jClient.ApiModels; -using Neo4jClient.ApiModels.Gremlin; -using Neo4jClient.Gremlin; -using Newtonsoft.Json; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class TableTests - { - [Test] - public void TableShouldAppendStepToQuery() - { - var query = new NodeReference(123) - .OutV() - .As("foo") - .Table(); - - Assert.IsInstanceOf>(query); - Assert.IsInstanceOf>(query); - Assert.IsInstanceOf(query); - - var enumerable = (IGremlinQuery)query; - Assert.AreEqual("g.v(p0).outV.as(p1).table(new Table()).cap", enumerable.QueryText); - Assert.AreEqual(123, enumerable.QueryParameters["p0"]); - Assert.AreEqual("foo", enumerable.QueryParameters["p1"]); - } - - [Test] - public void TableShouldAppendStepToQueryWithClosures() - { - var query = new NodeReference(123) - .OutV() - .As("foo") - .InV() - .As("bar") - .Table( - foo => foo.SomeText, - bar => bar.SomeNumber - ); - - Assert.IsInstanceOf>(query); - Assert.IsInstanceOf>(query); - Assert.IsInstanceOf(query); - - var enumerable = (IGremlinQuery)query; - Assert.AreEqual("g.v(p0).outV.as(p1).inV.as(p2).table(new Table()){it[p3]}{it[p4]}.cap", enumerable.QueryText); - Assert.AreEqual(123, enumerable.QueryParameters["p0"]); - Assert.AreEqual("foo", enumerable.QueryParameters["p1"]); - Assert.AreEqual("bar", enumerable.QueryParameters["p2"]); - Assert.AreEqual("SomeText", enumerable.QueryParameters["p3"]); - Assert.AreEqual("SomeNumber", enumerable.QueryParameters["p4"]); - } - - [Test] - public void TableCapShouldTransferResponseToResult() - { - // Arrange - var responses = new List> - { - new List - { - new GremlinTableCapResponse - { - Columns = new List - { - "Foo" - }, - Data = new List> - { - new List - {"data"} - } - } - } - }; - - // Act - var result = GremlinTableCapResponse.TransferResponseToResult(responses, new JsonConverter[0]).ToArray(); - - // Assert - Assert.AreEqual("data", result.First().Foo); - } - - public class Foo - { - public string SomeText { get; set; } - } - - public class Bar - { - public int SomeNumber { get; set; } - } - - public class TableResult - { - public string Foo { get; set; } - public string Bar { get; set; } - } - } +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Neo4jClient.ApiModels; +using Neo4jClient.ApiModels.Gremlin; +using Neo4jClient.Gremlin; +using Newtonsoft.Json; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class TableTests + { + [Test] + public void TableShouldAppendStepToQuery() + { + var query = new NodeReference(123) + .OutV() + .As("foo") + .Table(); + + Assert.IsInstanceOf>(query); + Assert.IsInstanceOf>(query); + Assert.IsInstanceOf(query); + + var enumerable = (IGremlinQuery)query; + Assert.AreEqual("g.v(p0).outV.as(p1).table(new Table()).cap", enumerable.QueryText); + Assert.AreEqual(123, enumerable.QueryParameters["p0"]); + Assert.AreEqual("foo", enumerable.QueryParameters["p1"]); + } + + [Test] + public void TableShouldAppendStepToQueryWithClosures() + { + var query = new NodeReference(123) + .OutV() + .As("foo") + .InV() + .As("bar") + .Table( + foo => foo.SomeText, + bar => bar.SomeNumber + ); + + Assert.IsInstanceOf>(query); + Assert.IsInstanceOf>(query); + Assert.IsInstanceOf(query); + + var enumerable = (IGremlinQuery)query; + Assert.AreEqual("g.v(p0).outV.as(p1).inV.as(p2).table(new Table()){it[p3]}{it[p4]}.cap", enumerable.QueryText); + Assert.AreEqual(123, enumerable.QueryParameters["p0"]); + Assert.AreEqual("foo", enumerable.QueryParameters["p1"]); + Assert.AreEqual("bar", enumerable.QueryParameters["p2"]); + Assert.AreEqual("SomeText", enumerable.QueryParameters["p3"]); + Assert.AreEqual("SomeNumber", enumerable.QueryParameters["p4"]); + } + + [Test] + public void TableCapShouldTransferResponseToResult() + { + // Arrange + var responses = new List> + { + new List + { + new GremlinTableCapResponse + { + Columns = new List + { + "Foo" + }, + Data = new List> + { + new List + {"data"} + } + } + } + }; + + // Act + var result = GremlinTableCapResponse.TransferResponseToResult(responses, new JsonConverter[0]).ToArray(); + + // Assert + Assert.AreEqual("data", result.First().Foo); + } + + public class Foo + { + public string SomeText { get; set; } + } + + public class Bar + { + public int SomeNumber { get; set; } + } + + public class TableResult + { + public string Foo { get; set; } + public string Bar { get; set; } + } + } } \ No newline at end of file diff --git a/Test/Gremlin/TranslateFilterTests.cs b/Neo4jClient.Tests/Gremlin/TranslateFilterTests.cs similarity index 97% rename from Test/Gremlin/TranslateFilterTests.cs rename to Neo4jClient.Tests/Gremlin/TranslateFilterTests.cs index 78a5c8d37..67ae33f76 100644 --- a/Test/Gremlin/TranslateFilterTests.cs +++ b/Neo4jClient.Tests/Gremlin/TranslateFilterTests.cs @@ -1,350 +1,350 @@ -using System; -using System.Globalization; -using System.Linq; -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test.Gremlin -{ - [TestFixture] - public class TranslateFilterTests - { - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsConstantStringExpression() - { - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == "abc" // This must be a constant - do not refactor this line - ) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual("abc", filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsConstantIntExpression() - { - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == 123 // This must be a constant - do not refactor this line - ) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual(123, filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsConstantLongExpression() - { - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == 123 // This must be a constant - do not refactor this line - ) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual((long)123, filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsConstantLongMaxExpression() - { - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == long.MaxValue // This must be a constant - do not refactor this line - ) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual(long.MaxValue, filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsLocalStringExpression() - { - var prop1Value = new string(new[] { 'a', 'b', 'c' }); // This must be a local - do not refactor this to a constant - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == prop1Value - ) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual("abc", filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsLocalIntExpression() - { - var prop1Value = int.Parse("123"); // This must be a local - do not refactor this to a constant - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == prop1Value - ) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual(123, filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsLocalLongExpression() - { - var prop1Value = long.Parse("123"); // This must be a local - do not refactor this to a constant - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == prop1Value) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual(123, filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsLocalLongMaxExpression() - { - var prop1Value = long.Parse(long.MaxValue.ToString(CultureInfo.InvariantCulture)); // This must be a local - do not refactor this to a constant - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == prop1Value - ) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual(long.MaxValue, filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsConstantEnumExpression() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == EnumForTesting.Foo) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual(EnumForTesting.Foo, filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsAnotherStringPropertyExpression() - { - var bar = new Bar { Prop1 = "def" }; - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == bar.Prop1 // This must be a property get - do not refactor this line - ) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual("def", filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveThreePropertiesEqualOtherStringPropertiesInBooleanAndAlsoChain() - { - var bar = new Bar { Prop1 = "def", Prop2 = "ghi", Prop3 = "jkl" }; - var filters = FilterFormatters - .TranslateFilter( - // These must be property gets - do not refactor this line - f => f.Prop1 == bar.Prop1 && f.Prop2 == bar.Prop2 && f.Prop3 == bar.Prop3 - ) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.AreEqual(3, filters.Count()); - Assert.AreEqual("Prop1", filters[0].PropertyName); - Assert.AreEqual("def", filters[0].Value); - Assert.AreEqual("Prop2", filters[1].PropertyName); - Assert.AreEqual("ghi", filters[1].Value); - Assert.AreEqual("Prop3", filters[2].PropertyName); - Assert.AreEqual("jkl", filters[2].Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsAStringFunctionExpression() - { - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == string.Format("{0}.{1}", "abc", "def").ToUpperInvariant() // This must be a method call - do not refactor this line - ) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual("ABC.DEF", filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolveSinglePropertyEqualsNull() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == null) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); - Assert.AreEqual(null, filters.FirstOrDefault().Value); - } - - [Test] - public void TranslateFilterShouldResolvePropertiesEqualBoolean() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop4 == true) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop4", filters[0].PropertyName); - Assert.AreEqual(true, filters[0].Value); - } - - [Test] - public void TranslateFilterShouldResolveBooleanPropertyToDefaultToCompareToTrue() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop4) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop4", filters[0].PropertyName); - Assert.AreEqual(true, filters[0].Value); - } - - [Test] - public void TranslateFilterShouldResolveBooleanPropertyToDefaultToCompareToFalse() - { - var filters = FilterFormatters - .TranslateFilter(f => !f.Prop4) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.AreEqual(1, filters.Count()); - Assert.AreEqual("Prop4", filters[0].PropertyName); - Assert.AreEqual(false, filters[0].Value); - } - - [Test] - public void TranslateFilterShouldResolveTwoPropertiesEqualNullWithBinaryAnd() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == null & f.Prop2 == null) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.AreEqual(2, filters.Count()); - Assert.AreEqual("Prop1", filters[0].PropertyName); - Assert.AreEqual(null, filters[0].Value); - Assert.AreEqual("Prop2", filters[1].PropertyName); - Assert.AreEqual(null, filters[1].Value); - } - - [Test] - public void TranslateFilterShouldResolveTwoPropertiesEqualNullWithBinaryAndAlso() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == null && f.Prop2 == null) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.AreEqual(2, filters.Count()); - Assert.AreEqual("Prop1", filters[0].PropertyName); - Assert.AreEqual(null, filters[0].Value); - Assert.AreEqual("Prop2", filters[1].PropertyName); - Assert.AreEqual(null, filters[1].Value); - } - - [Test] - public void TranslateFilterShouldResolveThreePropertiesEqualNullWithBinaryAndAlso() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == null && f.Prop2 == null && f.Prop3 == null) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.AreEqual(3, filters.Count()); - Assert.AreEqual("Prop1", filters[0].PropertyName); - Assert.AreEqual(null, filters[0].Value); - Assert.AreEqual("Prop2", filters[1].PropertyName); - Assert.AreEqual(null, filters[1].Value); - Assert.AreEqual("Prop3", filters[2].PropertyName); - Assert.AreEqual(null, filters[2].Value); - } - - [Test] - [ExpectedException( - typeof(NotSupportedException), - ExpectedMessage = "This expression is not a binary expression:", - MatchType = MessageMatch.StartsWith)] - public void TranslateFilterShouldThrowExceptionIfOuterExpressionIsAndAlsoAndInnerLeftExpressionIsNotABinaryExpression() - { - var testVariable = bool.Parse(bool.TrueString); - FilterFormatters.TranslateFilter(f => testVariable && f.Prop2 == null); - } - - [Test] - [ExpectedException( - typeof(NotSupportedException), - ExpectedMessage = "This expression is not a binary expression:", - MatchType = MessageMatch.StartsWith)] - public void TranslateFilterShouldThrowExceptionIfOuterExpressionIsAndAlsoAndInnerRightExpressionIsNotABinaryExpression() - { - var testVariable = bool.Parse(bool.TrueString); - FilterFormatters.TranslateFilter(f => f.Prop2 == null && testVariable); - } - - [Test] - [ExpectedException( - typeof(NotSupportedException), - ExpectedMessage = "Oprerator Or is not yet supported.", - MatchType = MessageMatch.StartsWith)] - public void TranslateFilterShouldThrowExceptionForOrExpression() - { - var testVariable = bool.Parse(bool.TrueString); - FilterFormatters.TranslateFilter(f => f.Prop2 == null | testVariable); - } - - [Test] - [ExpectedException( - typeof(NotSupportedException), - ExpectedMessage = "Oprerator OrElse is not yet supported.", - MatchType = MessageMatch.StartsWith)] - public void TranslateFilterShouldThrowExceptionForOrElseExpression() - { - var testVariable = bool.Parse(bool.TrueString); - FilterFormatters.TranslateFilter(f => f.Prop2 == null || testVariable); - } - - public class Foo - { - public string Prop1 { get; set; } - public string Prop2 { get; set; } - public string Prop3 { get; set; } - public bool Prop4 { get; set; } - } - - public class Bar - { - public string Prop1 { get; set; } - public string Prop2 { get; set; } - public string Prop3 { get; set; } - } - - public class NodeWithIntegers - { - public int Prop1 { get; set; } - public int Prop2 { get; set; } - } - - public class NodeWithLongs - { - public long Prop1 { get; set; } - public long Prop2 { get; set; } - } - - public enum EnumForTesting - { - Foo, - Bar, - Baz - } - - public class NodeWithEnums - { - public EnumForTesting Prop1 { get; set; } - } - } -} +using System; +using System.Globalization; +using System.Linq; +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test.Gremlin +{ + [TestFixture] + public class TranslateFilterTests + { + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsConstantStringExpression() + { + var filters = FilterFormatters + .TranslateFilter( + f => f.Prop1 == "abc" // This must be a constant - do not refactor this line + ) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual("abc", filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsConstantIntExpression() + { + var filters = FilterFormatters + .TranslateFilter( + f => f.Prop1 == 123 // This must be a constant - do not refactor this line + ) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual(123, filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsConstantLongExpression() + { + var filters = FilterFormatters + .TranslateFilter( + f => f.Prop1 == 123 // This must be a constant - do not refactor this line + ) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual((long)123, filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsConstantLongMaxExpression() + { + var filters = FilterFormatters + .TranslateFilter( + f => f.Prop1 == long.MaxValue // This must be a constant - do not refactor this line + ) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual(long.MaxValue, filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsLocalStringExpression() + { + var prop1Value = new string(new[] { 'a', 'b', 'c' }); // This must be a local - do not refactor this to a constant + var filters = FilterFormatters + .TranslateFilter( + f => f.Prop1 == prop1Value + ) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual("abc", filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsLocalIntExpression() + { + var prop1Value = int.Parse("123"); // This must be a local - do not refactor this to a constant + var filters = FilterFormatters + .TranslateFilter( + f => f.Prop1 == prop1Value + ) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual(123, filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsLocalLongExpression() + { + var prop1Value = long.Parse("123"); // This must be a local - do not refactor this to a constant + var filters = FilterFormatters + .TranslateFilter(f => f.Prop1 == prop1Value) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual(123, filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsLocalLongMaxExpression() + { + var prop1Value = long.Parse(long.MaxValue.ToString(CultureInfo.InvariantCulture)); // This must be a local - do not refactor this to a constant + var filters = FilterFormatters + .TranslateFilter( + f => f.Prop1 == prop1Value + ) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual(long.MaxValue, filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsConstantEnumExpression() + { + var filters = FilterFormatters + .TranslateFilter(f => f.Prop1 == EnumForTesting.Foo) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual(EnumForTesting.Foo, filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsAnotherStringPropertyExpression() + { + var bar = new Bar { Prop1 = "def" }; + var filters = FilterFormatters + .TranslateFilter( + f => f.Prop1 == bar.Prop1 // This must be a property get - do not refactor this line + ) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual("def", filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveThreePropertiesEqualOtherStringPropertiesInBooleanAndAlsoChain() + { + var bar = new Bar { Prop1 = "def", Prop2 = "ghi", Prop3 = "jkl" }; + var filters = FilterFormatters + .TranslateFilter( + // These must be property gets - do not refactor this line + f => f.Prop1 == bar.Prop1 && f.Prop2 == bar.Prop2 && f.Prop3 == bar.Prop3 + ) + .OrderBy(f => f.PropertyName) + .ToArray(); + Assert.AreEqual(3, filters.Count()); + Assert.AreEqual("Prop1", filters[0].PropertyName); + Assert.AreEqual("def", filters[0].Value); + Assert.AreEqual("Prop2", filters[1].PropertyName); + Assert.AreEqual("ghi", filters[1].Value); + Assert.AreEqual("Prop3", filters[2].PropertyName); + Assert.AreEqual("jkl", filters[2].Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsAStringFunctionExpression() + { + var filters = FilterFormatters + .TranslateFilter( + f => f.Prop1 == string.Format("{0}.{1}", "abc", "def").ToUpperInvariant() // This must be a method call - do not refactor this line + ) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual("ABC.DEF", filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolveSinglePropertyEqualsNull() + { + var filters = FilterFormatters + .TranslateFilter(f => f.Prop1 == null) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop1", filters.FirstOrDefault().PropertyName); + Assert.AreEqual(null, filters.FirstOrDefault().Value); + } + + [Test] + public void TranslateFilterShouldResolvePropertiesEqualBoolean() + { + var filters = FilterFormatters + .TranslateFilter(f => f.Prop4 == true) + .OrderBy(f => f.PropertyName) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop4", filters[0].PropertyName); + Assert.AreEqual(true, filters[0].Value); + } + + [Test] + public void TranslateFilterShouldResolveBooleanPropertyToDefaultToCompareToTrue() + { + var filters = FilterFormatters + .TranslateFilter(f => f.Prop4) + .OrderBy(f => f.PropertyName) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop4", filters[0].PropertyName); + Assert.AreEqual(true, filters[0].Value); + } + + [Test] + public void TranslateFilterShouldResolveBooleanPropertyToDefaultToCompareToFalse() + { + var filters = FilterFormatters + .TranslateFilter(f => !f.Prop4) + .OrderBy(f => f.PropertyName) + .ToArray(); + Assert.AreEqual(1, filters.Count()); + Assert.AreEqual("Prop4", filters[0].PropertyName); + Assert.AreEqual(false, filters[0].Value); + } + + [Test] + public void TranslateFilterShouldResolveTwoPropertiesEqualNullWithBinaryAnd() + { + var filters = FilterFormatters + .TranslateFilter(f => f.Prop1 == null & f.Prop2 == null) + .OrderBy(f => f.PropertyName) + .ToArray(); + Assert.AreEqual(2, filters.Count()); + Assert.AreEqual("Prop1", filters[0].PropertyName); + Assert.AreEqual(null, filters[0].Value); + Assert.AreEqual("Prop2", filters[1].PropertyName); + Assert.AreEqual(null, filters[1].Value); + } + + [Test] + public void TranslateFilterShouldResolveTwoPropertiesEqualNullWithBinaryAndAlso() + { + var filters = FilterFormatters + .TranslateFilter(f => f.Prop1 == null && f.Prop2 == null) + .OrderBy(f => f.PropertyName) + .ToArray(); + Assert.AreEqual(2, filters.Count()); + Assert.AreEqual("Prop1", filters[0].PropertyName); + Assert.AreEqual(null, filters[0].Value); + Assert.AreEqual("Prop2", filters[1].PropertyName); + Assert.AreEqual(null, filters[1].Value); + } + + [Test] + public void TranslateFilterShouldResolveThreePropertiesEqualNullWithBinaryAndAlso() + { + var filters = FilterFormatters + .TranslateFilter(f => f.Prop1 == null && f.Prop2 == null && f.Prop3 == null) + .OrderBy(f => f.PropertyName) + .ToArray(); + Assert.AreEqual(3, filters.Count()); + Assert.AreEqual("Prop1", filters[0].PropertyName); + Assert.AreEqual(null, filters[0].Value); + Assert.AreEqual("Prop2", filters[1].PropertyName); + Assert.AreEqual(null, filters[1].Value); + Assert.AreEqual("Prop3", filters[2].PropertyName); + Assert.AreEqual(null, filters[2].Value); + } + + [Test] + [ExpectedException( + typeof(NotSupportedException), + ExpectedMessage = "This expression is not a binary expression:", + MatchType = MessageMatch.StartsWith)] + public void TranslateFilterShouldThrowExceptionIfOuterExpressionIsAndAlsoAndInnerLeftExpressionIsNotABinaryExpression() + { + var testVariable = bool.Parse(bool.TrueString); + FilterFormatters.TranslateFilter(f => testVariable && f.Prop2 == null); + } + + [Test] + [ExpectedException( + typeof(NotSupportedException), + ExpectedMessage = "This expression is not a binary expression:", + MatchType = MessageMatch.StartsWith)] + public void TranslateFilterShouldThrowExceptionIfOuterExpressionIsAndAlsoAndInnerRightExpressionIsNotABinaryExpression() + { + var testVariable = bool.Parse(bool.TrueString); + FilterFormatters.TranslateFilter(f => f.Prop2 == null && testVariable); + } + + [Test] + [ExpectedException( + typeof(NotSupportedException), + ExpectedMessage = "Oprerator Or is not yet supported.", + MatchType = MessageMatch.StartsWith)] + public void TranslateFilterShouldThrowExceptionForOrExpression() + { + var testVariable = bool.Parse(bool.TrueString); + FilterFormatters.TranslateFilter(f => f.Prop2 == null | testVariable); + } + + [Test] + [ExpectedException( + typeof(NotSupportedException), + ExpectedMessage = "Oprerator OrElse is not yet supported.", + MatchType = MessageMatch.StartsWith)] + public void TranslateFilterShouldThrowExceptionForOrElseExpression() + { + var testVariable = bool.Parse(bool.TrueString); + FilterFormatters.TranslateFilter(f => f.Prop2 == null || testVariable); + } + + public class Foo + { + public string Prop1 { get; set; } + public string Prop2 { get; set; } + public string Prop3 { get; set; } + public bool Prop4 { get; set; } + } + + public class Bar + { + public string Prop1 { get; set; } + public string Prop2 { get; set; } + public string Prop3 { get; set; } + } + + public class NodeWithIntegers + { + public int Prop1 { get; set; } + public int Prop2 { get; set; } + } + + public class NodeWithLongs + { + public long Prop1 { get; set; } + public long Prop2 { get; set; } + } + + public enum EnumForTesting + { + Foo, + Bar, + Baz + } + + public class NodeWithEnums + { + public EnumForTesting Prop1 { get; set; } + } + } +} diff --git a/Test/IndexEntryTests.cs b/Neo4jClient.Tests/IndexEntryTests.cs similarity index 97% rename from Test/IndexEntryTests.cs rename to Neo4jClient.Tests/IndexEntryTests.cs index fae8ea15d..12b6cd10e 100644 --- a/Test/IndexEntryTests.cs +++ b/Neo4jClient.Tests/IndexEntryTests.cs @@ -1,94 +1,94 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; - -namespace Neo4jClient.Test -{ - [TestFixture] - public class IndexEntryTests - { - [Test] - public void CanInitializeWithLongForm() - { - var entry = new IndexEntry - { - Name = "index-entry", - KeyValues = new[] - { - new KeyValuePair("foo", 123), - new KeyValuePair("bar", "baz") - } - }; - - Assert.AreEqual("index-entry", entry.Name); - Assert.AreEqual(2, entry.KeyValues.Count()); - Assert.AreEqual("foo", entry.KeyValues.ElementAt(0).Key); - Assert.AreEqual(123, entry.KeyValues.ElementAt(0).Value); - Assert.AreEqual("bar", entry.KeyValues.ElementAt(1).Key); - Assert.AreEqual("baz", entry.KeyValues.ElementAt(1).Value); - } - - [Test] - public void CanInitializeWithCollectionIntializer() - { - var entry = new IndexEntry("index-entry") - { - { "foo", 123 }, - { "bar", "baz" } - }; - - Assert.AreEqual("index-entry", entry.Name); - Assert.AreEqual(2, entry.KeyValues.Count()); - Assert.AreEqual("foo", entry.KeyValues.ElementAt(0).Key); - Assert.AreEqual(123, entry.KeyValues.ElementAt(0).Value); - Assert.AreEqual("bar", entry.KeyValues.ElementAt(1).Key); - Assert.AreEqual("baz", entry.KeyValues.ElementAt(1).Value); - } - - [Test] - public void CanCallAddAfterUsingNameConstructor() - { - // ReSharper disable UseObjectOrCollectionInitializer - var entry = new IndexEntry("index-entry"); - entry.Add("qak", "qoo"); - // ReSharper restore UseObjectOrCollectionInitializer - - Assert.AreEqual(1, entry.KeyValues.Count()); - Assert.AreEqual("qak", entry.KeyValues.ElementAt(0).Key); - Assert.AreEqual("qoo", entry.KeyValues.ElementAt(0).Value); - } - - [Test] - public void CanCallAddAfterUsingCollectionIntializer() - { - // ReSharper disable UseObjectOrCollectionInitializer - var entry = new IndexEntry("index-entry") - { - { "foo", 123 }, - { "bar", "baz" } - }; - // ReSharper restore UseObjectOrCollectionInitializer - - entry.Add("qak", "qoo"); - - Assert.AreEqual(3, entry.KeyValues.Count()); - Assert.AreEqual("qak", entry.KeyValues.ElementAt(2).Key); - Assert.AreEqual("qoo", entry.KeyValues.ElementAt(2).Value); - } - - [Test] - public void AddAfterAssigningCustomListShouldThrowException() - { - var entry = new IndexEntry - { - KeyValues = new[] - { - new KeyValuePair("foo", 123) - } - }; - - Assert.Throws(() => entry.Add("qak", "qoo")); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace Neo4jClient.Test +{ + [TestFixture] + public class IndexEntryTests + { + [Test] + public void CanInitializeWithLongForm() + { + var entry = new IndexEntry + { + Name = "index-entry", + KeyValues = new[] + { + new KeyValuePair("foo", 123), + new KeyValuePair("bar", "baz") + } + }; + + Assert.AreEqual("index-entry", entry.Name); + Assert.AreEqual(2, entry.KeyValues.Count()); + Assert.AreEqual("foo", entry.KeyValues.ElementAt(0).Key); + Assert.AreEqual(123, entry.KeyValues.ElementAt(0).Value); + Assert.AreEqual("bar", entry.KeyValues.ElementAt(1).Key); + Assert.AreEqual("baz", entry.KeyValues.ElementAt(1).Value); + } + + [Test] + public void CanInitializeWithCollectionIntializer() + { + var entry = new IndexEntry("index-entry") + { + { "foo", 123 }, + { "bar", "baz" } + }; + + Assert.AreEqual("index-entry", entry.Name); + Assert.AreEqual(2, entry.KeyValues.Count()); + Assert.AreEqual("foo", entry.KeyValues.ElementAt(0).Key); + Assert.AreEqual(123, entry.KeyValues.ElementAt(0).Value); + Assert.AreEqual("bar", entry.KeyValues.ElementAt(1).Key); + Assert.AreEqual("baz", entry.KeyValues.ElementAt(1).Value); + } + + [Test] + public void CanCallAddAfterUsingNameConstructor() + { + // ReSharper disable UseObjectOrCollectionInitializer + var entry = new IndexEntry("index-entry"); + entry.Add("qak", "qoo"); + // ReSharper restore UseObjectOrCollectionInitializer + + Assert.AreEqual(1, entry.KeyValues.Count()); + Assert.AreEqual("qak", entry.KeyValues.ElementAt(0).Key); + Assert.AreEqual("qoo", entry.KeyValues.ElementAt(0).Value); + } + + [Test] + public void CanCallAddAfterUsingCollectionIntializer() + { + // ReSharper disable UseObjectOrCollectionInitializer + var entry = new IndexEntry("index-entry") + { + { "foo", 123 }, + { "bar", "baz" } + }; + // ReSharper restore UseObjectOrCollectionInitializer + + entry.Add("qak", "qoo"); + + Assert.AreEqual(3, entry.KeyValues.Count()); + Assert.AreEqual("qak", entry.KeyValues.ElementAt(2).Key); + Assert.AreEqual("qoo", entry.KeyValues.ElementAt(2).Value); + } + + [Test] + public void AddAfterAssigningCustomListShouldThrowException() + { + var entry = new IndexEntry + { + KeyValues = new[] + { + new KeyValuePair("foo", 123) + } + }; + + Assert.Throws(() => entry.Add("qak", "qoo")); + } + } +} diff --git a/Test/MockRequest.cs b/Neo4jClient.Tests/MockRequest.cs similarity index 96% rename from Test/MockRequest.cs rename to Neo4jClient.Tests/MockRequest.cs index a522bc8d6..9eb749663 100644 --- a/Test/MockRequest.cs +++ b/Neo4jClient.Tests/MockRequest.cs @@ -1,55 +1,55 @@ -using System.Net.Http; -using Neo4jClient.Serialization; - -namespace Neo4jClient.Test -{ - public class MockRequest - { - MockRequest() {} - - public HttpMethod Method { get; set; } - public string Resource { get; set; } - public string Body { get; set; } - - public static MockRequest Get(string uri) - { - if (uri == "") uri = "/"; - return new MockRequest { Resource = uri, Method = HttpMethod.Get }; - } - - public static MockRequest PostJson(string uri, string json) - { - return new MockRequest - { - Resource = uri, - Method = HttpMethod.Post, - Body = json - }; - } - - public static MockRequest PostObjectAsJson(string uri, object body) - { - return new MockRequest - { - Resource = uri, - Method = HttpMethod.Post, - Body = new CustomJsonSerializer{JsonConverters = GraphClient.DefaultJsonConverters}.Serialize(body) - }; - } - - public static MockRequest PutObjectAsJson(string uri, object body) - { - return new MockRequest - { - Resource = uri, - Method = HttpMethod.Put, - Body = new CustomJsonSerializer{JsonConverters = GraphClient.DefaultJsonConverters}.Serialize(body) - }; - } - - public static MockRequest Delete(string uri) - { - return new MockRequest { Resource = uri, Method = HttpMethod.Delete }; - } - } -} +using System.Net.Http; +using Neo4jClient.Serialization; + +namespace Neo4jClient.Test +{ + public class MockRequest + { + MockRequest() {} + + public HttpMethod Method { get; set; } + public string Resource { get; set; } + public string Body { get; set; } + + public static MockRequest Get(string uri) + { + if (uri == "") uri = "/"; + return new MockRequest { Resource = uri, Method = HttpMethod.Get }; + } + + public static MockRequest PostJson(string uri, string json) + { + return new MockRequest + { + Resource = uri, + Method = HttpMethod.Post, + Body = json + }; + } + + public static MockRequest PostObjectAsJson(string uri, object body) + { + return new MockRequest + { + Resource = uri, + Method = HttpMethod.Post, + Body = new CustomJsonSerializer{JsonConverters = GraphClient.DefaultJsonConverters}.Serialize(body) + }; + } + + public static MockRequest PutObjectAsJson(string uri, object body) + { + return new MockRequest + { + Resource = uri, + Method = HttpMethod.Put, + Body = new CustomJsonSerializer{JsonConverters = GraphClient.DefaultJsonConverters}.Serialize(body) + }; + } + + public static MockRequest Delete(string uri) + { + return new MockRequest { Resource = uri, Method = HttpMethod.Delete }; + } + } +} diff --git a/Test/MockResponse.cs b/Neo4jClient.Tests/MockResponse.cs similarity index 81% rename from Test/MockResponse.cs rename to Neo4jClient.Tests/MockResponse.cs index 628f9fe67..28179b023 100644 --- a/Test/MockResponse.cs +++ b/Neo4jClient.Tests/MockResponse.cs @@ -1,88 +1,106 @@ -using System.Net; - -namespace Neo4jClient.Test -{ - public class MockResponse - { - public HttpStatusCode StatusCode { get; set; } - - public string StatusDescription - { - get { return StatusCode.ToString(); } - } - - public string ContentType { get; set; } - public virtual string Content { get; set; } - - public static MockResponse Json(HttpStatusCode statusCode, string json) - { - return new MockResponse - { - StatusCode = statusCode, - ContentType = "application/json", - Content = json - }; - } - - public static MockResponse NeoRoot() - { - return Json(HttpStatusCode.OK, @"{ - 'cypher' : 'http://foo/db/data/cypher', - 'batch' : 'http://foo/db/data/batch', - 'node' : 'http://foo/db/data/node', - 'node_index' : 'http://foo/db/data/index/node', - 'relationship_index' : 'http://foo/db/data/index/relationship', - 'reference_node' : 'http://foo/db/data/node/123', - 'neo4j_version' : '1.5.M02', - 'extensions_info' : 'http://foo/db/data/ext', - 'extensions' : { - 'GremlinPlugin' : { - 'execute_script' : 'http://foo/db/data/ext/GremlinPlugin/graphdb/execute_script' - } - } - }"); - } - - public static MockResponse NeoRoot20() - { - return Json(HttpStatusCode.OK, @"{ - 'cypher' : 'http://foo/db/data/cypher', - 'batch' : 'http://foo/db/data/batch', - 'node' : 'http://foo/db/data/node', - 'node_index' : 'http://foo/db/data/index/node', - 'relationship_index' : 'http://foo/db/data/index/relationship', - 'reference_node' : 'http://foo/db/data/node/123', - 'neo4j_version' : '2.0.M06', - 'extensions_info' : 'http://foo/db/data/ext', - 'extensions' : {} - }"); - } - - public static MockResponse NeoRootPre15M02() - { - return Json(HttpStatusCode.OK, @"{ - 'batch' : 'http://foo/db/data/batch', - 'node' : 'http://foo/db/data/node', - 'node_index' : 'http://foo/db/data/index/node', - 'relationship_index' : 'http://foo/db/data/index/relationship', - 'reference_node' : 'http://foo/db/data/node/123', - 'extensions_info' : 'http://foo/db/data/ext', - 'extensions' : { - } - }"); - } - - public static MockResponse Http(int statusCode) - { - return new MockResponse - { - StatusCode = (HttpStatusCode)statusCode - }; - } - - public static MockResponse Throws() - { - return new MockResponseThrows(); - } - } -} +using System.Net; + +namespace Neo4jClient.Test +{ + public class MockResponse + { + public HttpStatusCode StatusCode { get; set; } + + public string StatusDescription + { + get { return StatusCode.ToString(); } + } + + public string ContentType { get; set; } + public virtual string Content { get; set; } + public string Location { get; set; } + + public static MockResponse Json(int statusCode, string json) + { + return Json((HttpStatusCode)statusCode, json, null); + } + + public static MockResponse Json(int statusCode, string json, string location) + { + return Json((HttpStatusCode)statusCode, json, location); + } + + public static MockResponse Json(HttpStatusCode statusCode, string json) + { + return Json(statusCode, json, null); + } + + public static MockResponse Json(HttpStatusCode statusCode, string json, string location) + { + return new MockResponse + { + StatusCode = statusCode, + ContentType = "application/json", + Content = json, + Location = location + }; + } + + public static MockResponse NeoRoot() + { + return Json(HttpStatusCode.OK, @"{ + 'cypher' : 'http://foo/db/data/cypher', + 'batch' : 'http://foo/db/data/batch', + 'node' : 'http://foo/db/data/node', + 'node_index' : 'http://foo/db/data/index/node', + 'relationship_index' : 'http://foo/db/data/index/relationship', + 'reference_node' : 'http://foo/db/data/node/123', + 'neo4j_version' : '1.5.M02', + 'extensions_info' : 'http://foo/db/data/ext', + 'extensions' : { + 'GremlinPlugin' : { + 'execute_script' : 'http://foo/db/data/ext/GremlinPlugin/graphdb/execute_script' + } + } + }"); + } + + public static MockResponse NeoRoot20() + { + return Json(HttpStatusCode.OK, @"{ + 'cypher' : 'http://foo/db/data/cypher', + 'batch' : 'http://foo/db/data/batch', + 'node' : 'http://foo/db/data/node', + 'node_index' : 'http://foo/db/data/index/node', + 'relationship_index' : 'http://foo/db/data/index/relationship', + 'reference_node' : 'http://foo/db/data/node/123', + 'neo4j_version' : '2.0.M06', + 'transaction': 'http://foo/db/data/transaction', + 'extensions_info' : 'http://foo/db/data/ext', + 'extensions' : {} + }"); + } + + public static MockResponse NeoRootPre15M02() + { + return Json(HttpStatusCode.OK, @"{ + 'batch' : 'http://foo/db/data/batch', + 'node' : 'http://foo/db/data/node', + 'node_index' : 'http://foo/db/data/index/node', + 'relationship_index' : 'http://foo/db/data/index/relationship', + 'reference_node' : 'http://foo/db/data/node/123', + 'extensions_info' : 'http://foo/db/data/ext', + 'extensions' : { + } + }"); + } + + public static MockResponse Http(int statusCode) + { + return new MockResponse + { + StatusCode = (HttpStatusCode)statusCode + }; + } + + public static MockResponse Throws() + { + return new MockResponseThrows(); + } + } +} diff --git a/Test/MockResponseThrows.cs b/Neo4jClient.Tests/MockResponseThrows.cs similarity index 100% rename from Test/MockResponseThrows.cs rename to Neo4jClient.Tests/MockResponseThrows.cs diff --git a/Test/MockResponseThrowsException.cs b/Neo4jClient.Tests/MockResponseThrowsException.cs similarity index 100% rename from Test/MockResponseThrowsException.cs rename to Neo4jClient.Tests/MockResponseThrowsException.cs diff --git a/Test/Test.csproj b/Neo4jClient.Tests/Neo4jClient.Tests.csproj similarity index 95% rename from Test/Test.csproj rename to Neo4jClient.Tests/Neo4jClient.Tests.csproj index a973bcbfe..ee3e2a27a 100644 --- a/Test/Test.csproj +++ b/Neo4jClient.Tests/Neo4jClient.Tests.csproj @@ -1,190 +1,195 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {2724A871-4B4F-4C83-8E0F-2439F69CADA2} - Library - Properties - Neo4jClient.Test - Neo4jClient.Test - v4.5 - 512 - ..\ - true - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - - ..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll - True - - - ..\packages\NSubstitute.1.6.0.0\lib\NET40\NSubstitute.dll - True - - - ..\packages\NUnit.2.6.2\lib\nunit.framework.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - {343B9889-6DDF-4474-A1EC-05508A652E5A} - Neo4jClient - - - - - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {2724A871-4B4F-4C83-8E0F-2439F69CADA2} + Library + Properties + Neo4jClient.Test + Neo4jClient.Tests + v4.5 + 512 + ..\ + true + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + false + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + + ..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll + True + + + ..\packages\NSubstitute.1.8.2.0\lib\net40\NSubstitute.dll + True + + + ..\packages\NUnit.2.6.2\lib\nunit.framework.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {343B9889-6DDF-4474-A1EC-05508A652E5A} + Neo4jClient + + + + + + + + \ No newline at end of file diff --git a/Test/Test.ncrunchproject b/Neo4jClient.Tests/Neo4jClient.Tests.ncrunchproject similarity index 98% rename from Test/Test.ncrunchproject rename to Neo4jClient.Tests/Neo4jClient.Tests.ncrunchproject index 8d36b6ed1..62257550e 100644 --- a/Test/Test.ncrunchproject +++ b/Neo4jClient.Tests/Neo4jClient.Tests.ncrunchproject @@ -1,17 +1,17 @@ - - false - false - false - false - false - false - true - true - false - true - true - 60000 - - - AutoDetect + + false + false + false + false + false + false + true + true + false + true + true + 60000 + + + AutoDetect \ No newline at end of file diff --git a/Neo4jClient.Tests/Neo4jClient.Tests.v2.ncrunchproject b/Neo4jClient.Tests/Neo4jClient.Tests.v2.ncrunchproject new file mode 100644 index 000000000..edc825628 Binary files /dev/null and b/Neo4jClient.Tests/Neo4jClient.Tests.v2.ncrunchproject differ diff --git a/Test/NodeReferenceTests.cs b/Neo4jClient.Tests/NodeReferenceTests.cs similarity index 97% rename from Test/NodeReferenceTests.cs rename to Neo4jClient.Tests/NodeReferenceTests.cs index c37714dc4..60e44c6ec 100644 --- a/Test/NodeReferenceTests.cs +++ b/Neo4jClient.Tests/NodeReferenceTests.cs @@ -1,168 +1,168 @@ -using System; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test -{ - [TestFixture] - public class NodeReferenceTests - { - [Test] - public void ShouldImplicitlyCastFromIntToUntypedReference() - { - NodeReference nodeReference = 3; - Assert.AreEqual(3, nodeReference.Id); - } - - [Test] - public void ShouldImplicitlyCastFromIntToTypedReference() - { - NodeReference nodeReference = 3; - Assert.AreEqual(3, nodeReference.Id); - } - - [Test] - public void ShouldExplicitlyCastFromIntToUntypedReference() - { - var nodeReference = (NodeReference)3; - Assert.IsInstanceOf(typeof(NodeReference), nodeReference); - Assert.AreEqual(3, nodeReference.Id); - } - - [Test] - public void ShouldExplicitlyCastFromIntToTypedReference() - { - var nodeReference = (NodeReference)3; - Assert.IsInstanceOf(typeof(NodeReference), nodeReference); - Assert.AreEqual(3, nodeReference.Id); - } - - [Test] - public void ShouldAllowDirectCreationOfTypedReference() - { - var nodeReference = new NodeReference(3); - Assert.AreEqual(3, nodeReference.Id); - } - - [Test] - [TestCase(1, 2, Result = false)] - [TestCase(3, 3, Result = true)] - public bool Equals(int lhs, int rhs) - { - return new NodeReference(lhs) == new NodeReference(rhs); - } - - [Test] - [TestCase(1, 2, Result = false)] - [TestCase(3, 3, Result = true)] - public bool GetHashCode(int lhs, int rhs) - { - return new NodeReference(lhs).GetHashCode() == new NodeReference(rhs).GetHashCode(); - } - - [Test] - public void EqualsOperatorShouldReturnFalseWhenComparingInstanceWithNull() - { - var lhs = new NodeReference(3); - Assert.IsFalse(lhs == null); - } - - [Test] - public void EqualsOperatorShouldReturnTrueWhenComparingNullWithNull() - { - NodeReference lhs = null; - Assert.IsTrue(lhs == null); - } - - [Test] - public void EqualsShouldReturnFalseWhenComparingWithNull() - { - var lhs = new NodeReference(3); - Assert.IsFalse(lhs.Equals(null)); - } - - [Test] - public void EqualsShouldReturnFalseWhenComparingWithDifferentType() - { - var lhs = new NodeReference(3); - Assert.IsFalse(lhs.Equals(new object())); - } - - [Test] - public void EqualsShouldReturnTrueWhenComparingRootNodeWithNodeReferenceOfSameId() - { - var lhs = new RootNode(123); - var rhs = new NodeReference(123); - Assert.IsTrue(lhs == rhs); - } - - [Test] - public void EqualsShouldReturnTrueWhenComparingRootNodeWithRootNodeOfSameId() - { - var lhs = new RootNode(123); - var rhs = new RootNode(123); - Assert.IsTrue(lhs == rhs); - } - - [Test] - public void EqualsShouldReturnFalseWhenComparingRootNodeWithRootNodeOfDifferentId() - { - var lhs = new RootNode(123); - var rhs = new RootNode(456); - Assert.IsFalse(lhs == rhs); - } - - [Test] - public void EqualsShouldReturnFalseWhenComparingRootNodeWithNodeReferenceOfDifferentId() - { - var lhs = new RootNode(123); - var rhs = new NodeReference(4); - Assert.IsFalse(lhs == rhs); - } - - [Test] - public void NodeTypeShouldReturnTypedNodeType() - { - var reference = (NodeReference)new NodeReference(123); - Assert.AreEqual(typeof(Randomizer), reference.NodeType); - } - - [Test] - public void NodeTypeShouldReturnNullWhenUntyped() - { - var reference = new NodeReference(123); - Assert.IsNull(reference.NodeType); - } - - [Test] - [ExpectedException( - typeof(NotSupportedException), - ExpectedMessage = "You're tring to initialize NodeReference> which is too many levels of nesting. You should just be using NodeReference instead. (You use a Node, or a NodeReference, but not both together.)")] - public void TypedNodeReferenceShouldThrowExceptionIfTNodeIsWrappedAgain() - { -// ReSharper disable ObjectCreationAsStatement - new NodeReference>(123); -// ReSharper restore ObjectCreationAsStatement - } - - [Test] - public void GremlinQueryTextShouldReturnSimpleVectorStep() - { - var reference = new NodeReference(123); - var query = ((IGremlinQuery) reference); - Assert.AreEqual("g.v(p0)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void CypherShouldStartQueryFromCurrentNodeReference() - { - var graphClient = Substitute.For(); - var reference = new NodeReference(123, graphClient); - var query = reference.StartCypher("foo").Query; - Assert.AreEqual("START foo=node({p0})", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - } +using System; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test +{ + [TestFixture] + public class NodeReferenceTests + { + [Test] + public void ShouldImplicitlyCastFromIntToUntypedReference() + { + NodeReference nodeReference = 3; + Assert.AreEqual(3, nodeReference.Id); + } + + [Test] + public void ShouldImplicitlyCastFromIntToTypedReference() + { + NodeReference nodeReference = 3; + Assert.AreEqual(3, nodeReference.Id); + } + + [Test] + public void ShouldExplicitlyCastFromIntToUntypedReference() + { + var nodeReference = (NodeReference)3; + Assert.IsInstanceOf(typeof(NodeReference), nodeReference); + Assert.AreEqual(3, nodeReference.Id); + } + + [Test] + public void ShouldExplicitlyCastFromIntToTypedReference() + { + var nodeReference = (NodeReference)3; + Assert.IsInstanceOf(typeof(NodeReference), nodeReference); + Assert.AreEqual(3, nodeReference.Id); + } + + [Test] + public void ShouldAllowDirectCreationOfTypedReference() + { + var nodeReference = new NodeReference(3); + Assert.AreEqual(3, nodeReference.Id); + } + + [Test] + [TestCase(1, 2, Result = false)] + [TestCase(3, 3, Result = true)] + public bool Equals(int lhs, int rhs) + { + return new NodeReference(lhs) == new NodeReference(rhs); + } + + [Test] + [TestCase(1, 2, Result = false)] + [TestCase(3, 3, Result = true)] + public bool GetHashCode(int lhs, int rhs) + { + return new NodeReference(lhs).GetHashCode() == new NodeReference(rhs).GetHashCode(); + } + + [Test] + public void EqualsOperatorShouldReturnFalseWhenComparingInstanceWithNull() + { + var lhs = new NodeReference(3); + Assert.IsFalse(lhs == null); + } + + [Test] + public void EqualsOperatorShouldReturnTrueWhenComparingNullWithNull() + { + NodeReference lhs = null; + Assert.IsTrue(lhs == null); + } + + [Test] + public void EqualsShouldReturnFalseWhenComparingWithNull() + { + var lhs = new NodeReference(3); + Assert.IsFalse(lhs.Equals(null)); + } + + [Test] + public void EqualsShouldReturnFalseWhenComparingWithDifferentType() + { + var lhs = new NodeReference(3); + Assert.IsFalse(lhs.Equals(new object())); + } + + [Test] + public void EqualsShouldReturnTrueWhenComparingRootNodeWithNodeReferenceOfSameId() + { + var lhs = new RootNode(123); + var rhs = new NodeReference(123); + Assert.IsTrue(lhs == rhs); + } + + [Test] + public void EqualsShouldReturnTrueWhenComparingRootNodeWithRootNodeOfSameId() + { + var lhs = new RootNode(123); + var rhs = new RootNode(123); + Assert.IsTrue(lhs == rhs); + } + + [Test] + public void EqualsShouldReturnFalseWhenComparingRootNodeWithRootNodeOfDifferentId() + { + var lhs = new RootNode(123); + var rhs = new RootNode(456); + Assert.IsFalse(lhs == rhs); + } + + [Test] + public void EqualsShouldReturnFalseWhenComparingRootNodeWithNodeReferenceOfDifferentId() + { + var lhs = new RootNode(123); + var rhs = new NodeReference(4); + Assert.IsFalse(lhs == rhs); + } + + [Test] + public void NodeTypeShouldReturnTypedNodeType() + { + var reference = (NodeReference)new NodeReference(123); + Assert.AreEqual(typeof(Randomizer), reference.NodeType); + } + + [Test] + public void NodeTypeShouldReturnNullWhenUntyped() + { + var reference = new NodeReference(123); + Assert.IsNull(reference.NodeType); + } + + [Test] + [ExpectedException( + typeof(NotSupportedException), + ExpectedMessage = "You're tring to initialize NodeReference> which is too many levels of nesting. You should just be using NodeReference instead. (You use a Node, or a NodeReference, but not both together.)")] + public void TypedNodeReferenceShouldThrowExceptionIfTNodeIsWrappedAgain() + { +// ReSharper disable ObjectCreationAsStatement + new NodeReference>(123); +// ReSharper restore ObjectCreationAsStatement + } + + [Test] + public void GremlinQueryTextShouldReturnSimpleVectorStep() + { + var reference = new NodeReference(123); + var query = ((IGremlinQuery) reference); + Assert.AreEqual("g.v(p0)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void CypherShouldStartQueryFromCurrentNodeReference() + { + var graphClient = Substitute.For(); + var reference = new NodeReference(123, graphClient); + var query = reference.StartCypher("foo").Query; + Assert.AreEqual("START foo=node({p0})", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + } } \ No newline at end of file diff --git a/Test/NodeTests.cs b/Neo4jClient.Tests/NodeTests.cs similarity index 97% rename from Test/NodeTests.cs rename to Neo4jClient.Tests/NodeTests.cs index ac4df0cc5..c610399f8 100644 --- a/Test/NodeTests.cs +++ b/Neo4jClient.Tests/NodeTests.cs @@ -1,84 +1,84 @@ -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Gremlin; - -namespace Neo4jClient.Test -{ - [TestFixture] - public class NodeTests - { - [Test] - public void ClientShouldReturnClientFromReference() - { - var client = Substitute.For(); - var reference = new NodeReference(123, client); - var node = new Node(new object(), reference); - Assert.AreEqual(client, ((IGremlinQuery)node).Client); - } - - [Test] - public void GremlinQueryShouldReturnSimpleVectorStep() - { - var client = Substitute.For(); - var reference = new NodeReference(123, client); - var node = new Node(new object(), reference); - var query = (IGremlinQuery)node; - Assert.AreEqual("g.v(p0)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void CypherQueryShouldIncludeNodeAsStartBit() - { - var client = Substitute.For(); - var reference = new NodeReference(123, client); - var node = new Node(new object(), reference); - var query = node.StartCypher("foo").Query; - Assert.AreEqual("START foo=node({p0})", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - - [Test] - public void CypherQueryShouldIncludeRootNodeAsStartBit() - { - var client = Substitute.For(); - client.RootNode.ReturnsForAnyArgs(new RootNode(4, client)); - var query = client.RootNode.StartCypher("foo").Query; - Assert.AreEqual("START foo=node({p0})", query.QueryText); - Assert.AreEqual(4, query.QueryParameters["p0"]); - } - - [Test] - public void CypherQueryShouldPreserveClientReference() - { - var client = Substitute.For(); - var reference = new NodeReference(123, client); - var node = new Node(new object(), reference); - var queryBuilder = (IAttachedReference)node.StartCypher("foo"); - Assert.AreEqual(client, queryBuilder.Client); - } - - [Test] - public void EqualityOperatorShouldReturnTrueForEquivalentReferences() - { - var node1 = new Node(new object(), new NodeReference(123)); - var node2 = new Node(new object(), new NodeReference(123)); - Assert.IsTrue(node1 == node2); - } - - [Test] - public void EqualityOperatorShouldReturnFalseForDifferentReferences() - { - var node1 = new Node(new object(), new NodeReference(123)); - var node2 = new Node(new object(), new NodeReference(456)); - Assert.IsFalse(node1 == node2); - } - - [Test] - public void GetHashCodeShouldReturnNodeId() - { - var node = new Node(new object(), new NodeReference(123)); - Assert.AreEqual(123, node.Reference.Id); - } - } +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Gremlin; + +namespace Neo4jClient.Test +{ + [TestFixture] + public class NodeTests + { + [Test] + public void ClientShouldReturnClientFromReference() + { + var client = Substitute.For(); + var reference = new NodeReference(123, client); + var node = new Node(new object(), reference); + Assert.AreEqual(client, ((IGremlinQuery)node).Client); + } + + [Test] + public void GremlinQueryShouldReturnSimpleVectorStep() + { + var client = Substitute.For(); + var reference = new NodeReference(123, client); + var node = new Node(new object(), reference); + var query = (IGremlinQuery)node; + Assert.AreEqual("g.v(p0)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void CypherQueryShouldIncludeNodeAsStartBit() + { + var client = Substitute.For(); + var reference = new NodeReference(123, client); + var node = new Node(new object(), reference); + var query = node.StartCypher("foo").Query; + Assert.AreEqual("START foo=node({p0})", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + + [Test] + public void CypherQueryShouldIncludeRootNodeAsStartBit() + { + var client = Substitute.For(); + client.RootNode.ReturnsForAnyArgs(new RootNode(4, client)); + var query = client.RootNode.StartCypher("foo").Query; + Assert.AreEqual("START foo=node({p0})", query.QueryText); + Assert.AreEqual(4, query.QueryParameters["p0"]); + } + + [Test] + public void CypherQueryShouldPreserveClientReference() + { + var client = Substitute.For(); + var reference = new NodeReference(123, client); + var node = new Node(new object(), reference); + var queryBuilder = (IAttachedReference)node.StartCypher("foo"); + Assert.AreEqual(client, queryBuilder.Client); + } + + [Test] + public void EqualityOperatorShouldReturnTrueForEquivalentReferences() + { + var node1 = new Node(new object(), new NodeReference(123)); + var node2 = new Node(new object(), new NodeReference(123)); + Assert.IsTrue(node1 == node2); + } + + [Test] + public void EqualityOperatorShouldReturnFalseForDifferentReferences() + { + var node1 = new Node(new object(), new NodeReference(123)); + var node2 = new Node(new object(), new NodeReference(456)); + Assert.IsFalse(node1 == node2); + } + + [Test] + public void GetHashCodeShouldReturnNodeId() + { + var node = new Node(new object(), new NodeReference(123)); + Assert.AreEqual(123, node.Reference.Id); + } + } } \ No newline at end of file diff --git a/Test/Properties/AssemblyInfo.cs b/Neo4jClient.Tests/Properties/AssemblyInfo.cs similarity index 97% rename from Test/Properties/AssemblyInfo.cs rename to Neo4jClient.Tests/Properties/AssemblyInfo.cs index 6de673a43..4dbfc98dc 100644 --- a/Test/Properties/AssemblyInfo.cs +++ b/Neo4jClient.Tests/Properties/AssemblyInfo.cs @@ -1,36 +1,36 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("Test")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("Test")] -[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("ea5aa7ac-b000-415e-b0c8-87a28369ca18")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -// You can specify all the values or you can default the Build and Revision Numbers -// by using the '*' as shown below: -// [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.0.0.0")] -[assembly: AssemblyFileVersion("1.0.0.0")] +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Test")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft")] +[assembly: AssemblyProduct("Test")] +[assembly: AssemblyCopyright("Copyright © Microsoft 2011")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ea5aa7ac-b000-415e-b0c8-87a28369ca18")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Test/RelationshipReferenceTests.cs b/Neo4jClient.Tests/RelationshipReferenceTests.cs similarity index 96% rename from Test/RelationshipReferenceTests.cs rename to Neo4jClient.Tests/RelationshipReferenceTests.cs index 4a2bd0b31..845979eec 100644 --- a/Test/RelationshipReferenceTests.cs +++ b/Neo4jClient.Tests/RelationshipReferenceTests.cs @@ -1,77 +1,77 @@ -using Neo4jClient.Gremlin; -using NUnit.Framework; - -namespace Neo4jClient.Test -{ - [TestFixture] - public class RelationshipReferenceTests - { - [Test] - public void ShouldImplicitlyCastFromInt() - { - RelationshipReference relationshipReference = 3; - Assert.AreEqual(3, relationshipReference.Id); - } - - [Test] - public void ShouldExplicitlyCastFromInt() - { - var relationshipReference = (RelationshipReference)3; - Assert.IsInstanceOf(typeof(RelationshipReference), relationshipReference); - Assert.AreEqual(3, relationshipReference.Id); - } - - [Test] - [TestCase(1, 2, Result = false)] - [TestCase(3, 3, Result = true)] - public bool Equals(int lhs, int rhs) - { - return new RelationshipReference(lhs) == new RelationshipReference(rhs); - } - - [Test] - [TestCase(1, 2, Result = false)] - [TestCase(3, 3, Result = true)] - public bool GetHashCode(int lhs, int rhs) - { - return new RelationshipReference(lhs).GetHashCode() == new RelationshipReference(rhs).GetHashCode(); - } - - [Test] - public void EqualsOperatorShouldReturnFalseWhenComparingInstanceWithNull() - { - var lhs = new RelationshipReference(3); - Assert.IsFalse(lhs == null); - } - - [Test] - public void EqualsOperatorShouldReturnTrueWhenComparingNullWithNull() - { - RelationshipReference lhs = null; - Assert.IsTrue(lhs == null); - } - - [Test] - public void EqualsShouldReturnFalseWhenComparingWithNull() - { - var lhs = new RelationshipReference(3); - Assert.IsFalse(lhs.Equals(null)); - } - - [Test] - public void EqualsShouldReturnFalseWhenComparingWithDifferentType() - { - var lhs = new RelationshipReference(3); - Assert.IsFalse(lhs.Equals(new object())); - } - - [Test] - public void GremlinQueryTextShouldReturnSimpleEdgeStep() - { - var reference = new RelationshipReference(123); - var query = ((IGremlinQuery)reference); - Assert.AreEqual("g.e(p0)", query.QueryText); - Assert.AreEqual(123, query.QueryParameters["p0"]); - } - } +using Neo4jClient.Gremlin; +using NUnit.Framework; + +namespace Neo4jClient.Test +{ + [TestFixture] + public class RelationshipReferenceTests + { + [Test] + public void ShouldImplicitlyCastFromInt() + { + RelationshipReference relationshipReference = 3; + Assert.AreEqual(3, relationshipReference.Id); + } + + [Test] + public void ShouldExplicitlyCastFromInt() + { + var relationshipReference = (RelationshipReference)3; + Assert.IsInstanceOf(typeof(RelationshipReference), relationshipReference); + Assert.AreEqual(3, relationshipReference.Id); + } + + [Test] + [TestCase(1, 2, Result = false)] + [TestCase(3, 3, Result = true)] + public bool Equals(int lhs, int rhs) + { + return new RelationshipReference(lhs) == new RelationshipReference(rhs); + } + + [Test] + [TestCase(1, 2, Result = false)] + [TestCase(3, 3, Result = true)] + public bool GetHashCode(int lhs, int rhs) + { + return new RelationshipReference(lhs).GetHashCode() == new RelationshipReference(rhs).GetHashCode(); + } + + [Test] + public void EqualsOperatorShouldReturnFalseWhenComparingInstanceWithNull() + { + var lhs = new RelationshipReference(3); + Assert.IsFalse(lhs == null); + } + + [Test] + public void EqualsOperatorShouldReturnTrueWhenComparingNullWithNull() + { + RelationshipReference lhs = null; + Assert.IsTrue(lhs == null); + } + + [Test] + public void EqualsShouldReturnFalseWhenComparingWithNull() + { + var lhs = new RelationshipReference(3); + Assert.IsFalse(lhs.Equals(null)); + } + + [Test] + public void EqualsShouldReturnFalseWhenComparingWithDifferentType() + { + var lhs = new RelationshipReference(3); + Assert.IsFalse(lhs.Equals(new object())); + } + + [Test] + public void GremlinQueryTextShouldReturnSimpleEdgeStep() + { + var reference = new RelationshipReference(123); + var query = ((IGremlinQuery)reference); + Assert.AreEqual("g.e(p0)", query.QueryText); + Assert.AreEqual(123, query.QueryParameters["p0"]); + } + } } \ No newline at end of file diff --git a/Test/RelationshipTests.cs b/Neo4jClient.Tests/RelationshipTests.cs similarity index 97% rename from Test/RelationshipTests.cs rename to Neo4jClient.Tests/RelationshipTests.cs index 3578e2a47..0765e96bf 100644 --- a/Test/RelationshipTests.cs +++ b/Neo4jClient.Tests/RelationshipTests.cs @@ -1,146 +1,146 @@ -using System; -using System.Linq; -using NUnit.Framework; - -namespace Neo4jClient.Test -{ - [TestFixture] - public class RelationshipTests - { - [Test] - public void GetAllowedSourceNodeTypesShouldReturnAllTypes() - { - // Act - var types = Relationship.GetAllowedNodeTypes(typeof (TestRelationship), RelationshipEnd.SourceNode); - - // Assert - CollectionAssert.AreEquivalent( - new[] { typeof(Foo), typeof(Bar) }, - types.ToArray() - ); - } - - [Test] - public void GetAllowedTargetNodeTypesShouldReturnAllTypes() - { - // Act - var types = Relationship.GetAllowedNodeTypes(typeof(TestRelationship), RelationshipEnd.TargetNode); - - // Assert - CollectionAssert.AreEquivalent( - new[] { typeof(Bar), typeof(Baz) }, - types.ToArray() - ); - } - - [Test] - [TestCase(RelationshipDirection.Incoming)] - [TestCase(RelationshipDirection.Outgoing)] - public void DetermineRelationshipDirectionShouldReturnExplicitDirection(RelationshipDirection direction) - { - // Arrange - var relationship = new TestRelationship(new NodeReference(0)) { Direction = direction }; - var calculatedDirection = Relationship.DetermineRelationshipDirection(null, relationship); - Assert.AreEqual(direction, calculatedDirection); - } - - [Test] - public void DetermineRelationshipDirectionShouldReturnOutgoingWhenBaseNodeIsOnlyValidAsASourceNodeAndOtherNodeIsOnlyValidAsATargetNode() - { - var baseNodeType = typeof(Foo); - var relationship = new TestRelationship(new NodeReference(123)); - var calculatedDirection = Relationship.DetermineRelationshipDirection(baseNodeType, relationship); - - Assert.AreEqual(RelationshipDirection.Outgoing, calculatedDirection); - } - - [Test] - public void DetermineRelationshipDirectionShouldReturnOutgoingWhenBaseNodeIsOnlyValidAsASourceNodeEvenIfOtherNodeIsAlsoValidAsASourceNode() - { - var baseNodeType = typeof(Foo); - var relationship = new TestRelationship(new NodeReference(123)); - var calculatedDirection = Relationship.DetermineRelationshipDirection(baseNodeType, relationship); - - Assert.AreEqual(RelationshipDirection.Outgoing, calculatedDirection); - } - - [Test] - public void DetermineRelationshipDirectionShouldReturnIncomingWhenBaseNodeIsOnlyValidAsATargetNodeAndOtherNodeIsOnlyValidAsASourceNode() - { - var baseNodeType = typeof(Baz); - var relationship = new TestRelationship(new NodeReference(123)); - var calculatedDirection = Relationship.DetermineRelationshipDirection(baseNodeType, relationship); - - Assert.AreEqual(RelationshipDirection.Incoming, calculatedDirection); - } - - [Test] - public void DetermineRelationshipDirectionShouldReturnIncomingWhenBaseNodeIsOnlyValidAsATargetNodeEvenIfOtherNodeIsAlsoValidAsATargetNode() - { - var baseNodeType = typeof(Baz); - var relationship = new TestRelationship(new NodeReference(123)); - var calculatedDirection = Relationship.DetermineRelationshipDirection(baseNodeType, relationship); - - Assert.AreEqual(RelationshipDirection.Incoming, calculatedDirection); - } - - [Test] - [ExpectedException(typeof(AmbiguousRelationshipDirectionException))] - public void DetermineRelationshipDirectionShouldThrowExceptionWhenBothNodesAreValidAsSourceAndTargetNodes() - { - var baseNodeType = typeof(Bar); - var relationship = new TestRelationship(new NodeReference(123)); - Relationship.DetermineRelationshipDirection(baseNodeType, relationship); - } - - [Test] - [ExpectedException(typeof(AmbiguousRelationshipDirectionException))] - public void DetermineRelationshipDirectionShouldReturnIncomingWhenBaseNodeOnlyValidAsTargetAndSourceNodeNotDefinedAsEither() - { - var baseNodeType = typeof(Baz); - var relationship = new TestRelationship(new NodeReference(123)); - Assert.AreEqual(RelationshipDirection.Incoming, Relationship.DetermineRelationshipDirection(baseNodeType, relationship)); - } - - [Test] - [ExpectedException(typeof(AmbiguousRelationshipDirectionException))] - public void DetermineRelationshipDirectionShouldThrowExceptionWhenNeitherNodeIsValidAtEitherEnd() - { - var baseNodeType = typeof(Zip); - var relationship = new TestRelationship(new NodeReference(123)); - Relationship.DetermineRelationshipDirection(baseNodeType, relationship); - } - - [Test] - public void DetermineRelationshipDirectionShouldReturnOutgoingWhenBaseNodeIsValidAsASourceNodeAndOtherNodeIsAnUntypedReference() - { - var baseNodeType = typeof(Bar); - var relationship = new TestRelationship(new NodeReference(123)); - var calculatedDirection = Relationship.DetermineRelationshipDirection(baseNodeType, relationship); - - Assert.AreEqual(RelationshipDirection.Outgoing, calculatedDirection); - } - - public class Foo { } - public class Bar { } - public class Baz { } - public class Qak { } - public class Zip { } - - public class TestRelationship : Relationship, - IRelationshipAllowingSourceNode, - IRelationshipAllowingSourceNode, - IRelationshipAllowingTargetNode, - IRelationshipAllowingTargetNode - { - public TestRelationship(NodeReference targetNode) : base(targetNode) - { - } - - public override string RelationshipTypeKey - { - get { throw new NotImplementedException(); } - } - } - } +using System; +using System.Linq; +using NUnit.Framework; + +namespace Neo4jClient.Test +{ + [TestFixture] + public class RelationshipTests + { + [Test] + public void GetAllowedSourceNodeTypesShouldReturnAllTypes() + { + // Act + var types = Relationship.GetAllowedNodeTypes(typeof (TestRelationship), RelationshipEnd.SourceNode); + + // Assert + CollectionAssert.AreEquivalent( + new[] { typeof(Foo), typeof(Bar) }, + types.ToArray() + ); + } + + [Test] + public void GetAllowedTargetNodeTypesShouldReturnAllTypes() + { + // Act + var types = Relationship.GetAllowedNodeTypes(typeof(TestRelationship), RelationshipEnd.TargetNode); + + // Assert + CollectionAssert.AreEquivalent( + new[] { typeof(Bar), typeof(Baz) }, + types.ToArray() + ); + } + + [Test] + [TestCase(RelationshipDirection.Incoming)] + [TestCase(RelationshipDirection.Outgoing)] + public void DetermineRelationshipDirectionShouldReturnExplicitDirection(RelationshipDirection direction) + { + // Arrange + var relationship = new TestRelationship(new NodeReference(0)) { Direction = direction }; + var calculatedDirection = Relationship.DetermineRelationshipDirection(null, relationship); + Assert.AreEqual(direction, calculatedDirection); + } + + [Test] + public void DetermineRelationshipDirectionShouldReturnOutgoingWhenBaseNodeIsOnlyValidAsASourceNodeAndOtherNodeIsOnlyValidAsATargetNode() + { + var baseNodeType = typeof(Foo); + var relationship = new TestRelationship(new NodeReference(123)); + var calculatedDirection = Relationship.DetermineRelationshipDirection(baseNodeType, relationship); + + Assert.AreEqual(RelationshipDirection.Outgoing, calculatedDirection); + } + + [Test] + public void DetermineRelationshipDirectionShouldReturnOutgoingWhenBaseNodeIsOnlyValidAsASourceNodeEvenIfOtherNodeIsAlsoValidAsASourceNode() + { + var baseNodeType = typeof(Foo); + var relationship = new TestRelationship(new NodeReference(123)); + var calculatedDirection = Relationship.DetermineRelationshipDirection(baseNodeType, relationship); + + Assert.AreEqual(RelationshipDirection.Outgoing, calculatedDirection); + } + + [Test] + public void DetermineRelationshipDirectionShouldReturnIncomingWhenBaseNodeIsOnlyValidAsATargetNodeAndOtherNodeIsOnlyValidAsASourceNode() + { + var baseNodeType = typeof(Baz); + var relationship = new TestRelationship(new NodeReference(123)); + var calculatedDirection = Relationship.DetermineRelationshipDirection(baseNodeType, relationship); + + Assert.AreEqual(RelationshipDirection.Incoming, calculatedDirection); + } + + [Test] + public void DetermineRelationshipDirectionShouldReturnIncomingWhenBaseNodeIsOnlyValidAsATargetNodeEvenIfOtherNodeIsAlsoValidAsATargetNode() + { + var baseNodeType = typeof(Baz); + var relationship = new TestRelationship(new NodeReference(123)); + var calculatedDirection = Relationship.DetermineRelationshipDirection(baseNodeType, relationship); + + Assert.AreEqual(RelationshipDirection.Incoming, calculatedDirection); + } + + [Test] + [ExpectedException(typeof(AmbiguousRelationshipDirectionException))] + public void DetermineRelationshipDirectionShouldThrowExceptionWhenBothNodesAreValidAsSourceAndTargetNodes() + { + var baseNodeType = typeof(Bar); + var relationship = new TestRelationship(new NodeReference(123)); + Relationship.DetermineRelationshipDirection(baseNodeType, relationship); + } + + [Test] + [ExpectedException(typeof(AmbiguousRelationshipDirectionException))] + public void DetermineRelationshipDirectionShouldReturnIncomingWhenBaseNodeOnlyValidAsTargetAndSourceNodeNotDefinedAsEither() + { + var baseNodeType = typeof(Baz); + var relationship = new TestRelationship(new NodeReference(123)); + Assert.AreEqual(RelationshipDirection.Incoming, Relationship.DetermineRelationshipDirection(baseNodeType, relationship)); + } + + [Test] + [ExpectedException(typeof(AmbiguousRelationshipDirectionException))] + public void DetermineRelationshipDirectionShouldThrowExceptionWhenNeitherNodeIsValidAtEitherEnd() + { + var baseNodeType = typeof(Zip); + var relationship = new TestRelationship(new NodeReference(123)); + Relationship.DetermineRelationshipDirection(baseNodeType, relationship); + } + + [Test] + public void DetermineRelationshipDirectionShouldReturnOutgoingWhenBaseNodeIsValidAsASourceNodeAndOtherNodeIsAnUntypedReference() + { + var baseNodeType = typeof(Bar); + var relationship = new TestRelationship(new NodeReference(123)); + var calculatedDirection = Relationship.DetermineRelationshipDirection(baseNodeType, relationship); + + Assert.AreEqual(RelationshipDirection.Outgoing, calculatedDirection); + } + + public class Foo { } + public class Bar { } + public class Baz { } + public class Qak { } + public class Zip { } + + public class TestRelationship : Relationship, + IRelationshipAllowingSourceNode, + IRelationshipAllowingSourceNode, + IRelationshipAllowingTargetNode, + IRelationshipAllowingTargetNode + { + public TestRelationship(NodeReference targetNode) : base(targetNode) + { + } + + public override string RelationshipTypeKey + { + get { throw new NotImplementedException(); } + } + } + } } \ No newline at end of file diff --git a/Test/Relationships/OwnedBy.cs b/Neo4jClient.Tests/Relationships/OwnedBy.cs similarity index 96% rename from Test/Relationships/OwnedBy.cs rename to Neo4jClient.Tests/Relationships/OwnedBy.cs index e4f1397b7..1a434ec8e 100644 --- a/Test/Relationships/OwnedBy.cs +++ b/Neo4jClient.Tests/Relationships/OwnedBy.cs @@ -1,19 +1,19 @@ -using Neo4jClient.Test.Domain; - -namespace Neo4jClient.Test.Relationships -{ - public class OwnedBy : - Relationship, - IRelationshipAllowingSourceNode, - IRelationshipAllowingTargetNode - { - public OwnedBy(NodeReference otherNode) - : base(otherNode) - {} - - public override string RelationshipTypeKey - { - get { return "OWNED_BY"; } - } - } +using Neo4jClient.Test.Domain; + +namespace Neo4jClient.Test.Relationships +{ + public class OwnedBy : + Relationship, + IRelationshipAllowingSourceNode, + IRelationshipAllowingTargetNode + { + public OwnedBy(NodeReference otherNode) + : base(otherNode) + {} + + public override string RelationshipTypeKey + { + get { return "OWNED_BY"; } + } + } } \ No newline at end of file diff --git a/Test/Relationships/Requires.cs b/Neo4jClient.Tests/Relationships/Requires.cs similarity index 96% rename from Test/Relationships/Requires.cs rename to Neo4jClient.Tests/Relationships/Requires.cs index af94d0c31..4656d67ca 100644 --- a/Test/Relationships/Requires.cs +++ b/Neo4jClient.Tests/Relationships/Requires.cs @@ -1,25 +1,25 @@ -using Neo4jClient.Test.Domain; - -namespace Neo4jClient.Test.Relationships -{ - public class Requires : - Relationship, - IRelationshipAllowingSourceNode, - IRelationshipAllowingSourceNode, - IRelationshipAllowingTargetNode - { - public Requires(NodeReference otherUser, Payload data) - : base(otherUser, data) - {} - - public class Payload - { - public int Count { get; set; } - } - - public override string RelationshipTypeKey - { - get { return "REQUIRES"; } - } - } +using Neo4jClient.Test.Domain; + +namespace Neo4jClient.Test.Relationships +{ + public class Requires : + Relationship, + IRelationshipAllowingSourceNode, + IRelationshipAllowingSourceNode, + IRelationshipAllowingTargetNode + { + public Requires(NodeReference otherUser, Payload data) + : base(otherUser, data) + {} + + public class Payload + { + public int Count { get; set; } + } + + public override string RelationshipTypeKey + { + get { return "REQUIRES"; } + } + } } \ No newline at end of file diff --git a/Test/Relationships/StoredIn.cs b/Neo4jClient.Tests/Relationships/StoredIn.cs similarity index 96% rename from Test/Relationships/StoredIn.cs rename to Neo4jClient.Tests/Relationships/StoredIn.cs index 1b7e4c559..9e24ab5cf 100644 --- a/Test/Relationships/StoredIn.cs +++ b/Neo4jClient.Tests/Relationships/StoredIn.cs @@ -1,20 +1,20 @@ -using Neo4jClient.Test.Domain; - -namespace Neo4jClient.Test.Relationships -{ - public class StoredIn : - Relationship, - IRelationshipAllowingSourceNode, - IRelationshipAllowingSourceNode, - IRelationshipAllowingTargetNode - { - public StoredIn(NodeReference otherNode) - : base(otherNode) - {} - - public override string RelationshipTypeKey - { - get { return "STORED_IN"; } - } - } +using Neo4jClient.Test.Domain; + +namespace Neo4jClient.Test.Relationships +{ + public class StoredIn : + Relationship, + IRelationshipAllowingSourceNode, + IRelationshipAllowingSourceNode, + IRelationshipAllowingTargetNode + { + public StoredIn(NodeReference otherNode) + : base(otherNode) + {} + + public override string RelationshipTypeKey + { + get { return "STORED_IN"; } + } + } } \ No newline at end of file diff --git a/Test/RestTestHarness.cs b/Neo4jClient.Tests/RestTestHarness.cs similarity index 70% rename from Test/RestTestHarness.cs rename to Neo4jClient.Tests/RestTestHarness.cs index 806762e36..6fb9021ac 100644 --- a/Test/RestTestHarness.cs +++ b/Neo4jClient.Tests/RestTestHarness.cs @@ -1,168 +1,220 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading.Tasks; -using NSubstitute; -using NUnit.Framework; - -namespace Neo4jClient.Test -{ - public class RestTestHarness : IEnumerable, IDisposable - { - readonly IDictionary recordedResponses = new Dictionary(); - readonly IList processedRequests = new List(); - readonly IList unservicedRequests = new List(); - public readonly string BaseUri = "http://foo/db/data"; - - public void Add(MockRequest request, MockResponse response) - { - recordedResponses.Add(request, response); - } - - public GraphClient CreateGraphClient() - { - if (!recordedResponses.Keys.Any(r => r.Resource == "" || r.Resource == "/")) - Add(MockRequest.Get(""), MockResponse.NeoRoot()); - - var httpClient = GenerateHttpClient(BaseUri); - - var graphClient = new GraphClient(new Uri(BaseUri), httpClient); - return graphClient; - } - - public IRawGraphClient CreateAndConnectGraphClient() - { - var graphClient = CreateGraphClient(); - graphClient.Connect(); - return graphClient; - } - - public IEnumerator GetEnumerator() - { - throw new NotSupportedException("This is just here to support dictionary style collection initializers for this type. Nothing more than syntactic sugar. Do not try and enumerate this type."); - } - - public void AssertAllRequestsWereReceived() - { - if (unservicedRequests.Any()) - Assert.Fail(string.Join("\r\n\r\n", unservicedRequests.ToArray())); - - var resourcesThatWereNeverRequested = recordedResponses - .Select(r => r.Key) - .Where(r => !processedRequests.Contains(r)) - .Select(r => string.Format("{0} {1}", r.Method, r.Resource)) - .ToArray(); - - if (!resourcesThatWereNeverRequested.Any()) - return; - - Assert.Fail( - "The test expected REST requests for the following resources, but they were never made: {0}", - string.Join(", ", resourcesThatWereNeverRequested)); - } - - public IHttpClient GenerateHttpClient(string baseUri) - { - var httpClient = Substitute.For(); - - httpClient - .SendAsync(Arg.Any()) - .ReturnsForAnyArgs(ci => - { - var request = ci.Arg(); - var task = new Task(() => HandleRequest(request, baseUri)); - task.Start(); - return task; - }); - - return httpClient; - } - - HttpResponseMessage HandleRequest(HttpRequestMessage request, string baseUri) - { - // User info isn't transmitted over the wire, so we need to strip it here too - var requestUri = request.RequestUri; - if (!string.IsNullOrEmpty(requestUri.UserInfo)) - requestUri = new UriBuilder(requestUri) {UserName = "", Password = ""}.Uri; - - var matchingRequests = recordedResponses - .Where(can => requestUri.AbsoluteUri == baseUri + can.Key.Resource) - .Where(can => request.Method.ToString().Equals(can.Key.Method.ToString(), StringComparison.OrdinalIgnoreCase)); - - string requestBody = null; - if (request.Content != null) - { - var requestBodyTask = request.Content.ReadAsStringAsync(); - requestBodyTask.Wait(); - requestBody = requestBodyTask.Result; - } - - if (request.Method == HttpMethod.Post) - { - matchingRequests = matchingRequests - .Where(can => - { - var cannedRequest = can.Key; - var cannedRequestBody = cannedRequest.Body; - cannedRequestBody = cannedRequestBody ?? ""; - return IsJsonEquivalent(cannedRequestBody, requestBody); - }); - } - - var results = matchingRequests.ToArray(); - - if (!results.Any()) - { - var message = string.Format("No corresponding request-response pair was defined in the test harness for: {0} {1}", request.Method, requestUri.AbsoluteUri); - if (!string.IsNullOrEmpty(requestBody)) - { - message += "\r\n\r\n" + requestBody; - } - unservicedRequests.Add(message); - throw new InvalidOperationException(message); - } - - var result = results.Single(); - - processedRequests.Add(result.Key); - - var response = result.Value; - - return new HttpResponseMessage - { - StatusCode = response.StatusCode, - ReasonPhrase = response.StatusDescription, - Content = string.IsNullOrEmpty(response.Content) ? null : new StringContent(response.Content, null, response.ContentType) - }; - } - - static bool IsJsonEquivalent(string lhs, string rhs) - { - lhs = NormalizeJson(lhs); - rhs = NormalizeJson(rhs); - return lhs == rhs; - } - - static string NormalizeJson(string input) - { - if (input.First() == '"' && - input.Last() == '"') - input = input.Substring(1, input.Length - 2); - - return input - .Replace(" ", "") - .Replace("'", "\"") - .Replace("\r", "") - .Replace("\\r", "") - .Replace("\n", "") - .Replace("\\n", ""); - } - - public void Dispose() - { - AssertAllRequestsWereReceived(); - } - } -} +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Threading; +using System.Threading.Tasks; +using Neo4jClient.Execution; +using Neo4jClient.Transactions; +using NSubstitute; +using NUnit.Framework; + +namespace Neo4jClient.Test +{ + public class RestTestHarness : IEnumerable, IDisposable + { + readonly IDictionary recordedResponses = new Dictionary(); + readonly List requestsThatShouldNotBeProcessed = new List(); + readonly IList processedRequests = new List(); + readonly IList unservicedRequests = new List(); + public readonly string BaseUri = "http://foo/db/data"; + private readonly bool assertConstraintsAreMet; + + public RestTestHarness() : this(true) + { + } + + public RestTestHarness(bool assertConstraintsAreMet) + { + this.assertConstraintsAreMet = assertConstraintsAreMet; + } + + public void Add(MockRequest request, MockResponse response) + { + recordedResponses.Add(request, response); + } + + public RestTestHarness ShouldNotBeCalled(params MockRequest[] requests) + { + requestsThatShouldNotBeProcessed.AddRange(requests); + return this; + } + + public GraphClient CreateGraphClient(bool neo4j2) + { + if (!recordedResponses.Keys.Any(r => r.Resource == "" || r.Resource == "/")) + Add(MockRequest.Get(""), neo4j2 ? MockResponse.NeoRoot20() : MockResponse.NeoRoot()); + + var httpClient = GenerateHttpClient(BaseUri); + + var graphClient = new GraphClient(new Uri(BaseUri), httpClient); + return graphClient; + } + + public ITransactionalGraphClient CreateAndConnectTransactionalGraphClient() + { + var graphClient = CreateGraphClient(true); + graphClient.Connect(); + return graphClient; + } + + public IRawGraphClient CreateAndConnectGraphClient() + { + var graphClient = CreateGraphClient(false); + graphClient.Connect(); + return graphClient; + } + + public IEnumerator GetEnumerator() + { + throw new NotSupportedException("This is just here to support dictionary style collection initializers for this type. Nothing more than syntactic sugar. Do not try and enumerate this type."); + } + + public void AssertRequestConstraintsAreMet() + { + if (unservicedRequests.Any()) + Assert.Fail(string.Join("\r\n\r\n", unservicedRequests.ToArray())); + + var resourcesThatWereNeverRequested = recordedResponses + .Select(r => r.Key) + .Where(r => !(processedRequests.Contains(r) || requestsThatShouldNotBeProcessed.Contains(r))) + .Select(r => string.Format("{0} {1}", r.Method, r.Resource)) + .ToArray(); + + var processedResourcesThatShouldntHaveBeenRequested = requestsThatShouldNotBeProcessed + .Where(r => processedRequests.Contains(r)) + .Select(r => string.Format("{0} {1}", r.Method, r.Resource)) + .ToArray(); + + if (processedResourcesThatShouldntHaveBeenRequested.Any()) + { + Assert.Fail( + "The test should not have made REST requests for the following resources: {0}", + string.Join(", ", processedResourcesThatShouldntHaveBeenRequested)); + } + + if (!resourcesThatWereNeverRequested.Any()) + return; + + Assert.Fail( + "The test expected REST requests for the following resources, but they were never made: {0}", + string.Join(", ", resourcesThatWereNeverRequested)); + } + + public IHttpClient GenerateHttpClient(string baseUri) + { + var httpClient = Substitute.For(); + + httpClient + .SendAsync(Arg.Any()) + .ReturnsForAnyArgs(ci => + { + var request = ci.Arg(); + var task = new Task(() => HandleRequest(request, baseUri)); + task.Start(); + return task; + }); + + return httpClient; + } + + protected virtual HttpResponseMessage HandleRequest(HttpRequestMessage request, string baseUri) + { + // User info isn't transmitted over the wire, so we need to strip it here too + var requestUri = request.RequestUri; + if (!string.IsNullOrEmpty(requestUri.UserInfo)) + requestUri = new UriBuilder(requestUri) {UserName = "", Password = ""}.Uri; + + var matchingRequests = recordedResponses + .Where(can => requestUri.AbsoluteUri == baseUri + can.Key.Resource) + .Where(can => request.Method.ToString().Equals(can.Key.Method.ToString(), StringComparison.OrdinalIgnoreCase)); + + string requestBody = null; + if (request.Content != null) + { + var requestBodyTask = request.Content.ReadAsStringAsync(); + requestBodyTask.Wait(); + requestBody = requestBodyTask.Result; + } + + if (request.Method == HttpMethod.Post) + { + matchingRequests = matchingRequests + .Where(can => + { + var cannedRequest = can.Key; + var cannedRequestBody = cannedRequest.Body; + cannedRequestBody = cannedRequestBody ?? ""; + return IsJsonEquivalent(cannedRequestBody, requestBody); + }); + } + + var results = matchingRequests.ToArray(); + + if (!results.Any()) + { + var message = string.Format("No corresponding request-response pair was defined in the test harness for: {0} {1}", request.Method, requestUri.AbsoluteUri); + if (!string.IsNullOrEmpty(requestBody)) + { + message += "\r\n\r\n" + requestBody; + } + unservicedRequests.Add(message); + throw new InvalidOperationException(message); + } + + var result = results.Single(); + + processedRequests.Add(result.Key); + + var response = result.Value; + + var httpResponse = new HttpResponseMessage + { + StatusCode = response.StatusCode, + ReasonPhrase = response.StatusDescription, + Content = string.IsNullOrEmpty(response.Content) ? null : new StringContent(response.Content, null, response.ContentType) + }; + + if (string.IsNullOrEmpty(response.Location)) + { + return httpResponse; + } + + httpResponse.Headers.Location = new Uri(response.Location); + return httpResponse; + } + + static bool IsJsonEquivalent(string lhs, string rhs) + { + lhs = NormalizeJson(lhs); + rhs = NormalizeJson(rhs); + return lhs == rhs; + } + + static string NormalizeJson(string input) + { + if (input.First() == '"' && + input.Last() == '"') + input = input.Substring(1, input.Length - 2); + + return input + .Replace(" ", "") + .Replace("'", "\"") + .Replace("\r", "") + .Replace("\\r", "") + .Replace("\n", "") + .Replace("\\n", ""); + } + + public void Dispose() + { + if (assertConstraintsAreMet) + { + AssertRequestConstraintsAreMet(); + } + } + } +} diff --git a/Test/Serialization/CustomJsonDeserializerTests.cs b/Neo4jClient.Tests/Serialization/CustomJsonDeserializerTests.cs similarity index 97% rename from Test/Serialization/CustomJsonDeserializerTests.cs rename to Neo4jClient.Tests/Serialization/CustomJsonDeserializerTests.cs index ffa47c6a1..136120104 100644 --- a/Test/Serialization/CustomJsonDeserializerTests.cs +++ b/Neo4jClient.Tests/Serialization/CustomJsonDeserializerTests.cs @@ -1,377 +1,377 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using Newtonsoft.Json.Serialization; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.ApiModels.Gremlin; -using Neo4jClient.Serialization; -using Newtonsoft.Json; - -namespace Neo4jClient.Test.Serialization -{ - [TestFixture] - public class CustomJsonDeserializerTests - { - [Test] - [TestCase("", null)] - [TestCase("rekjre", null)] - [TestCase("/Date(abcs)/", null)] - [TestCase("/Date(abcs+0000)/", null)] - [TestCase("/Date(1315271562384)/", "2011-09-06T01:12:42.3840000+00:00")] - [TestCase("/Date(1315271562384+0000)/", "2011-09-06T01:12:42.3840000+00:00")] - [TestCase("/Date(1315271562384+0200)/", "2011-09-06T03:12:42.3840000+02:00")] - [TestCase("/Date(1315271562384+1000)/", "2011-09-06T11:12:42.3840000+10:00")] - [TestCase("/Date(-2187290565386+0000)/", "1900-09-09T03:17:14.6140000+00:00")] - [TestCase("2011-09-06T01:12:42+10:00", "2011-09-06T01:12:42.0000000+10:00")] - [TestCase("2011-09-06T01:12:42+09:00", "2011-09-06T01:12:42.0000000+09:00")] - [TestCase("2011-09-06T01:12:42-07:00", "2011-09-06T01:12:42.0000000-07:00")] - [TestCase("2011-09-06T01:12:42+00:00", "2011-09-06T01:12:42.0000000+00:00")] - [TestCase("2012-08-31T10:11:00.3642578+10:00", "2012-08-31T10:11:00.3642578+10:00")] - [TestCase("2012-08-31T00:11:00.3642578+00:00", "2012-08-31T00:11:00.3642578+00:00")] - [TestCase("2011/09/06 10:11:00 +10:00", "2011-09-06T10:11:00.0000000+10:00")] - [TestCase("2011/09/06 10:11:00 AM +10:00", "2011-09-06T10:11:00.0000000+10:00")] - [TestCase("2011/09/06 12:11:00 PM +10:00", "2011-09-06T12:11:00.0000000+10:00")] - public void DeserializeShouldPreserveOffsetValuesUsingIso8601Format(string input, string expectedResult) - { - var culturesToTest = new[] {"en-AU", "en-US"}; - - foreach (var cultureName in culturesToTest) - { - // Arrange - var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters, new CultureInfo(cultureName)); - var content = string.Format("{{'Foo':'{0}'}}", input); - - // Act - var result = deserializer.Deserialize(content); - - // Assert - if (expectedResult == null) - Assert.IsNull(result.Foo); - else - { - Assert.IsNotNull(result.Foo); - Assert.AreEqual(expectedResult, result.Foo.Value.ToString("o", CultureInfo.InvariantCulture)); - } - } - } - - [Test] - [TestCase("", null, DateTimeKind.Utc)] - [TestCase("rekjre", null, DateTimeKind.Utc)] - [TestCase("/Date(abcs)/", null, DateTimeKind.Utc)] - [TestCase("/Date(1315271562384)/", "2011-09-06T01:12:42.3840000Z", DateTimeKind.Utc)] - [TestCase("/Date(-2187290565386)/", "1900-09-09T03:17:14.6140000Z", DateTimeKind.Utc)] - [TestCase("2015-07-27T22:30:35Z", "2015-07-27T22:30:35.0000000Z", DateTimeKind.Utc)] - [TestCase("2011-09-06T01:12:42", "2011-09-06T01:12:42.0000000", DateTimeKind.Unspecified)] - [TestCase("2012-08-31T10:11:00.3642578", "2012-08-31T10:11:00.3642578", DateTimeKind.Unspecified)] - [TestCase("2011/09/06 10:11:00", "2011-09-06T10:11:00.0000000", DateTimeKind.Unspecified)] - [TestCase("2011/09/06 10:11:00 AM", "2011-09-06T10:11:00.0000000", DateTimeKind.Unspecified)] - [TestCase("2011/09/06 12:11:00 PM", "2011-09-06T12:11:00.0000000", DateTimeKind.Unspecified)] - public void DeserializeShouldPreserveDateValuesUsingIso8601Format(string input, string expectedResult, DateTimeKind expectedKind) - { - var culturesToTest = new[] { "en-AU", "en-US" }; - - foreach (var cultureName in culturesToTest) - { - // Arrange - var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters, new CultureInfo(cultureName)); - var content = string.Format("{{'Foo':'{0}'}}", input); - - // Act - var result = deserializer.Deserialize(content); - - // Assert - if (expectedResult == null) - Assert.IsNull(result.Foo); - else - { - Assert.IsNotNull(result.Foo); - Assert.AreEqual(expectedKind, result.Foo.Value.Kind); - Assert.AreEqual(expectedResult, result.Foo.Value.ToString("o", CultureInfo.InvariantCulture)); - } - } - } - - [Test] - public void DeserializeTimeZoneInfoWithDefaultJsonConverters() - { - // Arrange - var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); - const string ausEasternStandardTime = "AUS Eastern Standard Time"; - var content = string.Format("{{'Foo':'{0}'}}", ausEasternStandardTime); - - // Act - var result = deserializer.Deserialize(content); - - // Assert - Assert.IsNotNull(result.Foo); - Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById(ausEasternStandardTime).DisplayName, - result.Foo.DisplayName); - } - - [TestCase("400.09:03:02.0100000", 400, 9,3,2,10)] - [TestCase("09:03:02.0100000", 0, 9, 3, 2, 10)] - [TestCase("09:03:02.0010000", 0, 9, 3, 2, 1)] - [TestCase("09:03:11.9990000", 0, 9, 3, 2, 9999)] - [TestCase("400.09:03:02", 400, 9, 3, 2, 0)] - [TestCase("09:03:02", 0, 9, 3, 2, 0)] - public void DeserializeTimeSpanWithDefaultJsonConverters(string value, int days, int hours, int minutes, int seconds, int milliseconds) - { - // Arrange - var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); - var content = string.Format("{{'Foo':'{0}'}}", value); - - // Act - var result = deserializer.Deserialize(content); - - // Assert - Assert.IsNotNull(result.Foo); - Assert.AreEqual(new TimeSpan(days, hours, minutes, seconds, milliseconds), result.Foo); - } - - public class DateTimeOffsetModel - { - public DateTimeOffset? Foo { get; set; } - } - - public class DateTimeModel - { - public DateTime? Foo { get; set; } - } - - public class TimeZoneModel - { - public TimeZoneInfo Foo { get; set; } - } - - public class TimeSpanModel - { - public TimeSpan Foo { get; set; } - } - - [Test] - public void DeserializeShouldConvertTableCapResponseToGremlinTableCapResponse() - { - // Arrange - var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); - const string content = @"{ - ""columns"" : [ ""ColumnA"" ], - ""data"" : [ [ ""DataA"" ], [ ""DataB"" ] ] - }"; - - // Act - var result = deserializer.Deserialize(content); - var data = result.Data.SelectMany(d => d).ToArray(); - - // Assert - Assert.IsTrue(result.Columns.Any(c => c == "ColumnA")); - Assert.IsTrue(data.Any(d => d == "DataA")); - Assert.IsTrue(data.Any(d => d == "DataB")); - } - - public class EnumModel - { - [JsonProperty] - public Gender Gender { get; set; } - [JsonProperty] - public Gender? GenderNullable { get; set; } - } - - public enum Gender - { - Male, - Female, - Unknown - } - - public class EnumerableModel - { - [JsonProperty] - public IEnumerable Guids { get; set; } - } - - [Test] - public void ReadJsonCanMapNullableEnumsToEnum() - { - // Arrange - var conv = new NullableEnumValueConverter(); - var jsonReader = Substitute.For(); - jsonReader.Value.ReturnsForAnyArgs("Female"); - - // Act - var result = conv.ReadJson(jsonReader, typeof (Gender?), null, null); - var expected = (Gender?) Gender.Female; - - // Assert - Assert.AreEqual(expected, result); - } - - [Test] - [TestCase("{\"Gender\": \"Female\"}", Gender.Female)] - [TestCase("{\"Gender\": \"1\"}", Gender.Female)] - public void DeserializeEnumFromStringWithDefaultJsonConverters(string content, Gender expectedGender) - { - // Arrange - var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); - - // Act - var deserialziedGender = deserializer.Deserialize(content); - - // Assert - Assert.IsNotNull(deserialziedGender); - Assert.AreEqual(deserialziedGender.Gender, expectedGender); - } - - [Test] - [TestCase("{\"GenderNullable\": \"Female\"}", Gender.Female)] - [TestCase("{\"GenderNullable\": \"1\"}", Gender.Female)] - public void DeserializeNullableEnumFromStringWithDefaultJsonConverters(string content, Gender? expectedGender) - { - // Arrange - var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); - - // Act - var result = deserializer.Deserialize(content); - - // Assert - Assert.IsNotNull(result); - Assert.AreEqual(expectedGender, result.GenderNullable); - } - - [Test] - public void DeserializeGuidWithDefaultJsonConverters() - { - //Arrage - var myGuid = Guid.NewGuid(); - var foo = new EnumerableModel { Guids = new List { myGuid } }; - - // Act - var customSerializer = new CustomJsonSerializer{JsonConverters = GraphClient.DefaultJsonConverters}; - var testStr = customSerializer.Serialize(foo); - - var customDeserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); - var result = customDeserializer.Deserialize(testStr); - - // Assert - Assert.AreEqual(myGuid, result.Guids.First()); - } - - [Test] - [TestCase("[ \"Male\", \"Female\", \"Unknown\" ]", new [] { Gender.Male, Gender.Female, Gender.Unknown })] - public void DeserializeIEnumerableOfEnumWithDefaultJsonConverters(string content, Gender[] genders) - { - // Act - var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); - - // Assert - var result = deserializer.Deserialize>(content); - CollectionAssert.AreEquivalent(result, genders); - } - - - public class ModelWithDecimal - { - public decimal MyDecimalValue { get; set; } - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/149/deserialization-of-type-decimal-fails-when")] - public void DecimalDeserializationIsCultureIndependent() - { - //SetupFixture defaults culture info so culture-dependent tests should preserve culture state - var currentNumberDecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; - - try - { - //Arrage - CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator = ","; - const string serializedModelWithDecimal = "{'data':{'MyDecimalValue':0.5}}"; - - //Act - var customDeserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); - var result = customDeserializer.Deserialize(serializedModelWithDecimal); - - //Assert - Assert.AreEqual(0.5m, result.MyDecimalValue); - } - finally - { - CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator = currentNumberDecimalSeparator; - } - } - - public class CamelModel - { - public string FirstName { get; set; } - public Gender Gender { get; set; } - public DateTimeOffset DateOfBirth { get; set; } - public string S { get; set; } - } - - [Test] - public void CamelCaseTest() - { - //setup - var model = new CamelModel - { - FirstName = "first", - DateOfBirth = new DateTime(1980, 4, 1), - Gender = Gender.Male, - S = "short property" - }; - var serializer = new CustomJsonSerializer(); - serializer.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var st = serializer.Serialize(model); - - //act - var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters, resolver: (DefaultContractResolver)serializer.JsonContractResolver); - var output = deserializer.Deserialize(st); - - //assert - AssertCamelModel(model, output); - } - - private void AssertCamelModel(CamelModel expected, CamelModel actual) - { - Assert.AreEqual(expected.FirstName, actual.FirstName); - Assert.AreEqual(expected.DateOfBirth, actual.DateOfBirth); - Assert.AreEqual(expected.Gender, actual.Gender); - } - - - [Test] - public void CamelCaseListTest() - { - //setup - var model = new List - { - new CamelModel - { - FirstName = "first", - DateOfBirth = new DateTime(1980, 4, 1), - Gender = Gender.Male - }, - new CamelModel - { - FirstName = "second", - DateOfBirth = new DateTime(1981, 4, 1), - Gender = Gender.Female - } - }; - - var serializer = new CustomJsonSerializer(); - serializer.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - var st = serializer.Serialize(model); - - //act - var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters, resolver: (DefaultContractResolver)serializer.JsonContractResolver); - var output = deserializer.Deserialize>(st); - - //assert - AssertCamelModel(model[0], output[0]); - AssertCamelModel(model[1], output[1]); - } - } -} +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using Newtonsoft.Json.Serialization; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.ApiModels.Gremlin; +using Neo4jClient.Serialization; +using Newtonsoft.Json; + +namespace Neo4jClient.Test.Serialization +{ + [TestFixture] + public class CustomJsonDeserializerTests + { + [Test] + [TestCase("", null)] + [TestCase("rekjre", null)] + [TestCase("/Date(abcs)/", null)] + [TestCase("/Date(abcs+0000)/", null)] + [TestCase("/Date(1315271562384)/", "2011-09-06T01:12:42.3840000+00:00")] + [TestCase("/Date(1315271562384+0000)/", "2011-09-06T01:12:42.3840000+00:00")] + [TestCase("/Date(1315271562384+0200)/", "2011-09-06T03:12:42.3840000+02:00")] + [TestCase("/Date(1315271562384+1000)/", "2011-09-06T11:12:42.3840000+10:00")] + [TestCase("/Date(-2187290565386+0000)/", "1900-09-09T03:17:14.6140000+00:00")] + [TestCase("2011-09-06T01:12:42+10:00", "2011-09-06T01:12:42.0000000+10:00")] + [TestCase("2011-09-06T01:12:42+09:00", "2011-09-06T01:12:42.0000000+09:00")] + [TestCase("2011-09-06T01:12:42-07:00", "2011-09-06T01:12:42.0000000-07:00")] + [TestCase("2011-09-06T01:12:42+00:00", "2011-09-06T01:12:42.0000000+00:00")] + [TestCase("2012-08-31T10:11:00.3642578+10:00", "2012-08-31T10:11:00.3642578+10:00")] + [TestCase("2012-08-31T00:11:00.3642578+00:00", "2012-08-31T00:11:00.3642578+00:00")] + [TestCase("2011/09/06 10:11:00 +10:00", "2011-09-06T10:11:00.0000000+10:00")] + [TestCase("2011/09/06 10:11:00 AM +10:00", "2011-09-06T10:11:00.0000000+10:00")] + [TestCase("2011/09/06 12:11:00 PM +10:00", "2011-09-06T12:11:00.0000000+10:00")] + public void DeserializeShouldPreserveOffsetValuesUsingIso8601Format(string input, string expectedResult) + { + var culturesToTest = new[] {"en-AU", "en-US"}; + + foreach (var cultureName in culturesToTest) + { + // Arrange + var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters, new CultureInfo(cultureName)); + var content = string.Format("{{'Foo':'{0}'}}", input); + + // Act + var result = deserializer.Deserialize(content); + + // Assert + if (expectedResult == null) + Assert.IsNull(result.Foo); + else + { + Assert.IsNotNull(result.Foo); + Assert.AreEqual(expectedResult, result.Foo.Value.ToString("o", CultureInfo.InvariantCulture)); + } + } + } + + [Test] + [TestCase("", null, DateTimeKind.Utc)] + [TestCase("rekjre", null, DateTimeKind.Utc)] + [TestCase("/Date(abcs)/", null, DateTimeKind.Utc)] + [TestCase("/Date(1315271562384)/", "2011-09-06T01:12:42.3840000Z", DateTimeKind.Utc)] + [TestCase("/Date(-2187290565386)/", "1900-09-09T03:17:14.6140000Z", DateTimeKind.Utc)] + [TestCase("2015-07-27T22:30:35Z", "2015-07-27T22:30:35.0000000Z", DateTimeKind.Utc)] + [TestCase("2011-09-06T01:12:42", "2011-09-06T01:12:42.0000000", DateTimeKind.Unspecified)] + [TestCase("2012-08-31T10:11:00.3642578", "2012-08-31T10:11:00.3642578", DateTimeKind.Unspecified)] + [TestCase("2011/09/06 10:11:00", "2011-09-06T10:11:00.0000000", DateTimeKind.Unspecified)] + [TestCase("2011/09/06 10:11:00 AM", "2011-09-06T10:11:00.0000000", DateTimeKind.Unspecified)] + [TestCase("2011/09/06 12:11:00 PM", "2011-09-06T12:11:00.0000000", DateTimeKind.Unspecified)] + public void DeserializeShouldPreserveDateValuesUsingIso8601Format(string input, string expectedResult, DateTimeKind expectedKind) + { + var culturesToTest = new[] { "en-AU", "en-US" }; + + foreach (var cultureName in culturesToTest) + { + // Arrange + var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters, new CultureInfo(cultureName)); + var content = string.Format("{{'Foo':'{0}'}}", input); + + // Act + var result = deserializer.Deserialize(content); + + // Assert + if (expectedResult == null) + Assert.IsNull(result.Foo); + else + { + Assert.IsNotNull(result.Foo); + Assert.AreEqual(expectedKind, result.Foo.Value.Kind); + Assert.AreEqual(expectedResult, result.Foo.Value.ToString("o", CultureInfo.InvariantCulture)); + } + } + } + + [Test] + public void DeserializeTimeZoneInfoWithDefaultJsonConverters() + { + // Arrange + var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); + const string ausEasternStandardTime = "AUS Eastern Standard Time"; + var content = string.Format("{{'Foo':'{0}'}}", ausEasternStandardTime); + + // Act + var result = deserializer.Deserialize(content); + + // Assert + Assert.IsNotNull(result.Foo); + Assert.AreEqual(TimeZoneInfo.FindSystemTimeZoneById(ausEasternStandardTime).DisplayName, + result.Foo.DisplayName); + } + + [TestCase("400.09:03:02.0100000", 400, 9,3,2,10)] + [TestCase("09:03:02.0100000", 0, 9, 3, 2, 10)] + [TestCase("09:03:02.0010000", 0, 9, 3, 2, 1)] + [TestCase("09:03:11.9990000", 0, 9, 3, 2, 9999)] + [TestCase("400.09:03:02", 400, 9, 3, 2, 0)] + [TestCase("09:03:02", 0, 9, 3, 2, 0)] + public void DeserializeTimeSpanWithDefaultJsonConverters(string value, int days, int hours, int minutes, int seconds, int milliseconds) + { + // Arrange + var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); + var content = string.Format("{{'Foo':'{0}'}}", value); + + // Act + var result = deserializer.Deserialize(content); + + // Assert + Assert.IsNotNull(result.Foo); + Assert.AreEqual(new TimeSpan(days, hours, minutes, seconds, milliseconds), result.Foo); + } + + public class DateTimeOffsetModel + { + public DateTimeOffset? Foo { get; set; } + } + + public class DateTimeModel + { + public DateTime? Foo { get; set; } + } + + public class TimeZoneModel + { + public TimeZoneInfo Foo { get; set; } + } + + public class TimeSpanModel + { + public TimeSpan Foo { get; set; } + } + + [Test] + public void DeserializeShouldConvertTableCapResponseToGremlinTableCapResponse() + { + // Arrange + var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); + const string content = @"{ + ""columns"" : [ ""ColumnA"" ], + ""data"" : [ [ ""DataA"" ], [ ""DataB"" ] ] + }"; + + // Act + var result = deserializer.Deserialize(content); + var data = result.Data.SelectMany(d => d).ToArray(); + + // Assert + Assert.IsTrue(result.Columns.Any(c => c == "ColumnA")); + Assert.IsTrue(data.Any(d => d == "DataA")); + Assert.IsTrue(data.Any(d => d == "DataB")); + } + + public class EnumModel + { + [JsonProperty] + public Gender Gender { get; set; } + [JsonProperty] + public Gender? GenderNullable { get; set; } + } + + public enum Gender + { + Male, + Female, + Unknown + } + + public class EnumerableModel + { + [JsonProperty] + public IEnumerable Guids { get; set; } + } + + [Test] + public void ReadJsonCanMapNullableEnumsToEnum() + { + // Arrange + var conv = new NullableEnumValueConverter(); + var jsonReader = Substitute.For(); + jsonReader.Value.ReturnsForAnyArgs("Female"); + + // Act + var result = conv.ReadJson(jsonReader, typeof (Gender?), null, null); + var expected = (Gender?) Gender.Female; + + // Assert + Assert.AreEqual(expected, result); + } + + [Test] + [TestCase("{\"Gender\": \"Female\"}", Gender.Female)] + [TestCase("{\"Gender\": \"1\"}", Gender.Female)] + public void DeserializeEnumFromStringWithDefaultJsonConverters(string content, Gender expectedGender) + { + // Arrange + var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); + + // Act + var deserialziedGender = deserializer.Deserialize(content); + + // Assert + Assert.IsNotNull(deserialziedGender); + Assert.AreEqual(deserialziedGender.Gender, expectedGender); + } + + [Test] + [TestCase("{\"GenderNullable\": \"Female\"}", Gender.Female)] + [TestCase("{\"GenderNullable\": \"1\"}", Gender.Female)] + public void DeserializeNullableEnumFromStringWithDefaultJsonConverters(string content, Gender? expectedGender) + { + // Arrange + var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); + + // Act + var result = deserializer.Deserialize(content); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(expectedGender, result.GenderNullable); + } + + [Test] + public void DeserializeGuidWithDefaultJsonConverters() + { + //Arrage + var myGuid = Guid.NewGuid(); + var foo = new EnumerableModel { Guids = new List { myGuid } }; + + // Act + var customSerializer = new CustomJsonSerializer{JsonConverters = GraphClient.DefaultJsonConverters}; + var testStr = customSerializer.Serialize(foo); + + var customDeserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); + var result = customDeserializer.Deserialize(testStr); + + // Assert + Assert.AreEqual(myGuid, result.Guids.First()); + } + + [Test] + [TestCase("[ \"Male\", \"Female\", \"Unknown\" ]", new [] { Gender.Male, Gender.Female, Gender.Unknown })] + public void DeserializeIEnumerableOfEnumWithDefaultJsonConverters(string content, Gender[] genders) + { + // Act + var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); + + // Assert + var result = deserializer.Deserialize>(content); + CollectionAssert.AreEquivalent(result, genders); + } + + + public class ModelWithDecimal + { + public decimal MyDecimalValue { get; set; } + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/149/deserialization-of-type-decimal-fails-when")] + public void DecimalDeserializationIsCultureIndependent() + { + //SetupFixture defaults culture info so culture-dependent tests should preserve culture state + var currentNumberDecimalSeparator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator; + + try + { + //Arrage + CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator = ","; + const string serializedModelWithDecimal = "{'data':{'MyDecimalValue':0.5}}"; + + //Act + var customDeserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); + var result = customDeserializer.Deserialize(serializedModelWithDecimal); + + //Assert + Assert.AreEqual(0.5m, result.MyDecimalValue); + } + finally + { + CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator = currentNumberDecimalSeparator; + } + } + + public class CamelModel + { + public string FirstName { get; set; } + public Gender Gender { get; set; } + public DateTimeOffset DateOfBirth { get; set; } + public string S { get; set; } + } + + [Test] + public void CamelCaseTest() + { + //setup + var model = new CamelModel + { + FirstName = "first", + DateOfBirth = new DateTime(1980, 4, 1), + Gender = Gender.Male, + S = "short property" + }; + var serializer = new CustomJsonSerializer(); + serializer.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var st = serializer.Serialize(model); + + //act + var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters, resolver: (DefaultContractResolver)serializer.JsonContractResolver); + var output = deserializer.Deserialize(st); + + //assert + AssertCamelModel(model, output); + } + + private void AssertCamelModel(CamelModel expected, CamelModel actual) + { + Assert.AreEqual(expected.FirstName, actual.FirstName); + Assert.AreEqual(expected.DateOfBirth, actual.DateOfBirth); + Assert.AreEqual(expected.Gender, actual.Gender); + } + + + [Test] + public void CamelCaseListTest() + { + //setup + var model = new List + { + new CamelModel + { + FirstName = "first", + DateOfBirth = new DateTime(1980, 4, 1), + Gender = Gender.Male + }, + new CamelModel + { + FirstName = "second", + DateOfBirth = new DateTime(1981, 4, 1), + Gender = Gender.Female + } + }; + + var serializer = new CustomJsonSerializer(); + serializer.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); + var st = serializer.Serialize(model); + + //act + var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters, resolver: (DefaultContractResolver)serializer.JsonContractResolver); + var output = deserializer.Deserialize>(st); + + //assert + AssertCamelModel(model[0], output[0]); + AssertCamelModel(model[1], output[1]); + } + } +} diff --git a/Test/Serialization/CustomJsonSerializerTests.cs b/Neo4jClient.Tests/Serialization/CustomJsonSerializerTests.cs similarity index 96% rename from Test/Serialization/CustomJsonSerializerTests.cs rename to Neo4jClient.Tests/Serialization/CustomJsonSerializerTests.cs index aa7689ef2..61010997e 100644 --- a/Test/Serialization/CustomJsonSerializerTests.cs +++ b/Neo4jClient.Tests/Serialization/CustomJsonSerializerTests.cs @@ -1,271 +1,271 @@ -using System; -using System.Globalization; -using NUnit.Framework; -using Neo4jClient.Serialization; -using Newtonsoft.Json; - -namespace Neo4jClient.Test.Serialization -{ - [TestFixture] - public class CustomJsonSerializerTests - { - [Test] - public void JsonSerializerShouldSerializeTimeZoneInfo() - { - // Arrange - var serializer = new CustomJsonSerializer - { - JsonConverters = GraphClient.DefaultJsonConverters - }; - - const string ausEasternStandardTime = "AUS Eastern Standard Time"; - var timeZoneData = TimeZoneInfo.FindSystemTimeZoneById(ausEasternStandardTime); - - // Act - var result = serializer.Serialize(timeZoneData); - - // Assert - Assert.AreEqual(ausEasternStandardTime, result.Replace("\"","")); - } - - [Test] - public void SerializeTimeSpan() - { - // Arrange - var serializer = new CustomJsonSerializer { JsonConverters = GraphClient.DefaultJsonConverters }; - var value = new TimeSpan(400, 13, 3, 2,10); - var model = new TimeSpanModel - { - Foo = value - }; - - // Act - var result = serializer.Serialize(model.Foo); - - // Assert - Assert.AreEqual("400.13:03:02.0100000", result.Replace("\"", "")); - } - - [Test] - public void ShouldSerializeDateTimeOffsetInCorrectStringFormat() - { - //Arrange - var serializer = new CustomJsonSerializer { JsonConverters = GraphClient.DefaultJsonConverters }; - var model = new DateOffsetModel - { - DateTime = DateTimeOffset.Parse("2012-08-31T00:11:00.3642578+10:00"), - DateTimeNullable = DateTimeOffset.Parse("2012-08-31T00:11:00.3642578+10:00") - }; - - //Act - var actual = serializer.Serialize(model); - - //Assert - const string expected = - "{\r\n \"DateTime\": \"2012-08-31T00:11:00.3642578+10:00\",\r\n \"DateTimeNullable\": \"2012-08-31T00:11:00.3642578+10:00\"\r\n}"; - Assert.AreEqual(expected, actual); - } - - [Test] - [TestCase("2012-08-31T00:11:00.3642578")] - [TestCase("2012-08-31T00:11:00Z")] - public void ShouldSerializeDateTimeInCorrectStringFormat(string dateTimeStr) - { - //Arrange - var serializer = new CustomJsonSerializer { JsonConverters = GraphClient.DefaultJsonConverters }; - var model = new DateModel - { - DateTime = DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal), - DateTimeNullable = DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal) - }; - - //Act - var actual = serializer.Serialize(model); - - //Assert - var expected = - "{\r\n \"DateTime\": \"" + dateTimeStr + "\",\r\n \"DateTimeNullable\": \"" + dateTimeStr + "\"\r\n}"; - Assert.AreEqual(expected, actual); - } - - [Test] - public void JsonSerializerShouldSerializeAllProperties() - { - // Arrange - var testNode = new TestNode { Foo = "foo", Bar = "bar" }; - var serializer = new CustomJsonSerializer - { - NullHandling = NullValueHandling.Ignore, - JsonConverters = GraphClient.DefaultJsonConverters - }; - - // Act - var result = serializer.Serialize(testNode); - const string expectedValue = "{\r\n \"Foo\": \"foo\",\r\n \"Bar\": \"bar\"\r\n}"; - - // Assert - Assert.AreEqual(expectedValue, result); - } - - [Test] - public void JsonSerializerShouldNotSerializeNullProperties() - { - // Arrange - var testNode = new TestNode { Foo = "foo", Bar = null }; - var serializer = new CustomJsonSerializer - { - NullHandling = NullValueHandling.Ignore, - JsonConverters = GraphClient.DefaultJsonConverters - }; - - // Act - var result = serializer.Serialize(testNode); - - const string expectedValue = "{\r\n \"Foo\": \"foo\"\r\n}"; - - // Assert - Assert.AreEqual(expectedValue, result); - } - - [Test] - public void JsonSerializerShouldSerializeEnumToString() - { - // Arrange - var testNode = new TestNodeWithEnum { Status = TestEnum.Value1 }; - var serializer = new CustomJsonSerializer - { - NullHandling = NullValueHandling.Ignore, - JsonConverters = new []{new EnumValueConverter()} - }; - - // Act - var result = serializer.Serialize(testNode); - - const string expectedValue = "{\r\n \"Status\": \"Value1\"\r\n}"; - - // Assert - Assert.AreEqual(expectedValue, result); - } - - public class TestNode - { - public string Foo { get; set; } - public string Bar { get; set; } - } - - public class TestNodeWithEnum - { - public string Foo { get; set; } - public string Bar { get; set; } - public TestEnum Status { get; set; } - } - - public enum TestEnum - { - Value1, - Value2 - } - - public class TimeZoneModel - { - public TimeZoneInfo Foo { get; set; } - } - - public class TimeSpanModel - { - public TimeSpan Foo { get; set; } - } - - public class DateOffsetModel - { - public DateTimeOffset DateTime { get; set; } - public DateTimeOffset? DateTimeNullable { get; set; } - } - - public class DateModel - { - public DateTime DateTime { get; set; } - public DateTime? DateTimeNullable { get; set; } - } - - public enum Gender{Male, Female, Unknown} - - public class TestFoo - { - public Gender Gender { get; set; } - public Gender? GenderNullable { get; set; } - } - - [Test] - public void JsonSerializerWithEnumConverterShouldConvertEnumToStringValues() - { - // Arrange - var testClass = new TestFoo - { - Gender = Gender.Female, - GenderNullable = Gender.Male - }; - - var serializer = new CustomJsonSerializer - { - JsonConverters = new JsonConverter[] - { - new EnumValueConverter(), - new NullableEnumValueConverter() - } - }; - - const string expected = "{\r\n \"Gender\": \"Female\",\r\n \"GenderNullable\": \"Male\"\r\n}"; - - // Act - var result = serializer.Serialize(testClass); - - // Assert - Assert.AreEqual(expected, result); - } - - public class NodeWithBuiltInTypes - { - public int? Foo { get; set; } - public bool? Bar { get; set; } - } - - [Test] - public void ShouldSerializeNullableInt32ToJsonNumberUsingDefaultJsonConverters() - { - // Arrange - var testNode = new NodeWithBuiltInTypes { Foo = 123 }; - var serializer = new CustomJsonSerializer - { - NullHandling = NullValueHandling.Ignore, - JsonConverters = GraphClient.DefaultJsonConverters - }; - - // Act - var result = serializer.Serialize(testNode); - const string expectedValue = "{\r\n \"Foo\": 123\r\n}"; - - // Assert - Assert.AreEqual(expectedValue, result); - } - - [Test] - public void ShouldSerializeNullableBoolToJsonBooleanUsingDefaultJsonConverters() - { - // Arrange - var testNode = new NodeWithBuiltInTypes { Bar = true }; - var serializer = new CustomJsonSerializer - { - NullHandling = NullValueHandling.Ignore, - JsonConverters = GraphClient.DefaultJsonConverters - }; - - // Act - var result = serializer.Serialize(testNode); - const string expectedValue = "{\r\n \"Bar\": true\r\n}"; - - // Assert - Assert.AreEqual(expectedValue, result); - } - } -} +using System; +using System.Globalization; +using NUnit.Framework; +using Neo4jClient.Serialization; +using Newtonsoft.Json; + +namespace Neo4jClient.Test.Serialization +{ + [TestFixture] + public class CustomJsonSerializerTests + { + [Test] + public void JsonSerializerShouldSerializeTimeZoneInfo() + { + // Arrange + var serializer = new CustomJsonSerializer + { + JsonConverters = GraphClient.DefaultJsonConverters + }; + + const string ausEasternStandardTime = "AUS Eastern Standard Time"; + var timeZoneData = TimeZoneInfo.FindSystemTimeZoneById(ausEasternStandardTime); + + // Act + var result = serializer.Serialize(timeZoneData); + + // Assert + Assert.AreEqual(ausEasternStandardTime, result.Replace("\"","")); + } + + [Test] + public void SerializeTimeSpan() + { + // Arrange + var serializer = new CustomJsonSerializer { JsonConverters = GraphClient.DefaultJsonConverters }; + var value = new TimeSpan(400, 13, 3, 2,10); + var model = new TimeSpanModel + { + Foo = value + }; + + // Act + var result = serializer.Serialize(model.Foo); + + // Assert + Assert.AreEqual("400.13:03:02.0100000", result.Replace("\"", "")); + } + + [Test] + public void ShouldSerializeDateTimeOffsetInCorrectStringFormat() + { + //Arrange + var serializer = new CustomJsonSerializer { JsonConverters = GraphClient.DefaultJsonConverters }; + var model = new DateOffsetModel + { + DateTime = DateTimeOffset.Parse("2012-08-31T00:11:00.3642578+10:00"), + DateTimeNullable = DateTimeOffset.Parse("2012-08-31T00:11:00.3642578+10:00") + }; + + //Act + var actual = serializer.Serialize(model); + + //Assert + const string expected = + "{\r\n \"DateTime\": \"2012-08-31T00:11:00.3642578+10:00\",\r\n \"DateTimeNullable\": \"2012-08-31T00:11:00.3642578+10:00\"\r\n}"; + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase("2012-08-31T00:11:00.3642578")] + [TestCase("2012-08-31T00:11:00Z")] + public void ShouldSerializeDateTimeInCorrectStringFormat(string dateTimeStr) + { + //Arrange + var serializer = new CustomJsonSerializer { JsonConverters = GraphClient.DefaultJsonConverters }; + var model = new DateModel + { + DateTime = DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal), + DateTimeNullable = DateTime.Parse(dateTimeStr, CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal) + }; + + //Act + var actual = serializer.Serialize(model); + + //Assert + var expected = + "{\r\n \"DateTime\": \"" + dateTimeStr + "\",\r\n \"DateTimeNullable\": \"" + dateTimeStr + "\"\r\n}"; + Assert.AreEqual(expected, actual); + } + + [Test] + public void JsonSerializerShouldSerializeAllProperties() + { + // Arrange + var testNode = new TestNode { Foo = "foo", Bar = "bar" }; + var serializer = new CustomJsonSerializer + { + NullHandling = NullValueHandling.Ignore, + JsonConverters = GraphClient.DefaultJsonConverters + }; + + // Act + var result = serializer.Serialize(testNode); + const string expectedValue = "{\r\n \"Foo\": \"foo\",\r\n \"Bar\": \"bar\"\r\n}"; + + // Assert + Assert.AreEqual(expectedValue, result); + } + + [Test] + public void JsonSerializerShouldNotSerializeNullProperties() + { + // Arrange + var testNode = new TestNode { Foo = "foo", Bar = null }; + var serializer = new CustomJsonSerializer + { + NullHandling = NullValueHandling.Ignore, + JsonConverters = GraphClient.DefaultJsonConverters + }; + + // Act + var result = serializer.Serialize(testNode); + + const string expectedValue = "{\r\n \"Foo\": \"foo\"\r\n}"; + + // Assert + Assert.AreEqual(expectedValue, result); + } + + [Test] + public void JsonSerializerShouldSerializeEnumToString() + { + // Arrange + var testNode = new TestNodeWithEnum { Status = TestEnum.Value1 }; + var serializer = new CustomJsonSerializer + { + NullHandling = NullValueHandling.Ignore, + JsonConverters = new []{new EnumValueConverter()} + }; + + // Act + var result = serializer.Serialize(testNode); + + const string expectedValue = "{\r\n \"Status\": \"Value1\"\r\n}"; + + // Assert + Assert.AreEqual(expectedValue, result); + } + + public class TestNode + { + public string Foo { get; set; } + public string Bar { get; set; } + } + + public class TestNodeWithEnum + { + public string Foo { get; set; } + public string Bar { get; set; } + public TestEnum Status { get; set; } + } + + public enum TestEnum + { + Value1, + Value2 + } + + public class TimeZoneModel + { + public TimeZoneInfo Foo { get; set; } + } + + public class TimeSpanModel + { + public TimeSpan Foo { get; set; } + } + + public class DateOffsetModel + { + public DateTimeOffset DateTime { get; set; } + public DateTimeOffset? DateTimeNullable { get; set; } + } + + public class DateModel + { + public DateTime DateTime { get; set; } + public DateTime? DateTimeNullable { get; set; } + } + + public enum Gender{Male, Female, Unknown} + + public class TestFoo + { + public Gender Gender { get; set; } + public Gender? GenderNullable { get; set; } + } + + [Test] + public void JsonSerializerWithEnumConverterShouldConvertEnumToStringValues() + { + // Arrange + var testClass = new TestFoo + { + Gender = Gender.Female, + GenderNullable = Gender.Male + }; + + var serializer = new CustomJsonSerializer + { + JsonConverters = new JsonConverter[] + { + new EnumValueConverter(), + new NullableEnumValueConverter() + } + }; + + const string expected = "{\r\n \"Gender\": \"Female\",\r\n \"GenderNullable\": \"Male\"\r\n}"; + + // Act + var result = serializer.Serialize(testClass); + + // Assert + Assert.AreEqual(expected, result); + } + + public class NodeWithBuiltInTypes + { + public int? Foo { get; set; } + public bool? Bar { get; set; } + } + + [Test] + public void ShouldSerializeNullableInt32ToJsonNumberUsingDefaultJsonConverters() + { + // Arrange + var testNode = new NodeWithBuiltInTypes { Foo = 123 }; + var serializer = new CustomJsonSerializer + { + NullHandling = NullValueHandling.Ignore, + JsonConverters = GraphClient.DefaultJsonConverters + }; + + // Act + var result = serializer.Serialize(testNode); + const string expectedValue = "{\r\n \"Foo\": 123\r\n}"; + + // Assert + Assert.AreEqual(expectedValue, result); + } + + [Test] + public void ShouldSerializeNullableBoolToJsonBooleanUsingDefaultJsonConverters() + { + // Arrange + var testNode = new NodeWithBuiltInTypes { Bar = true }; + var serializer = new CustomJsonSerializer + { + NullHandling = NullValueHandling.Ignore, + JsonConverters = GraphClient.DefaultJsonConverters + }; + + // Act + var result = serializer.Serialize(testNode); + const string expectedValue = "{\r\n \"Bar\": true\r\n}"; + + // Assert + Assert.AreEqual(expectedValue, result); + } + } +} diff --git a/Test/Serialization/CypherJsonDeserializerTests.cs b/Neo4jClient.Tests/Serialization/CypherJsonDeserializerTests.cs similarity index 76% rename from Test/Serialization/CypherJsonDeserializerTests.cs rename to Neo4jClient.Tests/Serialization/CypherJsonDeserializerTests.cs index 75af85cff..e4cc452ed 100644 --- a/Test/Serialization/CypherJsonDeserializerTests.cs +++ b/Neo4jClient.Tests/Serialization/CypherJsonDeserializerTests.cs @@ -1,1215 +1,1473 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using NSubstitute; -using NUnit.Framework; -using Neo4jClient.Cypher; -using Neo4jClient.Serialization; -using Newtonsoft.Json; - -namespace Neo4jClient.Test.Serialization -{ - [TestFixture] - public class CypherJsonDeserializerTests - { - const string SetModeContentFormat = - @"{{ - 'columns' : [ 'a' ], - 'data' : [ [ {{ 'Foo': '{0}', 'Bar': 'Bar' }} ] ] - }}"; - - const string ProjectionModeContentFormat = - @"{{ - 'columns' : [ 'Foo', 'Bar' ], - 'data' : [ [ '{0}', 'Bar' ] ] - }}"; - - [Test] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "", null)] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "rekjre", null)] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "/Date(abcs)/", null)] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "/Date(abcs+0000)/", null)] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "/Date(1315271562384)/", "2011-09-06 01:12:42 +00:00")] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "/Date(1315271562384+0000)/", "2011-09-06 01:12:42 +00:00")] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "/Date(1315271562384+0200)/", "2011-09-06 03:12:42 +02:00")] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "/Date(1315271562384+1000)/", "2011-09-06 11:12:42 +10:00")] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "/Date(-2187290565386+0000)/", "1900-09-09 03:17:14 +00:00")] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "2011-09-06T01:12:42+10:00", "2011-09-06 01:12:42 +10:00")] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "2011-09-06T01:12:42+00:00", "2011-09-06 01:12:42 +00:00")] - [TestCase(CypherResultMode.Set, SetModeContentFormat, "2012-08-31T10:11:00.3642578+10:00", "2012-08-31 10:11:00 +10:00")] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "", null)] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "rekjre", null)] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "/Date(abcs)/", null)] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "/Date(abcs+0000)/", null)] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "/Date(1315271562384)/", "2011-09-06 01:12:42 +00:00")] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "/Date(1315271562384+0000)/", "2011-09-06 01:12:42 +00:00")] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "/Date(1315271562384+0200)/", "2011-09-06 03:12:42 +02:00")] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "/Date(1315271562384+1000)/", "2011-09-06 11:12:42 +10:00")] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "/Date(-2187290565386+0000)/", "1900-09-09 03:17:14 +00:00")] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "2011-09-06T01:12:42+10:00", "2011-09-06 01:12:42 +10:00")] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "2011-09-06T01:12:42+00:00", "2011-09-06 01:12:42 +00:00")] - [TestCase(CypherResultMode.Projection, ProjectionModeContentFormat, "2012-08-31T10:11:00.3642578+10:00", "2012-08-31 10:11:00 +10:00")] - public void DeserializeShouldPreserveOffsetValues(CypherResultMode resultMode, string contentFormat, string input, string expectedResult) - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, resultMode); - var content = string.Format(contentFormat, input); - - // Act - var result = deserializer.Deserialize(content).Single(); - - // Assert - if (expectedResult == null) - Assert.IsNull(result.Foo); - else - { - Assert.IsNotNull(result.Foo); - Assert.AreEqual(expectedResult, result.Foo.Value.ToString("yyyy-MM-dd HH:mm:ss zzz")); - Assert.AreEqual("Bar", result.Bar); - } - } - - public class DateTimeOffsetModel - { - public DateTimeOffset? Foo { get; set; } - public string Bar { get; set; } - } - - [Test] - public void DeserializeShouldMapNodesInSetMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set); - var content = @"{ - 'data' : [ [ { - 'outgoing_relationships' : 'http://localhost:7474/db/data/node/5/relationships/out', - 'data' : { - 'Name' : '東京' - }, - 'all_typed_relationships' : 'http://localhost:7474/db/data/node/5/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:7474/db/data/node/5/traverse/{returnType}', - 'self' : 'http://localhost:7474/db/data/node/5', - 'property' : 'http://localhost:7474/db/data/node/5/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/5/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:7474/db/data/node/5/properties', - 'incoming_relationships' : 'http://localhost:7474/db/data/node/5/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:7474/db/data/node/5/relationships', - 'paged_traverse' : 'http://localhost:7474/db/data/node/5/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:7474/db/data/node/5/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/5/relationships/in/{-list|&|types}' - } ], [ { - 'outgoing_relationships' : 'http://localhost:7474/db/data/node/4/relationships/out', - 'data' : { - 'Name' : 'London' - }, - 'all_typed_relationships' : 'http://localhost:7474/db/data/node/4/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:7474/db/data/node/4/traverse/{returnType}', - 'self' : 'http://localhost:7474/db/data/node/4', - 'property' : 'http://localhost:7474/db/data/node/4/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/4/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:7474/db/data/node/4/properties', - 'incoming_relationships' : 'http://localhost:7474/db/data/node/4/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:7474/db/data/node/4/relationships', - 'paged_traverse' : 'http://localhost:7474/db/data/node/4/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:7474/db/data/node/4/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/4/relationships/in/{-list|&|types}' - } ], [ { - 'outgoing_relationships' : 'http://localhost:7474/db/data/node/3/relationships/out', - 'data' : { - 'Name' : 'Sydney' - }, - 'all_typed_relationships' : 'http://localhost:7474/db/data/node/3/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:7474/db/data/node/3/traverse/{returnType}', - 'self' : 'http://localhost:7474/db/data/node/3', - 'property' : 'http://localhost:7474/db/data/node/3/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/3/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:7474/db/data/node/3/properties', - 'incoming_relationships' : 'http://localhost:7474/db/data/node/3/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:7474/db/data/node/3/relationships', - 'paged_traverse' : 'http://localhost:7474/db/data/node/3/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:7474/db/data/node/3/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/3/relationships/in/{-list|&|types}' - } ] ], - 'columns' : [ 'c' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(3, results.Count()); - - var node = results.ElementAt(0); - Assert.AreEqual(5, node.Reference.Id); - Assert.AreEqual("東京", node.Data.Name); - - node = results.ElementAt(1); - Assert.AreEqual(4, node.Reference.Id); - Assert.AreEqual("London", node.Data.Name); - - node = results.ElementAt(2); - Assert.AreEqual(3, node.Reference.Id); - Assert.AreEqual("Sydney", node.Data.Name); - } - - [Test] - public void DeserializeShouldMapRelationshipsInSetMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set); - var content = @"{ - 'data' : [ [ { - 'start' : 'http://localhost:7474/db/data/node/55872', - 'data' : { - 'Name' : '東京' - }, - 'property' : 'http://localhost:7474/db/data/relationship/76931/properties/{key}', - 'self' : 'http://localhost:7474/db/data/relationship/76931', - 'properties' : 'http://localhost:7474/db/data/relationship/76931/properties', - 'type' : 'REFERRAL_HAS_WHO_SECTION', - 'extensions' : { - }, - 'end' : 'http://localhost:7474/db/data/node/55875' - } ], [ { - 'start' : 'http://localhost:7474/db/data/node/55872', - 'data' : { - 'Name' : 'London' - }, - 'property' : 'http://localhost:7474/db/data/relationship/76931/properties/{key}', - 'self' : 'http://localhost:7474/db/data/relationship/76931', - 'properties' : 'http://localhost:7474/db/data/relationship/76931/properties', - 'type' : 'REFERRAL_HAS_WHO_SECTION', - 'extensions' : { - }, - 'end' : 'http://localhost:7474/db/data/node/55875' - } ], [ { - 'start' : 'http://localhost:7474/db/data/node/55872', - 'data' : { - 'Name' : 'Sydney' - }, - 'property' : 'http://localhost:7474/db/data/relationship/76931/properties/{key}', - 'self' : 'http://localhost:7474/db/data/relationship/76931', - 'properties' : 'http://localhost:7474/db/data/relationship/76931/properties', - 'type' : 'REFERRAL_HAS_WHO_SECTION', - 'extensions' : { - }, - 'end' : 'http://localhost:7474/db/data/node/55875' - } ] ], - 'columns' : [ 'c' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(3, results.Count()); - - var relationships = results.ElementAt(0); - Assert.AreEqual("東京", relationships.Data.Name); - - relationships = results.ElementAt(1); - Assert.AreEqual("London", relationships.Data.Name); - - relationships = results.ElementAt(2); - Assert.AreEqual("Sydney", relationships.Data.Name); - } - - [Test] - public void DeserializeShouldMapIEnumerableOfRelationshipsInAProjectionMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = @"{ - 'data' : [ [ { - 'outgoing_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out', - 'data' : { - 'Name' : '東京', - 'Population' : 13000000 - }, - 'all_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:7474/db/data/node/55745/traverse/{returnType}', - 'self' : 'http://localhost:7474/db/data/node/55745', - 'property' : 'http://localhost:7474/db/data/node/55745/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:7474/db/data/node/55745/properties', - 'incoming_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:7474/db/data/node/55745/relationships', - 'paged_traverse' : 'http://localhost:7474/db/data/node/55745/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in/{-list|&|types}' - }, [ { - 'start' : 'http://localhost:7474/db/data/node/55745', - 'data' : { - 'Number' : 66 - }, - 'property' : 'http://localhost:7474/db/data/relationship/76743/properties/{key}', - 'self' : 'http://localhost:7474/db/data/relationship/76743', - 'properties' : 'http://localhost:7474/db/data/relationship/76743/properties', - 'type' : 'REFERRAL_HAS_WHO_SECTION', - 'extensions' : { - }, - 'end' : 'http://localhost:7474/db/data/node/55747' - } ] ], [ { - 'outgoing_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out', - 'data' : { - 'Name' : '東京', - 'Population' : 13000000 - }, - 'all_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:7474/db/data/node/55745/traverse/{returnType}', - 'self' : 'http://localhost:7474/db/data/node/55745', - 'property' : 'http://localhost:7474/db/data/node/55745/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:7474/db/data/node/55745/properties', - 'incoming_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:7474/db/data/node/55745/relationships', - 'paged_traverse' : 'http://localhost:7474/db/data/node/55745/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in/{-list|&|types}' - }, [ { - 'start' : 'http://localhost:7474/db/data/node/55745', - 'data' : { - 'Number' : 66 - }, - 'property' : 'http://localhost:7474/db/data/relationship/76743/properties/{key}', - 'self' : 'http://localhost:7474/db/data/relationship/76743', - 'properties' : 'http://localhost:7474/db/data/relationship/76743/properties', - 'type' : 'REFERRAL_HAS_WHO_SECTION', - 'extensions' : { - }, - 'end' : 'http://localhost:7474/db/data/node/55747' - }, { - 'start' : 'http://localhost:7474/db/data/node/55747', - 'data' : { - 'Number' : 77 - }, - 'property' : 'http://localhost:7474/db/data/relationship/76745/properties/{key}', - 'self' : 'http://localhost:7474/db/data/relationship/76745', - 'properties' : 'http://localhost:7474/db/data/relationship/76745/properties', - 'type' : 'HAS_AUDIT', - 'extensions' : { - }, - 'end' : 'http://localhost:7474/db/data/node/55748' - } ] ], [ { - 'outgoing_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out', - 'data' : { - 'Name' : '東京', - 'Population' : 13000000 - }, - 'all_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:7474/db/data/node/55745/traverse/{returnType}', - 'self' : 'http://localhost:7474/db/data/node/55745', - 'property' : 'http://localhost:7474/db/data/node/55745/properties/{key}', - 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:7474/db/data/node/55745/properties', - 'incoming_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:7474/db/data/node/55745/relationships', - 'paged_traverse' : 'http://localhost:7474/db/data/node/55745/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all', - 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in/{-list|&|types}' - }, [ { - 'start' : 'http://localhost:7474/db/data/node/55745', - 'data' : { - 'Number' : 77 - }, - 'property' : 'http://localhost:7474/db/data/relationship/76741/properties/{key}', - 'self' : 'http://localhost:7474/db/data/relationship/76741', - 'properties' : 'http://localhost:7474/db/data/relationship/76741/properties', - 'type' : 'HAS_AUDIT', - 'extensions' : { - }, - 'end' : 'http://localhost:7474/db/data/node/55746' - } ] ] ], - 'columns' : [ 'Node', 'Relationships' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - var result = results[0]; - Assert.AreEqual("東京", result.Node.Data.Name); - Assert.AreEqual(13000000, result.Node.Data.Population); - Assert.AreEqual(66, result.Relationships.First().Data.Number); - - result = results[1]; - Assert.AreEqual("東京", result.Node.Data.Name); - Assert.AreEqual(13000000, result.Node.Data.Population); - Assert.AreEqual(66, result.Relationships.ToArray()[0].Data.Number); - Assert.AreEqual(77, result.Relationships.ToArray()[1].Data.Number); - - result = results[2]; - Assert.AreEqual("東京", result.Node.Data.Name); - Assert.AreEqual(13000000, result.Node.Data.Population); - Assert.AreEqual(77, result.Relationships.First().Data.Number); - } - - [Test] - public void DeserializeShouldMapIEnumerableOfNodesReturnedByCollectInAProjectionMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = @"{ - 'data' : [ [ [ { - 'outgoing_relationships' : 'http://foo/db/data/node/920/relationships/out', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'all_typed_relationships' : 'http://foo/db/data/node/920/relationships/all/{-list|&|types}', - 'traverse' : 'http://foo/db/data/node/920/traverse/{returnType}', - 'property' : 'http://foo/db/data/node/920/properties/{key}', - 'self' : 'http://foo/db/data/node/920', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/920/relationships/out/{-list|&|types}', - 'properties' : 'http://foo/db/data/node/920/properties', - 'incoming_relationships' : 'http://foo/db/data/node/920/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/920/relationships', - 'paged_traverse' : 'http://foo/db/data/node/920/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/920/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/920/relationships/in/{-list|&|types}' - }, { - 'outgoing_relationships' : 'http://foo/db/data/node/5482/relationships/out', - 'data' : { - 'Bar' : 'bar', - 'Baz' : 'baz' - }, - 'all_typed_relationships' : 'http://foo/db/data/node/5482/relationships/all/{-list|&|types}', - 'traverse' : 'http://foo/db/data/node/5482/traverse/{returnType}', - 'property' : 'http://foo/db/data/node/5482/properties/{key}', - 'self' : 'http://foo/db/data/node/5482', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/5482/relationships/out/{-list|&|types}', - 'properties' : 'http://foo/db/data/node/5482/properties', - 'incoming_relationships' : 'http://foo/db/data/node/5482/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/5482/relationships', - 'paged_traverse' : 'http://foo/db/data/node/5482/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/5482/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/5482/relationships/in/{-list|&|types}' - } ] ] ], - 'columns' : ['Fooness'] - }".Replace('\'', '"'); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - Assert.IsInstanceOf>(results); - Assert.AreEqual(1, results.Count()); - Assert.AreEqual(2, results[0].Fooness.Count()); - - Assert.AreEqual("bar", results[0].Fooness.ToArray()[0].Data.Bar); - Assert.AreEqual("baz", results[0].Fooness.ToArray()[0].Data.Baz); - - Assert.AreEqual("bar", results[0].Fooness.ToArray()[1].Data.Bar); - Assert.AreEqual("baz", results[0].Fooness.ToArray()[1].Data.Baz); - - } - - [Test] - public void DeserializeShouldMapIEnumerableOfNodesReturnedByCollectInColumnInProjectionMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - - //The sample query that generated this data: - /* - START me=node:node_auto_index(UserIdentifier='USER0') - MATCH me-[rels:FOLLOWS*0..1]-myfriend - WITH myfriend - MATCH myfriend-[:POSTED*]-statusupdates<-[r?:LIKE]-likers - WHERE myfriend <> statusupdates - RETURN distinct statusupdates, FILTER (x in collect(distinct likers) : x <> null), myfriend - ORDER BY statusupdates.PostTime DESC - LIMIT 25; - */ - //The data below is copied from the return from the above query. You will notice that the second column of data (Likers) is actually an array - //of items. This means the deserializer has to be able to deal with columns that are returning a list of items. - var content = @"{ - 'columns':['Post','Likers','User'], - 'data':[[ - { - 'extensions':{}, - 'paged_traverse':'http://localhost:7474/db/data/node/189/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'outgoing_relationships':'http://localhost:7474/db/data/node/189/relationships/out', - 'traverse':'http://localhost:7474/db/data/node/189/traverse/{returnType}', - 'all_typed_relationships':'http://localhost:7474/db/data/node/189/relationships/all/{-list|&|types}', - 'property':'http://localhost:7474/db/data/node/189/properties/{key}', - 'all_relationships':'http://localhost:7474/db/data/node/189/relationships/all', - 'self':'http://localhost:7474/db/data/node/189', - 'properties':'http://localhost:7474/db/data/node/189/properties', - 'outgoing_typed_relationships':'http://localhost:7474/db/data/node/189/relationships/out/{-list|&|types}', - 'incoming_relationships':'http://localhost:7474/db/data/node/189/relationships/in', - 'incoming_typed_relationships':'http://localhost:7474/db/data/node/189/relationships/in/{-list|&|types}', - 'create_relationship':'http://localhost:7474/db/data/node/189/relationships', - 'data':{ - 'PostTime':634881866575852740, - 'PostIdentifier':'USER1POST0', - 'Comment':'Here is a post statement 0 posted by user 1', - 'Content':'Blah blah blah' - } - }, - [{ - 'extensions':{}, - 'paged_traverse':'http://localhost:7474/db/data/node/188/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'outgoing_relationships':'http://localhost:7474/db/data/node/188/relationships/out', - 'traverse':'http://localhost:7474/db/data/node/188/traverse/{returnType}', - 'all_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/all/{-list|&|types}', - 'property':'http://localhost:7474/db/data/node/188/properties/{key}', - 'all_relationships':'http://localhost:7474/db/data/node/188/relationships/all', - 'self':'http://localhost:7474/db/data/node/188', - 'properties':'http://localhost:7474/db/data/node/188/properties', - 'outgoing_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/out/{-list|&|types}', - 'incoming_relationships':'http://localhost:7474/db/data/node/188/relationships/in', - 'incoming_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/in/{-list|&|types}', - 'create_relationship':'http://localhost:7474/db/data/node/188/relationships', - 'data':{ - 'UserIdentifier':'USER1', - 'Email':'someoneelse@something.net', - 'FamilyName':'Jones', - 'GivenName':'bob' - } - }, - { - 'extensions':{}, - 'paged_traverse':'http://localhost:7474/db/data/node/197/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'outgoing_relationships':'http://localhost:7474/db/data/node/197/relationships/out', - 'traverse':'http://localhost:7474/db/data/node/197/traverse/{returnType}', - 'all_typed_relationships':'http://localhost:7474/db/data/node/197/relationships/all/{-list|&|types}', - 'property':'http://localhost:7474/db/data/node/197/properties/{key}', - 'all_relationships':'http://localhost:7474/db/data/node/197/relationships/all', - 'self':'http://localhost:7474/db/data/node/197', - 'properties':'http://localhost:7474/db/data/node/197/properties', - 'outgoing_typed_relationships':'http://localhost:7474/db/data/node/197/relationships/out/{-list|&|types}', - 'incoming_relationships':'http://localhost:7474/db/data/node/197/relationships/in', - 'incoming_typed_relationships':'http://localhost:7474/db/data/node/197/relationships/in/{-list|&|types}', - 'create_relationship':'http://localhost:7474/db/data/node/197/relationships', - 'data':{ - 'UserIdentifier':'USER2', - 'Email':'someone@someotheraddress.net', - 'FamilyName':'Bob', - 'GivenName':'Jimmy' - } - }], - { - 'extensions':{}, - 'paged_traverse':'http://localhost:7474/db/data/node/188/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'outgoing_relationships':'http://localhost:7474/db/data/node/188/relationships/out', - 'traverse':'http://localhost:7474/db/data/node/188/traverse/{returnType}', - 'all_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/all/{-list|&|types}', - 'property':'http://localhost:7474/db/data/node/188/properties/{key}', - 'all_relationships':'http://localhost:7474/db/data/node/188/relationships/all', - 'self':'http://localhost:7474/db/data/node/188', - 'properties':'http://localhost:7474/db/data/node/188/properties', - 'outgoing_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/out/{-list|&|types}', - 'incoming_relationships':'http://localhost:7474/db/data/node/188/relationships/in', - 'incoming_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/in/{-list|&|types}', - 'create_relationship':'http://localhost:7474/db/data/node/188/relationships', - 'data':{ - 'UserIdentifier':'USER1', - 'Email':'someoneelse@something.net', - 'FamilyName':'Jones', - 'GivenName':'bob'} - } - ]]}".Replace('\'', '"'); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - Assert.AreEqual(1, results.Count()); - Assert.IsNotNull(results[0].Post); - Assert.IsNotNull(results[0].User); - Assert.IsNotNull(results[0].Likers); - - Assert.IsInstanceOf>(results[0].Likers); - - Assert.AreEqual(2, results[0].Likers.Count()); - Assert.AreEqual("USER1", results[0].User.UserIdentifier); - Assert.AreEqual("USER1POST0", results[0].Post.PostIdentifier); - //and make sure the likers properties have been set - Assert.AreEqual("USER1", results[0].Likers.ToArray()[0].UserIdentifier); - Assert.AreEqual("USER2", results[0].Likers.ToArray()[1].UserIdentifier); - } - - [Test] - public void DeserializeShouldMapNullIEnumerableOfNodesReturnedByCollectInInAProjectionMode() - { - - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = - @"{ - 'data' : [ [ [ null ] ] ], - 'columns' : ['Fooness'] - }" - .Replace('\'', '"'); - - var results = deserializer.Deserialize(content).ToArray(); - - Assert.IsInstanceOf>(results); - Assert.AreEqual(1, results.Count()); - - Assert.IsNull(results[0].Fooness); - } - - [Test] - public void DeserializeShouldMapIEnumerableOfStringsInAProjectionMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = @"{ - 'data' : [ [ [ 'Ben Tu', 'Romiko Derbynew' ] ] ], - 'columns' : [ 'Names' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray().First().Names.ToArray(); - - // Assert - Assert.AreEqual("Ben Tu", results[0]); - Assert.AreEqual("Romiko Derbynew", results[1]); - } - - [Test] - public void DeserializeShouldMapIEnumerableOfStringsThatAreEmptyInAProjectionMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = @"{ - 'data' : [ [ [ ] ] ], - 'columns' : [ 'Names' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(0, results.First().Names.Count()); - } - - [Test] - public void DeserializeShouldMapIEnumerableOfStringsInSetMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set); - var content = @"{ - 'data' : [ [ [ 'Ben Tu', 'Romiko Derbynew' ] ] ], - 'columns' : [ 'Names' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray().First().ToArray(); - - // Assert - Assert.AreEqual("Ben Tu", results[0]); - Assert.AreEqual("Romiko Derbynew", results[1]); - } - - [Test] - public void DeserializeShouldMapArrayOfStringsInSetMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - var content = @"{ - 'data' : [ [ [ 'Ben Tu', 'Romiko Derbynew' ] ] ], - 'columns' : [ 'Names' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray().First().ToArray(); - - // Assert - Assert.AreEqual("Ben Tu", results[0]); - Assert.AreEqual("Romiko Derbynew", results[1]); - } - - [Test] - public void DeserializeShouldMapArrayOfIntegersInSetMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - var content = @"{ - 'data' : [ [ [ '666', '777' ] ] ], - 'columns' : [ 'Names' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray().First().ToArray(); - - // Assert - Assert.AreEqual(666, results[0]); - Assert.AreEqual(777, results[1]); - } - - [Test] - public void DeserializeShouldMapIntegerInSetMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - var content = @"{ - 'data' : [ [ 666 ] ], - 'columns' : [ 'count(distinct registration)' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(666, results.First()); - - } - - [Test] - public void DeserializeShouldRespectJsonPropertyAttribute() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - var content = @"{ - 'columns' : [ 'Foo' ], - 'data' : [ [ { - 'paged_traverse' : 'http://localhost:8000/db/data/node/740/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/740/relationships/out', - 'data' : { - 'givenName' : 'Bob' - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/740/traverse/{returnType}', - 'all_relationships' : 'http://localhost:8000/db/data/node/740/relationships/all', - 'self' : 'http://localhost:8000/db/data/node/740', - 'property' : 'http://localhost:8000/db/data/node/740/properties/{key}', - 'properties' : 'http://localhost:8000/db/data/node/740/properties', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/740/relationships/in', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/in/{-list|&|types}', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/740/relationships' - } ] ] -}".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual("Bob", results.Single().Name); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/63")] - public void DeserializeShouldMapNullCollectResultsWithOtherProperties() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = @"{ - 'columns' : [ 'Fans', 'Poster' ], - 'data' : [ [ [ null ], { - 'paged_traverse' : 'http://localhost:8000/db/data/node/740/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/740/relationships/out', - 'data' : { - 'GivenName' : 'Bob' - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/740/traverse/{returnType}', - 'all_relationships' : 'http://localhost:8000/db/data/node/740/relationships/all', - 'self' : 'http://localhost:8000/db/data/node/740', - 'property' : 'http://localhost:8000/db/data/node/740/properties/{key}', - 'properties' : 'http://localhost:8000/db/data/node/740/properties', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/740/relationships/in', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/in/{-list|&|types}', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/740/relationships' - } ] ] -}".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(1, results.Count()); - Assert.AreEqual(null, results[0].Fans); - Assert.IsInstanceOf>(results[0].Poster); - Assert.AreEqual(740, results[0].Poster.Reference.Id); - Assert.AreEqual("Bob", results[0].Poster.Data.GivenName); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/67")] - public void DeserializeShouldMapNullCollectResultsWithOtherProperties_Test2() - { - // START app=node(0) MATCH app<-[?:Alternative]-alternatives RETURN app AS App, collect(alternatives) AS Alternatives; - - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = @"{ - 'columns' : [ 'Application', 'Alternatives' ], - 'data' : [ [ { - 'paged_traverse' : 'http://localhost:8000/db/data/node/123/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'outgoing_relationships' : 'http://localhost:8000/db/data/node/123/relationships/out', - 'data' : { - }, - 'all_typed_relationships' : 'http://localhost:8000/db/data/node/123/relationships/all/{-list|&|types}', - 'traverse' : 'http://localhost:8000/db/data/node/123/traverse/{returnType}', - 'all_relationships' : 'http://localhost:8000/db/data/node/123/relationships/all', - 'property' : 'http://localhost:8000/db/data/node/123/properties/{key}', - 'self' : 'http://localhost:8000/db/data/node/123', - 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/123/relationships/out/{-list|&|types}', - 'properties' : 'http://localhost:8000/db/data/node/123/properties', - 'incoming_relationships' : 'http://localhost:8000/db/data/node/123/relationships/in', - 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/123/relationships/in/{-list|&|types}', - 'extensions' : { - }, - 'create_relationship' : 'http://localhost:8000/db/data/node/123/relationships' - }, [ null ] ] ] -}".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(1, results.Count()); - Assert.IsNull(results[0].Alternatives); - Assert.IsInstanceOf>(results[0].Application); - Assert.AreEqual(123, results[0].Application.Reference.Id); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/67")] - public void DeserializeShouldMapNullCollectResultsWithOtherProperties_Test3() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = @"{'columns':['Fans'],'data':[[[null,null]]]}".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(1, results.Count()); - Assert.IsNull(results[0].Fans); - } - - [Test] - [Description("http://stackoverflow.com/questions/23764217/argumentexception-when-result-is-empty")] - public void DeserializeShouldMapNullResult() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - var content = @"{ 'columns' : [ 'db' ], 'data' : [ [ null ] ] }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(1, results.Count()); - Assert.IsNull(results[0]); - } - - [Test] - public void DeserializeShouldMapProjectionIntoAnonymousType() - { - DeserializeShouldMapProjectionIntoAnonymousType(new { Name = "", Population = 0 }); - } - -// ReSharper disable UnusedParameter.Local - static void DeserializeShouldMapProjectionIntoAnonymousType(TAnon dummy) -// ReSharper restore UnusedParameter.Local - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = @"{ - 'data' : [ - [ 'Tokyo', 13000000 ], - [ 'London', 8000000 ], - [ 'Sydney', 4000000 ] - ], - 'columns' : [ 'Name', 'Population' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(3, results.Count()); - - dynamic city = results.ElementAt(0); - Assert.AreEqual("Tokyo", city.Name); - Assert.AreEqual(13000000, city.Population); - - city = results.ElementAt(1); - Assert.AreEqual("London", city.Name); - Assert.AreEqual(8000000, city.Population); - - city = results.ElementAt(2); - Assert.AreEqual("Sydney", city.Name); - Assert.AreEqual(4000000, city.Population); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/67/cypher-deserialization-error-when-using")] - public void DeserializeShouldMapProjectionIntoAnonymousTypeWithNullCollectResult() - { - DeserializeShouldMapProjectionIntoAnonymousTypeWithNullCollectResult(new { Name = "", Friends = new object[0] }); - } - - // ReSharper disable UnusedParameter.Local - static void DeserializeShouldMapProjectionIntoAnonymousTypeWithNullCollectResult(TAnon dummy) - // ReSharper restore UnusedParameter.Local - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = @"{ - 'data' : [ - [ 'Jim', [null] ] - ], - 'columns' : [ 'Name', 'Friends' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(1, results.Count()); - - dynamic person = results.ElementAt(0); - Assert.AreEqual("Jim", person.Name); - Assert.AreEqual(null, person.Friends); - } - - public class App - { - } - - public class AppAlternatives - { - public Node Application { get; set; } - public IEnumerable> Alternatives { get; set; } - } - - public class Post - { - public String Content { get; set; } - public String Comment { get; set; } - public String PostIdentifier { get; set; } - public long PostTime { get; set; } - } - - public class User - { - public string GivenName { get; set; } - public string FamilyName { get; set; } - public string Email { get; set; } - public string UserIdentifier { get; set; } - } - - public class ProjectionFeedItem - { - public Post Post { get; set; } - public User User { get; set; } - public IEnumerable Likers { get; set; } - } - - public class FooData - { - public string Bar { get; set; } - public string Baz { get; set; } - public DateTimeOffset? Date { get; set; } - } - - public class ResultWithNestedNodeDto - { - public IEnumerable> Fooness { get; set; } - public string Name { get; set; } - public long? UniqueId { get; set; } - } - - public class City - { - public string Name { get; set; } - public int Population { get; set; } - } - - - public class People - { - public IEnumerable Names { get; set; } - } - - public class Payload - { - public int Number { get; set; } - } - - public class Projection - { - public IEnumerable> Relationships { get; set; } - public Node Node { get; set; } - } - - public class ModelWithCollect - { - public Node Poster { get; set; } - public IEnumerable> Fans { get; set; } - } - - public class UserWithJsonPropertyAttribute - { - [JsonProperty("givenName")] - public string Name { get; set; } - } - - [Test] - public void DeserializeShouldPreserveUtf8Characters() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - var content = @"{ - 'data' : [ - [ { 'Name': '東京', 'Population': 13000000 } ], - [ { 'Name': 'London', 'Population': 8000000 } ], - [ { 'Name': 'Sydney', 'Population': 4000000 } ] - ], - 'columns' : [ 'Cities' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - Assert.AreEqual(3, results.Count()); - - var city = results.ElementAt(0); - Assert.AreEqual("東京", city.Name); - Assert.AreEqual(13000000, city.Population); - - city = results.ElementAt(1); - Assert.AreEqual("London", city.Name); - Assert.AreEqual(8000000, city.Population); - - city = results.ElementAt(2); - Assert.AreEqual("Sydney", city.Name); - Assert.AreEqual(4000000, city.Population); - } - - [Test] - public void DeserializeShouldMapNodesToObjectsInSetModeWhenTheSourceLooksLikeANodeButTheDestinationDoesnt() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - var content = @" - { - 'data' : [ [ { - 'outgoing_relationships' : 'http://foo/db/data/node/879/relationships/out', - 'data' : { - 'Name' : '67', - 'UniqueId' : 2 - }, - 'traverse' : 'http://foo/db/data/node/879/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/879/relationships/all/{-list|&|types}', - 'property' : 'http://foo/db/data/node/879/properties/{key}', - 'self' : 'http://foo/db/data/node/879', - 'properties' : 'http://foo/db/data/node/879/properties', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/879/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://foo/db/data/node/879/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/879/relationships', - 'paged_traverse' : 'http://foo/db/data/node/879/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/879/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/879/relationships/in/{-list|&|types}' - } ], [ { - 'outgoing_relationships' : 'http://foo/db/data/node/878/relationships/out', - 'data' : { - 'Name' : '15 Mile', - 'UniqueId' : 1 - }, - 'traverse' : 'http://foo/db/data/node/878/traverse/{returnType}', - 'all_typed_relationships' : 'http://foo/db/data/node/878/relationships/all/{-list|&|types}', - 'property' : 'http://foo/db/data/node/878/properties/{key}', - 'self' : 'http://foo/db/data/node/878', - 'properties' : 'http://foo/db/data/node/878/properties', - 'outgoing_typed_relationships' : 'http://foo/db/data/node/878/relationships/out/{-list|&|types}', - 'incoming_relationships' : 'http://foo/db/data/node/878/relationships/in', - 'extensions' : { - }, - 'create_relationship' : 'http://foo/db/data/node/878/relationships', - 'paged_traverse' : 'http://foo/db/data/node/878/paged/traverse/{returnType}{?pageSize,leaseTime}', - 'all_relationships' : 'http://foo/db/data/node/878/relationships/all', - 'incoming_typed_relationships' : 'http://foo/db/data/node/878/relationships/in/{-list|&|types}' - } ] ], - 'columns' : [ 'asset' ] - }".Replace("'", "\""); - - // Act - var results = deserializer.Deserialize(content).ToArray(); - - // Assert - var resultsArray = results.ToArray(); - Assert.AreEqual(2, resultsArray.Count()); - - var firstResult = resultsArray[0]; - Assert.AreEqual("67", firstResult.Name); - Assert.AreEqual(2, firstResult.UniqueId); - - var secondResult = resultsArray[1]; - Assert.AreEqual("15 Mile", secondResult.Name); - Assert.AreEqual(1, secondResult.UniqueId); - } - - [Test] - public void BadJsonShouldThrowExceptionThatIncludesJson() - { - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - const string content = @"xyz-json-zyx"; - - var ex = Assert.Throws(() => - deserializer.Deserialize(content) - ); - StringAssert.Contains(content, ex.Message); - } - - [Test] - public void BadJsonShouldThrowExceptionThatIncludesFullNameOfTargetType() - { - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - - var ex = Assert.Throws(() => - deserializer.Deserialize("xyz-json-zyx") - ); - StringAssert.Contains(typeof(Asset).FullName, ex.Message); - } - - [Test] - public void ClassWithoutDefaultPublicConstructorShouldThrowExceptionThatExplainsThis() - { - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - var content = @"{ - 'data' : [ - [ { 'Name': 'Tokyo', 'Population': 13000000 } ], - [ { 'Name': 'London', 'Population': 8000000 } ], - [ { 'Name': 'Sydney', 'Population': 4000000 } ] - ], - 'columns' : [ 'Cities' ] - }".Replace("'", "\""); - - var ex = Assert.Throws(() => deserializer.Deserialize(content)); - StringAssert.StartsWith("We expected a default public constructor on ClassWithoutDefaultPublicConstructor so that we could create instances of it to deserialize data into, however this constructor does not exist or is inaccessible.", ex.Message); - } - - public class ClassWithoutDefaultPublicConstructor - { - public int A { get; set; } - - public ClassWithoutDefaultPublicConstructor(int a) - { - A = a; - } - } - - public class Asset - { - public long UniqueId { get; set; } - public string Name { get; set; } - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/162/deserialization-of-int-long-into-nullable")] - public void DeserializeInt64IntoNullableInt64() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - var content = @"{ - 'data' : [ [ 123 ] ], - 'columns' : [ 'count(distinct registration)' ] - }".Replace("'", "\""); - - // Act - var result = deserializer.Deserialize(content).First(); - - // Assert - Assert.AreEqual(123, result); - } - - public class ModelWithByteArray - { - public byte[] Array { get; set; } - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/114/byte-support")] - public void DeserializeBase64StringIntoByteArrayInProjectionResultMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); - var content = @"{ - 'data' : [ [ 'AQIDBA==' ] ], - 'columns' : [ 'Array' ] - }".Replace("'", "\""); - - // Act - var result = deserializer.Deserialize(content).First(); - - // Assert - CollectionAssert.AreEqual(new byte[] {1, 2, 3, 4}, result.Array); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/114/byte-support")] - public void DeserializeBase64StringIntoByteArrayInSetResultMode() - { - // Arrange - var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); - var content = @"{ - 'data' : [ [ 'AQIDBA==' ] ], - 'columns' : [ 'column1' ] - }".Replace("'", "\""); - - // Act - var result = deserializer.Deserialize(content).First(); - - // Assert - CollectionAssert.AreEqual(new byte[] { 1, 2, 3, 4 }, result); - } - } -} +using System; +using System.Collections.Generic; +using System.Linq; +using NSubstitute; +using NUnit.Framework; +using Neo4jClient.Cypher; +using Neo4jClient.Serialization; +using Newtonsoft.Json; + +namespace Neo4jClient.Test.Serialization +{ + [TestFixture] + public class CypherJsonDeserializerTests + { + const string SetModeContentFormat = + @"{{ + 'columns' : [ 'a' ], + 'data' : [ [ {{ 'Foo': '{0}', 'Bar': 'Bar' }} ] ] + }}"; + + const string ProjectionModeContentFormat = + @"{{ + 'columns' : [ 'Foo', 'Bar' ], + 'data' : [ [ '{0}', 'Bar' ] ] + }}"; + + [Test] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "", null)] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "rekjre", null)] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "/Date(abcs)/", null)] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "/Date(abcs+0000)/", null)] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "/Date(1315271562384)/", "2011-09-06 01:12:42 +00:00")] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "/Date(1315271562384+0000)/", "2011-09-06 01:12:42 +00:00")] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "/Date(1315271562384+0200)/", "2011-09-06 03:12:42 +02:00")] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "/Date(1315271562384+1000)/", "2011-09-06 11:12:42 +10:00")] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "/Date(-2187290565386+0000)/", "1900-09-09 03:17:14 +00:00")] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "2011-09-06T01:12:42+10:00", "2011-09-06 01:12:42 +10:00")] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "2011-09-06T01:12:42+00:00", "2011-09-06 01:12:42 +00:00")] + [TestCase(CypherResultMode.Set, CypherResultFormat.Rest, SetModeContentFormat, "2012-08-31T10:11:00.3642578+10:00", "2012-08-31 10:11:00 +10:00")] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "", null)] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "rekjre", null)] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "/Date(abcs)/", null)] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "/Date(abcs+0000)/", null)] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "/Date(1315271562384)/", "2011-09-06 01:12:42 +00:00")] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "/Date(1315271562384+0000)/", "2011-09-06 01:12:42 +00:00")] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "/Date(1315271562384+0200)/", "2011-09-06 03:12:42 +02:00")] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "/Date(1315271562384+1000)/", "2011-09-06 11:12:42 +10:00")] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "/Date(-2187290565386+0000)/", "1900-09-09 03:17:14 +00:00")] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "2011-09-06T01:12:42+10:00", "2011-09-06 01:12:42 +10:00")] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "2011-09-06T01:12:42+00:00", "2011-09-06 01:12:42 +00:00")] + [TestCase(CypherResultMode.Projection, CypherResultFormat.Rest, ProjectionModeContentFormat, "2012-08-31T10:11:00.3642578+10:00", "2012-08-31 10:11:00 +10:00")] + public void DeserializeShouldPreserveOffsetValues(CypherResultMode resultMode, CypherResultFormat format, string contentFormat, string input, string expectedResult) + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, resultMode, format); + var content = string.Format(contentFormat, input); + + // Act + var result = deserializer.Deserialize(content).Single(); + + // Assert + if (expectedResult == null) + Assert.IsNull(result.Foo); + else + { + Assert.IsNotNull(result.Foo); + Assert.AreEqual(expectedResult, result.Foo.Value.ToString("yyyy-MM-dd HH:mm:ss zzz")); + Assert.AreEqual("Bar", result.Bar); + } + } + + public class DateTimeOffsetModel + { + public DateTimeOffset? Foo { get; set; } + public string Bar { get; set; } + } + + [Test] + public void DeserializeShouldMapNodesInSetMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set, + CypherResultFormat.Rest); + var content = @"{ + 'data' : [ [ { + 'outgoing_relationships' : 'http://localhost:7474/db/data/node/5/relationships/out', + 'data' : { + 'Name' : '東京' + }, + 'all_typed_relationships' : 'http://localhost:7474/db/data/node/5/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:7474/db/data/node/5/traverse/{returnType}', + 'self' : 'http://localhost:7474/db/data/node/5', + 'property' : 'http://localhost:7474/db/data/node/5/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/5/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:7474/db/data/node/5/properties', + 'incoming_relationships' : 'http://localhost:7474/db/data/node/5/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:7474/db/data/node/5/relationships', + 'paged_traverse' : 'http://localhost:7474/db/data/node/5/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:7474/db/data/node/5/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/5/relationships/in/{-list|&|types}' + } ], [ { + 'outgoing_relationships' : 'http://localhost:7474/db/data/node/4/relationships/out', + 'data' : { + 'Name' : 'London' + }, + 'all_typed_relationships' : 'http://localhost:7474/db/data/node/4/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:7474/db/data/node/4/traverse/{returnType}', + 'self' : 'http://localhost:7474/db/data/node/4', + 'property' : 'http://localhost:7474/db/data/node/4/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/4/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:7474/db/data/node/4/properties', + 'incoming_relationships' : 'http://localhost:7474/db/data/node/4/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:7474/db/data/node/4/relationships', + 'paged_traverse' : 'http://localhost:7474/db/data/node/4/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:7474/db/data/node/4/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/4/relationships/in/{-list|&|types}' + } ], [ { + 'outgoing_relationships' : 'http://localhost:7474/db/data/node/3/relationships/out', + 'data' : { + 'Name' : 'Sydney' + }, + 'all_typed_relationships' : 'http://localhost:7474/db/data/node/3/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:7474/db/data/node/3/traverse/{returnType}', + 'self' : 'http://localhost:7474/db/data/node/3', + 'property' : 'http://localhost:7474/db/data/node/3/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/3/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:7474/db/data/node/3/properties', + 'incoming_relationships' : 'http://localhost:7474/db/data/node/3/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:7474/db/data/node/3/relationships', + 'paged_traverse' : 'http://localhost:7474/db/data/node/3/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:7474/db/data/node/3/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/3/relationships/in/{-list|&|types}' + } ] ], + 'columns' : [ 'c' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(3, results.Count()); + + var node = results.ElementAt(0); + Assert.AreEqual(5, node.Reference.Id); + Assert.AreEqual("東京", node.Data.Name); + + node = results.ElementAt(1); + Assert.AreEqual(4, node.Reference.Id); + Assert.AreEqual("London", node.Data.Name); + + node = results.ElementAt(2); + Assert.AreEqual(3, node.Reference.Id); + Assert.AreEqual("Sydney", node.Data.Name); + } + + [Test] + public void DeserializeShouldMapRelationshipsInSetMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set, CypherResultFormat.Rest); + var content = @"{ + 'data' : [ [ { + 'start' : 'http://localhost:7474/db/data/node/55872', + 'data' : { + 'Name' : '東京' + }, + 'property' : 'http://localhost:7474/db/data/relationship/76931/properties/{key}', + 'self' : 'http://localhost:7474/db/data/relationship/76931', + 'properties' : 'http://localhost:7474/db/data/relationship/76931/properties', + 'type' : 'REFERRAL_HAS_WHO_SECTION', + 'extensions' : { + }, + 'end' : 'http://localhost:7474/db/data/node/55875' + } ], [ { + 'start' : 'http://localhost:7474/db/data/node/55872', + 'data' : { + 'Name' : 'London' + }, + 'property' : 'http://localhost:7474/db/data/relationship/76931/properties/{key}', + 'self' : 'http://localhost:7474/db/data/relationship/76931', + 'properties' : 'http://localhost:7474/db/data/relationship/76931/properties', + 'type' : 'REFERRAL_HAS_WHO_SECTION', + 'extensions' : { + }, + 'end' : 'http://localhost:7474/db/data/node/55875' + } ], [ { + 'start' : 'http://localhost:7474/db/data/node/55872', + 'data' : { + 'Name' : 'Sydney' + }, + 'property' : 'http://localhost:7474/db/data/relationship/76931/properties/{key}', + 'self' : 'http://localhost:7474/db/data/relationship/76931', + 'properties' : 'http://localhost:7474/db/data/relationship/76931/properties', + 'type' : 'REFERRAL_HAS_WHO_SECTION', + 'extensions' : { + }, + 'end' : 'http://localhost:7474/db/data/node/55875' + } ] ], + 'columns' : [ 'c' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(3, results.Count()); + + var relationships = results.ElementAt(0); + Assert.AreEqual("東京", relationships.Data.Name); + + relationships = results.ElementAt(1); + Assert.AreEqual("London", relationships.Data.Name); + + relationships = results.ElementAt(2); + Assert.AreEqual("Sydney", relationships.Data.Name); + } + + [Test] + public void DeserializeShouldMapIEnumerableOfRelationshipsInAProjectionMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.Rest); + var content = @"{ + 'data' : [ [ { + 'outgoing_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out', + 'data' : { + 'Name' : '東京', + 'Population' : 13000000 + }, + 'all_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:7474/db/data/node/55745/traverse/{returnType}', + 'self' : 'http://localhost:7474/db/data/node/55745', + 'property' : 'http://localhost:7474/db/data/node/55745/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:7474/db/data/node/55745/properties', + 'incoming_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:7474/db/data/node/55745/relationships', + 'paged_traverse' : 'http://localhost:7474/db/data/node/55745/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in/{-list|&|types}' + }, [ { + 'start' : 'http://localhost:7474/db/data/node/55745', + 'data' : { + 'Number' : 66 + }, + 'property' : 'http://localhost:7474/db/data/relationship/76743/properties/{key}', + 'self' : 'http://localhost:7474/db/data/relationship/76743', + 'properties' : 'http://localhost:7474/db/data/relationship/76743/properties', + 'type' : 'REFERRAL_HAS_WHO_SECTION', + 'extensions' : { + }, + 'end' : 'http://localhost:7474/db/data/node/55747' + } ] ], [ { + 'outgoing_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out', + 'data' : { + 'Name' : '東京', + 'Population' : 13000000 + }, + 'all_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:7474/db/data/node/55745/traverse/{returnType}', + 'self' : 'http://localhost:7474/db/data/node/55745', + 'property' : 'http://localhost:7474/db/data/node/55745/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:7474/db/data/node/55745/properties', + 'incoming_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:7474/db/data/node/55745/relationships', + 'paged_traverse' : 'http://localhost:7474/db/data/node/55745/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in/{-list|&|types}' + }, [ { + 'start' : 'http://localhost:7474/db/data/node/55745', + 'data' : { + 'Number' : 66 + }, + 'property' : 'http://localhost:7474/db/data/relationship/76743/properties/{key}', + 'self' : 'http://localhost:7474/db/data/relationship/76743', + 'properties' : 'http://localhost:7474/db/data/relationship/76743/properties', + 'type' : 'REFERRAL_HAS_WHO_SECTION', + 'extensions' : { + }, + 'end' : 'http://localhost:7474/db/data/node/55747' + }, { + 'start' : 'http://localhost:7474/db/data/node/55747', + 'data' : { + 'Number' : 77 + }, + 'property' : 'http://localhost:7474/db/data/relationship/76745/properties/{key}', + 'self' : 'http://localhost:7474/db/data/relationship/76745', + 'properties' : 'http://localhost:7474/db/data/relationship/76745/properties', + 'type' : 'HAS_AUDIT', + 'extensions' : { + }, + 'end' : 'http://localhost:7474/db/data/node/55748' + } ] ], [ { + 'outgoing_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out', + 'data' : { + 'Name' : '東京', + 'Population' : 13000000 + }, + 'all_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:7474/db/data/node/55745/traverse/{returnType}', + 'self' : 'http://localhost:7474/db/data/node/55745', + 'property' : 'http://localhost:7474/db/data/node/55745/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:7474/db/data/node/55745/properties', + 'incoming_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:7474/db/data/node/55745/relationships', + 'paged_traverse' : 'http://localhost:7474/db/data/node/55745/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/in/{-list|&|types}' + }, [ { + 'start' : 'http://localhost:7474/db/data/node/55745', + 'data' : { + 'Number' : 77 + }, + 'property' : 'http://localhost:7474/db/data/relationship/76741/properties/{key}', + 'self' : 'http://localhost:7474/db/data/relationship/76741', + 'properties' : 'http://localhost:7474/db/data/relationship/76741/properties', + 'type' : 'HAS_AUDIT', + 'extensions' : { + }, + 'end' : 'http://localhost:7474/db/data/node/55746' + } ] ] ], + 'columns' : [ 'Node', 'Relationships' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + var result = results[0]; + Assert.AreEqual("東京", result.Node.Data.Name); + Assert.AreEqual(13000000, result.Node.Data.Population); + Assert.AreEqual(66, result.Relationships.First().Data.Number); + + result = results[1]; + Assert.AreEqual("東京", result.Node.Data.Name); + Assert.AreEqual(13000000, result.Node.Data.Population); + Assert.AreEqual(66, result.Relationships.ToArray()[0].Data.Number); + Assert.AreEqual(77, result.Relationships.ToArray()[1].Data.Number); + + result = results[2]; + Assert.AreEqual("東京", result.Node.Data.Name); + Assert.AreEqual(13000000, result.Node.Data.Population); + Assert.AreEqual(77, result.Relationships.First().Data.Number); + } + + [Test] + public void DeserializeShouldMapIEnumerableOfNodesReturnedByCollectInAProjectionMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.Rest); + var content = @"{ + 'data' : [ [ [ { + 'outgoing_relationships' : 'http://foo/db/data/node/920/relationships/out', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'all_typed_relationships' : 'http://foo/db/data/node/920/relationships/all/{-list|&|types}', + 'traverse' : 'http://foo/db/data/node/920/traverse/{returnType}', + 'property' : 'http://foo/db/data/node/920/properties/{key}', + 'self' : 'http://foo/db/data/node/920', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/920/relationships/out/{-list|&|types}', + 'properties' : 'http://foo/db/data/node/920/properties', + 'incoming_relationships' : 'http://foo/db/data/node/920/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/920/relationships', + 'paged_traverse' : 'http://foo/db/data/node/920/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/920/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/920/relationships/in/{-list|&|types}' + }, { + 'outgoing_relationships' : 'http://foo/db/data/node/5482/relationships/out', + 'data' : { + 'Bar' : 'bar', + 'Baz' : 'baz' + }, + 'all_typed_relationships' : 'http://foo/db/data/node/5482/relationships/all/{-list|&|types}', + 'traverse' : 'http://foo/db/data/node/5482/traverse/{returnType}', + 'property' : 'http://foo/db/data/node/5482/properties/{key}', + 'self' : 'http://foo/db/data/node/5482', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/5482/relationships/out/{-list|&|types}', + 'properties' : 'http://foo/db/data/node/5482/properties', + 'incoming_relationships' : 'http://foo/db/data/node/5482/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/5482/relationships', + 'paged_traverse' : 'http://foo/db/data/node/5482/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/5482/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/5482/relationships/in/{-list|&|types}' + } ] ] ], + 'columns' : ['Fooness'] + }".Replace('\'', '"'); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + Assert.IsInstanceOf>(results); + Assert.AreEqual(1, results.Count()); + Assert.AreEqual(2, results[0].Fooness.Count()); + + Assert.AreEqual("bar", results[0].Fooness.ToArray()[0].Data.Bar); + Assert.AreEqual("baz", results[0].Fooness.ToArray()[0].Data.Baz); + + Assert.AreEqual("bar", results[0].Fooness.ToArray()[1].Data.Bar); + Assert.AreEqual("baz", results[0].Fooness.ToArray()[1].Data.Baz); + + } + + [Test] + public void DeserializeShouldMapIEnumerableOfNodesReturnedByCollectInColumnInProjectionMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); + + //The sample query that generated this data: + /* + START me=node:node_auto_index(UserIdentifier='USER0') + MATCH me-[rels:FOLLOWS*0..1]-myfriend + WITH myfriend + MATCH myfriend-[:POSTED*]-statusupdates<-[r?:LIKE]-likers + WHERE myfriend <> statusupdates + RETURN distinct statusupdates, FILTER (x in collect(distinct likers) : x <> null), myfriend + ORDER BY statusupdates.PostTime DESC + LIMIT 25; + */ + //The data below is copied from the return from the above query. You will notice that the second column of data (Likers) is actually an array + //of items. This means the deserializer has to be able to deal with columns that are returning a list of items. + var content = @"{ + 'columns':['Post','Likers','User'], + 'data':[[ + { + 'extensions':{}, + 'paged_traverse':'http://localhost:7474/db/data/node/189/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'outgoing_relationships':'http://localhost:7474/db/data/node/189/relationships/out', + 'traverse':'http://localhost:7474/db/data/node/189/traverse/{returnType}', + 'all_typed_relationships':'http://localhost:7474/db/data/node/189/relationships/all/{-list|&|types}', + 'property':'http://localhost:7474/db/data/node/189/properties/{key}', + 'all_relationships':'http://localhost:7474/db/data/node/189/relationships/all', + 'self':'http://localhost:7474/db/data/node/189', + 'properties':'http://localhost:7474/db/data/node/189/properties', + 'outgoing_typed_relationships':'http://localhost:7474/db/data/node/189/relationships/out/{-list|&|types}', + 'incoming_relationships':'http://localhost:7474/db/data/node/189/relationships/in', + 'incoming_typed_relationships':'http://localhost:7474/db/data/node/189/relationships/in/{-list|&|types}', + 'create_relationship':'http://localhost:7474/db/data/node/189/relationships', + 'data':{ + 'PostTime':634881866575852740, + 'PostIdentifier':'USER1POST0', + 'Comment':'Here is a post statement 0 posted by user 1', + 'Content':'Blah blah blah' + } + }, + [{ + 'extensions':{}, + 'paged_traverse':'http://localhost:7474/db/data/node/188/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'outgoing_relationships':'http://localhost:7474/db/data/node/188/relationships/out', + 'traverse':'http://localhost:7474/db/data/node/188/traverse/{returnType}', + 'all_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/all/{-list|&|types}', + 'property':'http://localhost:7474/db/data/node/188/properties/{key}', + 'all_relationships':'http://localhost:7474/db/data/node/188/relationships/all', + 'self':'http://localhost:7474/db/data/node/188', + 'properties':'http://localhost:7474/db/data/node/188/properties', + 'outgoing_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/out/{-list|&|types}', + 'incoming_relationships':'http://localhost:7474/db/data/node/188/relationships/in', + 'incoming_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/in/{-list|&|types}', + 'create_relationship':'http://localhost:7474/db/data/node/188/relationships', + 'data':{ + 'UserIdentifier':'USER1', + 'Email':'someoneelse@something.net', + 'FamilyName':'Jones', + 'GivenName':'bob' + } + }, + { + 'extensions':{}, + 'paged_traverse':'http://localhost:7474/db/data/node/197/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'outgoing_relationships':'http://localhost:7474/db/data/node/197/relationships/out', + 'traverse':'http://localhost:7474/db/data/node/197/traverse/{returnType}', + 'all_typed_relationships':'http://localhost:7474/db/data/node/197/relationships/all/{-list|&|types}', + 'property':'http://localhost:7474/db/data/node/197/properties/{key}', + 'all_relationships':'http://localhost:7474/db/data/node/197/relationships/all', + 'self':'http://localhost:7474/db/data/node/197', + 'properties':'http://localhost:7474/db/data/node/197/properties', + 'outgoing_typed_relationships':'http://localhost:7474/db/data/node/197/relationships/out/{-list|&|types}', + 'incoming_relationships':'http://localhost:7474/db/data/node/197/relationships/in', + 'incoming_typed_relationships':'http://localhost:7474/db/data/node/197/relationships/in/{-list|&|types}', + 'create_relationship':'http://localhost:7474/db/data/node/197/relationships', + 'data':{ + 'UserIdentifier':'USER2', + 'Email':'someone@someotheraddress.net', + 'FamilyName':'Bob', + 'GivenName':'Jimmy' + } + }], + { + 'extensions':{}, + 'paged_traverse':'http://localhost:7474/db/data/node/188/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'outgoing_relationships':'http://localhost:7474/db/data/node/188/relationships/out', + 'traverse':'http://localhost:7474/db/data/node/188/traverse/{returnType}', + 'all_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/all/{-list|&|types}', + 'property':'http://localhost:7474/db/data/node/188/properties/{key}', + 'all_relationships':'http://localhost:7474/db/data/node/188/relationships/all', + 'self':'http://localhost:7474/db/data/node/188', + 'properties':'http://localhost:7474/db/data/node/188/properties', + 'outgoing_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/out/{-list|&|types}', + 'incoming_relationships':'http://localhost:7474/db/data/node/188/relationships/in', + 'incoming_typed_relationships':'http://localhost:7474/db/data/node/188/relationships/in/{-list|&|types}', + 'create_relationship':'http://localhost:7474/db/data/node/188/relationships', + 'data':{ + 'UserIdentifier':'USER1', + 'Email':'someoneelse@something.net', + 'FamilyName':'Jones', + 'GivenName':'bob'} + } + ]]}".Replace('\'', '"'); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + Assert.AreEqual(1, results.Count()); + Assert.IsNotNull(results[0].Post); + Assert.IsNotNull(results[0].User); + Assert.IsNotNull(results[0].Likers); + + Assert.IsInstanceOf>(results[0].Likers); + + Assert.AreEqual(2, results[0].Likers.Count()); + Assert.AreEqual("USER1", results[0].User.UserIdentifier); + Assert.AreEqual("USER1POST0", results[0].Post.PostIdentifier); + //and make sure the likers properties have been set + Assert.AreEqual("USER1", results[0].Likers.ToArray()[0].UserIdentifier); + Assert.AreEqual("USER2", results[0].Likers.ToArray()[1].UserIdentifier); + } + + [Test] + public void DeserializeShouldMapNullIEnumerableOfNodesReturnedByCollectInInAProjectionMode() + { + + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); + var content = + @"{ + 'data' : [ [ [ null ] ] ], + 'columns' : ['Fooness'] + }" + .Replace('\'', '"'); + + var results = deserializer.Deserialize(content).ToArray(); + + Assert.IsInstanceOf>(results); + Assert.AreEqual(1, results.Count()); + + Assert.IsNull(results[0].Fooness); + } + + [Test] + public void DeserializeShouldMapIEnumerableOfStringsInAProjectionMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ [ [ 'Ben Tu', 'Romiko Derbynew' ] ] ], + 'columns' : [ 'Names' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray().First().Names.ToArray(); + + // Assert + Assert.AreEqual("Ben Tu", results[0]); + Assert.AreEqual("Romiko Derbynew", results[1]); + } + + [Test] + public void DeserializeShouldMapIEnumerableOfStringsThatAreEmptyInAProjectionMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ [ [ ] ] ], + 'columns' : [ 'Names' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(0, results.First().Names.Count()); + } + + [Test] + public void DeserializeShouldMapIEnumerableOfStringsInSetMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ [ [ 'Ben Tu', 'Romiko Derbynew' ] ] ], + 'columns' : [ 'Names' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray().First().ToArray(); + + // Assert + Assert.AreEqual("Ben Tu", results[0]); + Assert.AreEqual("Romiko Derbynew", results[1]); + } + + [Test] + public void DeserializeShouldMapArrayOfStringsInSetMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ [ [ 'Ben Tu', 'Romiko Derbynew' ] ] ], + 'columns' : [ 'Names' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray().First().ToArray(); + + // Assert + Assert.AreEqual("Ben Tu", results[0]); + Assert.AreEqual("Romiko Derbynew", results[1]); + } + + [Test] + public void DeserializeShouldMapArrayOfIntegersInSetMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ [ [ '666', '777' ] ] ], + 'columns' : [ 'Names' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray().First().ToArray(); + + // Assert + Assert.AreEqual(666, results[0]); + Assert.AreEqual(777, results[1]); + } + + [Test] + public void DeserializeShouldMapIntegerInSetMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ [ 666 ] ], + 'columns' : [ 'count(distinct registration)' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(666, results.First()); + + } + + [Test] + public void DeserializeShouldRespectJsonPropertyAttribute() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'columns' : [ 'Foo' ], + 'data' : [ [ { + 'paged_traverse' : 'http://localhost:8000/db/data/node/740/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/740/relationships/out', + 'data' : { + 'givenName' : 'Bob' + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/740/traverse/{returnType}', + 'all_relationships' : 'http://localhost:8000/db/data/node/740/relationships/all', + 'self' : 'http://localhost:8000/db/data/node/740', + 'property' : 'http://localhost:8000/db/data/node/740/properties/{key}', + 'properties' : 'http://localhost:8000/db/data/node/740/properties', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/740/relationships/in', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/in/{-list|&|types}', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/740/relationships' + } ] ] +}".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual("Bob", results.Single().Name); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/63")] + public void DeserializeShouldMapNullCollectResultsWithOtherProperties() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'columns' : [ 'Fans', 'Poster' ], + 'data' : [ [ [ null ], { + 'paged_traverse' : 'http://localhost:8000/db/data/node/740/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/740/relationships/out', + 'data' : { + 'GivenName' : 'Bob' + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/740/traverse/{returnType}', + 'all_relationships' : 'http://localhost:8000/db/data/node/740/relationships/all', + 'self' : 'http://localhost:8000/db/data/node/740', + 'property' : 'http://localhost:8000/db/data/node/740/properties/{key}', + 'properties' : 'http://localhost:8000/db/data/node/740/properties', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/740/relationships/in', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/740/relationships/in/{-list|&|types}', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/740/relationships' + } ] ] +}".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(1, results.Count()); + Assert.AreEqual(null, results[0].Fans); + Assert.IsInstanceOf>(results[0].Poster); + Assert.AreEqual(740, results[0].Poster.Reference.Id); + Assert.AreEqual("Bob", results[0].Poster.Data.GivenName); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/67")] + public void DeserializeShouldMapNullCollectResultsWithOtherProperties_Test2() + { + // START app=node(0) MATCH app<-[?:Alternative]-alternatives RETURN app AS App, collect(alternatives) AS Alternatives; + + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'columns' : [ 'Application', 'Alternatives' ], + 'data' : [ [ { + 'paged_traverse' : 'http://localhost:8000/db/data/node/123/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'outgoing_relationships' : 'http://localhost:8000/db/data/node/123/relationships/out', + 'data' : { + }, + 'all_typed_relationships' : 'http://localhost:8000/db/data/node/123/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:8000/db/data/node/123/traverse/{returnType}', + 'all_relationships' : 'http://localhost:8000/db/data/node/123/relationships/all', + 'property' : 'http://localhost:8000/db/data/node/123/properties/{key}', + 'self' : 'http://localhost:8000/db/data/node/123', + 'outgoing_typed_relationships' : 'http://localhost:8000/db/data/node/123/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:8000/db/data/node/123/properties', + 'incoming_relationships' : 'http://localhost:8000/db/data/node/123/relationships/in', + 'incoming_typed_relationships' : 'http://localhost:8000/db/data/node/123/relationships/in/{-list|&|types}', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:8000/db/data/node/123/relationships' + }, [ null ] ] ] +}".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(1, results.Count()); + Assert.IsNull(results[0].Alternatives); + Assert.IsInstanceOf>(results[0].Application); + Assert.AreEqual(123, results[0].Application.Reference.Id); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/67")] + public void DeserializeShouldMapNullCollectResultsWithOtherProperties_Test3() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); + var content = @"{'columns':['Fans'],'data':[[[null,null]]]}".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(1, results.Count()); + Assert.IsNull(results[0].Fans); + } + + [Test] + [Description("http://stackoverflow.com/questions/23764217/argumentexception-when-result-is-empty")] + public void DeserializeShouldMapNullResult() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @"{ 'columns' : [ 'db' ], 'data' : [ [ null ] ] }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(1, results.Count()); + Assert.IsNull(results[0]); + } + + [Test] + public void DeserializeShouldMapProjectionIntoAnonymousType() + { + DeserializeShouldMapProjectionIntoAnonymousType(new { Name = "", Population = 0 }); + } + +// ReSharper disable UnusedParameter.Local + static void DeserializeShouldMapProjectionIntoAnonymousType(TAnon dummy) +// ReSharper restore UnusedParameter.Local + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ + [ 'Tokyo', 13000000 ], + [ 'London', 8000000 ], + [ 'Sydney', 4000000 ] + ], + 'columns' : [ 'Name', 'Population' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(3, results.Count()); + + dynamic city = results.ElementAt(0); + Assert.AreEqual("Tokyo", city.Name); + Assert.AreEqual(13000000, city.Population); + + city = results.ElementAt(1); + Assert.AreEqual("London", city.Name); + Assert.AreEqual(8000000, city.Population); + + city = results.ElementAt(2); + Assert.AreEqual("Sydney", city.Name); + Assert.AreEqual(4000000, city.Population); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/67/cypher-deserialization-error-when-using")] + public void DeserializeShouldMapProjectionIntoAnonymousTypeWithNullCollectResult() + { + DeserializeShouldMapProjectionIntoAnonymousTypeWithNullCollectResult(new { Name = "", Friends = new object[0] }); + } + + // ReSharper disable UnusedParameter.Local + static void DeserializeShouldMapProjectionIntoAnonymousTypeWithNullCollectResult(TAnon dummy) + // ReSharper restore UnusedParameter.Local + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ + [ 'Jim', [null] ] + ], + 'columns' : [ 'Name', 'Friends' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(1, results.Count()); + + dynamic person = results.ElementAt(0); + Assert.AreEqual("Jim", person.Name); + Assert.AreEqual(null, person.Friends); + } + + public class App + { + } + + public class AppAlternatives + { + public Node Application { get; set; } + public IEnumerable> Alternatives { get; set; } + } + + public class Post + { + public String Content { get; set; } + public String Comment { get; set; } + public String PostIdentifier { get; set; } + public long PostTime { get; set; } + } + + public class User + { + public string GivenName { get; set; } + public string FamilyName { get; set; } + public string Email { get; set; } + public string UserIdentifier { get; set; } + } + + public class ProjectionFeedItem + { + public Post Post { get; set; } + public User User { get; set; } + public IEnumerable Likers { get; set; } + } + + public class FooData + { + public string Bar { get; set; } + public string Baz { get; set; } + public DateTimeOffset? Date { get; set; } + } + + public class ResultWithNestedNodeDto + { + public IEnumerable> Fooness { get; set; } + public string Name { get; set; } + public long? UniqueId { get; set; } + } + + public class City + { + public string Name { get; set; } + public int Population { get; set; } + } + + + public class People + { + public IEnumerable Names { get; set; } + } + + public class Payload + { + public int Number { get; set; } + } + + public class Projection + { + public IEnumerable> Relationships { get; set; } + public Node Node { get; set; } + } + + public class ModelWithCollect + { + public Node Poster { get; set; } + public IEnumerable> Fans { get; set; } + } + + public class UserWithJsonPropertyAttribute + { + [JsonProperty("givenName")] + public string Name { get; set; } + } + + private class CityAndLabel + { + public City City { get; set; } + public IEnumerable Labels { get; set; } + } + + private class State + { + public string Name { get; set; } + } + + private class StateCityAndLabel + { + public State State { get; set; } + public IEnumerable Labels { get; set; } + public IEnumerable Cities { get; set; } + } + + private class StateCityAndLabelWithNode + { + public State State { get; set; } + public IEnumerable Labels { get; set; } + public IEnumerable> Cities { get; set; } + } + + [Test] + public void DeserializeNestedObjectsInTransactionReturningNode() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.Rest, true); + var content = @"{'results':[ + { + 'columns':[ + 'State','Labels','Cities' + ], + 'data':[ + { + 'rest':[ + {'Name':'Baja California'}, + ['State'], + [ + { + 'outgoing_relationships' : 'http://localhost:7474/db/data/node/5/relationships/out', + 'data' : { + 'Name' : 'Tijuana', + 'Population': 1300000 + }, + 'all_typed_relationships' : 'http://localhost:7474/db/data/node/5/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:7474/db/data/node/5/traverse/{returnType}', + 'self' : 'http://localhost:7474/db/data/node/5', + 'property' : 'http://localhost:7474/db/data/node/5/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/5/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:7474/db/data/node/5/properties', + 'incoming_relationships' : 'http://localhost:7474/db/data/node/5/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:7474/db/data/node/5/relationships', + 'paged_traverse' : 'http://localhost:7474/db/data/node/5/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:7474/db/data/node/5/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/5/relationships/in/{-list|&|types}' + } , { + 'outgoing_relationships' : 'http://localhost:7474/db/data/node/4/relationships/out', + 'data' : { + 'Name' : 'Mexicali', + 'Population': 500000 + }, + 'all_typed_relationships' : 'http://localhost:7474/db/data/node/4/relationships/all/{-list|&|types}', + 'traverse' : 'http://localhost:7474/db/data/node/4/traverse/{returnType}', + 'self' : 'http://localhost:7474/db/data/node/4', + 'property' : 'http://localhost:7474/db/data/node/4/properties/{key}', + 'outgoing_typed_relationships' : 'http://localhost:7474/db/data/node/4/relationships/out/{-list|&|types}', + 'properties' : 'http://localhost:7474/db/data/node/4/properties', + 'incoming_relationships' : 'http://localhost:7474/db/data/node/4/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://localhost:7474/db/data/node/4/relationships', + 'paged_traverse' : 'http://localhost:7474/db/data/node/4/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://localhost:7474/db/data/node/4/relationships/all', + 'incoming_typed_relationships' : 'http://localhost:7474/db/data/node/4/relationships/in/{-list|&|types}' + } + ] + ] + } + ] + } + ]}"; + var results = deserializer.Deserialize(content).ToArray(); + Assert.AreEqual(1, results.Length); + var result = results[0]; + Assert.AreEqual("Baja California", result.State.Name); + Assert.AreEqual("State", result.Labels.First()); + + var cities = result.Cities.ToArray(); + Assert.AreEqual(2, cities.Length); + + var city = cities[0]; + Assert.AreEqual("Tijuana", city.Data.Name); + Assert.AreEqual(1300000, city.Data.Population); + + city = cities[1]; + Assert.AreEqual("Mexicali", city.Data.Name); + Assert.AreEqual(500000, city.Data.Population); + } + + [Test] + public void DeserializeNestedObjectsInTransaction() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment, true); + var content = @"{'results':[ + { + 'columns':[ + 'State','Labels','Cities' + ], + 'data':[ + { + 'row':[ + {'Name':'Baja California'}, + ['State'], + [ + {'Name':'Tijuana', 'Population': 1300000}, + {'Name':'Mexicali', 'Population': 500000} + ] + ] + } + ] + } + ]}"; + var results = deserializer.Deserialize(content).ToArray(); + Assert.AreEqual(1, results.Length); + var result = results[0]; + Assert.AreEqual("Baja California", result.State.Name); + Assert.AreEqual("State", result.Labels.First()); + + var cities = result.Cities.ToArray(); + Assert.AreEqual(2, cities.Length); + + var city = cities[0]; + Assert.AreEqual("Tijuana", city.Name); + Assert.AreEqual(1300000, city.Population); + + city = cities[1]; + Assert.AreEqual("Mexicali", city.Name); + Assert.AreEqual(500000, city.Population); + } + + + [Test] + public void DeserializerTwoLevelProjectionInTransaction() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment, true); + var content = @"{'results':[ + { + 'columns':[ + 'City', 'Labels' + ], + 'data':[ + { + 'row': [{ 'Name': 'Sydney', 'Population': 4000000}, ['City1']] + }, + { + 'row': [{ 'Name': 'Tijuana', 'Population': 1300000}, ['City2']] + } + ] + } + ]}"; + var results = deserializer.Deserialize(content).ToArray(); + Assert.AreEqual(2, results.Length); + var city = results[0]; + Assert.AreEqual("Sydney", city.City.Name); + Assert.AreEqual(4000000, city.City.Population); + Assert.AreEqual("City1", city.Labels.First()); + city = results[1]; + Assert.AreEqual("Tijuana", city.City.Name); + Assert.AreEqual(1300000, city.City.Population); + Assert.AreEqual("City2", city.Labels.First()); + } + + [Test] + public void DeserializerProjectionInTransaction() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment, true); + var content = @"{'results':[ + { + 'columns':[ + 'Name', 'Population' + ], + 'data':[ + { + 'row': ['Sydney', 4000000] + }, + { + 'row': ['Tijuana', 1300000] + } + ] + } + ]}"; + var results = deserializer.Deserialize(content).ToArray(); + Assert.AreEqual(2, results.Length); + var city = results[0]; + Assert.AreEqual("Sydney", city.Name); + Assert.AreEqual(4000000, city.Population); + city = results[1]; + Assert.AreEqual("Tijuana", city.Name); + Assert.AreEqual(1300000, city.Population); + } + + [Test] + public void DeserializeSimpleSetInTransaction() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment, true); + var content = @"{'results':[ + { + 'columns':[ + 'count(n)' + ], + 'data':[ + { + 'row': [3] + } + ] + } + ]}"; + var results = deserializer.Deserialize(content).ToArray(); + Assert.AreEqual(1, results.Length); + Assert.AreEqual(3, results[0]); + } + + [Test] + public void DeserializeResultsSetInTransaction() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment, true); + var content = @"{'results':[ + { + 'columns':[ + 'c' + ], + 'data':[ + { + 'row': [{ + 'Name': 'Sydney', 'Population': 4000000 + }] + } + ] + } + ]}"; + var results = deserializer.Deserialize(content).ToArray(); + Assert.AreEqual(1, results.Length); + var city = results[0]; + Assert.AreEqual("Sydney", city.Name); + Assert.AreEqual(4000000, city.Population); + } + + [Test] + public void DeserializeShouldPreserveUtf8Characters() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ + [ { 'Name': '東京', 'Population': 13000000 } ], + [ { 'Name': 'London', 'Population': 8000000 } ], + [ { 'Name': 'Sydney', 'Population': 4000000 } ] + ], + 'columns' : [ 'Cities' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + Assert.AreEqual(3, results.Count()); + + var city = results.ElementAt(0); + Assert.AreEqual("東京", city.Name); + Assert.AreEqual(13000000, city.Population); + + city = results.ElementAt(1); + Assert.AreEqual("London", city.Name); + Assert.AreEqual(8000000, city.Population); + + city = results.ElementAt(2); + Assert.AreEqual("Sydney", city.Name); + Assert.AreEqual(4000000, city.Population); + } + + [Test] + public void DeserializeShouldMapNodesToObjectsInSetModeWhenTheSourceLooksLikeANodeButTheDestinationDoesnt() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @" + { + 'data' : [ [ { + 'outgoing_relationships' : 'http://foo/db/data/node/879/relationships/out', + 'data' : { + 'Name' : '67', + 'UniqueId' : 2 + }, + 'traverse' : 'http://foo/db/data/node/879/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/879/relationships/all/{-list|&|types}', + 'property' : 'http://foo/db/data/node/879/properties/{key}', + 'self' : 'http://foo/db/data/node/879', + 'properties' : 'http://foo/db/data/node/879/properties', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/879/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://foo/db/data/node/879/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/879/relationships', + 'paged_traverse' : 'http://foo/db/data/node/879/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/879/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/879/relationships/in/{-list|&|types}' + } ], [ { + 'outgoing_relationships' : 'http://foo/db/data/node/878/relationships/out', + 'data' : { + 'Name' : '15 Mile', + 'UniqueId' : 1 + }, + 'traverse' : 'http://foo/db/data/node/878/traverse/{returnType}', + 'all_typed_relationships' : 'http://foo/db/data/node/878/relationships/all/{-list|&|types}', + 'property' : 'http://foo/db/data/node/878/properties/{key}', + 'self' : 'http://foo/db/data/node/878', + 'properties' : 'http://foo/db/data/node/878/properties', + 'outgoing_typed_relationships' : 'http://foo/db/data/node/878/relationships/out/{-list|&|types}', + 'incoming_relationships' : 'http://foo/db/data/node/878/relationships/in', + 'extensions' : { + }, + 'create_relationship' : 'http://foo/db/data/node/878/relationships', + 'paged_traverse' : 'http://foo/db/data/node/878/paged/traverse/{returnType}{?pageSize,leaseTime}', + 'all_relationships' : 'http://foo/db/data/node/878/relationships/all', + 'incoming_typed_relationships' : 'http://foo/db/data/node/878/relationships/in/{-list|&|types}' + } ] ], + 'columns' : [ 'asset' ] + }".Replace("'", "\""); + + // Act + var results = deserializer.Deserialize(content).ToArray(); + + // Assert + var resultsArray = results.ToArray(); + Assert.AreEqual(2, resultsArray.Count()); + + var firstResult = resultsArray[0]; + Assert.AreEqual("67", firstResult.Name); + Assert.AreEqual(2, firstResult.UniqueId); + + var secondResult = resultsArray[1]; + Assert.AreEqual("15 Mile", secondResult.Name); + Assert.AreEqual(1, secondResult.UniqueId); + } + + [Test] + public void BadJsonShouldThrowExceptionThatIncludesJson() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + const string content = @"xyz-json-zyx"; + + var ex = Assert.Throws(() => + deserializer.Deserialize(content) + ); + StringAssert.Contains(content, ex.Message); + } + + [Test] + public void BadJsonShouldThrowExceptionThatIncludesFullNameOfTargetType() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + + var ex = Assert.Throws(() => + deserializer.Deserialize("xyz-json-zyx") + ); + StringAssert.Contains(typeof(Asset).FullName, ex.Message); + } + + [Test] + public void ClassWithoutDefaultPublicConstructorShouldThrowExceptionThatExplainsThis() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ + [ { 'Name': 'Tokyo', 'Population': 13000000 } ], + [ { 'Name': 'London', 'Population': 8000000 } ], + [ { 'Name': 'Sydney', 'Population': 4000000 } ] + ], + 'columns' : [ 'Cities' ] + }".Replace("'", "\""); + + var ex = Assert.Throws(() => deserializer.Deserialize(content)); + StringAssert.StartsWith("We expected a default public constructor on ClassWithoutDefaultPublicConstructor so that we could create instances of it to deserialize data into, however this constructor does not exist or is inaccessible.", ex.Message); + } + + public class ClassWithoutDefaultPublicConstructor + { + public int A { get; set; } + + public ClassWithoutDefaultPublicConstructor(int a) + { + A = a; + } + } + + public class Asset + { + public long UniqueId { get; set; } + public string Name { get; set; } + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/162/deserialization-of-int-long-into-nullable")] + public void DeserializeInt64IntoNullableInt64() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ [ 123 ] ], + 'columns' : [ 'count(distinct registration)' ] + }".Replace("'", "\""); + + // Act + var result = deserializer.Deserialize(content).First(); + + // Assert + Assert.AreEqual(123, result); + } + + public class ModelWithByteArray + { + public byte[] Array { get; set; } + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/114/byte-support")] + public void DeserializeBase64StringIntoByteArrayInProjectionResultMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ [ 'AQIDBA==' ] ], + 'columns' : [ 'Array' ] + }".Replace("'", "\""); + + // Act + var result = deserializer.Deserialize(content).First(); + + // Assert + CollectionAssert.AreEqual(new byte[] {1, 2, 3, 4}, result.Array); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/114/byte-support")] + public void DeserializeBase64StringIntoByteArrayInSetResultMode() + { + // Arrange + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); + var content = @"{ + 'data' : [ [ 'AQIDBA==' ] ], + 'columns' : [ 'column1' ] + }".Replace("'", "\""); + + // Act + var result = deserializer.Deserialize(content).First(); + + // Assert + CollectionAssert.AreEqual(new byte[] { 1, 2, 3, 4 }, result); + } + } +} diff --git a/Test/Serialization/UserSuppliedSerializationTests.cs b/Neo4jClient.Tests/Serialization/UserSuppliedSerializationTests.cs similarity index 97% rename from Test/Serialization/UserSuppliedSerializationTests.cs rename to Neo4jClient.Tests/Serialization/UserSuppliedSerializationTests.cs index 123dec8ab..6ee9bb571 100644 --- a/Test/Serialization/UserSuppliedSerializationTests.cs +++ b/Neo4jClient.Tests/Serialization/UserSuppliedSerializationTests.cs @@ -1,232 +1,232 @@ -using System; -using System.Globalization; -using NUnit.Framework; -using Neo4jClient.Serialization; -using Newtonsoft.Json; - -namespace Neo4jClient.Test.Serialization -{ - [TestFixture] - public class UserSuppliedSerializationTests - { - public class TestValueA - { - public char A { get; set; } - public char B { get; set; } - } - - public class TestModelA - { - public TestValueA CustomValue { get; set; } - } - - public class TestValueAConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - var typedValue = (TestValueA)value; - writer.WriteValue(typedValue.A + typedValue.B.ToString(CultureInfo.InvariantCulture)); - } - - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - var rawValue = reader.Value.ToString(); - return new TestValueA - { - A = rawValue[0], - B = rawValue[1] - }; - } - - public override bool CanConvert(Type objectType) - { - return typeof(TestValueA) == objectType; - } - } - - [System.ComponentModel.TypeConverter(typeof(TestValueBTypeConverter))] - public class TestValueB - { - public char A { get; set; } - public char B { get; set; } - } - - public class TestModelB - { - public TestValueB CustomValue { get; set; } - } - - public class TestModelC - { - public System.Drawing.Point MyPoint { get; set; } - } - - public class TestValueBTypeConverter : System.ComponentModel.TypeConverter - { - public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) - { - return sourceType == typeof (string); - } - - public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType) - { - return destinationType == typeof (string); - } - - public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, CultureInfo culture, object value) - { - if (value == null) return null; - var valueAsString = value.ToString(); - return new TestValueB {A = valueAsString[0], B = valueAsString[1]}; - } - - public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) - { - var typedValue = (TestValueB)value; - return typedValue.A + typedValue.B.ToString(CultureInfo.InvariantCulture); - } - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] - public void ShouldSerializeCustomValueWithCustomJsonConverter() - { - //Arrange - var serializer = new CustomJsonSerializer - { - JsonConverters = new []{new TestValueAConverter()} - }; - - var model = new TestModelA - { - CustomValue = new TestValueA - { - A = 'o', - B = 'p' - } - }; - - //Act - var rawResult = serializer.Serialize(model); - - //Assert - const string expectedRawOutput = - "{\r\n \"CustomValue\": \"op\"\r\n}"; - - Assert.AreEqual(expectedRawOutput, rawResult); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] - public void ShouldDeserializeCustomValueWithCustomJsonConverter() - { - //Arrange - const string rawInput = - "{\r\n \"CustomValue\": \"op\"\r\n}"; - - var serializer = new CustomJsonDeserializer(new []{new TestValueAConverter()}); - - //Act - var model = serializer.Deserialize(rawInput); - - //Assert - Assert.NotNull(model, "Deserialization failed."); - Assert.NotNull(model.CustomValue, "Model.CustomValue is unexpectedly null."); - Assert.AreEqual('o', model.CustomValue.A); - Assert.AreEqual('p', model.CustomValue.B); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] - public void ShouldSerializeCustomTypeThatHasTypeConverterUsingTypeConverterBasedJsonConverter() - { - //Arrange - var serializer = new CustomJsonSerializer - { - JsonConverters = new[] { new TypeConverterBasedJsonConverter() } - }; - - var model = new TestModelB - { - CustomValue = new TestValueB - { - A = 'o', - B = 'p' - } - }; - - //Act - var rawResult = serializer.Serialize(model); - - //Assert - const string expectedRawOutput = - "{\r\n \"CustomValue\": \"op\"\r\n}"; - - Assert.AreEqual(expectedRawOutput, rawResult); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] - public void ShouldDeserializeCustomTypeThatHasTypeConverterUsingTypeConverterBasedJsonConverter() - { - //Arrange - const string rawInput = - "{\r\n \"CustomValue\": \"op\"\r\n}"; - - var serializer = new CustomJsonDeserializer(new JsonConverter[]{new TypeConverterBasedJsonConverter()}); - - //Act - var model = serializer.Deserialize(rawInput); - - //Assert - Assert.NotNull(model, "Deserialization failed."); - Assert.NotNull(model.CustomValue, "Model.CustomValue is unexpectedly null."); - Assert.AreEqual('o', model.CustomValue.A); - Assert.AreEqual('p', model.CustomValue.B); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] - public void ShouldSerializeBuiltInTypeThatHasTypeConverterUsingTypeConverterBasedJsonConverter() - { - //Arrange - var serializer = new CustomJsonSerializer - { - JsonConverters = new[] { new TypeConverterBasedJsonConverter() } - }; - - var model = new TestModelC - { - MyPoint = new System.Drawing.Point(100, 200) - }; - - //Act - var rawResult = serializer.Serialize(model); - - //Assert - const string expectedRawOutput = - "{\r\n \"MyPoint\": \"100, 200\"\r\n}"; - - Assert.AreEqual(expectedRawOutput, rawResult); - } - - [Test] - [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] - public void ShouldDeserializeBuiltInTypeThatHasTypeConverterUsingTypeConverterBasedJsonConverter() - { - //Arrange - const string rawInput = - "{\r\n \"MyPoint\": \"100, 200\"\r\n}"; - - var serializer = new CustomJsonDeserializer(new JsonConverter[] { new TypeConverterBasedJsonConverter() }); - - //Act - var model = serializer.Deserialize(rawInput); - - //Assert - Assert.NotNull(model, "Deserialization failed."); - Assert.NotNull(model.MyPoint, "Model.MyPoint is unexpectedly null."); - Assert.AreEqual(new System.Drawing.Point(100, 200), model.MyPoint); - } - } -} +using System; +using System.Globalization; +using NUnit.Framework; +using Neo4jClient.Serialization; +using Newtonsoft.Json; + +namespace Neo4jClient.Test.Serialization +{ + [TestFixture] + public class UserSuppliedSerializationTests + { + public class TestValueA + { + public char A { get; set; } + public char B { get; set; } + } + + public class TestModelA + { + public TestValueA CustomValue { get; set; } + } + + public class TestValueAConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + var typedValue = (TestValueA)value; + writer.WriteValue(typedValue.A + typedValue.B.ToString(CultureInfo.InvariantCulture)); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + var rawValue = reader.Value.ToString(); + return new TestValueA + { + A = rawValue[0], + B = rawValue[1] + }; + } + + public override bool CanConvert(Type objectType) + { + return typeof(TestValueA) == objectType; + } + } + + [System.ComponentModel.TypeConverter(typeof(TestValueBTypeConverter))] + public class TestValueB + { + public char A { get; set; } + public char B { get; set; } + } + + public class TestModelB + { + public TestValueB CustomValue { get; set; } + } + + public class TestModelC + { + public System.Drawing.Point MyPoint { get; set; } + } + + public class TestValueBTypeConverter : System.ComponentModel.TypeConverter + { + public override bool CanConvertFrom(System.ComponentModel.ITypeDescriptorContext context, Type sourceType) + { + return sourceType == typeof (string); + } + + public override bool CanConvertTo(System.ComponentModel.ITypeDescriptorContext context, Type destinationType) + { + return destinationType == typeof (string); + } + + public override object ConvertFrom(System.ComponentModel.ITypeDescriptorContext context, CultureInfo culture, object value) + { + if (value == null) return null; + var valueAsString = value.ToString(); + return new TestValueB {A = valueAsString[0], B = valueAsString[1]}; + } + + public override object ConvertTo(System.ComponentModel.ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) + { + var typedValue = (TestValueB)value; + return typedValue.A + typedValue.B.ToString(CultureInfo.InvariantCulture); + } + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] + public void ShouldSerializeCustomValueWithCustomJsonConverter() + { + //Arrange + var serializer = new CustomJsonSerializer + { + JsonConverters = new []{new TestValueAConverter()} + }; + + var model = new TestModelA + { + CustomValue = new TestValueA + { + A = 'o', + B = 'p' + } + }; + + //Act + var rawResult = serializer.Serialize(model); + + //Assert + const string expectedRawOutput = + "{\r\n \"CustomValue\": \"op\"\r\n}"; + + Assert.AreEqual(expectedRawOutput, rawResult); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] + public void ShouldDeserializeCustomValueWithCustomJsonConverter() + { + //Arrange + const string rawInput = + "{\r\n \"CustomValue\": \"op\"\r\n}"; + + var serializer = new CustomJsonDeserializer(new []{new TestValueAConverter()}); + + //Act + var model = serializer.Deserialize(rawInput); + + //Assert + Assert.NotNull(model, "Deserialization failed."); + Assert.NotNull(model.CustomValue, "Model.CustomValue is unexpectedly null."); + Assert.AreEqual('o', model.CustomValue.A); + Assert.AreEqual('p', model.CustomValue.B); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] + public void ShouldSerializeCustomTypeThatHasTypeConverterUsingTypeConverterBasedJsonConverter() + { + //Arrange + var serializer = new CustomJsonSerializer + { + JsonConverters = new[] { new TypeConverterBasedJsonConverter() } + }; + + var model = new TestModelB + { + CustomValue = new TestValueB + { + A = 'o', + B = 'p' + } + }; + + //Act + var rawResult = serializer.Serialize(model); + + //Assert + const string expectedRawOutput = + "{\r\n \"CustomValue\": \"op\"\r\n}"; + + Assert.AreEqual(expectedRawOutput, rawResult); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] + public void ShouldDeserializeCustomTypeThatHasTypeConverterUsingTypeConverterBasedJsonConverter() + { + //Arrange + const string rawInput = + "{\r\n \"CustomValue\": \"op\"\r\n}"; + + var serializer = new CustomJsonDeserializer(new JsonConverter[]{new TypeConverterBasedJsonConverter()}); + + //Act + var model = serializer.Deserialize(rawInput); + + //Assert + Assert.NotNull(model, "Deserialization failed."); + Assert.NotNull(model.CustomValue, "Model.CustomValue is unexpectedly null."); + Assert.AreEqual('o', model.CustomValue.A); + Assert.AreEqual('p', model.CustomValue.B); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] + public void ShouldSerializeBuiltInTypeThatHasTypeConverterUsingTypeConverterBasedJsonConverter() + { + //Arrange + var serializer = new CustomJsonSerializer + { + JsonConverters = new[] { new TypeConverterBasedJsonConverter() } + }; + + var model = new TestModelC + { + MyPoint = new System.Drawing.Point(100, 200) + }; + + //Act + var rawResult = serializer.Serialize(model); + + //Assert + const string expectedRawOutput = + "{\r\n \"MyPoint\": \"100, 200\"\r\n}"; + + Assert.AreEqual(expectedRawOutput, rawResult); + } + + [Test] + [Description("https://bitbucket.org/Readify/neo4jclient/issue/89")] + public void ShouldDeserializeBuiltInTypeThatHasTypeConverterUsingTypeConverterBasedJsonConverter() + { + //Arrange + const string rawInput = + "{\r\n \"MyPoint\": \"100, 200\"\r\n}"; + + var serializer = new CustomJsonDeserializer(new JsonConverter[] { new TypeConverterBasedJsonConverter() }); + + //Act + var model = serializer.Deserialize(rawInput); + + //Assert + Assert.NotNull(model, "Deserialization failed."); + Assert.NotNull(model.MyPoint, "Model.MyPoint is unexpectedly null."); + Assert.AreEqual(new System.Drawing.Point(100, 200), model.MyPoint); + } + } +} diff --git a/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs b/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs new file mode 100644 index 000000000..b23d876e5 --- /dev/null +++ b/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs @@ -0,0 +1,18 @@ +using System; +using System.Linq; +using System.Net.Http; +using System.Reflection; +using System.Threading.Tasks; +using Neo4jClient.Execution; +using Neo4jClient.Transactions; +using Newtonsoft.Json; +using NUnit.Framework; + +namespace Neo4jClient.Test.Transactions +{ + [TestFixture] + public class Neo4jTransactionResourceManagerTests + { + + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests/Transactions/QueriesInTransactionTests.cs b/Neo4jClient.Tests/Transactions/QueriesInTransactionTests.cs new file mode 100644 index 000000000..d97f41731 --- /dev/null +++ b/Neo4jClient.Tests/Transactions/QueriesInTransactionTests.cs @@ -0,0 +1,955 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; +using Neo4jClient.Transactions; +using NUnit.Framework; +using TransactionScopeOption = System.Transactions.TransactionScopeOption; + +namespace Neo4jClient.Test.Transactions +{ + [TestFixture] + public class QueriesInTransactionTests + { + private static string ResetTransactionTimer() + { + return new DateTime().AddSeconds(60).ToString("ddd, dd, MMM yyyy HH:mm:ss +0000"); + } + + private string GenerateInitTransactionResponse(int id, string results) + { + return string.Format( + @"{{'commit': 'http://foo/db/data/transaction/{0}/commit', 'results': [{1}], 'errors': [], 'transaction': {{ 'expires': '{2}' }} }}", + id, + results, + ResetTransactionTimer() + ); + } + + private string GenerateInitTransactionResponse(int id) + { + return GenerateInitTransactionResponse(id, string.Empty); + } + + [Test] + public void CommitWithoutRequestsShouldNotGenerateMessage() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{'statements': []}"); + var commitTransactionRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + commitTransactionRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }.ShouldNotBeCalled(initTransactionRequest, commitTransactionRequest)) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + // no requests + transaction.Commit(); + } + } + } + + [Test] + public void KeepAliveWithoutRequestsShouldNotGenerateMessage() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{'statements': []}"); + var keepAliveRequest = MockRequest.PostJson("/transaction/1", @"{'statements': []}"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + keepAliveRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }.ShouldNotBeCalled(initTransactionRequest, keepAliveRequest)) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + // no requests + transaction.KeepAlive(); + } + } + } + + [Test] + public void RollbackWithoutRequestsShouldNotGenerateMessage() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{'statements': []}"); + var rollbackTransactionRequest = MockRequest.Delete("/transaction/1"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + rollbackTransactionRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }.ShouldNotBeCalled(initTransactionRequest, rollbackTransactionRequest)) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + // no requests + transaction.Rollback(); + } + } + } + + [Test] + public void UpdateTransactionEndpointAfterFirstRequest() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var rollbackTransactionRequest = MockRequest.Delete("/transaction/1"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + rollbackTransactionRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + + Assert.AreEqual( + new Uri("http://foo/db/data/transaction/1"), + ((INeo4jTransaction)((TransactionScopeProxy) transaction).TransactionContext).Endpoint); + } + } + } + + [Test] + public void ExecuteMultipleStatementInOneRequest() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}, {'statement': 'MATCH t\r\nRETURN count(t)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + var rawClient = client as IRawGraphClient; + if (rawClient == null) + { + Assert.Fail("ITransactionalGraphClient is not IRawGraphClient"); + } + + var queries = new List() + { + client.Cypher + .Match("n") + .Return(n => n.Count()) + .Query, + client.Cypher + .Match("t") + .Return(t => t.Count()) + .Query + }; + + rawClient.ExecuteMultipleCypherQueriesInTransaction(queries); + transaction.Commit(); + } + } + } + + [Test] + public void TransactionCommit() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + + transaction.Commit(); + } + } + } + + [Test] + public void PromoteDurableInAmbientTransaction() + { + // when two durables are registered they get promoted + + // this request will be made by the ForceKeepAlive() call when PSPE registration fails for the second client + var afterPspeFailRequest = MockRequest.PostJson("/transaction", @"{'statements': []}"); + // this request will be mode in Promote() after second durable enlistment + var promoteRequest = MockRequest.PostJson("/transaction/1", @"{'statements': []}"); + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + + var secondClientRequest = MockRequest.PostJson("/transaction/2", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + + // there are no delete requests because those will be made in another app domain + + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + secondClientRequest, + MockResponse.Json(200, @"{'results':[], 'errors':[] }") + }, + { + afterPspeFailRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(2), "http://foo/db/data/transaction/2") + }, + { + promoteRequest, + MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + using (var scope = new TransactionScope()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + + var client2 = testHarness.CreateAndConnectTransactionalGraphClient(); + + client2.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + } + + // we sleep so that the app domain for the resource manager gets cleaned up + Thread.Sleep(500); + } + } + + [Test] + public void SuppressTransactionScopeShouldNotEmitTransactionalQuery() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + + var nonTransactionalRequest = MockRequest.PostJson("/cypher", @"{'query': 'MATCH n\r\nRETURN count(n)', 'params': {}}"); + + var commitTransactionRequest = MockRequest.PostJson("/transaction/1/commit", @"{ + 'statements': []}"); + var deleteRequest = MockRequest.Delete("/transaction/1"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + commitTransactionRequest, + MockResponse.Json(200, @"{'results':[], 'errors':[] }") + }, + { + nonTransactionalRequest, + MockResponse.Json(200, @"{'columns':['count(n)'], 'data':[[0]] }") + }, + { + deleteRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }.ShouldNotBeCalled(deleteRequest)) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var tran = new TransactionScope()) + { + using (var tran2 = new TransactionScope(TransactionScopeOption.Suppress)) + { + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + + // no rollback should be generated + } + + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + + tran.Complete(); + } + } + } + + [Test] + public void NestedRequiresNewTransactionScope() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson("/transaction/1/commit", @"{ + 'statements': []}"); + var deleteRequest = MockRequest.Delete("/transaction/1"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + commitTransactionRequest, + MockResponse.Json(200, @"{'results':[], 'errors':[] }") + }, + { + deleteRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var scope = new TransactionScope()) + { + using (var scope2 = new TransactionScope(TransactionScopeOption.RequiresNew)) + { + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + + // this should commit + scope2.Complete(); + } + + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + // this should rollback + } + } + } + + [Test] + public void NestedJoinedTransactionScope() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var secondRequest = MockRequest.PostJson("/transaction/1", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var deleteRequest = MockRequest.Delete("/transaction/1"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + secondRequest, + MockResponse.Json(200, @"{'results':[], 'errors':[] }") + }, + { + deleteRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var scope = new TransactionScope()) + { + using (var scope2 = new TransactionScope()) + { + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + + // this will not commit + scope2.Complete(); + } + + // this should generate a request to the known transaction ID + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + } + } + } + + [Test] + public void TransactionRollbackInTransactionScope() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var deleteRequest = MockRequest.Delete("/transaction/1"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + deleteRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var scope = new TransactionScope()) + { + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + } + } + } + + [Test] + public void NestedTransactionWithTransactionScopeQueryFirst() + { + const string queryTextMsTransaction = @"MATCH (n) RETURN count(n)"; + const string queryTextTx = @"MATCH (t) RETURN count(t)"; + const string resultColumn = @"{'columns':['count(n)'], 'data':[{'row':[1]}]}"; + var cypherQueryMsTx = new CypherQuery(queryTextMsTransaction, new Dictionary(), CypherResultMode.Projection); + var cypherQueryMsTxStatement = new CypherStatementList { new CypherTransactionStatement(cypherQueryMsTx, false) }; + var cypherQueryTx = new CypherQuery(queryTextTx, new Dictionary(), CypherResultMode.Projection); + var cypherQueryTxStatement = new CypherStatementList { new CypherTransactionStatement(cypherQueryTx, false) }; + var deleteRequest = MockRequest.Delete("/transaction/1"); + var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); + var commitRequestTx = MockRequest.PostJson("/transaction/2/commit", @"{'statements': []}"); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/transaction", cypherQueryMsTxStatement), + MockResponse.Json(201, GenerateInitTransactionResponse(1, resultColumn), "http://foo/db/data/transaction/1") + }, + { + MockRequest.PostObjectAsJson("/transaction", cypherQueryTxStatement), + MockResponse.Json(201, GenerateInitTransactionResponse(2, resultColumn), "http://foo/db/data/transaction/2") + }, + { + commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + }, + { + commitRequestTx, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + }, + { + deleteRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }.ShouldNotBeCalled(commitRequest)) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var msTransaction = new TransactionScope()) + { + Assert.IsTrue(client.InTransaction); + + long totalMsTx = client.Cypher + .Match("(n)") + .Return(n => n.Count()) + .Results + .SingleOrDefault(); + Assert.AreEqual(1, totalMsTx); + + using (var tx = client.BeginTransaction()) + { + long total = client.Cypher + .Match("(t)") + .Return(t => t.Count()) + .Results + .SingleOrDefault(); + + Assert.AreEqual(1, total); + + // should not be called + tx.Commit(); + } + } + } + } + + [Test] + public void NestedTransactionMixedBetweenTransactionScopeAndBeginTransaction() + { + const string queryText = @"MATCH (n) RETURN count(n)"; + const string resultColumn = @"{'columns':['count(n)'], 'data':[{'row':[1]}]}"; + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery, false) }; + var deleteRequest = MockRequest.Delete("/transaction/1"); + var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/transaction", cypherApiQuery), + MockResponse.Json(201, GenerateInitTransactionResponse(1, resultColumn), "http://foo/db/data/transaction/1") + }, + { + commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + }, + { + deleteRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }.ShouldNotBeCalled(commitRequest)) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var parentTx = client.BeginTransaction()) + { + using (var msTransaction = new TransactionScope()) + { + Assert.IsTrue(client.InTransaction); + + using (var tx = client.BeginTransaction()) + { + long total = client.Cypher + .Match("(n)") + .Return(n => n.Count()) + .Results + .SingleOrDefault(); + + Assert.AreEqual(1, total); + + // should not be called + tx.Commit(); + } + + msTransaction.Complete(); + } + } + + Assert.IsFalse(client.InTransaction); + } + } + + [Test] + public void TestTransactionScopeWithSimpleDeserialization() + { + const string queryText = @"MATCH (n) RETURN count(n)"; + const string resultColumn = @"{'columns':['count(n)'], 'data':[{'row':[1]}]}"; + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery, false) }; + var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/transaction", cypherApiQuery), + MockResponse.Json(201, GenerateInitTransactionResponse(1, resultColumn), "http://foo/db/data/transaction/1") + }, + { + commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var msTransaction = new TransactionScope()) + { + Assert.IsTrue(client.InTransaction); + + long total = client.Cypher + .Match("(n)") + .Return(n => n.Count()) + .Results + .SingleOrDefault(); + + Assert.AreEqual(1, total); + + msTransaction.Complete(); + } + + Assert.IsFalse(client.InTransaction); + } + } + + [Test] + public void TransactionCommitInTransactionScope() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var scope = new TransactionScope()) + { + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + scope.Complete(); + } + } + } + + [Test] + public void SecondRequestDoesntReturnCreateHttpStatus() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var secondRequest = MockRequest.PostJson("/transaction/1", @"{ + 'statements': [{'statement': 'MATCH t\r\nRETURN count(t)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + }, + { + secondRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + // dummy queries to generate request + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + + client.Cypher + .Match("t") + .Return(t => t.Count()) + .ExecuteWithoutResults(); + + transaction.Commit(); + } + } + } + + [Test] + public void KeepAliveAfterFirstRequest() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var rollbackTransactionRequest = MockRequest.Delete("/transaction/1"); + var keepAliveRequest = MockRequest.PostJson("/transaction/1", @"{'statements': []}"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + keepAliveRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + }, + { + rollbackTransactionRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + + transaction.KeepAlive(); + } + } + } + + [Test] + public void DeserializeResultsFromTransaction() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var deserializationRequest = MockRequest.PostJson("/transaction/1", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var rollbackTransactionRequest = MockRequest.Delete("/transaction/1"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + deserializationRequest, + MockResponse.Json(200, @"{'results':[{'columns': ['count(n)'], 'data': [{'row': [0]}]}]}") + }, + { + rollbackTransactionRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + + // this query will hit the deserializer + var count = client.Cypher + .Match("n") + .Return(n => n.Count()) + .Results; + + Assert.AreEqual(count.First(), 0); + } + } + } + + [Test] + public void OnTransactionDisposeCallRollback() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var rollbackTransactionRequest = MockRequest.Delete("/transaction/1"); + using (var testHarness = new RestTestHarness + { + { + initTransactionRequest, + MockResponse.Json(201, GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") + }, + { + rollbackTransactionRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResults(); + } + } + } + + public class DummyTotal + { + public int Total { get; set; } + } + + [Test] + public void ExecuteAsyncRequestInTransaction() + { + const string queryText = @"MATCH (n) RETURN count(n) as Total"; + const string resultColumn = @"{'columns':['Total'], 'data':[{'row':[1]}]}"; + + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery, false) }; + var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/transaction", cypherApiQuery), + MockResponse.Json(201, GenerateInitTransactionResponse(1, resultColumn), "http://foo/db/data/transaction/1") + }, + { + commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var rawClient = (IRawGraphClient) client; + using (var tran = client.BeginTransaction()) + { + var totalObj = rawClient.ExecuteGetCypherResultsAsync(cypherQuery).Result.Single(); + Assert.AreEqual(1, totalObj.Total); + tran.Commit(); + } + + } + } + + private class RestHarnessWithCounter : RestTestHarness + { + public ConcurrentQueue Queue { get; set; } + + public RestHarnessWithCounter() + { + Queue = new ConcurrentQueue(); + } + + protected override HttpResponseMessage HandleRequest(HttpRequestMessage request, string baseUri) + { + if (request.Method == HttpMethod.Post) + { + var content = request.Content.ReadAsString(); + int totalIndex = content.IndexOf("RETURN ", StringComparison.InvariantCultureIgnoreCase); + if (totalIndex > 0) + { + totalIndex += "RETURN ".Length; + int spaceIndex = content.IndexOf(" ", totalIndex, StringComparison.InvariantCultureIgnoreCase); + Assert.Greater(spaceIndex, totalIndex); + Queue.Enqueue(int.Parse(content.Substring(totalIndex, spaceIndex - totalIndex))); + } + } + + return base.HandleRequest(request, baseUri); + } + } + + [Test] + public void AsyncRequestsInTransactionShouldBeExecutedInOrder() + { + const string queryTextBase = @"MATCH (n) RETURN {0} as Total"; + const string resultColumnBase = @"{{'columns':['Total'], 'data':[{{'row':[{0}]}}]}}"; + const int asyncRequests = 15; + + var queries = new CypherQuery[asyncRequests]; + var apiQueries = new CypherStatementList[asyncRequests]; + var responses = new MockResponse[asyncRequests]; + var testHarness = new RestHarnessWithCounter(); + + for (int i = 0; i < asyncRequests; i++) + { + queries[i] = new CypherQuery(string.Format(queryTextBase, i), new Dictionary(), + CypherResultMode.Projection); + apiQueries[i] = new CypherStatementList {new CypherTransactionStatement(queries[i], false)}; + responses[i] = MockResponse.Json(200, + @"{'results':[" + string.Format(resultColumnBase, i) + @"], 'errors':[] }"); + if (i > 0) + { + testHarness.Add(MockRequest.PostObjectAsJson("/transaction/1", apiQueries[i]), responses[i]); + } + } + + testHarness.Add( + MockRequest.PostObjectAsJson("/transaction", apiQueries[0]), + MockResponse.Json(201, GenerateInitTransactionResponse(1, string.Format(resultColumnBase, 0)), + "http://foo/db/data/transaction/1") + ); + testHarness.Add( + MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"), + MockResponse.Json(200, @"{'results':[], 'errors':[] }") + ); + try + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var rawClient = (IRawGraphClient)client; + var tasks = new Task[asyncRequests]; + using (var tran = client.BeginTransaction()) + { + for (int i = 0; i < asyncRequests; i++) + { + int tmpResult = i; + tasks[i] = rawClient.ExecuteGetCypherResultsAsync(queries[i]).ContinueWith(task => + { + Assert.AreEqual(tmpResult, task.Result.Single().Total); + }); + } + + Task.WaitAll(tasks); + tran.Commit(); + } + } + finally + { + testHarness.Dispose(); + } + + // check that we have a total order + Assert.AreEqual(asyncRequests, testHarness.Queue.Count); + int lastElement = -1; + for (int i = 0; i < asyncRequests; i++) + { + int headItem; + Assert.IsTrue(testHarness.Queue.TryDequeue(out headItem)); + Assert.Greater(headItem, lastElement); + lastElement = headItem; + } + } + + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void CommitFailsOnPendingAsyncRequests() + { + const string queryText = @"MATCH (n) RETURN count(n) as Total"; + const string resultColumn = @"{'columns':['Total'], 'data':[{'row':[1]}]}"; + + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery, false) }; + + using (var testHarness = new RestTestHarness(false) + { + { + MockRequest.PostObjectAsJson("/transaction", cypherApiQuery), + MockResponse.Json(201, GenerateInitTransactionResponse(1, resultColumn), "http://foo/db/data/transaction/1") + } + }) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var rawClient = (IRawGraphClient) client; + using (var tran = client.BeginTransaction()) + { + rawClient.ExecuteGetCypherResultsAsync(cypherQuery); + tran.Commit(); + } + + } + + Assert.Fail("Commit did not fail with pending tasks"); + } + } +} diff --git a/Neo4jClient.Tests/Transactions/RestCallScenarioTests.cs b/Neo4jClient.Tests/Transactions/RestCallScenarioTests.cs new file mode 100644 index 000000000..ab0650d61 --- /dev/null +++ b/Neo4jClient.Tests/Transactions/RestCallScenarioTests.cs @@ -0,0 +1,204 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Text; +using Neo4jClient.Test.Relationships; +using Neo4jClient.Transactions; +using NUnit.Framework; + +namespace Neo4jClient.Test.Transactions +{ + [TestFixture] + public class RestCallScenarioTests + { + private class TestNode + { + public string Foo { get; set; } + } + + private void ExecuteRestMethodUnderTransaction( + Action restAction, + TransactionScopeOption option = TransactionScopeOption.Join, + IEnumerable> requests = null) + { + requests = requests ?? Enumerable.Empty>(); + using (var testHarness = new RestTestHarness()) + { + foreach (var request in requests) + { + testHarness.Add(request.Key, request.Value); + } + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction(option)) + { + restAction(client); + } + } + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void CreateShouldFailUnderTransaction() + { + ExecuteRestMethodUnderTransaction(client => client.Create(new object())); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void UpdateShouldFailUnderTransaction() + { + ExecuteRestMethodUnderTransaction(client => + { + var pocoReference = new NodeReference(456); + var updatedNode = client.Update( + pocoReference, nodeFromDb => + { + nodeFromDb.Foo = "fooUpdated"; + }); + }); + } + + [Test] + [ExpectedException(typeof (InvalidOperationException))] + public void DeleteNodeShouldFailUnderTransaction() + { + var pocoReference = new NodeReference(456); + ExecuteRestMethodUnderTransaction(client => client.Delete(pocoReference, DeleteMode.NodeOnly)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void DeleteAndRelationshipsShouldFailUnderTransaction() + { + var pocoReference = new NodeReference(456); + ExecuteRestMethodUnderTransaction(client => client.Delete(pocoReference, DeleteMode.NodeAndRelationships)); + } + + [Test] + [ExpectedException(typeof (InvalidOperationException))] + public void DeleteNodeIndexShouldFailUnderTransaction() + { + ExecuteRestMethodUnderTransaction(client => client.DeleteIndex("node", IndexFor.Node)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void DeleteRelationshipIndexShouldFailUnderTransaction() + { + ExecuteRestMethodUnderTransaction(client => client.DeleteIndex("rel", IndexFor.Relationship)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void DeleteIndexEntriesShouldFailUnderTransaction() + { + var pocoReference = new NodeReference(456); + ExecuteRestMethodUnderTransaction(client => client.DeleteIndexEntries("rel", pocoReference)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void DeleteRelationshipShouldFailUnderTransaction() + { + var relReference = new RelationshipReference(1); + ExecuteRestMethodUnderTransaction(client => client.DeleteRelationship(relReference)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void GetNodeShouldFailUnderTransaction() + { + var nodeReference = new NodeReference(1); + ExecuteRestMethodUnderTransaction(client => client.Get(nodeReference)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void GetRelationshipShouldFailUnderTransaction() + { + var relReference = new RelationshipReference(1); + ExecuteRestMethodUnderTransaction(client => client.Get(relReference)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void GetNodeIndexesShouldFailUnderTransaction() + { + ExecuteRestMethodUnderTransaction(client => client.GetIndexes(IndexFor.Node)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void GetRelationshipIndexesShouldUnderTransaction() + { + ExecuteRestMethodUnderTransaction(client => client.GetIndexes(IndexFor.Relationship)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void ReIndexShouldFailUnderTransaction() + { + var nodeReference = new NodeReference(1); + var indexEntries = new IndexEntry[] {new IndexEntry("node")}; + ExecuteRestMethodUnderTransaction(client => client.ReIndex(nodeReference, indexEntries)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void CreateRelationshipShouldFailUnderTransaction() + { + var nodeReference = new NodeReference(1); + var rel = new OwnedBy(new NodeReference(3)); + ExecuteRestMethodUnderTransaction(client => client.CreateRelationship(nodeReference, rel)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void CreateNodeIndexShouldFailUnderTransaction() + { + ExecuteRestMethodUnderTransaction( + client => client.CreateIndex("node", new IndexConfiguration(), IndexFor.Node)); + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void CreateRelationshipIndexShouldFailUnderTransaction() + { + ExecuteRestMethodUnderTransaction( + client => client.CreateIndex("rel", new IndexConfiguration(), IndexFor.Relationship)); + } + + [Test] + public void GetShouldSucceedInSuppressedMode() + { + var nodeReference = new NodeReference(1); + var requests = new List>() + { + new KeyValuePair( + MockRequest.Get("/node/1"), + MockResponse.Json(HttpStatusCode.OK, + @"{ 'self': 'http://foo/db/data/node/456', + 'data': { 'Foo': 'foo' + }, + 'create_relationship': 'http://foo/db/data/node/1/relationships', + 'all_relationships': 'http://foo/db/data/node/1/relationships/all', + 'all_typed relationships': 'http://foo/db/data/node/1/relationships/all/{-list|&|types}', + 'incoming_relationships': 'http://foo/db/data/node/1/relationships/in', + 'incoming_typed relationships': 'http://foo/db/data/node/1/relationships/in/{-list|&|types}', + 'outgoing_relationships': 'http://foo/db/data/node/1/relationships/out', + 'outgoing_typed relationships': 'http://foo/db/data/node/1/relationships/out/{-list|&|types}', + 'properties': 'http://foo/db/data/node/1/properties', + 'property': 'http://foo/db/data/node/1/property/{key}', + 'traverse': 'http://foo/db/data/node/1/traverse/{returnType}' + }") + ) + }; + ExecuteRestMethodUnderTransaction( + client => client.Get(nodeReference), + TransactionScopeOption.Suppress, + requests); + } + + } +} diff --git a/Neo4jClient.Tests/Transactions/TransactionManagementTests.cs b/Neo4jClient.Tests/Transactions/TransactionManagementTests.cs new file mode 100644 index 000000000..9593b4392 --- /dev/null +++ b/Neo4jClient.Tests/Transactions/TransactionManagementTests.cs @@ -0,0 +1,438 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; +using Neo4jClient.Transactions; +using NUnit.Framework; +using TransactionScopeOption = Neo4jClient.Transactions.TransactionScopeOption; + +namespace Neo4jClient.Test.Transactions +{ + [TestFixture] + public class TransactionManagementTests + { + [Test] + [ExpectedException(typeof (NotSupportedException))] + public void BeginTransactionShouldFailWithLower20Versions() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateGraphClient(false); + client.Connect(); + client.BeginTransaction(); + } + } + + [Test] + [ExpectedException(typeof (InvalidOperationException))] + public void BeginTransactionShouldFailWithoutConnectingFirst() + { + var client = new GraphClient(new Uri("http://foo/db/data"), null); + client.BeginTransaction(); + } + + [Test] + public void ShouldBeAbleToGetTransactionObjectAfterBeginTransaction() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + client.Connect(); + using (var transaction = client.BeginTransaction()) + { + Assert.AreSame(transaction, client.Transaction); + } + } + } + + [Test] + public void IgnoreSystemTransactionsIfInsideInternalTransaction() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + using (var msTransaction = new TransactionScope()) + { + Assert.IsTrue(client.InTransaction); + } + Assert.IsTrue(client.InTransaction); + } + + } + } + + [Test] + public void ShouldNotBeAbleToGetTransactionAfterTransactionScope() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + + } + + Assert.IsNull(client.Transaction); + } + } + + private ITransaction GetRealTransaction(ITransaction proxiedTransaction) + { + var txContext = ((TransactionScopeProxy) proxiedTransaction).TransactionContext; + return txContext == null ? null : txContext.Transaction; + } + + [Test] + public void ShouldNotBeInATransactionScopeWhileSuppressed() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + using (var tran2 = client.BeginTransaction(TransactionScopeOption.Suppress)) + { + Assert.AreNotSame(GetRealTransaction(tran2), GetRealTransaction(transaction)); + Assert.IsFalse(client.InTransaction); + } + } + } + } + + [Test] + public void TransactionJoinedShouldBeTheSame() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + + using (var tran2 = client.BeginTransaction()) + { + Assert.AreSame(GetRealTransaction(tran2), GetRealTransaction(transaction)); + } + } + } + } + + [Test] + public void RequiresNewCreateNewTransaction() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + using (var tran2 = client.BeginTransaction(TransactionScopeOption.RequiresNew)) + { + Assert.AreNotSame(GetRealTransaction(tran2), GetRealTransaction(transaction)); + Assert.IsTrue(client.InTransaction); + } + Assert.IsTrue(client.InTransaction); + } + Assert.IsFalse(client.InTransaction); + } + } + + [Test] + public void JoinTransactionAfterSuppressCreatesNewTransaction() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var tran = client.BeginTransaction()) + { + using (var tran2 = client.BeginTransaction(TransactionScopeOption.Suppress)) + { + Assert.AreNotSame(tran2, tran); + Assert.IsFalse(client.InTransaction); + using (var tran3 = client.BeginTransaction(TransactionScopeOption.Join)) + { + Assert.AreNotSame(GetRealTransaction(tran2), GetRealTransaction(tran3)); + Assert.AreNotSame(GetRealTransaction(tran3), GetRealTransaction(tran2)); + Assert.IsTrue(client.InTransaction); + } + } + Assert.IsTrue(client.InTransaction); + } + Assert.IsFalse(client.InTransaction); + } + } + + [Test] + public void JoinedTransactionsCommitAfterAllEmitVote() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var tran = client.BeginTransaction()) + { + using (var tran2 = client.BeginTransaction()) + { + tran2.Commit(); + } + + Assert.IsTrue(tran.IsOpen); + + using (var tran3 = client.BeginTransaction(TransactionScopeOption.Suppress)) + { + Assert.AreNotSame(GetRealTransaction(tran3), GetRealTransaction(tran)); + Assert.IsFalse(client.InTransaction); + } + + Assert.IsTrue(client.InTransaction); + Assert.IsTrue(tran.IsOpen); + + tran.Commit(); + Assert.IsFalse(tran.IsOpen); + } + Assert.IsFalse(client.InTransaction); + } + } + + [Test] + public void RollbackInJoinedTransactionClosesAllJoinedScopes() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var tran = client.BeginTransaction()) + { + using (var tran2 = client.BeginTransaction()) + { + tran2.Rollback(); + } + + Assert.IsFalse(tran.IsOpen); + } + Assert.IsFalse(client.InTransaction); + } + } + + [Test] + public void CommitInRequiresNewDoesntAffectParentScope() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var tran = client.BeginTransaction()) + { + using (var tran2 = client.BeginTransaction(TransactionScopeOption.RequiresNew)) + { + tran2.Commit(); + Assert.IsFalse(tran2.IsOpen); + } + + Assert.IsTrue(tran.IsOpen); + } + Assert.IsFalse(client.InTransaction); + } + } + + [Test] + public void RollbackInRequiresNewDoesntAffectParentScope() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var tran = client.BeginTransaction()) + { + using (var tran2 = client.BeginTransaction(TransactionScopeOption.RequiresNew)) + { + tran2.Rollback(); + Assert.IsFalse(tran2.IsOpen); + } + + Assert.IsTrue(tran.IsOpen); + } + Assert.IsFalse(client.InTransaction); + } + } + + [Test] + [ExpectedException(typeof(ClosedTransactionException))] + public void CannotJoinAfterClosedTransaction() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var tran = client.BeginTransaction()) + { + tran.Commit(); + + Assert.IsFalse(tran.IsOpen); + // should fail here + using (var tran2 = client.BeginTransaction()) + { + } + } + } + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void FailsForCommitInSuppressMode() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var tran = client.BeginTransaction(TransactionScopeOption.Suppress)) + { + tran.Commit(); + } + } + } + + [Test] + [ExpectedException(typeof(InvalidOperationException))] + public void FailsForRollbackInSuppressMode() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var tran = client.BeginTransaction(TransactionScopeOption.Suppress)) + { + tran.Rollback(); + } + } + } + + [Test] + [ExpectedException(typeof (ClosedTransactionException))] + public void ShouldNotBeAbleToCommitTwice() + { + var transaction = new Neo4jTransaction(new GraphClient(new Uri("http://foo/db/data"))); + transaction.Commit(); + transaction.Commit(); + } + + [Test] + [ExpectedException(typeof (ClosedTransactionException))] + public void ShouldNotBeAbleToRollbackTwice() + { + var transaction = new Neo4jTransaction(new GraphClient(new Uri("http://foo/db/data"))); + transaction.Rollback(); + transaction.Rollback(); + } + + [Test] + [ExpectedException(typeof (ClosedTransactionException))] + public void ShouldNotBeAbleToCommitAfterRollback() + { + var transaction = new Neo4jTransaction(new GraphClient(new Uri("http://foo/db/data"))); + transaction.Rollback(); + transaction.Commit(); + } + + [Test] + [ExpectedException(typeof (ClosedTransactionException))] + public void ShouldNotBeAbleToRollbackAfterCommit() + { + var transaction = new Neo4jTransaction(new GraphClient(new Uri("http://foo/db/data"))); + transaction.Commit(); + transaction.Rollback(); + } + + [Test] + public void TwoThreadsShouldNotHaveTheSameTransactionObject() + { + // if thread support is not well implemented then the t2's BeginTransaction will fail with NotSupportedException + ITransaction transactionFromThread1 = null; + ITransaction transactionFromThread2 = null; + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var firstTransactionSet = new EventWaitHandle(false, EventResetMode.AutoReset); + var secondTransactionSet = new EventWaitHandle(false, EventResetMode.AutoReset); + var t1 = new Task(() => + { + try + { + using (var transaction = client.BeginTransaction()) + { + transactionFromThread1 = transaction; + firstTransactionSet.Set(); + secondTransactionSet.WaitOne(); + } + } + catch (Exception e) + { + firstTransactionSet.Set(); + throw; + } + }); + + var t2 = new Task(() => + { + firstTransactionSet.WaitOne(); + try + { + using (var transaction = client.BeginTransaction()) + { + transactionFromThread2 = transaction; + secondTransactionSet.Set(); + } + } + catch (Exception e) + { + secondTransactionSet.Set(); + throw; + } + }); + + t1.Start(); + t2.Start(); + Task.WaitAll(t1, t2); + Assert.IsNotNull(transactionFromThread1); + Assert.IsNotNull(transactionFromThread2); + Assert.AreNotEqual(transactionFromThread1, transactionFromThread2); + + } + } + + [Test] + public void ShouldPromoteBadQueryResponseToNiceException() + { + // Arrange + const string queryText = @"broken query"; + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection); + var cypherApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery, false)}; + + using (var testHarness = new RestTestHarness + { + { + MockRequest.PostObjectAsJson("/transaction", cypherApiQuery), + MockResponse.Json(HttpStatusCode.OK, @"{'results':[], 'errors': [{ + 'code' : 'Neo.ClientError.Statement.InvalidSyntax', + 'message' : 'Invalid input b: expected SingleStatement (line 1, column 1)\nThis is not a valid Cypher Statement.\n ^' + }]}") + } + }) + { + var graphClient = testHarness.CreateAndConnectTransactionalGraphClient(); + var rawClient = (IRawGraphClient) graphClient; + + using (graphClient.BeginTransaction()) + { + + var ex = Assert.Throws(() => rawClient.ExecuteCypher(cypherQuery)); + Assert.AreEqual("InvalidSyntax: Invalid input b: expected SingleStatement (line 1, column 1)\nThis is not a valid Cypher Statement.\n ^", ex.Message); + Assert.AreEqual("Invalid input b: expected SingleStatement (line 1, column 1)\nThis is not a valid Cypher Statement.\n ^", ex.NeoMessage); + Assert.AreEqual("InvalidSyntax", ex.NeoExceptionName); + Assert.AreEqual("Neo.ClientError.Statement.InvalidSyntax", ex.NeoFullName); + CollectionAssert.AreEqual(new String[] {}, ex.NeoStackTrace); + } + } + } + } +} diff --git a/Test/UtilitiesTests.cs b/Neo4jClient.Tests/UtilitiesTests.cs similarity index 98% rename from Test/UtilitiesTests.cs rename to Neo4jClient.Tests/UtilitiesTests.cs index 8ca31797b..1c61c8009 100644 --- a/Test/UtilitiesTests.cs +++ b/Neo4jClient.Tests/UtilitiesTests.cs @@ -1,31 +1,31 @@ -using System.Collections.Generic; -using System.Linq; -using NUnit.Framework; - -namespace Neo4jClient.Test -{ - [TestFixture] - public class UtilitiesTests - { - [Test] - public void ShouldReturnDifferenceBetweenDictionaries() - { - var dic1 = new Dictionary { { "key1", "value1" }, { "key2", "value2" }, { "key3", "value3" }}; - var dic2 = new Dictionary { { "key1", "newValue1" }, { "key2", "value2" }, { "key3", "newValue3" }}; - var dic3 = new Dictionary { { "key1", "value1" }, { "key2", "value2" }, { "key3", "value3" } }; - var dic4 = new Dictionary { { "key2", "value2" }, { "key3", "newValue3" } }; - - - var differences12 = Utilities.GetDifferencesBetweenDictionaries(dic1, dic2).ToArray(); - var differences13 = Utilities.GetDifferencesBetweenDictionaries(dic1, dic3).ToArray(); - var differences14 = Utilities.GetDifferencesBetweenDictionaries(dic1, dic4).ToArray(); - - Assert.IsTrue(differences12.Count() == 2); - Assert.IsTrue(differences12.Any(d=>d.FieldName == "key1" && d.OldValue == "value1" && d.NewValue == "newValue1")); - Assert.IsTrue(differences12.Any(d => d.FieldName == "key3" && d.OldValue == "value3" && d.NewValue == "newValue3")); - Assert.IsTrue(!differences13.Any()); - Assert.IsTrue(differences14.Any(d => d.FieldName == "key1" && d.OldValue == "value1" && d.NewValue == "")); - Assert.IsTrue(differences14.Any(d => d.FieldName == "key3" && d.OldValue == "value3" && d.NewValue == "newValue3")); - } - } -} +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace Neo4jClient.Test +{ + [TestFixture] + public class UtilitiesTests + { + [Test] + public void ShouldReturnDifferenceBetweenDictionaries() + { + var dic1 = new Dictionary { { "key1", "value1" }, { "key2", "value2" }, { "key3", "value3" }}; + var dic2 = new Dictionary { { "key1", "newValue1" }, { "key2", "value2" }, { "key3", "newValue3" }}; + var dic3 = new Dictionary { { "key1", "value1" }, { "key2", "value2" }, { "key3", "value3" } }; + var dic4 = new Dictionary { { "key2", "value2" }, { "key3", "newValue3" } }; + + + var differences12 = Utilities.GetDifferencesBetweenDictionaries(dic1, dic2).ToArray(); + var differences13 = Utilities.GetDifferencesBetweenDictionaries(dic1, dic3).ToArray(); + var differences14 = Utilities.GetDifferencesBetweenDictionaries(dic1, dic4).ToArray(); + + Assert.IsTrue(differences12.Count() == 2); + Assert.IsTrue(differences12.Any(d=>d.FieldName == "key1" && d.OldValue == "value1" && d.NewValue == "newValue1")); + Assert.IsTrue(differences12.Any(d => d.FieldName == "key3" && d.OldValue == "value3" && d.NewValue == "newValue3")); + Assert.IsTrue(!differences13.Any()); + Assert.IsTrue(differences14.Any(d => d.FieldName == "key1" && d.OldValue == "value1" && d.NewValue == "")); + Assert.IsTrue(differences14.Any(d => d.FieldName == "key3" && d.OldValue == "value3" && d.NewValue == "newValue3")); + } + } +} diff --git a/Test/packages.config b/Neo4jClient.Tests/packages.config similarity index 78% rename from Test/packages.config rename to Neo4jClient.Tests/packages.config index 43bdd93d4..0e4e6fb6c 100644 --- a/Test/packages.config +++ b/Neo4jClient.Tests/packages.config @@ -1,7 +1,7 @@ - - - - - - + + + + + + \ No newline at end of file diff --git a/Neo4jClient.sln b/Neo4jClient.sln index 33ff60002..1012b3144 100644 --- a/Neo4jClient.sln +++ b/Neo4jClient.sln @@ -1,10 +1,10 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 +# Visual Studio 14 +VisualStudioVersion = 14.0.22823.1 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient", "Neo4jClient\Neo4jClient.csproj", "{343B9889-6DDF-4474-A1EC-05508A652E5A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Test", "Test\Test.csproj", "{2724A871-4B4F-4C83-8E0F-2439F69CADA2}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{4BDBC8A7-BD54-412B-B8C9-95F4BA6C0F7A}" ProjectSection(SolutionItems) = preProject .nuget\NuGet.Config = .nuget\NuGet.Config @@ -12,6 +12,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{4BDBC8 .nuget\NuGet.targets = .nuget\NuGet.targets EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient.Tests", "Neo4jClient.Tests\Neo4jClient.Tests.csproj", "{2724A871-4B4F-4C83-8E0F-2439F69CADA2}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/Neo4jClient.sln.DotSettings b/Neo4jClient.sln.DotSettings index cd278f53c..fe9a0a6cb 100644 --- a/Neo4jClient.sln.DotSettings +++ b/Neo4jClient.sln.DotSettings @@ -1,4 +1,7 @@  + False + False + False <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> \ No newline at end of file diff --git a/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs b/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs new file mode 100644 index 000000000..ac6a7ab57 --- /dev/null +++ b/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs @@ -0,0 +1,104 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using Neo4jClient.Cypher; +using Newtonsoft.Json; + +namespace Neo4jClient.ApiModels.Cypher +{ + /// + /// Represents the collection of Cypher statements that are going to be sent through a transaction. + /// + [JsonObject] + class CypherStatementList : IList + { + private readonly IList _statements; + + public CypherStatementList() + { + _statements = new List(); + } + + public CypherStatementList(IEnumerable queries) + { + _statements = queries + .Select(query => new CypherTransactionStatement(query, query.ResultFormat == CypherResultFormat.Rest)) + .ToList(); + } + + [JsonProperty("statements")] + public IList Statements + { + get { return _statements; } + } + + public IEnumerator GetEnumerator() + { + return _statements.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable) _statements).GetEnumerator(); + } + + public void Add(CypherTransactionStatement item) + { + _statements.Add(item); + } + + public void Clear() + { + _statements.Clear(); + } + + public bool Contains(CypherTransactionStatement item) + { + return _statements.Contains(item); + } + + public void CopyTo(CypherTransactionStatement[] array, int arrayIndex) + { + _statements.CopyTo(array, arrayIndex); + } + + public bool Remove(CypherTransactionStatement item) + { + return _statements.Remove(item); + } + + [JsonIgnore] + public int Count + { + get { return _statements.Count; } + } + + [JsonIgnore] + public bool IsReadOnly + { + get { return _statements.IsReadOnly; } + } + + public int IndexOf(CypherTransactionStatement item) + { + return _statements.IndexOf(item); + } + + public void Insert(int index, CypherTransactionStatement item) + { + _statements.Insert(index, item); + } + + public void RemoveAt(int index) + { + _statements.RemoveAt(index); + } + + [JsonIgnore] + public CypherTransactionStatement this[int index] + { + get { return _statements[index]; } + set { _statements[index] = value; } + } + } +} diff --git a/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs b/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs new file mode 100644 index 000000000..0d6aaa65e --- /dev/null +++ b/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using Neo4jClient.Cypher; +using Newtonsoft.Json; + +namespace Neo4jClient.ApiModels.Cypher +{ + /// + /// Very similar to CypherApiQuery but it's used for opened transactions as their serialization + /// is different + /// + class CypherTransactionStatement + { + private readonly string _queryText; + private readonly IDictionary _queryParameters; + private readonly string[] _formatContents; + + public CypherTransactionStatement(CypherQuery query, bool restFormat) + { + _queryText = query.QueryText; + _queryParameters = query.QueryParameters ?? new Dictionary(); + _formatContents = restFormat ? new[] {"REST"} : new string[] {}; + } + + [JsonProperty("statement")] + public string Statement + { + get { return _queryText; } + } + + [JsonProperty("resultDataContents")] + public IEnumerable ResultDataContents + { + get { return _formatContents; } + } + + [JsonProperty("parameters")] + public IDictionary Parameters + { + get { return _queryParameters; } + } + } +} diff --git a/Neo4jClient/ApiModels/RootApiResponse.cs b/Neo4jClient/ApiModels/RootApiResponse.cs index 6e06dc2c4..651636da1 100644 --- a/Neo4jClient/ApiModels/RootApiResponse.cs +++ b/Neo4jClient/ApiModels/RootApiResponse.cs @@ -6,6 +6,9 @@ namespace Neo4jClient.ApiModels { class RootApiResponse { + [JsonProperty("transaction")] + public string Transaction { get; set; } + [JsonProperty("cypher")] public string Cypher { get; set; } diff --git a/Neo4jClient/Cypher/CypherFluentQuery.cs b/Neo4jClient/Cypher/CypherFluentQuery.cs index f0c7b8b60..f8517be38 100644 --- a/Neo4jClient/Cypher/CypherFluentQuery.cs +++ b/Neo4jClient/Cypher/CypherFluentQuery.cs @@ -293,9 +293,9 @@ public ICypherFluentQuery ForEach(string text) return Mutate(w => w.AppendClause("FOREACH " + text)); } - public ICypherFluentQuery Unwind(string collectionName, string identity) + public ICypherFluentQuery Unwind(string collectionName, string columnName) { - return Mutate(w => w.AppendClause(string.Format("UNWIND {0} AS {1}", collectionName, identity))); + return Mutate(w => w.AppendClause(string.Format("UNWIND {0} AS {1}", collectionName, columnName))); } public ICypherFluentQuery Unwind(IEnumerable collection, string identity) diff --git a/Neo4jClient/Cypher/CypherFluentQuery`Return.cs b/Neo4jClient/Cypher/CypherFluentQuery`Return.cs index c1ba2ccc1..05dd9ac55 100644 --- a/Neo4jClient/Cypher/CypherFluentQuery`Return.cs +++ b/Neo4jClient/Cypher/CypherFluentQuery`Return.cs @@ -1,5 +1,6 @@ using System; using System.Linq.Expressions; +using Microsoft.SqlServer.Server; namespace Neo4jClient.Cypher { @@ -20,8 +21,22 @@ public ICypherFluentQuery Return(string identity) if(identityIsCollection && (typeof(TResult).GetInterface("IEnumerable") == null)) throw new ArgumentException(IdentityLooksLikeACollectionButTheResultIsNotEnumerableMessage, "identity"); + + var resultType = typeof (TResult); + + var restFormatNeeded = resultType.IsGenericType && ( + resultType.GetGenericTypeDefinition() == typeof (Node<>) || + resultType.GetGenericTypeDefinition() == typeof (RelationshipInstance<>) || + resultType.GetGenericTypeDefinition() == typeof (Relationship<>)); - return Mutate(w => w.AppendClause("RETURN " + identity)); + return Mutate(w => + { + if (restFormatNeeded) + { + w.ResultFormat = CypherResultFormat.Rest; + } + w.AppendClause("RETURN " + identity); + }); } [Obsolete("This was an internal that never should have been exposed. If you want to create a projection, you should be using the lambda overload instead. See the 'Using Functions in Return Clauses' and 'Using Custom Text in Return Clauses' sections of https://bitbucket.org/Readify/neo4jclient/wiki/cypher for details of how to do this.", true)] @@ -42,6 +57,7 @@ ICypherFluentQuery Return(LambdaExpression expression) return Mutate(w => { w.ResultMode = returnExpression.ResultMode; + w.ResultFormat = returnExpression.ResultFormat; w.AppendClause("RETURN " + returnExpression.Text); }); } @@ -53,6 +69,7 @@ ICypherFluentQuery ReturnDistinct(LambdaExpression expression) return Mutate(w => { w.ResultMode = returnExpression.ResultMode; + w.ResultFormat = returnExpression.ResultFormat; w.AppendClause("RETURN distinct " + returnExpression.Text); }); } diff --git a/Neo4jClient/Cypher/CypherFluentQuery`Where.cs b/Neo4jClient/Cypher/CypherFluentQuery`Where.cs index ab58a9db8..0608d92d7 100644 --- a/Neo4jClient/Cypher/CypherFluentQuery`Where.cs +++ b/Neo4jClient/Cypher/CypherFluentQuery`Where.cs @@ -281,4 +281,4 @@ public ICypherFluentQuery OrWhere queryParameters; readonly CypherResultMode resultMode; - readonly IContractResolver jsonContractResolver; + readonly CypherResultFormat resultFormat; + readonly IContractResolver jsonContractResolver; + + public CypherQuery( + string queryText, + IDictionary queryParameters, + CypherResultMode resultMode, + IContractResolver contractResolver = null) : + this(queryText, queryParameters, resultMode, CypherResultFormat.DependsOnEnvironment, contractResolver) + { + } public CypherQuery( string queryText, IDictionary queryParameters, - CypherResultMode resultMode, - IContractResolver contractResolver = null) + CypherResultMode resultMode, + CypherResultFormat resultFormat, + IContractResolver contractResolver = null) { this.queryText = queryText; this.queryParameters = queryParameters; this.resultMode = resultMode; + this.resultFormat = resultFormat; jsonContractResolver = contractResolver ?? GraphClient.DefaultJsonContractResolver; } @@ -36,6 +55,11 @@ public string QueryText get { return queryText; } } + public CypherResultFormat ResultFormat + { + get { return resultFormat; } + } + public CypherResultMode ResultMode { get { return resultMode; } diff --git a/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs b/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs index 0872a3e42..b5d6df0dd 100644 --- a/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs +++ b/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs @@ -27,6 +27,23 @@ public class CypherReturnExpressionBuilder internal const string UnsupportedBinaryExpressionComparisonExceptionMessage = "We don't currently support anything other than null for a binary expression comparison. Please raise an issue at https://github.com/Readify/Neo4jClient including your query code."; + private class ExpressionBuild + { + public ExpressionBuild(string expressionText) + : this(expressionText, CypherResultFormat.DependsOnEnvironment) + { + } + + public ExpressionBuild(string expressionText, CypherResultFormat format) + { + ExpressionText = expressionText; + ResultFormat = format; + } + + public string ExpressionText { get; private set; } + public CypherResultFormat ResultFormat { get; private set; } + } + // Terminology used in this file: // // - a "statement" is something like "x.Foo? AS Bar" @@ -48,30 +65,52 @@ public static ReturnExpression BuildText( body = ((UnaryExpression)expression.Body).Operand; } - string text; + ExpressionBuild expressionBuild; switch (body.NodeType) { case ExpressionType.MemberInit: var memberInitExpression = (MemberInitExpression) body; - text = BuildText(memberInitExpression, capabilities, jsonConvertersThatTheDeserializerWillUse,camelCaseProperties); - return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Projection}; + expressionBuild = BuildText(memberInitExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties); + return new ReturnExpression + { + Text = expressionBuild.ExpressionText, + ResultMode = CypherResultMode.Projection, + ResultFormat = expressionBuild.ResultFormat + }; case ExpressionType.New: var newExpression = (NewExpression) body; - text = BuildText(newExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties); - return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Projection}; + expressionBuild = BuildText(newExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties); + return new ReturnExpression + { + Text = expressionBuild.ExpressionText, + ResultMode = CypherResultMode.Projection, + ResultFormat = expressionBuild.ResultFormat + }; case ExpressionType.Call: var methodCallExpression = (MethodCallExpression) body; - text = BuildText(methodCallExpression, capabilities, jsonConvertersThatTheDeserializerWillUse); - return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Set}; + expressionBuild = BuildText(methodCallExpression, capabilities, jsonConvertersThatTheDeserializerWillUse); + return new ReturnExpression + { + Text = expressionBuild.ExpressionText, + ResultMode = CypherResultMode.Set, + ResultFormat = expressionBuild.ResultFormat + }; case ExpressionType.MemberAccess: var memberExpression = (MemberExpression) body; - text = BuildText(memberExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties); - return new ReturnExpression { Text = text, ResultMode = CypherResultMode.Set }; + expressionBuild = BuildText(memberExpression, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties); + return new ReturnExpression + { + Text = expressionBuild.ExpressionText, + ResultMode = CypherResultMode.Set, + ResultFormat = expressionBuild.ResultFormat + }; default: throw new ArgumentException(ReturnExpressionShouldBeOneOfExceptionMessage, "expression"); } } + + /// /// This build method caters to object initializers, like: /// @@ -82,7 +121,7 @@ public static ReturnExpression BuildText( /// /// caters to anonymous types. /// - static string BuildText( + static ExpressionBuild BuildText( MemberInitExpression expression, CypherCapabilities capabilities, IEnumerable jsonConvertersThatTheDeserializerWillUse, @@ -101,13 +140,21 @@ static string BuildText( var bindingTexts = expression.Bindings.Select(binding => { if (binding.BindingType != MemberBindingType.Assignment) - throw new ArgumentException("All bindings must be assignments. For example: n => new MyResultType { Foo = n.Bar }", "expression"); + throw new ArgumentException( + "All bindings must be assignments. For example: n => new MyResultType { Foo = n.Bar }", + "expression"); - var memberAssignment = (MemberAssignment)binding; - return BuildStatement(memberAssignment.Expression, binding.Member, capabilities, jsonConvertersThatTheDeserializerWillUse,camelCaseProperties); - }); + var memberAssignment = (MemberAssignment) binding; + return BuildStatement(memberAssignment.Expression, binding.Member, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties); + }).ToArray(); - return string.Join(", ", bindingTexts.ToArray()); + var resultFormat = bindingTexts.Any(expressionBuild => expressionBuild.ResultFormat == CypherResultFormat.Rest) + ? CypherResultFormat.Rest + : CypherResultFormat.DependsOnEnvironment; + + return new ExpressionBuild( + string.Join(", ", bindingTexts.Select(expressionBuild => expressionBuild.ExpressionText)), + resultFormat); } /// @@ -123,7 +170,7 @@ static string BuildText( /// /// This is the scenario that this build method caters for. /// - static string BuildText( + static ExpressionBuild BuildText( NewExpression expression, CypherCapabilities capabilities, IEnumerable jsonConvertersThatTheDeserializerWillUse, @@ -166,16 +213,22 @@ static string BuildText( var bindingTexts = expressionMembers.Select((member, index) => { var argument = expression.Arguments[index]; - return BuildStatement(argument, member, capabilities, jsonConvertersThatTheDeserializerWillUse,camelCaseProperties); - }); + return BuildStatement(argument, member, capabilities, jsonConvertersThatTheDeserializerWillUse, camelCaseProperties); + }).ToArray(); + + var resultFormat = bindingTexts.Any(expressionBuild => expressionBuild.ResultFormat == CypherResultFormat.Rest) + ? CypherResultFormat.Rest + : CypherResultFormat.DependsOnEnvironment; - return string.Join(", ", bindingTexts.ToArray()); + return new ExpressionBuild( + string.Join(", ", bindingTexts.Select(expressionBuild => expressionBuild.ExpressionText)), + resultFormat); } /// /// This build method caters to expressions like: item => item.Count() /// - static string BuildText( + static ExpressionBuild BuildText( MethodCallExpression expression, CypherCapabilities capabilities, IEnumerable jsonConvertersThatTheDeserializerWillUse) @@ -186,7 +239,7 @@ static string BuildText( /// /// This build method caters to expressions like: item => item.As<Foo>().Bar /// - static string BuildText( + static ExpressionBuild BuildText( MemberExpression expression, CypherCapabilities capabilities, IEnumerable jsonConvertersThatTheDeserializerWillUse, @@ -199,12 +252,12 @@ static string BuildText( throw new ArgumentException("Member expressions are only supported off ICypherResultItem.As(). For example: Return(foo => foo.As().Baz).", "expression"); var baseStatement = BuildStatement(innerExpression, false, capabilities, jsonConvertersThatTheDeserializerWillUse); - var statement = string.Format("{0}.{1}", baseStatement, CypherFluentQuery.ApplyCamelCase(camelCaseProperties, expression.Member.Name)); + var statement = string.Format("{0}.{1}", baseStatement.ExpressionText, CypherFluentQuery.ApplyCamelCase(camelCaseProperties, expression.Member.Name)); - return statement; + return new ExpressionBuild(statement, baseStatement.ResultFormat); } - static string BuildStatement( + static ExpressionBuild BuildStatement( Expression sourceExpression, MemberInfo targetMember, CypherCapabilities capabilities, @@ -230,7 +283,7 @@ static string BuildStatement( unwrappedExpression.GetType().FullName)); } - static string BuildStatement( + static ExpressionBuild BuildStatement( BinaryExpression binaryExpression, MemberInfo targetMember) { @@ -252,10 +305,12 @@ static string BuildStatement( throw new NotSupportedException(UnsupportedBinaryExpressionComparisonExceptionMessage); var targetObject = (ParameterExpression)binaryExpression.Left; - return string.Format("{0} {1} NULL AS {2}", targetObject.Name, expression, targetMember.Name); + return new ExpressionBuild( + string.Format("{0} {1} NULL AS {2}", targetObject.Name, expression, targetMember.Name), + CypherResultFormat.DependsOnEnvironment); } - static string BuildStatement( + static ExpressionBuild BuildStatement( MemberExpression memberExpression, MemberInfo targetMember, CypherCapabilities capabilities, @@ -292,10 +347,10 @@ static string BuildStatement( if (isNullable) optionalIndicator = "?"; } - return string.Format("{0}.{1}{2} AS {3}", targetObject.Name, CypherFluentQuery.ApplyCamelCase(camelCaseProperties, memberInfo.Name), optionalIndicator, targetMember.Name); + return new ExpressionBuild(string.Format("{0}.{1}{2} AS {3}", targetObject.Name, CypherFluentQuery.ApplyCamelCase(camelCaseProperties, memberInfo.Name), optionalIndicator, targetMember.Name)); } - static string BuildStatement( + static ExpressionBuild BuildStatement( MethodCallExpression expression, MemberInfo targetMember, CypherCapabilities capabilities, @@ -303,30 +358,25 @@ static string BuildStatement( { var isNullable = IsMemberNullable(targetMember); var statement = BuildStatement(expression, isNullable, capabilities, jsonConvertersThatTheDeserializerWillUse); - statement = statement + " AS " + targetMember.Name; - return statement; + return new ExpressionBuild(statement.ExpressionText + " AS " + targetMember.Name, statement.ResultFormat); } - static string BuildStatement( + static ExpressionBuild BuildStatement( MethodCallExpression expression, bool isNullable, CypherCapabilities capabilities, IEnumerable jsonConvertersThatTheDeserializerWillUse) { - string statement; if (expression.Method.DeclaringType == typeof(ICypherResultItem) || expression.Method.DeclaringType == typeof(IFluentCypherResultItem)) - statement = BuildCypherResultItemStatement(expression, isNullable, capabilities, jsonConvertersThatTheDeserializerWillUse); - else if (expression.Method.DeclaringType == typeof(All)) - statement = BuildCypherAllStatement(expression); - else if (expression.Method.DeclaringType == typeof(Return)) - statement = BuildCypherReturnStatement(expression); - else - throw new ArgumentException(ReturnExpressionCannotBeSerializedToCypherExceptionMessage); - - return statement; + return BuildCypherResultItemStatement(expression, isNullable, capabilities, jsonConvertersThatTheDeserializerWillUse); + if (expression.Method.DeclaringType == typeof(All)) + return BuildCypherAllStatement(expression); + if (expression.Method.DeclaringType == typeof(Return)) + return BuildCypherReturnStatement(expression); + throw new ArgumentException(ReturnExpressionCannotBeSerializedToCypherExceptionMessage); } - static string BuildCypherResultItemStatement( + static ExpressionBuild BuildCypherResultItemStatement( MethodCallExpression expression, bool isNullable, CypherCapabilities capabilities, @@ -355,6 +405,7 @@ static string BuildCypherResultItemStatement( ? expression.Method.GetGenericArguments().Single() : null; + CypherResultFormat format = CypherResultFormat.DependsOnEnvironment; switch (methodName) { case "As": @@ -363,15 +414,20 @@ static string BuildCypherResultItemStatement( if (!IsSupportedForAs(singleGenericArgument, jsonConvertersThatTheDeserializerWillUse)) throw new ArgumentException(string.Format(ReturnAsTypeShouldBeOneOfExceptionMessage, singleGenericArgument.Name), "expression"); finalStatement = string.Format("{0}{1}", targetObject.Name, optionalIndicator); + format = methodName == "Node" ? CypherResultFormat.Rest : CypherResultFormat.DependsOnEnvironment; break; case "CollectAs": - if (IsNodeOfT(singleGenericArgument)) - throw new ArgumentException(CollectAsShouldNotBeNodeTExceptionMessage, "expression"); + if (IsNodeOrRelationshipOfT(singleGenericArgument)) + { + format = CypherResultFormat.Rest; + } finalStatement = string.Format("collect({0})", targetObject.Name); break; case "CollectAsDistinct": - if (IsNodeOfT(singleGenericArgument)) - throw new ArgumentException(CollectAsDistinctShouldNotBeNodeTExceptionMessage, "expression"); + if (IsNodeOrRelationshipOfT(singleGenericArgument)) + { + format = CypherResultFormat.Rest; + } finalStatement = string.Format("collect(distinct {0})", targetObject.Name); break; case "Count": @@ -400,13 +456,16 @@ static string BuildCypherResultItemStatement( ? string.Format(statement, finalStatement) : finalStatement; - return statement; + return new ExpressionBuild(statement, format); } - static bool IsNodeOfT(Type type) + static bool IsNodeOrRelationshipOfT(Type type) { if (type == null) throw new ArgumentNullException("type"); - return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Node<>); + return type.IsGenericType && ( + type.GetGenericTypeDefinition() == typeof (Node<>) || + type.GetGenericTypeDefinition() == typeof (Relationship<>) || + type.GetGenericTypeDefinition() == typeof (RelationshipInstance<>)); } static bool IsSupportedForAs(Type type, IEnumerable jsonConvertersThatTheDeserializerWillUse) @@ -503,19 +562,19 @@ class WrappedFunctionCall public Expression InnerExpression { get; set; } } - static string BuildCypherAllStatement(MethodCallExpression expression) + static ExpressionBuild BuildCypherAllStatement(MethodCallExpression expression) { var methodName = expression.Method.Name; switch (methodName) { case "Count": - return "count(*)"; + return new ExpressionBuild("count(*)"); default: throw new InvalidOperationException("Unexpected All method definition, All." + methodName); } } - static string BuildCypherReturnStatement(MethodCallExpression expression) + static ExpressionBuild BuildCypherReturnStatement(MethodCallExpression expression) { var methodName = expression.Method.Name; switch (methodName) @@ -523,7 +582,7 @@ static string BuildCypherReturnStatement(MethodCallExpression expression) case "As": var cypherTextExpression = expression.Arguments.Single(); var cypherText = Expression.Lambda>(cypherTextExpression).Compile()(); - return cypherText; + return new ExpressionBuild(cypherText); default: throw new InvalidOperationException("Unexpected Return method definition, Return." + methodName); } diff --git a/Neo4jClient/Cypher/ICypherFluentQuery.cs b/Neo4jClient/Cypher/ICypherFluentQuery.cs index ee0320eb6..63eff5fd4 100644 --- a/Neo4jClient/Cypher/ICypherFluentQuery.cs +++ b/Neo4jClient/Cypher/ICypherFluentQuery.cs @@ -48,7 +48,7 @@ public partial interface ICypherFluentQuery ICypherFluentQuery Set(string setText); ICypherFluentQuery Remove(string removeText); ICypherFluentQuery ForEach(string text); - ICypherFluentQuery Unwind(string collection, string identity); + ICypherFluentQuery Unwind(string collection, string columnName); ICypherFluentQuery Unwind(IEnumerable collection, string identity); ICypherFluentQuery Union(); ICypherFluentQuery UnionAll(); @@ -59,7 +59,7 @@ public partial interface ICypherFluentQuery ICypherFluentQuery Return(string identity); [Obsolete("This was an internal that never should have been exposed. If you want to create a projection, you should be using the lambda overload instead. See the 'Using Functions in Return Clauses' and 'Using Custom Text in Return Clauses' sections of https://bitbucket.org/Readify/neo4jclient/wiki/cypher for details of how to do this.", true)] ICypherFluentQuery Return(string identity, CypherResultMode resultMode); - ICypherFluentQuery Return(Expression> expression); + ICypherFluentQuery Return(Expression> expression); ICypherFluentQuery Return(Expression> expression); ICypherFluentQuery Return(Expression> expression); ICypherFluentQuery Return(Expression> expression); @@ -77,8 +77,9 @@ public partial interface ICypherFluentQuery ICypherFluentQuery Return(Expression> expression); ICypherFluentQuery Return(Expression> expression); - ICypherFluentQuery ReturnDistinct(string identity); - ICypherFluentQuery ReturnDistinct(Expression> expression); + ICypherFluentQuery ReturnDistinct(string identity); +// ICypherFluentQuery ReturnDistinct(Expression> expression); + ICypherFluentQuery ReturnDistinct(Expression> expression); ICypherFluentQuery ReturnDistinct(Expression> expression); ICypherFluentQuery ReturnDistinct(Expression> expression); ICypherFluentQuery ReturnDistinct(Expression> expression); diff --git a/Neo4jClient/Cypher/ICypherResultItem.cs b/Neo4jClient/Cypher/ICypherResultItem.cs index 84ddf6e4c..e064be99d 100644 --- a/Neo4jClient/Cypher/ICypherResultItem.cs +++ b/Neo4jClient/Cypher/ICypherResultItem.cs @@ -20,13 +20,13 @@ public interface ICypherResultItem /// Equivalent to RETURN collect(foo) /// http://docs.neo4j.org/chunked/stable/query-aggregation.html#aggregation-collect /// - IEnumerable> CollectAs(); + IEnumerable CollectAs(); /// /// Equivalent to RETURN collect(distinct foo) /// http://docs.neo4j.org/chunked/stable/query-aggregation.html#aggregation-collect /// - IEnumerable> CollectAsDistinct(); + IEnumerable CollectAsDistinct(); /// /// Equivalent to RETURN head() diff --git a/Neo4jClient/Cypher/IFluentCypherResultItem.cs b/Neo4jClient/Cypher/IFluentCypherResultItem.cs index 61711c110..6677f1f3a 100644 --- a/Neo4jClient/Cypher/IFluentCypherResultItem.cs +++ b/Neo4jClient/Cypher/IFluentCypherResultItem.cs @@ -2,6 +2,6 @@ { public interface IFluentCypherResultItem { - Node CollectAs(); + T CollectAs(); } } diff --git a/Neo4jClient/Cypher/QueryWriter.cs b/Neo4jClient/Cypher/QueryWriter.cs index 6dd860659..1ca98bc59 100644 --- a/Neo4jClient/Cypher/QueryWriter.cs +++ b/Neo4jClient/Cypher/QueryWriter.cs @@ -11,22 +11,26 @@ public class QueryWriter readonly StringBuilder queryTextBuilder; readonly IDictionary queryParameters; CypherResultMode resultMode; + private CypherResultFormat resultFormat; public QueryWriter() { queryTextBuilder = new StringBuilder(); queryParameters = new Dictionary(); resultMode = CypherResultMode.Set; + resultFormat = CypherResultFormat.DependsOnEnvironment; } QueryWriter( StringBuilder queryTextBuilder, IDictionary queryParameters, - CypherResultMode resultMode) + CypherResultMode resultMode, + CypherResultFormat resultFormat) { this.queryTextBuilder = queryTextBuilder; this.queryParameters = queryParameters; this.resultMode = resultMode; + this.resultFormat = resultFormat; } public CypherResultMode ResultMode @@ -35,11 +39,17 @@ public CypherResultMode ResultMode set { resultMode = value; } } + public CypherResultFormat ResultFormat + { + get { return resultFormat; } + set { resultFormat = value; } + } + public QueryWriter Clone() { var clonedQueryTextBuilder = new StringBuilder(queryTextBuilder.ToString()); var clonedParameters = new Dictionary(queryParameters); - return new QueryWriter(clonedQueryTextBuilder, clonedParameters, resultMode); + return new QueryWriter(clonedQueryTextBuilder, clonedParameters, resultMode, resultFormat); } public CypherQuery ToCypherQuery(IContractResolver contractResolver = null) @@ -51,8 +61,9 @@ public CypherQuery ToCypherQuery(IContractResolver contractResolver = null) return new CypherQuery( queryText, new Dictionary(queryParameters), - resultMode, - contractResolver); + resultMode, + resultFormat, + contractResolver); } public string CreateParameter(object paramValue) diff --git a/Neo4jClient/Cypher/ReturnExpression.cs b/Neo4jClient/Cypher/ReturnExpression.cs index 97e6bfb3a..b84662bec 100644 --- a/Neo4jClient/Cypher/ReturnExpression.cs +++ b/Neo4jClient/Cypher/ReturnExpression.cs @@ -4,5 +4,6 @@ public struct ReturnExpression { public string Text { get; set; } public CypherResultMode ResultMode { get; set; } + public CypherResultFormat ResultFormat { get; set; } } } diff --git a/Neo4jClient/CypherPartialResult.cs b/Neo4jClient/CypherPartialResult.cs new file mode 100644 index 000000000..744fdf0d1 --- /dev/null +++ b/Neo4jClient/CypherPartialResult.cs @@ -0,0 +1,17 @@ +using System.Net.Http; +using Neo4jClient.Serialization; + +namespace Neo4jClient +{ + /// + /// This class is only used to hold partial results on the execution of a cypher query. + /// Depending on whether we are running inside a transaction, the result string is already + /// deserialized (because it has to be checked for errors), or we need the full HttpResponseMessage + /// object. + /// + internal class CypherPartialResult + { + public HttpResponseMessage ResponseObject { get; set; } + public PartialDeserializationContext DeserializationContext { get; set; } + } +} diff --git a/Neo4jClient/Execution/BatchExecutionPolicy.cs b/Neo4jClient/Execution/BatchExecutionPolicy.cs new file mode 100644 index 000000000..f7ee315ca --- /dev/null +++ b/Neo4jClient/Execution/BatchExecutionPolicy.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Execution +{ + internal class BatchExecutionPolicy : GraphClientBasedExecutionPolicy + { + public BatchExecutionPolicy(IGraphClient client) : base(client) + { + } + + public override TransactionExecutionPolicy TransactionExecutionPolicy + { + get { return TransactionExecutionPolicy.Denied; } + } + + public override void AfterExecution(IDictionary executionMetadata, object executionContext) + { + } + + public override Uri BaseEndpoint + { + get { return Client.BatchEndpoint; } + } + } +} diff --git a/Neo4jClient/Execution/CypherExecutionPolicy.cs b/Neo4jClient/Execution/CypherExecutionPolicy.cs new file mode 100644 index 000000000..f9efe5892 --- /dev/null +++ b/Neo4jClient/Execution/CypherExecutionPolicy.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; +using Neo4jClient.Transactions; + +namespace Neo4jClient.Execution +{ + /// + /// Describes the behavior for a cypher execution. + /// + internal class CypherExecutionPolicy : GraphClientBasedExecutionPolicy + { + public CypherExecutionPolicy(IGraphClient client) : base(client) + { + } + + private INeo4jTransaction GetTransactionInScope() + { + // first try to get the Non DTC transaction and if it doesn't succeed then try it with the DTC + var transactionalClient = Client as IInternalTransactionalGraphClient; + if (transactionalClient == null) + { + return null; + } + + var proxiedTransaction = transactionalClient.Transaction as TransactionScopeProxy; + if (proxiedTransaction != null) + { + return proxiedTransaction.TransactionContext;; + } + + var ambientTransaction = transactionalClient.TransactionManager.CurrentDtcTransaction; + if (ambientTransaction != null) + { + return (INeo4jTransaction) ambientTransaction; + } + + return null; + } + + public override Uri BaseEndpoint + { + get + { + if (!InTransaction) + { + return Client.CypherEndpoint; + } + + var proxiedTransaction = GetTransactionInScope(); + var transactionalClient = (ITransactionalGraphClient) Client; + if (proxiedTransaction == null) + { + return transactionalClient.TransactionEndpoint; + } + + var startingReference = proxiedTransaction.Endpoint ?? transactionalClient.TransactionEndpoint; + return startingReference; + } + } + + public override TransactionExecutionPolicy TransactionExecutionPolicy + { + get { return TransactionExecutionPolicy.Allowed; } + } + + public override string SerializeRequest(object toSerialize) + { + var query = toSerialize as CypherQuery; + if (query == null) + { + throw new InvalidOperationException( + "Unsupported operation: Attempting to serialize something that was not a query."); + } + + if (InTransaction) + { + return Client + .Serializer + .Serialize(new CypherStatementList + { + new CypherTransactionStatement(query, query.ResultFormat == CypherResultFormat.Rest) + }); + } + return Client.Serializer.Serialize(new CypherApiQuery(query)); + } + + public override void AfterExecution(IDictionary executionMetadata, object executionContext) + { + if (Client == null || executionMetadata == null || executionMetadata.Count == 0) + { + return; + } + + // determine if we need to update the transaction end point + var transaction = executionContext as INeo4jTransaction; + if (transaction == null || transaction.Endpoint != null) + { + return; + } + + var locationHeader = executionMetadata["Location"] as IEnumerable; + if (locationHeader == null) + { + return; + } + + var generatedEndpoint = locationHeader.FirstOrDefault(); + if (!string.IsNullOrEmpty(generatedEndpoint)) + { + transaction.Endpoint = new Uri(generatedEndpoint); + } + } + + } +} diff --git a/Neo4jClient/Execution/CypherTransactionExecutionPolicy.cs b/Neo4jClient/Execution/CypherTransactionExecutionPolicy.cs new file mode 100644 index 000000000..4cb12b25d --- /dev/null +++ b/Neo4jClient/Execution/CypherTransactionExecutionPolicy.cs @@ -0,0 +1,15 @@ +namespace Neo4jClient.Execution +{ + internal class CypherTransactionExecutionPolicy : CypherExecutionPolicy + { + public CypherTransactionExecutionPolicy(IGraphClient client) + : base(client) + { + } + + public override TransactionExecutionPolicy TransactionExecutionPolicy + { + get { return TransactionExecutionPolicy.Required; } + } + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/EndpointBuilderExtension.cs b/Neo4jClient/Execution/EndpointBuilderExtension.cs new file mode 100644 index 000000000..d6a1ea449 --- /dev/null +++ b/Neo4jClient/Execution/EndpointBuilderExtension.cs @@ -0,0 +1,40 @@ +using System; + +namespace Neo4jClient.Execution +{ + /// + /// Restricts the end point URI builder to adding paths + /// + internal static class EndpointBuilderExtension + { + public static Uri AddPath(this Uri startUri, string path) + { + var uriBuilder = new UriBuilder(startUri); + if (path.StartsWith("/") || uriBuilder.Path.EndsWith("/")) + { + uriBuilder.Path += path; + } + else + { + uriBuilder.Path += "/" + path; + } + return uriBuilder.Uri; + } + + public static Uri AddQuery(this Uri startUri, string query) + { + var uriBuilder = new UriBuilder(startUri); + if (uriBuilder.Query != null && uriBuilder.Query.Length > 1) + uriBuilder.Query = uriBuilder.Query.Substring(1) + "&" + query; + else + uriBuilder.Query = query; + return uriBuilder.Uri; + } + + public static Uri AddPath(this Uri startUri, object startReference, IExecutionPolicy policy) + { + return policy.AddPath(startUri, startReference); + } + + } +} diff --git a/Neo4jClient/Execution/ErrorGenerator.cs b/Neo4jClient/Execution/ErrorGenerator.cs new file mode 100644 index 000000000..6e2f3b897 --- /dev/null +++ b/Neo4jClient/Execution/ErrorGenerator.cs @@ -0,0 +1,11 @@ +using System; +using System.Net.Http; + +namespace Neo4jClient.Execution +{ + internal class ErrorGenerator + { + public Func Condition { get; set; } + public Func Generator { get; set; } + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/ExecutionConfiguration.cs b/Neo4jClient/Execution/ExecutionConfiguration.cs new file mode 100644 index 000000000..88f0a49cd --- /dev/null +++ b/Neo4jClient/Execution/ExecutionConfiguration.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Neo4jClient.Execution +{ + + public class ExecutionConfiguration + { + public IHttpClient HttpClient { get; set; } + public bool UseJsonStreaming { get; set; } + public string UserAgent { get; set; } + public IEnumerable JsonConverters { get; set; } + public string Username { get; set; } + public string Password { get; set; } + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/ExecutionPolicyFactory.cs b/Neo4jClient/Execution/ExecutionPolicyFactory.cs new file mode 100644 index 000000000..66ffb63f4 --- /dev/null +++ b/Neo4jClient/Execution/ExecutionPolicyFactory.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Execution +{ + class ExecutionPolicyFactory : IExecutionPolicyFactory + { + private IGraphClient _client; + + public ExecutionPolicyFactory(IGraphClient client) + { + _client = client; + } + + public IExecutionPolicy GetPolicy(PolicyType type) + { + if (!_client.IsConnected) + { + throw new InvalidOperationException("Client has not connected to the Neo4j server"); + } + + // todo: this should be a prototype-based object creation + switch (type) + { + case PolicyType.Cypher: + return new CypherExecutionPolicy(_client); + case PolicyType.Gremlin: + return new GremlinExecutionPolicy(_client); + case PolicyType.Batch: + return new BatchExecutionPolicy(_client); + case PolicyType.Rest: + return new RestExecutionPolicy(_client); + case PolicyType.Transaction: + return new CypherTransactionExecutionPolicy(_client); + case PolicyType.NodeIndex: + return new NodeIndexExecutionPolicy(_client); + case PolicyType.RelationshipIndex: + return new RelationshipIndexExecutionPolicy(_client); + default: + throw new InvalidOperationException("Unknown execution policy:" + type.ToString()); + } + } + } +} diff --git a/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs b/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs new file mode 100644 index 000000000..1626dd318 --- /dev/null +++ b/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs @@ -0,0 +1,43 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Transactions; +using Neo4jClient.Transactions; + +namespace Neo4jClient.Execution +{ + internal abstract class GraphClientBasedExecutionPolicy : IExecutionPolicy + { + protected IGraphClient Client; + + protected GraphClientBasedExecutionPolicy(IGraphClient client) + { + Client = client; + } + + public bool InTransaction + { + get + { + var transactionalGraphClient = Client as ITransactionalGraphClient; + return transactionalGraphClient != null && + (transactionalGraphClient.InTransaction || Transaction.Current != null); + } + } + + public abstract TransactionExecutionPolicy TransactionExecutionPolicy { get; } + public abstract void AfterExecution(IDictionary executionMetadata, object executionContext); + + public virtual string SerializeRequest(object toSerialize) + { + return Client.Serializer.Serialize(toSerialize); + } + + public abstract Uri BaseEndpoint { get; } + public virtual Uri AddPath(Uri startUri, object startReference) + { + return startUri; + } + } +} diff --git a/Neo4jClient/Execution/GremlinExecutionPolicy.cs b/Neo4jClient/Execution/GremlinExecutionPolicy.cs new file mode 100644 index 000000000..0f451f89f --- /dev/null +++ b/Neo4jClient/Execution/GremlinExecutionPolicy.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Execution +{ + internal class GremlinExecutionPolicy : GraphClientBasedExecutionPolicy + { + public GremlinExecutionPolicy(IGraphClient client) : base(client) + { + } + + public override TransactionExecutionPolicy TransactionExecutionPolicy + { + get { return TransactionExecutionPolicy.Denied; } + } + + public override void AfterExecution(IDictionary executionMetadata, object executionContext) + { + } + + public override Uri BaseEndpoint + { + get { return Client.GremlinEndpoint; } + } + } +} diff --git a/Neo4jClient/HttpResponseMessageExtensions.cs b/Neo4jClient/Execution/HttpResponseMessageExtensions.cs similarity index 95% rename from Neo4jClient/HttpResponseMessageExtensions.cs rename to Neo4jClient/Execution/HttpResponseMessageExtensions.cs index 4f87d7eaf..6c5d5b21c 100644 --- a/Neo4jClient/HttpResponseMessageExtensions.cs +++ b/Neo4jClient/Execution/HttpResponseMessageExtensions.cs @@ -1,63 +1,63 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -using Neo4jClient.ApiModels; -using Newtonsoft.Json; - -namespace Neo4jClient -{ - internal static class HttpResponseMessageExtensions - { - internal static void EnsureExpectedStatusCode(this HttpResponseMessage response, params HttpStatusCode[] expectedStatusCodes) - { - response.EnsureExpectedStatusCode(null, expectedStatusCodes); - } - - internal static void EnsureExpectedStatusCode(this HttpResponseMessage response, string commandDescription, params HttpStatusCode[] expectedStatusCodes) - { - if (expectedStatusCodes.Contains(response.StatusCode)) - return; - - if (response.StatusCode == HttpStatusCode.BadRequest) - { - var neoException = TryBuildNeoException(response); - if (neoException != null) throw neoException; - } - - commandDescription = string.IsNullOrWhiteSpace(commandDescription) - ? "" - : commandDescription + "\r\n\r\n"; - - var rawBody = string.Empty; - if (response.Content != null) - { - var readTask = response.Content.ReadAsStringAsync(); - readTask.Wait(); - var rawContent = readTask.Result; - rawBody = string.Format("\r\n\r\nThe response from Neo4j (which might include useful detail!) was: {0}", rawContent); - } - - throw new ApplicationException(string.Format( - "Received an unexpected HTTP status when executing the request.\r\n\r\n{0}The response status was: {1} {2}{3}", - commandDescription, - (int)response.StatusCode, - response.ReasonPhrase, - rawBody)); - } - - static NeoException TryBuildNeoException(HttpResponseMessage response) - { - var isJson = response.Content.Headers.ContentType.MediaType.Equals("application/json", StringComparison.InvariantCulture); - if (!isJson) return null; - - var exceptionResponse = response.Content.ReadAsJson(new JsonConverter[0]); - - if (string.IsNullOrEmpty(exceptionResponse.Message) || - string.IsNullOrEmpty(exceptionResponse.Exception)) - return null; - - return new NeoException(exceptionResponse); - } - } -} +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using Neo4jClient.ApiModels; +using Newtonsoft.Json; + +namespace Neo4jClient.Execution +{ + internal static class HttpResponseMessageExtensions + { + internal static void EnsureExpectedStatusCode(this HttpResponseMessage response, params HttpStatusCode[] expectedStatusCodes) + { + response.EnsureExpectedStatusCode(null, expectedStatusCodes); + } + + internal static void EnsureExpectedStatusCode(this HttpResponseMessage response, string commandDescription, params HttpStatusCode[] expectedStatusCodes) + { + if (expectedStatusCodes.Contains(response.StatusCode)) + return; + + if (response.StatusCode == HttpStatusCode.BadRequest) + { + var neoException = TryBuildNeoException(response); + if (neoException != null) throw neoException; + } + + commandDescription = string.IsNullOrWhiteSpace(commandDescription) + ? "" + : commandDescription + "\r\n\r\n"; + + var rawBody = string.Empty; + if (response.Content != null) + { + var readTask = response.Content.ReadAsStringAsync(); + readTask.Wait(); + var rawContent = readTask.Result; + rawBody = string.Format("\r\n\r\nThe response from Neo4j (which might include useful detail!) was: {0}", rawContent); + } + + throw new ApplicationException(string.Format( + "Received an unexpected HTTP status when executing the request.\r\n\r\n{0}The response status was: {1} {2}{3}", + commandDescription, + (int)response.StatusCode, + response.ReasonPhrase, + rawBody)); + } + + static NeoException TryBuildNeoException(HttpResponseMessage response) + { + var isJson = response.Content.Headers.ContentType.MediaType.Equals("application/json", StringComparison.InvariantCulture); + if (!isJson) return null; + + var exceptionResponse = response.Content.ReadAsJson(new JsonConverter[0]); + + if (string.IsNullOrEmpty(exceptionResponse.Message) || + string.IsNullOrEmpty(exceptionResponse.Exception)) + return null; + + return new NeoException(exceptionResponse); + } + } +} diff --git a/Neo4jClient/Execution/IExecutionPolicy.cs b/Neo4jClient/Execution/IExecutionPolicy.cs new file mode 100644 index 000000000..e64d5442f --- /dev/null +++ b/Neo4jClient/Execution/IExecutionPolicy.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; + +namespace Neo4jClient.Execution +{ + internal interface IExecutionPolicy + { + bool InTransaction { get; } + TransactionExecutionPolicy TransactionExecutionPolicy { get; } + void AfterExecution(IDictionary executionMetadata, object executionContext); + string SerializeRequest(object toSerialize); + Uri BaseEndpoint { get; } + Uri AddPath(Uri startUri, object startReference); + } + + internal enum TransactionExecutionPolicy + { + Allowed, + Denied, + Required + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/IExecutionPolicyFactory.cs b/Neo4jClient/Execution/IExecutionPolicyFactory.cs new file mode 100644 index 000000000..161825ee5 --- /dev/null +++ b/Neo4jClient/Execution/IExecutionPolicyFactory.cs @@ -0,0 +1,24 @@ +namespace Neo4jClient.Execution +{ + /// + /// A factory class that returns a policy factory given the type and a IGraphClient connection. + /// + internal interface IExecutionPolicyFactory + { + IExecutionPolicy GetPolicy(PolicyType type); + } + + /// + /// Possible enumerations of queries that a policy may represent + /// + public enum PolicyType + { + Cypher, + Gremlin, + Rest, + Batch, + Transaction, + NodeIndex, + RelationshipIndex + } +} \ No newline at end of file diff --git a/Neo4jClient/IHttpClient.cs b/Neo4jClient/Execution/IHttpClient.cs similarity index 64% rename from Neo4jClient/IHttpClient.cs rename to Neo4jClient/Execution/IHttpClient.cs index 8ae567dd2..585361576 100644 --- a/Neo4jClient/IHttpClient.cs +++ b/Neo4jClient/Execution/IHttpClient.cs @@ -1,10 +1,12 @@ -using System.Net.Http; -using System.Threading.Tasks; - -namespace Neo4jClient -{ - public interface IHttpClient - { - Task SendAsync(HttpRequestMessage request); - } -} +using System.Net.Http; +using System.Threading.Tasks; + +namespace Neo4jClient.Execution +{ + public interface IHttpClient + { + Task SendAsync(HttpRequestMessage request); + string Username { get; } + string Password { get; } + } +} diff --git a/Neo4jClient/Execution/IRequestTypeBuilder.cs b/Neo4jClient/Execution/IRequestTypeBuilder.cs new file mode 100644 index 000000000..900335444 --- /dev/null +++ b/Neo4jClient/Execution/IRequestTypeBuilder.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Execution +{ + internal interface IRequestTypeBuilder + { + IResponseBuilder Delete(Uri endpoint); + IResponseBuilder Get(Uri endpoint); + IRequestWithPendingContentBuilder Post(Uri endpoint); + IRequestWithPendingContentBuilder Put(Uri endpoint); + } +} diff --git a/Neo4jClient/Execution/IRequestWithPendingContentBuilder.cs b/Neo4jClient/Execution/IRequestWithPendingContentBuilder.cs new file mode 100644 index 000000000..c28b78b2e --- /dev/null +++ b/Neo4jClient/Execution/IRequestWithPendingContentBuilder.cs @@ -0,0 +1,8 @@ +namespace Neo4jClient.Execution +{ + internal interface IRequestWithPendingContentBuilder + { + IResponseBuilder WithContent(string content); + IResponseBuilder WithJsonContent(string jsonContent); + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/IResponseBuilder.cs b/Neo4jClient/Execution/IResponseBuilder.cs new file mode 100644 index 000000000..eaf7bda00 --- /dev/null +++ b/Neo4jClient/Execution/IResponseBuilder.cs @@ -0,0 +1,33 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Neo4jClient.Execution +{ + internal interface IResponseBuilder + { + IResponseBuilder WithExpectedStatusCodes(params HttpStatusCode[] statusCodes); + IResponseFailBuilder FailOnCondition(Func condition); + Task ExecuteAsync(); + Task ExecuteAsync(string commandDescription); + Task ExecuteAsync(Func, HttpResponseMessage> continuationFunction); + Task ExecuteAsync(string commandDescription, Func, HttpResponseMessage> continuationFunction); + Task ExecuteAsync(string commandDescription, Func, HttpResponseMessage> continuationFunction, TaskFactory taskFactory); + + Task ExecuteAsync(Func, TExpected> continuationFunction); + Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction); + Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction, TaskFactory taskFactory); + + HttpResponseMessage Execute(); + HttpResponseMessage Execute(string commandDescription); + HttpResponseMessage Execute(string commandDescription, TaskFactory taskFactory); + IResponseBuilder ParseAs() where TParse : new(); + } + + internal interface IResponseFailBuilder + { + IResponseBuilder WithError(Func errorBuilder); + IResponseBuilder WithNull(); + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/IResponseBuilder`TResult.cs b/Neo4jClient/Execution/IResponseBuilder`TResult.cs new file mode 100644 index 000000000..9d7abe481 --- /dev/null +++ b/Neo4jClient/Execution/IResponseBuilder`TResult.cs @@ -0,0 +1,29 @@ +using System; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Execution +{ + internal interface IResponseBuilder where TResult : new() + { + IResponseBuilder WithExpectedStatusCodes(params HttpStatusCode[] statusCodes); + IResponseFailBuilder FailOnCondition(Func condition); + Task ExecuteAsync(string commandDescription); + Task ExecuteAsync(Func, TResult> continuationFunction); + Task ExecuteAsync(string commandDescription, Func, TResult> continuationFunction); + Task ExecuteAsync(string commandDescription, + Func, TResult> continuationFunction, TaskFactory taskFactory); + Task ExecuteAsync(); + + Task ExecuteAsync(Func, TExpected> continuationFunction); + Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction); + Task ExecuteAsync(string commandDescription, + Func, TExpected> continuationFunction, TaskFactory taskFactory); + + TResult Execute(string commandDescription, TaskFactory taskFactory); + TResult Execute(string commandDescription); + TResult Execute(); + } +} diff --git a/Neo4jClient/Execution/IResponseFailBuilder.cs b/Neo4jClient/Execution/IResponseFailBuilder.cs new file mode 100644 index 000000000..24bb7fd44 --- /dev/null +++ b/Neo4jClient/Execution/IResponseFailBuilder.cs @@ -0,0 +1,11 @@ +using System; +using System.Net.Http; + +namespace Neo4jClient.Execution +{ + internal interface IResponseFailBuilder where TResult : new() + { + IResponseBuilder WithError(Func errorBuilder); + IResponseBuilder WithDefault(); + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/NodeIndexExecutionPolicy.cs b/Neo4jClient/Execution/NodeIndexExecutionPolicy.cs new file mode 100644 index 000000000..98b8d3102 --- /dev/null +++ b/Neo4jClient/Execution/NodeIndexExecutionPolicy.cs @@ -0,0 +1,17 @@ +using System; + +namespace Neo4jClient.Execution +{ + internal class NodeIndexExecutionPolicy : RestExecutionPolicy + { + public NodeIndexExecutionPolicy(IGraphClient client) + : base(client) + { + } + + public override Uri BaseEndpoint + { + get { return Client.NodeIndexEndpoint; } + } + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/RelationshipIndexExecutionPolicy.cs b/Neo4jClient/Execution/RelationshipIndexExecutionPolicy.cs new file mode 100644 index 000000000..4b87538f8 --- /dev/null +++ b/Neo4jClient/Execution/RelationshipIndexExecutionPolicy.cs @@ -0,0 +1,17 @@ +using System; + +namespace Neo4jClient.Execution +{ + internal class RelationshipIndexExecutionPolicy : RestExecutionPolicy + { + public RelationshipIndexExecutionPolicy(IGraphClient client) + : base(client) + { + } + + public override Uri BaseEndpoint + { + get { return Client.RelationshipIndexEndpoint; } + } + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/Request.cs b/Neo4jClient/Execution/Request.cs new file mode 100644 index 000000000..f19e16090 --- /dev/null +++ b/Neo4jClient/Execution/Request.cs @@ -0,0 +1,10 @@ +namespace Neo4jClient.Execution +{ + internal static class Request + { + public static IRequestTypeBuilder With(ExecutionConfiguration configuration) + { + return new RequestTypeBuilder(configuration); + } + } +} diff --git a/Neo4jClient/Execution/RequestTypeBuilder.cs b/Neo4jClient/Execution/RequestTypeBuilder.cs new file mode 100644 index 000000000..8e149ac8f --- /dev/null +++ b/Neo4jClient/Execution/RequestTypeBuilder.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; + +namespace Neo4jClient.Execution +{ + internal class RequestTypeBuilder : IRequestTypeBuilder + { + private readonly ExecutionConfiguration _executionConfiguration; + + public RequestTypeBuilder(ExecutionConfiguration executionConfiguration) + { + _executionConfiguration = executionConfiguration; + } + + public IResponseBuilder Delete(Uri endpoint) + { + return new ResponseBuilder(new HttpRequestMessage(HttpMethod.Delete, endpoint), _executionConfiguration); + } + + public IResponseBuilder Get(Uri endpoint) + { + return new ResponseBuilder(new HttpRequestMessage(HttpMethod.Get, endpoint), _executionConfiguration); + } + + public IRequestWithPendingContentBuilder Post(Uri endpoint) + { + return new RequestWithPendingContentBuilder(HttpMethod.Post, endpoint, _executionConfiguration); + } + + public IRequestWithPendingContentBuilder Put(Uri endpoint) + { + return new RequestWithPendingContentBuilder(HttpMethod.Put, endpoint, _executionConfiguration); + } + } + + +} diff --git a/Neo4jClient/Execution/RequestWithPendingContentBuilder.cs b/Neo4jClient/Execution/RequestWithPendingContentBuilder.cs new file mode 100644 index 000000000..90eb9ac0e --- /dev/null +++ b/Neo4jClient/Execution/RequestWithPendingContentBuilder.cs @@ -0,0 +1,42 @@ +using System; +using System.Net.Http; +using System.Text; + +namespace Neo4jClient.Execution +{ + internal class RequestWithPendingContentBuilder : IRequestWithPendingContentBuilder + { + private readonly HttpMethod _httpMethod; + private readonly Uri _endpoint; + private readonly ExecutionConfiguration _executionConfiguration; + + public RequestWithPendingContentBuilder(HttpMethod httpMethod, Uri endpoint, ExecutionConfiguration executionConfiguration) + { + _httpMethod = httpMethod; + _endpoint = endpoint; + _executionConfiguration = executionConfiguration; + } + + public IResponseBuilder WithContent(string content) + { + return new ResponseBuilder( + new HttpRequestMessage(_httpMethod, _endpoint) + { + Content = new StringContent(content, Encoding.UTF8) + }, + _executionConfiguration + ); + } + + public IResponseBuilder WithJsonContent(string jsonContent) + { + return new ResponseBuilder( + new HttpRequestMessage(_httpMethod, _endpoint) + { + Content = new StringContent(jsonContent, Encoding.UTF8, "application/json") + }, + _executionConfiguration + ); + } + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/ResponseBuilder.cs b/Neo4jClient/Execution/ResponseBuilder.cs new file mode 100644 index 000000000..25e983ade --- /dev/null +++ b/Neo4jClient/Execution/ResponseBuilder.cs @@ -0,0 +1,203 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; + +namespace Neo4jClient.Execution +{ + internal class ResponseBuilder : IResponseBuilder + { + protected readonly HttpRequestMessage _request; + protected readonly ExecutionConfiguration _executionConfiguration; + protected readonly ISet _expectedStatusCodes; + protected readonly Func _errorCondition; + protected readonly Func _errorGenerator; + protected readonly IList _errorGenerators; + + public ISet ExpectedStatusCodes + { + get { return _expectedStatusCodes; } + } + + public IList ErrorGenerators + { + get { return _errorGenerators; } + } + + public ResponseBuilder(HttpRequestMessage request, ExecutionConfiguration executionConfiguration) + : this(request, new HashSet(), executionConfiguration) + { + } + + public ResponseBuilder(HttpRequestMessage request, ISet expectedStatusCodes, ExecutionConfiguration executionConfiguration) : + this(request, expectedStatusCodes, executionConfiguration, new List()) + { + } + + public ResponseBuilder(HttpRequestMessage request, ISet expectedStatusCodes, + ExecutionConfiguration executionConfiguration, IList errorGenerators) + { + _request = request; + _expectedStatusCodes = expectedStatusCodes; + _executionConfiguration = executionConfiguration; + _errorGenerators = errorGenerators; + } + + protected ISet UnionStatusCodes( + IEnumerable source1, + IEnumerable source2 + ) + { + var expectedStatusCodes = new HashSet(source1); + expectedStatusCodes.UnionWith(source2); + return expectedStatusCodes; + } + + public IResponseBuilder WithExpectedStatusCodes(params HttpStatusCode[] statusCodes) + { + return new ResponseBuilder(_request, UnionStatusCodes(_expectedStatusCodes, statusCodes), + _executionConfiguration, _errorGenerators); + } + + public IResponseFailBuilder FailOnCondition(Func condition) + { + return new ResponseFailBuilder(_request, _expectedStatusCodes, _executionConfiguration, _errorGenerators, + condition); + } + + private Task PrepareAsync(TaskFactory taskFactory) + { + if (_executionConfiguration.UseJsonStreaming) + { + _request.Headers.Accept.Clear(); + _request.Headers.Remove("Accept"); + _request.Headers.Add("Accept", "application/json;stream=true"); + } + + _request.Headers.Add("User-Agent", _executionConfiguration.UserAgent); + + var userInfo = _request.RequestUri.UserInfo; + if (!string.IsNullOrEmpty(userInfo)) + { + var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(userInfo)); + _request.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials); + } + + if (taskFactory == null) + { + // use the standard factory + return _executionConfiguration.HttpClient.SendAsync(_request); + } + + // use a custom task factory + return taskFactory.StartNew(() => _executionConfiguration.HttpClient.SendAsync(_request).Result); + } + + public Task ExecuteAsync() + { + return ExecuteAsync(null, null, null); + } + + public Task ExecuteAsync(string commandDescription) + { + return ExecuteAsync(commandDescription, null, null); + } + + public Task ExecuteAsync(string commandDescription, Func, HttpResponseMessage> continuationFunction) + { + return ExecuteAsync(commandDescription, continuationFunction, null); + } + + public Task ExecuteAsync(Func, HttpResponseMessage> continuationFunction) + { + return ExecuteAsync(null, continuationFunction, null); + } + + public Task ExecuteAsync(string commandDescription, Func, HttpResponseMessage> continuationFunction, TaskFactory taskFactory) + { + var executionTask = PrepareAsync(taskFactory).ContinueWith(requestTask => + { + var response = requestTask.Result; + if (string.IsNullOrEmpty(commandDescription)) + { + response.EnsureExpectedStatusCode(_expectedStatusCodes.ToArray()); + } + else + { + response.EnsureExpectedStatusCode(commandDescription, _expectedStatusCodes.ToArray()); + } + + // if there is condition for an error, but its generator is null then return null + // for generics this will get converted to default(TParse) + foreach (var errorGenerator in _errorGenerators) + { + if (errorGenerator.Condition(response)) + { + if (errorGenerator.Generator != null) + { + throw errorGenerator.Generator(response); + } + + return null; + } + } + + return response; + }); + + return continuationFunction != null ? + executionTask.ContinueWith(continuationFunction) : + executionTask; + } + + public Task ExecuteAsync(Func, TExpected> continuationFunction) + { + return ExecuteAsync(null, continuationFunction); + } + + public Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction) + { + return ExecuteAsync(commandDescription, continuationFunction, null); + } + + public Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction, TaskFactory taskFactory) + { + return ExecuteAsync(commandDescription, null, taskFactory).ContinueWith(continuationFunction); + } + + public HttpResponseMessage Execute() + { + return Execute(null, null); + } + + public HttpResponseMessage Execute(string commandDescription) + { + return Execute(commandDescription, null); + } + + public HttpResponseMessage Execute(string commandDescription, TaskFactory taskFactory) + { + var task = ExecuteAsync(commandDescription, null, taskFactory); + try + { + Task.WaitAll(task); + } + catch (AggregateException ex) + { + if (ex.InnerExceptions.Count() == 1) + throw ex.InnerExceptions.Single(); + throw; + } + return task.Result; + } + + public IResponseBuilder ParseAs() where TParse : new() + { + return new ResponseBuilder(_request, _expectedStatusCodes, _executionConfiguration); + } + } +} diff --git a/Neo4jClient/Execution/ResponseBuilder`TParse.cs b/Neo4jClient/Execution/ResponseBuilder`TParse.cs new file mode 100644 index 000000000..57e7dc780 --- /dev/null +++ b/Neo4jClient/Execution/ResponseBuilder`TParse.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +namespace Neo4jClient.Execution +{ + internal class ResponseBuilder + : ResponseBuilder, IResponseBuilder where TParse : new() + { + public ResponseBuilder(HttpRequestMessage request, ISet expectedStatusCodes, + ExecutionConfiguration executionConfiguration) + : this(request, expectedStatusCodes, executionConfiguration, new List()) + { + } + + public ResponseBuilder(HttpRequestMessage request, ISet expectedStatusCodes, + ExecutionConfiguration executionConfiguration, IList errorGenerators) + : base(request, expectedStatusCodes, executionConfiguration, errorGenerators) + { + } + + private TParse CastIntoResult(HttpResponseMessage response) + { + return response == null || response.Content == null ? + default(TParse) : + response.Content.ReadAsJson(_executionConfiguration.JsonConverters); + } + + public new IResponseBuilder WithExpectedStatusCodes(params HttpStatusCode[] statusCodes) + { + return new ResponseBuilder(_request, UnionStatusCodes(_expectedStatusCodes, statusCodes), _executionConfiguration); + } + + public new IResponseFailBuilder FailOnCondition(Func condition) + { + return new ResponseFailBuilder(_request, _expectedStatusCodes, _executionConfiguration, ErrorGenerators, condition); + } + + public new Task ExecuteAsync(string commandDescription) + { + return ExecuteAsync(commandDescription, null, null); + } + + public Task ExecuteAsync(Func, TParse> continuationFunction) + { + return ExecuteAsync(null, continuationFunction); + } + + public Task ExecuteAsync(string commandDescription, Func, TParse> continuationFunction) + { + return ExecuteAsync(commandDescription, continuationFunction, null); + } + + public Task ExecuteAsync(string commandDescription, Func, TParse> continuationFunction, TaskFactory taskFactory) + { + var executionTask = base.ExecuteAsync(commandDescription, null, taskFactory) + .ContinueWith( + responseAction => + responseAction.Result == null ? default(TParse) : CastIntoResult(responseAction.Result)); + return continuationFunction == null ? executionTask : executionTask.ContinueWith(continuationFunction); + } + + public new Task ExecuteAsync() + { + return ExecuteAsync(null, null, null); + } + + public Task ExecuteAsync(Func, TExpected> continuationFunction) + { + return ExecuteAsync(null, continuationFunction); + } + + public Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction) + { + return ExecuteAsync(commandDescription).ContinueWith(continuationFunction); + } + + public Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction, TaskFactory taskFactory) + { + throw new NotImplementedException(); + } + + public new TParse Execute(string commandDescription) + { + return Execute(commandDescription, null); + } + + public new TParse Execute(string commandDescription, TaskFactory taskFactory) + { + return CastIntoResult(base.Execute(commandDescription, taskFactory)); + } + + public new TParse Execute() + { + return Execute(null); + } + } +} diff --git a/Neo4jClient/Execution/ResponseFailBuilder.cs b/Neo4jClient/Execution/ResponseFailBuilder.cs new file mode 100644 index 000000000..9e0d832ae --- /dev/null +++ b/Neo4jClient/Execution/ResponseFailBuilder.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; + +namespace Neo4jClient.Execution +{ + internal class ResponseFailBuilder : IResponseFailBuilder + { + private readonly HttpRequestMessage _request; + private readonly ISet _expectedStatusCodes; + private readonly ExecutionConfiguration _executionConfiguration; + private readonly IList _errorGenerators; + private readonly Func _errorCondition; + + public ResponseFailBuilder(HttpRequestMessage request, ISet expectedStatusCodes, + ExecutionConfiguration executionConfiguration, IList errorGenerators, + Func errorCondition) + { + _request = request; + _expectedStatusCodes = expectedStatusCodes; + _executionConfiguration = executionConfiguration; + _errorGenerators = errorGenerators; + _errorCondition = errorCondition; + } + + public IResponseBuilder WithError(Func errorBuilder) + { + var newGenerators = new List(_errorGenerators) + { + new ErrorGenerator + { + Condition = _errorCondition, + Generator = errorBuilder + } + }; + + return new ResponseBuilder( + _request, + _expectedStatusCodes, + _executionConfiguration, + newGenerators + ); + } + + public IResponseBuilder WithNull() + { + var newGenerators = new List(_errorGenerators) + { + new ErrorGenerator + { + Condition = _errorCondition, + Generator = null + } + }; + + return new ResponseBuilder( + _request, + _expectedStatusCodes, + _executionConfiguration, + newGenerators + ); + } + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/ResponseFailBuilder`TParse.cs b/Neo4jClient/Execution/ResponseFailBuilder`TParse.cs new file mode 100644 index 000000000..e3fd03199 --- /dev/null +++ b/Neo4jClient/Execution/ResponseFailBuilder`TParse.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; + +namespace Neo4jClient.Execution +{ + internal class ResponseFailBuilder : IResponseFailBuilder where TParse : new() + { + private readonly HttpRequestMessage _request; + private readonly ISet _expectedStatusCodes; + private readonly ExecutionConfiguration _executionConfiguration; + private readonly IList _errorGenerators; + private readonly Func _errorCondition; + + public ResponseFailBuilder(HttpRequestMessage request, ISet expectedStatusCodes, + ExecutionConfiguration executionConfiguration, IList errorGenerators, + Func condition) + { + _request = request; + _expectedStatusCodes = expectedStatusCodes; + _executionConfiguration = executionConfiguration; + _errorGenerators = errorGenerators; + _errorCondition = condition; + } + + public IResponseBuilder WithError(Func errorBuilder) + { + var newGenerators = new List(_errorGenerators) + { + new ErrorGenerator + { + Condition = _errorCondition, + Generator = errorBuilder + } + }; + return new ResponseBuilder(_request, _expectedStatusCodes, _executionConfiguration, newGenerators); + } + + public IResponseBuilder WithDefault() + { + var newGenerators = new List(_errorGenerators) + { + new ErrorGenerator + { + Condition = _errorCondition, + Generator = null + } + }; + return new ResponseBuilder(_request, _expectedStatusCodes, _executionConfiguration, newGenerators); + } + } +} diff --git a/Neo4jClient/Execution/RestExecutionPolicy.cs b/Neo4jClient/Execution/RestExecutionPolicy.cs new file mode 100644 index 000000000..db24e50bc --- /dev/null +++ b/Neo4jClient/Execution/RestExecutionPolicy.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Execution +{ + internal class RestExecutionPolicy : GraphClientBasedExecutionPolicy + { + public RestExecutionPolicy(IGraphClient client) : base(client) + { + } + + public override TransactionExecutionPolicy TransactionExecutionPolicy + { + get { return TransactionExecutionPolicy.Denied; } + } + + public override void AfterExecution(IDictionary executionMetadata, object executionContext) + { + } + + public override Uri BaseEndpoint + { + get { return Client.RootEndpoint; } + } + + public override Uri AddPath(Uri startUri, object startReference) + { + if (startReference == null) + { + return startUri; + } + + if (startReference is NodeReference) + { + return AddPath(startUri, startReference as NodeReference); + } + + if (startReference is RelationshipReference) + { + return AddPath(startUri, startReference as RelationshipReference); + } + + throw new NotImplementedException("Unknown startReference parameter for REST policy"); + } + + private Uri AddPath(Uri startUri, NodeReference node) + { + return startUri.AddPath("node").AddPath(node.Id.ToString()); + } + + private Uri AddPath(Uri startUri, RelationshipReference relationship) + { + return startUri.AddPath("relationship").AddPath(relationship.Id.ToString()); + } + } +} diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index 6168a0370..81c1fcefe 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -8,23 +8,24 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; using System.Threading.Tasks; using Neo4jClient.ApiModels; using Neo4jClient.ApiModels.Cypher; using Neo4jClient.ApiModels.Gremlin; using Neo4jClient.Cypher; +using Neo4jClient.Execution; using Neo4jClient.Gremlin; using Neo4jClient.Serialization; +using Neo4jClient.Transactions; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; namespace Neo4jClient { - public class GraphClient : IRawGraphClient + public class GraphClient : IRawGraphClient, IInternalTransactionalGraphClient, IDisposable { - internal const string GremlinPluginUnavailable = "You're attempting to execute a Gremlin query, however the server instance you are connected to does not have the Gremlin plugin loaded. If you've recently upgraded to Neo4j 2.0, you'll need to be aware that Gremlin no longer ships as part of the normal Neo4j distribution. Please move to equivalent (but much more powerful and readable!) Cypher."; + internal const string GremlinPluginUnavailable = + "You're attempting to execute a Gremlin query, however the server instance you are connected to does not have the Gremlin plugin loaded. If you've recently upgraded to Neo4j 2.0, you'll need to be aware that Gremlin no longer ships as part of the normal Neo4j distribution. Please move to equivalent (but much more powerful and readable!) Cypher."; public static readonly JsonConverter[] DefaultJsonConverters = { @@ -36,26 +37,29 @@ public class GraphClient : IRawGraphClient public static readonly DefaultContractResolver DefaultJsonContractResolver = new DefaultContractResolver(); + private ITransactionManager transactionManager; + private readonly IExecutionPolicyFactory policyFactory; + + public ExecutionConfiguration ExecutionConfiguration { get; private set; } internal readonly Uri RootUri; - readonly IHttpClient httpClient; internal RootApiResponse RootApiResponse; - RootNode rootNode; - bool jsonStreamingAvailable; - readonly string userAgent; - CypherCapabilities cypherCapabilities = CypherCapabilities.Default; + private RootNode rootNode; - public bool UseJsonStreamingIfAvailable { get; set; } + private CypherCapabilities cypherCapabilities = CypherCapabilities.Default; - public GraphClient(Uri rootUri) - : this(rootUri, new HttpClientWrapper()) + + public bool UseJsonStreamingIfAvailable { get; set; } + + public GraphClient(Uri rootUri, string username = null, string password = null) + : this(rootUri, new HttpClientWrapper(username, password)) { ServicePointManager.Expect100Continue = true; ServicePointManager.UseNagleAlgorithm = false; } - public GraphClient(Uri rootUri, bool expect100Continue, bool useNagleAlgorithm) - : this(rootUri, new HttpClientWrapper()) + public GraphClient(Uri rootUri, bool expect100Continue, bool useNagleAlgorithm, string username = null, string password = null) + : this(rootUri, new HttpClientWrapper(username, password)) { ServicePointManager.Expect100Continue = expect100Continue; ServicePointManager.UseNagleAlgorithm = useNagleAlgorithm; @@ -64,20 +68,23 @@ public GraphClient(Uri rootUri, bool expect100Continue, bool useNagleAlgorithm) public GraphClient(Uri rootUri, IHttpClient httpClient) { RootUri = rootUri; - this.httpClient = httpClient; - UseJsonStreamingIfAvailable = true; - - var assemblyVersion = GetType().Assembly.GetName().Version; - userAgent = string.Format("Neo4jClient/{0}", assemblyVersion); - JsonConverters = new List(); JsonConverters.AddRange(DefaultJsonConverters); JsonContractResolver = DefaultJsonContractResolver; + ExecutionConfiguration = new ExecutionConfiguration + { + HttpClient = httpClient, + UserAgent = string.Format("Neo4jClient/{0}", GetType().Assembly.GetName().Version), + UseJsonStreaming = true, + JsonConverters = JsonConverters, + Username = httpClient == null ? null : httpClient.Username, + Password = httpClient == null ? null : httpClient.Password + }; + UseJsonStreamingIfAvailable = true; + policyFactory = new ExecutionPolicyFactory(this); } - internal string UserAgent { get { return userAgent; } } - - Uri BuildUri(string relativeUri) + private Uri BuildUri(string relativeUri) { var baseUri = RootUri; if (!RootUri.AbsoluteUri.EndsWith("/")) @@ -89,109 +96,32 @@ Uri BuildUri(string relativeUri) return new Uri(baseUri, relativeUri); } - HttpRequestMessage HttpDelete(string relativeUri) - { - var absoluteUri = BuildUri(relativeUri); - return new HttpRequestMessage(HttpMethod.Delete, absoluteUri); - } - - HttpRequestMessage HttpGet(string relativeUri) - { - var absoluteUri = BuildUri(relativeUri); - return new HttpRequestMessage(HttpMethod.Get, absoluteUri); - } - - HttpRequestMessage HttpPostAsJson(string relativeUri, object postBody) - { - var absoluteUri = BuildUri(relativeUri); - var postBodyJson = BuildSerializer().Serialize(postBody); - var request = new HttpRequestMessage(HttpMethod.Post, absoluteUri) - { - Content = new StringContent(postBodyJson, Encoding.UTF8, "application/json") - }; - return request; - } - - HttpRequestMessage HttpPutAsJson(string relativeUri, object putBody) - { - var absoluteUri = BuildUri(relativeUri); - var postBodyJson = BuildSerializer().Serialize(putBody); - var request = new HttpRequestMessage(HttpMethod.Put, absoluteUri) - { - Content = new StringContent(postBodyJson, Encoding.UTF8, "application/json") - }; - return request; - } - - HttpResponseMessage SendHttpRequest(HttpRequestMessage request, params HttpStatusCode[] expectedStatusCodes) + private string SerializeAsJson(object contents) { - return SendHttpRequest(request, null, expectedStatusCodes); + return Serializer.Serialize(contents); } - Task SendHttpRequestAsync(HttpRequestMessage request, params HttpStatusCode[] expectedStatusCodes) + public virtual bool IsConnected { - return SendHttpRequestAsync(request, null, expectedStatusCodes); - } - - HttpResponseMessage SendHttpRequest(HttpRequestMessage request, string commandDescription, params HttpStatusCode[] expectedStatusCodes) - { - var task = SendHttpRequestAsync(request, commandDescription, expectedStatusCodes); - try - { - Task.WaitAll(task); - } - catch (AggregateException ex) - { - if (ex.InnerExceptions.Count() == 1) - throw ex.InnerExceptions.Single(); - throw; - } - return task.Result; + get { return RootApiResponse != null; } } - async Task SendHttpRequestAsync(HttpRequestMessage request, string commandDescription, params HttpStatusCode[] expectedStatusCodes) + public virtual void Connect() { - if (UseJsonStreamingIfAvailable && jsonStreamingAvailable) + if (IsConnected) { - request.Headers.Accept.Clear(); - request.Headers.Remove("Accept"); - request.Headers.Add("Accept", "application/json;stream=true"); - } - - request.Headers.Add("User-Agent", userAgent); - - var userInfo = request.RequestUri.UserInfo; - if (!string.IsNullOrEmpty(userInfo)) - { - var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(userInfo)); - request.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials); + return; } - var response = await httpClient.SendAsync(request).ConfigureAwait(false); - response.EnsureExpectedStatusCode(commandDescription, expectedStatusCodes); - - return response; - } - - T SendHttpRequestAndParseResultAs(HttpRequestMessage request, params HttpStatusCode[] expectedStatusCodes) where T : new() - { - return SendHttpRequestAndParseResultAs(request, null, expectedStatusCodes); - } - - T SendHttpRequestAndParseResultAs(HttpRequestMessage request, string commandDescription, params HttpStatusCode[] expectedStatusCodes) where T : new() - { - var response = SendHttpRequest(request, commandDescription, expectedStatusCodes); - return response.Content == null ? default(T) : response.Content.ReadAsJson(JsonConverters, JsonContractResolver); - } - - public virtual void Connect() - { + //return response.Content == null ? default(T) : response.Content.ReadAsJson(JsonConverters, JsonContractResolver); var stopwatch = new Stopwatch(); stopwatch.Start(); - var result = SendHttpRequestAndParseResultAs( - HttpGet(""), - HttpStatusCode.OK); + var result = Request.With(ExecutionConfiguration) + .Get(BuildUri("")) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ParseAs() + .Execute(); var rootUriWithoutUserInfo = RootUri; if (!string.IsNullOrEmpty(rootUriWithoutUserInfo.UserInfo)) @@ -205,6 +135,13 @@ public virtual void Connect() RootApiResponse.Relationship = "/relationship"; //Doesn't come in on the Service Root RootApiResponse.RelationshipIndex = RootApiResponse.RelationshipIndex.Substring(baseUriLengthToTrim); RootApiResponse.ExtensionsInfo = RootApiResponse.ExtensionsInfo.Substring(baseUriLengthToTrim); + + if (!string.IsNullOrEmpty(RootApiResponse.Transaction)) + { + RootApiResponse.Transaction = RootApiResponse.Transaction.Substring(baseUriLengthToTrim); + transactionManager = new TransactionManager(this); + } + if (RootApiResponse.Extensions != null && RootApiResponse.Extensions.GremlinPlugin != null) { RootApiResponse.Extensions.GremlinPlugin.ExecuteScript = @@ -222,11 +159,13 @@ public virtual void Connect() : new RootNode(long.Parse(GetLastPathSegment(RootApiResponse.ReferenceNode)), this); // http://blog.neo4j.org/2012/04/streaming-rest-api-interview-with.html - jsonStreamingAvailable = RootApiResponse.Version >= new Version(1, 8); + ExecutionConfiguration.UseJsonStreaming = ExecutionConfiguration.UseJsonStreaming && + RootApiResponse.Version >= new Version(1, 8); if (RootApiResponse.Version < new Version(2, 0)) cypherCapabilities = CypherCapabilities.Cypher19; + stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs { @@ -236,7 +175,9 @@ public virtual void Connect() }); } - [Obsolete("The concept of a single root node has being dropped in Neo4j 2.0. Use an alternate strategy for having known reference points in the graph, such as labels.")] + [Obsolete( + "The concept of a single root node has being dropped in Neo4j 2.0. Use an alternate strategy for having known reference points in the graph, such as labels." + )] public virtual RootNode RootNode { get @@ -252,12 +193,12 @@ public virtual NodeReference Create( IEnumerable indexEntries) where TNode : class { - if (typeof(TNode).IsGenericType && - typeof(TNode).GetGenericTypeDefinition() == typeof(Node<>)) + if (typeof (TNode).IsGenericType && + typeof (TNode).GetGenericTypeDefinition() == typeof (Node<>)) { throw new ArgumentException(string.Format( "You're trying to pass in a Node<{0}> instance. Just pass the {0} instance instead.", - typeof(TNode).GetGenericArguments()[0].Name), + typeof (TNode).GetGenericArguments()[0].Name), "node"); } @@ -276,13 +217,15 @@ public virtual NodeReference Create( var calculatedRelationships = relationships .Cast() .Select(r => new - { - CalculatedDirection = Relationship.DetermineRelationshipDirection(typeof (TNode), r), - Relationship = r - }) + { + CalculatedDirection = Relationship.DetermineRelationshipDirection(typeof (TNode), r), + Relationship = r + }) .ToArray(); CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Batch); + CheckTransactionEnvironmentWithPolicy(policy); var batchSteps = new List(); @@ -326,7 +269,7 @@ public virtual NodeReference Create( .KeyValues .Select(kv => new { - IndexAddress = BuildIndexAddress(i.Name, IndexFor.Node), + IndexAddress = BuildRelativeIndexAddress(i.Name, IndexFor.Node), kv.Key, Value = EncodeIndexValue(kv.Value) }) @@ -341,8 +284,7 @@ public virtual NodeReference Create( }); } - var batchResponse = ExecuteBatch(batchSteps); - + var batchResponse = ExecuteBatch(batchSteps, policy); var createResponse = batchResponse[createNodeStep]; EnsureNodeWasCreated(createResponse); var nodeId = long.Parse(GetLastPathSegment(createResponse.Location)); @@ -351,7 +293,7 @@ public virtual NodeReference Create( stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs { - QueryText = string.Format("Create<{0}>", typeof(TNode).Name), + QueryText = string.Format("Create<{0}>", typeof (TNode).Name), ResourcesReturned = 0, TimeTaken = stopwatch.Elapsed }); @@ -359,13 +301,14 @@ public virtual NodeReference Create( return nodeReference; } - BatchResponse ExecuteBatch(List batchSteps) + private BatchResponse ExecuteBatch(List batchSteps, IExecutionPolicy policy) { - var response = SendHttpRequestAndParseResultAs( - HttpPostAsJson(RootApiResponse.Batch, batchSteps), - HttpStatusCode.OK); - - return response; + return Request.With(ExecutionConfiguration) + .Post(policy.BaseEndpoint) + .WithJsonContent(SerializeAsJson(batchSteps)) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ParseAs() + .Execute(); } public virtual RelationshipReference CreateRelationship( @@ -382,37 +325,44 @@ public virtual RelationshipReference CreateRelationship>() + .FailOnCondition(responseMessage => responseMessage.StatusCode == HttpStatusCode.NotFound) + .WithError(responseMessage => new ApplicationException(string.Format( "One of the nodes referenced in the relationship could not be found. Referenced nodes were {0} and {1}.", sourceNode.Id, - targetNode.Id)); - - return response - .Content - .ReadAsJson>(JsonConverters,JsonContractResolver) + targetNode.Id)) + ) + .Execute() + //.ReadAsJson>(JsonConverters,JsonContractResolver) .ToRelationshipReference(this); } @@ -421,23 +371,29 @@ CustomJsonSerializer BuildSerializer() return new CustomJsonSerializer { JsonConverters = JsonConverters, JsonContractResolver = JsonContractResolver }; } + public ISerializer Serializer + { + get { return new CustomJsonSerializer { JsonConverters = JsonConverters , JsonContractResolver = JsonContractResolver}; } + } + public void DeleteRelationship(RelationshipReference reference) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); - var relationshipEndpoint = ResolveEndpoint(reference); - var response = SendHttpRequest( - HttpDelete(relationshipEndpoint), - HttpStatusCode.NoContent, HttpStatusCode.NotFound); - - if (response.StatusCode == HttpStatusCode.NotFound) - throw new ApplicationException(string.Format( + Request.With(ExecutionConfiguration) + .Delete(policy.BaseEndpoint.AddPath(reference, policy)) + .WithExpectedStatusCodes(HttpStatusCode.NoContent, HttpStatusCode.NotFound) + .FailOnCondition(response => response.StatusCode == HttpStatusCode.NotFound) + .WithError(response => new ApplicationException(string.Format( "Unable to delete the relationship. The response status was: {0} {1}", (int) response.StatusCode, - response.ReasonPhrase)); + response.ReasonPhrase))) + .Execute(); stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs @@ -451,6 +407,13 @@ public void DeleteRelationship(RelationshipReference reference) public virtual Node Get(NodeReference reference) { var task = GetAsync(reference); + if (task.Exception != null) + { + Exception unwrappedException; + if (task.Exception.TryUnwrap(out unwrappedException)) + throw unwrappedException; + throw task.Exception; + } Task.WaitAll(task); return task.Result; } @@ -458,17 +421,16 @@ public virtual Node Get(NodeReference reference) public virtual async Task> GetAsync(NodeReference reference) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + CheckTransactionEnvironmentWithPolicy(policy); - var nodeEndpoint = ResolveEndpoint(reference); - var response = await SendHttpRequestAsync(HttpGet(nodeEndpoint), HttpStatusCode.OK, HttpStatusCode.NotFound).ConfigureAwait(false); - - if (response.StatusCode == HttpStatusCode.NotFound) - return (Node)null; - - return response - .Content - .ReadAsJson>(JsonConverters, JsonContractResolver) - .ToNode(this); + return await Request.With(ExecutionConfiguration) + .Get(policy.BaseEndpoint.AddPath(reference, policy)) + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.NotFound) + .ParseAs>() + .FailOnCondition(response => response.StatusCode == HttpStatusCode.NotFound) + .WithDefault() + .ExecuteAsync(nodeMessage => nodeMessage.Result != null ? nodeMessage.Result.ToNode(this) : null).ConfigureAwait(false); } public virtual Node Get(NodeReference reference) @@ -476,14 +438,24 @@ public virtual Node Get(NodeReference reference) return Get((NodeReference) reference); } - public virtual RelationshipInstance Get(RelationshipReference reference) where TData : class, new() + public virtual RelationshipInstance Get(RelationshipReference reference) + where TData : class, new() { - return Get((RelationshipReference)reference); + return Get((RelationshipReference) reference); } - public virtual RelationshipInstance Get(RelationshipReference reference) where TData : class, new() + public virtual RelationshipInstance Get(RelationshipReference reference) + where TData : class, new() { var task = GetAsync(reference); + if (task.Exception != null) + { + Exception unwrappedException; + if (task.Exception.TryUnwrap(out unwrappedException)) + throw unwrappedException; + throw task.Exception; + } + Task.WaitAll(task); return task.Result; } @@ -491,22 +463,26 @@ public virtual Node Get(NodeReference reference) public virtual async Task> GetAsync(RelationshipReference reference) where TData : class, new() { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + CheckTransactionEnvironmentWithPolicy(policy); - var endpoint = ResolveEndpoint(reference); - var response = await SendHttpRequestAsync(HttpGet(endpoint), HttpStatusCode.OK, HttpStatusCode.NotFound).ConfigureAwait(false); - - if (response.StatusCode == HttpStatusCode.NotFound) - return (RelationshipInstance)null; - - return response - .Content - .ReadAsJson>(JsonConverters, JsonContractResolver) - .ToRelationshipInstance(this); + return await Request.With(ExecutionConfiguration) + .Get(policy.BaseEndpoint.AddPath(reference, policy)) + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.NotFound) + .ParseAs>() + .FailOnCondition(response => response.StatusCode == HttpStatusCode.NotFound) + .WithDefault() + .ExecuteAsync( + responseTask => + responseTask.Result != null ? responseTask.Result.ToRelationshipInstance(this) : null); } - public void Update(NodeReference nodeReference, TNode replacementData, IEnumerable indexEntries = null) + public void Update(NodeReference nodeReference, TNode replacementData, + IEnumerable indexEntries = null) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -515,10 +491,11 @@ public void Update(NodeReference nodeReference, TNode replacementD ? new IndexEntry[0] : indexEntries.ToArray(); - var nodePropertiesEndpoint = ResolveEndpoint(nodeReference) + "/properties"; - SendHttpRequest( - HttpPutAsJson(nodePropertiesEndpoint, replacementData), - HttpStatusCode.NoContent); + Request.With(ExecutionConfiguration) + .Put(policy.BaseEndpoint.AddPath(nodeReference, policy).AddPath("properties")) + .WithJsonContent(SerializeAsJson(replacementData)) + .WithExpectedStatusCodes(HttpStatusCode.NoContent) + .Execute(); if (allIndexEntries.Any()) ReIndex(nodeReference, allIndexEntries); @@ -526,7 +503,7 @@ public void Update(NodeReference nodeReference, TNode replacementD stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs { - QueryText = string.Format("Update<{0}> {1}", typeof(TNode).Name, nodeReference.Id), + QueryText = string.Format("Update<{0}> {1}", typeof (TNode).Name, nodeReference.Id), ResourcesReturned = 0, TimeTaken = stopwatch.Elapsed }); @@ -537,6 +514,8 @@ public Node Update(NodeReference nodeReference, Action> changeCallback = null) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -550,7 +529,7 @@ public Node Update(NodeReference nodeReference, Action Update(NodeReference nodeReference, Action>(originalValuesString); + var originalValuesDictionary = + new CustomJsonDeserializer(JsonConverters,resolver:JsonContractResolver).Deserialize>( + originalValuesString); var newValuesString = serializer.Serialize(node.Data); - var newValuesDictionary = new CustomJsonDeserializer(JsonConverters, resolver: JsonContractResolver).Deserialize>(newValuesString); - var differences = Utilities.GetDifferencesBetweenDictionaries(originalValuesDictionary, newValuesDictionary); + var newValuesDictionary = + new CustomJsonDeserializer(JsonConverters,resolver:JsonContractResolver).Deserialize>(newValuesString); + var differences = Utilities.GetDifferencesBetweenDictionaries(originalValuesDictionary, + newValuesDictionary); changeCallback(differences); } - var nodePropertiesEndpoint = ResolveEndpoint(nodeReference) + "/properties"; - SendHttpRequest( - HttpPutAsJson(nodePropertiesEndpoint, node.Data), - HttpStatusCode.NoContent); + Request.With(ExecutionConfiguration) + .Put(policy.BaseEndpoint.AddPath(nodeReference, policy).AddPath("properties")) + .WithJsonContent(serializer.Serialize(node.Data)) + .WithExpectedStatusCodes(HttpStatusCode.NoContent) + .Execute(); if (indexEntriesCallback != null) { @@ -578,7 +562,7 @@ public Node Update(NodeReference nodeReference, Action {1}", typeof(TNode).Name, nodeReference.Id), + QueryText = string.Format("Update<{0}> {1}", typeof (TNode).Name, nodeReference.Id), ResourcesReturned = 0, TimeTaken = stopwatch.Elapsed }); @@ -586,31 +570,37 @@ public Node Update(NodeReference nodeReference, Action(RelationshipReference relationshipReference, Action updateCallback) + public void Update(RelationshipReference relationshipReference, + Action updateCallback) where TRelationshipData : class, new() { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); - var propertiesEndpoint = ResolveEndpoint(relationshipReference) + "/properties"; - - var currentData = SendHttpRequestAndParseResultAs( - HttpGet(propertiesEndpoint), - HttpStatusCode.OK, HttpStatusCode.NoContent); + var propertiesEndpoint = policy.BaseEndpoint.AddPath(relationshipReference, policy).AddPath("properties"); + var currentData = Request.With(ExecutionConfiguration) + .Get(propertiesEndpoint) + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.NoContent) + .ParseAs() + .Execute(); var payload = currentData ?? new TRelationshipData(); updateCallback(payload); - SendHttpRequest( - HttpPutAsJson(propertiesEndpoint, payload), - HttpStatusCode.NoContent); + Request.With(ExecutionConfiguration) + .Put(propertiesEndpoint) + .WithJsonContent(SerializeAsJson(payload)) + .WithExpectedStatusCodes(HttpStatusCode.NoContent) + .Execute(); stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs { - QueryText = string.Format("Update<{0}> {1}", typeof(TRelationshipData).Name, relationshipReference.Id), + QueryText = string.Format("Update<{0}> {1}", typeof (TRelationshipData).Name, relationshipReference.Id), ResourcesReturned = 0, TimeTaken = stopwatch.Elapsed }); @@ -619,25 +609,26 @@ public void Update(RelationshipReference r public virtual void Delete(NodeReference reference, DeleteMode mode) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); if (mode == DeleteMode.NodeAndRelationships) { - DeleteAllRelationships(reference); + DeleteAllRelationships(reference, policy); } - var nodeEndpoint = ResolveEndpoint(reference); - var response = SendHttpRequest( - HttpDelete(nodeEndpoint), - HttpStatusCode.NoContent, HttpStatusCode.Conflict); - - if (response.StatusCode == HttpStatusCode.Conflict) - throw new ApplicationException(string.Format( + Request.With(ExecutionConfiguration) + .Delete(policy.BaseEndpoint.AddPath(reference, policy)) + .WithExpectedStatusCodes(HttpStatusCode.NoContent, HttpStatusCode.Conflict) + .FailOnCondition(response => response.StatusCode == HttpStatusCode.Conflict) + .WithError(response => new ApplicationException(string.Format( "Unable to delete the node. The node may still have relationships. The response status was: {0} {1}", (int) response.StatusCode, - response.ReasonPhrase)); + response.ReasonPhrase))) + .Execute(); stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs @@ -648,34 +639,30 @@ public virtual void Delete(NodeReference reference, DeleteMode mode) }); } - void DeleteAllRelationships(NodeReference reference) + private void DeleteAllRelationships(NodeReference reference, IExecutionPolicy policy) { //TODO: Make this a dynamic endpoint resolution - var relationshipsEndpoint = ResolveEndpoint(reference) + "/relationships/all"; - var result = SendHttpRequestAndParseResultAs>>( - HttpGet(relationshipsEndpoint), - HttpStatusCode.OK); - - var relationshipResources = result - .Select(r => r.Self.Substring(RootUri.AbsoluteUri.Length)); - + var relationshipEndpoint = policy.BaseEndpoint + .AddPath(reference, policy) + .AddPath("relationships") + .AddPath("all"); + var result = Request.With(ExecutionConfiguration) + .Get(relationshipEndpoint) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ParseAs>>() + .Execute(); + + var relationshipResources = result.Select(r => r.Self); foreach (var relationshipResource in relationshipResources) - SendHttpRequest( - HttpDelete(relationshipResource), - HttpStatusCode.NoContent, HttpStatusCode.NotFound); - } - - string ResolveEndpoint(NodeReference node) - { - return RootApiResponse.Node + "/" + node.Id; - } - - string ResolveEndpoint(RelationshipReference relationship) - { - return RootApiResponse.Relationship + "/" + relationship.Id; + { + Request.With(ExecutionConfiguration) + .Delete(new Uri(relationshipResource)) + .WithExpectedStatusCodes(HttpStatusCode.NoContent, HttpStatusCode.NotFound) + .Execute(); + } } - static string GetLastPathSegment(string uri) + private static string GetLastPathSegment(string uri) { var path = new Uri(uri).AbsolutePath; return path @@ -685,10 +672,12 @@ static string GetLastPathSegment(string uri) public ICypherFluentQuery Cypher { - get {return new CypherFluentQuery(this); } + get { return new CypherFluentQuery(this); } } - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] + [Obsolete( + "Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher." + )] public IGremlinClient Gremlin { get { return new GremlinClient(this); } @@ -703,12 +692,139 @@ public Version ServerVersion } } + public Uri RootEndpoint + { + get + { + CheckRoot(); + return BuildUri(""); + } + } + + public Uri TransactionEndpoint + { + get + { + CheckRoot(); + return BuildUri(RootApiResponse.Transaction); + } + } + + public Uri BatchEndpoint + { + get + { + CheckRoot(); + return BuildUri(RootApiResponse.Batch); + } + } + + public Uri CypherEndpoint + { + get + { + CheckRoot(); + return BuildUri(RootApiResponse.Cypher); + } + } + + public Uri RelationshipIndexEndpoint + { + get + { + CheckRoot(); + return BuildUri(RootApiResponse.RelationshipIndex); + } + } + + public Uri NodeIndexEndpoint + { + get + { + CheckRoot(); + return BuildUri(RootApiResponse.NodeIndex); + } + } + + public Uri GremlinEndpoint + { + get + { + CheckRoot(); + if (RootApiResponse.Extensions.GremlinPlugin == null || + string.IsNullOrEmpty(RootApiResponse.Extensions.GremlinPlugin.ExecuteScript)) + { + return null; + } + return BuildUri(RootApiResponse.Extensions.GremlinPlugin.ExecuteScript); + } + } + public List JsonConverters { get; private set; } - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] + private void CheckTransactionEnvironmentWithPolicy(IExecutionPolicy policy) + { + bool inTransaction = InTransaction; + + if (transactionManager != null) + { + transactionManager.RegisterToTransactionIfNeeded(); + } + + if (inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Denied) + { + throw new InvalidOperationException("Cannot be done inside a transaction scope."); + } + + if (!inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Required) + { + throw new InvalidOperationException("Cannot be done outside a transaction scope."); + } + } + + public ITransaction BeginTransaction() + { + return BeginTransaction(TransactionScopeOption.Join); + } + + public ITransaction BeginTransaction(TransactionScopeOption scopeOption) + { + CheckRoot(); + if (transactionManager == null) + { + throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); + } + + return transactionManager.BeginTransaction(scopeOption); + } + + public ITransaction Transaction + { + get { return transactionManager == null ? null : transactionManager.CurrentNonDtcTransaction; } + } + + public bool InTransaction + { + get { return transactionManager != null && transactionManager.InTransaction; } + } + + public void EndTransaction() + { + if (transactionManager == null) + { + throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); + } + transactionManager.EndTransaction(); + } + + [Obsolete( + "Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher." + )] public virtual string ExecuteScalarGremlin(string query, IDictionary parameters) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Gremlin); + CheckTransactionEnvironmentWithPolicy(policy); if (RootApiResponse.Extensions.GremlinPlugin == null || RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) @@ -717,10 +833,11 @@ public virtual string ExecuteScalarGremlin(string query, IDictionary ExecuteGetAllProjectionsGremlin(IGremlinQuery query) where TResult : new() + [Obsolete( + "Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher." + )] + public virtual IEnumerable ExecuteGetAllProjectionsGremlin(IGremlinQuery query) + where TResult : new() { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Gremlin); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); - var response = SendHttpRequestAndParseResultAs>>( - HttpPostAsJson( - RootApiResponse.Extensions.GremlinPlugin.ExecuteScript, - new GremlinApiQuery(query.QueryText, query.QueryParameters)), - string.Format("The query was: {0}", query.QueryText), - HttpStatusCode.OK); + var response = Request.With(ExecutionConfiguration) + .Post(policy.BaseEndpoint) + .WithJsonContent(SerializeAsJson(new GremlinApiQuery(query.QueryText, query.QueryParameters))) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ParseAs>>() + .Execute(string.Format("The query was: {0}", query.QueryText)); - var responses = response ?? new List> { new List() }; + var responses = response ?? new List> {new List()}; stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs @@ -768,13 +890,47 @@ public CypherCapabilities CypherCapabilities get { return cypherCapabilities; } } - [Obsolete("This method is for use by the framework internally. Use IGraphClient.Cypher instead, and read the documentation at https://bitbucket.org/Readify/neo4jclient/wiki/cypher. If you really really want to call this method directly, and you accept the fact that YOU WILL LIKELY INTRODUCE A RUNTIME SECURITY RISK if you do so, then it shouldn't take you too long to find the correct explicit interface implementation that you have to call. This hurdle is for your own protection. You really really should not do it. This signature may be removed or renamed at any time.", true)] + [Obsolete( + "This method is for use by the framework internally. Use IGraphClient.Cypher instead, and read the documentation at https://bitbucket.org/Readify/neo4jclient/wiki/cypher. If you really really want to call this method directly, and you accept the fact that YOU WILL LIKELY INTRODUCE A RUNTIME SECURITY RISK if you do so, then it shouldn't take you too long to find the correct explicit interface implementation that you have to call. This hurdle is for your own protection. You really really should not do it. This signature may be removed or renamed at any time.", + true)] [EditorBrowsable(EditorBrowsableState.Never)] public virtual IEnumerable ExecuteGetCypherResults(CypherQuery query) { throw new NotImplementedException(); } + private Task PrepareCypherRequest(CypherQuery query, IExecutionPolicy policy) + { + if (InTransaction) + { + return transactionManager + .EnqueueCypherRequest(string.Format("The query was: {0}", query.QueryText), this, query) + .ContinueWith(responseTask => + { + // we need to check for errors returned by the transaction. The difference with a normal REST cypher + // query is that the errors are embedded within the result object, instead of having a 400 bad request + // status code. + var response = responseTask.Result; + var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, true); + return new CypherPartialResult + { + DeserializationContext = + deserializer.CheckForErrorsInTransactionResponse(response.Content.ReadAsString()), + ResponseObject = response + }; + }); + } + + return Request.With(ExecutionConfiguration) + .Post(policy.BaseEndpoint) + .WithJsonContent(policy.SerializeRequest(query)) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ExecuteAsync(response => new CypherPartialResult + { + ResponseObject = response.Result + }); + } + IEnumerable IRawGraphClient.ExecuteGetCypherResults(CypherQuery query) { var task = ((IRawGraphClient) this).ExecuteGetCypherResultsAsync(query); @@ -795,47 +951,61 @@ IEnumerable IRawGraphClient.ExecuteGetCypherResults(CypherQuer async Task> IRawGraphClient.ExecuteGetCypherResultsAsync(CypherQuery query) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Cypher); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); - var response = await SendHttpRequestAsync( - HttpPostAsJson(RootApiResponse.Cypher, new CypherApiQuery(query)), - string.Format("The query was: {0}", query.QueryText), - HttpStatusCode.OK).ConfigureAwait(false); + // the transaction handling is handled by a thread-local variable (ThreadStatic) so we need + // to know if we are in a transaction right now because our deserializer will run in another thread + bool inTransaction = InTransaction; - var deserializer = new CypherJsonDeserializer(this, query.ResultMode); - var results = deserializer - .Deserialize(response.Content.ReadAsString()) - .ToList(); + var response = await PrepareCypherRequest(query, policy).ConfigureAwait(false); + var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, inTransaction); + List results; + if (inTransaction) + results = deserializer.DeserializeFromTransactionPartialContext(response.DeserializationContext).ToList(); + else + results = deserializer.Deserialize(response.ResponseObject.Content.ReadAsString()).ToList(); stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs { - QueryText = query.DebugQueryText, + QueryText = query.QueryText, ResourcesReturned = results.Count(), TimeTaken = stopwatch.Elapsed }); - return (IEnumerable)results; + return results; } void IRawGraphClient.ExecuteCypher(CypherQuery query) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Cypher); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); - SendHttpRequest( - HttpPostAsJson(RootApiResponse.Cypher, new CypherApiQuery(query)), - string.Format("The query was: {0}", query.QueryText), - HttpStatusCode.OK); + var task = PrepareCypherRequest(query, policy); + try + { + Task.WaitAll(task); + } + catch (AggregateException ex) + { + if (ex.InnerExceptions.Count() == 1) + throw ex.InnerExceptions.Single(); + throw; + } + policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(task.Result.ResponseObject), null); stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs { - QueryText = query.DebugQueryText, + QueryText = query.QueryText, ResourcesReturned = 0, TimeTaken = stopwatch.Elapsed }); @@ -844,35 +1014,73 @@ void IRawGraphClient.ExecuteCypher(CypherQuery query) async Task IRawGraphClient.ExecuteCypherAsync(CypherQuery query) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Cypher); + CheckTransactionEnvironmentWithPolicy(policy); + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + var response = await PrepareCypherRequest(query, policy); + policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(response.ResponseObject), null); + stopwatch.Stop(); + OnOperationCompleted(new OperationCompletedEventArgs + { + QueryText = query.QueryText, + ResourcesReturned = 0, + TimeTaken = stopwatch.Elapsed + }); + } + + void IRawGraphClient.ExecuteMultipleCypherQueriesInTransaction(IEnumerable queries) + { + CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Transaction); + CheckTransactionEnvironmentWithPolicy(policy); + + var queryList = queries.ToList(); + string queriesInText = string.Join(", ", queryList.Select(query => query.QueryText)); var stopwatch = new Stopwatch(); stopwatch.Start(); - await SendHttpRequestAsync( - HttpPostAsJson(RootApiResponse.Cypher, new CypherApiQuery(query)), - string.Format("The query was: {0}", query.QueryText), - HttpStatusCode.OK).ConfigureAwait(false); + var response = Request.With(ExecutionConfiguration) + .Post(policy.BaseEndpoint) + .WithJsonContent(SerializeAsJson(new CypherStatementList(queryList))) + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created) + .Execute("Executing multiple queries: " + queriesInText); + + var transactionObject = transactionManager.CurrentNonDtcTransaction ?? + transactionManager.CurrentDtcTransaction; + policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(response), transactionObject); stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs { - QueryText = query.DebugQueryText, + QueryText = string.Join(", ", queryList.Select(query => query.QueryText)), ResourcesReturned = 0, TimeTaken = stopwatch.Elapsed }); } - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public virtual IEnumerable ExecuteGetAllRelationshipsGremlin(string query, IDictionary parameters) + [Obsolete( + "Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher." + )] + public virtual IEnumerable ExecuteGetAllRelationshipsGremlin(string query, + IDictionary parameters) { return ExecuteGetAllRelationshipsGremlin(query, parameters); } - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public virtual IEnumerable> ExecuteGetAllRelationshipsGremlin(string query, IDictionary parameters) + [Obsolete( + "Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher." + )] + public virtual IEnumerable> ExecuteGetAllRelationshipsGremlin(string query, + IDictionary parameters) where TData : class, new() { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Gremlin); + CheckTransactionEnvironmentWithPolicy(policy); if (RootApiResponse.Extensions.GremlinPlugin == null || RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) @@ -881,10 +1089,12 @@ public virtual IEnumerable> ExecuteGetAllRelationshi var stopwatch = new Stopwatch(); stopwatch.Start(); - var response = SendHttpRequestAndParseResultAs>>( - HttpPostAsJson(RootApiResponse.Extensions.GremlinPlugin.ExecuteScript, new GremlinApiQuery(query, parameters)), - string.Format("The query was: {0}", query), - HttpStatusCode.OK); + var response = Request.With(ExecutionConfiguration) + .Post(policy.BaseEndpoint) + .WithJsonContent(SerializeAsJson(new GremlinApiQuery(query, parameters))) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ParseAs>>() + .Execute(string.Format("The query was: {0}", query)); var relationships = response == null ? new RelationshipInstance[0] @@ -901,22 +1111,32 @@ public virtual IEnumerable> ExecuteGetAllRelationshi return relationships; } - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public virtual IEnumerable> ExecuteGetAllNodesGremlin(string query, IDictionary parameters) + [Obsolete( + "Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher." + )] + public virtual IEnumerable> ExecuteGetAllNodesGremlin(string query, + IDictionary parameters) { return ExecuteGetAllNodesGremlin(new GremlinQuery(this, query, parameters, new List())); } - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public virtual IEnumerable> ExecuteGetAllNodesGremlin(string query, IDictionary parameters, IList declarations) + [Obsolete( + "Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher." + )] + public virtual IEnumerable> ExecuteGetAllNodesGremlin(string query, + IDictionary parameters, IList declarations) { return ExecuteGetAllNodesGremlin(new GremlinQuery(this, query, parameters, declarations)); } - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] + [Obsolete( + "Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher." + )] public virtual IEnumerable> ExecuteGetAllNodesGremlin(IGremlinQuery query) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Gremlin); + CheckTransactionEnvironmentWithPolicy(policy); if (RootApiResponse.Extensions.GremlinPlugin == null || RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) @@ -925,10 +1145,12 @@ public virtual IEnumerable> ExecuteGetAllNodesGremlin(IGremli var stopwatch = new Stopwatch(); stopwatch.Start(); - var response = SendHttpRequestAndParseResultAs>>( - HttpPostAsJson(RootApiResponse.Extensions.GremlinPlugin.ExecuteScript, new GremlinApiQuery(query.QueryText, query.QueryParameters)), - string.Format("The query was: {0}", query.QueryText), - HttpStatusCode.OK); + var response = Request.With(ExecutionConfiguration) + .Post(policy.BaseEndpoint) + .WithJsonContent(SerializeAsJson(new GremlinApiQuery(query.QueryText, query.QueryParameters))) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ParseAs>>() + .Execute(string.Format("The query was: {0}", query.QueryText)); var nodes = response == null ? new Node[0] @@ -945,60 +1167,55 @@ public virtual IEnumerable> ExecuteGetAllNodesGremlin(IGremli return nodes; } - public Dictionary GetIndexes(IndexFor indexFor) + private IExecutionPolicy GetPolicyForIndex(IndexFor indexFor) { - CheckRoot(); - - string indexResource; switch (indexFor) { case IndexFor.Node: - indexResource = RootApiResponse.NodeIndex; - break; + return policyFactory.GetPolicy(PolicyType.NodeIndex); case IndexFor.Relationship: - indexResource = RootApiResponse.RelationshipIndex; - break; + return policyFactory.GetPolicy(PolicyType.RelationshipIndex); default: throw new NotSupportedException(string.Format("GetIndexes does not support indexfor {0}", indexFor)); } + } - var response = SendHttpRequest( - HttpGet(indexResource), - HttpStatusCode.OK, HttpStatusCode.NoContent); + private Uri GetUriForIndexType(IndexFor indexFor) + { + var policy = GetPolicyForIndex(indexFor); + CheckTransactionEnvironmentWithPolicy(policy); + return policy.BaseEndpoint; + } - if(response.StatusCode == HttpStatusCode.NoContent) - return new Dictionary(); + public Dictionary GetIndexes(IndexFor indexFor) + { + CheckRoot(); - var result = response.Content.ReadAsJson>(JsonConverters,JsonContractResolver); + var result = Request.With(ExecutionConfiguration) + .Get(GetUriForIndexType(indexFor)) + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.NoContent) + .ParseAs>() + .FailOnCondition(response => response.StatusCode == HttpStatusCode.NoContent) + .WithDefault() + .Execute(); - return result; + return result ?? new Dictionary(); } public bool CheckIndexExists(string indexName, IndexFor indexFor) { CheckRoot(); - string indexResource; - switch (indexFor) - { - case IndexFor.Node: - indexResource = RootApiResponse.NodeIndex; - break; - case IndexFor.Relationship: - indexResource = RootApiResponse.RelationshipIndex; - break; - default: - throw new NotSupportedException(string.Format("IndexExists does not support indexfor {0}", indexFor)); - } - - var response = SendHttpRequest( - HttpGet(string.Format("{0}/{1}",indexResource, indexName)), - HttpStatusCode.OK, HttpStatusCode.NotFound); + var baseEndpoint = GetUriForIndexType(indexFor); + var response = Request.With(ExecutionConfiguration) + .Get(baseEndpoint.AddPath(indexName)) + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.NotFound) + .Execute(); return response.StatusCode == HttpStatusCode.OK; } - void CheckRoot() + private void CheckRoot() { if (RootApiResponse == null) throw new InvalidOperationException( @@ -1009,117 +1226,102 @@ public void CreateIndex(string indexName, IndexConfiguration config, IndexFor in { CheckRoot(); - string indexResource; - switch (indexFor) - { - case IndexFor.Node: - indexResource = RootApiResponse.NodeIndex; - break; - case IndexFor.Relationship: - indexResource = RootApiResponse.RelationshipIndex; - break; - default: - throw new NotSupportedException(string.Format("CreateIndex does not support indexfor {0}", indexFor)); - } - + var baseEndpoint = GetUriForIndexType(indexFor); var createIndexApiRequest = new { name = indexName, config }; - SendHttpRequest( - HttpPostAsJson(indexResource, createIndexApiRequest), - HttpStatusCode.Created); + Request.With(ExecutionConfiguration) + .Post(baseEndpoint) + .WithJsonContent(SerializeAsJson(createIndexApiRequest)) + .WithExpectedStatusCodes(HttpStatusCode.Created) + .Execute(); } public void ReIndex(NodeReference node, IEnumerable indexEntries) { - var entityUri = ResolveEndpoint(node); + var restPolicy = policyFactory.GetPolicy(PolicyType.Rest); + var entityUri = restPolicy.BaseEndpoint.AddPath(node, restPolicy); var entityId = node.Id; - ReIndex(entityUri, entityId, IndexFor.Node, indexEntries); + ReIndex(entityUri.ToString(), entityId, IndexFor.Node, indexEntries); } public void ReIndex(RelationshipReference relationship, IEnumerable indexEntries) { - var entityUri = ResolveEndpoint(relationship); + var restPolicy = policyFactory.GetPolicy(PolicyType.Rest); + var entityUri = restPolicy.BaseEndpoint.AddPath(relationship, restPolicy); var entityId = relationship.Id; - ReIndex(entityUri, entityId, IndexFor.Relationship, indexEntries); + ReIndex(entityUri.ToString(), entityId, IndexFor.Relationship, indexEntries); } - public void ReIndex(string entityUri, long entityId, IndexFor indexFor, IEnumerable indexEntries) + private void ReIndex(string entityUri, long entityId, IndexFor indexFor, IEnumerable indexEntries, + IExecutionPolicy policy) { if (indexEntries == null) throw new ArgumentNullException("indexEntries"); CheckRoot(); + CheckTransactionEnvironmentWithPolicy(policy); var updates = indexEntries .SelectMany( i => i.KeyValues, - (i, kv) => new { IndexName = i.Name, kv.Key, kv.Value }) + (i, kv) => new {IndexName = i.Name, kv.Key, kv.Value}) .Where(update => update.Value != null) .ToList(); foreach (var indexName in updates.Select(u => u.IndexName).Distinct()) - DeleteIndexEntries(indexName, entityId, indexFor); + DeleteIndexEntries(indexName, entityId, GetUriForIndexType(indexFor)); foreach (var update in updates) AddIndexEntry(update.IndexName, update.Key, update.Value, entityUri, indexFor); } + public void ReIndex(string entityUri, long entityId, IndexFor indexFor, IEnumerable indexEntries) + { + ReIndex(entityUri, entityId, indexFor, indexEntries, GetPolicyForIndex(indexFor)); + } + public void DeleteIndex(string indexName, IndexFor indexFor) { CheckRoot(); + var policy = GetPolicyForIndex(indexFor); + CheckTransactionEnvironmentWithPolicy(policy); - string indexResource; - switch (indexFor) - { - case IndexFor.Node: - indexResource = RootApiResponse.NodeIndex; - break; - case IndexFor.Relationship: - indexResource = RootApiResponse.RelationshipIndex; - break; - default: - throw new NotSupportedException(string.Format("DeleteIndex does not support indexfor {0}", indexFor)); - } - - SendHttpRequest( - HttpDelete(string.Format("{0}/{1}", indexResource, indexName)), - HttpStatusCode.NoContent); + Request.With(ExecutionConfiguration) + .Delete(policy.BaseEndpoint.AddPath(indexName)) + .WithExpectedStatusCodes(HttpStatusCode.NoContent) + .Execute(); } public void DeleteIndexEntries(string indexName, NodeReference nodeReference) { - DeleteIndexEntries(indexName, nodeReference.Id, IndexFor.Node); + DeleteIndexEntries(indexName, nodeReference.Id, GetUriForIndexType(IndexFor.Node)); } public void DeleteIndexEntries(string indexName, RelationshipReference relationshipReference) { - DeleteIndexEntries(indexName, relationshipReference.Id, IndexFor.Relationship); + DeleteIndexEntries(indexName, relationshipReference.Id, GetUriForIndexType(IndexFor.Relationship)); } - void DeleteIndexEntries(string indexName, long id, IndexFor indexFor) + private void DeleteIndexEntries(string indexName, long id, Uri indexUri) { - var indexResponse = indexFor == IndexFor.Node - ? RootApiResponse.NodeIndex - : RootApiResponse.RelationshipIndex; + var indexAddress = indexUri + .AddPath(Uri.EscapeDataString(indexName)) + .AddPath(Uri.EscapeDataString(id.ToString(CultureInfo.InvariantCulture))); - var indexAddress = string.Join("/", new[] - { - indexResponse, - Uri.EscapeDataString(indexName), - Uri.EscapeDataString(id.ToString(CultureInfo.InvariantCulture)) - }); - - SendHttpRequest( - HttpDelete(indexAddress), - string.Format("Deleting entries from index {0} for node {1}", indexName, id), - HttpStatusCode.NoContent); + Request.With(ExecutionConfiguration) + .Delete(indexAddress) + .WithExpectedStatusCodes(HttpStatusCode.NoContent) + .Execute( + string.Format("Deleting entries from index {0} for node {1}", indexName, id) + ); } - void AddIndexEntry(string indexName, string indexKey, object indexValue, string address, IndexFor indexFor) + private void AddIndexEntry(string indexName, string indexKey, object indexValue, string address, + IndexFor indexFor) { var encodedIndexValue = EncodeIndexValue(indexValue); if (string.IsNullOrWhiteSpace(encodedIndexValue)) @@ -1131,30 +1333,31 @@ void AddIndexEntry(string indexName, string indexKey, object indexValue, string { key = indexKey, value = encodedIndexValue, - uri = RootUri + address + uri = address }; - SendHttpRequest( - HttpPostAsJson(indexAddress, indexEntry), - string.Format("Adding '{0}'='{1}' to index {2} for {3}", indexKey, indexValue, indexName, address), - HttpStatusCode.Created); + Request.With(ExecutionConfiguration) + .Post(indexAddress) + .WithJsonContent(SerializeAsJson(indexEntry)) + .WithExpectedStatusCodes(HttpStatusCode.Created) + .Execute(string.Format("Adding '{0}'='{1}' to index {2} for {3}", indexKey, indexValue, indexName, + address)); } - string BuildIndexAddress(string indexName, IndexFor indexFor) + private string BuildRelativeIndexAddress(string indexName, IndexFor indexFor) { - var indexResponse = indexFor == IndexFor.Node - ? RootApiResponse.NodeIndex - : RootApiResponse.RelationshipIndex; + var baseUri = indexFor == IndexFor.Node + ? new UriBuilder() {Path = RootApiResponse.NodeIndex} + : new UriBuilder() {Path = RootApiResponse.RelationshipIndex}; + return baseUri.Uri.AddPath(Uri.EscapeDataString(indexName)).LocalPath; + } - var indexAddress = string.Join("/", new[] - { - indexResponse, - Uri.EscapeDataString(indexName) - }); - return indexAddress; + private Uri BuildIndexAddress(string indexName, IndexFor indexFor) + { + return GetUriForIndexType(indexFor).AddPath(Uri.EscapeDataString(indexName)); } - static string EncodeIndexValue(object value) + private static string EncodeIndexValue(object value) { string indexValue; if (value is DateTimeOffset) @@ -1163,7 +1366,7 @@ static string EncodeIndexValue(object value) } else if (value is DateTime) { - indexValue = ((DateTime)value).Ticks.ToString(CultureInfo.InvariantCulture); + indexValue = ((DateTime) value).Ticks.ToString(CultureInfo.InvariantCulture); } else { @@ -1178,76 +1381,56 @@ static string EncodeIndexValue(object value) } //ToDo Check status of https://github.com/neo4j/community/issues/249 for limiting query result sets - [Obsolete("There are encoding issues with this method. You should use the newer Cypher approach instead. See https://bitbucket.org/Readify/neo4jclient/issue/54/spaces-in-search-text-while-searching-for for an explanation of the problem, and https://bitbucket.org/Readify/neo4jclient/wiki/cypher for documentation about doing index queries with Cypher.")] + [Obsolete( + "There are encoding issues with this method. You should use the newer Cypher approach instead. See https://bitbucket.org/Readify/neo4jclient/issue/54/spaces-in-search-text-while-searching-for for an explanation of the problem, and https://bitbucket.org/Readify/neo4jclient/wiki/cypher for documentation about doing index queries with Cypher." + )] public IEnumerable> QueryIndex(string indexName, IndexFor indexFor, string query) { CheckRoot(); + var indexEndpoint = GetUriForIndexType(indexFor) + .AddPath(indexName) + .AddQuery("query=" + Uri.EscapeDataString(query)); - string indexResource; - switch (indexFor) - { - case IndexFor.Node: - indexResource = RootApiResponse.NodeIndex; - break; - case IndexFor.Relationship: - indexResource = RootApiResponse.RelationshipIndex; - break; - default: - throw new NotSupportedException(string.Format("QueryIndex does not support indexfor {0}", indexFor)); - } - - indexResource = string.Format("{0}/{1}?query={2}", indexResource, indexName, Uri.EscapeDataString(query)); - var response = SendHttpRequest( - HttpGet(indexResource), - HttpStatusCode.OK); - - var data = new CustomJsonDeserializer(JsonConverters, resolver: JsonContractResolver).Deserialize>>(response.Content.ReadAsString()); - - return data == null - ? Enumerable.Empty>() - : data.Select(r => r.ToNode(this)); + return Request.With(ExecutionConfiguration) + .Get(indexEndpoint) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ParseAs>>() + .Execute() + .Select(nodeResponse => nodeResponse.ToNode(this)); } - public IEnumerable> LookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, long id) + public IEnumerable> LookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, + long id) { return BuildLookupIndex(exactIndexName, indexFor, indexKey, id.ToString(CultureInfo.InvariantCulture)); } - public IEnumerable> LookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, int id) + public IEnumerable> LookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, + int id) { return BuildLookupIndex(exactIndexName, indexFor, indexKey, id.ToString(CultureInfo.InvariantCulture)); } - IEnumerable> BuildLookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, string id) + private IEnumerable> BuildLookupIndex(string exactIndexName, IndexFor indexFor, + string indexKey, string id) { CheckRoot(); - - string indexResource; - switch (indexFor) - { - case IndexFor.Node: - indexResource = RootApiResponse.NodeIndex; - break; - case IndexFor.Relationship: - indexResource = RootApiResponse.RelationshipIndex; - break; - default: - throw new NotSupportedException(string.Format("LookupIndex does not support indexfor {0}", indexFor)); - } - - indexResource = string.Format("{0}/{1}/{2}/{3}", indexResource, exactIndexName, indexKey, id); - var response = SendHttpRequest( - HttpGet(indexResource), - HttpStatusCode.OK); - - var data = new CustomJsonDeserializer(JsonConverters, resolver: JsonContractResolver).Deserialize>>(response.Content.ReadAsString()); - - return data == null - ? Enumerable.Empty>() - : data.Select(r => r.ToNode(this)); - } - - [Obsolete("This method depends on Cypher, which is being dropped in Neo4j 2.0. Find an alternate strategy for server lifetime management.")] + var indexResource = GetUriForIndexType(indexFor) + .AddPath(exactIndexName) + .AddPath(indexKey) + .AddPath(id.ToString()); + + return Request.With(ExecutionConfiguration) + .Get(indexResource) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ParseAs>>() + .Execute() + .Select(query => query.ToNode(this)); + } + + [Obsolete( + "This method depends on Gremlin, which is being dropped in Neo4j 2.0. Find an alternate strategy for server lifetime management." + )] public void ShutdownServer() { ExecuteScalarGremlin("g.getRawGraph().shutdown()", null); @@ -1275,6 +1458,25 @@ private void EnsureNodeWasCreated(BatchStepResult createResponse) } } + protected virtual void Dispose(bool disposing) + { + if (!disposing) return; + + if (transactionManager != null) + transactionManager.Dispose(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + public DefaultContractResolver JsonContractResolver { get; set; } + + public ITransactionManager TransactionManager + { + get { return transactionManager; } + } } } diff --git a/Neo4jClient/HttpClient.cs b/Neo4jClient/HttpClient.cs index a76aec00f..4d77ae3aa 100644 --- a/Neo4jClient/HttpClient.cs +++ b/Neo4jClient/HttpClient.cs @@ -1,13 +1,27 @@ -using System.Net.Http; +using System; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; using System.Threading.Tasks; +using Neo4jClient.Execution; namespace Neo4jClient { public class HttpClientWrapper : IHttpClient { - readonly HttpClient client; + internal AuthenticationHeaderValue AuthenticationHeaderValue { get; private set; } + private readonly HttpClient client; - public HttpClientWrapper() : this(new HttpClient()) {} + public HttpClientWrapper(string username = null, string password = null) : this(new HttpClient()) + { + Username = username; + Password = password; + if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password)) + return; + + var encoded = Encoding.ASCII.GetBytes(string.Format("{0}:{1}", username, password)); + AuthenticationHeaderValue = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(encoded)); + } public HttpClientWrapper(HttpClient client) { @@ -16,7 +30,13 @@ public HttpClientWrapper(HttpClient client) public Task SendAsync(HttpRequestMessage request) { + if (AuthenticationHeaderValue != null) + request.Headers.Authorization = AuthenticationHeaderValue; + return client.SendAsync(request); } + + public string Username { get; private set; } + public string Password { get; private set; } } -} +} \ No newline at end of file diff --git a/Neo4jClient/IGraphClient.cs b/Neo4jClient/IGraphClient.cs index 0301696fd..89fe5cef8 100644 --- a/Neo4jClient/IGraphClient.cs +++ b/Neo4jClient/IGraphClient.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel.Design; using System.Threading.Tasks; using Neo4jClient.ApiModels; using Neo4jClient.Cypher; +using Neo4jClient.Execution; using Neo4jClient.Gremlin; +using Neo4jClient.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -109,7 +112,7 @@ RelationshipReference CreateRelationship(NodeReferen IEnumerable> LookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, long id); IEnumerable> LookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, int id); - [Obsolete("This method depends on Cypher, which is being dropped in Neo4j 2.0. Find an alternate strategy for server lifetime management.")] + [Obsolete("This method depends on Gremlin, which is being dropped in Neo4j 2.0. Find an alternate strategy for server lifetime management.")] void ShutdownServer(); event OperationCompletedEventHandler OperationCompleted; @@ -121,8 +124,27 @@ RelationshipReference CreateRelationship(NodeReferen Version ServerVersion { get; } - List JsonConverters { get; } + Uri RootEndpoint { get; } + + Uri BatchEndpoint { get; } + + Uri CypherEndpoint { get; } + + Uri GremlinEndpoint { get; } + + Uri NodeIndexEndpoint { get; } + + Uri RelationshipIndexEndpoint { get; } + ISerializer Serializer { get; } + + ExecutionConfiguration ExecutionConfiguration { get; } + + bool IsConnected { get; } + + void Connect(); + + List JsonConverters { get; } DefaultContractResolver JsonContractResolver { get; set; } } } diff --git a/Neo4jClient/IRawGraphClient.cs b/Neo4jClient/IRawGraphClient.cs index 76239753b..1e997cfba 100644 --- a/Neo4jClient/IRawGraphClient.cs +++ b/Neo4jClient/IRawGraphClient.cs @@ -14,6 +14,7 @@ public interface IRawGraphClient : IGraphClient IEnumerable ExecuteGetCypherResults(CypherQuery query); Task> ExecuteGetCypherResultsAsync(CypherQuery query); void ExecuteCypher(CypherQuery query); + void ExecuteMultipleCypherQueriesInTransaction(IEnumerable queries); Task ExecuteCypherAsync(CypherQuery query); } } diff --git a/Neo4jClient/Neo4jClient.csproj b/Neo4jClient/Neo4jClient.csproj index 13b877879..aead07440 100644 --- a/Neo4jClient/Neo4jClient.csproj +++ b/Neo4jClient/Neo4jClient.csproj @@ -49,6 +49,7 @@ + @@ -62,12 +63,15 @@ + + + @@ -86,14 +90,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - @@ -114,6 +144,8 @@ + + @@ -198,6 +230,25 @@ + + + + + + + + + + + + + + + + + + + diff --git a/Neo4jClient/Neo4jClient.v2.ncrunchproject b/Neo4jClient/Neo4jClient.v2.ncrunchproject new file mode 100644 index 000000000..edc825628 Binary files /dev/null and b/Neo4jClient/Neo4jClient.v2.ncrunchproject differ diff --git a/Neo4jClient/OrphanedTransactionException.cs b/Neo4jClient/OrphanedTransactionException.cs new file mode 100644 index 000000000..deb9472d0 --- /dev/null +++ b/Neo4jClient/OrphanedTransactionException.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient +{ + public class OrphanedTransactionException : Exception + { + private readonly NeoException _internalException; + private readonly string _transactionEndpoint; + + internal OrphanedTransactionException(NeoException internalException, string transactionEndpoint) + : base("The transaction has timed out and it has been rolled back by the server") + { + _internalException = internalException; + _transactionEndpoint = transactionEndpoint; + } + + public NeoException InternalException + { + get { return _internalException; } + } + + public string TransactionEndpoint + { + get { return _transactionEndpoint; } + } + } +} diff --git a/Neo4jClient/Properties/AssemblyInfo.cs b/Neo4jClient/Properties/AssemblyInfo.cs index 4181e7fb7..38a6e8979 100644 --- a/Neo4jClient/Properties/AssemblyInfo.cs +++ b/Neo4jClient/Properties/AssemblyInfo.cs @@ -35,4 +35,4 @@ [assembly: AssemblyVersion("0.0.0.0")] [assembly: AssemblyFileVersion("0.0.0.0")] -[assembly: InternalsVisibleTo("Neo4jClient.Test")] \ No newline at end of file +[assembly: InternalsVisibleTo("Neo4jClient.Tests")] \ No newline at end of file diff --git a/Neo4jClient/Serialization/CustomJsonSerializer.cs b/Neo4jClient/Serialization/CustomJsonSerializer.cs index e37c888c8..cc30bba0b 100644 --- a/Neo4jClient/Serialization/CustomJsonSerializer.cs +++ b/Neo4jClient/Serialization/CustomJsonSerializer.cs @@ -6,7 +6,7 @@ namespace Neo4jClient.Serialization { - public class CustomJsonSerializer + public class CustomJsonSerializer : ISerializer { public IEnumerable JsonConverters { get; set; } public string ContentType { get; set; } diff --git a/Neo4jClient/Serialization/CypherJsonDeserializer.cs b/Neo4jClient/Serialization/CypherJsonDeserializer.cs index 2355126b5..f45f0f8e9 100644 --- a/Neo4jClient/Serialization/CypherJsonDeserializer.cs +++ b/Neo4jClient/Serialization/CypherJsonDeserializer.cs @@ -6,6 +6,7 @@ using System.Reflection; using Neo4jClient.ApiModels; using Neo4jClient.Cypher; +using Neo4jClient.Transactions; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; @@ -16,26 +17,64 @@ public class CypherJsonDeserializer { readonly IGraphClient client; readonly CypherResultMode resultMode; + private readonly CypherResultFormat resultFormat; + private readonly bool inTransaction; readonly CultureInfo culture = CultureInfo.InvariantCulture; - public CypherJsonDeserializer(IGraphClient client, CypherResultMode resultMode) + public CypherJsonDeserializer(IGraphClient client, CypherResultMode resultMode, CypherResultFormat resultFormat) + : this(client, resultMode, resultFormat, false) + { + } + + public CypherJsonDeserializer(IGraphClient client, CypherResultMode resultMode, CypherResultFormat resultFormat, bool inTransaction) { this.client = client; this.resultMode = resultMode; + this.inTransaction = inTransaction; + // here is where we decide if we should deserialize as transactional or REST endpoint data format. + if (resultFormat == CypherResultFormat.DependsOnEnvironment) + { + this.resultFormat = inTransaction ? CypherResultFormat.Transactional : CypherResultFormat.Rest; + } + else + { + this.resultFormat = resultFormat; + } } public IEnumerable Deserialize(string content) { try { + var context = new DeserializationContext + { + Culture = culture, + JsonConverters = Enumerable.Reverse(client.JsonConverters ?? new List(0)).ToArray(), + JsonContractResolver = client.JsonContractResolver + }; + content = CommonDeserializerMethods.ReplaceAllDateInstacesWithNeoDates(content); + + var reader = new JsonTextReader(new StringReader(content)) + { + DateParseHandling = DateParseHandling.DateTimeOffset + }; + // Force the deserialization to happen now, not later, as there's // not much value to deferred execution here and we'd like to know // about any errors now - return DeserializeInternal(content).ToArray(); + return inTransaction + ? FullDeserializationFromTransactionResponse(reader, context).ToArray() + : DeserializeFromRoot(content, reader, context).ToArray(); } catch (Exception ex) { + // we want the NeoException to be thrown + if (ex is NeoException) + { + throw; + } + const string messageTemplate = @"Neo4j returned a valid response, however Neo4jClient was unable to deserialize into the object structure you supplied. @@ -62,8 +101,8 @@ Include the full type definition of {0}. throw new ArgumentException(message, "content", ex); } } - - IEnumerable DeserializeInternal(string content) + + IEnumerable DeserializeInternal(string content) { var context = new DeserializationContext { @@ -133,6 +172,208 @@ IEnumerable DeserializeInternal(string content) } } + IEnumerable DeserializeResultSet(JToken resultRoot, DeserializationContext context) + { + var columnsArray = (JArray)resultRoot["columns"]; + var columnNames = columnsArray + .Children() + .Select(c => c.AsString()) + .ToArray(); + + var jsonTypeMappings = new List + { + new TypeMapping + { + ShouldTriggerForPropertyType = (nestingLevel, type) => + type.IsGenericType && + type.GetGenericTypeDefinition() == typeof(Node<>), + DetermineTypeToParseJsonIntoBasedOnPropertyType = t => + { + var nodeType = t.GetGenericArguments(); + return typeof (NodeApiResponse<>).MakeGenericType(nodeType); + }, + MutationCallback = n => n.GetType().GetMethod("ToNode").Invoke(n, new object[] { client }) + }, + new TypeMapping + { + ShouldTriggerForPropertyType = (nestingLevel, type) => + type.IsGenericType && + type.GetGenericTypeDefinition() == typeof(RelationshipInstance<>), + DetermineTypeToParseJsonIntoBasedOnPropertyType = t => + { + var relationshipType = t.GetGenericArguments(); + return typeof (RelationshipApiResponse<>).MakeGenericType(relationshipType); + }, + MutationCallback = n => n.GetType().GetMethod("ToRelationshipInstance").Invoke(n, new object[] { client }) + } + }; + + switch (resultMode) + { + case CypherResultMode.Set: + return ParseInSingleColumnMode(context, resultRoot, columnNames, jsonTypeMappings.ToArray()); + case CypherResultMode.Projection: + // if we are in transaction and we have an object we dont need a mutation + if (!inTransaction) + { + jsonTypeMappings.Add(new TypeMapping + { + ShouldTriggerForPropertyType = (nestingLevel, type) => + nestingLevel == 0 && type.IsClass, + DetermineTypeToParseJsonIntoBasedOnPropertyType = t => + typeof (NodeOrRelationshipApiResponse<>).MakeGenericType(new[] {t}), + MutationCallback = n => + n.GetType().GetProperty("Data").GetGetMethod().Invoke(n, new object[0]) + }); + } + return ParseInProjectionMode(context, resultRoot, columnNames, jsonTypeMappings.ToArray()); + default: + throw new NotSupportedException(string.Format("Unrecognised result mode of {0}.", resultMode)); + } + } + + private string GetStringPropertyFromObject(JObject obj, string propertyName) + { + JToken propValue; + if (obj.TryGetValue(propertyName, out propValue)) + { + return (string) (propValue as JValue); + } + return null; + } + + private NeoException BuildNeoException(JToken error) + { + var errorObject = error as JObject; + var code = GetStringPropertyFromObject(errorObject, "code"); + if (code == null) + { + throw new InvalidOperationException("Expected 'code' property on error message"); + } + + var message = GetStringPropertyFromObject(errorObject, "message"); + if (message == null) + { + throw new InvalidOperationException("Expected 'message' property on error message"); + } + + var lastCodePart = code.Substring(code.LastIndexOf('.') + 1); + + return new NeoException(new ExceptionResponse + { + // there is no stack trace in transaction error response + StackTrace = new string[] {}, + Exception = lastCodePart, + FullName = code, + Message = message + }); + } + + public PartialDeserializationContext CheckForErrorsInTransactionResponse(string content) + { + if (!inTransaction) + { + throw new InvalidOperationException("Deserialization of this type must be done inside of a transaction scope."); + } + + var context = new DeserializationContext + { + Culture = culture, + JsonConverters = Enumerable.Reverse(client.JsonConverters ?? new List(0)).ToArray() + }; + content = CommonDeserializerMethods.ReplaceAllDateInstacesWithNeoDates(content); + + var reader = new JsonTextReader(new StringReader(content)) + { + DateParseHandling = DateParseHandling.DateTimeOffset + }; + + var root = JToken.ReadFrom(reader).Root as JObject; + + return new PartialDeserializationContext + { + RootResult = GetRootResultInTransaction(root), + DeserializationContext = context + }; + } + + private JToken GetRootResultInTransaction(JObject root) + { + if (root == null) + { + throw new InvalidOperationException("Root expected to be a JSON object."); + } + + JToken rawErrors; + if (root.TryGetValue("errors", out rawErrors)) + { + var errors = rawErrors as JArray; + if (errors == null) + { + throw new InvalidOperationException("`errors` property expected to a JSON array."); + } + + if (errors.Count > 0) + { + throw BuildNeoException(errors.First()); + } + } + + JToken rawResults; + if (!root.TryGetValue("results", out rawResults)) + { + throw new InvalidOperationException("Expected `results` property on JSON root object"); + } + + var results = rawResults as JArray; + if (results == null) + { + throw new InvalidOperationException("`results` property expected to a JSON array."); + } + + return results.FirstOrDefault(); + } + + public IEnumerable DeserializeFromTransactionPartialContext(PartialDeserializationContext context) + { + if (context.RootResult == null) + { + throw new InvalidOperationException( + @"`results` array should have one result set. +This means no query was emitted, so a method that doesn't care about getting results should have been called." + ); + } + return DeserializeResultSet(context.RootResult, context.DeserializationContext); + } + + private IEnumerable FullDeserializationFromTransactionResponse(JsonTextReader reader, DeserializationContext context) + { + var root = JToken.ReadFrom(reader).Root as JObject; + + // discarding all the results but the first + // (this won't affect the library because as of now there is no way of executing + // multiple statements in the same batch within a transaction and returning the results) + var resultSet = GetRootResultInTransaction(root); + if (resultSet == null) + { + throw new InvalidOperationException( + @"`results` array should have one result set. +This means no query was emitted, so a method that doesn't care about getting results should have been called." + ); + } + return DeserializeResultSet(resultSet, context); + } + + IEnumerable DeserializeFromRoot(string content, JsonTextReader reader, DeserializationContext context) + { + var root = JToken.ReadFrom(reader).Root; + if (!(root is JObject)) + { + throw new InvalidOperationException("Root expected to be a JSON object."); + } + return DeserializeResultSet(root, context); + } + // ReSharper disable UnusedParameter.Local IEnumerable ParseInSingleColumnMode(DeserializationContext context, JToken root, string[] columnNames, TypeMapping[] jsonTypeMappings) // ReSharper restore UnusedParameter.Local @@ -149,12 +390,33 @@ IEnumerable ParseInSingleColumnMode(DeserializationContext context, JTo var dataArray = (JArray)root["data"]; var rows = dataArray.Children(); + + var dataPropertyNameInTransaction = resultFormat == CypherResultFormat.Rest ? "rest" : "row"; var results = rows.Select(row => { + if (inTransaction) + { + var rowObject = row as JObject; + if (rowObject == null) + { + throw new InvalidOperationException("Expected the row to be a JSON object, but it wasn't."); + } + + JToken rowProperty; + if (!rowObject.TryGetValue(dataPropertyNameInTransaction, out rowProperty)) + { + throw new InvalidOperationException("There is no row property in the JSON object."); + } + row = rowProperty; + + } + if (!(row is JArray)) + { + // no transaction mode and the row is not an array throw new InvalidOperationException("Expected the row to be a JSON array of values, but it wasn't."); - - var rowAsArray = (JArray) row; + } + var rowAsArray = (JArray)row; if (rowAsArray.Count != 1) throw new InvalidOperationException(string.Format("Expected the row to only have a single array value, but it had {0}.", rowAsArray.Count)); @@ -233,9 +495,9 @@ IEnumerable ParseInProjectionMode(DeserializationContext context, JToke var dataArray = (JArray)root["data"]; var rows = dataArray.Children(); - var results = rows.Select(getRow); - return results; + var dataPropertyNameInTransaction = resultFormat == CypherResultFormat.Rest ? "rest" : "row"; + return inTransaction ? rows.Select(row => row[dataPropertyNameInTransaction]).Select(getRow) : rows.Select(getRow); } TResult ReadProjectionRowUsingCtor( diff --git a/Neo4jClient/Serialization/ISerializer.cs b/Neo4jClient/Serialization/ISerializer.cs new file mode 100644 index 000000000..9b2ae131f --- /dev/null +++ b/Neo4jClient/Serialization/ISerializer.cs @@ -0,0 +1,7 @@ +namespace Neo4jClient.Serialization +{ + public interface ISerializer + { + string Serialize(object toSerialize); + } +} \ No newline at end of file diff --git a/Neo4jClient/Serialization/PartialDeserializationContext.cs b/Neo4jClient/Serialization/PartialDeserializationContext.cs new file mode 100644 index 000000000..d7e488360 --- /dev/null +++ b/Neo4jClient/Serialization/PartialDeserializationContext.cs @@ -0,0 +1,10 @@ +using Newtonsoft.Json.Linq; + +namespace Neo4jClient.Serialization +{ + public class PartialDeserializationContext + { + public JToken RootResult { get; set; } + public DeserializationContext DeserializationContext { get; set; } + } +} \ No newline at end of file diff --git a/Neo4jClient/Transactions/ClosedTransactionException.cs b/Neo4jClient/Transactions/ClosedTransactionException.cs new file mode 100644 index 000000000..cfde8b7e1 --- /dev/null +++ b/Neo4jClient/Transactions/ClosedTransactionException.cs @@ -0,0 +1,22 @@ +using System; + +namespace Neo4jClient.Transactions +{ + public class ClosedTransactionException : Exception + { + private readonly string _transactionEndpoint; + + public ClosedTransactionException(string transactionEndpoint) + : base("The transaction has been committed or rolled back.") + { + _transactionEndpoint = string.IsNullOrEmpty(transactionEndpoint) ? + "No transaction endpoint. No requests were made for the transaction." : + transactionEndpoint; + } + + public string TransactionEndpoint + { + get { return _transactionEndpoint; } + } + } +} diff --git a/Neo4jClient/Transactions/IInternalTransactionalGraphClient.cs b/Neo4jClient/Transactions/IInternalTransactionalGraphClient.cs new file mode 100644 index 000000000..16c45a07b --- /dev/null +++ b/Neo4jClient/Transactions/IInternalTransactionalGraphClient.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Transactions +{ + /// + /// Exposes the same methods and members as ITransactionalGraphClient, however it is used + /// internally to access the ITransactionManager that the GraphClient uses. + /// + internal interface IInternalTransactionalGraphClient : ITransactionalGraphClient + { + ITransactionManager TransactionManager { get; } + } +} diff --git a/Neo4jClient/Transactions/INeo4jTransaction.cs b/Neo4jClient/Transactions/INeo4jTransaction.cs new file mode 100644 index 000000000..cc01af807 --- /dev/null +++ b/Neo4jClient/Transactions/INeo4jTransaction.cs @@ -0,0 +1,16 @@ +using System; + +namespace Neo4jClient.Transactions +{ + /// + /// Represents the same interface as ITransaction, however this interface is used + /// internally to manage the properties that requires interaction with the Neo4j HTTP interface + /// + internal interface INeo4jTransaction : ITransaction + { + /// + /// The Neo4j base endpoint for this transaction + /// + Uri Endpoint { get; set; } + } +} diff --git a/Neo4jClient/Transactions/ITransaction.cs b/Neo4jClient/Transactions/ITransaction.cs new file mode 100644 index 000000000..691f7e288 --- /dev/null +++ b/Neo4jClient/Transactions/ITransaction.cs @@ -0,0 +1,35 @@ +using System; + +namespace Neo4jClient.Transactions +{ + /// + /// Represents a Neo4j transaction shared between multiple HTTP requests + /// + /// + /// Neo4j server prevents abandoned transactions from clogging server resources + /// by rolling back those that do not have requests in the configured timeout period. + /// + public interface ITransaction : IDisposable + { + /// + /// Commits our open transaction. + /// + void Commit(); + + /// + /// Rollbacks any changes made by our open transaction + /// + void Rollback(); + + /// + /// Prevents the transaction from being claimed as an orphaned transaction. + /// + void KeepAlive(); + + /// + /// Returns true if the transaction is still open, that is, if the programmer has not called + /// Commit() or Rollback(). + /// + bool IsOpen { get; } + } +} diff --git a/Neo4jClient/Transactions/ITransactionManager.cs b/Neo4jClient/Transactions/ITransactionManager.cs new file mode 100644 index 000000000..b2035399d --- /dev/null +++ b/Neo4jClient/Transactions/ITransactionManager.cs @@ -0,0 +1,22 @@ +using System; +using System.Net.Http; +using System.Threading.Tasks; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Transactions +{ + /// + /// Interface that handles all the queries related to transactions that could be needed in a ITransactionalGraphClient + /// for implementation. + /// + public interface ITransactionManager : IDisposable + { + bool InTransaction { get; } + ITransaction CurrentNonDtcTransaction { get; } + ITransaction CurrentDtcTransaction { get; } + ITransaction BeginTransaction(TransactionScopeOption option); + void EndTransaction(); + void RegisterToTransactionIfNeeded(); + Task EnqueueCypherRequest(string commandDescription, IGraphClient client, CypherQuery query); + } +} diff --git a/Neo4jClient/Transactions/ITransactionResourceManager.cs b/Neo4jClient/Transactions/ITransactionResourceManager.cs new file mode 100644 index 000000000..0d5219f8f --- /dev/null +++ b/Neo4jClient/Transactions/ITransactionResourceManager.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Transactions +{ + internal interface ITransactionResourceManager + { + void Enlist(ITransactionExecutionEnvironment transactionExecutionEnvironment, byte[] transactionToken); + byte[] Promote(ITransactionExecutionEnvironment transactionExecutionEnvironment); + void CommitTransaction(int transactionId); + void RollbackTransaction(int transactionId); + } +} diff --git a/Neo4jClient/Transactions/ITransactionalGraphClient.cs b/Neo4jClient/Transactions/ITransactionalGraphClient.cs new file mode 100644 index 000000000..637a6d759 --- /dev/null +++ b/Neo4jClient/Transactions/ITransactionalGraphClient.cs @@ -0,0 +1,99 @@ +using System; + +namespace Neo4jClient.Transactions +{ + /// + /// Describes the options for a transaction scope when calling ITransactionalGraphClient.BeginTransaction + /// + public enum TransactionScopeOption + { + Join, + RequiresNew, + Suppress + } + + /// + /// Expands the capabilities of a IGraphClient interface to support a transactional model + /// for Neo4j HTTP Cypher endpoint. + /// + public interface ITransactionalGraphClient : IGraphClient + { + /// + /// Scopes the next cypher queries within a transaction, or joins an existing one. + /// + /// + /// This method should only be used when executing multiple Cypher queries + /// in multiple HTTP requests. Neo4j already encapsulates a single Cypher request within its + /// own transaction. + /// + /// The transaction object created is thread static, that is, that the following queries will only be within + /// a transaction for the current thread. + /// + ITransaction BeginTransaction(); + + /// + /// Scopes the next cypher queries within a transaction (or supress it), according to a given scope option. + /// + /// + /// This method should be used when executing multiple Cypher queries in multiple HTTP requests, + /// or when the thread is already under a transaction and the programmer wishes to temporarily supress it. + /// + /// It should not be called to execute a single Cypher query as it will only add latency to the process. Neo4j already encapsulates + /// a single Cypher request within its own transaction. + /// + /// Be aware that joining a nested transaction must be done before the parent scope completes either by committing or rolling back, + /// otherwise it will throw an InvalidOperationException. + /// + /// The transaction object created is thread static, that is, that the following queries will only be within + /// a transaction for the current thread. + /// + /// + /// + /// + /// + /// Join + /// Creates a new transaction, or joins an existing one. This the default value. + /// The transaction commits until all the scope that have a reference to it commit. However, the transaction rolls back + /// on the first call to Rollback(). + /// + /// + /// RequiresNew + /// The method will generate a new transaction. It is important to notice that this new transaction is not + /// related to an existent parent transaction scope. Committing or rolling back either one has no effect on the other. + /// + /// + /// + /// Suppress + /// The statements that are executed under this scope, will not be executed under the transaction. + /// Committing or rolling back generates an InvalidOperationException. Creating a new transaction scope with Join under + /// a suppressed one, will be the same as RequiresNew. + /// + /// + /// + ITransaction BeginTransaction(TransactionScopeOption scopeOption); + + /// + /// The current transaction object. + /// + /// + /// This object represents our current transactional scope. If it is not null, it doesn't that the code is executing under + /// a transaction. This latter behavior can occurred when BeginTransaction() is called with TransactionScopeOption.Suppress + /// + ITransaction Transaction { get; } + + /// + /// Determines if the code will be executed under a transaction. + /// + bool InTransaction { get; } + + /// + /// Closes the scope of a transaction. The ITransaction will behave as if it was being disposed. + /// + void EndTransaction(); + + /// + /// The Neo4j transaction initial transaction endpoint + /// + Uri TransactionEndpoint { get; } + } +} \ No newline at end of file diff --git a/Neo4jClient/Transactions/Neo4jTransaction.cs b/Neo4jClient/Transactions/Neo4jTransaction.cs new file mode 100644 index 000000000..1be4f4159 --- /dev/null +++ b/Neo4jClient/Transactions/Neo4jTransaction.cs @@ -0,0 +1,265 @@ +using System; +using System.Net; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Execution; +using Neo4jClient.Serialization; + +namespace Neo4jClient.Transactions +{ + /// + /// Implements the Neo4j HTTP transaction for multiple HTTP requests + /// + internal class Neo4jTransaction : INeo4jTransaction + { + private readonly ITransactionalGraphClient _client; + + public bool IsOpen { get; private set; } + + public Uri Endpoint { get; set; } + + internal int Id + { + get + { + if (Endpoint == null) + { + throw new InvalidOperationException("Id is unknown at this point"); + } + + var transactionEndpoint = _client.TransactionEndpoint.ToString(); + int transactionEndpointLength = transactionEndpoint.Length; + if (!transactionEndpoint.EndsWith("/")) + { + transactionEndpointLength++; + } + return int.Parse(Endpoint.ToString().Substring(transactionEndpointLength)); + } + } + + internal static Neo4jTransaction FromIdAndClient(int transactionId, ITransactionalGraphClient client) + { + return new Neo4jTransaction(client) + { + Endpoint = client.TransactionEndpoint.AddPath(transactionId.ToString()) + }; + } + + public Neo4jTransaction(ITransactionalGraphClient graphClient) + { + Endpoint = null; + IsOpen = true; + _client = graphClient; + } + + protected void CleanupAfterClosedTransaction() + { + IsOpen = false; + } + + private void CheckForOpenTransaction() + { + if (IsOpen) + { + return; + } + + string endPointText = null; + if (Endpoint != null) + { + endPointText = Endpoint.ToString(); + } + throw new ClosedTransactionException(endPointText); + } + + /// + /// Cancels a transaction without closing it in the server + /// + internal void Cancel() + { + Endpoint = null; + IsOpen = false; + } + + /// + /// Commits our current transaction and closes the transaction. + /// + public void Commit() + { + CheckForOpenTransaction(); + // we have to check for an empty endpoint because we dont have one until our first request + if (Endpoint == null) + { + CleanupAfterClosedTransaction(); + return; + } + + DoCommit(Endpoint, _client.ExecutionConfiguration, _client.Serializer); + CleanupAfterClosedTransaction(); + } + + /// + /// Rolls back our current transaction and closes the transaction. + /// + public void Rollback() + { + CheckForOpenTransaction(); + // we have to check for an empty endpoint because we dont have one until our first request + if (Endpoint == null) + { + CleanupAfterClosedTransaction(); + return; + } + + Request.With(_client.ExecutionConfiguration) + .Delete(Endpoint) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .Execute(); + + CleanupAfterClosedTransaction(); + } + + /// + /// Emits an empty request to keep alive our current transaction. + /// + public void KeepAlive() + { + CheckForOpenTransaction(); + // no need to issue a request as we haven't sent a single request + if (Endpoint == null) + { + return; + } + + DoKeepAlive(Endpoint, _client.ExecutionConfiguration, _client.Serializer); + } + + /// + /// Forces a keep alive, setting the endpoint if necessary + /// + internal void ForceKeepAlive() + { + var keepAliveUri = Endpoint ?? _client.TransactionEndpoint; + var transactionEndpoint = DoKeepAlive( + keepAliveUri, + _client.ExecutionConfiguration, + _client.Serializer, + Endpoint == null); + + if (Endpoint != null) + { + return; + } + Endpoint = transactionEndpoint; + } + + private static void DoCommit(Uri commitUri, ExecutionConfiguration executionConfiguration, ISerializer serializer) + { + Request.With(executionConfiguration) + .Post(commitUri.AddPath("commit")) + .WithJsonContent(serializer.Serialize(new CypherStatementList())) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .Execute(); + } + + private static void DoRollback(Uri rollbackUri, ExecutionConfiguration executionConfiguration) + { + // not found is ok because it means our transaction either was committed or the timeout was expired + // and it was rolled back for us + Request.With(executionConfiguration) + .Delete(rollbackUri) + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.NotFound) + .Execute(); + } + + private static Uri DoKeepAlive( + Uri keepAliveUri, + ExecutionConfiguration executionConfiguration, + ISerializer serializer, + bool newTransaction = false) + { + var partialRequest = Request.With(executionConfiguration) + .Post(keepAliveUri) + .WithJsonContent(serializer.Serialize(new CypherStatementList())); + + var response = newTransaction ? + partialRequest.WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created).Execute() : + partialRequest.WithExpectedStatusCodes(HttpStatusCode.OK).Execute(); + + return response.Headers.Location; + } + + /// + /// Commits a transaction given the ID + /// + /// The transaction execution environment + internal static void DoCommit(ITransactionExecutionEnvironment transactionExecutionEnvironment) + { + var commitUri = transactionExecutionEnvironment.TransactionBaseEndpoint.AddPath( + transactionExecutionEnvironment.TransactionId.ToString()); + DoCommit( + commitUri, + new ExecutionConfiguration + { + HttpClient = new HttpClientWrapper(transactionExecutionEnvironment.Username, transactionExecutionEnvironment.Password), + JsonConverters = GraphClient.DefaultJsonConverters, + UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, + UserAgent = transactionExecutionEnvironment.UserAgent + }, + new CustomJsonSerializer()); + } + + /// + /// Rolls back a transaction given the ID + /// + /// The transaction execution environment + internal static void DoRollback(ITransactionExecutionEnvironment transactionExecutionEnvironment) + { + try + { + var rollbackUri = transactionExecutionEnvironment.TransactionBaseEndpoint.AddPath( + transactionExecutionEnvironment.TransactionId.ToString()); + DoRollback( + rollbackUri, + new ExecutionConfiguration + { + HttpClient = new HttpClientWrapper(transactionExecutionEnvironment.Username, transactionExecutionEnvironment.Password), + JsonConverters = GraphClient.DefaultJsonConverters, + UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, + UserAgent = transactionExecutionEnvironment.UserAgent + }); + } + catch (Exception e) + { + throw e; + } + } + + /// + /// Keeps alive a transaction given the ID + /// + /// The transaction execution environment + internal static void DoKeepAlive(ITransactionExecutionEnvironment transactionExecutionEnvironment) + { + var keepAliveUri = transactionExecutionEnvironment.TransactionBaseEndpoint.AddPath( + transactionExecutionEnvironment.TransactionId.ToString()); + DoKeepAlive( + keepAliveUri, + new ExecutionConfiguration + { + HttpClient = new HttpClientWrapper(transactionExecutionEnvironment.Username, transactionExecutionEnvironment.Password), + JsonConverters = GraphClient.DefaultJsonConverters, + UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, + UserAgent = transactionExecutionEnvironment.UserAgent + }, + new CustomJsonSerializer()); + } + + public void Dispose() + { + if (IsOpen) + { + Rollback(); + } + } + } +} diff --git a/Neo4jClient/Transactions/Neo4jTransactionProxy.cs b/Neo4jClient/Transactions/Neo4jTransactionProxy.cs new file mode 100644 index 000000000..481566798 --- /dev/null +++ b/Neo4jClient/Transactions/Neo4jTransactionProxy.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Transactions +{ + /// + /// Implements the TransactionScopeProxy interfaces for INeo4jTransaction + /// + internal class Neo4jTransactionProxy : TransactionScopeProxy + { + private readonly bool _doCommitInScope; + + public Neo4jTransactionProxy(ITransactionalGraphClient client, TransactionContext transactionContext, bool newScope) + : base(client, transactionContext) + { + _doCommitInScope = newScope; + } + + protected override void DoCommit() + { + if (_doCommitInScope) + { + TransactionContext.Commit(); + } + } + + protected override bool ShouldDisposeTransaction() + { + return _doCommitInScope; + } + + public override bool Committable + { + get { return true; } + } + + public override void Rollback() + { + TransactionContext.Rollback(); + } + + public override void KeepAlive() + { + TransactionContext.KeepAlive(); + } + + public override bool IsOpen + { + get + { + return TransactionContext != null && TransactionContext.IsOpen; + } + } + } +} diff --git a/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs b/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs new file mode 100644 index 000000000..767cb61e1 --- /dev/null +++ b/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Transactions; + +namespace Neo4jClient.Transactions +{ + using Neo4jClient.Execution; + + /// + /// When TransactionPromotableSinglePhaseNotification fails to register as PSPE, then this class will + /// be registered, and all the necessary work will be done in here + /// + internal class Neo4jTransationSinglePhaseNotification : ISinglePhaseNotification + { + private static readonly Guid TransactionResourceId = new Guid("{BB792575-FAA7-4C72-A6B1-A69876CC3E1E}"); + private ITransactionExecutionEnvironment _transactionExecutionEnvironment; + + public Neo4jTransationSinglePhaseNotification(ITransactionExecutionEnvironment transactionExecutionEnvironment) + { + _transactionExecutionEnvironment = transactionExecutionEnvironment; + } + + public void Prepare(PreparingEnlistment preparingEnlistment) + { + Neo4jTransaction.DoKeepAlive(_transactionExecutionEnvironment); + preparingEnlistment.Done(); + } + + public void Commit(Enlistment enlistment) + { + try + { + Neo4jTransaction.DoCommit(_transactionExecutionEnvironment); + } + finally + { + // always have to call Done() or we clog the resources + enlistment.Done(); + } + } + + public void Rollback(Enlistment enlistment) + { + try + { + Neo4jTransaction.DoRollback(_transactionExecutionEnvironment); + } + finally + { + // always have to call Done() or we clog the resources + enlistment.Done(); + } + } + + public void InDoubt(Enlistment enlistment) + { + enlistment.Done(); + } + + public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) + { + try + { + Neo4jTransaction.DoCommit(_transactionExecutionEnvironment); + singlePhaseEnlistment.Committed(); + } + finally + { + singlePhaseEnlistment.Aborted(); + } + } + + public void Enlist(Transaction tx) + { + tx.EnlistDurable(TransactionResourceId, this, EnlistmentOptions.None); + } + } + + internal class Neo4jTransactionResourceManager : MarshalByRefObject, ITransactionResourceManager + { + private readonly IDictionary _transactions = new Dictionary(); + + public void Enlist(ITransactionExecutionEnvironment transactionExecutionEnvironment, byte[] transactionToken) + { + var tx = TransactionInterop.GetTransactionFromTransmitterPropagationToken(transactionToken); + new Neo4jTransationSinglePhaseNotification(transactionExecutionEnvironment).Enlist(tx); + } + + public byte[] Promote(ITransactionExecutionEnvironment transactionExecutionEnvironment) + { + var promotedTx = new CommittableTransaction(); + var neo4jTransactionHandler = new Neo4jTransationSinglePhaseNotification(transactionExecutionEnvironment); + var token = TransactionInterop.GetTransmitterPropagationToken(promotedTx); + _transactions[transactionExecutionEnvironment.TransactionId] = promotedTx; + neo4jTransactionHandler.Enlist(promotedTx); + + return token; + } + + public void CommitTransaction(int transactionId) + { + CommittableTransaction tx; + if (_transactions.TryGetValue(transactionId, out tx)) + { + tx.Commit(); + _transactions.Remove(transactionId); + } + } + + public void RollbackTransaction(int transactionId) + { + CommittableTransaction tx; + if (_transactions.TryGetValue(transactionId, out tx)) + { + _transactions.Remove(transactionId); + tx.Rollback(); + } + } + } +} diff --git a/Neo4jClient/Transactions/SuppressTransactionProxy.cs b/Neo4jClient/Transactions/SuppressTransactionProxy.cs new file mode 100644 index 000000000..f68b04553 --- /dev/null +++ b/Neo4jClient/Transactions/SuppressTransactionProxy.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Transactions +{ + internal class SuppressTransactionProxy : TransactionScopeProxy + { + public SuppressTransactionProxy(ITransactionalGraphClient client) + : base(client, null) + { + } + + protected override bool ShouldDisposeTransaction() + { + return false; + } + + protected override void DoCommit() + { + throw new InvalidOperationException("Committing during a suppressed transaction scope"); + } + + public override bool Committable + { + get { return false; } + } + + public override void Rollback() + { + throw new InvalidOperationException("Rolling back during a suppressed transaction scope"); + } + + public override void KeepAlive() + { + // no-op + } + + public override bool IsOpen + { + // we cannot call Commit() or Rollback() for this proxy + get { return true; } + } + } +} diff --git a/Neo4jClient/Transactions/TransactionConnectionContext.cs b/Neo4jClient/Transactions/TransactionConnectionContext.cs new file mode 100644 index 000000000..561190bea --- /dev/null +++ b/Neo4jClient/Transactions/TransactionConnectionContext.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Transactions +{ + internal class TransactionConnectionContext + { + public ITransactionalGraphClient Client { get; set; } + + } +} diff --git a/Neo4jClient/Transactions/TransactionContext.cs b/Neo4jClient/Transactions/TransactionContext.cs new file mode 100644 index 000000000..76ee63637 --- /dev/null +++ b/Neo4jClient/Transactions/TransactionContext.cs @@ -0,0 +1,150 @@ +using System; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Neo4jClient.Cypher; +using Neo4jClient.Execution; + +namespace Neo4jClient.Transactions +{ + /// + /// Encapsulates a transaction object with its transaction scheduler. + /// + /// + /// All requests to the same transaction have to made sequentially. The purpose of this class is to ensure + /// that such calls are made in that fashion. + /// + internal class TransactionContext : INeo4jTransaction + { + /// + /// The Neo4j transaction object. + /// + public INeo4jTransaction Transaction { get; protected set; } + + /// + /// The consumer of all the tasks (a single thread) + /// + private Action _consumer = null; + + /// + /// This is where the producer generates all the tasks + /// + private BlockingCollection _taskQueue; + + /// + /// Where the cancellation token generates + /// + private CancellationTokenSource _cancellationTokenSource; + + public TransactionContext(INeo4jTransaction transaction) + { + Transaction = transaction; + _cancellationTokenSource = new CancellationTokenSource(); + _taskQueue = new BlockingCollection(); + } + + public Task EnqueueTask(string commandDescription, IGraphClient client, IExecutionPolicy policy, CypherQuery query) + { + // grab the endpoint in the same thread + var txBaseEndpoint = policy.BaseEndpoint; + var serializedQuery = policy.SerializeRequest(query); + var task = new Task(() => + Request.With(client.ExecutionConfiguration) + .Post(Endpoint ?? txBaseEndpoint) + .WithJsonContent(serializedQuery) + // HttpStatusCode.Created may be returned when emitting the first query on a transaction + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created) + .ExecuteAsync( + commandDescription, + responseTask => + { + // we need to check for errors returned by the transaction. The difference with a normal REST cypher + // query is that the errors are embedded within the result object, instead of having a 400 bad request + // status code. + var response = responseTask.Result; + policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(response), this); + + return response; + }) + .Result + ); + _taskQueue.Add(task, _cancellationTokenSource.Token); + + if (_consumer == null) + { + _consumer = () => + { + while (true) + { + try + { + Task queuedTask; + if (!_taskQueue.TryTake(out queuedTask, 0, _cancellationTokenSource.Token)) + { + // no items to consume + _consumer = null; + break; + } + queuedTask.RunSynchronously(); + } + catch (InvalidOperationException) + { + // we are done, CompleteAdding has been called + break; + } + catch (OperationCanceledException) + { + // we are done, we were canceled + break; + } + } + }; + + _consumer.BeginInvoke(null, null); + } + + return task; + } + + public void Dispose() + { + Transaction.Dispose(); + } + + public void Commit() + { + _taskQueue.CompleteAdding(); + if (_taskQueue.Count > 0) + { + _cancellationTokenSource.Cancel(); + throw new InvalidOperationException("Cannot commit unless all exceptions have been completed"); + } + Transaction.Commit(); + } + + public void Rollback() + { + _taskQueue.CompleteAdding(); + _cancellationTokenSource.Cancel(); + Transaction.Rollback(); + } + + public void KeepAlive() + { + Transaction.KeepAlive(); + } + + public bool IsOpen + { + get { return Transaction.IsOpen; } + } + + public Uri Endpoint + { + get { return Transaction.Endpoint; } + set { Transaction.Endpoint = value; } + } + } +} diff --git a/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs b/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs new file mode 100644 index 000000000..0c6405202 --- /dev/null +++ b/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using Neo4jClient.Execution; +using Neo4jClient.Serialization; +using Newtonsoft.Json; + +namespace Neo4jClient.Transactions +{ + /// + /// Because the resource manager is held in another application domain, the transaction execution environment + /// has to be serialized to cross app domain boundaries. + /// + internal class TransactionExecutionEnvironment : MarshalByRefObject, ITransactionExecutionEnvironment + { + public Uri TransactionBaseEndpoint { get; set; } + public int TransactionId { get; set; } + public bool UseJsonStreaming { get; set; } + public string UserAgent { get; set; } + public IEnumerable JsonConverters { get; set; } + public string Username { get; set; } + public string Password { get; set; } + + public TransactionExecutionEnvironment(ExecutionConfiguration executionConfiguration) + { + UserAgent = executionConfiguration.UserAgent; + UseJsonStreaming = executionConfiguration.UseJsonStreaming; + Username = executionConfiguration.Username; + Password = executionConfiguration.Password; + JsonConverters = executionConfiguration.JsonConverters; + } + + } + + internal interface ITransactionExecutionEnvironment + { + Uri TransactionBaseEndpoint { get; } + int TransactionId { get; } + bool UseJsonStreaming { get; set; } + string UserAgent { get; set; } + IEnumerable JsonConverters { get; set; } + string Username { get; set; } + string Password { get; set; } + } +} diff --git a/Neo4jClient/Transactions/TransactionHttpUtils.cs b/Neo4jClient/Transactions/TransactionHttpUtils.cs new file mode 100644 index 000000000..2483b2f7e --- /dev/null +++ b/Neo4jClient/Transactions/TransactionHttpUtils.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; + +namespace Neo4jClient.Transactions +{ + /// + /// Contains utility methods for handling HttpResponseMessages in a transaction scope + /// + internal static class TransactionHttpUtils + { + public static IDictionary GetMetadataFromResponse(HttpResponseMessage response) + { + return response.Headers.ToDictionary( + headerPair => headerPair.Key, + headerPair => (object)headerPair.Value); + } + } +} diff --git a/Neo4jClient/Transactions/TransactionManager.cs b/Neo4jClient/Transactions/TransactionManager.cs new file mode 100644 index 000000000..6d79545da --- /dev/null +++ b/Neo4jClient/Transactions/TransactionManager.cs @@ -0,0 +1,251 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using System.Transactions; +using Neo4jClient.Cypher; +using Neo4jClient.Execution; + +namespace Neo4jClient.Transactions +{ + /// + /// Handles all the queries related to transactions that could be needed in a ITransactionalGraphClient + /// + internal class TransactionManager : ITransactionManager + { + // holds the transaction objects per thread + [ThreadStatic] private static Stack _scopedTransactions; + // holds the transaction contexts for transactions from the System.Transactions framework + private IDictionary _dtcContexts; + private TransactionPromotableSinglePhaseNotification _promotable; + private ITransactionalGraphClient _client; + + public TransactionManager(ITransactionalGraphClient client) + { + _client = client; + // specifies that we are about to use variables that depend on OS threads + Thread.BeginThreadAffinity(); + _scopedTransactions = new Stack(); + + // this object enables the interacion with System.Transactions and MSDTC, at first by + // letting us manage the transaction objects ourselves, and if we require to be promoted to MSDTC, + // then it notifies the library how to do it. + _promotable = new TransactionPromotableSinglePhaseNotification(client); + _dtcContexts = new Dictionary(); + } + + private TransactionContext GetOrCreateDtcTransactionContext() + { + // we need to lock as we could get other async requests to the same transaction + var txId = Transaction.Current.TransactionInformation.LocalIdentifier; + lock (_dtcContexts) + { + TransactionContext txContext; + if (_dtcContexts.TryGetValue(txId, out txContext)) + { + return txContext; + } + + // associate it with the ambient transaction + txContext = new TransactionContext(_promotable.AmbientTransaction); + _dtcContexts[txId] = txContext; + + return txContext; + } + } + + private TransactionContext GetContext() + { + var nonDtcTransaction = CurrentInternalTransaction; + if (nonDtcTransaction != null && nonDtcTransaction.Committable) + { + return nonDtcTransaction.TransactionContext; + } + + // if we are not in a native transaction get the context of our ambient transaction + return GetOrCreateDtcTransactionContext(); + } + + public bool InTransaction + { + get + { + var transactionObject = CurrentInternalTransaction; + if (transactionObject != null) + { + return transactionObject.Committable; + } + + // if we are in an ambient System.Transactions transaction then we are in a transaction! + return Transaction.Current != null; + } + } + + public TransactionScopeProxy CurrentInternalTransaction + { + get + { + try + { + return _scopedTransactions == null ? null : _scopedTransactions.Peek(); + } + catch (InvalidOperationException) + { + // the stack is empty + return null; + } + } + } + + public ITransaction CurrentNonDtcTransaction + { + get { return CurrentInternalTransaction; } + } + + public ITransaction CurrentDtcTransaction + { + get + { + return Transaction.Current == null ? null : _promotable.AmbientTransaction; + } + } + + /// + /// Implements the internal part for ITransactionalGraphClient.BeginTransaction + /// + /// How should the transaction scope be created. + /// + /// for more information. + /// + public ITransaction BeginTransaction(TransactionScopeOption scopeOption) + { + if (scopeOption == TransactionScopeOption.Suppress) + { + // TransactionScopeOption.Suppress doesn't fail with older versions of Neo4j + return BeginSupressTransaction(); + } + + if (_client.ServerVersion < new Version(2, 0)) + { + throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); + } + + if (scopeOption == TransactionScopeOption.Join) + { + var joinedTransaction = BeginJoinTransaction(); + if (joinedTransaction != null) + { + return joinedTransaction; + } + } + + // then scopeOption == TransactionScopeOption.RequiresNew or we dont have a current transaction + return BeginNewTransaction(); + } + + private TransactionContext GenerateTransaction() + { + return new TransactionContext(new Neo4jTransaction(_client)); + } + + private TransactionContext GenerateTransaction(TransactionContext reference) + { + return new TransactionContext(reference.Transaction); + } + + private void PushScopeTransaction(TransactionScopeProxy transaction) + { + if (_scopedTransactions == null) + { + _scopedTransactions = new Stack(); + } + _scopedTransactions.Push(transaction); + } + + private ITransaction BeginNewTransaction() + { + var transaction = new Neo4jTransactionProxy(_client, GenerateTransaction(), true); + PushScopeTransaction(transaction); + return transaction; + } + + private ITransaction BeginJoinTransaction() + { + var parentScope = CurrentInternalTransaction; + if (parentScope == null) + { + return null; + } + + if (!parentScope.Committable) + { + return null; + } + + if (!parentScope.IsOpen) + { + throw new ClosedTransactionException(null); + } + + var joinedTransaction = new Neo4jTransactionProxy(_client, GenerateTransaction(parentScope.TransactionContext), false); + PushScopeTransaction(joinedTransaction); + return joinedTransaction; + } + + private ITransaction BeginSupressTransaction() + { + var suppressTransaction = new SuppressTransactionProxy(_client); + PushScopeTransaction(suppressTransaction); + return suppressTransaction; + } + + public void EndTransaction() + { + TransactionScopeProxy currentTransaction = null; + try + { + currentTransaction = _scopedTransactions == null ? null : _scopedTransactions.Pop(); + } + catch (InvalidOperationException) + { + } + if (currentTransaction != null) + { + currentTransaction.Dispose(); + } + } + + /// + /// Registers to ambient System.Transactions.TransactionContext if needed + /// + public void RegisterToTransactionIfNeeded() + { + if (_promotable == null) + { + // no need to register as we don't support transactions + return; + } + _promotable.EnlistIfNecessary(); + } + + public Task EnqueueCypherRequest(string commandDescription, IGraphClient client, CypherQuery query) + { + var policy = new CypherTransactionExecutionPolicy(client); + // we try to get the current dtc transaction. If we are in a System.Transactions transaction and it has + // been "promoted" to be handled by DTC then transactionObject will be null, but it doesn't matter as + // we don't care about updating the object. + var txContext = GetContext(); + + // the main difference with a normal Request.With() call is that the request is associated with the + // TX context. + return txContext.EnqueueTask(commandDescription, client, policy, query); + } + + public void Dispose() + { + _scopedTransactions = null; + Thread.EndThreadAffinity(); + } + } +} \ No newline at end of file diff --git a/Neo4jClient/Transactions/TransactionScopeProxy.cs b/Neo4jClient/Transactions/TransactionScopeProxy.cs new file mode 100644 index 000000000..c11bac9f7 --- /dev/null +++ b/Neo4jClient/Transactions/TransactionScopeProxy.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Transactions; + +namespace Neo4jClient.Transactions +{ + /// + /// Represents a TransactionContext scope within an ITransactionalManager. Encapsulates the real TransactionContext, so that in reality + /// it only exists one single TransactionContext object in a joined scope, but multiple TransactionScopeProxies that can be pushed, or + /// popped (in a scope context). + /// + internal abstract class TransactionScopeProxy : INeo4jTransaction + { + private readonly ITransactionalGraphClient _client; + private bool _markCommitted = false; + private bool _disposing = false; + private TransactionContext _transactionContext; + + + public TransactionContext TransactionContext + { + get { return _transactionContext; } + } + + protected TransactionScopeProxy(ITransactionalGraphClient client, TransactionContext transactionContext) + { + _client = client; + _disposing = false; + _transactionContext = transactionContext; + } + + public Uri Endpoint + { + get { return _transactionContext.Endpoint; } + set { _transactionContext.Endpoint = value; } + } + + public virtual void Dispose() + { + if (_disposing) + { + return; + } + + _disposing = true; + _client.EndTransaction(); + if (!_markCommitted && Committable && TransactionContext.IsOpen) + { + Rollback(); + } + + if (_transactionContext != null && ShouldDisposeTransaction()) + { + _transactionContext.Dispose(); + _transactionContext = null; + } + } + + public void Commit() + { + _markCommitted = true; + DoCommit(); + } + + protected abstract bool ShouldDisposeTransaction(); + protected abstract void DoCommit(); + public abstract bool Committable { get; } + public abstract void Rollback(); + public abstract void KeepAlive(); + public abstract bool IsOpen { get; } + } +} diff --git a/Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs b/Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs new file mode 100644 index 000000000..ce74e7554 --- /dev/null +++ b/Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Transactions; + +namespace Neo4jClient.Transactions +{ + /// + /// This class manages the System.Transactions protocol in order to support TransactionScope bindings + /// + internal class TransactionPromotableSinglePhaseNotification : IPromotableSinglePhaseNotification + { + private Neo4jTransaction _transaction; + private readonly ITransactionalGraphClient _client; + private static ITransactionResourceManager _resourceManager; + private ISet _enlistedInTransactions = new HashSet(); + private int _transactionId; + + public TransactionPromotableSinglePhaseNotification(ITransactionalGraphClient client) + { + _client = client; + //_resourceManager = new Neo4jTransactionResourceManager(); + } + + public void EnlistIfNecessary() + { + if (!_enlistedInTransactions.Contains(Transaction.Current)) + { + Enlist(Transaction.Current); + } + } + + private void Enlist(Transaction transaction) + { + if (transaction == null) + { + // no enlistment as we are not in a TransactionScope + return; + } + + // try to enlist as a PSPE + if (!transaction.EnlistPromotableSinglePhase(this)) + { + // our enlistmente fail so we need to enlist ourselves as durable. + + // we create a transaction directly instead of using BeginTransaction that GraphClient + // doesn't store it in its stack of scopes. + var localTransaction = new Neo4jTransaction(_client); + localTransaction.ForceKeepAlive(); + _transactionId = localTransaction.Id; + var resourceManager = GetResourceManager(); + var propagationToken = TransactionInterop.GetTransmitterPropagationToken(transaction); + var transactionExecutionEnvironment = new TransactionExecutionEnvironment(_client.ExecutionConfiguration) + { + TransactionId = localTransaction.Id, + TransactionBaseEndpoint = _client.TransactionEndpoint + }; + resourceManager.Enlist(transactionExecutionEnvironment, propagationToken); + localTransaction.Cancel(); + } + + _enlistedInTransactions.Add(transaction); + } + + public byte[] Promote() + { + // we have been promoted to MSTDC, so we have to clean the local resources + if (_transaction == null) + { + _transaction = new Neo4jTransaction(_client); + } + + // do a keep alive in case the promotion takes too long or in case we don't have an ID + _transaction.ForceKeepAlive(); + _transactionId = _transaction.Id; + _transaction.Cancel(); + _transaction = null; + + if (_transactionId == 0) + { + throw new InvalidOperationException("For some reason we don't have a TransactionContext ID"); + } + + var resourceManager = GetResourceManager(); + return resourceManager.Promote(new TransactionExecutionEnvironment(_client.ExecutionConfiguration) + { + TransactionId = _transactionId, + TransactionBaseEndpoint = _client.TransactionEndpoint + }); + } + + public void Initialize() + { + // enlistment has completed successfully. + // For now we can use local transactions + // we create it directly instead of using BeginTransaction that GraphClient + // doesn't store it in its stack of scopes. + _transaction = new Neo4jTransaction(_client); + } + + public Neo4jTransaction AmbientTransaction + { + get + { + // If _transaction is null, then our PSPE enlistment failed or we got promoted. + // If we got promoted then we can reconstruct it because we have the id and the client, + // but only if we have an ID, if we don't have an ID that means we haven't executed a single query + if (_transaction == null && _transactionId > 0) + { + return Neo4jTransaction.FromIdAndClient(_transactionId, _client); + } + + return _transaction; + } + } + + public void SinglePhaseCommit(SinglePhaseEnlistment singlePhaseEnlistment) + { + // we receive a commit message + // if we own a local transaction, then commit that transaction + if (_transaction != null) + { + _transaction.Commit(); + _transaction = null; + singlePhaseEnlistment.Committed(); + } + else if (_transactionId > 0) + { + GetResourceManager().CommitTransaction(_transactionId); + singlePhaseEnlistment.Committed(); + } + + _enlistedInTransactions.Remove(Transaction.Current); + } + + public void Rollback(SinglePhaseEnlistment singlePhaseEnlistment) + { + // we receive a commit message + // if we own a local transaction, then commit that transaction + if (_transaction != null) + { + _transaction.Rollback(); + _transaction = null; + } + else if (_transactionId > 0) + { + GetResourceManager().RollbackTransaction(_transactionId); + } + singlePhaseEnlistment.Aborted(); + + _enlistedInTransactions.Remove(Transaction.Current); + } + + // the following was adapted from Npgsql sources: + private static System.Runtime.Remoting.Lifetime.ClientSponsor _sponser; + private static ITransactionResourceManager GetResourceManager() + { + if (_resourceManager == null) + { + _sponser = new System.Runtime.Remoting.Lifetime.ClientSponsor(); + AppDomain rmDomain = AppDomain.CreateDomain("Neo4jTransactionResourceManager", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation); + _resourceManager = (ITransactionResourceManager) rmDomain.CreateInstanceAndUnwrap( + typeof(Neo4jTransactionResourceManager).Assembly.FullName, + typeof(Neo4jTransactionResourceManager).FullName); + _sponser.Register((MarshalByRefObject)_resourceManager); + } + return _resourceManager; + } + } +}