From 1b5fcf2e9917cb31d84bccba523700e40ef939cb Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Mon, 13 Jan 2014 18:07:49 -0800 Subject: [PATCH 01/27] REST Transaction implementation for GraphClient Neo4jClient.GraphClient implements a new interface called ITransactionalGraphClient that exposes the necessary methods to support the REST transactions. There is no need to create multiple GraphClient objects as the Transaction object is held in a thread static static property. This commit also includes a refactoring for the execution mechanism as it was necessary to differentiate policies according if we were in a transaction scope or not. REST methods (create, update, etc...) throw InvalidOperationException when called inside a transaction scope. --- .../ApiModels/Cypher/CypherStatementList.cs | 102 ++ .../Cypher/CypherTransactionStatement.cs | 34 + Neo4jClient/ApiModels/RootApiResponse.cs | 3 + Neo4jClient/ClosedTransactionException.cs | 25 + Neo4jClient/Execution/BatchExecutionPolicy.cs | 28 + .../Execution/CypherExecutionPolicy.cs | 86 ++ .../CypherTransactionExecutionPolicy.cs | 15 + .../Execution/EndpointBuilderExtension.cs | 40 + Neo4jClient/Execution/ErrorGenerator.cs | 11 + .../Execution/ExecutionConfiguration.cs | 13 + .../Execution/ExecutionPolicyFactory.cs | 46 + .../GraphClientBasedExecutionPolicy.cs | 41 + .../Execution/GremlinExecutionPolicy.cs | 28 + .../HttpResponseMessageExtensions.cs | 126 +-- Neo4jClient/Execution/IExecutionPolicy.cs | 22 + .../Execution/IExecutionPolicyFactory.cs | 24 + Neo4jClient/{ => Execution}/IHttpClient.cs | 20 +- Neo4jClient/Execution/IRequestTypeBuilder.cs | 15 + .../IRequestWithPendingContentBuilder.cs | 8 + Neo4jClient/Execution/IResponseBuilder.cs | 30 + .../Execution/IResponseBuilder`TResult.cs | 24 + Neo4jClient/Execution/IResponseFailBuilder.cs | 11 + .../Execution/NodeIndexExecutionPolicy.cs | 17 + .../RelationshipIndexExecutionPolicy.cs | 17 + Neo4jClient/Execution/Request.cs | 10 + Neo4jClient/Execution/RequestTypeBuilder.cs | 40 + .../RequestWithPendingContentBuilder.cs | 42 + Neo4jClient/Execution/ResponseBuilder.cs | 181 ++++ .../Execution/ResponseBuilder`TParse.cs | 85 ++ Neo4jClient/Execution/ResponseFailBuilder.cs | 65 ++ .../Execution/ResponseFailBuilder`TParse.cs | 55 ++ Neo4jClient/Execution/RestExecutionPolicy.cs | 58 ++ Neo4jClient/GraphClient.cs | 887 ++++++++++-------- Neo4jClient/HttpClient.cs | 1 + Neo4jClient/IGraphClient.cs | 26 +- Neo4jClient/IRawGraphClient.cs | 1 + Neo4jClient/Neo4jClient.csproj | 39 +- Neo4jClient/OrphanedTransactionException.cs | 30 + .../Serialization/CustomJsonSerializer.cs | 2 +- .../Serialization/CypherJsonDeserializer.cs | 79 +- Neo4jClient/Serialization/ISerializer.cs | 7 + Neo4jClient/Transactions/INeo4jTransaction.cs | 16 + Neo4jClient/Transactions/ITransaction.cs | 29 + .../Transactions/ITransactionalGraphClient.cs | 36 + Neo4jClient/Transactions/Transaction.cs | 159 ++++ Test/GraphClientTests/ConnectTests.cs | 5 +- Test/GraphClientTests/CreateNodeTests.cs | 14 +- Test/MockResponse.cs | 20 +- Test/RestTestHarness.cs | 52 +- Test/Test.csproj | 3 + .../Transactions/QueriesInTransactionTests.cs | 267 ++++++ Test/Transactions/RestCallFailTests.cs | 162 ++++ .../TransactionManagementTests.cs | 154 +++ 53 files changed, 2787 insertions(+), 524 deletions(-) create mode 100644 Neo4jClient/ApiModels/Cypher/CypherStatementList.cs create mode 100644 Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs create mode 100644 Neo4jClient/ClosedTransactionException.cs create mode 100644 Neo4jClient/Execution/BatchExecutionPolicy.cs create mode 100644 Neo4jClient/Execution/CypherExecutionPolicy.cs create mode 100644 Neo4jClient/Execution/CypherTransactionExecutionPolicy.cs create mode 100644 Neo4jClient/Execution/EndpointBuilderExtension.cs create mode 100644 Neo4jClient/Execution/ErrorGenerator.cs create mode 100644 Neo4jClient/Execution/ExecutionConfiguration.cs create mode 100644 Neo4jClient/Execution/ExecutionPolicyFactory.cs create mode 100644 Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs create mode 100644 Neo4jClient/Execution/GremlinExecutionPolicy.cs rename Neo4jClient/{ => Execution}/HttpResponseMessageExtensions.cs (95%) create mode 100644 Neo4jClient/Execution/IExecutionPolicy.cs create mode 100644 Neo4jClient/Execution/IExecutionPolicyFactory.cs rename Neo4jClient/{ => Execution}/IHttpClient.cs (84%) create mode 100644 Neo4jClient/Execution/IRequestTypeBuilder.cs create mode 100644 Neo4jClient/Execution/IRequestWithPendingContentBuilder.cs create mode 100644 Neo4jClient/Execution/IResponseBuilder.cs create mode 100644 Neo4jClient/Execution/IResponseBuilder`TResult.cs create mode 100644 Neo4jClient/Execution/IResponseFailBuilder.cs create mode 100644 Neo4jClient/Execution/NodeIndexExecutionPolicy.cs create mode 100644 Neo4jClient/Execution/RelationshipIndexExecutionPolicy.cs create mode 100644 Neo4jClient/Execution/Request.cs create mode 100644 Neo4jClient/Execution/RequestTypeBuilder.cs create mode 100644 Neo4jClient/Execution/RequestWithPendingContentBuilder.cs create mode 100644 Neo4jClient/Execution/ResponseBuilder.cs create mode 100644 Neo4jClient/Execution/ResponseBuilder`TParse.cs create mode 100644 Neo4jClient/Execution/ResponseFailBuilder.cs create mode 100644 Neo4jClient/Execution/ResponseFailBuilder`TParse.cs create mode 100644 Neo4jClient/Execution/RestExecutionPolicy.cs create mode 100644 Neo4jClient/OrphanedTransactionException.cs create mode 100644 Neo4jClient/Serialization/ISerializer.cs create mode 100644 Neo4jClient/Transactions/INeo4jTransaction.cs create mode 100644 Neo4jClient/Transactions/ITransaction.cs create mode 100644 Neo4jClient/Transactions/ITransactionalGraphClient.cs create mode 100644 Neo4jClient/Transactions/Transaction.cs create mode 100644 Test/Transactions/QueriesInTransactionTests.cs create mode 100644 Test/Transactions/RestCallFailTests.cs create mode 100644 Test/Transactions/TransactionManagementTests.cs diff --git a/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs b/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs new file mode 100644 index 000000000..e668e00d5 --- /dev/null +++ b/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs @@ -0,0 +1,102 @@ +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)).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..40e35a1f7 --- /dev/null +++ b/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs @@ -0,0 +1,34 @@ +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; + + public CypherTransactionStatement(CypherQuery query) + { + _queryText = query.QueryText; + _queryParameters = query.QueryParameters ?? new Dictionary(); + } + + [JsonProperty("statement")] + public string Statement + { + get { return _queryText; } + } + + [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/ClosedTransactionException.cs b/Neo4jClient/ClosedTransactionException.cs new file mode 100644 index 000000000..bf01df328 --- /dev/null +++ b/Neo4jClient/ClosedTransactionException.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient +{ + 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/Execution/BatchExecutionPolicy.cs b/Neo4jClient/Execution/BatchExecutionPolicy.cs new file mode 100644 index 000000000..75a3d7394 --- /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) + { + } + + 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..bb3976509 --- /dev/null +++ b/Neo4jClient/Execution/CypherExecutionPolicy.cs @@ -0,0 +1,86 @@ +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) + { + } + + public override Uri BaseEndpoint + { + get + { + if (!InTransaction) + { + return Client.CypherEndpoint; + } + + var transactionalClient = (ITransactionalGraphClient) Client; + var startingReference = ((INeo4jTransaction) transactionalClient.Transaction).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 (toSerialize == 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)}); + } + return Client.Serializer.Serialize(new CypherApiQuery(query)); + } + + public override void AfterExecution(IDictionary executionMetadata) + { + if (Client == null || executionMetadata == null || executionMetadata.Count == 0) + { + return; + } + + var transactionalClient = Client as ITransactionalGraphClient; + if (!InTransaction || transactionalClient == null) + { + return; + } + + // determine if we need to update the transaction end point + var transaction = (INeo4jTransaction) transactionalClient.Transaction; + if (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..ad6ff2056 --- /dev/null +++ b/Neo4jClient/Execution/ExecutionConfiguration.cs @@ -0,0 +1,13 @@ +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; } + } +} \ 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..c9f802106 --- /dev/null +++ b/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +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.Transaction != null; + } + } + + public abstract TransactionExecutionPolicy TransactionExecutionPolicy { get; } + public abstract void AfterExecution(IDictionary executionMetadata); + + 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..8f39bd991 --- /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) + { + } + + 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..4f8d17a82 --- /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); + 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 84% rename from Neo4jClient/IHttpClient.cs rename to Neo4jClient/Execution/IHttpClient.cs index 8ae567dd2..5afca78a1 100644 --- a/Neo4jClient/IHttpClient.cs +++ b/Neo4jClient/Execution/IHttpClient.cs @@ -1,10 +1,10 @@ -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); + } +} 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..81d8c6259 --- /dev/null +++ b/Neo4jClient/Execution/IResponseBuilder.cs @@ -0,0 +1,30 @@ +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(Func, TExpected> continuationFunction); + Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction); + + HttpResponseMessage Execute(); + HttpResponseMessage Execute(string commandDescription); + 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..1c13540fb --- /dev/null +++ b/Neo4jClient/Execution/IResponseBuilder`TResult.cs @@ -0,0 +1,24 @@ +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(); + + Task ExecuteAsync(Func, TExpected> continuationFunction); + Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction); + + 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..a9107eb5e --- /dev/null +++ b/Neo4jClient/Execution/ResponseBuilder.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Security.Policy; +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() + { + 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); + } + return _executionConfiguration.HttpClient.SendAsync(_request); + } + + public Task ExecuteAsync() + { + return ExecuteAsync(null, null); + } + + public Task ExecuteAsync(string commandDescription) + { + return ExecuteAsync(commandDescription, null); + } + + public Task ExecuteAsync(Func, HttpResponseMessage> continuationFunction) + { + return ExecuteAsync(null, continuationFunction); + } + + public Task ExecuteAsync(string commandDescription, Func, HttpResponseMessage> continuationFunction) + { + var executionTask = PrepareAsync().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).ContinueWith(continuationFunction); + } + + public HttpResponseMessage Execute() + { + return Execute(null); + } + + public HttpResponseMessage Execute(string commandDescription) + { + var task = ExecuteAsync(commandDescription, null); + 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..4d13bbdfa --- /dev/null +++ b/Neo4jClient/Execution/ResponseBuilder`TParse.cs @@ -0,0 +1,85 @@ +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); + } + + public Task ExecuteAsync(Func, TParse> continuationFunction) + { + return ExecuteAsync(null, continuationFunction); + } + + public Task ExecuteAsync(string commandDescription, Func, TParse> continuationFunction) + { + var executionTask = base.ExecuteAsync(commandDescription) + .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); + } + + public Task ExecuteAsync(Func, TExpected> continuationFunction) + { + return ExecuteAsync(null, continuationFunction); + } + + public Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction) + { + return ExecuteAsync(commandDescription).ContinueWith(continuationFunction); + } + + public new TParse Execute(string commandDescription) + { + return CastIntoResult(base.Execute(commandDescription)); + } + + 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..4219945ce --- /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) + { + } + + 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 163ce1068..5cee16a59 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -8,20 +8,21 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Net.Http.Headers; -using System.Text; +using System.Threading; 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; namespace Neo4jClient { - public class GraphClient : IRawGraphClient + public class GraphClient : IRawGraphClient, ITransactionalGraphClient { 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."; @@ -33,12 +34,14 @@ public class GraphClient : IRawGraphClient new EnumValueConverter() }; + [ThreadStatic] private static Transaction ambientTransaction; + + private 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; public bool UseJsonStreamingIfAvailable { get; set; } @@ -60,17 +63,19 @@ 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); - } - internal string UserAgent { get { return userAgent; } } + ExecutionConfiguration = new ExecutionConfiguration + { + HttpClient = httpClient, + UserAgent = string.Format("Neo4jClient/{0}", GetType().Assembly.GetName().Version), + UseJsonStreaming = true, + JsonConverters = JsonConverters + }; + UseJsonStreamingIfAvailable = true; + policyFactory = new ExecutionPolicyFactory(this); + } Uri BuildUri(string relativeUri) { @@ -84,103 +89,22 @@ 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) - { - return SendHttpRequest(request, null, expectedStatusCodes); - } - - Task SendHttpRequestAsync(HttpRequestMessage request, params HttpStatusCode[] expectedStatusCodes) - { - return SendHttpRequestAsync(request, null, expectedStatusCodes); - } - - HttpResponseMessage SendHttpRequest(HttpRequestMessage request, string commandDescription, params HttpStatusCode[] expectedStatusCodes) + private IDictionary GetMetadataFromResponse(HttpResponseMessage response) { - 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; + return response.Headers.ToDictionary( + headerPair => headerPair.Key, + headerPair => (object)headerPair.Value + ); } - Task SendHttpRequestAsync(HttpRequestMessage request, string commandDescription, params HttpStatusCode[] expectedStatusCodes) + private string SerializeAsJson(object contents) { - if (UseJsonStreamingIfAvailable && jsonStreamingAvailable) - { - 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); - } - - var baseTask = httpClient.SendAsync(request); - var continuationTask = baseTask.ContinueWith(requestTask => - { - var response = requestTask.Result; - response.EnsureExpectedStatusCode(commandDescription, expectedStatusCodes); - return response; - }); - return continuationTask; - } - - T SendHttpRequestAndParseResultAs(HttpRequestMessage request, params HttpStatusCode[] expectedStatusCodes) where T : new() - { - return SendHttpRequestAndParseResultAs(request, null, expectedStatusCodes); + return Serializer.Serialize(contents); } - T SendHttpRequestAndParseResultAs(HttpRequestMessage request, string commandDescription, params HttpStatusCode[] expectedStatusCodes) where T : new() + public virtual bool IsConnected { - var response = SendHttpRequest(request, commandDescription, expectedStatusCodes); - return response.Content == null ? default(T) : response.Content.ReadAsJson(JsonConverters); + get { return RootApiResponse != null; } } public virtual void Connect() @@ -188,9 +112,11 @@ public virtual void Connect() 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)) @@ -204,6 +130,12 @@ 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); + } + if (RootApiResponse.Extensions != null && RootApiResponse.Extensions.GremlinPlugin != null) { RootApiResponse.Extensions.GremlinPlugin.ExecuteScript = @@ -221,10 +153,12 @@ 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 @@ -282,6 +216,8 @@ public virtual NodeReference Create( .ToArray(); CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Batch); + FailDependingOnPolicy(policy); var batchSteps = new List(); @@ -325,7 +261,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) }) @@ -340,8 +276,7 @@ public virtual NodeReference Create( }); } - var batchResponse = ExecuteBatch(batchSteps); - + var batchResponse = ExecuteBatch(batchSteps, policy); var createResponse = batchResponse[createNodeStep]; var nodeId = long.Parse(GetLastPathSegment(createResponse.Location)); var nodeReference = new NodeReference(nodeId, this); @@ -357,13 +292,14 @@ public virtual NodeReference Create( return nodeReference; } - BatchResponse ExecuteBatch(List batchSteps) + 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( @@ -380,62 +316,68 @@ 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) + targetNode.Id)) + ) + .Execute() .ToRelationshipReference(this); } - CustomJsonSerializer BuildSerializer() + public ISerializer Serializer { - return new CustomJsonSerializer { JsonConverters = JsonConverters }; + get { return new CustomJsonSerializer {JsonConverters = JsonConverters}; } } public void DeleteRelationship(RelationshipReference reference) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + FailDependingOnPolicy(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 @@ -456,22 +398,16 @@ public virtual Node Get(NodeReference reference) public virtual Task> GetAsync(NodeReference reference) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + FailDependingOnPolicy(policy); - var nodeEndpoint = ResolveEndpoint(reference); - return - SendHttpRequestAsync(HttpGet(nodeEndpoint), HttpStatusCode.OK, HttpStatusCode.NotFound) - .ContinueWith(responseTask => - { - var response = responseTask.Result; - - if (response.StatusCode == HttpStatusCode.NotFound) - return (Node)null; - - return response - .Content - .ReadAsJson>(JsonConverters) - .ToNode(this); - }); + return 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); } public virtual Node Get(NodeReference reference) @@ -494,27 +430,25 @@ public virtual Node Get(NodeReference reference) public virtual Task> GetAsync(RelationshipReference reference) where TData : class, new() { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + FailDependingOnPolicy(policy); - var endpoint = ResolveEndpoint(reference); - return - SendHttpRequestAsync(HttpGet(endpoint), HttpStatusCode.OK, HttpStatusCode.NotFound) - .ContinueWith(responseTask => - { - var response = responseTask.Result; - - if (response.StatusCode == HttpStatusCode.NotFound) - return (RelationshipInstance)null; - - return response - .Content - .ReadAsJson>(JsonConverters) - .ToRelationshipInstance(this); - }); + return 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) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + FailDependingOnPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -523,10 +457,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); @@ -545,6 +480,8 @@ public Node Update(NodeReference nodeReference, Action> changeCallback = null) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + FailDependingOnPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -558,7 +495,7 @@ public Node Update(NodeReference nodeReference, Action Update(NodeReference nodeReference, Action(RelationshipReference r where TRelationshipData : class, new() { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + FailDependingOnPolicy(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 @@ -627,25 +570,26 @@ public void Update(RelationshipReference r public virtual void Delete(NodeReference reference, DeleteMode mode) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Rest); + FailDependingOnPolicy(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 @@ -656,31 +600,27 @@ public virtual void Delete(NodeReference reference, DeleteMode mode) }); } - void DeleteAllRelationships(NodeReference reference) + 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) @@ -711,12 +651,129 @@ 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; } + private void FailDependingOnPolicy(IExecutionPolicy policy) + { + if (policy.InTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Denied) + { + throw new InvalidOperationException("Cannot be done inside a transaction scope."); + } + + + if (policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Required) + { + throw new InvalidOperationException("Cannot be done outside a transaction scope."); + } + } + + public ITransaction BeginTransaction() + { + if (ServerVersion < new Version(2, 0) || RootApiResponse.Transaction == null) + { + throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); + } + + if (Transaction != null) + { + throw new NotSupportedException("Parallel transactions per GraphClient are not supported."); + } + + Thread.BeginThreadAffinity(); + ambientTransaction = new Transaction(this, policyFactory); + return ambientTransaction; + } + + public ITransaction Transaction + { + get { return ambientTransaction; } + } + + public void EndTransaction() + { + if (Transaction != null) + { + Transaction.Dispose(); + } + + ambientTransaction = null; + Thread.EndThreadAffinity(); + } + [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); + FailDependingOnPolicy(policy); if (RootApiResponse.Extensions.GremlinPlugin == null || RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) @@ -725,11 +782,12 @@ public virtual string ExecuteScalarGremlin(string query, IDictionary ExecuteGetAllProjectionsGremlin(IGremlinQuery query) where TResult : new() { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Gremlin); + FailDependingOnPolicy(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() }; @@ -783,6 +843,19 @@ public virtual IEnumerable ExecuteGetCypherResults(CypherQuery throw new NotImplementedException(); } + private IResponseBuilder PrepareCypherRequest(CypherQuery query, IExecutionPolicy policy) + { + var request = Request.With(ExecutionConfiguration) + .Post(policy.BaseEndpoint) + .WithJsonContent(policy.SerializeRequest(query)); + if (Transaction != null) + { + // HttpStatusCode.Created may be returned when emitting the first query on a transaction + return request.WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created); + } + return request.WithExpectedStatusCodes(HttpStatusCode.OK); + } + IEnumerable IRawGraphClient.ExecuteGetCypherResults(CypherQuery query) { var task = ((IRawGraphClient) this).ExecuteGetCypherResultsAsync(query); @@ -803,46 +876,47 @@ IEnumerable IRawGraphClient.ExecuteGetCypherResults(CypherQuer Task> IRawGraphClient.ExecuteGetCypherResultsAsync(CypherQuery query) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Cypher); + FailDependingOnPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); - - return - SendHttpRequestAsync( - HttpPostAsJson(RootApiResponse.Cypher, new CypherApiQuery(query)), + return PrepareCypherRequest(query, policy) + .ExecuteAsync( string.Format("The query was: {0}", query.QueryText), - HttpStatusCode.OK) - .ContinueWith(responseTask => - { - var response = responseTask.Result; - var deserializer = new CypherJsonDeserializer(this, query.ResultMode); - var results = deserializer - .Deserialize(response.Content.ReadAsString()) - .ToList(); - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs + responseTask => { - QueryText = query.QueryText, - ResourcesReturned = results.Count(), - TimeTaken = stopwatch.Elapsed + var response = responseTask.Result; + policy.AfterExecution(GetMetadataFromResponse(response)); + + var deserializer = new CypherJsonDeserializer(this, query.ResultMode); + var results = deserializer + .Deserialize(response.Content.ReadAsString()) + .ToList(); + + stopwatch.Stop(); + OnOperationCompleted(new OperationCompletedEventArgs + { + QueryText = query.QueryText, + ResourcesReturned = results.Count(), + TimeTaken = stopwatch.Elapsed + }); + + return (IEnumerable)results; }); - - return (IEnumerable)results; - }); } void IRawGraphClient.ExecuteCypher(CypherQuery query) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Cypher); + FailDependingOnPolicy(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 response = PrepareCypherRequest(query, policy).Execute(string.Format("The query was: {0}", query.QueryText)); + policy.AfterExecution(GetMetadataFromResponse(response)); stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs @@ -853,6 +927,35 @@ void IRawGraphClient.ExecuteCypher(CypherQuery query) }); } + void IRawGraphClient.ExecuteMultipleCypherQueriesInTransaction(IEnumerable queries) + { + CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Transaction); + FailDependingOnPolicy(policy); + + var queryList = queries.ToList(); + string queriesInText = string.Join(", ", queryList.Select(query => query.QueryText)); + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + var response = Request.With(ExecutionConfiguration) + .Post(policy.BaseEndpoint) + .WithJsonContent(SerializeAsJson(new CypherStatementList(queryList))) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .Execute("Executing multiple queries: " + queriesInText); + + policy.AfterExecution(GetMetadataFromResponse(response)); + + stopwatch.Stop(); + OnOperationCompleted(new OperationCompletedEventArgs + { + 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) { @@ -864,6 +967,8 @@ public virtual IEnumerable> ExecuteGetAllRelationshi where TData : class, new() { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Gremlin); + FailDependingOnPolicy(policy); if (RootApiResponse.Extensions.GremlinPlugin == null || RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) @@ -872,10 +977,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] @@ -908,6 +1015,8 @@ public virtual IEnumerable> ExecuteGetAllNodesGremlin(string public virtual IEnumerable> ExecuteGetAllNodesGremlin(IGremlinQuery query) { CheckRoot(); + var policy = policyFactory.GetPolicy(PolicyType.Gremlin); + FailDependingOnPolicy(policy); if (RootApiResponse.Extensions.GremlinPlugin == null || RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) @@ -916,10 +1025,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] @@ -936,55 +1047,50 @@ 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); + FailDependingOnPolicy(policy); + return policy.BaseEndpoint; + } - if(response.StatusCode == HttpStatusCode.NoContent) - return new Dictionary(); + public Dictionary GetIndexes(IndexFor indexFor) + { + CheckRoot(); - var result = response.Content.ReadAsJson>(JsonConverters); + 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; } @@ -1000,50 +1106,43 @@ 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(); + FailDependingOnPolicy(policy); var updates = indexEntries .SelectMany( @@ -1053,61 +1152,51 @@ public void ReIndex(string entityUri, long entityId, IndexFor indexFor, IEnumera .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); + FailDependingOnPolicy(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) + void DeleteIndexEntries(string indexName, long id, Uri indexUri) { - var indexResponse = indexFor == IndexFor.Node - ? RootApiResponse.NodeIndex - : RootApiResponse.RelationshipIndex; - - var indexAddress = string.Join("/", new[] - { - indexResponse, - Uri.EscapeDataString(indexName), - Uri.EscapeDataString(id.ToString(CultureInfo.InvariantCulture)) - }); + var indexAddress = indexUri + .AddPath(Uri.EscapeDataString(indexName)) + .AddPath(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) @@ -1122,27 +1211,28 @@ 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) @@ -1173,30 +1263,16 @@ static string EncodeIndexValue(object value) 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).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) @@ -1212,33 +1288,20 @@ public IEnumerable> LookupIndex(string exactIndexName, IndexF IEnumerable> BuildLookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, string id) { CheckRoot(); + var indexResource = GetUriForIndexType(indexFor) + .AddPath(exactIndexName) + .AddPath(indexKey) + .AddPath(id.ToString()); - 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).Deserialize>>(response.Content.ReadAsString()); - - return data == null - ? Enumerable.Empty>() - : data.Select(r => r.ToNode(this)); + return Request.With(ExecutionConfiguration) + .Get(indexResource) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ParseAs>>() + .Execute() + .Select(query => query.ToNode(this)); } - [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.")] public void ShutdownServer() { ExecuteScalarGremlin("g.getRawGraph().shutdown()", null); diff --git a/Neo4jClient/HttpClient.cs b/Neo4jClient/HttpClient.cs index a76aec00f..cd95ea632 100644 --- a/Neo4jClient/HttpClient.cs +++ b/Neo4jClient/HttpClient.cs @@ -1,5 +1,6 @@ using System.Net.Http; using System.Threading.Tasks; +using Neo4jClient.Execution; namespace Neo4jClient { diff --git a/Neo4jClient/IGraphClient.cs b/Neo4jClient/IGraphClient.cs index 737ff621a..2fdb653ee 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; namespace Neo4jClient @@ -108,7 +111,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; @@ -122,6 +125,27 @@ RelationshipReference CreateRelationship(NodeReferen Version ServerVersion { 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; } + } } diff --git a/Neo4jClient/IRawGraphClient.cs b/Neo4jClient/IRawGraphClient.cs index 5c8932ad5..f98eb4304 100644 --- a/Neo4jClient/IRawGraphClient.cs +++ b/Neo4jClient/IRawGraphClient.cs @@ -14,5 +14,6 @@ public interface IRawGraphClient : IGraphClient IEnumerable ExecuteGetCypherResults(CypherQuery query); Task> ExecuteGetCypherResultsAsync(CypherQuery query); void ExecuteCypher(CypherQuery query); + void ExecuteMultipleCypherQueriesInTransaction(IEnumerable queries); } } diff --git a/Neo4jClient/Neo4jClient.csproj b/Neo4jClient/Neo4jClient.csproj index 9529fa837..dea14d458 100644 --- a/Neo4jClient/Neo4jClient.csproj +++ b/Neo4jClient/Neo4jClient.csproj @@ -63,12 +63,15 @@ + + + @@ -87,13 +90,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - @@ -114,6 +143,7 @@ + @@ -198,6 +228,11 @@ + + + + + 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/Serialization/CustomJsonSerializer.cs b/Neo4jClient/Serialization/CustomJsonSerializer.cs index cb36fb0e2..19ff46c5f 100644 --- a/Neo4jClient/Serialization/CustomJsonSerializer.cs +++ b/Neo4jClient/Serialization/CustomJsonSerializer.cs @@ -5,7 +5,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 52bac7f17..a0243eaa6 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; @@ -28,10 +29,25 @@ public IEnumerable Deserialize(string content) { try { + 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 + }; + // 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(); + var transactionalClient = client as ITransactionalGraphClient; + return transactionalClient == null || transactionalClient.Transaction == null ? + DeserializeFromRoot(content, reader, context).ToArray() : + DeserializeFromResults(content, reader, context).ToArray(); } catch (Exception ex) { @@ -62,22 +78,9 @@ Include the full type definition of {0}. } } - IEnumerable DeserializeInternal(string content) + IEnumerable DeserializeResultSet(JToken resultRoot, DeserializationContext context) { - 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; - - var columnsArray = (JArray)root["columns"]; + var columnsArray = (JArray)resultRoot["columns"]; var columnNames = columnsArray .Children() .Select(c => c.AsString()) @@ -114,7 +117,7 @@ IEnumerable DeserializeInternal(string content) switch (resultMode) { case CypherResultMode.Set: - return ParseInSingleColumnMode(context, root, columnNames, jsonTypeMappings.ToArray()); + return ParseInSingleColumnMode(context, resultRoot, columnNames, jsonTypeMappings.ToArray()); case CypherResultMode.Projection: jsonTypeMappings.Add(new TypeMapping { @@ -125,12 +128,52 @@ IEnumerable DeserializeInternal(string content) MutationCallback = n => n.GetType().GetProperty("Data").GetGetMethod().Invoke(n, new object[0]) }); - return ParseInProjectionMode(context, root, columnNames, jsonTypeMappings.ToArray()); + return ParseInProjectionMode(context, resultRoot, columnNames, jsonTypeMappings.ToArray()); default: throw new NotSupportedException(string.Format("Unrecognised result mode of {0}.", resultMode)); } } + private IEnumerable DeserializeFromResults(string content, JsonTextReader reader, DeserializationContext context) + { + var root = JToken.ReadFrom(reader).Root as JObject; + if (root == null) + { + throw new InvalidOperationException("Root expected to be a JSON object."); + } + var results = root + .Properties() + .Single(property => property.Name == "results") + .Value as JArray; + if (results == null) + { + throw new InvalidOperationException("`results` property expected to a JSON array."); + } + + if (results.Count == 0) + { + 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." + ); + } + + // 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) + return DeserializeResultSet(results.First, 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 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/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..571c921e1 --- /dev/null +++ b/Neo4jClient/Transactions/ITransaction.cs @@ -0,0 +1,29 @@ +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(); + } +} diff --git a/Neo4jClient/Transactions/ITransactionalGraphClient.cs b/Neo4jClient/Transactions/ITransactionalGraphClient.cs new file mode 100644 index 000000000..653f7e951 --- /dev/null +++ b/Neo4jClient/Transactions/ITransactionalGraphClient.cs @@ -0,0 +1,36 @@ +using System; + +namespace Neo4jClient.Transactions +{ + /// + /// 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. + /// + /// + /// This method should only be used when multiple executing multiple Cypher queries + /// in multiple HTTP requests. Neo4j already encapsulates a single Cypher request within its + /// own transaction. + /// + ITransaction BeginTransaction(); + + /// + /// The current transaction object. + /// + ITransaction Transaction { 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/Transaction.cs b/Neo4jClient/Transactions/Transaction.cs new file mode 100644 index 000000000..09aeea437 --- /dev/null +++ b/Neo4jClient/Transactions/Transaction.cs @@ -0,0 +1,159 @@ +using System; +using System.Net; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Execution; + +namespace Neo4jClient.Transactions +{ + /// + /// Implements the Neo4j HTTP transaction for multiple HTTP requests + /// + class Transaction : INeo4jTransaction + { + private bool _isOpen; + private bool _disposing; + private readonly ITransactionalGraphClient _graphClient; + private readonly IExecutionPolicyFactory _policyFactory; + + public Uri Endpoint { get; set; } + + public Transaction(ITransactionalGraphClient graphClient) + : this(graphClient, new ExecutionPolicyFactory(graphClient)) + { + } + + public Transaction(ITransactionalGraphClient graphClient, IExecutionPolicyFactory policyFactory) + { + _isOpen = true; + Endpoint = null; + _graphClient = graphClient; + _disposing = false; + _policyFactory = policyFactory; + } + + private void CheckForOpenTransaction() + { + if (_isOpen) + { + return; + } + + string endPointText = null; + if (Endpoint != null) + { + endPointText = Endpoint.ToString(); + } + throw new ClosedTransactionException(endPointText); + } + + private void CleanupAfterClosedTransaction() + { + _isOpen = false; + _graphClient.EndTransaction(); + _disposing = false; + } + + /// + /// Dispose our current transaction, rolling back if it is still open. + /// + public void Dispose() + { + if (_disposing) + { + return; + } + + if (_isOpen && Endpoint != null) + { + Rollback(); + } + else + { + _disposing = true; + CleanupAfterClosedTransaction(); + } + } + + /// + /// Commits our current transaction and closes the transaction. + /// + public void Commit() + { + if (_disposing) + { + return; + } + _disposing = true; + + CheckForOpenTransaction(); + // we have to check for an empty endpoint because we dont have one until our first request + if (Endpoint == null) + { + CleanupAfterClosedTransaction(); + return; + } + + var policy = _policyFactory.GetPolicy(PolicyType.Cypher); + Request.With(_graphClient.ExecutionConfiguration) + .Post(policy.BaseEndpoint.AddPath("commit")) + .WithJsonContent(_graphClient.Serializer.Serialize(new CypherStatementList())) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .Execute(); + + CleanupAfterClosedTransaction(); + } + + /// + /// Rolls back our current transaction and closes the transaction. + /// + public void Rollback() + { + if (_disposing) + { + return; + } + _disposing = true; + + CheckForOpenTransaction(); + // we have to check for an empty endpoint because we dont have one until our first request + if (Endpoint == null) + { + CleanupAfterClosedTransaction(); + return; + } + + var policy = _policyFactory.GetPolicy(PolicyType.Cypher); + Request.With(_graphClient.ExecutionConfiguration) + .Delete(policy.BaseEndpoint) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .Execute(); + + CleanupAfterClosedTransaction(); + } + + /// + /// Emits an empty request to keep alive our current transaction. + /// + public void KeepAlive() + { + if (_disposing) + { + return; + } + + CheckForOpenTransaction(); + // no need to issue a request as we haven't sent a single request + if (Endpoint == null) + { + return; + } + + var policy = _policyFactory.GetPolicy(PolicyType.Cypher); + Request.With(_graphClient.ExecutionConfiguration) + .Post(policy.BaseEndpoint) + .WithJsonContent(_graphClient.Serializer.Serialize(new CypherStatementList())) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .Execute(); + } + } +} diff --git a/Test/GraphClientTests/ConnectTests.cs b/Test/GraphClientTests/ConnectTests.cs index 075aa7d65..d9cbf5304 100644 --- a/Test/GraphClientTests/ConnectTests.cs +++ b/Test/GraphClientTests/ConnectTests.cs @@ -5,6 +5,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Neo4jClient.Cypher; +using Neo4jClient.Execution; using NSubstitute; using NUnit.Framework; @@ -186,7 +187,7 @@ public void ShouldSendCustomUserAgent() // Arrange var httpClient = Substitute.For(); var graphClient = new GraphClient(new Uri("http://localhost"), httpClient); - var expectedUserAgent = graphClient.UserAgent; + var expectedUserAgent = graphClient.ExecutionConfiguration.UserAgent; httpClient .SendAsync(Arg.Do(message => { @@ -227,7 +228,7 @@ public void ShouldSendCustomUserAgent() public void ShouldFormatUserAgentCorrectly() { var graphClient = new GraphClient(new Uri("http://localhost")); - var userAgent = graphClient.UserAgent; + 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"); } } diff --git a/Test/GraphClientTests/CreateNodeTests.cs b/Test/GraphClientTests/CreateNodeTests.cs index 712b503d5..a3a83d129 100644 --- a/Test/GraphClientTests/CreateNodeTests.cs +++ b/Test/GraphClientTests/CreateNodeTests.cs @@ -91,7 +91,7 @@ public void ShouldNotThrowANotSupportedExceptionForPre15M02DatabaseWhenThereAreN graphClient.Create(testNode, null, null); - testHarness.AssertAllRequestsWereReceived(); + testHarness.AssertRequestConstraintsAreMet(); } [Test] @@ -141,7 +141,7 @@ public void ShouldSerializeAllProperties() graphClient.Create(new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }); - testHarness.AssertAllRequestsWereReceived(); + testHarness.AssertRequestConstraintsAreMet(); } [Test] @@ -187,7 +187,7 @@ public void ShouldPreserveUnicodeCharactersInStringProperties() graphClient.Create(new TestNode { Foo = "foo東京", Bar = "bar", Baz = "baz" }); - testHarness.AssertAllRequestsWereReceived(); + testHarness.AssertRequestConstraintsAreMet(); } [Test] @@ -232,7 +232,7 @@ public void ShouldReturnReferenceToCreatedNode() var node = graphClient.Create(testNode); Assert.AreEqual(760, node.Id); - testHarness.AssertAllRequestsWereReceived(); + testHarness.AssertRequestConstraintsAreMet(); } [Test] @@ -277,7 +277,7 @@ public void ShouldReturnReferenceToCreatedNodeWithLongId() var node = graphClient.Create(testNode); Assert.AreEqual(2157483647, node.Id); - testHarness.AssertAllRequestsWereReceived(); + testHarness.AssertRequestConstraintsAreMet(); } [Test] @@ -384,7 +384,7 @@ public void ShouldCreateOutgoingRelationship() testNode, new TestRelationship(789, testRelationshipPayload)); - testHarness.AssertAllRequestsWereReceived(); + testHarness.AssertRequestConstraintsAreMet(); } [Test] @@ -527,7 +527,7 @@ public void ShouldCreateIncomingRelationship() testNode, new TestRelationship(789, testRelationshipPayload)); - testHarness.AssertAllRequestsWereReceived(); + testHarness.AssertRequestConstraintsAreMet(); } public class TestNode diff --git a/Test/MockResponse.cs b/Test/MockResponse.cs index 3feb2e2a9..7cccc34ba 100644 --- a/Test/MockResponse.cs +++ b/Test/MockResponse.cs @@ -15,14 +15,31 @@ public string StatusDescription public string ContentType { get; set; } public 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 + Content = json, + Location = location }; } @@ -55,6 +72,7 @@ public static MockResponse NeoRoot20() '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' : {} }"); diff --git a/Test/RestTestHarness.cs b/Test/RestTestHarness.cs index de72e4b5f..d7643139d 100644 --- a/Test/RestTestHarness.cs +++ b/Test/RestTestHarness.cs @@ -2,8 +2,12 @@ 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.Tasks; +using Neo4jClient.Execution; +using Neo4jClient.Transactions; using NSubstitute; using NUnit.Framework; @@ -12,6 +16,7 @@ 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"; @@ -21,10 +26,16 @@ public void Add(MockRequest request, MockResponse response) recordedResponses.Add(request, response); } - public GraphClient CreateGraphClient() + 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(""), MockResponse.NeoRoot()); + Add(MockRequest.Get(""), neo4j2 ? MockResponse.NeoRoot20() : MockResponse.NeoRoot()); var httpClient = GenerateHttpClient(BaseUri); @@ -32,9 +43,16 @@ public GraphClient CreateGraphClient() return graphClient; } + public ITransactionalGraphClient CreateAndConnectTransactionalGraphClient() + { + var graphClient = CreateGraphClient(true); + graphClient.Connect(); + return graphClient; + } + public IRawGraphClient CreateAndConnectGraphClient() { - var graphClient = CreateGraphClient(); + var graphClient = CreateGraphClient(false); graphClient.Connect(); return graphClient; } @@ -44,17 +62,29 @@ 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() + 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)) + .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; @@ -131,12 +161,20 @@ HttpResponseMessage HandleRequest(HttpRequestMessage request, string baseUri) var response = result.Value; - return new HttpResponseMessage + 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) @@ -163,7 +201,7 @@ static string NormalizeJson(string input) public void Dispose() { - AssertAllRequestsWereReceived(); + AssertRequestConstraintsAreMet(); } } } diff --git a/Test/Test.csproj b/Test/Test.csproj index 3331e45b0..eb83265e7 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -158,6 +158,9 @@ + + + diff --git a/Test/Transactions/QueriesInTransactionTests.cs b/Test/Transactions/QueriesInTransactionTests.cs new file mode 100644 index 000000000..b9ae7ae2b --- /dev/null +++ b/Test/Transactions/QueriesInTransactionTests.cs @@ -0,0 +1,267 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Neo4jClient.Transactions; +using NUnit.Framework; + +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) + { + return string.Format( + @"{{'commit': 'http://foo/db/data/transaction/{0}/commit', 'results': [], 'errors': [], 'transaction': {{ 'expires': '{1}' }} }}", + id, + ResetTransactionTimer() + ); + } + + [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)', '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"), ((Transaction) transaction).Endpoint); + } + } + } + + [Test] + public void TransactionCommit() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', '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 SecondRequestDoesntReturnCreateHttpStatus() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + var secondRequest = MockRequest.PostJson("/transaction/1", @"{ + 'statements': [{'statement': 'MATCH t\r\nRETURN count(t)', '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)', '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 OnTransactionDisposeCallRollback() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', '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(); + } + } + } + } +} diff --git a/Test/Transactions/RestCallFailTests.cs b/Test/Transactions/RestCallFailTests.cs new file mode 100644 index 000000000..15feb1dc2 --- /dev/null +++ b/Test/Transactions/RestCallFailTests.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Neo4jClient.Test.Relationships; +using NUnit.Framework; + +namespace Neo4jClient.Test.Transactions +{ + [TestFixture] + public class RestCallFailTests + { + private class TestNode + { + public string Foo { get; set; } + } + + private void ExecuteRestMethodUnderTransaction(Action restAction) + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + 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)); + } + } +} diff --git a/Test/Transactions/TransactionManagementTests.cs b/Test/Transactions/TransactionManagementTests.cs new file mode 100644 index 000000000..db76b1b16 --- /dev/null +++ b/Test/Transactions/TransactionManagementTests.cs @@ -0,0 +1,154 @@ +using System; +using System.Threading; +using System.Threading.Tasks; +using Neo4jClient.Transactions; +using NUnit.Framework; + +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 ShouldNotBeAbleToGetTransactionAfterTransactionScope() + { + using (var testHarness = new RestTestHarness()) + { + var client = testHarness.CreateAndConnectTransactionalGraphClient(); + using (var transaction = client.BeginTransaction()) + { + + } + + Assert.IsNull(client.Transaction); + } + } + + [Test] + [ExpectedException(typeof (ClosedTransactionException))] + public void ShouldNotBeAbleToCommitTwice() + { + var transaction = new Transaction(new GraphClient(new Uri("http://foo/db/data")), null); + transaction.Commit(); + transaction.Commit(); + } + + [Test] + [ExpectedException(typeof (ClosedTransactionException))] + public void ShouldNotBeAbleToRollbackTwice() + { + var transaction = new Transaction(new GraphClient(new Uri("http://foo/db/data")), null); + transaction.Rollback(); + transaction.Rollback(); + } + + [Test] + [ExpectedException(typeof (ClosedTransactionException))] + public void ShouldNotBeAbleToCommitAfterRollback() + { + var transaction = new Transaction(new GraphClient(new Uri("http://foo/db/data")), null); + transaction.Rollback(); + transaction.Commit(); + } + + [Test] + [ExpectedException(typeof (ClosedTransactionException))] + public void ShouldNotBeAbleToRollbackAfterCommit() + { + var transaction = new Transaction(new GraphClient(new Uri("http://foo/db/data")), null); + 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); + + } + } + } +} From 375cd864b9edd9b58cec7d0a640c9d56668e4e46 Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Tue, 14 Jan 2014 15:28:25 -0800 Subject: [PATCH 02/27] Neo4j Scoped Transactions This commit enables transaction scoping with three options: * TransactionScopeOption.Join: Joins the current transaction or creates a new one. * TransactionScopeOption.RequiresNew: Always creates a new transaction. * TransactionScopeOption.Suppress: Disables a current transaction. --- .../Execution/CypherExecutionPolicy.cs | 33 ++- .../GraphClientBasedExecutionPolicy.cs | 2 +- Neo4jClient/GraphClient.cs | 131 +++++++++- Neo4jClient/Neo4jClient.csproj | 10 +- Neo4jClient/Transactions/ITransaction.cs | 6 + .../Transactions/ITransactionalGraphClient.cs | 67 ++++- .../{Transaction.cs => Neo4jTransaction.cs} | 97 +++----- .../Transactions/Neo4jTransactionProxy.cs | 54 +++++ .../Transactions/SuppressTransactionProxy.cs | 46 ++++ .../Transactions/TransactionScopeProxy.cs | 60 +++++ Test/Test.csproj | 2 +- .../Transactions/QueriesInTransactionTests.cs | 4 +- ...lFailTests.cs => RestCallScenarioTests.cs} | 48 +++- .../TransactionManagementTests.cs | 229 +++++++++++++++++- 14 files changed, 689 insertions(+), 100 deletions(-) rename Neo4jClient/Transactions/{Transaction.cs => Neo4jTransaction.cs} (52%) create mode 100644 Neo4jClient/Transactions/Neo4jTransactionProxy.cs create mode 100644 Neo4jClient/Transactions/SuppressTransactionProxy.cs create mode 100644 Neo4jClient/Transactions/TransactionScopeProxy.cs rename Test/Transactions/{RestCallFailTests.cs => RestCallScenarioTests.cs} (70%) diff --git a/Neo4jClient/Execution/CypherExecutionPolicy.cs b/Neo4jClient/Execution/CypherExecutionPolicy.cs index bb3976509..db4e4ffbf 100644 --- a/Neo4jClient/Execution/CypherExecutionPolicy.cs +++ b/Neo4jClient/Execution/CypherExecutionPolicy.cs @@ -16,6 +16,22 @@ public CypherExecutionPolicy(IGraphClient client) : base(client) { } + private INeo4jTransaction GetTransactionInScope() + { + var transactionalClient = Client as ITransactionalGraphClient; + if (transactionalClient == null) + { + return null; + } + var proxiedTransaction = transactionalClient.Transaction as TransactionScopeProxy; + if (proxiedTransaction == null) + { + return null; + } + + return (INeo4jTransaction) proxiedTransaction.Transaction; + } + public override Uri BaseEndpoint { get @@ -25,9 +41,14 @@ public override Uri BaseEndpoint return Client.CypherEndpoint; } + var proxiedTransaction = GetTransactionInScope(); var transactionalClient = (ITransactionalGraphClient) Client; - var startingReference = ((INeo4jTransaction) transactionalClient.Transaction).Endpoint ?? - transactionalClient.TransactionEndpoint; + if (proxiedTransaction == null) + { + return transactionalClient.TransactionEndpoint; + } + + var startingReference = proxiedTransaction.Endpoint ?? transactionalClient.TransactionEndpoint; return startingReference; } } @@ -67,8 +88,12 @@ public override void AfterExecution(IDictionary executionMetadat } // determine if we need to update the transaction end point - var transaction = (INeo4jTransaction) transactionalClient.Transaction; - if (transaction.Endpoint != null) return; + var transaction = GetTransactionInScope(); + if (transaction == null || transaction.Endpoint != null) + { + return; + } + var locationHeader = executionMetadata["Location"] as IEnumerable; if (locationHeader == null) { diff --git a/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs b/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs index c9f802106..814f55564 100644 --- a/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs +++ b/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs @@ -20,7 +20,7 @@ public bool InTransaction get { var transactionalGraphClient = Client as ITransactionalGraphClient; - return transactionalGraphClient != null && transactionalGraphClient.Transaction != null; + return transactionalGraphClient != null && transactionalGraphClient.InTransaction; } } diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index 5cee16a59..584a42aac 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -22,7 +22,7 @@ namespace Neo4jClient { - public class GraphClient : IRawGraphClient, ITransactionalGraphClient + public class GraphClient : IRawGraphClient, ITransactionalGraphClient, 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."; @@ -34,7 +34,8 @@ public class GraphClient : IRawGraphClient, ITransactionalGraphClient new EnumValueConverter() }; - [ThreadStatic] private static Transaction ambientTransaction; + // holds the transaction objects + [ThreadStatic] private static Stack scopedTransactions; private IExecutionPolicyFactory policyFactory; public ExecutionConfiguration ExecutionConfiguration { get; private set; } @@ -109,6 +110,11 @@ public virtual bool IsConnected public virtual void Connect() { + if (IsConnected) + { + return; + } + var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -134,6 +140,8 @@ public virtual void Connect() if (!string.IsNullOrEmpty(RootApiResponse.Transaction)) { RootApiResponse.Transaction = RootApiResponse.Transaction.Substring(baseUriLengthToTrim); + Thread.BeginThreadAffinity(); + scopedTransactions = new Stack(); } if (RootApiResponse.Extensions != null && RootApiResponse.Extensions.GremlinPlugin != null) @@ -737,35 +745,127 @@ private void FailDependingOnPolicy(IExecutionPolicy policy) public ITransaction BeginTransaction() { + return BeginTransaction(TransactionScopeOption.Join); + } + + public ITransaction BeginTransaction(TransactionScopeOption scopeOption) + { + CheckRoot(); + + if (scopeOption == TransactionScopeOption.Suppress) + { + // TransactionScopeOption.Suppress doesn't fail with older versions of Neo4j + return BeginSupressTransaction(); + } + if (ServerVersion < new Version(2, 0) || RootApiResponse.Transaction == null) { throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); } - if (Transaction != null) + 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 void PushScopeTransaction(TransactionScopeProxy transaction) + { + if (scopedTransactions == null) { - throw new NotSupportedException("Parallel transactions per GraphClient are not supported."); + scopedTransactions = new Stack(); } + scopedTransactions.Push(transaction); + } - Thread.BeginThreadAffinity(); - ambientTransaction = new Transaction(this, policyFactory); - return ambientTransaction; + private ITransaction BeginNewTransaction() + { + var transaction = new Neo4jTransactionProxy(this, new Neo4jTransaction(this), true); + PushScopeTransaction(transaction); + return transaction; + } + + private ITransaction BeginJoinTransaction() + { + var parentScope = InternalTransaction; + if (parentScope == null) + { + return null; + } + + if (!parentScope.Committable) + { + return null; + } + + if (!parentScope.IsOpen) + { + throw new ClosedTransactionException(null); + } + + var joinedTransaction = new Neo4jTransactionProxy(this, parentScope.Transaction, false); + PushScopeTransaction(joinedTransaction); + return joinedTransaction; + } + + private ITransaction BeginSupressTransaction() + { + var suppressTransaction = new SuppressTransactionProxy(this); + PushScopeTransaction(suppressTransaction); + return suppressTransaction; + } + + private TransactionScopeProxy InternalTransaction + { + get + { + try + { + return scopedTransactions == null ? null : scopedTransactions.Peek(); + } + catch (InvalidOperationException) + { + // the stack is empty + return null; + } + } } public ITransaction Transaction { - get { return ambientTransaction; } + get { return InternalTransaction; } } - public void EndTransaction() + public bool InTransaction { - if (Transaction != null) + get { - Transaction.Dispose(); + var transactionObject = InternalTransaction; + return transactionObject != null && transactionObject.Committable; } + } - ambientTransaction = null; - Thread.EndThreadAffinity(); + public void EndTransaction() + { + TransactionScopeProxy currentTransaction = null; + try + { + currentTransaction = scopedTransactions == null ? null : scopedTransactions.Pop(); + } + catch (InvalidOperationException) + { + } + if (currentTransaction != null) + { + currentTransaction.Dispose(); + } } [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] @@ -1315,5 +1415,10 @@ protected void OnOperationCompleted(OperationCompletedEventArgs args) if (eventInstance != null) eventInstance(this, args); } + + public void Dispose() + { + Thread.EndThreadAffinity(); + } } } diff --git a/Neo4jClient/Neo4jClient.csproj b/Neo4jClient/Neo4jClient.csproj index dea14d458..49de64116 100644 --- a/Neo4jClient/Neo4jClient.csproj +++ b/Neo4jClient/Neo4jClient.csproj @@ -50,6 +50,7 @@ ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + @@ -232,7 +233,14 @@ - + + + + + + + + diff --git a/Neo4jClient/Transactions/ITransaction.cs b/Neo4jClient/Transactions/ITransaction.cs index 571c921e1..691f7e288 100644 --- a/Neo4jClient/Transactions/ITransaction.cs +++ b/Neo4jClient/Transactions/ITransaction.cs @@ -25,5 +25,11 @@ public interface ITransaction : IDisposable /// 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/ITransactionalGraphClient.cs b/Neo4jClient/Transactions/ITransactionalGraphClient.cs index 653f7e951..637a6d759 100644 --- a/Neo4jClient/Transactions/ITransactionalGraphClient.cs +++ b/Neo4jClient/Transactions/ITransactionalGraphClient.cs @@ -2,6 +2,16 @@ 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. @@ -9,20 +19,73 @@ namespace Neo4jClient.Transactions public interface ITransactionalGraphClient : IGraphClient { /// - /// Scopes the next cypher queries within a transaction. + /// Scopes the next cypher queries within a transaction, or joins an existing one. /// /// - /// This method should only be used when multiple executing multiple Cypher queries + /// 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. /// diff --git a/Neo4jClient/Transactions/Transaction.cs b/Neo4jClient/Transactions/Neo4jTransaction.cs similarity index 52% rename from Neo4jClient/Transactions/Transaction.cs rename to Neo4jClient/Transactions/Neo4jTransaction.cs index 09aeea437..58cf79e0b 100644 --- a/Neo4jClient/Transactions/Transaction.cs +++ b/Neo4jClient/Transactions/Neo4jTransaction.cs @@ -8,32 +8,29 @@ namespace Neo4jClient.Transactions /// /// Implements the Neo4j HTTP transaction for multiple HTTP requests /// - class Transaction : INeo4jTransaction + class Neo4jTransaction : INeo4jTransaction { - private bool _isOpen; - private bool _disposing; - private readonly ITransactionalGraphClient _graphClient; - private readonly IExecutionPolicyFactory _policyFactory; + private readonly ITransactionalGraphClient _client; + + public bool IsOpen { get; private set; } public Uri Endpoint { get; set; } - public Transaction(ITransactionalGraphClient graphClient) - : this(graphClient, new ExecutionPolicyFactory(graphClient)) + public Neo4jTransaction(ITransactionalGraphClient graphClient) { + Endpoint = null; + IsOpen = true; + _client = graphClient; } - public Transaction(ITransactionalGraphClient graphClient, IExecutionPolicyFactory policyFactory) + protected void CleanupAfterClosedTransaction() { - _isOpen = true; - Endpoint = null; - _graphClient = graphClient; - _disposing = false; - _policyFactory = policyFactory; + IsOpen = false; } private void CheckForOpenTransaction() { - if (_isOpen) + if (IsOpen) { return; } @@ -46,45 +43,11 @@ private void CheckForOpenTransaction() throw new ClosedTransactionException(endPointText); } - private void CleanupAfterClosedTransaction() - { - _isOpen = false; - _graphClient.EndTransaction(); - _disposing = false; - } - - /// - /// Dispose our current transaction, rolling back if it is still open. - /// - public void Dispose() - { - if (_disposing) - { - return; - } - - if (_isOpen && Endpoint != null) - { - Rollback(); - } - else - { - _disposing = true; - CleanupAfterClosedTransaction(); - } - } - /// /// Commits our current transaction and closes the transaction. /// public void Commit() { - if (_disposing) - { - return; - } - _disposing = true; - CheckForOpenTransaction(); // we have to check for an empty endpoint because we dont have one until our first request if (Endpoint == null) @@ -93,10 +56,9 @@ public void Commit() return; } - var policy = _policyFactory.GetPolicy(PolicyType.Cypher); - Request.With(_graphClient.ExecutionConfiguration) - .Post(policy.BaseEndpoint.AddPath("commit")) - .WithJsonContent(_graphClient.Serializer.Serialize(new CypherStatementList())) + Request.With(_client.ExecutionConfiguration) + .Post(Endpoint.AddPath("commit")) + .WithJsonContent(_client.Serializer.Serialize(new CypherStatementList())) .WithExpectedStatusCodes(HttpStatusCode.OK) .Execute(); @@ -108,12 +70,6 @@ public void Commit() /// public void Rollback() { - if (_disposing) - { - return; - } - _disposing = true; - CheckForOpenTransaction(); // we have to check for an empty endpoint because we dont have one until our first request if (Endpoint == null) @@ -122,9 +78,8 @@ public void Rollback() return; } - var policy = _policyFactory.GetPolicy(PolicyType.Cypher); - Request.With(_graphClient.ExecutionConfiguration) - .Delete(policy.BaseEndpoint) + Request.With(_client.ExecutionConfiguration) + .Delete(Endpoint) .WithExpectedStatusCodes(HttpStatusCode.OK) .Execute(); @@ -136,11 +91,6 @@ public void Rollback() /// public void KeepAlive() { - if (_disposing) - { - return; - } - CheckForOpenTransaction(); // no need to issue a request as we haven't sent a single request if (Endpoint == null) @@ -148,12 +98,19 @@ public void KeepAlive() return; } - var policy = _policyFactory.GetPolicy(PolicyType.Cypher); - Request.With(_graphClient.ExecutionConfiguration) - .Post(policy.BaseEndpoint) - .WithJsonContent(_graphClient.Serializer.Serialize(new CypherStatementList())) + Request.With(_client.ExecutionConfiguration) + .Post(Endpoint) + .WithJsonContent(_client.Serializer.Serialize(new CypherStatementList())) .WithExpectedStatusCodes(HttpStatusCode.OK) .Execute(); } + + public void Dispose() + { + if (IsOpen) + { + Rollback(); + } + } } } diff --git a/Neo4jClient/Transactions/Neo4jTransactionProxy.cs b/Neo4jClient/Transactions/Neo4jTransactionProxy.cs new file mode 100644 index 000000000..4d72bdc3a --- /dev/null +++ b/Neo4jClient/Transactions/Neo4jTransactionProxy.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Neo4jClient.Transactions +{ + internal class Neo4jTransactionProxy : TransactionScopeProxy + { + private readonly bool _doCommitInScope; + + public Neo4jTransactionProxy(ITransactionalGraphClient client, ITransaction transaction, bool newScope) + : base(client, transaction) + { + _doCommitInScope = newScope; + } + + protected override void DoCommit() + { + if (_doCommitInScope) + { + Transaction.Commit(); + } + } + + protected override bool ShouldDisposeTransaction() + { + return _doCommitInScope; + } + + public override bool Committable + { + get { return true; } + } + + public override void Rollback() + { + Transaction.Rollback(); + } + + public override void KeepAlive() + { + Transaction.KeepAlive(); + } + + public override bool IsOpen + { + get + { + return Transaction != null && Transaction.IsOpen; + } + } + } +} 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/TransactionScopeProxy.cs b/Neo4jClient/Transactions/TransactionScopeProxy.cs new file mode 100644 index 000000000..508b57635 --- /dev/null +++ b/Neo4jClient/Transactions/TransactionScopeProxy.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Transactions; + +namespace Neo4jClient.Transactions +{ + internal abstract class TransactionScopeProxy : ITransaction + { + private ITransactionalGraphClient _client; + private bool _markCommitted = false; + private bool _disposing = false; + + + public ITransaction Transaction { get; private set; } + + protected TransactionScopeProxy(ITransactionalGraphClient client, ITransaction transaction) + { + _client = client; + _disposing = false; + Transaction = transaction; + } + + public virtual void Dispose() + { + if (_disposing) + { + return; + } + + _disposing = true; + _client.EndTransaction(); + if (!_markCommitted && Committable && Transaction.IsOpen) + { + Rollback(); + } + + if (Transaction != null && ShouldDisposeTransaction()) + { + Transaction.Dispose(); + Transaction = 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/Test/Test.csproj b/Test/Test.csproj index eb83265e7..fed9f0719 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -159,7 +159,7 @@ - + diff --git a/Test/Transactions/QueriesInTransactionTests.cs b/Test/Transactions/QueriesInTransactionTests.cs index b9ae7ae2b..b30e86e91 100644 --- a/Test/Transactions/QueriesInTransactionTests.cs +++ b/Test/Transactions/QueriesInTransactionTests.cs @@ -124,7 +124,9 @@ public void UpdateTransactionEndpointAfterFirstRequest() .Return(n => n.Count()) .ExecuteWithoutResults(); - Assert.AreEqual(new Uri("http://foo/db/data/transaction/1"), ((Transaction) transaction).Endpoint); + Assert.AreEqual( + new Uri("http://foo/db/data/transaction/1"), + ((INeo4jTransaction)((TransactionScopeProxy) transaction).Transaction).Endpoint); } } } diff --git a/Test/Transactions/RestCallFailTests.cs b/Test/Transactions/RestCallScenarioTests.cs similarity index 70% rename from Test/Transactions/RestCallFailTests.cs rename to Test/Transactions/RestCallScenarioTests.cs index 15feb1dc2..ab0650d61 100644 --- a/Test/Transactions/RestCallFailTests.cs +++ b/Test/Transactions/RestCallScenarioTests.cs @@ -1,26 +1,36 @@ 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 RestCallFailTests + public class RestCallScenarioTests { private class TestNode { public string Foo { get; set; } } - private void ExecuteRestMethodUnderTransaction(Action restAction) + 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()) + using (var transaction = client.BeginTransaction(option)) { restAction(client); } @@ -158,5 +168,37 @@ 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/Test/Transactions/TransactionManagementTests.cs b/Test/Transactions/TransactionManagementTests.cs index db76b1b16..e10a06a80 100644 --- a/Test/Transactions/TransactionManagementTests.cs +++ b/Test/Transactions/TransactionManagementTests.cs @@ -58,11 +58,232 @@ public void ShouldNotBeAbleToGetTransactionAfterTransactionScope() } } + private ITransaction GetRealTransaction(ITransaction proxiedTransaction) + { + return ((TransactionScopeProxy) proxiedTransaction).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 Transaction(new GraphClient(new Uri("http://foo/db/data")), null); + var transaction = new Neo4jTransaction(new GraphClient(new Uri("http://foo/db/data"))); transaction.Commit(); transaction.Commit(); } @@ -71,7 +292,7 @@ public void ShouldNotBeAbleToCommitTwice() [ExpectedException(typeof (ClosedTransactionException))] public void ShouldNotBeAbleToRollbackTwice() { - var transaction = new Transaction(new GraphClient(new Uri("http://foo/db/data")), null); + var transaction = new Neo4jTransaction(new GraphClient(new Uri("http://foo/db/data"))); transaction.Rollback(); transaction.Rollback(); } @@ -80,7 +301,7 @@ public void ShouldNotBeAbleToRollbackTwice() [ExpectedException(typeof (ClosedTransactionException))] public void ShouldNotBeAbleToCommitAfterRollback() { - var transaction = new Transaction(new GraphClient(new Uri("http://foo/db/data")), null); + var transaction = new Neo4jTransaction(new GraphClient(new Uri("http://foo/db/data"))); transaction.Rollback(); transaction.Commit(); } @@ -89,7 +310,7 @@ public void ShouldNotBeAbleToCommitAfterRollback() [ExpectedException(typeof (ClosedTransactionException))] public void ShouldNotBeAbleToRollbackAfterCommit() { - var transaction = new Transaction(new GraphClient(new Uri("http://foo/db/data")), null); + var transaction = new Neo4jTransaction(new GraphClient(new Uri("http://foo/db/data"))); transaction.Commit(); transaction.Rollback(); } From 17132df3f9902b2c1fc4ec5bdb02ed057758b6c7 Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Wed, 15 Jan 2014 04:35:40 -0800 Subject: [PATCH 03/27] System.Transactions support This commit includes support for System.Transactions framework. It is worth noting that the transactions between this framework and the call to BeginTransaction() are not compatible. --- .../Execution/CypherExecutionPolicy.cs | 9 +- .../Execution/ExecutionConfiguration.cs | 2 + .../GraphClientBasedExecutionPolicy.cs | 4 +- Neo4jClient/GraphClient.cs | 369 ++++++++---------- Neo4jClient/Neo4jClient.csproj | 6 +- .../ClosedTransactionException.cs | 22 ++ .../IInternalTransactionalGraphClient.cs | 16 + .../Transactions/ITransactionManager.cs | 18 + .../ITransactionResourceManager.cs | 15 + Neo4jClient/Transactions/Neo4jTransaction.cs | 170 +++++++- .../Neo4jTransactionResourceManager.cs | 120 ++++++ .../TransactionConnectionContext.cs | 13 + .../TransactionExecutionEnvironment.cs | 35 ++ .../Transactions/TransactionManager.cs | 185 +++++++++ .../TransactionSinglePhaseNotification.cs | 168 ++++++++ Test/Test.csproj | 1 + .../Transactions/QueriesInTransactionTests.cs | 257 ++++++++++++ 17 files changed, 1195 insertions(+), 215 deletions(-) create mode 100644 Neo4jClient/Transactions/ClosedTransactionException.cs create mode 100644 Neo4jClient/Transactions/IInternalTransactionalGraphClient.cs create mode 100644 Neo4jClient/Transactions/ITransactionManager.cs create mode 100644 Neo4jClient/Transactions/ITransactionResourceManager.cs create mode 100644 Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs create mode 100644 Neo4jClient/Transactions/TransactionConnectionContext.cs create mode 100644 Neo4jClient/Transactions/TransactionExecutionEnvironment.cs create mode 100644 Neo4jClient/Transactions/TransactionManager.cs create mode 100644 Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs diff --git a/Neo4jClient/Execution/CypherExecutionPolicy.cs b/Neo4jClient/Execution/CypherExecutionPolicy.cs index db4e4ffbf..5a45cf5b9 100644 --- a/Neo4jClient/Execution/CypherExecutionPolicy.cs +++ b/Neo4jClient/Execution/CypherExecutionPolicy.cs @@ -18,11 +18,18 @@ public CypherExecutionPolicy(IGraphClient client) : base(client) private INeo4jTransaction GetTransactionInScope() { - var transactionalClient = Client as ITransactionalGraphClient; + var transactionalClient = Client as IInternalTransactionalGraphClient; if (transactionalClient == null) { return null; } + + var ambientTransaction = transactionalClient.TransactionManager.CurrentDtcTransaction; + if (ambientTransaction != null) + { + return (INeo4jTransaction) ambientTransaction; + } + var proxiedTransaction = transactionalClient.Transaction as TransactionScopeProxy; if (proxiedTransaction == null) { diff --git a/Neo4jClient/Execution/ExecutionConfiguration.cs b/Neo4jClient/Execution/ExecutionConfiguration.cs index ad6ff2056..92c4919d7 100644 --- a/Neo4jClient/Execution/ExecutionConfiguration.cs +++ b/Neo4jClient/Execution/ExecutionConfiguration.cs @@ -1,8 +1,10 @@ +using System; using System.Collections.Generic; using Newtonsoft.Json; namespace Neo4jClient.Execution { + public class ExecutionConfiguration { public IHttpClient HttpClient { get; set; } diff --git a/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs b/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs index 814f55564..d53232708 100644 --- a/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs +++ b/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Transactions; using Neo4jClient.Transactions; namespace Neo4jClient.Execution @@ -20,7 +21,8 @@ public bool InTransaction get { var transactionalGraphClient = Client as ITransactionalGraphClient; - return transactionalGraphClient != null && transactionalGraphClient.InTransaction; + return transactionalGraphClient != null && + (transactionalGraphClient.InTransaction || Transaction.Current != null); } } diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index 584a42aac..1f07df744 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Net; using System.Net.Http; -using System.Threading; using System.Threading.Tasks; using Neo4jClient.ApiModels; using Neo4jClient.ApiModels.Cypher; @@ -22,9 +21,10 @@ namespace Neo4jClient { - public class GraphClient : IRawGraphClient, ITransactionalGraphClient, IDisposable + 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 = { @@ -34,19 +34,17 @@ public class GraphClient : IRawGraphClient, ITransactionalGraphClient, IDisposab new EnumValueConverter() }; - // holds the transaction objects - [ThreadStatic] private static Stack scopedTransactions; - + private ITransactionManager transactionManager; private IExecutionPolicyFactory policyFactory; public ExecutionConfiguration ExecutionConfiguration { get; private set; } internal readonly Uri RootUri; internal RootApiResponse RootApiResponse; - RootNode rootNode; - - CypherCapabilities cypherCapabilities = CypherCapabilities.Default; + private RootNode rootNode; + + private CypherCapabilities cypherCapabilities = CypherCapabilities.Default; public bool UseJsonStreamingIfAvailable { get; set; } - + public GraphClient(Uri rootUri) : this(rootUri, new HttpClientWrapper()) { @@ -78,7 +76,7 @@ public GraphClient(Uri rootUri, IHttpClient httpClient) policyFactory = new ExecutionPolicyFactory(this); } - Uri BuildUri(string relativeUri) + private Uri BuildUri(string relativeUri) { var baseUri = RootUri; if (!RootUri.AbsoluteUri.EndsWith("/")) @@ -94,8 +92,8 @@ private IDictionary GetMetadataFromResponse(HttpResponseMessage { return response.Headers.ToDictionary( headerPair => headerPair.Key, - headerPair => (object)headerPair.Value - ); + headerPair => (object) headerPair.Value + ); } private string SerializeAsJson(object contents) @@ -140,8 +138,7 @@ public virtual void Connect() if (!string.IsNullOrEmpty(RootApiResponse.Transaction)) { RootApiResponse.Transaction = RootApiResponse.Transaction.Substring(baseUriLengthToTrim); - Thread.BeginThreadAffinity(); - scopedTransactions = new Stack(); + transactionManager = new TransactionManager(this); } if (RootApiResponse.Extensions != null && RootApiResponse.Extensions.GremlinPlugin != null) @@ -162,11 +159,11 @@ public virtual void Connect() // http://blog.neo4j.org/2012/04/streaming-rest-api-interview-with.html ExecutionConfiguration.UseJsonStreaming = ExecutionConfiguration.UseJsonStreaming && - RootApiResponse.Version >= new Version(1, 8); + RootApiResponse.Version >= new Version(1, 8); if (RootApiResponse.Version < new Version(2, 0)) cypherCapabilities = CypherCapabilities.Cypher19; - + stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs @@ -177,7 +174,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 @@ -193,12 +192,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"); } @@ -217,15 +216,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); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); var batchSteps = new List(); @@ -292,7 +291,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 }); @@ -300,7 +299,7 @@ public virtual NodeReference Create( return nodeReference; } - BatchResponse ExecuteBatch(List batchSteps, IExecutionPolicy policy) + private BatchResponse ExecuteBatch(List batchSteps, IExecutionPolicy policy) { return Request.With(ExecutionConfiguration) .Post(policy.BaseEndpoint) @@ -325,7 +324,7 @@ public virtual RelationshipReference CreateRelationship> GetAsync(NodeReference reference) { CheckRoot(); var policy = policyFactory.GetPolicy(PolicyType.Rest); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); return Request.With(ExecutionConfiguration) .Get(policy.BaseEndpoint.AddPath(reference, policy)) @@ -423,23 +423,26 @@ 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); Task.WaitAll(task); return task.Result; } - public virtual Task> GetAsync(RelationshipReference reference) where TData : class, new() + public virtual Task> GetAsync(RelationshipReference reference) + where TData : class, new() { CheckRoot(); var policy = policyFactory.GetPolicy(PolicyType.Rest); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); return Request.With(ExecutionConfiguration) .Get(policy.BaseEndpoint.AddPath(reference, policy)) @@ -452,11 +455,12 @@ public virtual Node Get(NodeReference reference) 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); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -477,7 +481,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 }); @@ -489,7 +493,7 @@ public Node Update(NodeReference nodeReference, Action Update(NodeReference nodeReference, Action>(originalValuesString); + var originalValuesDictionary = + new CustomJsonDeserializer(JsonConverters).Deserialize>( + originalValuesString); var newValuesString = serializer.Serialize(node.Data); - var newValuesDictionary = new CustomJsonDeserializer(JsonConverters).Deserialize>(newValuesString); - var differences = Utilities.GetDifferencesBetweenDictionaries(originalValuesDictionary, newValuesDictionary); + var newValuesDictionary = + new CustomJsonDeserializer(JsonConverters).Deserialize>(newValuesString); + var differences = Utilities.GetDifferencesBetweenDictionaries(originalValuesDictionary, + newValuesDictionary); changeCallback(differences); } Request.With(ExecutionConfiguration) - .Put(policy.BaseEndpoint.AddPath(nodeReference, policy).AddPath("properties")) - .WithJsonContent(serializer.Serialize(node.Data)) - .WithExpectedStatusCodes(HttpStatusCode.NoContent) - .Execute(); + .Put(policy.BaseEndpoint.AddPath(nodeReference, policy).AddPath("properties")) + .WithJsonContent(serializer.Serialize(node.Data)) + .WithExpectedStatusCodes(HttpStatusCode.NoContent) + .Execute(); if (indexEntriesCallback != null) { @@ -532,7 +540,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 }); @@ -540,12 +548,13 @@ 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); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -569,7 +578,7 @@ public void Update(RelationshipReference r 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 }); @@ -579,7 +588,7 @@ public virtual void Delete(NodeReference reference, DeleteMode mode) { CheckRoot(); var policy = policyFactory.GetPolicy(PolicyType.Rest); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -608,7 +617,7 @@ public virtual void Delete(NodeReference reference, DeleteMode mode) }); } - void DeleteAllRelationships(NodeReference reference, IExecutionPolicy policy) + private void DeleteAllRelationships(NodeReference reference, IExecutionPolicy policy) { //TODO: Make this a dynamic endpoint resolution var relationshipEndpoint = policy.BaseEndpoint @@ -631,7 +640,7 @@ void DeleteAllRelationships(NodeReference reference, IExecutionPolicy policy) } } - static string GetLastPathSegment(string uri) + private static string GetLastPathSegment(string uri) { var path = new Uri(uri).AbsolutePath; return path @@ -641,10 +650,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); } @@ -729,9 +740,14 @@ public Uri GremlinEndpoint public List JsonConverters { get; private set; } - private void FailDependingOnPolicy(IExecutionPolicy policy) + private void CheckTransactionEnvironmentWithPolicy(IExecutionPolicy policy) { - if (policy.InTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Denied) + if (transactionManager != null) + { + transactionManager.RegisterToTransactionIfNeeded(); + } + + if (InTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Denied) { throw new InvalidOperationException("Cannot be done inside a transaction scope."); } @@ -751,129 +767,41 @@ public ITransaction BeginTransaction() public ITransaction BeginTransaction(TransactionScopeOption scopeOption) { CheckRoot(); - - if (scopeOption == TransactionScopeOption.Suppress) - { - // TransactionScopeOption.Suppress doesn't fail with older versions of Neo4j - return BeginSupressTransaction(); - } - - if (ServerVersion < new Version(2, 0) || RootApiResponse.Transaction == null) + if (transactionManager == null) { 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 void PushScopeTransaction(TransactionScopeProxy transaction) - { - if (scopedTransactions == null) - { - scopedTransactions = new Stack(); - } - scopedTransactions.Push(transaction); - } - - private ITransaction BeginNewTransaction() - { - var transaction = new Neo4jTransactionProxy(this, new Neo4jTransaction(this), true); - PushScopeTransaction(transaction); - return transaction; - } - - private ITransaction BeginJoinTransaction() - { - var parentScope = InternalTransaction; - if (parentScope == null) - { - return null; - } - - if (!parentScope.Committable) - { - return null; - } - - if (!parentScope.IsOpen) - { - throw new ClosedTransactionException(null); - } - - var joinedTransaction = new Neo4jTransactionProxy(this, parentScope.Transaction, false); - PushScopeTransaction(joinedTransaction); - return joinedTransaction; - } - - private ITransaction BeginSupressTransaction() - { - var suppressTransaction = new SuppressTransactionProxy(this); - PushScopeTransaction(suppressTransaction); - return suppressTransaction; - } - - private TransactionScopeProxy InternalTransaction - { - get - { - try - { - return scopedTransactions == null ? null : scopedTransactions.Peek(); - } - catch (InvalidOperationException) - { - // the stack is empty - return null; - } - } + return transactionManager.BeginTransaction(scopeOption); } public ITransaction Transaction { - get { return InternalTransaction; } + get { return transactionManager == null ? null : transactionManager.CurrentNonDtcTransaction; } } public bool InTransaction { - get - { - var transactionObject = InternalTransaction; - return transactionObject != null && transactionObject.Committable; - } + get { return transactionManager != null && transactionManager.InTransaction; } } public void EndTransaction() { - TransactionScopeProxy currentTransaction = null; - try - { - currentTransaction = scopedTransactions == null ? null : scopedTransactions.Pop(); - } - catch (InvalidOperationException) + if (transactionManager == null) { + throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); } - if (currentTransaction != null) - { - currentTransaction.Dispose(); - } + transactionManager.EndTransaction(); } - [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 string ExecuteScalarGremlin(string query, IDictionary parameters) { CheckRoot(); var policy = policyFactory.GetPolicy(PolicyType.Gremlin); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); if (RootApiResponse.Extensions.GremlinPlugin == null || RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) @@ -887,7 +815,7 @@ 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); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -916,7 +847,7 @@ public virtual string ExecuteScalarGremlin(string query, IDictionary>>() .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 @@ -936,7 +867,9 @@ 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) { @@ -948,7 +881,7 @@ private IResponseBuilder PrepareCypherRequest(CypherQuery query, IExecutionPolic var request = Request.With(ExecutionConfiguration) .Post(policy.BaseEndpoint) .WithJsonContent(policy.SerializeRequest(query)); - if (Transaction != null) + if (InTransaction) { // HttpStatusCode.Created may be returned when emitting the first query on a transaction return request.WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created); @@ -977,7 +910,7 @@ Task> IRawGraphClient.ExecuteGetCypherResultsAsync { CheckRoot(); var policy = policyFactory.GetPolicy(PolicyType.Cypher); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); @@ -1002,7 +935,7 @@ Task> IRawGraphClient.ExecuteGetCypherResultsAsync TimeTaken = stopwatch.Elapsed }); - return (IEnumerable)results; + return (IEnumerable) results; }); } @@ -1010,12 +943,13 @@ void IRawGraphClient.ExecuteCypher(CypherQuery query) { CheckRoot(); var policy = policyFactory.GetPolicy(PolicyType.Cypher); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); var stopwatch = new Stopwatch(); stopwatch.Start(); - var response = PrepareCypherRequest(query, policy).Execute(string.Format("The query was: {0}", query.QueryText)); + var response = + PrepareCypherRequest(query, policy).Execute(string.Format("The query was: {0}", query.QueryText)); policy.AfterExecution(GetMetadataFromResponse(response)); stopwatch.Stop(); @@ -1031,7 +965,7 @@ void IRawGraphClient.ExecuteMultipleCypherQueriesInTransaction(IEnumerable query.QueryText)); @@ -1056,19 +990,25 @@ void IRawGraphClient.ExecuteMultipleCypherQueriesInTransaction(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); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); if (RootApiResponse.Extensions.GremlinPlugin == null || RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) @@ -1099,24 +1039,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); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); if (RootApiResponse.Extensions.GremlinPlugin == null || RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) @@ -1163,7 +1111,7 @@ private IExecutionPolicy GetPolicyForIndex(IndexFor indexFor) private Uri GetUriForIndexType(IndexFor indexFor) { var policy = GetPolicyForIndex(indexFor); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); return policy.BaseEndpoint; } @@ -1195,7 +1143,7 @@ public bool CheckIndexExists(string indexName, IndexFor indexFor) return response.StatusCode == HttpStatusCode.OK; } - void CheckRoot() + private void CheckRoot() { if (RootApiResponse == null) throw new InvalidOperationException( @@ -1236,18 +1184,19 @@ public void ReIndex(RelationshipReference relationship, IEnumerable ReIndex(entityUri.ToString(), entityId, IndexFor.Relationship, indexEntries); } - private void ReIndex(string entityUri, long entityId, IndexFor indexFor, IEnumerable indexEntries, IExecutionPolicy policy) + private void ReIndex(string entityUri, long entityId, IndexFor indexFor, IEnumerable indexEntries, + IExecutionPolicy policy) { if (indexEntries == null) throw new ArgumentNullException("indexEntries"); CheckRoot(); - FailDependingOnPolicy(policy); + 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(); @@ -1267,7 +1216,7 @@ public void DeleteIndex(string indexName, IndexFor indexFor) { CheckRoot(); var policy = GetPolicyForIndex(indexFor); - FailDependingOnPolicy(policy); + CheckTransactionEnvironmentWithPolicy(policy); Request.With(ExecutionConfiguration) .Delete(policy.BaseEndpoint.AddPath(indexName)) @@ -1285,7 +1234,7 @@ public void DeleteIndexEntries(string indexName, RelationshipReference relations DeleteIndexEntries(indexName, relationshipReference.Id, GetUriForIndexType(IndexFor.Relationship)); } - void DeleteIndexEntries(string indexName, long id, Uri indexUri) + private void DeleteIndexEntries(string indexName, long id, Uri indexUri) { var indexAddress = indexUri .AddPath(Uri.EscapeDataString(indexName)) @@ -1299,7 +1248,8 @@ void DeleteIndexEntries(string indexName, long id, Uri indexUri) ); } - 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)) @@ -1335,7 +1285,7 @@ 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) @@ -1344,7 +1294,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 { @@ -1359,7 +1309,9 @@ 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(); @@ -1375,17 +1327,20 @@ public IEnumerable> QueryIndex(string indexName, IndexFor ind .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(); var indexResource = GetUriForIndexType(indexFor) @@ -1401,7 +1356,9 @@ IEnumerable> BuildLookupIndex(string exactIndexName, IndexFor .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.")] + [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); @@ -1418,7 +1375,15 @@ protected void OnOperationCompleted(OperationCompletedEventArgs args) public void Dispose() { - Thread.EndThreadAffinity(); + if (transactionManager != null) + { + transactionManager.Dispose(); + } + } + + public ITransactionManager TransactionManager + { + get { return transactionManager; } } } } diff --git a/Neo4jClient/Neo4jClient.csproj b/Neo4jClient/Neo4jClient.csproj index 49de64116..d613480be 100644 --- a/Neo4jClient/Neo4jClient.csproj +++ b/Neo4jClient/Neo4jClient.csproj @@ -72,7 +72,6 @@ - @@ -230,15 +229,20 @@ + + + + + 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/ITransactionManager.cs b/Neo4jClient/Transactions/ITransactionManager.cs new file mode 100644 index 000000000..b1080ada4 --- /dev/null +++ b/Neo4jClient/Transactions/ITransactionManager.cs @@ -0,0 +1,18 @@ +using System; + +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(); + } +} 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/Neo4jTransaction.cs b/Neo4jClient/Transactions/Neo4jTransaction.cs index 58cf79e0b..7ffc0961a 100644 --- a/Neo4jClient/Transactions/Neo4jTransaction.cs +++ b/Neo4jClient/Transactions/Neo4jTransaction.cs @@ -2,13 +2,14 @@ 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 /// - class Neo4jTransaction : INeo4jTransaction + internal class Neo4jTransaction : INeo4jTransaction { private readonly ITransactionalGraphClient _client; @@ -16,6 +17,33 @@ class Neo4jTransaction : INeo4jTransaction 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; @@ -43,6 +71,15 @@ private void CheckForOpenTransaction() 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. /// @@ -56,12 +93,7 @@ public void Commit() return; } - Request.With(_client.ExecutionConfiguration) - .Post(Endpoint.AddPath("commit")) - .WithJsonContent(_client.Serializer.Serialize(new CypherStatementList())) - .WithExpectedStatusCodes(HttpStatusCode.OK) - .Execute(); - + DoCommit(Endpoint, _client.ExecutionConfiguration, _client.Serializer); CleanupAfterClosedTransaction(); } @@ -98,13 +130,131 @@ public void KeepAlive() return; } - Request.With(_client.ExecutionConfiguration) - .Post(Endpoint) - .WithJsonContent(_client.Serializer.Serialize(new CypherStatementList())) + 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) + { + Request.With(executionConfiguration) + .Delete(rollbackUri) .WithExpectedStatusCodes(HttpStatusCode.OK) .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 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(), + JsonConverters = GraphClient.DefaultJsonConverters, + UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, + UserAgent = transactionExecutionEnvironment.UserAgent + }, + new CustomJsonSerializer()); + } + + /// + /// Rolls back a transaction given the ID + /// + /// The transaction 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(), + JsonConverters = GraphClient.DefaultJsonConverters, + UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, + UserAgent = transactionExecutionEnvironment.UserAgent + }); + } + catch (Exception e) + { + throw e; + } + } + + /// + /// Keeps alive a transaction given the ID + /// + /// The transaction 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(), + JsonConverters = GraphClient.DefaultJsonConverters, + UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, + UserAgent = transactionExecutionEnvironment.UserAgent + }, + new CustomJsonSerializer()); + } + public void Dispose() { if (IsOpen) diff --git a/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs b/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs new file mode 100644 index 000000000..b26c4bb3c --- /dev/null +++ b/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs @@ -0,0 +1,120 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Transactions; + +namespace Neo4jClient.Transactions +{ + /// + /// 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/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/TransactionExecutionEnvironment.cs b/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs new file mode 100644 index 000000000..8a8270be6 --- /dev/null +++ b/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs @@ -0,0 +1,35 @@ +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 TransactionExecutionEnvironment(ExecutionConfiguration executionConfiguration) + { + UserAgent = executionConfiguration.UserAgent; + UseJsonStreaming = executionConfiguration.UseJsonStreaming; + } + + } + + internal interface ITransactionExecutionEnvironment + { + Uri TransactionBaseEndpoint { get; } + int TransactionId { get; } + bool UseJsonStreaming { get; set; } + string UserAgent { get; set; } + } +} diff --git a/Neo4jClient/Transactions/TransactionManager.cs b/Neo4jClient/Transactions/TransactionManager.cs new file mode 100644 index 000000000..e32d79a6d --- /dev/null +++ b/Neo4jClient/Transactions/TransactionManager.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Transactions; + +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; + 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(); + _promotable = new TransactionPromotableSinglePhaseNotification(client); + } + + public bool InTransaction + { + get + { + // if we are in an ambient System.Transactions transaction then we are in a transaction! + if (Transaction.Current != null) + { + return true; + } + + var transactionObject = CurrentInternalTransaction; + return transactionObject != null && transactionObject.Committable; + } + } + + 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 void PushScopeTransaction(TransactionScopeProxy transaction) + { + if (_scopedTransactions == null) + { + _scopedTransactions = new Stack(); + } + _scopedTransactions.Push(transaction); + } + + private ITransaction BeginNewTransaction() + { + var transaction = new Neo4jTransactionProxy(_client, new Neo4jTransaction(_client), 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, parentScope.Transaction, 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.Transaction if needed + /// + public void RegisterToTransactionIfNeeded() + { + if (_promotable == null) + { + // no need to register as we don't support transactions + return; + } + _promotable.EnlistIfNecessary(); + } + + public void Dispose() + { + _scopedTransactions = null; + Thread.EndThreadAffinity(); + } + } +} \ No newline at end of file diff --git a/Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs b/Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs new file mode 100644 index 000000000..cc7b30183 --- /dev/null +++ b/Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs @@ -0,0 +1,168 @@ +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(); + 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 Transaction 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; + } + } +} diff --git a/Test/Test.csproj b/Test/Test.csproj index fed9f0719..f7a88d4e3 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -56,6 +56,7 @@ ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + diff --git a/Test/Transactions/QueriesInTransactionTests.cs b/Test/Transactions/QueriesInTransactionTests.cs index b30e86e91..69cb273f9 100644 --- a/Test/Transactions/QueriesInTransactionTests.cs +++ b/Test/Transactions/QueriesInTransactionTests.cs @@ -2,8 +2,11 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; +using System.Transactions; using Neo4jClient.Transactions; using NUnit.Framework; +using TransactionScopeOption = System.Transactions.TransactionScopeOption; namespace Neo4jClient.Test.Transactions { @@ -162,6 +165,260 @@ public void TransactionCommit() } } + [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)', '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") + }, + { + 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)', '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)', '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)', 'parameters': {}}]}"); + var secondRequest = MockRequest.PostJson("/transaction/1", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', '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)', '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 TransactionCommitInTransactionScope() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', '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() { From e52fee7bb75202b963a476ce5fa4d1161fad85b7 Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Wed, 15 Jan 2014 14:31:45 -0800 Subject: [PATCH 04/27] Fix for serialization of transaction The result set is different from the REST endpoint. This commit fixes those issues. Also in Neo4jTransaction, the rollback doesn't care anymore if the transaction already doesn't exist, this could happen when a Rollback() is done after the transaction timeout. --- Neo4jClient/ClosedTransactionException.cs | 25 ---- Neo4jClient/GraphClient.cs | 7 +- .../Serialization/CypherJsonDeserializer.cs | 60 ++++++--- Neo4jClient/Transactions/Neo4jTransaction.cs | 4 +- .../CypherJsonDeserializerTests.cs | 116 ++++++++++++++++++ .../Transactions/QueriesInTransactionTests.cs | 43 +++++++ 6 files changed, 212 insertions(+), 43 deletions(-) delete mode 100644 Neo4jClient/ClosedTransactionException.cs diff --git a/Neo4jClient/ClosedTransactionException.cs b/Neo4jClient/ClosedTransactionException.cs deleted file mode 100644 index bf01df328..000000000 --- a/Neo4jClient/ClosedTransactionException.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Neo4jClient -{ - 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/GraphClient.cs b/Neo4jClient/GraphClient.cs index 1f07df744..7063d3f79 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -914,6 +914,11 @@ Task> IRawGraphClient.ExecuteGetCypherResultsAsync var stopwatch = new Stopwatch(); stopwatch.Start(); + + // 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; + return PrepareCypherRequest(query, policy) .ExecuteAsync( string.Format("The query was: {0}", query.QueryText), @@ -922,7 +927,7 @@ Task> IRawGraphClient.ExecuteGetCypherResultsAsync var response = responseTask.Result; policy.AfterExecution(GetMetadataFromResponse(response)); - var deserializer = new CypherJsonDeserializer(this, query.ResultMode); + var deserializer = new CypherJsonDeserializer(this, query.ResultMode, inTransaction); var results = deserializer .Deserialize(response.Content.ReadAsString()) .ToList(); diff --git a/Neo4jClient/Serialization/CypherJsonDeserializer.cs b/Neo4jClient/Serialization/CypherJsonDeserializer.cs index a0243eaa6..68693d076 100644 --- a/Neo4jClient/Serialization/CypherJsonDeserializer.cs +++ b/Neo4jClient/Serialization/CypherJsonDeserializer.cs @@ -16,13 +16,20 @@ public class CypherJsonDeserializer { readonly IGraphClient client; readonly CypherResultMode resultMode; + private readonly bool inTransaction; readonly CultureInfo culture = CultureInfo.InvariantCulture; public CypherJsonDeserializer(IGraphClient client, CypherResultMode resultMode) + : this(client, resultMode, false) + { + } + + public CypherJsonDeserializer(IGraphClient client, CypherResultMode resultMode, bool inTransaction) { this.client = client; this.resultMode = resultMode; + this.inTransaction = inTransaction; } public IEnumerable Deserialize(string content) @@ -44,10 +51,9 @@ public IEnumerable Deserialize(string content) // 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 - var transactionalClient = client as ITransactionalGraphClient; - return transactionalClient == null || transactionalClient.Transaction == null ? - DeserializeFromRoot(content, reader, context).ToArray() : - DeserializeFromResults(content, reader, context).ToArray(); + return inTransaction + ? DeserializeFromResults(content, reader, context).ToArray() + : DeserializeFromRoot(content, reader, context).ToArray(); } catch (Exception ex) { @@ -119,15 +125,19 @@ IEnumerable DeserializeResultSet(JToken resultRoot, DeserializationCont case CypherResultMode.Set: return ParseInSingleColumnMode(context, resultRoot, columnNames, jsonTypeMappings.ToArray()); case CypherResultMode.Projection: - jsonTypeMappings.Add(new TypeMapping + // if we are in transaction and we have an object we dont need a mutation + if (!inTransaction) { - 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]) - }); + 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)); @@ -192,10 +202,29 @@ IEnumerable ParseInSingleColumnMode(DeserializationContext context, JTo var rows = dataArray.Children(); 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("row", 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)); @@ -274,9 +303,8 @@ IEnumerable ParseInProjectionMode(DeserializationContext context, JToke var dataArray = (JArray)root["data"]; var rows = dataArray.Children(); - var results = rows.Select(getRow); - return results; + return inTransaction ? rows.Select(row => row["row"]).Select(getRow) : rows.Select(getRow); } TResult ReadProjectionRowUsingCtor( diff --git a/Neo4jClient/Transactions/Neo4jTransaction.cs b/Neo4jClient/Transactions/Neo4jTransaction.cs index 7ffc0961a..a4dddc224 100644 --- a/Neo4jClient/Transactions/Neo4jTransaction.cs +++ b/Neo4jClient/Transactions/Neo4jTransaction.cs @@ -163,9 +163,11 @@ private static void DoCommit(Uri commitUri, ExecutionConfiguration executionConf 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) + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.NotFound) .Execute(); } diff --git a/Test/Serialization/CypherJsonDeserializerTests.cs b/Test/Serialization/CypherJsonDeserializerTests.cs index 9e601dc85..b06061a76 100644 --- a/Test/Serialization/CypherJsonDeserializerTests.cs +++ b/Test/Serialization/CypherJsonDeserializerTests.cs @@ -974,6 +974,122 @@ public class UserWithJsonPropertyAttribute public string Name { get; set; } } + private class CityAndLabel + { + public City City { get; set; } + public IEnumerable Labels { get; set; } + } + + [Test] + public void DeserializerTwoLevelProjectionInTransaction() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, 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, 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, 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, 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() { diff --git a/Test/Transactions/QueriesInTransactionTests.cs b/Test/Transactions/QueriesInTransactionTests.cs index 69cb273f9..d15033217 100644 --- a/Test/Transactions/QueriesInTransactionTests.cs +++ b/Test/Transactions/QueriesInTransactionTests.cs @@ -494,6 +494,49 @@ public void KeepAliveAfterFirstRequest() } } + [Test] + public void DeserializeResultsFromTransaction() + { + var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + var deserializationRequest = MockRequest.PostJson("/transaction/1", @"{ + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', '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() { From 65556a179bf634e578c5b6c4b734415e4d1045da Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Thu, 16 Jan 2014 14:29:32 -0800 Subject: [PATCH 05/27] Error handling for transaction queries Neo4j manages errors differently when the query is executed under a transaction: instead of returning a 400 Bad Request, it embeds the errors as a JSON list within the result object (and returns 200 OK). In order to manage (and throw a NeoException), the code has to partially deserialize and see if the result contains a non-empty error list. --- Neo4jClient/CypherPartialResult.cs | 17 +++ Neo4jClient/GraphClient.cs | 72 +++++++--- Neo4jClient/Neo4jClient.csproj | 2 + .../Serialization/CypherJsonDeserializer.cs | 124 ++++++++++++++++-- .../PartialDeserializationContext.cs | 10 ++ .../CypherJsonDeserializerTests.cs | 55 ++++++++ .../TransactionManagementTests.cs | 42 ++++++ 7 files changed, 299 insertions(+), 23 deletions(-) create mode 100644 Neo4jClient/CypherPartialResult.cs create mode 100644 Neo4jClient/Serialization/PartialDeserializationContext.cs 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/GraphClient.cs b/Neo4jClient/GraphClient.cs index 7063d3f79..5c20007cb 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -876,7 +876,7 @@ public virtual IEnumerable ExecuteGetCypherResults(CypherQuery throw new NotImplementedException(); } - private IResponseBuilder PrepareCypherRequest(CypherQuery query, IExecutionPolicy policy) + private Task PrepareCypherRequest(CypherQuery query, IExecutionPolicy policy) { var request = Request.With(ExecutionConfiguration) .Post(policy.BaseEndpoint) @@ -884,9 +884,33 @@ private IResponseBuilder PrepareCypherRequest(CypherQuery query, IExecutionPolic if (InTransaction) { // HttpStatusCode.Created may be returned when emitting the first query on a transaction - return request.WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created); + return request + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created) + .ExecuteAsync( + string.Format("The query was: {0}", query.QueryText), + 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(GetMetadataFromResponse(response)); + + var deserializer = new CypherJsonDeserializer(this, query.ResultMode, true); + return new CypherPartialResult + { + DeserializationContext = + deserializer.CheckForErrorsInTransactionResponse(response.Content.ReadAsString()), + ResponseObject = response + }; + }); } - return request.WithExpectedStatusCodes(HttpStatusCode.OK); + return request + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ExecuteAsync(response => new CypherPartialResult + { + ResponseObject = response.Result + }); } IEnumerable IRawGraphClient.ExecuteGetCypherResults(CypherQuery query) @@ -919,18 +943,27 @@ Task> IRawGraphClient.ExecuteGetCypherResultsAsync // to know if we are in a transaction right now because our deserializer will run in another thread bool inTransaction = InTransaction; - return PrepareCypherRequest(query, policy) - .ExecuteAsync( - string.Format("The query was: {0}", query.QueryText), + return PrepareCypherRequest(query, policy) + .ContinueWith( responseTask => { - var response = responseTask.Result; - policy.AfterExecution(GetMetadataFromResponse(response)); - var deserializer = new CypherJsonDeserializer(this, query.ResultMode, inTransaction); - var results = deserializer - .Deserialize(response.Content.ReadAsString()) - .ToList(); + List results; + if (inTransaction) + { + results = deserializer + .DeserializeFromTransactionPartialContext(responseTask.Result.DeserializationContext) + .ToList(); + } + else + { + var response = responseTask.Result.ResponseObject; + policy.AfterExecution(GetMetadataFromResponse(response)); + + results = deserializer + .Deserialize(response.Content.ReadAsString()) + .ToList(); + } stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs @@ -953,9 +986,18 @@ void IRawGraphClient.ExecuteCypher(CypherQuery query) var stopwatch = new Stopwatch(); stopwatch.Start(); - var response = - PrepareCypherRequest(query, policy).Execute(string.Format("The query was: {0}", query.QueryText)); - policy.AfterExecution(GetMetadataFromResponse(response)); + var task = PrepareCypherRequest(query, policy); + try + { + Task.WaitAll(task); + } + catch (AggregateException ex) + { + if (ex.InnerExceptions.Count() == 1) + throw ex.InnerExceptions.Single(); + throw; + } + policy.AfterExecution(GetMetadataFromResponse(task.Result.ResponseObject)); stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs diff --git a/Neo4jClient/Neo4jClient.csproj b/Neo4jClient/Neo4jClient.csproj index d613480be..b7165680b 100644 --- a/Neo4jClient/Neo4jClient.csproj +++ b/Neo4jClient/Neo4jClient.csproj @@ -72,6 +72,7 @@ + @@ -144,6 +145,7 @@ + diff --git a/Neo4jClient/Serialization/CypherJsonDeserializer.cs b/Neo4jClient/Serialization/CypherJsonDeserializer.cs index 68693d076..0fa4801bc 100644 --- a/Neo4jClient/Serialization/CypherJsonDeserializer.cs +++ b/Neo4jClient/Serialization/CypherJsonDeserializer.cs @@ -52,11 +52,17 @@ public IEnumerable Deserialize(string content) // not much value to deferred execution here and we'd like to know // about any errors now return inTransaction - ? DeserializeFromResults(content, reader, context).ToArray() + ? 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. @@ -144,34 +150,136 @@ IEnumerable DeserializeResultSet(JToken resultRoot, DeserializationCont } } - private IEnumerable DeserializeFromResults(string content, JsonTextReader reader, DeserializationContext context) + 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."); } - var results = root - .Properties() - .Single(property => property.Name == "results") - .Value as JArray; + + 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."); } - if (results.Count == 0) + 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) - return DeserializeResultSet(results.First, context); + 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) 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/Test/Serialization/CypherJsonDeserializerTests.cs b/Test/Serialization/CypherJsonDeserializerTests.cs index b06061a76..5982bf7df 100644 --- a/Test/Serialization/CypherJsonDeserializerTests.cs +++ b/Test/Serialization/CypherJsonDeserializerTests.cs @@ -980,6 +980,61 @@ private class CityAndLabel 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; } + } + + [Test] + public void DeserializeNestedObjectsInTransaction() + { + var client = Substitute.For(); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, 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() { diff --git a/Test/Transactions/TransactionManagementTests.cs b/Test/Transactions/TransactionManagementTests.cs index e10a06a80..550e11af2 100644 --- a/Test/Transactions/TransactionManagementTests.cs +++ b/Test/Transactions/TransactionManagementTests.cs @@ -1,6 +1,13 @@ 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 Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; using Neo4jClient.Transactions; using NUnit.Framework; @@ -371,5 +378,40 @@ public void TwoThreadsShouldNotHaveTheSameTransactionObject() } } + + [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)}; + + 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); + } + } + } } } From 4b3ed86428a4abd4451f339e8e892607d1d4d602 Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Fri, 17 Jan 2014 00:58:31 -0800 Subject: [PATCH 06/27] Fix for CollectAs<> and deserialization that handles Node and RelationshipInstance<> --- .../ApiModels/Cypher/CypherStatementList.cs | 4 +- .../Cypher/CypherTransactionStatement.cs | 10 +- .../Cypher/CypherFluentQuery`Return.cs | 19 +- Neo4jClient/Cypher/CypherQuery.cs | 25 ++- .../Cypher/CypherReturnExpressionBuilder.cs | 151 ++++++++----- Neo4jClient/Cypher/QueryWriter.cs | 17 +- Neo4jClient/Cypher/ReturnExpression.cs | 1 + .../Execution/CypherExecutionPolicy.cs | 9 +- Neo4jClient/GraphClient.cs | 4 +- .../Serialization/CypherJsonDeserializer.cs | 23 +- Test/Cypher/AggregateTests.cs | 8 + Test/Cypher/CypherFluentQueryReturnTests.cs | 24 +- Test/Cypher/StartBitFormatterTests.cs | 2 +- .../Cypher/ExecuteCypherTests.cs | 2 +- .../Cypher/ExecuteGetCypherResultsTests.cs | 19 +- .../CypherJsonDeserializerTests.cs | 205 +++++++++++++----- .../Transactions/QueriesInTransactionTests.cs | 30 +-- .../TransactionManagementTests.cs | 2 +- 18 files changed, 405 insertions(+), 150 deletions(-) diff --git a/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs b/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs index e668e00d5..ac6a7ab57 100644 --- a/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs +++ b/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs @@ -21,7 +21,9 @@ public CypherStatementList() public CypherStatementList(IEnumerable queries) { - _statements = queries.Select(query => new CypherTransactionStatement(query)).ToList(); + _statements = queries + .Select(query => new CypherTransactionStatement(query, query.ResultFormat == CypherResultFormat.Rest)) + .ToList(); } [JsonProperty("statements")] diff --git a/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs b/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs index 40e35a1f7..0d6aaa65e 100644 --- a/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs +++ b/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs @@ -12,11 +12,13 @@ class CypherTransactionStatement { private readonly string _queryText; private readonly IDictionary _queryParameters; + private readonly string[] _formatContents; - public CypherTransactionStatement(CypherQuery query) + public CypherTransactionStatement(CypherQuery query, bool restFormat) { _queryText = query.QueryText; _queryParameters = query.QueryParameters ?? new Dictionary(); + _formatContents = restFormat ? new[] {"REST"} : new string[] {}; } [JsonProperty("statement")] @@ -25,6 +27,12 @@ public string Statement get { return _queryText; } } + [JsonProperty("resultDataContents")] + public IEnumerable ResultDataContents + { + get { return _formatContents; } + } + [JsonProperty("parameters")] public IDictionary Parameters { diff --git a/Neo4jClient/Cypher/CypherFluentQuery`Return.cs b/Neo4jClient/Cypher/CypherFluentQuery`Return.cs index 219f96196..ba667f45e 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 { @@ -18,8 +19,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)] @@ -40,6 +55,7 @@ ICypherFluentQuery Return(LambdaExpression expression) return Mutate(w => { w.ResultMode = returnExpression.ResultMode; + w.ResultFormat = returnExpression.ResultFormat; w.AppendClause("RETURN " + returnExpression.Text); }); } @@ -51,6 +67,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/CypherQuery.cs b/Neo4jClient/Cypher/CypherQuery.cs index ae743c764..ece7932a8 100644 --- a/Neo4jClient/Cypher/CypherQuery.cs +++ b/Neo4jClient/Cypher/CypherQuery.cs @@ -4,21 +4,39 @@ namespace Neo4jClient.Cypher { + public enum CypherResultFormat + { + Rest, + Transactional, + DependsOnEnvironment + } + [DebuggerDisplay("{DebugQueryText}")] public class CypherQuery { readonly string queryText; readonly IDictionary queryParameters; readonly CypherResultMode resultMode; + private readonly CypherResultFormat resultFormat; + + public CypherQuery( + string queryText, + IDictionary queryParameters, + CypherResultMode resultMode) : + this(queryText, queryParameters, resultMode, CypherResultFormat.DependsOnEnvironment) + { + } public CypherQuery( string queryText, IDictionary queryParameters, - CypherResultMode resultMode) + CypherResultMode resultMode, + CypherResultFormat resultFormat) { this.queryText = queryText; this.queryParameters = queryParameters; this.resultMode = resultMode; + this.resultFormat = resultFormat; } public IDictionary QueryParameters @@ -31,6 +49,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 6010db012..f5122c161 100644 --- a/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs +++ b/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs @@ -25,6 +25,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" @@ -45,25 +62,45 @@ 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); - return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Projection}; + expressionBuild = BuildText(memberInitExpression, capabilities, jsonConvertersThatTheDeserializerWillUse); + return new ReturnExpression + { + Text = expressionBuild.ExpressionText, + ResultMode = CypherResultMode.Projection, + ResultFormat = expressionBuild.ResultFormat + }; case ExpressionType.New: var newExpression = (NewExpression) body; - text = BuildText(newExpression, capabilities, jsonConvertersThatTheDeserializerWillUse); - return new ReturnExpression {Text = text, ResultMode = CypherResultMode.Projection}; + expressionBuild = BuildText(newExpression, capabilities, jsonConvertersThatTheDeserializerWillUse); + 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); - return new ReturnExpression { Text = text, ResultMode = CypherResultMode.Set }; + expressionBuild = BuildText(memberExpression, capabilities, jsonConvertersThatTheDeserializerWillUse); + return new ReturnExpression + { + Text = expressionBuild.ExpressionText, + ResultMode = CypherResultMode.Set, + ResultFormat = expressionBuild.ResultFormat + }; default: throw new ArgumentException(ReturnExpressionShouldBeOneOfExceptionMessage, "expression"); } @@ -79,7 +116,7 @@ public static ReturnExpression BuildText( /// /// caters to anonymous types. /// - static string BuildText( + static ExpressionBuild BuildText( MemberInitExpression expression, CypherCapabilities capabilities, IEnumerable jsonConvertersThatTheDeserializerWillUse) @@ -92,13 +129,22 @@ 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"); - - var memberAssignment = (MemberAssignment)binding; - return BuildStatement(memberAssignment.Expression, binding.Member, capabilities, jsonConvertersThatTheDeserializerWillUse); - }); - - return string.Join(", ", bindingTexts.ToArray()); + 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); + }).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); } /// @@ -114,7 +160,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) @@ -136,15 +182,21 @@ static string BuildText( { var argument = expression.Arguments[index]; return BuildStatement(argument, member, capabilities, jsonConvertersThatTheDeserializerWillUse); - }); + }).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) @@ -155,7 +207,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) @@ -167,12 +219,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, expression.Member.Name); + var statement = string.Format("{0}.{1}", baseStatement.ExpressionText, expression.Member.Name); - return statement; + return new ExpressionBuild(statement, baseStatement.ResultFormat); } - static string BuildStatement( + static ExpressionBuild BuildStatement( Expression sourceExpression, MemberInfo targetMember, CypherCapabilities capabilities, @@ -197,7 +249,7 @@ static string BuildStatement( unwrappedExpression.GetType().FullName)); } - static string BuildStatement( + static ExpressionBuild BuildStatement( BinaryExpression binaryExpression, MemberInfo targetMember) { @@ -219,10 +271,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) @@ -258,10 +312,10 @@ static string BuildStatement( if (isNullable) optionalIndicator = "?"; } - return string.Format("{0}.{1}{2} AS {3}", targetObject.Name, memberInfo.Name, optionalIndicator, targetMember.Name); + return new ExpressionBuild(string.Format("{0}.{1}{2} AS {3}", targetObject.Name, memberInfo.Name, optionalIndicator, targetMember.Name)); } - static string BuildStatement( + static ExpressionBuild BuildStatement( MethodCallExpression expression, MemberInfo targetMember, CypherCapabilities capabilities, @@ -269,30 +323,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, @@ -321,6 +370,7 @@ static string BuildCypherResultItemStatement( ? expression.Method.GetGenericArguments().Single() : null; + CypherResultFormat format = CypherResultFormat.DependsOnEnvironment; switch (methodName) { case "As": @@ -329,16 +379,19 @@ 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"); finalStatement = string.Format("collect({0})", targetObject.Name); + format = CypherResultFormat.Rest; break; case "CollectAsDistinct": if (IsNodeOfT(singleGenericArgument)) throw new ArgumentException(CollectAsDistinctShouldNotBeNodeTExceptionMessage, "expression"); finalStatement = string.Format("collect(distinct {0})", targetObject.Name); + format = CypherResultFormat.Rest; break; case "Count": finalStatement = string.Format("count({0})", targetObject.Name); @@ -366,7 +419,7 @@ static string BuildCypherResultItemStatement( ? string.Format(statement, finalStatement) : finalStatement; - return statement; + return new ExpressionBuild(statement, format); } static bool IsNodeOfT(Type type) @@ -469,19 +522,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) @@ -489,7 +542,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/QueryWriter.cs b/Neo4jClient/Cypher/QueryWriter.cs index f15d66856..25e358485 100644 --- a/Neo4jClient/Cypher/QueryWriter.cs +++ b/Neo4jClient/Cypher/QueryWriter.cs @@ -10,22 +10,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 @@ -34,11 +38,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() @@ -50,7 +60,8 @@ public CypherQuery ToCypherQuery() return new CypherQuery( queryText, new Dictionary(queryParameters), - resultMode); + resultMode, + resultFormat); } 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/Execution/CypherExecutionPolicy.cs b/Neo4jClient/Execution/CypherExecutionPolicy.cs index 5a45cf5b9..f8b848945 100644 --- a/Neo4jClient/Execution/CypherExecutionPolicy.cs +++ b/Neo4jClient/Execution/CypherExecutionPolicy.cs @@ -68,7 +68,7 @@ public override TransactionExecutionPolicy TransactionExecutionPolicy public override string SerializeRequest(object toSerialize) { var query = toSerialize as CypherQuery; - if (toSerialize == null) + if (query == null) { throw new InvalidOperationException( "Unsupported operation: Attempting to serialize something that was not a query."); @@ -76,7 +76,12 @@ public override string SerializeRequest(object toSerialize) if (InTransaction) { - return Client.Serializer.Serialize(new CypherStatementList {new CypherTransactionStatement(query)}); + return Client + .Serializer + .Serialize(new CypherStatementList + { + new CypherTransactionStatement(query, query.ResultFormat == CypherResultFormat.Rest) + }); } return Client.Serializer.Serialize(new CypherApiQuery(query)); } diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index 5c20007cb..708ccde82 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -896,7 +896,7 @@ private Task PrepareCypherRequest(CypherQuery quer var response = responseTask.Result; policy.AfterExecution(GetMetadataFromResponse(response)); - var deserializer = new CypherJsonDeserializer(this, query.ResultMode, true); + var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, true); return new CypherPartialResult { DeserializationContext = @@ -947,7 +947,7 @@ Task> IRawGraphClient.ExecuteGetCypherResultsAsync .ContinueWith( responseTask => { - var deserializer = new CypherJsonDeserializer(this, query.ResultMode, inTransaction); + var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, inTransaction); List results; if (inTransaction) { diff --git a/Neo4jClient/Serialization/CypherJsonDeserializer.cs b/Neo4jClient/Serialization/CypherJsonDeserializer.cs index 0fa4801bc..9a2b01151 100644 --- a/Neo4jClient/Serialization/CypherJsonDeserializer.cs +++ b/Neo4jClient/Serialization/CypherJsonDeserializer.cs @@ -16,20 +16,30 @@ 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) - : this(client, resultMode, false) + public CypherJsonDeserializer(IGraphClient client, CypherResultMode resultMode, CypherResultFormat resultFormat) + : this(client, resultMode, resultFormat, false) { } - public CypherJsonDeserializer(IGraphClient client, CypherResultMode resultMode, bool inTransaction) + 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) @@ -308,6 +318,8 @@ 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) @@ -319,7 +331,7 @@ IEnumerable ParseInSingleColumnMode(DeserializationContext context, JTo } JToken rowProperty; - if (!rowObject.TryGetValue("row", out rowProperty)) + if (!rowObject.TryGetValue(dataPropertyNameInTransaction, out rowProperty)) { throw new InvalidOperationException("There is no row property in the JSON object."); } @@ -412,7 +424,8 @@ IEnumerable ParseInProjectionMode(DeserializationContext context, JToke var dataArray = (JArray)root["data"]; var rows = dataArray.Children(); - return inTransaction ? rows.Select(row => row["row"]).Select(getRow) : rows.Select(getRow); + var dataPropertyNameInTransaction = resultFormat == CypherResultFormat.Rest ? "rest" : "row"; + return inTransaction ? rows.Select(row => row[dataPropertyNameInTransaction]).Select(getRow) : rows.Select(getRow); } TResult ReadProjectionRowUsingCtor( diff --git a/Test/Cypher/AggregateTests.cs b/Test/Cypher/AggregateTests.cs index 442275306..0c7c61791 100644 --- a/Test/Cypher/AggregateTests.cs +++ b/Test/Cypher/AggregateTests.cs @@ -18,6 +18,7 @@ public void Length() Assert.AreEqual("RETURN length(n) AS Foo", query.QueryText); Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -30,6 +31,7 @@ public void Type() Assert.AreEqual("RETURN type(n) AS Foo", query.QueryText); Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -42,6 +44,7 @@ public void Id() Assert.AreEqual("RETURN id(n) AS Foo", query.QueryText); Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -54,6 +57,7 @@ public void Count() Assert.AreEqual("RETURN count(n) AS Foo", query.QueryText); Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } @@ -69,6 +73,7 @@ public void CountDistinct() Assert.AreEqual("RETURN count(distinct n) AS Foo", query.QueryText); Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -84,6 +89,7 @@ public void CountAll() Assert.AreEqual("RETURN count(*) AS Foo", query.QueryText); Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -98,6 +104,7 @@ public void CountUsingICypderFluentQuery() Assert.AreEqual("RETURN count(*) AS Foo", resultQuery.QueryText); Assert.AreEqual(0, resultQuery.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, resultQuery.ResultFormat); } [Test] @@ -114,6 +121,7 @@ public void CountAllWithOtherIdentities() Assert.AreEqual("RETURN count(*) AS Foo, collect(bar) AS Baz", query.QueryText); Assert.AreEqual(0, query.QueryParameters.Count()); + Assert.AreEqual(CypherResultFormat.Rest, query.ResultFormat); } } } diff --git a/Test/Cypher/CypherFluentQueryReturnTests.cs b/Test/Cypher/CypherFluentQueryReturnTests.cs index ef9eb7ea3..30c8ab76d 100644 --- a/Test/Cypher/CypherFluentQueryReturnTests.cs +++ b/Test/Cypher/CypherFluentQueryReturnTests.cs @@ -26,6 +26,7 @@ public void ReturnDistinct() 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] @@ -41,6 +42,7 @@ public void ReturnDistinctWithLimit() 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] @@ -57,6 +59,7 @@ public void ReturnDistinctWithLimitAndOrderBy() 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] @@ -70,6 +73,7 @@ public void ReturnIdentity() Assert.AreEqual("START n=node({p0})\r\nRETURN n", query.QueryText); Assert.AreEqual(3, query.QueryParameters["p0"]); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -85,6 +89,7 @@ public void ReturnWithLimit() 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] @@ -101,6 +106,7 @@ public void ReturnWithLimitAndOrderBy() 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] @@ -128,6 +134,7 @@ RETURN common 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] @@ -139,6 +146,7 @@ public void ShouldReturnRawFunctionCall() .Query; Assert.AreEqual("RETURN count(item)", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -151,6 +159,7 @@ public void ReturnsWhenIdentityIsACollection() .Query; Assert.AreEqual("MATCH (n0),(n1)\r\nRETURN [n0,n1]", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -163,6 +172,7 @@ public void ReturnsWhenIdentityIsACollectionRegardlessOfPadding() .Query; Assert.AreEqual("MATCH (n0),(n1)\r\nRETURN [n0,n1]", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -205,6 +215,7 @@ public void ShouldReturnCountAllOnItsOwn() .Query; Assert.AreEqual("RETURN count(*)", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -216,6 +227,7 @@ public void ShouldReturnCustomFunctionCall() .Query; Assert.AreEqual("RETURN sum(foo.bar)", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -227,6 +239,7 @@ public void ShouldReturnSpecificPropertyOnItsOwn() .Query; Assert.AreEqual("RETURN a.Name", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -246,6 +259,7 @@ public void ShouldUseSetResultModeForIdentityBasedReturn() .Query; Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -257,6 +271,7 @@ public void ShouldUseSetResultModeForRawFunctionCallReturn() .Query; Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -268,6 +283,7 @@ public void ShouldUseSetResultModeForSingleFunctionReturn() .Query; Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -279,6 +295,7 @@ public void ShouldUseSetResultModeForAllFunctionReturn() .Query; Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -290,6 +307,7 @@ public void ShouldUseSetResultModeForSpecificPropertyReturn() .Query; Assert.AreEqual(CypherResultMode.Set, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -301,6 +319,7 @@ public void ShouldUseProjectionResultModeForAnonymousObjectReturn() .Query; Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -312,6 +331,7 @@ public void ShouldUseProjectionResultModeForNamedObjectReturn() .Query; Assert.AreEqual(CypherResultMode.Projection, query.ResultMode); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -323,7 +343,7 @@ public void ShouldSupportAnonymousReturnTypesEndToEnd() {"p0", 123} }; - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Projection); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Projection, CypherResultFormat.Rest); var cypherApiQuery = new CypherApiQuery(cypherQuery); using (var testHarness = new RestTestHarness @@ -433,6 +453,7 @@ public void BinaryExpressionIsNotNull() .Query; Assert.AreEqual("MATCH (a)\r\nRETURN a IS NOT NULL AS NotNull", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] @@ -445,6 +466,7 @@ public void BinaryExpressionIsNull() .Query; Assert.AreEqual("MATCH (a)\r\nRETURN a IS NULL AS IsNull", query.QueryText); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } [Test] diff --git a/Test/Cypher/StartBitFormatterTests.cs b/Test/Cypher/StartBitFormatterTests.cs index 96476adb3..aa57b2787 100644 --- a/Test/Cypher/StartBitFormatterTests.cs +++ b/Test/Cypher/StartBitFormatterTests.cs @@ -360,7 +360,7 @@ static CypherQuery ToCypher(object startBits) var cypherText = StartBitFormatter.FormatAsCypherText(startBits, createParameter); - var query = new CypherQuery(cypherText, parameters, CypherResultMode.Projection); + var query = new CypherQuery(cypherText, parameters, CypherResultMode.Projection, CypherResultFormat.Rest); return query; } } diff --git a/Test/GraphClientTests/Cypher/ExecuteCypherTests.cs b/Test/GraphClientTests/Cypher/ExecuteCypherTests.cs index a656de95e..738d5c207 100644 --- a/Test/GraphClientTests/Cypher/ExecuteCypherTests.cs +++ b/Test/GraphClientTests/Cypher/ExecuteCypherTests.cs @@ -20,7 +20,7 @@ public void ShouldSendCommandAndNotCareAboutResults() {"p1", 219} }; - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, CypherResultFormat.Rest); var cypherApiQuery = new CypherApiQuery(cypherQuery); using (var testHarness = new RestTestHarness diff --git a/Test/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs b/Test/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs index 60083a0b8..7fe996c36 100644 --- a/Test/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs +++ b/Test/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs @@ -31,7 +31,7 @@ [Test] public void ShouldDeserializePathsResultAsSetBased() {"p1", 219} }; - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, CypherResultFormat.Rest); var cypherApiQuery = new CypherApiQuery(cypherQuery); using (var testHarness = new RestTestHarness @@ -91,7 +91,8 @@ RETURN type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId { {"p0", 123} }, - CypherResultMode.Projection); + CypherResultMode.Projection, + CypherResultFormat.Rest); var cypherApiQuery = new CypherApiQuery(cypherQuery); @@ -141,7 +142,8 @@ public void ShouldDeserializeArrayOfNodesInPropertyAsResultOfCollectFunctionInCy 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); + CypherResultMode.Projection, + CypherResultFormat.Rest); var cypherApiQuery = new CypherApiQuery(cypherQuery); @@ -302,7 +304,8 @@ MATCH x-[r]->n { {"p0", 123} }, - CypherResultMode.Projection); + CypherResultMode.Projection, + CypherResultFormat.Rest); var cypherApiQuery = new CypherApiQuery(cypherQuery); @@ -426,7 +429,8 @@ MATCH x-[r]->n { {"p0", 123} }, - CypherResultMode.Projection); + CypherResultMode.Projection, + CypherResultFormat.Rest); var cypherApiQuery = new CypherApiQuery(cypherQuery); @@ -547,7 +551,8 @@ MATCH x-[r]->n { {"p0", 123} }, - CypherResultMode.Projection); + CypherResultMode.Projection, + CypherResultFormat.Rest); var cypherApiQuery = new CypherApiQuery(cypherQuery); @@ -644,7 +649,7 @@ public void ShouldPromoteBadQueryResponseToNiceException() { // Arrange const string queryText = @"broken query"; - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection); + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Rest); var cypherApiQuery = new CypherApiQuery(cypherQuery); using (var testHarness = new RestTestHarness diff --git a/Test/Serialization/CypherJsonDeserializerTests.cs b/Test/Serialization/CypherJsonDeserializerTests.cs index 5982bf7df..1db5476f9 100644 --- a/Test/Serialization/CypherJsonDeserializerTests.cs +++ b/Test/Serialization/CypherJsonDeserializerTests.cs @@ -25,35 +25,35 @@ public class CypherJsonDeserializerTests }}"; [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) + [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); + var deserializer = new CypherJsonDeserializer(client, resultMode, format); var content = string.Format(contentFormat, input); // Act @@ -81,7 +81,8 @@ public void DeserializeShouldMapNodesInSetMode() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set, + CypherResultFormat.Rest); var content = @"{ 'data' : [ [ { 'outgoing_relationships' : 'http://localhost:7474/db/data/node/5/relationships/out', @@ -165,7 +166,7 @@ public void DeserializeShouldMapRelationshipsInSetMode() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set, CypherResultFormat.Rest); var content = @"{ 'data' : [ [ { 'start' : 'http://localhost:7474/db/data/node/55872', @@ -228,7 +229,7 @@ public void DeserializeShouldMapIEnumerableOfRelationshipsInAProjectionMode() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.Rest); var content = @"{ 'data' : [ [ { 'outgoing_relationships' : 'http://localhost:7474/db/data/node/55745/relationships/out', @@ -365,7 +366,7 @@ public void DeserializeShouldMapIEnumerableOfNodesReturnedByCollectInAProjection { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.Rest); var content = @"{ 'data' : [ [ [ { 'outgoing_relationships' : 'http://foo/db/data/node/920/relationships/out', @@ -429,7 +430,7 @@ public void DeserializeShouldMapIEnumerableOfNodesReturnedByCollectInColumnInPro { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); //The sample query that generated this data: /* @@ -555,7 +556,7 @@ public void DeserializeShouldMapNullIEnumerableOfNodesReturnedByCollectInInAProj { var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ [ null ] ] ], @@ -576,7 +577,7 @@ public void DeserializeShouldMapIEnumerableOfStringsInAProjectionMode() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ [ 'Ben Tu', 'Romiko Derbynew' ] ] ], 'columns' : [ 'Names' ] @@ -595,7 +596,7 @@ public void DeserializeShouldMapIEnumerableOfStringsThatAreEmptyInAProjectionMod { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ [ ] ] ], 'columns' : [ 'Names' ] @@ -613,7 +614,7 @@ public void DeserializeShouldMapIEnumerableOfStringsInSetMode() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer>(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ [ 'Ben Tu', 'Romiko Derbynew' ] ] ], 'columns' : [ 'Names' ] @@ -632,7 +633,7 @@ public void DeserializeShouldMapArrayOfStringsInSetMode() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ [ 'Ben Tu', 'Romiko Derbynew' ] ] ], 'columns' : [ 'Names' ] @@ -651,7 +652,7 @@ public void DeserializeShouldMapArrayOfIntegersInSetMode() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ [ '666', '777' ] ] ], 'columns' : [ 'Names' ] @@ -670,7 +671,7 @@ public void DeserializeShouldMapIntegerInSetMode() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ 666 ] ], 'columns' : [ 'count(distinct registration)' ] @@ -689,7 +690,7 @@ public void DeserializeShouldRespectJsonPropertyAttribute() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'columns' : [ 'Foo' ], 'data' : [ [ { @@ -726,7 +727,7 @@ public void DeserializeShouldMapNullCollectResultsWithOtherProperties() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'columns' : [ 'Fans', 'Poster' ], 'data' : [ [ [ null ], { @@ -769,7 +770,7 @@ public void DeserializeShouldMapNullCollectResultsWithOtherProperties_Test2() // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'columns' : [ 'Application', 'Alternatives' ], 'data' : [ [ { @@ -808,7 +809,7 @@ public void DeserializeShouldMapNullCollectResultsWithOtherProperties_Test3() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); var content = @"{'columns':['Fans'],'data':[[[null,null]]]}".Replace("'", "\""); // Act @@ -831,7 +832,7 @@ static void DeserializeShouldMapProjectionIntoAnonymousType(TAnon dummy) { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ 'Tokyo', 13000000 ], @@ -873,7 +874,7 @@ static void DeserializeShouldMapProjectionIntoAnonymousTypeWithNullCollectResult { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ 'Jim', [null] ] @@ -992,11 +993,97 @@ private class StateCityAndLabel 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, true); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment, true); var content = @"{'results':[ { 'columns':[ @@ -1008,8 +1095,8 @@ public void DeserializeNestedObjectsInTransaction() {'Name':'Baja California'}, ['State'], [ - {'Name':'Tijuana', Population: 1300000}, - {'Name':'Mexicali', Population: 500000} + {'Name':'Tijuana', 'Population': 1300000}, + {'Name':'Mexicali', 'Population': 500000} ] ] } @@ -1039,7 +1126,7 @@ public void DeserializeNestedObjectsInTransaction() public void DeserializerTwoLevelProjectionInTransaction() { var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, true); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment, true); var content = @"{'results':[ { 'columns':[ @@ -1071,7 +1158,7 @@ public void DeserializerTwoLevelProjectionInTransaction() public void DeserializerProjectionInTransaction() { var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, true); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment, true); var content = @"{'results':[ { 'columns':[ @@ -1101,7 +1188,7 @@ public void DeserializerProjectionInTransaction() public void DeserializeSimpleSetInTransaction() { var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, true); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment, true); var content = @"{'results':[ { 'columns':[ @@ -1123,7 +1210,7 @@ public void DeserializeSimpleSetInTransaction() public void DeserializeResultsSetInTransaction() { var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, true); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment, true); var content = @"{'results':[ { 'columns':[ @@ -1150,7 +1237,7 @@ public void DeserializeShouldPreserveUtf8Characters() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ { 'Name': '東京', 'Population': 13000000 } ], @@ -1184,7 +1271,7 @@ public void DeserializeShouldMapNodesToObjectsInSetModeWhenTheSourceLooksLikeANo { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var content = @" { 'data' : [ [ { @@ -1249,7 +1336,7 @@ public void DeserializeShouldMapNodesToObjectsInSetModeWhenTheSourceLooksLikeANo public void BadJsonShouldThrowExceptionThatIncludesJson() { var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); const string content = @"xyz-json-zyx"; var ex = Assert.Throws(() => @@ -1262,7 +1349,7 @@ public void BadJsonShouldThrowExceptionThatIncludesJson() public void BadJsonShouldThrowExceptionThatIncludesFullNameOfTargetType() { var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var ex = Assert.Throws(() => deserializer.Deserialize("xyz-json-zyx") @@ -1274,7 +1361,7 @@ public void BadJsonShouldThrowExceptionThatIncludesFullNameOfTargetType() public void ClassWithoutDefaultPublicConstructorShouldThrowExceptionThatExplainsThis() { var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ { 'Name': 'Tokyo', 'Population': 13000000 } ], @@ -1310,7 +1397,7 @@ public void DeserializeInt64IntoNullableInt64() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ 123 ] ], 'columns' : [ 'count(distinct registration)' ] @@ -1334,7 +1421,7 @@ public void DeserializeBase64StringIntoByteArrayInProjectionResultMode() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Projection, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ 'AQIDBA==' ] ], 'columns' : [ 'Array' ] @@ -1353,7 +1440,7 @@ public void DeserializeBase64StringIntoByteArrayInSetResultMode() { // Arrange var client = Substitute.For(); - var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set); + var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var content = @"{ 'data' : [ [ 'AQIDBA==' ] ], 'columns' : [ 'column1' ] diff --git a/Test/Transactions/QueriesInTransactionTests.cs b/Test/Transactions/QueriesInTransactionTests.cs index d15033217..5b895bf50 100644 --- a/Test/Transactions/QueriesInTransactionTests.cs +++ b/Test/Transactions/QueriesInTransactionTests.cs @@ -105,7 +105,7 @@ public void RollbackWithoutRequestsShouldNotGenerateMessage() public void UpdateTransactionEndpointAfterFirstRequest() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var rollbackTransactionRequest = MockRequest.Delete("/transaction/1"); using (var testHarness = new RestTestHarness { @@ -138,7 +138,7 @@ public void UpdateTransactionEndpointAfterFirstRequest() public void TransactionCommit() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); using (var testHarness = new RestTestHarness { @@ -175,7 +175,7 @@ public void PromoteDurableInAmbientTransaction() // 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)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); // there are no delete requests because those will be made in another app domain @@ -221,7 +221,7 @@ public void PromoteDurableInAmbientTransaction() public void SuppressTransactionScopeShouldNotEmitTransactionalQuery() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var nonTransactionalRequest = MockRequest.PostJson("/cypher", @"{'query': 'MATCH n\r\nRETURN count(n)', 'params': {}}"); @@ -274,7 +274,7 @@ public void SuppressTransactionScopeShouldNotEmitTransactionalQuery() public void NestedRequiresNewTransactionScope() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var commitTransactionRequest = MockRequest.PostJson("/transaction/1/commit", @"{ 'statements': []}"); var deleteRequest = MockRequest.Delete("/transaction/1"); @@ -320,9 +320,9 @@ public void NestedRequiresNewTransactionScope() public void NestedJoinedTransactionScope() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var secondRequest = MockRequest.PostJson("/transaction/1", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var deleteRequest = MockRequest.Delete("/transaction/1"); using (var testHarness = new RestTestHarness { @@ -366,7 +366,7 @@ public void NestedJoinedTransactionScope() public void TransactionRollbackInTransactionScope() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var deleteRequest = MockRequest.Delete("/transaction/1"); using (var testHarness = new RestTestHarness { @@ -394,7 +394,7 @@ public void TransactionRollbackInTransactionScope() public void TransactionCommitInTransactionScope() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); using (var testHarness = new RestTestHarness { @@ -423,9 +423,9 @@ public void TransactionCommitInTransactionScope() public void SecondRequestDoesntReturnCreateHttpStatus() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var secondRequest = MockRequest.PostJson("/transaction/1", @"{ - 'statements': [{'statement': 'MATCH t\r\nRETURN count(t)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH t\r\nRETURN count(t)', 'resultDataContents':[], 'parameters': {}}]}"); var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); using (var testHarness = new RestTestHarness { @@ -464,7 +464,7 @@ public void SecondRequestDoesntReturnCreateHttpStatus() public void KeepAliveAfterFirstRequest() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + '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 @@ -498,9 +498,9 @@ public void KeepAliveAfterFirstRequest() public void DeserializeResultsFromTransaction() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var deserializationRequest = MockRequest.PostJson("/transaction/1", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var rollbackTransactionRequest = MockRequest.Delete("/transaction/1"); using (var testHarness = new RestTestHarness { @@ -541,7 +541,7 @@ public void DeserializeResultsFromTransaction() public void OnTransactionDisposeCallRollback() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'parameters': {}}]}"); + 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); var rollbackTransactionRequest = MockRequest.Delete("/transaction/1"); using (var testHarness = new RestTestHarness { diff --git a/Test/Transactions/TransactionManagementTests.cs b/Test/Transactions/TransactionManagementTests.cs index 550e11af2..b1dc1ed6e 100644 --- a/Test/Transactions/TransactionManagementTests.cs +++ b/Test/Transactions/TransactionManagementTests.cs @@ -385,7 +385,7 @@ 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)}; + var cypherApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery, false)}; using (var testHarness = new RestTestHarness { From f417ffae74d013958a046ac287d2b8236fa9231c Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Fri, 17 Jan 2014 01:21:37 -0800 Subject: [PATCH 07/27] CollectAs, CollectDistinctAs returns T instead of Node Done with work of previous commits where the code could detect if the REST endpoint serialization was needed. --- .../Cypher/CypherReturnExpressionBuilder.cs | 25 +++++++++-------- Neo4jClient/Cypher/ICypherResultItem.cs | 4 +-- Neo4jClient/Cypher/IFluentCypherResultItem.cs | 2 +- Test/Cypher/AggregateTests.cs | 19 ++++++++++++- .../CypherReturnExpressionBuilderTests.cs | 28 ------------------- 5 files changed, 34 insertions(+), 44 deletions(-) diff --git a/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs b/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs index f5122c161..6f1999c19 100644 --- a/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs +++ b/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs @@ -17,10 +17,6 @@ public class CypherReturnExpressionBuilder internal const string ReturnAsTypeShouldBeOneOfExceptionMessage = "You've called As<{0}>() in your return clause, where {0} 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."; - internal const string CollectAsShouldNotBeNodeTExceptionMessage = "You've called CollectAs>(), however this method already wraps the type in Node<>. Your current code would result in Node>, which is invalid. Use CollectAs() instead."; - - internal const string CollectAsDistinctShouldNotBeNodeTExceptionMessage = "You've called CollectAsDistinct>(), however this method already wraps the type in Node<>. Your current code would result in Node>, which is invalid. Use CollectAsDistinct() instead."; - internal const string UnsupportedBinaryExpressionExceptionMessageFormat = "We don't currently support {0} as an expression. Please raise an issue at https://github.com/Readify/Neo4jClient including your query code."; 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."; @@ -382,16 +378,18 @@ static ExpressionBuild BuildCypherResultItemStatement( 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); - format = CypherResultFormat.Rest; 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); - format = CypherResultFormat.Rest; break; case "Count": finalStatement = string.Format("count({0})", targetObject.Name); @@ -422,10 +420,13 @@ static ExpressionBuild BuildCypherResultItemStatement( 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) 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/Test/Cypher/AggregateTests.cs b/Test/Cypher/AggregateTests.cs index 0c7c61791..01aa8f700 100644 --- a/Test/Cypher/AggregateTests.cs +++ b/Test/Cypher/AggregateTests.cs @@ -107,6 +107,23 @@ public void CountUsingICypderFluentQuery() 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() { @@ -121,7 +138,7 @@ public void CountAllWithOtherIdentities() Assert.AreEqual("RETURN count(*) AS Foo, collect(bar) AS Baz", query.QueryText); Assert.AreEqual(0, query.QueryParameters.Count()); - Assert.AreEqual(CypherResultFormat.Rest, query.ResultFormat); + Assert.AreEqual(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } } } diff --git a/Test/Cypher/CypherReturnExpressionBuilderTests.cs b/Test/Cypher/CypherReturnExpressionBuilderTests.cs index 2a67798d9..cc47b96ea 100644 --- a/Test/Cypher/CypherReturnExpressionBuilderTests.cs +++ b/Test/Cypher/CypherReturnExpressionBuilderTests.cs @@ -203,33 +203,6 @@ public void ReturnCollectedNodesInColumn() 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() @@ -426,7 +399,6 @@ public void ThrowNiceErrorForChainedMethods() { Foo = a .CollectAs() - .Select(f => f.Data) .ToList() }; From cb04e775c2e1e9fefc1062c3ee40247db5d00084 Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Mon, 27 Jan 2014 01:57:57 -0800 Subject: [PATCH 08/27] Fix for broken update of Transaction object after update TransactionScopeProxy implementation broke the update of the transaction object, although it was initially broken in a different way (because of threading). Also changed the unit test REST harness, so that the response is actually calculated in another thread, instead of being precalculated before the thread actually started (which alters the expected behavior for this bug). --- Neo4jClient/Execution/BatchExecutionPolicy.cs | 2 +- .../Execution/CypherExecutionPolicy.cs | 10 ++---- .../GraphClientBasedExecutionPolicy.cs | 2 +- .../Execution/GremlinExecutionPolicy.cs | 2 +- Neo4jClient/Execution/IExecutionPolicy.cs | 2 +- Neo4jClient/Execution/RestExecutionPolicy.cs | 2 +- Neo4jClient/GraphClient.cs | 17 +++++++--- .../Transactions/Neo4jTransactionProxy.cs | 5 ++- .../Transactions/TransactionManager.cs | 6 +++- .../Transactions/TransactionScopeProxy.cs | 31 ++++++++++++++----- Test/RestTestHarness.cs | 3 +- 11 files changed, 53 insertions(+), 29 deletions(-) diff --git a/Neo4jClient/Execution/BatchExecutionPolicy.cs b/Neo4jClient/Execution/BatchExecutionPolicy.cs index 75a3d7394..f7ee315ca 100644 --- a/Neo4jClient/Execution/BatchExecutionPolicy.cs +++ b/Neo4jClient/Execution/BatchExecutionPolicy.cs @@ -16,7 +16,7 @@ public override TransactionExecutionPolicy TransactionExecutionPolicy get { return TransactionExecutionPolicy.Denied; } } - public override void AfterExecution(IDictionary executionMetadata) + public override void AfterExecution(IDictionary executionMetadata, object executionContext) { } diff --git a/Neo4jClient/Execution/CypherExecutionPolicy.cs b/Neo4jClient/Execution/CypherExecutionPolicy.cs index f8b848945..1d42c3c26 100644 --- a/Neo4jClient/Execution/CypherExecutionPolicy.cs +++ b/Neo4jClient/Execution/CypherExecutionPolicy.cs @@ -86,21 +86,15 @@ public override string SerializeRequest(object toSerialize) return Client.Serializer.Serialize(new CypherApiQuery(query)); } - public override void AfterExecution(IDictionary executionMetadata) + public override void AfterExecution(IDictionary executionMetadata, object executionContext) { if (Client == null || executionMetadata == null || executionMetadata.Count == 0) { return; } - var transactionalClient = Client as ITransactionalGraphClient; - if (!InTransaction || transactionalClient == null) - { - return; - } - // determine if we need to update the transaction end point - var transaction = GetTransactionInScope(); + var transaction = executionContext as INeo4jTransaction; if (transaction == null || transaction.Endpoint != null) { return; diff --git a/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs b/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs index d53232708..1626dd318 100644 --- a/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs +++ b/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs @@ -27,7 +27,7 @@ public bool InTransaction } public abstract TransactionExecutionPolicy TransactionExecutionPolicy { get; } - public abstract void AfterExecution(IDictionary executionMetadata); + public abstract void AfterExecution(IDictionary executionMetadata, object executionContext); public virtual string SerializeRequest(object toSerialize) { diff --git a/Neo4jClient/Execution/GremlinExecutionPolicy.cs b/Neo4jClient/Execution/GremlinExecutionPolicy.cs index 8f39bd991..0f451f89f 100644 --- a/Neo4jClient/Execution/GremlinExecutionPolicy.cs +++ b/Neo4jClient/Execution/GremlinExecutionPolicy.cs @@ -16,7 +16,7 @@ public override TransactionExecutionPolicy TransactionExecutionPolicy get { return TransactionExecutionPolicy.Denied; } } - public override void AfterExecution(IDictionary executionMetadata) + public override void AfterExecution(IDictionary executionMetadata, object executionContext) { } diff --git a/Neo4jClient/Execution/IExecutionPolicy.cs b/Neo4jClient/Execution/IExecutionPolicy.cs index 4f8d17a82..e64d5442f 100644 --- a/Neo4jClient/Execution/IExecutionPolicy.cs +++ b/Neo4jClient/Execution/IExecutionPolicy.cs @@ -7,7 +7,7 @@ internal interface IExecutionPolicy { bool InTransaction { get; } TransactionExecutionPolicy TransactionExecutionPolicy { get; } - void AfterExecution(IDictionary executionMetadata); + void AfterExecution(IDictionary executionMetadata, object executionContext); string SerializeRequest(object toSerialize); Uri BaseEndpoint { get; } Uri AddPath(Uri startUri, object startReference); diff --git a/Neo4jClient/Execution/RestExecutionPolicy.cs b/Neo4jClient/Execution/RestExecutionPolicy.cs index 4219945ce..db24e50bc 100644 --- a/Neo4jClient/Execution/RestExecutionPolicy.cs +++ b/Neo4jClient/Execution/RestExecutionPolicy.cs @@ -16,7 +16,7 @@ public override TransactionExecutionPolicy TransactionExecutionPolicy get { return TransactionExecutionPolicy.Denied; } } - public override void AfterExecution(IDictionary executionMetadata) + public override void AfterExecution(IDictionary executionMetadata, object executionContext) { } diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index 708ccde82..ba3fe062b 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -883,6 +883,13 @@ private Task PrepareCypherRequest(CypherQuery quer .WithJsonContent(policy.SerializeRequest(query)); if (InTransaction) { + // 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 transactionObject = transactionManager.CurrentDtcTransaction ?? + transactionManager.CurrentNonDtcTransaction; + + // HttpStatusCode.Created may be returned when emitting the first query on a transaction return request .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created) @@ -894,7 +901,7 @@ private Task PrepareCypherRequest(CypherQuery quer // 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(GetMetadataFromResponse(response)); + policy.AfterExecution(GetMetadataFromResponse(response), transactionObject); var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, true); return new CypherPartialResult @@ -958,7 +965,7 @@ Task> IRawGraphClient.ExecuteGetCypherResultsAsync else { var response = responseTask.Result.ResponseObject; - policy.AfterExecution(GetMetadataFromResponse(response)); + policy.AfterExecution(GetMetadataFromResponse(response), null); results = deserializer .Deserialize(response.Content.ReadAsString()) @@ -997,7 +1004,7 @@ void IRawGraphClient.ExecuteCypher(CypherQuery query) throw ex.InnerExceptions.Single(); throw; } - policy.AfterExecution(GetMetadataFromResponse(task.Result.ResponseObject)); + policy.AfterExecution(GetMetadataFromResponse(task.Result.ResponseObject), null); stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs @@ -1026,7 +1033,9 @@ void IRawGraphClient.ExecuteMultipleCypherQueriesInTransaction(IEnumerable + /// Implements the TransactionScopeProxy interfaces for INeo4jTransaction + /// internal class Neo4jTransactionProxy : TransactionScopeProxy { private readonly bool _doCommitInScope; - public Neo4jTransactionProxy(ITransactionalGraphClient client, ITransaction transaction, bool newScope) + public Neo4jTransactionProxy(ITransactionalGraphClient client, INeo4jTransaction transaction, bool newScope) : base(client, transaction) { _doCommitInScope = newScope; diff --git a/Neo4jClient/Transactions/TransactionManager.cs b/Neo4jClient/Transactions/TransactionManager.cs index e32d79a6d..3317b6084 100644 --- a/Neo4jClient/Transactions/TransactionManager.cs +++ b/Neo4jClient/Transactions/TransactionManager.cs @@ -21,6 +21,10 @@ public TransactionManager(ITransactionalGraphClient 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); } @@ -135,7 +139,7 @@ private ITransaction BeginJoinTransaction() throw new ClosedTransactionException(null); } - var joinedTransaction = new Neo4jTransactionProxy(_client, parentScope.Transaction, false); + var joinedTransaction = new Neo4jTransactionProxy(_client, (INeo4jTransaction)parentScope.Transaction, false); PushScopeTransaction(joinedTransaction); return joinedTransaction; } diff --git a/Neo4jClient/Transactions/TransactionScopeProxy.cs b/Neo4jClient/Transactions/TransactionScopeProxy.cs index 508b57635..1a29730bf 100644 --- a/Neo4jClient/Transactions/TransactionScopeProxy.cs +++ b/Neo4jClient/Transactions/TransactionScopeProxy.cs @@ -7,20 +7,35 @@ namespace Neo4jClient.Transactions { - internal abstract class TransactionScopeProxy : ITransaction + /// + /// Represents a transaction scope within an ITransactionalManager. Encapsulates the real transaction, so that in reality + /// it only exists one single transaction object in a joined scope, but multiple TransactionScopeProxies that can be pushed, or + /// popped (in a scope context). + /// + internal abstract class TransactionScopeProxy : INeo4jTransaction { - private ITransactionalGraphClient _client; + private readonly ITransactionalGraphClient _client; private bool _markCommitted = false; private bool _disposing = false; + private INeo4jTransaction _transaction; - public ITransaction Transaction { get; private set; } + public ITransaction Transaction + { + get { return _transaction; } + } - protected TransactionScopeProxy(ITransactionalGraphClient client, ITransaction transaction) + protected TransactionScopeProxy(ITransactionalGraphClient client, INeo4jTransaction transaction) { _client = client; _disposing = false; - Transaction = transaction; + _transaction = transaction; + } + + public Uri Endpoint + { + get { return _transaction.Endpoint; } + set { _transaction.Endpoint = value; } } public virtual void Dispose() @@ -37,10 +52,10 @@ public virtual void Dispose() Rollback(); } - if (Transaction != null && ShouldDisposeTransaction()) + if (_transaction != null && ShouldDisposeTransaction()) { - Transaction.Dispose(); - Transaction = null; + _transaction.Dispose(); + _transaction = null; } } diff --git a/Test/RestTestHarness.cs b/Test/RestTestHarness.cs index d7643139d..5980ab5b6 100644 --- a/Test/RestTestHarness.cs +++ b/Test/RestTestHarness.cs @@ -102,8 +102,7 @@ public IHttpClient GenerateHttpClient(string baseUri) .ReturnsForAnyArgs(ci => { var request = ci.Arg(); - var response = HandleRequest(request, baseUri); - var task = new Task(() => response); + var task = new Task(() => HandleRequest(request, baseUri)); task.Start(); return task; }); From a5690345467c3f3ba0b10023b5620d116f67286f Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Tue, 28 Jan 2014 05:42:37 -0800 Subject: [PATCH 09/27] Transaction scheduler In this commit a scheduler with a maximum concurrency level of 1 per request controls every query done within a transaction. --- .../Execution/CypherExecutionPolicy.cs | 2 +- Neo4jClient/Execution/IResponseBuilder.cs | 3 + .../Execution/IResponseBuilder`TResult.cs | 5 + Neo4jClient/Execution/ResponseBuilder.cs | 44 +++- .../Execution/ResponseBuilder`TParse.cs | 23 +- Neo4jClient/GraphClient.cs | 64 ++---- Neo4jClient/Neo4jClient.csproj | 2 + .../Transactions/ITransactionManager.cs | 4 + .../Transactions/Neo4jTransactionProxy.cs | 12 +- .../Transactions/TransactionContext.cs | 151 ++++++++++++++ .../Transactions/TransactionHttpUtils.cs | 19 ++ .../Transactions/TransactionManager.cs | 68 +++++- .../Transactions/TransactionScopeProxy.cs | 26 +-- .../TransactionSinglePhaseNotification.cs | 3 +- Test/RestTestHarness.cs | 18 +- .../Transactions/QueriesInTransactionTests.cs | 197 +++++++++++++++++- .../TransactionManagementTests.cs | 3 +- 17 files changed, 553 insertions(+), 91 deletions(-) create mode 100644 Neo4jClient/Transactions/TransactionContext.cs create mode 100644 Neo4jClient/Transactions/TransactionHttpUtils.cs diff --git a/Neo4jClient/Execution/CypherExecutionPolicy.cs b/Neo4jClient/Execution/CypherExecutionPolicy.cs index 1d42c3c26..25b21a096 100644 --- a/Neo4jClient/Execution/CypherExecutionPolicy.cs +++ b/Neo4jClient/Execution/CypherExecutionPolicy.cs @@ -36,7 +36,7 @@ private INeo4jTransaction GetTransactionInScope() return null; } - return (INeo4jTransaction) proxiedTransaction.Transaction; + return (INeo4jTransaction) proxiedTransaction.TransactionContext; } public override Uri BaseEndpoint diff --git a/Neo4jClient/Execution/IResponseBuilder.cs b/Neo4jClient/Execution/IResponseBuilder.cs index 81d8c6259..eaf7bda00 100644 --- a/Neo4jClient/Execution/IResponseBuilder.cs +++ b/Neo4jClient/Execution/IResponseBuilder.cs @@ -13,12 +13,15 @@ internal interface IResponseBuilder 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(); } diff --git a/Neo4jClient/Execution/IResponseBuilder`TResult.cs b/Neo4jClient/Execution/IResponseBuilder`TResult.cs index 1c13540fb..9d7abe481 100644 --- a/Neo4jClient/Execution/IResponseBuilder`TResult.cs +++ b/Neo4jClient/Execution/IResponseBuilder`TResult.cs @@ -13,11 +13,16 @@ namespace Neo4jClient.Execution 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/ResponseBuilder.cs b/Neo4jClient/Execution/ResponseBuilder.cs index a9107eb5e..25e983ade 100644 --- a/Neo4jClient/Execution/ResponseBuilder.cs +++ b/Neo4jClient/Execution/ResponseBuilder.cs @@ -4,7 +4,6 @@ using System.Net; using System.Net.Http; using System.Net.Http.Headers; -using System.Security.Policy; using System.Text; using System.Threading.Tasks; @@ -70,7 +69,7 @@ public IResponseFailBuilder FailOnCondition(Func cond condition); } - private Task PrepareAsync() + private Task PrepareAsync(TaskFactory taskFactory) { if (_executionConfiguration.UseJsonStreaming) { @@ -87,27 +86,40 @@ private Task PrepareAsync() var credentials = Convert.ToBase64String(Encoding.ASCII.GetBytes(userInfo)); _request.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials); } - return _executionConfiguration.HttpClient.SendAsync(_request); + + 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); + return ExecuteAsync(null, null, null); } public Task ExecuteAsync(string commandDescription) { - return ExecuteAsync(commandDescription, null); + 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); + return ExecuteAsync(null, continuationFunction, null); } - public Task ExecuteAsync(string commandDescription, Func, HttpResponseMessage> continuationFunction) + public Task ExecuteAsync(string commandDescription, Func, HttpResponseMessage> continuationFunction, TaskFactory taskFactory) { - var executionTask = PrepareAsync().ContinueWith(requestTask => + var executionTask = PrepareAsync(taskFactory).ContinueWith(requestTask => { var response = requestTask.Result; if (string.IsNullOrEmpty(commandDescription)) @@ -149,17 +161,27 @@ public Task ExecuteAsync(Func, T public Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction) { - return ExecuteAsync(commandDescription).ContinueWith(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); + return Execute(null, null); } public HttpResponseMessage Execute(string commandDescription) { - var task = ExecuteAsync(commandDescription, null); + return Execute(commandDescription, null); + } + + public HttpResponseMessage Execute(string commandDescription, TaskFactory taskFactory) + { + var task = ExecuteAsync(commandDescription, null, taskFactory); try { Task.WaitAll(task); diff --git a/Neo4jClient/Execution/ResponseBuilder`TParse.cs b/Neo4jClient/Execution/ResponseBuilder`TParse.cs index 4d13bbdfa..57e7dc780 100644 --- a/Neo4jClient/Execution/ResponseBuilder`TParse.cs +++ b/Neo4jClient/Execution/ResponseBuilder`TParse.cs @@ -40,7 +40,7 @@ private TParse CastIntoResult(HttpResponseMessage response) public new Task ExecuteAsync(string commandDescription) { - return ExecuteAsync(commandDescription, null); + return ExecuteAsync(commandDescription, null, null); } public Task ExecuteAsync(Func, TParse> continuationFunction) @@ -50,7 +50,12 @@ public Task ExecuteAsync(Func, TParse> continuationFunction public Task ExecuteAsync(string commandDescription, Func, TParse> continuationFunction) { - var executionTask = base.ExecuteAsync(commandDescription) + 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)); @@ -59,7 +64,7 @@ public Task ExecuteAsync(string commandDescription, Func, T public new Task ExecuteAsync() { - return ExecuteAsync(null, null); + return ExecuteAsync(null, null, null); } public Task ExecuteAsync(Func, TExpected> continuationFunction) @@ -72,9 +77,19 @@ public Task ExecuteAsync(string commandDescription, Func ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction, TaskFactory taskFactory) + { + throw new NotImplementedException(); + } + public new TParse Execute(string commandDescription) { - return CastIntoResult(base.Execute(commandDescription)); + return Execute(commandDescription, null); + } + + public new TParse Execute(string commandDescription, TaskFactory taskFactory) + { + return CastIntoResult(base.Execute(commandDescription, taskFactory)); } public new TParse Execute() diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index ba3fe062b..91847bb06 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -88,14 +88,6 @@ private Uri BuildUri(string relativeUri) return new Uri(baseUri, relativeUri); } - private IDictionary GetMetadataFromResponse(HttpResponseMessage response) - { - return response.Headers.ToDictionary( - headerPair => headerPair.Key, - headerPair => (object) headerPair.Value - ); - } - private string SerializeAsJson(object contents) { return Serializer.Serialize(contents); @@ -878,41 +870,29 @@ public virtual IEnumerable ExecuteGetCypherResults(CypherQuery private Task PrepareCypherRequest(CypherQuery query, IExecutionPolicy policy) { - var request = Request.With(ExecutionConfiguration) - .Post(policy.BaseEndpoint) - .WithJsonContent(policy.SerializeRequest(query)); if (InTransaction) { - // 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 transactionObject = transactionManager.CurrentDtcTransaction ?? - transactionManager.CurrentNonDtcTransaction; - - - // HttpStatusCode.Created may be returned when emitting the first query on a transaction - return request - .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created) - .ExecuteAsync( - string.Format("The query was: {0}", query.QueryText), - responseTask => + 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 { - // 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(GetMetadataFromResponse(response), transactionObject); - - var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, true); - return new CypherPartialResult - { - DeserializationContext = - deserializer.CheckForErrorsInTransactionResponse(response.Content.ReadAsString()), - ResponseObject = response - }; - }); + DeserializationContext = + deserializer.CheckForErrorsInTransactionResponse(response.Content.ReadAsString()), + ResponseObject = response + }; + }); } - return request + + return Request.With(ExecutionConfiguration) + .Post(policy.BaseEndpoint) + .WithJsonContent(policy.SerializeRequest(query)) .WithExpectedStatusCodes(HttpStatusCode.OK) .ExecuteAsync(response => new CypherPartialResult { @@ -965,8 +945,6 @@ Task> IRawGraphClient.ExecuteGetCypherResultsAsync else { var response = responseTask.Result.ResponseObject; - policy.AfterExecution(GetMetadataFromResponse(response), null); - results = deserializer .Deserialize(response.Content.ReadAsString()) .ToList(); @@ -1004,7 +982,7 @@ void IRawGraphClient.ExecuteCypher(CypherQuery query) throw ex.InnerExceptions.Single(); throw; } - policy.AfterExecution(GetMetadataFromResponse(task.Result.ResponseObject), null); + policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(task.Result.ResponseObject), null); stopwatch.Stop(); OnOperationCompleted(new OperationCompletedEventArgs @@ -1035,7 +1013,7 @@ void IRawGraphClient.ExecuteMultipleCypherQueriesInTransaction(IEnumerable + + diff --git a/Neo4jClient/Transactions/ITransactionManager.cs b/Neo4jClient/Transactions/ITransactionManager.cs index b1080ada4..b2035399d 100644 --- a/Neo4jClient/Transactions/ITransactionManager.cs +++ b/Neo4jClient/Transactions/ITransactionManager.cs @@ -1,4 +1,7 @@ using System; +using System.Net.Http; +using System.Threading.Tasks; +using Neo4jClient.Cypher; namespace Neo4jClient.Transactions { @@ -14,5 +17,6 @@ public interface ITransactionManager : IDisposable ITransaction BeginTransaction(TransactionScopeOption option); void EndTransaction(); void RegisterToTransactionIfNeeded(); + Task EnqueueCypherRequest(string commandDescription, IGraphClient client, CypherQuery query); } } diff --git a/Neo4jClient/Transactions/Neo4jTransactionProxy.cs b/Neo4jClient/Transactions/Neo4jTransactionProxy.cs index 0a72266f4..481566798 100644 --- a/Neo4jClient/Transactions/Neo4jTransactionProxy.cs +++ b/Neo4jClient/Transactions/Neo4jTransactionProxy.cs @@ -12,8 +12,8 @@ internal class Neo4jTransactionProxy : TransactionScopeProxy { private readonly bool _doCommitInScope; - public Neo4jTransactionProxy(ITransactionalGraphClient client, INeo4jTransaction transaction, bool newScope) - : base(client, transaction) + public Neo4jTransactionProxy(ITransactionalGraphClient client, TransactionContext transactionContext, bool newScope) + : base(client, transactionContext) { _doCommitInScope = newScope; } @@ -22,7 +22,7 @@ protected override void DoCommit() { if (_doCommitInScope) { - Transaction.Commit(); + TransactionContext.Commit(); } } @@ -38,19 +38,19 @@ public override bool Committable public override void Rollback() { - Transaction.Rollback(); + TransactionContext.Rollback(); } public override void KeepAlive() { - Transaction.KeepAlive(); + TransactionContext.KeepAlive(); } public override bool IsOpen { get { - return Transaction != null && Transaction.IsOpen; + return TransactionContext != null && TransactionContext.IsOpen; } } } diff --git a/Neo4jClient/Transactions/TransactionContext.cs b/Neo4jClient/Transactions/TransactionContext.cs new file mode 100644 index 000000000..27295b8da --- /dev/null +++ b/Neo4jClient/Transactions/TransactionContext.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +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/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 index 3317b6084..37fc536f1 100644 --- a/Neo4jClient/Transactions/TransactionManager.cs +++ b/Neo4jClient/Transactions/TransactionManager.cs @@ -1,7 +1,11 @@ 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 { @@ -12,6 +16,8 @@ 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; @@ -26,6 +32,39 @@ public TransactionManager(ITransactionalGraphClient client) // 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 @@ -105,6 +144,16 @@ public ITransaction BeginTransaction(TransactionScopeOption scopeOption) 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) @@ -116,7 +165,7 @@ private void PushScopeTransaction(TransactionScopeProxy transaction) private ITransaction BeginNewTransaction() { - var transaction = new Neo4jTransactionProxy(_client, new Neo4jTransaction(_client), true); + var transaction = new Neo4jTransactionProxy(_client, GenerateTransaction(), true); PushScopeTransaction(transaction); return transaction; } @@ -139,7 +188,7 @@ private ITransaction BeginJoinTransaction() throw new ClosedTransactionException(null); } - var joinedTransaction = new Neo4jTransactionProxy(_client, (INeo4jTransaction)parentScope.Transaction, false); + var joinedTransaction = new Neo4jTransactionProxy(_client, GenerateTransaction(parentScope.TransactionContext), false); PushScopeTransaction(joinedTransaction); return joinedTransaction; } @@ -168,7 +217,7 @@ public void EndTransaction() } /// - /// Registers to ambient System.Transactions.Transaction if needed + /// Registers to ambient System.Transactions.TransactionContext if needed /// public void RegisterToTransactionIfNeeded() { @@ -180,6 +229,19 @@ public void RegisterToTransactionIfNeeded() _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; diff --git a/Neo4jClient/Transactions/TransactionScopeProxy.cs b/Neo4jClient/Transactions/TransactionScopeProxy.cs index 1a29730bf..c11bac9f7 100644 --- a/Neo4jClient/Transactions/TransactionScopeProxy.cs +++ b/Neo4jClient/Transactions/TransactionScopeProxy.cs @@ -8,8 +8,8 @@ namespace Neo4jClient.Transactions { /// - /// Represents a transaction scope within an ITransactionalManager. Encapsulates the real transaction, so that in reality - /// it only exists one single transaction object in a joined scope, but multiple TransactionScopeProxies that can be pushed, or + /// 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 @@ -17,25 +17,25 @@ internal abstract class TransactionScopeProxy : INeo4jTransaction private readonly ITransactionalGraphClient _client; private bool _markCommitted = false; private bool _disposing = false; - private INeo4jTransaction _transaction; + private TransactionContext _transactionContext; - public ITransaction Transaction + public TransactionContext TransactionContext { - get { return _transaction; } + get { return _transactionContext; } } - protected TransactionScopeProxy(ITransactionalGraphClient client, INeo4jTransaction transaction) + protected TransactionScopeProxy(ITransactionalGraphClient client, TransactionContext transactionContext) { _client = client; _disposing = false; - _transaction = transaction; + _transactionContext = transactionContext; } public Uri Endpoint { - get { return _transaction.Endpoint; } - set { _transaction.Endpoint = value; } + get { return _transactionContext.Endpoint; } + set { _transactionContext.Endpoint = value; } } public virtual void Dispose() @@ -47,15 +47,15 @@ public virtual void Dispose() _disposing = true; _client.EndTransaction(); - if (!_markCommitted && Committable && Transaction.IsOpen) + if (!_markCommitted && Committable && TransactionContext.IsOpen) { Rollback(); } - if (_transaction != null && ShouldDisposeTransaction()) + if (_transactionContext != null && ShouldDisposeTransaction()) { - _transaction.Dispose(); - _transaction = null; + _transactionContext.Dispose(); + _transactionContext = null; } } diff --git a/Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs b/Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs index cc7b30183..ce74e7554 100644 --- a/Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs +++ b/Neo4jClient/Transactions/TransactionSinglePhaseNotification.cs @@ -46,6 +46,7 @@ private void Enlist(Transaction transaction) // 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) @@ -76,7 +77,7 @@ public byte[] Promote() if (_transactionId == 0) { - throw new InvalidOperationException("For some reason we don't have a Transaction ID"); + throw new InvalidOperationException("For some reason we don't have a TransactionContext ID"); } var resourceManager = GetResourceManager(); diff --git a/Test/RestTestHarness.cs b/Test/RestTestHarness.cs index 5980ab5b6..0bb64d0d6 100644 --- a/Test/RestTestHarness.cs +++ b/Test/RestTestHarness.cs @@ -5,6 +5,7 @@ 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; @@ -20,6 +21,16 @@ public class RestTestHarness : IEnumerable, IDisposable 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) { @@ -110,7 +121,7 @@ public IHttpClient GenerateHttpClient(string baseUri) return httpClient; } - HttpResponseMessage HandleRequest(HttpRequestMessage request, string baseUri) + 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; @@ -200,7 +211,10 @@ static string NormalizeJson(string input) public void Dispose() { - AssertRequestConstraintsAreMet(); + if (assertConstraintsAreMet) + { + AssertRequestConstraintsAreMet(); + } } } } diff --git a/Test/Transactions/QueriesInTransactionTests.cs b/Test/Transactions/QueriesInTransactionTests.cs index 5b895bf50..5b92c7c65 100644 --- a/Test/Transactions/QueriesInTransactionTests.cs +++ b/Test/Transactions/QueriesInTransactionTests.cs @@ -1,9 +1,13 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; -using System.Text; +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; @@ -18,15 +22,21 @@ private static string ResetTransactionTimer() return new DateTime().AddSeconds(60).ToString("ddd, dd, MMM yyyy HH:mm:ss +0000"); } - private string GenerateInitTransactionResponse(int id) + private string GenerateInitTransactionResponse(int id, string results) { return string.Format( - @"{{'commit': 'http://foo/db/data/transaction/{0}/commit', 'results': [], 'errors': [], 'transaction': {{ 'expires': '{1}' }} }}", + @"{{'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() { @@ -129,7 +139,7 @@ public void UpdateTransactionEndpointAfterFirstRequest() Assert.AreEqual( new Uri("http://foo/db/data/transaction/1"), - ((INeo4jTransaction)((TransactionScopeProxy) transaction).Transaction).Endpoint); + ((INeo4jTransaction)((TransactionScopeProxy) transaction).TransactionContext).Endpoint); } } } @@ -177,6 +187,9 @@ public void PromoteDurableInAmbientTransaction() 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 @@ -185,7 +198,11 @@ public void PromoteDurableInAmbientTransaction() 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") }, @@ -560,10 +577,178 @@ public void OnTransactionDisposeCallRollback() // dummy query to generate request client.Cypher .Match("n") - .Return(n => n.Count()) + .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/Test/Transactions/TransactionManagementTests.cs b/Test/Transactions/TransactionManagementTests.cs index b1dc1ed6e..5977aec72 100644 --- a/Test/Transactions/TransactionManagementTests.cs +++ b/Test/Transactions/TransactionManagementTests.cs @@ -67,7 +67,8 @@ public void ShouldNotBeAbleToGetTransactionAfterTransactionScope() private ITransaction GetRealTransaction(ITransaction proxiedTransaction) { - return ((TransactionScopeProxy) proxiedTransaction).Transaction; + var txContext = ((TransactionScopeProxy) proxiedTransaction).TransactionContext; + return txContext == null ? null : txContext.Transaction; } [Test] From 28da8f4318f31d1f37c5d1676df0eedd234dd398 Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Wed, 5 Mar 2014 15:03:26 -0800 Subject: [PATCH 10/27] Removed a reference that was not being used --- Neo4jClient/Transactions/TransactionContext.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Neo4jClient/Transactions/TransactionContext.cs b/Neo4jClient/Transactions/TransactionContext.cs index 27295b8da..76ee63637 100644 --- a/Neo4jClient/Transactions/TransactionContext.cs +++ b/Neo4jClient/Transactions/TransactionContext.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading; From 5c55995b218e9c71a93866714caa1adc6adb7baf Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Mon, 21 Apr 2014 16:16:28 -0700 Subject: [PATCH 11/27] Workflow bug fix for ExecuteMultipleCypherQueriesInTransaction --- Neo4jClient/GraphClient.cs | 9 ++-- .../Transactions/QueriesInTransactionTests.cs | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index 91847bb06..06e064653 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -734,18 +734,19 @@ public Uri GremlinEndpoint private void CheckTransactionEnvironmentWithPolicy(IExecutionPolicy policy) { + bool inTransaction = InTransaction; + if (transactionManager != null) { transactionManager.RegisterToTransactionIfNeeded(); } - if (InTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Denied) + if (inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Denied) { throw new InvalidOperationException("Cannot be done inside a transaction scope."); } - - if (policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Required) + if (!inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Required) { throw new InvalidOperationException("Cannot be done outside a transaction scope."); } @@ -1008,7 +1009,7 @@ void IRawGraphClient.ExecuteMultipleCypherQueriesInTransaction(IEnumerable() + { + 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() { From 04f5d697f21718a2172f09ade9c87801a518254d Mon Sep 17 00:00:00 2001 From: Arturo Sevilla Date: Tue, 22 Apr 2014 12:20:09 -0700 Subject: [PATCH 12/27] NonDTC takes precedence over DTC transactions Although in general this was true, there was some edge cases for the GraphClient and the TransactionManager where there was mixing in the order. Included unit tests to test the expected behavior. --- .../Execution/CypherExecutionPolicy.cs | 15 +- Neo4jClient/GraphClient.cs | 4 +- .../Transactions/TransactionManager.cs | 10 +- .../Transactions/QueriesInTransactionTests.cs | 156 ++++++++++++++++++ .../TransactionManagementTests.cs | 20 +++ 5 files changed, 191 insertions(+), 14 deletions(-) diff --git a/Neo4jClient/Execution/CypherExecutionPolicy.cs b/Neo4jClient/Execution/CypherExecutionPolicy.cs index 25b21a096..f9efe5892 100644 --- a/Neo4jClient/Execution/CypherExecutionPolicy.cs +++ b/Neo4jClient/Execution/CypherExecutionPolicy.cs @@ -18,25 +18,26 @@ 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 ambientTransaction = transactionalClient.TransactionManager.CurrentDtcTransaction; - if (ambientTransaction != null) + var proxiedTransaction = transactionalClient.Transaction as TransactionScopeProxy; + if (proxiedTransaction != null) { - return (INeo4jTransaction) ambientTransaction; + return proxiedTransaction.TransactionContext;; } - var proxiedTransaction = transactionalClient.Transaction as TransactionScopeProxy; - if (proxiedTransaction == null) + var ambientTransaction = transactionalClient.TransactionManager.CurrentDtcTransaction; + if (ambientTransaction != null) { - return null; + return (INeo4jTransaction) ambientTransaction; } - return (INeo4jTransaction) proxiedTransaction.TransactionContext; + return null; } public override Uri BaseEndpoint diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index 06e064653..dbeed9d29 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -1012,8 +1012,8 @@ void IRawGraphClient.ExecuteMultipleCypherQueriesInTransaction(IEnumerable(), 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() { diff --git a/Test/Transactions/TransactionManagementTests.cs b/Test/Transactions/TransactionManagementTests.cs index 5977aec72..9593b4392 100644 --- a/Test/Transactions/TransactionManagementTests.cs +++ b/Test/Transactions/TransactionManagementTests.cs @@ -6,10 +6,12 @@ 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 { @@ -50,6 +52,24 @@ public void ShouldBeAbleToGetTransactionObjectAfterBeginTransaction() } } + [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() { From 36f1157af0ac1f04d68be44bbb30d64fb65460f0 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Mon, 13 Jul 2015 14:25:56 +0100 Subject: [PATCH 13/27] Adding ncrunch settings for tests to run successfully. --- .gitignore | 2 +- Neo4jClient/Neo4jClient.v2.ncrunchproject | Bin 0 -> 2802 bytes Test/Test.v2.ncrunchproject | Bin 0 -> 2802 bytes 3 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 Neo4jClient/Neo4jClient.v2.ncrunchproject create mode 100644 Test/Test.v2.ncrunchproject diff --git a/.gitignore b/.gitignore index 8c675a670..5f4e127f1 100644 --- a/.gitignore +++ b/.gitignore @@ -8,5 +8,5 @@ Neo4jClient.*.nupkg *.docstates *.crunchsolution.cache packages -*.ncrunch* +*.ncrunchsolution *.Cache diff --git a/Neo4jClient/Neo4jClient.v2.ncrunchproject b/Neo4jClient/Neo4jClient.v2.ncrunchproject new file mode 100644 index 0000000000000000000000000000000000000000..edc8256288238d0b7cd6ed5ca34114346acfaf9d GIT binary patch literal 2802 zcmb_e%T9w(5S_J&|KJ}~H^xQN1Ro0*#>UpTR-_dzV1WAgdG(yRy$+PtLTd;Kk2`bD zyyyF?FGI;ClV^z}lv2*{oXSLEnaUi0?(r?wZv)wvJvqSDms9+UWhRd*&Hp*xOZk+6 z9Lb?xed+yq&a@Hp6|y(rFu|SUPQfOE{y+*`ktFf}yckb~jB)->F<?X0DL78>KEu1Q++C+} zWGo}-&w%pS+Mmm;w$)-h0&pOUcR5E~Q?Nv8+Nq{BDs(cSQx@mKb6MdL93ZBRljzTGqiJPV&1bSxr$~_y4I}B zVX5nBb)>d3hC=_lnZY`*c8=x*EOgFnns)!A(HPPu#*jH%{!TRSYi4^1e;+R<{h?0W zb~1)>i>_5HMhk!vc#pNi$|Ls_6MBd0R;d6 literal 0 HcmV?d00001 diff --git a/Test/Test.v2.ncrunchproject b/Test/Test.v2.ncrunchproject new file mode 100644 index 0000000000000000000000000000000000000000..edc8256288238d0b7cd6ed5ca34114346acfaf9d GIT binary patch literal 2802 zcmb_e%T9w(5S_J&|KJ}~H^xQN1Ro0*#>UpTR-_dzV1WAgdG(yRy$+PtLTd;Kk2`bD zyyyF?FGI;ClV^z}lv2*{oXSLEnaUi0?(r?wZv)wvJvqSDms9+UWhRd*&Hp*xOZk+6 z9Lb?xed+yq&a@Hp6|y(rFu|SUPQfOE{y+*`ktFf}yckb~jB)->F<?X0DL78>KEu1Q++C+} zWGo}-&w%pS+Mmm;w$)-h0&pOUcR5E~Q?Nv8+Nq{BDs(cSQx@mKb6MdL93ZBRljzTGqiJPV&1bSxr$~_y4I}B zVX5nBb)>d3hC=_lnZY`*c8=x*EOgFnns)!A(HPPu#*jH%{!TRSYi4^1e;+R<{h?0W zb~1)>i>_5HMhk!vc#pNi$|Ls_6MBd0R;d6 literal 0 HcmV?d00001 From ba67a5fa462c1419d15e462dacc4a3b0d80ce140 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Tue, 14 Jul 2015 16:02:18 +0100 Subject: [PATCH 14/27] IDisposable and Notes Removed two notes to myself (made during merge) and implemented IDisposable to the MS guidlines. --- Neo4jClient/GraphClient.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index 20b359296..f31bc655a 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -1398,7 +1398,6 @@ public IEnumerable> QueryIndex(string indexName, IndexFor ind .WithExpectedStatusCodes(HttpStatusCode.OK) .ParseAs>>() .Execute() -//CDS: , resolver: JsonContractResolver .Select(nodeResponse => nodeResponse.ToNode(this)); } @@ -1427,7 +1426,6 @@ private IEnumerable> BuildLookupIndex(string exactIndexName, .Get(indexResource) .WithExpectedStatusCodes(HttpStatusCode.OK) .ParseAs>>() -//CDS: Resolver .Execute() .Select(query => query.ToNode(this)); } @@ -1461,13 +1459,19 @@ private void EnsureNodeWasCreated(BatchStepResult createResponse) throw new NeoException(exceptionResponse); } } - - public void Dispose() + + 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; } From fcb3f60f9d95cef93ffd14ffb2d6e95e217b8323 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Wed, 15 Jul 2015 09:08:28 +0100 Subject: [PATCH 15/27] Adding MyGet build.bat --- build.bat | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 build.bat diff --git a/build.bat b/build.bat new file mode 100644 index 000000000..79547f30c --- /dev/null +++ b/build.bat @@ -0,0 +1,31 @@ +@echo Off +set config=%1 +if "%config%" == "" ( + set config=Release +) + +set version= +if not "%PackageVersion%" == "" ( + set version=-Version %PackageVersion% +) + +set nunit="tools\nunit\nunit-console.exe" + +REM Build +%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild Neo4jClient.sln /p:Configuration="%config%" /m /v:M /fl /flp:LogFile=msbuild.log;Verbosity=Normal /nr:false +if not "%errorlevel%"=="0" goto failure + +REM Unit tests +%nunit% Test\bin\%config%\Neo4jClient.Test.dll +if not "%errorlevel%"=="0" goto failure + +REM Package +mkdir Build +call %nuget% pack "Neo4jClient\Neo4jClient.csproj" -symbols -o Build -p Configuration=%config% %version% +if not "%errorlevel%"=="0" goto failure + +:success +exit 0 + +:failure +exit -1 \ No newline at end of file From cb13027a45250031b3773edc40f071bc1a99c15f Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Wed, 15 Jul 2015 10:49:42 +0100 Subject: [PATCH 16/27] Updating build.bat to point to proj rather than dll --- Test/Test.csproj | 6 +++--- Test/packages.config | 8 ++++---- build.bat | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Test/Test.csproj b/Test/Test.csproj index b22f0e9dc..885cb747b 100644 --- a/Test/Test.csproj +++ b/Test/Test.csproj @@ -38,9 +38,9 @@ False ..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll - - False - ..\packages\NSubstitute.1.6.0.0\lib\NET40\NSubstitute.dll + + ..\packages\NSubstitute.1.8.2.0\lib\net40\NSubstitute.dll + True False diff --git a/Test/packages.config b/Test/packages.config index 569ed98db..a28833d52 100644 --- a/Test/packages.config +++ b/Test/packages.config @@ -1,7 +1,7 @@  - - - - + + + + \ No newline at end of file diff --git a/build.bat b/build.bat index 79547f30c..e411431b1 100644 --- a/build.bat +++ b/build.bat @@ -12,11 +12,11 @@ if not "%PackageVersion%" == "" ( set nunit="tools\nunit\nunit-console.exe" REM Build -%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild Neo4jClient.sln /p:Configuration="%config%" /m /v:M /fl /flp:LogFile=msbuild.log;Verbosity=Normal /nr:false +%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild Neo4jClient.sln /p:Configuration="%config%" /m /v:M /fl /flp:LogFile=msbuild.log;Verbosity=Normal /nr:false /p:nowarn=1574,0618 if not "%errorlevel%"=="0" goto failure REM Unit tests -%nunit% Test\bin\%config%\Neo4jClient.Test.dll +%nunit% Test\Test.csproj if not "%errorlevel%"=="0" goto failure REM Package From a93b245fbdc7181d3a29d2e8369ead9c944db404 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Wed, 15 Jul 2015 10:53:40 +0100 Subject: [PATCH 17/27] More MyGet changes. --- build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.bat b/build.bat index e411431b1..bd5b9a435 100644 --- a/build.bat +++ b/build.bat @@ -12,7 +12,7 @@ if not "%PackageVersion%" == "" ( set nunit="tools\nunit\nunit-console.exe" REM Build -%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild Neo4jClient.sln /p:Configuration="%config%" /m /v:M /fl /flp:LogFile=msbuild.log;Verbosity=Normal /nr:false /p:nowarn=1574,0618 +%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild Neo4jClient.sln /p:Configuration="%config%" /m /v:M /fl /flp:LogFile=msbuild.log;Verbosity=Normal /nr:false /p:nowarn="1574,0618" if not "%errorlevel%"=="0" goto failure REM Unit tests From 3c8cf82f7fb3490f8a25a73e1567858eb004ab77 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Wed, 15 Jul 2015 11:10:12 +0100 Subject: [PATCH 18/27] Turns out no need for custom bat. --- build.bat | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 build.bat diff --git a/build.bat b/build.bat deleted file mode 100644 index bd5b9a435..000000000 --- a/build.bat +++ /dev/null @@ -1,31 +0,0 @@ -@echo Off -set config=%1 -if "%config%" == "" ( - set config=Release -) - -set version= -if not "%PackageVersion%" == "" ( - set version=-Version %PackageVersion% -) - -set nunit="tools\nunit\nunit-console.exe" - -REM Build -%WINDIR%\Microsoft.NET\Framework\v4.0.30319\msbuild Neo4jClient.sln /p:Configuration="%config%" /m /v:M /fl /flp:LogFile=msbuild.log;Verbosity=Normal /nr:false /p:nowarn="1574,0618" -if not "%errorlevel%"=="0" goto failure - -REM Unit tests -%nunit% Test\Test.csproj -if not "%errorlevel%"=="0" goto failure - -REM Package -mkdir Build -call %nuget% pack "Neo4jClient\Neo4jClient.csproj" -symbols -o Build -p Configuration=%config% %version% -if not "%errorlevel%"=="0" goto failure - -:success -exit 0 - -:failure -exit -1 \ No newline at end of file From ef6478965a53ac8834b1c6492b307663a8a41bd6 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Wed, 15 Jul 2015 11:36:25 +0100 Subject: [PATCH 19/27] Renaming and moving 'Test' folder Renamed 'Test' folder to 'Neo4jClient.Tests' for MyGet to scan for --- .../ApiModels/GremlinTableCapResponseTests.cs | 702 ++-- .../ApiModels/RootApiResponseTests.cs | 54 +- {Test => Neo4jClient.Tests}/ApiUsageIdeas.cs | 122 +- .../CultureInfoSetupFixture.cs | 42 +- .../Cypher/AggregateTests.cs | 288 +- .../Cypher/CypherFluentQueryConstraintTest.cs | 0 .../CypherFluentQueryCreateUniqueTests.cs | 102 +- .../Cypher/CypherFluentQueryDeleteTests.cs | 198 +- .../Cypher/CypherFluentQueryDropTests.cs | 190 +- .../Cypher/CypherFluentQueryForEachTests.cs | 0 .../Cypher/CypherFluentQueryLimitTests.cs | 206 +- .../Cypher/CypherFluentQueryMatchTests.cs | 0 .../Cypher/CypherFluentQueryMergeTests.cs | 142 +- .../CypherFluentQueryParserVersionTests.cs | 122 +- .../Cypher/CypherFluentQueryRemoveTests.cs | 0 .../Cypher/CypherFluentQueryResultsTests.cs | 318 +- .../Cypher/CypherFluentQueryReturnTests.cs | 1318 ++++---- .../Cypher/CypherFluentQuerySetTests.cs | 84 +- .../Cypher/CypherFluentQuerySkipTests.cs | 208 +- .../Cypher/CypherFluentQueryStartTests.cs | 560 ++-- .../Cypher/CypherFluentQueryTests.cs | 2238 ++++++------- .../Cypher/CypherFluentQueryUnwindTests.cs | 0 .../CypherFluentQueryUsingIndexTests.cs | 0 .../Cypher/CypherFluentQueryWhereTests.cs | 218 +- .../Cypher/CypherFluentQueryWithParamTests.cs | 306 +- .../Cypher/CypherFluentQueryWithTests.cs | 388 +-- .../Cypher/CypherQueryTests.cs | 0 .../CypherReturnExpressionBuilderTests.cs | 1112 +++---- .../CypherWhereExpressionBuilderTests.cs | 1278 +++---- .../Cypher/DocumentationExamples.cs | 602 ++-- .../Cypher/QueryWriterTests.cs | 258 +- .../Cypher/StartBitFormatterTests.cs | 734 ++-- .../Cypher/UnionTests.cs | 74 +- {Test => Neo4jClient.Tests}/Domain/Product.cs | 14 +- .../Domain/StorageLocation.cs | 12 +- {Test => Neo4jClient.Tests}/Domain/User.cs | 12 +- .../GraphClientTests/ConnectTests.cs | 470 +-- .../GraphClientTests/CreateIndexTests.cs | 274 +- .../GraphClientTests/CreateNodeTests.cs | 1244 +++---- .../CreateRelationshipTests.cs | 268 +- .../Cypher/ExecuteCypherTests.cs | 222 +- .../Cypher/ExecuteGetCypherResultsTests.cs | 1390 ++++---- .../GraphClientTests/DeleteIndexTests.cs | 52 +- .../GraphClientTests/DeleteNodeTests.cs | 194 +- .../DeleteRelationshipTests.cs | 100 +- .../GraphClientTests/GetIndexesTests.cs | 148 +- .../GraphClientTests/GetNodeTests.cs | 464 +-- .../Gremlin/ExecuteGetAllNodesGremlinTests.cs | 296 +- .../ExecuteGetAllRelationshipsGremlinTests.cs | 572 ++-- .../Gremlin/ExecuteScalarGremlinTests.cs | 120 +- .../GraphClientTests/IndexExistsTests.cs | 64 +- .../GraphClientTests/LookupIndexTests.cs | 108 +- .../GraphClientTests/QueryNodeIndexTests.cs | 110 +- .../GraphClientTests/ReIndexNodesTests.cs | 386 +-- .../ReIndexRelationshipsTests.cs | 420 +-- .../GraphClientTests/RootNodeTests.cs | 64 +- .../GraphClientTests/ServerVersionTests.cs | 42 +- .../GraphClientTests/UpdateNodeTests.cs | 594 ++-- .../UpdateRelationshipTests.cs | 152 +- .../Gremlin/AggregateStepTests.cs | 122 +- .../Gremlin/AsStepTests.cs | 76 +- .../Gremlin/BackTests.cs | 114 +- .../Gremlin/BasicStepsTests.cs | 762 ++--- .../Gremlin/CopySplitStepTests.cs | 248 +- .../Gremlin/EmitPropertyStepTests.cs | 70 +- .../Gremlin/ExceptStepTests.cs | 106 +- .../Gremlin/ExhaustMergeStepTests.cs | 70 +- .../Gremlin/FairMergeStepTests.cs | 70 +- .../Gremlin/FormatGremlinFilterTests.cs | 1406 ++++---- .../Gremlin/GremlinClientTests.cs | 118 +- .../Gremlin/GremlinDistinctStepTests.cs | 70 +- .../Gremlin/GremlinHasNextStepTests.cs | 70 +- .../Gremlin/GremlinNodeEnumerableTests.cs | 146 +- .../Gremlin/GremlinPagedEnumeratorTests.cs | 422 +-- .../Gremlin/IfThenElseTests.cs | 170 +- .../Gremlin/IteratorTests.cs | 154 +- .../Gremlin/LoopStepTests.cs | 120 +- .../Gremlin/PrintLineStatementTests.cs | 38 +- .../Gremlin/RetainStepTests.cs | 106 +- .../Gremlin/StoreStepTests.cs | 106 +- .../Gremlin/TableTests.cs | 206 +- .../Gremlin/TranslateFilterTests.cs | 700 ++-- .../IndexEntryTests.cs | 188 +- {Test => Neo4jClient.Tests}/MockRequest.cs | 110 +- {Test => Neo4jClient.Tests}/MockResponse.cs | 212 +- .../MockResponseThrows.cs | 0 .../MockResponseThrowsException.cs | 0 .../Neo4jClient.Tests.csproj | 388 +-- .../Neo4jClient.Tests.ncrunchproject | 32 +- .../Neo4jClient.Tests.v2.ncrunchproject | Bin .../NodeReferenceTests.cs | 334 +- {Test => Neo4jClient.Tests}/NodeTests.cs | 166 +- .../Properties/AssemblyInfo.cs | 72 +- .../RelationshipReferenceTests.cs | 152 +- .../RelationshipTests.cs | 290 +- .../Relationships/OwnedBy.cs | 36 +- .../Relationships/Requires.cs | 48 +- .../Relationships/StoredIn.cs | 38 +- .../RestTestHarness.cs | 440 +-- .../CustomJsonDeserializerTests.cs | 666 ++-- .../CustomJsonSerializerTests.cs | 484 +-- .../CypherJsonDeserializerTests.cs | 2946 ++++++++--------- .../UserSuppliedSerializationTests.cs | 464 +-- .../Transactions/QueriesInTransactionTests.cs | 0 .../Transactions/RestCallScenarioTests.cs | 0 .../TransactionManagementTests.cs | 0 {Test => Neo4jClient.Tests}/UtilitiesTests.cs | 62 +- {Test => Neo4jClient.Tests}/packages.config | 12 +- Neo4jClient.sln | 8 +- 109 files changed, 16138 insertions(+), 16136 deletions(-) rename {Test => Neo4jClient.Tests}/ApiModels/GremlinTableCapResponseTests.cs (97%) rename {Test => Neo4jClient.Tests}/ApiModels/RootApiResponseTests.cs (97%) rename {Test => Neo4jClient.Tests}/ApiUsageIdeas.cs (97%) rename {Test => Neo4jClient.Tests}/CultureInfoSetupFixture.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/AggregateTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryConstraintTest.cs (100%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryCreateUniqueTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryDeleteTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryDropTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryForEachTests.cs (100%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryLimitTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryMatchTests.cs (100%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryMergeTests.cs (96%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryParserVersionTests.cs (96%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryRemoveTests.cs (100%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryResultsTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryReturnTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQuerySetTests.cs (96%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQuerySkipTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryStartTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryUnwindTests.cs (100%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryUsingIndexTests.cs (100%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryWhereTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryWithParamTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherFluentQueryWithTests.cs (96%) rename {Test => Neo4jClient.Tests}/Cypher/CypherQueryTests.cs (100%) rename {Test => Neo4jClient.Tests}/Cypher/CypherReturnExpressionBuilderTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/CypherWhereExpressionBuilderTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/DocumentationExamples.cs (96%) rename {Test => Neo4jClient.Tests}/Cypher/QueryWriterTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/StartBitFormatterTests.cs (97%) rename {Test => Neo4jClient.Tests}/Cypher/UnionTests.cs (96%) rename {Test => Neo4jClient.Tests}/Domain/Product.cs (95%) rename {Test => Neo4jClient.Tests}/Domain/StorageLocation.cs (95%) rename {Test => Neo4jClient.Tests}/Domain/User.cs (95%) rename {Test => Neo4jClient.Tests}/GraphClientTests/ConnectTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/CreateIndexTests.cs (96%) rename {Test => Neo4jClient.Tests}/GraphClientTests/CreateNodeTests.cs (98%) rename {Test => Neo4jClient.Tests}/GraphClientTests/CreateRelationshipTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/Cypher/ExecuteCypherTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs (98%) rename {Test => Neo4jClient.Tests}/GraphClientTests/DeleteIndexTests.cs (96%) rename {Test => Neo4jClient.Tests}/GraphClientTests/DeleteNodeTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/DeleteRelationshipTests.cs (96%) rename {Test => Neo4jClient.Tests}/GraphClientTests/GetIndexesTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/GetNodeTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs (96%) rename {Test => Neo4jClient.Tests}/GraphClientTests/IndexExistsTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/LookupIndexTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/QueryNodeIndexTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/ReIndexNodesTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/ReIndexRelationshipsTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/RootNodeTests.cs (96%) rename {Test => Neo4jClient.Tests}/GraphClientTests/ServerVersionTests.cs (96%) rename {Test => Neo4jClient.Tests}/GraphClientTests/UpdateNodeTests.cs (97%) rename {Test => Neo4jClient.Tests}/GraphClientTests/UpdateRelationshipTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/AggregateStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/AsStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/BackTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/BasicStepsTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/CopySplitStepTests.cs (98%) rename {Test => Neo4jClient.Tests}/Gremlin/EmitPropertyStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/ExceptStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/ExhaustMergeStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/FairMergeStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/FormatGremlinFilterTests.cs (98%) rename {Test => Neo4jClient.Tests}/Gremlin/GremlinClientTests.cs (96%) rename {Test => Neo4jClient.Tests}/Gremlin/GremlinDistinctStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/GremlinHasNextStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/GremlinNodeEnumerableTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/GremlinPagedEnumeratorTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/IfThenElseTests.cs (98%) rename {Test => Neo4jClient.Tests}/Gremlin/IteratorTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/LoopStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/PrintLineStatementTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/RetainStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/StoreStepTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/TableTests.cs (97%) rename {Test => Neo4jClient.Tests}/Gremlin/TranslateFilterTests.cs (97%) rename {Test => Neo4jClient.Tests}/IndexEntryTests.cs (97%) rename {Test => Neo4jClient.Tests}/MockRequest.cs (96%) rename {Test => Neo4jClient.Tests}/MockResponse.cs (97%) rename {Test => Neo4jClient.Tests}/MockResponseThrows.cs (100%) rename {Test => Neo4jClient.Tests}/MockResponseThrowsException.cs (100%) rename Test/Test.csproj => Neo4jClient.Tests/Neo4jClient.Tests.csproj (98%) rename Test/Test.ncrunchproject => Neo4jClient.Tests/Neo4jClient.Tests.ncrunchproject (98%) rename Test/Test.v2.ncrunchproject => Neo4jClient.Tests/Neo4jClient.Tests.v2.ncrunchproject (100%) rename {Test => Neo4jClient.Tests}/NodeReferenceTests.cs (97%) rename {Test => Neo4jClient.Tests}/NodeTests.cs (97%) rename {Test => Neo4jClient.Tests}/Properties/AssemblyInfo.cs (97%) rename {Test => Neo4jClient.Tests}/RelationshipReferenceTests.cs (96%) rename {Test => Neo4jClient.Tests}/RelationshipTests.cs (97%) rename {Test => Neo4jClient.Tests}/Relationships/OwnedBy.cs (96%) rename {Test => Neo4jClient.Tests}/Relationships/Requires.cs (96%) rename {Test => Neo4jClient.Tests}/Relationships/StoredIn.cs (96%) rename {Test => Neo4jClient.Tests}/RestTestHarness.cs (97%) rename {Test => Neo4jClient.Tests}/Serialization/CustomJsonDeserializerTests.cs (97%) rename {Test => Neo4jClient.Tests}/Serialization/CustomJsonSerializerTests.cs (96%) rename {Test => Neo4jClient.Tests}/Serialization/CypherJsonDeserializerTests.cs (98%) rename {Test => Neo4jClient.Tests}/Serialization/UserSuppliedSerializationTests.cs (97%) rename {Test => Neo4jClient.Tests}/Transactions/QueriesInTransactionTests.cs (100%) rename {Test => Neo4jClient.Tests}/Transactions/RestCallScenarioTests.cs (100%) rename {Test => Neo4jClient.Tests}/Transactions/TransactionManagementTests.cs (100%) rename {Test => Neo4jClient.Tests}/UtilitiesTests.cs (98%) rename {Test => Neo4jClient.Tests}/packages.config (98%) 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 97% rename from Test/Cypher/AggregateTests.cs rename to Neo4jClient.Tests/Cypher/AggregateTests.cs index 01aa8f700..b3fc7b9f0 100644 --- a/Test/Cypher/AggregateTests.cs +++ b/Neo4jClient.Tests/Cypher/AggregateTests.cs @@ -1,144 +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()); - 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); - } - } -} +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 97% rename from Test/Cypher/CypherFluentQueryReturnTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs index b2550af63..38e852d40 100644 --- a/Test/Cypher/CypherFluentQueryReturnTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs @@ -1,659 +1,659 @@ -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(@"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"]); - 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; } - } - } -} +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(@"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"]); + 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 97% rename from Test/Cypher/CypherFluentQueryStartTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryStartTests.cs index 54425f59f..708832397 100644 --- a/Test/Cypher/CypherFluentQueryStartTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryStartTests.cs @@ -1,280 +1,280 @@ -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(@"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 + { + } + } +} diff --git a/Test/Cypher/CypherFluentQueryTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs similarity index 97% rename from Test/Cypher/CypherFluentQueryTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs index 075e49901..9ba4e9d0a 100644 --- a/Test/Cypher/CypherFluentQueryTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs @@ -1,1119 +1,1119 @@ -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; + + 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; } + } + } +} 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 97% rename from Test/Cypher/CypherReturnExpressionBuilderTests.cs rename to Neo4jClient.Tests/Cypher/CypherReturnExpressionBuilderTests.cs index 558407a54..ef9eb75e3 100644 --- a/Test/Cypher/CypherReturnExpressionBuilderTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherReturnExpressionBuilderTests.cs @@ -1,556 +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] - 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; } - } - } -} +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 97% rename from Test/Cypher/QueryWriterTests.cs rename to Neo4jClient.Tests/Cypher/QueryWriterTests.cs index 73d7b3f81..0188376ea 100644 --- a/Test/Cypher/QueryWriterTests.cs +++ b/Neo4jClient.Tests/Cypher/QueryWriterTests.cs @@ -1,129 +1,129 @@ -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 +{ + [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"]); + } + } +} 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 aa57b2787..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, CypherResultFormat.Rest); - 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 97% rename from Test/GraphClientTests/ConnectTests.cs rename to Neo4jClient.Tests/GraphClientTests/ConnectTests.cs index d9cbf5304..5cf5199f2 100644 --- a/Test/GraphClientTests/ConnectTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/ConnectTests.cs @@ -1,235 +1,235 @@ -using System; -using System.Linq; -using System.Net; -using System.Net.Http; -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().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.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"); - } - } -} +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +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().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.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"); + } + } +} 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 98% rename from Test/GraphClientTests/CreateNodeTests.cs rename to Neo4jClient.Tests/GraphClientTests/CreateNodeTests.cs index 2be9db3c5..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.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"; } - } - } - } +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 97% rename from Test/GraphClientTests/Cypher/ExecuteCypherTests.cs rename to Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteCypherTests.cs index 9c24c861e..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, 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"); - } - } - } -} +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 98% rename from Test/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs rename to Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs index 7fe996c36..49ca68763 100644 --- a/Test/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs @@ -1,695 +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, 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); - } - } - } -} +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 97% rename from Test/MockResponse.cs rename to Neo4jClient.Tests/MockResponse.cs index cdb18d47e..28179b023 100644 --- a/Test/MockResponse.cs +++ b/Neo4jClient.Tests/MockResponse.cs @@ -1,106 +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 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(); - } - } -} +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 98% rename from Test/Test.csproj rename to Neo4jClient.Tests/Neo4jClient.Tests.csproj index 885cb747b..b658ea758 100644 --- a/Test/Test.csproj +++ b/Neo4jClient.Tests/Neo4jClient.Tests.csproj @@ -1,195 +1,195 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {2724A871-4B4F-4C83-8E0F-2439F69CADA2} - Library - Properties - Neo4jClient.Test - Neo4jClient.Test - v4.0 - 512 - ..\ - true - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - False - ..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll - - - ..\packages\NSubstitute.1.8.2.0\lib\net40\NSubstitute.dll - True - - - False - ..\packages\NUnit.2.6.2\lib\nunit.framework.dll - - - - - - - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll - - - ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {343B9889-6DDF-4474-A1EC-05508A652E5A} - Neo4jClient - - - - - - - - + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {2724A871-4B4F-4C83-8E0F-2439F69CADA2} + Library + Properties + Neo4jClient.Test + Neo4jClient.Test + v4.0 + 512 + ..\ + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll + + + ..\packages\NSubstitute.1.8.2.0\lib\net40\NSubstitute.dll + True + + + False + ..\packages\NUnit.2.6.2\lib\nunit.framework.dll + + + + + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.dll + + + ..\packages\Microsoft.Net.Http.2.0.20710.0\lib\net40\System.Net.Http.WebRequest.dll + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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/Test/Test.v2.ncrunchproject b/Neo4jClient.Tests/Neo4jClient.Tests.v2.ncrunchproject similarity index 100% rename from Test/Test.v2.ncrunchproject rename to Neo4jClient.Tests/Neo4jClient.Tests.v2.ncrunchproject 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 97% rename from Test/RestTestHarness.cs rename to Neo4jClient.Tests/RestTestHarness.cs index 0bb64d0d6..6fb9021ac 100644 --- a/Test/RestTestHarness.cs +++ b/Neo4jClient.Tests/RestTestHarness.cs @@ -1,220 +1,220 @@ -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(); - } - } - } -} +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 77e0262d0..c71c520cd 100644 --- a/Test/Serialization/CustomJsonDeserializerTests.cs +++ b/Neo4jClient.Tests/Serialization/CustomJsonDeserializerTests.cs @@ -1,333 +1,333 @@ -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+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] - 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 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+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] + 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 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 0c3264a61..6d00fb05c 100644 --- a/Test/Serialization/CustomJsonSerializerTests.cs +++ b/Neo4jClient.Tests/Serialization/CustomJsonSerializerTests.cs @@ -1,242 +1,242 @@ -using System; -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 DateModel - { - 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] - 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 DateModel - { - public DateTimeOffset DateTime { get; set; } - public DateTimeOffset? 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 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 DateModel + { + 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] + 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 DateModel + { + public DateTimeOffset DateTime { get; set; } + public DateTimeOffset? 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 98% rename from Test/Serialization/CypherJsonDeserializerTests.cs rename to Neo4jClient.Tests/Serialization/CypherJsonDeserializerTests.cs index 91b545d20..e4cc452ed 100644 --- a/Test/Serialization/CypherJsonDeserializerTests.cs +++ b/Neo4jClient.Tests/Serialization/CypherJsonDeserializerTests.cs @@ -1,1473 +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, 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); - } - } -} +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/Test/Transactions/QueriesInTransactionTests.cs b/Neo4jClient.Tests/Transactions/QueriesInTransactionTests.cs similarity index 100% rename from Test/Transactions/QueriesInTransactionTests.cs rename to Neo4jClient.Tests/Transactions/QueriesInTransactionTests.cs diff --git a/Test/Transactions/RestCallScenarioTests.cs b/Neo4jClient.Tests/Transactions/RestCallScenarioTests.cs similarity index 100% rename from Test/Transactions/RestCallScenarioTests.cs rename to Neo4jClient.Tests/Transactions/RestCallScenarioTests.cs diff --git a/Test/Transactions/TransactionManagementTests.cs b/Neo4jClient.Tests/Transactions/TransactionManagementTests.cs similarity index 100% rename from Test/Transactions/TransactionManagementTests.cs rename to Neo4jClient.Tests/Transactions/TransactionManagementTests.cs 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 98% rename from Test/packages.config rename to Neo4jClient.Tests/packages.config index a28833d52..bd2e89046 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 From 6f48409aa5b610faad16bbf1a80f469763bc508b Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Wed, 15 Jul 2015 13:14:50 +0100 Subject: [PATCH 20/27] Fixing tests for MyGet --- .../Cypher/CypherFluentQueryReturnTests.cs | 6 +--- .../Cypher/CypherFluentQueryStartTests.cs | 4 +-- .../Cypher/CypherFluentQueryTests.cs | 30 ++++--------------- Neo4jClient.Tests/Cypher/QueryWriterTests.cs | 5 ++-- 4 files changed, 11 insertions(+), 34 deletions(-) diff --git a/Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs index 38e852d40..003cbf13b 100644 --- a/Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs @@ -127,11 +127,7 @@ public void ShouldCombineWithLimitAndOrderBy() .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(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"]); diff --git a/Neo4jClient.Tests/Cypher/CypherFluentQueryStartTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryStartTests.cs index 708832397..8bb0ffc07 100644 --- a/Neo4jClient.Tests/Cypher/CypherFluentQueryStartTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryStartTests.cs @@ -192,9 +192,7 @@ public void NodeByAutoIndexLookup() }) .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(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"]); } diff --git a/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs index 9ba4e9d0a..3f489830f 100644 --- a/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs @@ -384,10 +384,7 @@ public void ReturnPropertiesIntoAnonymousType() }) .Query; - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b) -RETURN b.Age AS SomeAge, b.Name AS SomeName"; + 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"]); @@ -408,10 +405,7 @@ public void ReturnPropertiesIntoAnonymousTypeWithAutoNames() }) .Query; - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b) -RETURN b.Age AS Age, b.Name AS Name"; + 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"]); @@ -432,10 +426,7 @@ public void ReturnPropertiesFromMultipleNodesIntoAnonymousTypeWithAutoNames() }) .Query; - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b)-->(c) -RETURN b.Age AS Age, c.Name AS Name"; + 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"]); @@ -455,10 +446,7 @@ public void ReturnNodeDataIntoAnonymousType() }) .Query; - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b) -RETURN b AS NodeB"; + 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"]); @@ -478,10 +466,7 @@ public void ReturnEntireNodeDataAndReferenceIntoAnonymousType() }) .Query; - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b) -RETURN b AS NodeB"; + 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"]); @@ -501,10 +486,7 @@ public void ReturnEntireNodeDataAndReferenceIntoProjectionType() }) .Query; - const string expected = @" -START a=node({p0}) -MATCH (a)-->(b) -RETURN b AS NodeB"; + 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"]); diff --git a/Neo4jClient.Tests/Cypher/QueryWriterTests.cs b/Neo4jClient.Tests/Cypher/QueryWriterTests.cs index 0188376ea..e60ff120b 100644 --- a/Neo4jClient.Tests/Cypher/QueryWriterTests.cs +++ b/Neo4jClient.Tests/Cypher/QueryWriterTests.cs @@ -3,6 +3,8 @@ namespace Neo4jClient.Test.Cypher { + using System; + [TestFixture] public class QueryWriterTests { @@ -116,8 +118,7 @@ public void AppendMultipleClausesWithMultipleParameters() writer.AppendClause("{0} qoo {1} zoo", "abc", "xyz"); var query = writer.ToCypherQuery(); - const string expectedText = @"foo {p0} bar {p1} -{p2} qoo {p3} zoo"; + 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"]); From 74186ded75edaa874cde59afed4f6c3043719551 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Wed, 15 Jul 2015 13:18:32 +0100 Subject: [PATCH 21/27] Last test to fix. --- Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs index 3f489830f..6a0237a81 100644 --- a/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs @@ -1058,15 +1058,7 @@ public void SupportsFlexibleOrderOfClauses() // 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(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"]); } From ed7cf66c490e9fc1f880b9560425a94112c921d8 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Tue, 21 Jul 2015 09:02:15 +0100 Subject: [PATCH 22/27] Neo4jTransaction using default HttpClientWrapper Instead of the one passed in via the GraphClient constructor (also the JsonConverters) --- Neo4jClient/GraphClient.cs | 10 +++++++++- Neo4jClient/Transactions/Neo4jTransaction.cs | 12 ++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index f31bc655a..1612d112e 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -39,15 +39,23 @@ public class GraphClient : IRawGraphClient, IInternalTransactionalGraphClient, I public static readonly DefaultContractResolver DefaultJsonContractResolver = new DefaultContractResolver(); + internal static ExecutionConfiguration StaticExecutionConfiguration { get; private set; } private ITransactionManager transactionManager; private IExecutionPolicyFactory policyFactory; - public ExecutionConfiguration ExecutionConfiguration { get; private set; } + + public ExecutionConfiguration ExecutionConfiguration + { + get { return StaticExecutionConfiguration; } + private set { StaticExecutionConfiguration = value; } + } + internal readonly Uri RootUri; internal RootApiResponse RootApiResponse; private RootNode rootNode; private CypherCapabilities cypherCapabilities = CypherCapabilities.Default; + public bool UseJsonStreamingIfAvailable { get; set; } diff --git a/Neo4jClient/Transactions/Neo4jTransaction.cs b/Neo4jClient/Transactions/Neo4jTransaction.cs index a4dddc224..79c067f08 100644 --- a/Neo4jClient/Transactions/Neo4jTransaction.cs +++ b/Neo4jClient/Transactions/Neo4jTransaction.cs @@ -201,8 +201,8 @@ internal static void DoCommit(ITransactionExecutionEnvironment transactionExecut commitUri, new ExecutionConfiguration { - HttpClient = new HttpClientWrapper(), - JsonConverters = GraphClient.DefaultJsonConverters, + HttpClient = GraphClient.StaticExecutionConfiguration.HttpClient, + JsonConverters = GraphClient.StaticExecutionConfiguration.JsonConverters, UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, UserAgent = transactionExecutionEnvironment.UserAgent }, @@ -224,8 +224,8 @@ internal static void DoRollback(ITransactionExecutionEnvironment transactionExec rollbackUri, new ExecutionConfiguration { - HttpClient = new HttpClientWrapper(), - JsonConverters = GraphClient.DefaultJsonConverters, + HttpClient = GraphClient.StaticExecutionConfiguration.HttpClient, + JsonConverters = GraphClient.StaticExecutionConfiguration.JsonConverters, UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, UserAgent = transactionExecutionEnvironment.UserAgent }); @@ -249,8 +249,8 @@ internal static void DoKeepAlive(ITransactionExecutionEnvironment transactionExe keepAliveUri, new ExecutionConfiguration { - HttpClient = new HttpClientWrapper(), - JsonConverters = GraphClient.DefaultJsonConverters, + HttpClient = GraphClient.StaticExecutionConfiguration.HttpClient, + JsonConverters = GraphClient.StaticExecutionConfiguration.JsonConverters, UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, UserAgent = transactionExecutionEnvironment.UserAgent }, From 5eb7a91f7433a76079c67e4b958733ae4a8f2373 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Tue, 21 Jul 2015 16:16:24 +0100 Subject: [PATCH 23/27] ExecutionConfiguration AppDomain issues Ensuring the ExecutionConfiguration is set across AppDomain --- Neo4jClient.Tests/Neo4jClient.Tests.csproj | 3 +- .../Neo4jTransactionResourceManagerTests.cs | 101 ++++++++++++++++++ Neo4jClient.sln.DotSettings | 3 + Neo4jClient/GraphClient.cs | 15 +-- Neo4jClient/Properties/AssemblyInfo.cs | 2 +- Neo4jClient/Transactions/Neo4jTransaction.cs | 12 +-- .../Neo4jTransactionResourceManager.cs | 4 + 7 files changed, 125 insertions(+), 15 deletions(-) create mode 100644 Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs diff --git a/Neo4jClient.Tests/Neo4jClient.Tests.csproj b/Neo4jClient.Tests/Neo4jClient.Tests.csproj index b658ea758..5e2834ff6 100644 --- a/Neo4jClient.Tests/Neo4jClient.Tests.csproj +++ b/Neo4jClient.Tests/Neo4jClient.Tests.csproj @@ -9,7 +9,7 @@ Library Properties Neo4jClient.Test - Neo4jClient.Test + Neo4jClient.Tests v4.0 512 ..\ @@ -166,6 +166,7 @@ + diff --git a/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs b/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs new file mode 100644 index 000000000..c1b6f0865 --- /dev/null +++ b/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs @@ -0,0 +1,101 @@ +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 + { + #region Helper Classes + private class TestJsonConverter : JsonConverter + { + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + + public override bool CanConvert(Type objectType) + { + throw new NotImplementedException(); + } + } + + private class TestHttpClient : IHttpClient + { + public string PropertyOne { get; set; } + + public Task SendAsync(HttpRequestMessage request) + { + throw new NotImplementedException(); + } + } + #endregion Helper Classes + + private static ExecutionConfiguration GetExecutionConfigurationFromNewAppDomain() + { + var newDomain = AppDomain.CreateDomain("NewDomain", null, new AppDomainSetup {ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase}); + var assemblyInDomain = newDomain.Load(typeof (Neo4jTransactionResourceManager).Assembly.FullName); + var ntrm = assemblyInDomain.GetType(typeof (Neo4jTransactionResourceManager).FullName); + var property = ntrm.GetProperty("ExecutionConfiguration", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.NonPublic); + var ec = property.GetValue(null, null) as ExecutionConfiguration; + return ec; + } + + [Test] + public void KeepsHttpClient_AcrossAppDomain() + { + const string propOne = "Property One"; + new GraphClient(new Uri("http://foo/bar"), new TestHttpClient {PropertyOne = propOne}); + + var ec = GetExecutionConfigurationFromNewAppDomain(); + var tc = ec.HttpClient as TestHttpClient; + Assert.IsNotNull(tc); + Assert.AreEqual(propOne, tc.PropertyOne); + } + + [Test] + public void KeepsJsonDeserializers_AcrossAppDomain() + { + var client = new GraphClient(new Uri("http://foo/bar")); + client.JsonConverters.Add(new TestJsonConverter()); + + var ec = GetExecutionConfigurationFromNewAppDomain(); + var converters = ec.JsonConverters.Select(j => j.GetType().FullName).ToList(); + Assert.Contains(typeof (TestJsonConverter).FullName, converters); + } + + [Test] + public void KeepsUserAgent_AcrossAppDomain() + { + const string userAgent = "UserAgent_AppDomain"; + new GraphClient(new Uri("http://foo/bar")) {ExecutionConfiguration = {UserAgent = userAgent}}; + + var ec = GetExecutionConfigurationFromNewAppDomain(); + Assert.IsNotNull(ec); + Assert.AreEqual(userAgent, ec.UserAgent); + } + + [Test] + [TestCase(false)] + [TestCase(true)] + public void KeepsUseJsonStreaming_AcrossAppDomain(bool useStreaming) + { + new GraphClient(new Uri("http://foo/bar")) { ExecutionConfiguration = { UseJsonStreaming = useStreaming } }; + var ec = GetExecutionConfigurationFromNewAppDomain(); + + Assert.AreEqual(useStreaming, ec.UseJsonStreaming); + } + } +} \ No newline at end of file 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/GraphClient.cs b/Neo4jClient/GraphClient.cs index 1612d112e..e515423df 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -8,8 +8,6 @@ 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; @@ -39,15 +37,18 @@ public class GraphClient : IRawGraphClient, IInternalTransactionalGraphClient, I public static readonly DefaultContractResolver DefaultJsonContractResolver = new DefaultContractResolver(); - internal static ExecutionConfiguration StaticExecutionConfiguration { get; private set; } - private ITransactionManager transactionManager; - private IExecutionPolicyFactory policyFactory; + private readonly IExecutionPolicyFactory policyFactory; + private ExecutionConfiguration executionConfiguration; public ExecutionConfiguration ExecutionConfiguration { - get { return StaticExecutionConfiguration; } - private set { StaticExecutionConfiguration = value; } + get { return executionConfiguration; } + private set + { + executionConfiguration = value; + Neo4jTransactionResourceManager.ExecutionConfiguration = value; + } } internal readonly Uri RootUri; 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/Transactions/Neo4jTransaction.cs b/Neo4jClient/Transactions/Neo4jTransaction.cs index 79c067f08..3cccce572 100644 --- a/Neo4jClient/Transactions/Neo4jTransaction.cs +++ b/Neo4jClient/Transactions/Neo4jTransaction.cs @@ -201,8 +201,8 @@ internal static void DoCommit(ITransactionExecutionEnvironment transactionExecut commitUri, new ExecutionConfiguration { - HttpClient = GraphClient.StaticExecutionConfiguration.HttpClient, - JsonConverters = GraphClient.StaticExecutionConfiguration.JsonConverters, + HttpClient = Neo4jTransactionResourceManager.ExecutionConfiguration.HttpClient, + JsonConverters = Neo4jTransactionResourceManager.ExecutionConfiguration.JsonConverters, UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, UserAgent = transactionExecutionEnvironment.UserAgent }, @@ -224,8 +224,8 @@ internal static void DoRollback(ITransactionExecutionEnvironment transactionExec rollbackUri, new ExecutionConfiguration { - HttpClient = GraphClient.StaticExecutionConfiguration.HttpClient, - JsonConverters = GraphClient.StaticExecutionConfiguration.JsonConverters, + HttpClient = Neo4jTransactionResourceManager.ExecutionConfiguration.HttpClient, + JsonConverters = Neo4jTransactionResourceManager.ExecutionConfiguration.JsonConverters, UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, UserAgent = transactionExecutionEnvironment.UserAgent }); @@ -249,8 +249,8 @@ internal static void DoKeepAlive(ITransactionExecutionEnvironment transactionExe keepAliveUri, new ExecutionConfiguration { - HttpClient = GraphClient.StaticExecutionConfiguration.HttpClient, - JsonConverters = GraphClient.StaticExecutionConfiguration.JsonConverters, + HttpClient = Neo4jTransactionResourceManager.ExecutionConfiguration.HttpClient, + JsonConverters = Neo4jTransactionResourceManager.ExecutionConfiguration.JsonConverters, UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, UserAgent = transactionExecutionEnvironment.UserAgent }, diff --git a/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs b/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs index b26c4bb3c..d55a1ae1a 100644 --- a/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs +++ b/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs @@ -6,6 +6,8 @@ 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 @@ -78,6 +80,8 @@ public void Enlist(Transaction tx) internal class Neo4jTransactionResourceManager : MarshalByRefObject, ITransactionResourceManager { + internal static ExecutionConfiguration ExecutionConfiguration { get; set; } + private readonly IDictionary _transactions = new Dictionary(); public void Enlist(ITransactionExecutionEnvironment transactionExecutionEnvironment, byte[] transactionToken) From 57febda4d2fca4a3a68156a6ad3534fdafa9c99d Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Thu, 23 Jul 2015 11:01:46 +0100 Subject: [PATCH 24/27] Adding authentication to GraphClient Can now create with user/pass in constructor, this is passed to the transactions side of things so they now authenticate properly. Also had to update Json.NET due to a weird compilation issue. Hmmm --- .../GraphClientTests/ConnectTests.cs | 16 +++- Neo4jClient.Tests/Neo4jClient.Tests.csproj | 6 +- .../Neo4jTransactionResourceManagerTests.cs | 83 ------------------- Neo4jClient.Tests/packages.config | 8 +- .../Execution/ExecutionConfiguration.cs | 2 + Neo4jClient/Execution/IHttpClient.cs | 2 + Neo4jClient/GraphClient.cs | 37 ++++----- Neo4jClient/HttpClient.cs | 27 +++++- Neo4jClient/Neo4jClient.csproj | 6 +- Neo4jClient/Transactions/Neo4jTransaction.cs | 15 ++-- .../Neo4jTransactionResourceManager.cs | 2 - .../TransactionExecutionEnvironment.cs | 9 ++ Neo4jClient/packages.config | 4 +- 13 files changed, 84 insertions(+), 133 deletions(-) diff --git a/Neo4jClient.Tests/GraphClientTests/ConnectTests.cs b/Neo4jClient.Tests/GraphClientTests/ConnectTests.cs index 5cf5199f2..88a7a278b 100644 --- a/Neo4jClient.Tests/GraphClientTests/ConnectTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/ConnectTests.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; using Neo4jClient.Cypher; @@ -159,7 +160,7 @@ public void CredentialsPreservedAllTheWayThroughToHttpStack() } // ReSharper restore EmptyGeneralCatchClause - var httpCall = httpClient.ReceivedCalls().First(); + var httpCall = httpClient.ReceivedCalls().Last(); var httpRequest = (HttpRequestMessage) httpCall.GetArguments()[0]; StringAssert.AreEqualIgnoringCase("Basic", httpRequest.Headers.Authorization.Scheme); @@ -231,5 +232,18 @@ public void ShouldFormatUserAgentCorrectly() 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/Neo4jClient.Tests/Neo4jClient.Tests.csproj b/Neo4jClient.Tests/Neo4jClient.Tests.csproj index 5e2834ff6..43f6c36b9 100644 --- a/Neo4jClient.Tests/Neo4jClient.Tests.csproj +++ b/Neo4jClient.Tests/Neo4jClient.Tests.csproj @@ -34,9 +34,9 @@ 4 - - False - ..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.7.0.1\lib\net40\Newtonsoft.Json.dll + True ..\packages\NSubstitute.1.8.2.0\lib\net40\NSubstitute.dll diff --git a/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs b/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs index c1b6f0865..b23d876e5 100644 --- a/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs +++ b/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs @@ -13,89 +13,6 @@ namespace Neo4jClient.Test.Transactions [TestFixture] public class Neo4jTransactionResourceManagerTests { - #region Helper Classes - private class TestJsonConverter : JsonConverter - { - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) - { - throw new NotImplementedException(); - } - - public override bool CanConvert(Type objectType) - { - throw new NotImplementedException(); - } - } - - private class TestHttpClient : IHttpClient - { - public string PropertyOne { get; set; } - - public Task SendAsync(HttpRequestMessage request) - { - throw new NotImplementedException(); - } - } - #endregion Helper Classes - - private static ExecutionConfiguration GetExecutionConfigurationFromNewAppDomain() - { - var newDomain = AppDomain.CreateDomain("NewDomain", null, new AppDomainSetup {ApplicationBase = AppDomain.CurrentDomain.SetupInformation.ApplicationBase}); - var assemblyInDomain = newDomain.Load(typeof (Neo4jTransactionResourceManager).Assembly.FullName); - var ntrm = assemblyInDomain.GetType(typeof (Neo4jTransactionResourceManager).FullName); - var property = ntrm.GetProperty("ExecutionConfiguration", BindingFlags.Static | BindingFlags.GetProperty | BindingFlags.NonPublic); - var ec = property.GetValue(null, null) as ExecutionConfiguration; - return ec; - } - - [Test] - public void KeepsHttpClient_AcrossAppDomain() - { - const string propOne = "Property One"; - new GraphClient(new Uri("http://foo/bar"), new TestHttpClient {PropertyOne = propOne}); - - var ec = GetExecutionConfigurationFromNewAppDomain(); - var tc = ec.HttpClient as TestHttpClient; - Assert.IsNotNull(tc); - Assert.AreEqual(propOne, tc.PropertyOne); - } - - [Test] - public void KeepsJsonDeserializers_AcrossAppDomain() - { - var client = new GraphClient(new Uri("http://foo/bar")); - client.JsonConverters.Add(new TestJsonConverter()); - - var ec = GetExecutionConfigurationFromNewAppDomain(); - var converters = ec.JsonConverters.Select(j => j.GetType().FullName).ToList(); - Assert.Contains(typeof (TestJsonConverter).FullName, converters); - } - - [Test] - public void KeepsUserAgent_AcrossAppDomain() - { - const string userAgent = "UserAgent_AppDomain"; - new GraphClient(new Uri("http://foo/bar")) {ExecutionConfiguration = {UserAgent = userAgent}}; - - var ec = GetExecutionConfigurationFromNewAppDomain(); - Assert.IsNotNull(ec); - Assert.AreEqual(userAgent, ec.UserAgent); - } - - [Test] - [TestCase(false)] - [TestCase(true)] - public void KeepsUseJsonStreaming_AcrossAppDomain(bool useStreaming) - { - new GraphClient(new Uri("http://foo/bar")) { ExecutionConfiguration = { UseJsonStreaming = useStreaming } }; - var ec = GetExecutionConfigurationFromNewAppDomain(); - - Assert.AreEqual(useStreaming, ec.UseJsonStreaming); - } } } \ No newline at end of file diff --git a/Neo4jClient.Tests/packages.config b/Neo4jClient.Tests/packages.config index bd2e89046..98961b073 100644 --- a/Neo4jClient.Tests/packages.config +++ b/Neo4jClient.Tests/packages.config @@ -1,7 +1,7 @@  - - - - + + + + \ No newline at end of file diff --git a/Neo4jClient/Execution/ExecutionConfiguration.cs b/Neo4jClient/Execution/ExecutionConfiguration.cs index 92c4919d7..88f0a49cd 100644 --- a/Neo4jClient/Execution/ExecutionConfiguration.cs +++ b/Neo4jClient/Execution/ExecutionConfiguration.cs @@ -11,5 +11,7 @@ public class ExecutionConfiguration 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/IHttpClient.cs b/Neo4jClient/Execution/IHttpClient.cs index 5afca78a1..585361576 100644 --- a/Neo4jClient/Execution/IHttpClient.cs +++ b/Neo4jClient/Execution/IHttpClient.cs @@ -6,5 +6,7 @@ namespace Neo4jClient.Execution public interface IHttpClient { Task SendAsync(HttpRequestMessage request); + string Username { get; } + string Password { get; } } } diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index e515423df..eeec8a21f 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -40,16 +40,7 @@ public class GraphClient : IRawGraphClient, IInternalTransactionalGraphClient, I private ITransactionManager transactionManager; private readonly IExecutionPolicyFactory policyFactory; - private ExecutionConfiguration executionConfiguration; - public ExecutionConfiguration ExecutionConfiguration - { - get { return executionConfiguration; } - private set - { - executionConfiguration = value; - Neo4jTransactionResourceManager.ExecutionConfiguration = value; - } - } + public ExecutionConfiguration ExecutionConfiguration { get; private set; } internal readonly Uri RootUri; internal RootApiResponse RootApiResponse; @@ -60,15 +51,15 @@ private set public bool UseJsonStreamingIfAvailable { get; set; } - public GraphClient(Uri rootUri) - : this(rootUri, new HttpClientWrapper()) + 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; @@ -79,13 +70,15 @@ public GraphClient(Uri rootUri, IHttpClient httpClient) RootUri = rootUri; JsonConverters = new List(); JsonConverters.AddRange(DefaultJsonConverters); - JsonContractResolver = DefaultJsonContractResolver; + JsonContractResolver = DefaultJsonContractResolver; ExecutionConfiguration = new ExecutionConfiguration { HttpClient = httpClient, UserAgent = string.Format("Neo4jClient/{0}", GetType().Assembly.GetName().Version), UseJsonStreaming = true, - JsonConverters = JsonConverters + JsonConverters = JsonConverters, + Username = httpClient == null ? null : httpClient.Username, + Password = httpClient == null ? null : httpClient.Password }; UseJsonStreamingIfAvailable = true; policyFactory = new ExecutionPolicyFactory(this); @@ -293,7 +286,7 @@ public virtual NodeReference Create( var batchResponse = ExecuteBatch(batchSteps, policy); var createResponse = batchResponse[createNodeStep]; - EnsureNodeWasCreated(createResponse); + EnsureNodeWasCreated(createResponse); var nodeId = long.Parse(GetLastPathSegment(createResponse.Location)); var nodeReference = new NodeReference(nodeId, this); @@ -369,7 +362,7 @@ private RelationshipReference CreateRelationship(NodeReference sourceNode, NodeR targetNode.Id)) ) .Execute() - //.ReadAsJson>(JsonConverters,JsonContractResolver) + //.ReadAsJson>(JsonConverters,JsonContractResolver) .ToRelationshipReference(this); } @@ -1466,8 +1459,8 @@ private void EnsureNodeWasCreated(BatchStepResult createResponse) throw new ApplicationException(string.Format("Response from Neo4J: {0}", createResponse.Body)); throw new NeoException(exceptionResponse); - } - } + } + } protected virtual void Dispose(bool disposing) { @@ -1483,9 +1476,9 @@ public void Dispose() GC.SuppressFinalize(this); } - public DefaultContractResolver JsonContractResolver { get; set; } + public DefaultContractResolver JsonContractResolver { get; set; } - public ITransactionManager TransactionManager + public ITransactionManager TransactionManager { get { return transactionManager; } } diff --git a/Neo4jClient/HttpClient.cs b/Neo4jClient/HttpClient.cs index cd95ea632..4d77ae3aa 100644 --- a/Neo4jClient/HttpClient.cs +++ b/Neo4jClient/HttpClient.cs @@ -1,4 +1,7 @@ -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; @@ -6,9 +9,19 @@ 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) { @@ -17,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/Neo4jClient.csproj b/Neo4jClient/Neo4jClient.csproj index dcbf0f6c2..e5f780384 100644 --- a/Neo4jClient/Neo4jClient.csproj +++ b/Neo4jClient/Neo4jClient.csproj @@ -37,9 +37,9 @@ 1591 - - False - ..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.7.0.1\lib\net40\Newtonsoft.Json.dll + True diff --git a/Neo4jClient/Transactions/Neo4jTransaction.cs b/Neo4jClient/Transactions/Neo4jTransaction.cs index 3cccce572..1be4f4159 100644 --- a/Neo4jClient/Transactions/Neo4jTransaction.cs +++ b/Neo4jClient/Transactions/Neo4jTransaction.cs @@ -191,7 +191,6 @@ private static Uri DoKeepAlive( /// /// Commits a transaction given the ID /// - /// The transaction ID /// The transaction execution environment internal static void DoCommit(ITransactionExecutionEnvironment transactionExecutionEnvironment) { @@ -201,8 +200,8 @@ internal static void DoCommit(ITransactionExecutionEnvironment transactionExecut commitUri, new ExecutionConfiguration { - HttpClient = Neo4jTransactionResourceManager.ExecutionConfiguration.HttpClient, - JsonConverters = Neo4jTransactionResourceManager.ExecutionConfiguration.JsonConverters, + HttpClient = new HttpClientWrapper(transactionExecutionEnvironment.Username, transactionExecutionEnvironment.Password), + JsonConverters = GraphClient.DefaultJsonConverters, UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, UserAgent = transactionExecutionEnvironment.UserAgent }, @@ -212,7 +211,6 @@ internal static void DoCommit(ITransactionExecutionEnvironment transactionExecut /// /// Rolls back a transaction given the ID /// - /// The transaction ID /// The transaction execution environment internal static void DoRollback(ITransactionExecutionEnvironment transactionExecutionEnvironment) { @@ -224,8 +222,8 @@ internal static void DoRollback(ITransactionExecutionEnvironment transactionExec rollbackUri, new ExecutionConfiguration { - HttpClient = Neo4jTransactionResourceManager.ExecutionConfiguration.HttpClient, - JsonConverters = Neo4jTransactionResourceManager.ExecutionConfiguration.JsonConverters, + HttpClient = new HttpClientWrapper(transactionExecutionEnvironment.Username, transactionExecutionEnvironment.Password), + JsonConverters = GraphClient.DefaultJsonConverters, UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, UserAgent = transactionExecutionEnvironment.UserAgent }); @@ -239,7 +237,6 @@ internal static void DoRollback(ITransactionExecutionEnvironment transactionExec /// /// Keeps alive a transaction given the ID /// - /// The transaction ID /// The transaction execution environment internal static void DoKeepAlive(ITransactionExecutionEnvironment transactionExecutionEnvironment) { @@ -249,8 +246,8 @@ internal static void DoKeepAlive(ITransactionExecutionEnvironment transactionExe keepAliveUri, new ExecutionConfiguration { - HttpClient = Neo4jTransactionResourceManager.ExecutionConfiguration.HttpClient, - JsonConverters = Neo4jTransactionResourceManager.ExecutionConfiguration.JsonConverters, + HttpClient = new HttpClientWrapper(transactionExecutionEnvironment.Username, transactionExecutionEnvironment.Password), + JsonConverters = GraphClient.DefaultJsonConverters, UseJsonStreaming = transactionExecutionEnvironment.UseJsonStreaming, UserAgent = transactionExecutionEnvironment.UserAgent }, diff --git a/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs b/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs index d55a1ae1a..767cb61e1 100644 --- a/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs +++ b/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs @@ -80,8 +80,6 @@ public void Enlist(Transaction tx) internal class Neo4jTransactionResourceManager : MarshalByRefObject, ITransactionResourceManager { - internal static ExecutionConfiguration ExecutionConfiguration { get; set; } - private readonly IDictionary _transactions = new Dictionary(); public void Enlist(ITransactionExecutionEnvironment transactionExecutionEnvironment, byte[] transactionToken) diff --git a/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs b/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs index 8a8270be6..0c6405202 100644 --- a/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs +++ b/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs @@ -16,11 +16,17 @@ internal class TransactionExecutionEnvironment : MarshalByRefObject, ITransactio 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; } } @@ -31,5 +37,8 @@ internal interface ITransactionExecutionEnvironment 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/packages.config b/Neo4jClient/packages.config index e80203e6c..1df5f4567 100644 --- a/Neo4jClient/packages.config +++ b/Neo4jClient/packages.config @@ -1,5 +1,5 @@  - - + + \ No newline at end of file From 8ae829a873b80298226ce80e990c4e917e7d6aea Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Tue, 28 Jul 2015 10:18:41 +0100 Subject: [PATCH 25/27] Updating .gitignore to ignore VS2015 folder. --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 5f4e127f1..92b062ad7 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ Neo4jClient.*.nupkg packages *.ncrunchsolution *.Cache +*.vs\ \ No newline at end of file From a795de592a141b4d9f3c5b85212be1493f6591ae Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Tue, 28 Jul 2015 10:54:31 +0100 Subject: [PATCH 26/27] Updating back to 6.0.3 for Json.NET --- Neo4jClient.Tests/packages.config | 2 +- Neo4jClient/packages.config | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Neo4jClient.Tests/packages.config b/Neo4jClient.Tests/packages.config index 98961b073..d4f959b85 100644 --- a/Neo4jClient.Tests/packages.config +++ b/Neo4jClient.Tests/packages.config @@ -1,7 +1,7 @@  - + \ No newline at end of file diff --git a/Neo4jClient/packages.config b/Neo4jClient/packages.config index 1df5f4567..4f0cf9581 100644 --- a/Neo4jClient/packages.config +++ b/Neo4jClient/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file From 2fb2271de4a8a8db47eef5ec165292b2aa9bbbec Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Tue, 28 Jul 2015 13:45:53 +0100 Subject: [PATCH 27/27] Update Json.NET --- Neo4jClient.Tests/Neo4jClient.Tests.csproj | 4 ++-- Neo4jClient.Tests/packages.config | 2 +- Neo4jClient/Neo4jClient.csproj | 4 ++-- Neo4jClient/packages.config | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Neo4jClient.Tests/Neo4jClient.Tests.csproj b/Neo4jClient.Tests/Neo4jClient.Tests.csproj index 43f6c36b9..e4c213452 100644 --- a/Neo4jClient.Tests/Neo4jClient.Tests.csproj +++ b/Neo4jClient.Tests/Neo4jClient.Tests.csproj @@ -34,8 +34,8 @@ 4 - - ..\packages\Newtonsoft.Json.7.0.1\lib\net40\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll True diff --git a/Neo4jClient.Tests/packages.config b/Neo4jClient.Tests/packages.config index d4f959b85..a7aca9e6e 100644 --- a/Neo4jClient.Tests/packages.config +++ b/Neo4jClient.Tests/packages.config @@ -1,7 +1,7 @@  - + \ No newline at end of file diff --git a/Neo4jClient/Neo4jClient.csproj b/Neo4jClient/Neo4jClient.csproj index e5f780384..12457bc46 100644 --- a/Neo4jClient/Neo4jClient.csproj +++ b/Neo4jClient/Neo4jClient.csproj @@ -37,8 +37,8 @@ 1591 - - ..\packages\Newtonsoft.Json.7.0.1\lib\net40\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.6.0.3\lib\net40\Newtonsoft.Json.dll True diff --git a/Neo4jClient/packages.config b/Neo4jClient/packages.config index 4f0cf9581..1ecbd7f79 100644 --- a/Neo4jClient/packages.config +++ b/Neo4jClient/packages.config @@ -1,5 +1,5 @@  - + \ No newline at end of file