From 4ebac9e83cb4d20dcfec294a42b099cbfeb37a06 Mon Sep 17 00:00:00 2001 From: Chris Skardon Date: Fri, 18 Sep 2020 13:43:26 +0100 Subject: [PATCH] 4.0 into Main (#376) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * change bolt client to use async endpoints of driver * accidental change * restructure solution for dotnet standard only * remove all sync endpoints, move everything to async (except transactions), and delete gremlin * add transactions * transactions tests * cleanup a couple of things * Multi-targeting 4.5.2, 4.6 as well as netstandard 2.0 now Added build constants for net45, and added the VB tests back in Adding Version into the csproj for CI * Updating the Readme * It will build - but not tested. DO NOT USE. * Preventing NCrunch solution files being committed * Parameters all use $ syntax now * Fixing some tests - more to go - mocking issues atm * Removed a lot of REST/Gremlin code from the GraphClient * `GraphClient` and `BoltGraphClient` currently connect against `4.0` Need to test GC with 3.5 - as no longer using `cypher` endpoint for anything, only tx * Majority of tests fixed, 3 to go * Tests fixed * update neo4j driver and add support for new signed and self-signed sc… (#356) * update neo4j driver and add support for new signed and self-signed schemes * update readme * Moved cursor.ToListAsync inside transaction, as required for Neo4J 4.x. Also sorted out some nulls in bookmarks. (#362) * Updating so the `Http` and `Bolt` versions work (#363) * Transactions and WithDatabase (#367) * Handling Encryption * Transactional 'WithDatabase' * Fix #386 on 4.x (#372) * Adding `USE` to the CypherFluentQuery * 40 development (#374) * Tests for Transactions Throws exceptions when cypher gives back errors This is now for the 'ExecuteCypherNoResults' side of things QueryStats for GraphClient - ExecuteNoResults 3.5 / 4.x * WithQueryStats for GraphClient * WithQueryStats for BoltGraphClient * Manual implementation of 'Neo4jIgnoreAttribute' To take into account changes since the PR was originally written by https://github.com/Clooney24 * 40 development (#375) * Remove `Start` * Create(string, params object[]) * Return(string, CypherResultMode) * StartBit (various) * Mapper * Hiding unused member * Database Administration Commands * Set capabilities properly * Updating Readme Co-authored-by: Toby Miller --- .gitignore | 334 ++++ .../Neo4jClient.FSharp.Tests.fsproj | 15 +- Neo4jClient.Full.Shared/BoltGraphClient.cs | 69 - .../Execution/CypherExecutionPolicy.cs | 69 - .../GraphClientBasedExecutionPolicy.cs | 20 - Neo4jClient.Full.Shared/GraphClient.cs | 113 -- .../Neo4jClient.Full.Shared.projitems | 36 - .../Neo4jClient.Full.Shared.shproj | 13 - .../Bolt/BoltTransactionContext.cs | 139 -- ...ctionPromotableSinglePhasesNotification.cs | 188 -- .../Transactions/BoltNeo4jTransaction.cs | 110 -- .../ClosedTransactionException.cs | 22 - .../Transactions/TransactionContext.cs | 155 -- .../TransactionSinglePhaseNotification.cs | 180 -- Neo4jClient.Full/Neo4jClient.Full.csproj | 104 -- .../Neo4jClient.Full.csproj.DotSettings | 2 - .../Neo4jClient.Full.ncrunchproject | 17 - .../Neo4jClient.Full.v2.ncrunchproject | Bin 2802 -> 0 bytes .../Neo4jClient.Full.v3.ncrunchproject | 9 - .../Neo4jClient.csproj.DotSettings | 2 - Neo4jClient.Full/Properties/AssemblyInfo.cs | 38 - Neo4jClient.Full/app.config | 23 - Neo4jClient.Full/packages.config | 54 - .../Neo4jClient.Full452.csproj | 66 - .../Properties/AssemblyInfo.cs | 36 - Neo4jClient.Full452/app.config | 23 - Neo4jClient.Full452/packages.config | 54 - .../Cypher/CypherTransactionStatement.cs | 42 - .../ApiModels/ExtensionsApiResponse.cs | 9 - .../ApiModels/Gremlin/GremlinApiQuery.cs | 29 - .../Gremlin/GremlinPluginApiResponse.cs | 10 - .../Gremlin/GremlinTableCapResponse.cs | 51 - .../ApiModels/RootApiResponse.cs | 71 - Neo4jClient.Shared/BoltGraphClient.cs | 749 -------- Neo4jClient.Shared/Cypher/CypherStartBit.cs | 58 - .../CypherStartBitWithNodeIndexLookup.cs | 31 - ...tWithNodeIndexLookupWithSingleParameter.cs | 28 - Neo4jClient.Shared/Cypher/ICypherStartBit.cs | 10 - .../Cypher/RawCypherStartBit.cs | 32 - Neo4jClient.Shared/DriverWrapper.cs | 133 -- .../Execution/BatchExecutionPolicy.cs | 28 - .../Execution/CypherExecutionPolicy.cs | 80 - .../GraphClientBasedExecutionPolicy.cs | 30 - .../Execution/GremlinExecutionPolicy.cs | 28 - .../Execution/NodeIndexExecutionPolicy.cs | 17 - .../RelationshipIndexExecutionPolicy.cs | 17 - Neo4jClient.Shared/GraphClient.cs | 1531 ----------------- Neo4jClient.Shared/GraphClientExtensions.cs | 13 - Neo4jClient.Shared/Gremlin/AggregateStep.cs | 30 - Neo4jClient.Shared/Gremlin/AsStep.cs | 30 - Neo4jClient.Shared/Gremlin/Back.cs | 24 - Neo4jClient.Shared/Gremlin/BasicSteps.cs | 353 ---- Neo4jClient.Shared/Gremlin/CopySplitStep.cs | 29 - .../Gremlin/EmitPropertyStep.cs | 24 - Neo4jClient.Shared/Gremlin/ExceptStep.cs | 24 - .../Gremlin/ExhaustMergeStep.cs | 24 - Neo4jClient.Shared/Gremlin/FairMergeStep.cs | 24 - Neo4jClient.Shared/Gremlin/Filter.cs | 11 - .../Gremlin/FilterFormatters.cs | 253 --- Neo4jClient.Shared/Gremlin/FormattedFilter.cs | 10 - Neo4jClient.Shared/Gremlin/GremlinClient.cs | 30 - .../Gremlin/GremlinDistinctStep.cs | 24 - Neo4jClient.Shared/Gremlin/GremlinIterator.cs | 27 - .../Gremlin/GremlinNodeEnumerable.cs | 61 - .../Gremlin/GremlinPagedEnumerator.cs | 77 - .../Gremlin/GremlinProjectionEnumerable.cs | 60 - Neo4jClient.Shared/Gremlin/GremlinQuery.cs | 40 - .../Gremlin/GremlinQueryExtensions.cs | 133 -- .../Gremlin/GremlinRelationshipEnumerable.cs | 60 - .../GremlinRelationshipEnumerable`TData.cs | 76 - Neo4jClient.Shared/Gremlin/HasNextStep.cs | 24 - Neo4jClient.Shared/Gremlin/IGremlinClient.cs | 11 - .../Gremlin/IGremlinNodeQuery.cs | 8 - Neo4jClient.Shared/Gremlin/IGremlinQuery.cs | 11 - .../Gremlin/IGremlinRelationshipQuery.cs | 8 - .../IGremlinRelationshipQuery`TData.cs | 10 - Neo4jClient.Shared/Gremlin/IdentityPipe.cs | 27 - Neo4jClient.Shared/Gremlin/IfThenElseStep.cs | 34 - Neo4jClient.Shared/Gremlin/IteratorSteps.cs | 43 - Neo4jClient.Shared/Gremlin/LoopStep.cs | 24 - .../Gremlin/PrintLineStatement.cs | 11 - Neo4jClient.Shared/Gremlin/RetainStep.cs | 24 - Neo4jClient.Shared/Gremlin/Statement.cs | 28 - Neo4jClient.Shared/Gremlin/StoreStep.cs | 31 - Neo4jClient.Shared/Gremlin/TableStep.cs | 212 --- Neo4jClient.Shared/Gremlin/TypeFilter.cs | 12 - Neo4jClient.Shared/HttpContentExtensions.cs | 30 - Neo4jClient.Shared/IGraphClient.cs | 153 -- Neo4jClient.Shared/IGraphClientFactory.cs | 10 - Neo4jClient.Shared/Mappers/MapperHelper.cs | 67 - .../Neo4jClient.Shared.projitems | 225 --- Neo4jClient.Shared/Neo4jClient.Shared.shproj | 13 - Neo4jClient.Shared/NeoServerConfiguration.cs | 192 --- Neo4jClient.Shared/Node`TNode.cs | 97 -- Neo4jClient.Shared/Properties/AssemblyInfo.cs | 38 - Neo4jClient.Shared/RootNode.cs | 12 - .../Transactions/BoltResponse.cs | 9 - .../Transactions/ITransactionManager.cs | 23 - Neo4jClient.Shared/packages.config | 4 - .../CypherFluentQueryCustomHeaderTests.cs | 69 - .../Neo4jClient.Tests.Core.csproj | 30 - .../Neo4jClient.Tests.Core.v3.ncrunchproject | 6 - .../ApiModels/GremlinTableCapResponseTests.cs | 352 ---- Neo4jClient.Tests.Shared/ApiUsageIdeas.cs | 62 - .../BoltGraphClientTests.cs | 354 ---- .../Cypher/ExecuteGetCypherResultsTests.cs | 1452 ---------------- Neo4jClient.Tests.Shared/BoltTestHarness.cs | 39 - .../Cypher/CypherFluentQueryStartTests.cs | 279 --- .../Cypher/DocumentationExamples.cs | 301 ---- .../GraphClientTests/CreateIndexTests.cs | 138 -- .../GraphClientTests/CreateNodeTests.cs | 621 ------- .../CreateRelationshipTests.cs | 132 -- .../GraphClientTests/DeleteIndexTests.cs | 27 - .../DeleteRelationshipTests.cs | 51 - .../GraphClientTests/GetNodeTests.cs | 232 --- .../Gremlin/ExecuteGetAllNodesGremlinTests.cs | 149 -- .../ExecuteGetAllRelationshipsGremlinTests.cs | 287 --- .../Gremlin/ExecuteScalarGremlinTests.cs | 61 - .../GraphClientTests/IndexExistsTests.cs | 34 - .../GraphClientTests/LookupIndexTests.cs | 55 - .../GraphClientTests/QueryNodeIndexTests.cs | 56 - .../GraphClientTests/ReIndexNodesTests.cs | 194 --- .../ReIndexRelationshipsTests.cs | 212 --- .../GraphClientTests/RootNodeTests.cs | 33 - .../GraphClientTests/ServerVersionTests.cs | 23 - .../GraphClientTests/UpdateNodeTests.cs | 299 ---- .../UpdateRelationshipTests.cs | 77 - .../Gremlin/AggregateStepTests.cs | 63 - .../Gremlin/AsStepTests.cs | 40 - Neo4jClient.Tests.Shared/Gremlin/BackTests.cs | 58 - .../Gremlin/BasicStepsTests.cs | 382 ---- .../Gremlin/CopySplitStepTests.cs | 126 -- .../Gremlin/EmitPropertyStepTests.cs | 37 - .../Gremlin/ExceptStepTests.cs | 55 - .../Gremlin/ExhaustMergeStepTests.cs | 37 - .../Gremlin/FairMergeStepTests.cs | 37 - .../Gremlin/FormatGremlinFilterTests.cs | 698 -------- .../Gremlin/GremlinClientTests.cs | 60 - .../Gremlin/GremlinDistinctStepTests.cs | 37 - .../Gremlin/GremlinHasNextStepTests.cs | 37 - .../Gremlin/GremlinNodeEnumerableTests.cs | 74 - .../Gremlin/GremlinPagedEnumeratorTests.cs | 212 --- .../Gremlin/IfThenElseTests.cs | 87 - .../Gremlin/IteratorTests.cs | 76 - .../Gremlin/LoopStepTests.cs | 61 - .../Gremlin/PrintLineStatementTests.cs | 21 - .../Gremlin/RetainStepTests.cs | 55 - .../Gremlin/StoreStepTests.cs | 55 - .../Gremlin/TableTests.cs | 105 -- .../Gremlin/TranslateFilterTests.cs | 344 ---- .../Neo4jClient.Tests.Shared.projitems | 142 -- .../Neo4jClient.Tests.Shared.shproj | 13 - Neo4jClient.Tests.Shared/NodeTests.cs | 85 - .../Relationships/OwnedBy.cs | 19 - .../AddressResolverTests.cs | 6 +- .../ApiModels/RootApiResponseTests.cs | 5 +- Neo4jClient.Tests/ApiUsageIdeas.cs | 63 + .../BoltGraphClientTests.cs | 328 ++++ .../Bookmarks/BookmarkTests.cs | 92 +- .../BoltGraphClientTests/ConnectAsyncTests.cs | 74 +- .../Cypher/ExecuteGetCypherResultsTests.cs | 722 ++++++++ Neo4jClient.Tests/BoltTestHarness.cs | 87 + Neo4jClient.Tests/Bookmarks/BookmarkTests.cs | 48 - .../CultureInfoSetupFixture.cs | 2 +- .../Cypher/AggregateTests.cs | 5 +- .../Cypher/CypherFluentQueryAdvancedTests.cs | 17 +- .../Cypher/CypherFluentQueryCallTests.cs | 3 +- .../Cypher/CypherFluentQueryConstraintTest.cs | 10 +- .../CypherFluentQueryCreateUniqueTests.cs | 14 +- .../CypherFluentQueryCustomHeaderTests.cs | 8 +- ...rFluentQueryDatabaseAdministrationTests.cs | 394 +++++ .../Cypher/CypherFluentQueryDatabaseTests.cs | 79 + .../Cypher/CypherFluentQueryDeleteTests.cs | 39 +- .../CypherFluentQueryDetachDeleteTests.cs | 18 +- .../Cypher/CypherFluentQueryDropTests.cs | 36 +- .../Cypher/CypherFluentQueryForEachTests.cs | 7 +- .../Cypher/CypherFluentQueryLimitTests.cs | 42 +- .../Cypher/CypherFluentQueryLoadCsvTests.cs | 6 +- .../Cypher/CypherFluentQueryMatchTests.cs | 22 +- .../CypherFluentQueryMaxExecutionTimeTests.cs | 7 +- .../Cypher/CypherFluentQueryMergeTests.cs | 15 +- .../CypherFluentQueryParserVersionTests.cs | 33 +- .../Cypher/CypherFluentQueryPlannerTests.cs | 3 +- .../CypherFluentQueryQueryStatsTests.cs | 22 + .../Cypher/CypherFluentQueryReadWriteTests.cs | 6 +- .../Cypher/CypherFluentQueryRemoveTests.cs | 19 +- .../Cypher/CypherFluentQueryResultsTests.cs | 71 +- .../Cypher/CypherFluentQueryReturnTests.cs | 285 +-- .../Cypher/CypherFluentQuerySetClientTests.cs | 3 +- .../Cypher/CypherFluentQuerySetTests.cs | 19 +- .../Cypher/CypherFluentQuerySkipTests.cs | 40 +- .../Cypher/CypherFluentQueryTests.cs | 245 +-- .../Cypher/CypherFluentQueryUnwindTests.cs | 8 +- .../Cypher/CypherFluentQueryUseTests.cs | 25 + .../CypherFluentQueryUsingIndexTests.cs | 9 +- .../Cypher/CypherFluentQueryWhereTests.cs | 13 +- .../CypherFluentQueryWithBookmarkTests.cs | 19 +- .../CypherFluentQueryWithIdentifierTests.cs | 17 +- .../Cypher/CypherFluentQueryWithParamTests.cs | 72 +- .../Cypher/CypherFluentQueryWithTests.cs | 12 +- .../Cypher/CypherFluentQueryYieldTests.cs | 3 +- .../Cypher/CypherQueryTests.cs | 36 +- .../CypherReturnExpressionBuilderTests.cs | 5 +- .../CypherWhereExpressionBuilderTests.cs | 11 +- .../Cypher/DocumentationExamples.cs | 181 ++ .../Cypher/QueryWriterTests.cs | 7 +- .../Cypher/StartBitFormatterTests.cs | 49 +- .../Cypher/UnionTests.cs | 5 +- .../Domain/Product.cs | 2 +- .../Domain/StorageLocation.cs | 2 +- .../Domain/User.cs | 2 +- .../Extensions/MemberInfoExtensionsTests.cs | 5 +- .../Extensions/Neo4jDriverExtensionsTests.cs | 3 +- .../Extensions/ObjectExtensionTests.cs | 3 +- .../GraphClientTests/ConnectAsyncTests.cs | 47 +- .../GraphClientTests/ConnectTests.cs | 109 +- .../GraphClientTests/CreateIndexTests.cs | 138 ++ .../GraphClientTests/CreateNodeTests.cs | 620 +++++++ .../CreateRelationshipTests.cs | 132 ++ .../Cypher/ExecuteCypherTests.cs | 205 ++- .../Cypher/ExecuteGetCypherResultsTests.cs | 154 +- .../GraphClientTests/DefaultDatabaseTests.cs | 32 + .../GraphClientTests/DeleteIndexTests.cs | 27 + .../GraphClientTests/DeleteNodeTests.cs | 30 +- .../DeleteRelationshipTests.cs | 51 + .../GraphClientTests/FactoryTests.cs | 32 +- .../GraphClientTests/GetIndexesTests.cs | 20 +- .../GraphClientTests/GetNodeTests.cs | 231 +++ .../GraphClientTests/IndexExistsTests.cs | 34 + .../GraphClientTests/LookupIndexTests.cs | 55 + .../GraphClientTests/ReIndexNodesTests.cs | 194 +++ .../ReIndexRelationshipsTests.cs | 212 +++ .../GraphClientTests/RootNodeTests.cs | 20 + .../GraphClientTests/UpdateNodeTests.cs | 299 ++++ .../UpdateRelationshipTests.cs | 77 + .../HttpClientWrapperTests.cs | 3 +- .../IndexEntryTests.cs | 3 +- .../MockRequest.cs | 9 +- .../MockResponse.cs | 59 +- .../MockResponseThrows.cs | 2 +- .../MockResponseThrowsException.cs | 2 +- Neo4jClient.Tests/Neo4jClient.Tests.csproj | 267 +-- .../Neo4jClient.Tests.csproj.DotSettings | 2 - .../Neo4jClient.Tests.ncrunchproject | 17 - .../Neo4jClient.Tests.v2.ncrunchproject | 26 - .../Neo4jClient.Tests.v3.ncrunchproject | 6 - .../NodeReferenceTests.cs | 56 +- Neo4jClient.Tests/NodeTests.cs | 72 + Neo4jClient.Tests/Properties/AssemblyInfo.cs | 36 - Neo4jClient.Tests/QueryStatsTests.cs | 439 +++++ .../RelationshipReferenceTests.cs | 13 +- .../RelationshipTests.cs | 3 +- Neo4jClient.Tests/Relationships/OwnedBy.cs | 19 + .../Relationships/Requires.cs | 4 +- .../Relationships/StoredIn.cs | 4 +- .../RestTestHarness.cs | 54 +- .../CustomJsonDeserializerTests.cs | 40 +- .../CustomJsonSerializerTests.cs | 33 +- .../CypherJsonDeserializerTests.cs | 82 +- .../SerializesDateTimeAsNeoDateTimeTests.cs | 23 +- .../UserSuppliedSerializationTests.cs | 61 +- .../StatementResultHelperTests.cs | 41 +- .../TestUtilities.cs | 2 +- .../TestsImplementFixture.cs | 3 +- .../Transactions/BoltClientTestHelper.cs | 72 + .../BoltQueriesInTransactionTests.cs | 311 +--- .../Neo4jTransactionResourceManagerTests.cs | 12 +- .../Transactions/QueriesInTransactionTests.cs | 760 +------- .../Transactions/RestCallScenarioTests.cs | 373 ++-- .../TransactionExecutionEnvironmentTests.cs | 3 +- .../TransactionManagementTests.cs | 171 +- .../TransactionRestResponseHelper.cs | 2 +- .../Transactions/TransactionTests.cs | 754 ++++++++ .../UtilitiesTests.cs | 4 +- Neo4jClient.Tests/app.config | 27 - Neo4jClient.Tests/packages.config | 70 - .../My Project/Application.Designer.vb | 13 - .../My Project/Application.myapp | 10 - .../My Project/AssemblyInfo.vb | 35 - .../My Project/Resources.Designer.vb | 63 - .../My Project/Resources.resx | 117 -- .../My Project/Settings.Designer.vb | 73 - .../My Project/Settings.settings | 7 - .../Neo4jClient.Vb.Tests.v2.ncrunchproject | 26 - .../Neo4jClient.Vb.Tests.v3.ncrunchproject | 5 - .../Neo4jClient.Vb.Tests.vbproj | 305 +--- Neo4jClient.Vb.Tests/app.config | 31 - Neo4jClient.Vb.Tests/packages.config | 63 - Neo4jClient.nuspec | 35 +- Neo4jClient.sln | 140 +- .../AddressResolver.cs | 2 +- .../AggregateExceptionExtensions.cs | 0 ...AmbiguousRelationshipDirectionException.cs | 0 .../ApiModels/BatchResponse.cs | 0 .../ApiModels/BatchStep.cs | 0 .../ApiModels/BatchStepExtensions.cs | 0 .../ApiModels/BatchStepResult.cs | 0 .../ApiModels/Cypher/CypherApiQuery.cs | 0 .../ApiModels/Cypher/CypherStatementList.cs | 7 +- .../Cypher/CypherTransactionStatement.cs | 36 + .../ApiModels/Cypher/PathsResult.cs | 0 .../ApiModels/Cypher/PathsResultBolt.cs | 2 +- Neo4jClient/ApiModels/Cypher/QueryStats.cs | 41 + .../ApiModels/ExceptionResponse.cs | 0 .../ApiModels/FieldChange.cs | 0 .../ApiModels/NodeApiResponse.cs | 0 .../NodeOrRelationshipApiResponse.cs | 0 .../ApiModels/RelationshipApiResponse.cs | 0 .../ApiModels/RelationshipTemplate.cs | 0 Neo4jClient/ApiModels/RootApiResponse.cs | 118 ++ .../Attributes/Neo4jDateTimeAttribute.cs | 0 .../Attributes/Neo4jIgnoreAttribute.cs | 9 + Neo4jClient/BoltGraphClient.cs | 511 +++++- .../Cypher/All.cs | 6 - .../Cypher/CypherCapabilities.cs | 20 +- .../Cypher/CypherFluentQuery.cs | 193 +-- .../Cypher/CypherFluentQueryAdvanced.cs | 14 +- ...ypherFluentQuery`DatabaseAdministration.cs | 65 + .../Cypher/CypherFluentQuery`Return.cs | 16 +- .../Cypher/CypherFluentQuery`TResult.cs | 6 +- .../Cypher/CypherFluentQuery`Where.cs | 0 .../Cypher/CypherFluentQuery`With.cs | 0 .../Cypher/CypherFluentQuery`WithBookmark.cs | 8 +- .../Cypher/CypherPlanner.cs | 0 .../Cypher/CypherQuery.cs | 57 +- .../Cypher/CypherResultMode.cs | 0 .../Cypher/CypherReturnExpressionBuilder.cs | 15 +- .../Cypher/CypherWhereExpressionBuilder.cs | 0 .../Cypher/CypherWhereExpressionVisitor.cs | 0 .../Cypher/CypherWithExpressionBuilder.cs | 0 .../Cypher/ICypherFluentQuery.cs | 61 +- .../Cypher/ICypherFluentQueryAdvanced.cs | 0 ...ypherFluentQuery`DatabaseAdministration.cs | 82 + .../Cypher/ICypherFluentQuery`TResult.cs | 1 - .../Cypher/ICypherFluentQuery`Where.cs | 0 .../Cypher/ICypherFluentQuery`With.cs | 0 .../Cypher/ICypherResultItem.cs | 0 .../Cypher/IFluentCypherResultItem.cs | 0 .../Cypher/IOrderedCypherFluentQuery.cs | 0 .../IOrderedCypherFluentQuery`TResult.cs | 0 .../Cypher/Node.cs | 0 .../Cypher/OrderByType.cs | 0 .../Cypher/QueryWriter.cs | 28 +- .../Cypher/Relationship.cs | 0 .../Cypher/Return.cs | 0 .../Cypher/ReturnExpression.cs | 0 .../Cypher/StartBit.cs | 0 .../Cypher/StartBitFormatter.cs | 0 .../Cypher/VbComparer.cs | 0 .../CypherPartialResult.cs | 0 .../DeleteMode.cs | 0 .../DetachedNodeException.cs | 0 .../Diagrams/Batching.cd | 0 .../Diagrams/GraphClientCore.cd | 0 .../Diagrams/Responses.cd | 0 Neo4jClient/DriverWrapper.cs | 127 ++ Neo4jClient/Execution/BatchExecutionPolicy.cs | 28 + .../Execution/CypherExecutionPolicy.cs | 70 +- .../CypherTransactionExecutionPolicy.cs | 5 +- .../Execution/EndpointBuilderExtension.cs | 0 .../Execution/ErrorGenerator.cs | 0 .../Execution/ExecutionConfiguration.cs | 2 + .../Execution/ExecutionPolicyFactory.cs | 18 +- .../GraphClientBasedExecutionPolicy.cs | 41 +- .../HttpResponseMessageExtensions.cs | 13 +- .../Execution/IExecutionPolicy.cs | 3 +- .../Execution/IExecutionPolicyFactory.cs | 1 - .../Execution/IHttpClient.cs | 0 .../Execution/IRequestTypeBuilder.cs | 0 .../IRequestWithPendingContentBuilder.cs | 0 .../Execution/IResponseBuilder.cs | 11 +- .../Execution/IResponseBuilder`TResult.cs | 11 +- .../Execution/IResponseFailBuilder.cs | 0 .../Execution/NodeIndexExecutionPolicy.cs | 17 + .../RelationshipIndexExecutionPolicy.cs | 17 + .../Execution/Request.cs | 0 .../Execution/RequestTypeBuilder.cs | 0 .../RequestWithPendingContentBuilder.cs | 0 .../Execution/ResponseBuilder.cs | 78 +- .../Execution/ResponseBuilder`TParse.cs | 35 +- .../Execution/ResponseFailBuilder.cs | 0 .../Execution/ResponseFailBuilder`TParse.cs | 0 .../Execution/RestExecutionPolicy.cs | 14 +- Neo4jClient/Extensions/DriverExtensions.cs | 34 + .../Extensions/EnumerableExtensions.cs | 0 .../Extensions/MemberInfoExtensions.cs | 0 .../Extensions/ObjectExtensions.cs | 0 Neo4jClient/GraphClient.cs | 697 ++++++-- Neo4jClient/GraphClientExtensions.cs | 14 + .../GraphClientFactory.cs | 9 +- .../HttpClient.cs | 0 Neo4jClient/HttpContentExtensions.cs | 29 + .../IAttachedReference.cs | 0 .../IBoltGraphClient.cs | 0 .../ICypherGraphClient.cs | 0 Neo4jClient/IGraphClient.cs | 46 + Neo4jClient/IGraphClientFactory.cs | 11 + .../IHasNodeReference.cs | 0 .../IRawGraphClient.cs | 4 +- ...lationshipAllowingParticipantNode`TNode.cs | 0 .../IRelationshipAllowingSourceNode`TNode.cs | 0 .../IRelationshipAllowingTargetNode`TNode.cs | 0 .../ITypedNodeReference.cs | 0 .../IndexConfiguration.cs | 0 .../IndexEntry.cs | 0 .../IndexFor.cs | 0 .../IndexMetaData.cs | 0 .../IndexProvider.cs | 0 .../IndexType.cs | 0 .../Indexing.cd | 0 .../JTokenExtensions.cs | 0 Neo4jClient/NameValueCollection.cs | 23 - .../Neo4jClient.Pcl.csproj.DotSettings | 2 - Neo4jClient/Neo4jClient.Pcl.nuget.targets | 9 - .../Neo4jClient.Shared.v3.ncrunchproject | 0 Neo4jClient/Neo4jClient.csproj | 130 +- Neo4jClient/Neo4jClient.csproj.DotSettings | 2 - Neo4jClient/Neo4jClient.v2.ncrunchproject | 29 - Neo4jClient/Neo4jClient.v3.ncrunchproject | 10 - .../Neo4jDriverExtensions.cs | 29 +- .../NeoException.cs | 0 Neo4jClient/NeoServerConfiguration.cs | 88 + .../NodeReference.cs | 55 +- .../NodeReference`TNode.cs | 0 Neo4jClient/Node`TNode.cs | 57 + .../OperationCompletedEventHandler.cs | 15 +- .../OrphanedTransactionException.cs | 0 Neo4jClient/Properties/AssemblyInfo.cs | 31 - .../Relationship.cs | 0 .../RelationshipDirection.cs | 0 .../RelationshipEnd.cs | 0 .../RelationshipInstance.cs | 0 .../RelationshipInstance`TData.cs | 0 .../RelationshipReference.cs | 18 +- .../RelationshipReference`TData.cs | 0 .../Relationship`TData.cs | 0 .../CommonDeserializerMethods.cs | 36 +- .../Serialization/CustomJsonDeserializer.cs | 2 +- .../Serialization/CustomJsonSerializer.cs | 0 .../Serialization/CypherJsonDeserializer.cs | 131 +- .../Serialization/DeserializationContext.cs | 0 .../Serialization/DeserializationException.cs | 0 .../Serialization/EnumValueConverter.cs | 0 .../Serialization/ICypherJsonDeserializer.cs | 4 +- .../Serialization/ISerializer.cs | 0 .../Serialization/Neo4jContractSerializer.cs | 46 + .../NullableEnumValueConverter.cs | 0 .../PartialDeserializationContext.cs | 0 .../Serialization/TimeZoneInfoConverter.cs | 0 .../TypeConverterBasedJsonConverter.cs | 0 .../Serialization/TypeMapping.cs | 0 .../Serialization/ZonedDateTimeConverter.cs | 2 +- .../StatementResultHelper.cs | 8 +- .../Transactions/Bolt/BoltNeo4jTransaction.cs | 132 ++ .../Bolt/BoltNeo4jTransactionProxy.cs | 25 +- Neo4jClient/Transactions/Bolt/BoltResponse.cs | 9 + .../Bolt/BoltSuppressTransactionProxy.cs | 19 +- .../Bolt/BoltTransactionContext.cs | 30 + .../Bolt/BoltTransactionManager.cs | 100 +- .../Bolt/BoltTransactionScopeProxy.cs | 26 +- .../ClosedTransactionException.cs | 15 + .../IInternalTransactionalGraphClient.cs | 0 .../Transactions/INeo4jTransaction.cs | 0 .../Transactions/ITransaction.cs | 23 +- .../ITransactionExecutionEnvironment.cs | 2 +- .../Transactions/ITransactionManager.cs | 34 + .../ITransactionResourceManager.cs | 0 .../Transactions/ITransactionalGraphClient.cs | 14 +- .../Transactions/Neo4jRestTransaction.cs | 78 +- .../Transactions/Neo4jTransactionProxy.cs | 21 +- .../Neo4jTransactionResourceManager.cs | 1 + .../Transactions/SuppressTransactionProxy.cs | 12 +- .../Transactions/ThreadContextHelpers.cs | 5 +- .../TransactionConnectionContext.cs | 0 .../Transactions/TransactionContext.cs | 58 + .../Transactions/TransactionContextBase.cs | 100 ++ .../TransactionExecutionEnvironment.cs | 43 +- .../Transactions/TransactionHttpUtils.cs | 0 .../Transactions/TransactionManager.cs | 101 +- .../Transactions/TransactionScopeProxy.cs | 22 +- .../UriCreator.cs | 0 .../Utilities.cs | 0 Neo4jClient/project.json | 21 - README.md | 72 +- 484 files changed, 10407 insertions(+), 21226 deletions(-) delete mode 100644 Neo4jClient.Full.Shared/BoltGraphClient.cs delete mode 100644 Neo4jClient.Full.Shared/Execution/CypherExecutionPolicy.cs delete mode 100644 Neo4jClient.Full.Shared/Execution/GraphClientBasedExecutionPolicy.cs delete mode 100644 Neo4jClient.Full.Shared/GraphClient.cs delete mode 100644 Neo4jClient.Full.Shared/Neo4jClient.Full.Shared.projitems delete mode 100644 Neo4jClient.Full.Shared/Neo4jClient.Full.Shared.shproj delete mode 100644 Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionContext.cs delete mode 100644 Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionPromotableSinglePhasesNotification.cs delete mode 100644 Neo4jClient.Full.Shared/Transactions/BoltNeo4jTransaction.cs delete mode 100644 Neo4jClient.Full.Shared/Transactions/ClosedTransactionException.cs delete mode 100644 Neo4jClient.Full.Shared/Transactions/TransactionContext.cs delete mode 100644 Neo4jClient.Full.Shared/Transactions/TransactionSinglePhaseNotification.cs delete mode 100644 Neo4jClient.Full/Neo4jClient.Full.csproj delete mode 100644 Neo4jClient.Full/Neo4jClient.Full.csproj.DotSettings delete mode 100644 Neo4jClient.Full/Neo4jClient.Full.ncrunchproject delete mode 100644 Neo4jClient.Full/Neo4jClient.Full.v2.ncrunchproject delete mode 100644 Neo4jClient.Full/Neo4jClient.Full.v3.ncrunchproject delete mode 100644 Neo4jClient.Full/Neo4jClient.csproj.DotSettings delete mode 100644 Neo4jClient.Full/Properties/AssemblyInfo.cs delete mode 100644 Neo4jClient.Full/app.config delete mode 100644 Neo4jClient.Full/packages.config delete mode 100644 Neo4jClient.Full452/Neo4jClient.Full452.csproj delete mode 100644 Neo4jClient.Full452/Properties/AssemblyInfo.cs delete mode 100644 Neo4jClient.Full452/app.config delete mode 100644 Neo4jClient.Full452/packages.config delete mode 100644 Neo4jClient.Shared/ApiModels/Cypher/CypherTransactionStatement.cs delete mode 100644 Neo4jClient.Shared/ApiModels/ExtensionsApiResponse.cs delete mode 100644 Neo4jClient.Shared/ApiModels/Gremlin/GremlinApiQuery.cs delete mode 100644 Neo4jClient.Shared/ApiModels/Gremlin/GremlinPluginApiResponse.cs delete mode 100644 Neo4jClient.Shared/ApiModels/Gremlin/GremlinTableCapResponse.cs delete mode 100644 Neo4jClient.Shared/ApiModels/RootApiResponse.cs delete mode 100644 Neo4jClient.Shared/BoltGraphClient.cs delete mode 100644 Neo4jClient.Shared/Cypher/CypherStartBit.cs delete mode 100644 Neo4jClient.Shared/Cypher/CypherStartBitWithNodeIndexLookup.cs delete mode 100644 Neo4jClient.Shared/Cypher/CypherStartBitWithNodeIndexLookupWithSingleParameter.cs delete mode 100644 Neo4jClient.Shared/Cypher/ICypherStartBit.cs delete mode 100644 Neo4jClient.Shared/Cypher/RawCypherStartBit.cs delete mode 100644 Neo4jClient.Shared/DriverWrapper.cs delete mode 100644 Neo4jClient.Shared/Execution/BatchExecutionPolicy.cs delete mode 100644 Neo4jClient.Shared/Execution/CypherExecutionPolicy.cs delete mode 100644 Neo4jClient.Shared/Execution/GraphClientBasedExecutionPolicy.cs delete mode 100644 Neo4jClient.Shared/Execution/GremlinExecutionPolicy.cs delete mode 100644 Neo4jClient.Shared/Execution/NodeIndexExecutionPolicy.cs delete mode 100644 Neo4jClient.Shared/Execution/RelationshipIndexExecutionPolicy.cs delete mode 100644 Neo4jClient.Shared/GraphClient.cs delete mode 100644 Neo4jClient.Shared/GraphClientExtensions.cs delete mode 100644 Neo4jClient.Shared/Gremlin/AggregateStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/AsStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/Back.cs delete mode 100644 Neo4jClient.Shared/Gremlin/BasicSteps.cs delete mode 100644 Neo4jClient.Shared/Gremlin/CopySplitStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/EmitPropertyStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/ExceptStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/ExhaustMergeStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/FairMergeStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/Filter.cs delete mode 100644 Neo4jClient.Shared/Gremlin/FilterFormatters.cs delete mode 100644 Neo4jClient.Shared/Gremlin/FormattedFilter.cs delete mode 100644 Neo4jClient.Shared/Gremlin/GremlinClient.cs delete mode 100644 Neo4jClient.Shared/Gremlin/GremlinDistinctStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/GremlinIterator.cs delete mode 100644 Neo4jClient.Shared/Gremlin/GremlinNodeEnumerable.cs delete mode 100644 Neo4jClient.Shared/Gremlin/GremlinPagedEnumerator.cs delete mode 100644 Neo4jClient.Shared/Gremlin/GremlinProjectionEnumerable.cs delete mode 100644 Neo4jClient.Shared/Gremlin/GremlinQuery.cs delete mode 100644 Neo4jClient.Shared/Gremlin/GremlinQueryExtensions.cs delete mode 100644 Neo4jClient.Shared/Gremlin/GremlinRelationshipEnumerable.cs delete mode 100644 Neo4jClient.Shared/Gremlin/GremlinRelationshipEnumerable`TData.cs delete mode 100644 Neo4jClient.Shared/Gremlin/HasNextStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/IGremlinClient.cs delete mode 100644 Neo4jClient.Shared/Gremlin/IGremlinNodeQuery.cs delete mode 100644 Neo4jClient.Shared/Gremlin/IGremlinQuery.cs delete mode 100644 Neo4jClient.Shared/Gremlin/IGremlinRelationshipQuery.cs delete mode 100644 Neo4jClient.Shared/Gremlin/IGremlinRelationshipQuery`TData.cs delete mode 100644 Neo4jClient.Shared/Gremlin/IdentityPipe.cs delete mode 100644 Neo4jClient.Shared/Gremlin/IfThenElseStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/IteratorSteps.cs delete mode 100644 Neo4jClient.Shared/Gremlin/LoopStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/PrintLineStatement.cs delete mode 100644 Neo4jClient.Shared/Gremlin/RetainStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/Statement.cs delete mode 100644 Neo4jClient.Shared/Gremlin/StoreStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/TableStep.cs delete mode 100644 Neo4jClient.Shared/Gremlin/TypeFilter.cs delete mode 100644 Neo4jClient.Shared/HttpContentExtensions.cs delete mode 100644 Neo4jClient.Shared/IGraphClient.cs delete mode 100644 Neo4jClient.Shared/IGraphClientFactory.cs delete mode 100644 Neo4jClient.Shared/Mappers/MapperHelper.cs delete mode 100644 Neo4jClient.Shared/Neo4jClient.Shared.projitems delete mode 100644 Neo4jClient.Shared/Neo4jClient.Shared.shproj delete mode 100644 Neo4jClient.Shared/NeoServerConfiguration.cs delete mode 100644 Neo4jClient.Shared/Node`TNode.cs delete mode 100644 Neo4jClient.Shared/Properties/AssemblyInfo.cs delete mode 100644 Neo4jClient.Shared/RootNode.cs delete mode 100644 Neo4jClient.Shared/Transactions/BoltResponse.cs delete mode 100644 Neo4jClient.Shared/Transactions/ITransactionManager.cs delete mode 100644 Neo4jClient.Shared/packages.config delete mode 100644 Neo4jClient.Tests.Core/Cypher/CypherFluentQueryCustomHeaderTests.cs delete mode 100644 Neo4jClient.Tests.Core/Neo4jClient.Tests.Core.csproj delete mode 100644 Neo4jClient.Tests.Core/Neo4jClient.Tests.Core.v3.ncrunchproject delete mode 100644 Neo4jClient.Tests.Shared/ApiModels/GremlinTableCapResponseTests.cs delete mode 100644 Neo4jClient.Tests.Shared/ApiUsageIdeas.cs delete mode 100644 Neo4jClient.Tests.Shared/BoltGraphClientTests/BoltGraphClientTests.cs delete mode 100644 Neo4jClient.Tests.Shared/BoltGraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs delete mode 100644 Neo4jClient.Tests.Shared/BoltTestHarness.cs delete mode 100644 Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryStartTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Cypher/DocumentationExamples.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/CreateIndexTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/CreateNodeTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/CreateRelationshipTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/DeleteIndexTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/DeleteRelationshipTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/GetNodeTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/IndexExistsTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/LookupIndexTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/QueryNodeIndexTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/ReIndexNodesTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/ReIndexRelationshipsTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/RootNodeTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/ServerVersionTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/UpdateNodeTests.cs delete mode 100644 Neo4jClient.Tests.Shared/GraphClientTests/UpdateRelationshipTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/AggregateStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/AsStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/BackTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/BasicStepsTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/CopySplitStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/EmitPropertyStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/ExceptStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/ExhaustMergeStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/FairMergeStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/FormatGremlinFilterTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/GremlinClientTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/GremlinDistinctStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/GremlinHasNextStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/GremlinNodeEnumerableTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/GremlinPagedEnumeratorTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/IfThenElseTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/IteratorTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/LoopStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/PrintLineStatementTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/RetainStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/StoreStepTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/TableTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Gremlin/TranslateFilterTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Neo4jClient.Tests.Shared.projitems delete mode 100644 Neo4jClient.Tests.Shared/Neo4jClient.Tests.Shared.shproj delete mode 100644 Neo4jClient.Tests.Shared/NodeTests.cs delete mode 100644 Neo4jClient.Tests.Shared/Relationships/OwnedBy.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/AddressResolverTests.cs (93%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/ApiModels/RootApiResponseTests.cs (94%) create mode 100644 Neo4jClient.Tests/ApiUsageIdeas.cs create mode 100644 Neo4jClient.Tests/BoltGraphClientTests/BoltGraphClientTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/BoltGraphClientTests/Bookmarks/BookmarkTests.cs (54%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/BoltGraphClientTests/ConnectAsyncTests.cs (58%) create mode 100644 Neo4jClient.Tests/BoltGraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs create mode 100644 Neo4jClient.Tests/BoltTestHarness.cs delete mode 100644 Neo4jClient.Tests/Bookmarks/BookmarkTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/CultureInfoSetupFixture.cs (96%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/AggregateTests.cs (98%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryAdvancedTests.cs (71%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryCallTests.cs (95%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryConstraintTest.cs (91%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryCreateUniqueTests.cs (71%) create mode 100644 Neo4jClient.Tests/Cypher/CypherFluentQueryDatabaseAdministrationTests.cs create mode 100644 Neo4jClient.Tests/Cypher/CypherFluentQueryDatabaseTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryDeleteTests.cs (62%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryDetachDeleteTests.cs (81%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryDropTests.cs (65%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryForEachTests.cs (85%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryLimitTests.cs (71%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryLoadCsvTests.cs (93%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryMatchTests.cs (80%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryMaxExecutionTimeTests.cs (89%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryMergeTests.cs (72%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryParserVersionTests.cs (66%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryPlannerTests.cs (96%) create mode 100644 Neo4jClient.Tests/Cypher/CypherFluentQueryQueryStatsTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryReadWriteTests.cs (95%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryRemoveTests.cs (62%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryResultsTests.cs (69%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryReturnTests.cs (52%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQuerySetClientTests.cs (96%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQuerySetTests.cs (61%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQuerySkipTests.cs (72%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryTests.cs (77%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryUnwindTests.cs (87%) create mode 100644 Neo4jClient.Tests/Cypher/CypherFluentQueryUseTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryUsingIndexTests.cs (84%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryWhereTests.cs (98%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryWithBookmarkTests.cs (83%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryWithIdentifierTests.cs (80%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryWithParamTests.cs (67%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryWithTests.cs (96%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherFluentQueryYieldTests.cs (95%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherQueryTests.cs (75%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherReturnExpressionBuilderTests.cs (99%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/CypherWhereExpressionBuilderTests.cs (99%) create mode 100644 Neo4jClient.Tests/Cypher/DocumentationExamples.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/QueryWriterTests.cs (95%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/StartBitFormatterTests.cs (90%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Cypher/UnionTests.cs (94%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Domain/Product.cs (76%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Domain/StorageLocation.cs (70%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Domain/User.cs (67%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Extensions/MemberInfoExtensionsTests.cs (93%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Extensions/Neo4jDriverExtensionsTests.cs (99%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Extensions/ObjectExtensionTests.cs (94%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/GraphClientTests/ConnectAsyncTests.cs (78%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/GraphClientTests/ConnectTests.cs (72%) create mode 100644 Neo4jClient.Tests/GraphClientTests/CreateIndexTests.cs create mode 100644 Neo4jClient.Tests/GraphClientTests/CreateNodeTests.cs create mode 100644 Neo4jClient.Tests/GraphClientTests/CreateRelationshipTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/GraphClientTests/Cypher/ExecuteCypherTests.cs (57%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs (86%) create mode 100644 Neo4jClient.Tests/GraphClientTests/DefaultDatabaseTests.cs create mode 100644 Neo4jClient.Tests/GraphClientTests/DeleteIndexTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/GraphClientTests/DeleteNodeTests.cs (73%) create mode 100644 Neo4jClient.Tests/GraphClientTests/DeleteRelationshipTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/GraphClientTests/FactoryTests.cs (53%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/GraphClientTests/GetIndexesTests.cs (81%) create mode 100644 Neo4jClient.Tests/GraphClientTests/GetNodeTests.cs create mode 100644 Neo4jClient.Tests/GraphClientTests/IndexExistsTests.cs create mode 100644 Neo4jClient.Tests/GraphClientTests/LookupIndexTests.cs create mode 100644 Neo4jClient.Tests/GraphClientTests/ReIndexNodesTests.cs create mode 100644 Neo4jClient.Tests/GraphClientTests/ReIndexRelationshipsTests.cs create mode 100644 Neo4jClient.Tests/GraphClientTests/RootNodeTests.cs create mode 100644 Neo4jClient.Tests/GraphClientTests/UpdateNodeTests.cs create mode 100644 Neo4jClient.Tests/GraphClientTests/UpdateRelationshipTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/HttpClientWrapperTests.cs (97%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/IndexEntryTests.cs (98%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/MockRequest.cs (83%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/MockResponse.cs (69%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/MockResponseThrows.cs (92%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/MockResponseThrowsException.cs (88%) delete mode 100644 Neo4jClient.Tests/Neo4jClient.Tests.csproj.DotSettings delete mode 100644 Neo4jClient.Tests/Neo4jClient.Tests.ncrunchproject delete mode 100644 Neo4jClient.Tests/Neo4jClient.Tests.v2.ncrunchproject delete mode 100644 Neo4jClient.Tests/Neo4jClient.Tests.v3.ncrunchproject rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/NodeReferenceTests.cs (66%) create mode 100644 Neo4jClient.Tests/NodeTests.cs delete mode 100644 Neo4jClient.Tests/Properties/AssemblyInfo.cs create mode 100644 Neo4jClient.Tests/QueryStatsTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/RelationshipReferenceTests.cs (83%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/RelationshipTests.cs (98%) create mode 100644 Neo4jClient.Tests/Relationships/OwnedBy.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Relationships/Requires.cs (87%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Relationships/StoredIn.cs (84%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/RestTestHarness.cs (82%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Serialization/CustomJsonDeserializerTests.cs (94%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Serialization/CustomJsonSerializerTests.cs (81%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Serialization/CypherJsonDeserializerTests.cs (96%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Serialization/SerializesDateTimeAsNeoDateTimeTests.cs (59%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/Serialization/UserSuppliedSerializationTests.cs (77%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/StatementResultHelperTests.cs (98%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/TestUtilities.cs (96%) rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/TestsImplementFixture.cs (96%) create mode 100644 Neo4jClient.Tests/Transactions/BoltClientTestHelper.cs create mode 100644 Neo4jClient.Tests/Transactions/TransactionTests.cs rename {Neo4jClient.Tests.Shared => Neo4jClient.Tests}/UtilitiesTests.cs (95%) delete mode 100644 Neo4jClient.Tests/app.config delete mode 100644 Neo4jClient.Tests/packages.config delete mode 100644 Neo4jClient.Vb.Tests/My Project/Application.Designer.vb delete mode 100644 Neo4jClient.Vb.Tests/My Project/Application.myapp delete mode 100644 Neo4jClient.Vb.Tests/My Project/AssemblyInfo.vb delete mode 100644 Neo4jClient.Vb.Tests/My Project/Resources.Designer.vb delete mode 100644 Neo4jClient.Vb.Tests/My Project/Resources.resx delete mode 100644 Neo4jClient.Vb.Tests/My Project/Settings.Designer.vb delete mode 100644 Neo4jClient.Vb.Tests/My Project/Settings.settings delete mode 100644 Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.v2.ncrunchproject delete mode 100644 Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.v3.ncrunchproject delete mode 100644 Neo4jClient.Vb.Tests/app.config delete mode 100644 Neo4jClient.Vb.Tests/packages.config rename {Neo4jClient.Shared => Neo4jClient}/AddressResolver.cs (98%) rename {Neo4jClient.Shared => Neo4jClient}/AggregateExceptionExtensions.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/AmbiguousRelationshipDirectionException.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/BatchResponse.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/BatchStep.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/BatchStepExtensions.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/BatchStepResult.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/Cypher/CypherApiQuery.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/Cypher/CypherStatementList.cs (93%) create mode 100644 Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/Cypher/PathsResult.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/Cypher/PathsResultBolt.cs (99%) create mode 100644 Neo4jClient/ApiModels/Cypher/QueryStats.cs rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/ExceptionResponse.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/FieldChange.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/NodeApiResponse.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/NodeOrRelationshipApiResponse.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/RelationshipApiResponse.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ApiModels/RelationshipTemplate.cs (100%) create mode 100644 Neo4jClient/ApiModels/RootApiResponse.cs rename {Neo4jClient.Shared => Neo4jClient}/Attributes/Neo4jDateTimeAttribute.cs (100%) create mode 100644 Neo4jClient/Attributes/Neo4jIgnoreAttribute.cs rename {Neo4jClient.Shared => Neo4jClient}/Cypher/All.cs (81%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherCapabilities.cs (74%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherFluentQuery.cs (70%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherFluentQueryAdvanced.cs (81%) create mode 100644 Neo4jClient/Cypher/CypherFluentQuery`DatabaseAdministration.cs rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherFluentQuery`Return.cs (90%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherFluentQuery`TResult.cs (92%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherFluentQuery`Where.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherFluentQuery`With.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherFluentQuery`WithBookmark.cs (71%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherPlanner.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherQuery.cs (64%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherResultMode.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherReturnExpressionBuilder.cs (98%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherWhereExpressionBuilder.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherWhereExpressionVisitor.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/CypherWithExpressionBuilder.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/ICypherFluentQuery.cs (86%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/ICypherFluentQueryAdvanced.cs (100%) create mode 100644 Neo4jClient/Cypher/ICypherFluentQuery`DatabaseAdministration.cs rename {Neo4jClient.Shared => Neo4jClient}/Cypher/ICypherFluentQuery`TResult.cs (93%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/ICypherFluentQuery`Where.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/ICypherFluentQuery`With.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/ICypherResultItem.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/IFluentCypherResultItem.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/IOrderedCypherFluentQuery.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/IOrderedCypherFluentQuery`TResult.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/Node.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/OrderByType.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/QueryWriter.cs (86%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/Relationship.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/Return.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/ReturnExpression.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/StartBit.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/StartBitFormatter.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Cypher/VbComparer.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/CypherPartialResult.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/DeleteMode.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/DetachedNodeException.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Diagrams/Batching.cd (100%) rename {Neo4jClient.Shared => Neo4jClient}/Diagrams/GraphClientCore.cd (100%) rename {Neo4jClient.Shared => Neo4jClient}/Diagrams/Responses.cd (100%) create mode 100644 Neo4jClient/DriverWrapper.cs create mode 100644 Neo4jClient/Execution/BatchExecutionPolicy.cs rename {Neo4jClient.Shared => Neo4jClient}/Execution/CypherTransactionExecutionPolicy.cs (75%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/EndpointBuilderExtension.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/ErrorGenerator.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/ExecutionConfiguration.cs (90%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/ExecutionPolicyFactory.cs (66%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/HttpResponseMessageExtensions.cs (73%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/IExecutionPolicy.cs (85%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/IExecutionPolicyFactory.cs (97%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/IHttpClient.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/IRequestTypeBuilder.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/IRequestWithPendingContentBuilder.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/IResponseBuilder.cs (63%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/IResponseBuilder`TResult.cs (63%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/IResponseFailBuilder.cs (100%) create mode 100644 Neo4jClient/Execution/NodeIndexExecutionPolicy.cs create mode 100644 Neo4jClient/Execution/RelationshipIndexExecutionPolicy.cs rename {Neo4jClient.Shared => Neo4jClient}/Execution/Request.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/RequestTypeBuilder.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/RequestWithPendingContentBuilder.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/ResponseBuilder.cs (72%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/ResponseBuilder`TParse.cs (60%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/ResponseFailBuilder.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/ResponseFailBuilder`TParse.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Execution/RestExecutionPolicy.cs (83%) create mode 100644 Neo4jClient/Extensions/DriverExtensions.cs rename {Neo4jClient.Shared => Neo4jClient}/Extensions/EnumerableExtensions.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Extensions/MemberInfoExtensions.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Extensions/ObjectExtensions.cs (100%) create mode 100644 Neo4jClient/GraphClientExtensions.cs rename {Neo4jClient.Shared => Neo4jClient}/GraphClientFactory.cs (72%) rename {Neo4jClient.Shared => Neo4jClient}/HttpClient.cs (100%) create mode 100644 Neo4jClient/HttpContentExtensions.cs rename {Neo4jClient.Shared => Neo4jClient}/IAttachedReference.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/IBoltGraphClient.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ICypherGraphClient.cs (100%) create mode 100644 Neo4jClient/IGraphClient.cs create mode 100644 Neo4jClient/IGraphClientFactory.cs rename {Neo4jClient.Shared => Neo4jClient}/IHasNodeReference.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/IRawGraphClient.cs (70%) rename {Neo4jClient.Shared => Neo4jClient}/IRelationshipAllowingParticipantNode`TNode.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/IRelationshipAllowingSourceNode`TNode.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/IRelationshipAllowingTargetNode`TNode.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/ITypedNodeReference.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/IndexConfiguration.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/IndexEntry.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/IndexFor.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/IndexMetaData.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/IndexProvider.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/IndexType.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Indexing.cd (100%) rename {Neo4jClient.Shared => Neo4jClient}/JTokenExtensions.cs (100%) delete mode 100644 Neo4jClient/NameValueCollection.cs delete mode 100644 Neo4jClient/Neo4jClient.Pcl.csproj.DotSettings delete mode 100644 Neo4jClient/Neo4jClient.Pcl.nuget.targets rename {Neo4jClient.Shared => Neo4jClient}/Neo4jClient.Shared.v3.ncrunchproject (100%) delete mode 100644 Neo4jClient/Neo4jClient.csproj.DotSettings delete mode 100644 Neo4jClient/Neo4jClient.v2.ncrunchproject delete mode 100644 Neo4jClient/Neo4jClient.v3.ncrunchproject rename {Neo4jClient.Shared => Neo4jClient}/Neo4jDriverExtensions.cs (80%) rename {Neo4jClient.Shared => Neo4jClient}/NeoException.cs (100%) create mode 100644 Neo4jClient/NeoServerConfiguration.cs rename {Neo4jClient.Shared => Neo4jClient}/NodeReference.cs (50%) rename {Neo4jClient.Shared => Neo4jClient}/NodeReference`TNode.cs (100%) create mode 100644 Neo4jClient/Node`TNode.cs rename {Neo4jClient.Shared => Neo4jClient}/OperationCompletedEventHandler.cs (62%) rename {Neo4jClient.Shared => Neo4jClient}/OrphanedTransactionException.cs (100%) delete mode 100644 Neo4jClient/Properties/AssemblyInfo.cs rename {Neo4jClient.Shared => Neo4jClient}/Relationship.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/RelationshipDirection.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/RelationshipEnd.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/RelationshipInstance.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/RelationshipInstance`TData.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/RelationshipReference.cs (76%) rename {Neo4jClient.Shared => Neo4jClient}/RelationshipReference`TData.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Relationship`TData.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/CommonDeserializerMethods.cs (92%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/CustomJsonDeserializer.cs (98%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/CustomJsonSerializer.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/CypherJsonDeserializer.cs (81%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/DeserializationContext.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/DeserializationException.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/EnumValueConverter.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/ICypherJsonDeserializer.cs (69%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/ISerializer.cs (100%) create mode 100644 Neo4jClient/Serialization/Neo4jContractSerializer.cs rename {Neo4jClient.Shared => Neo4jClient}/Serialization/NullableEnumValueConverter.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/PartialDeserializationContext.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/TimeZoneInfoConverter.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/TypeConverterBasedJsonConverter.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/TypeMapping.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Serialization/ZonedDateTimeConverter.cs (98%) rename {Neo4jClient.Shared => Neo4jClient}/StatementResultHelper.cs (98%) create mode 100644 Neo4jClient/Transactions/Bolt/BoltNeo4jTransaction.cs rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/Bolt/BoltNeo4jTransactionProxy.cs (52%) create mode 100644 Neo4jClient/Transactions/Bolt/BoltResponse.cs rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/Bolt/BoltSuppressTransactionProxy.cs (60%) create mode 100644 Neo4jClient/Transactions/Bolt/BoltTransactionContext.cs rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/Bolt/BoltTransactionManager.cs (63%) rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/Bolt/BoltTransactionScopeProxy.cs (72%) create mode 100644 Neo4jClient/Transactions/ClosedTransactionException.cs rename {Neo4jClient.Shared => Neo4jClient}/Transactions/IInternalTransactionalGraphClient.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Transactions/INeo4jTransaction.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Transactions/ITransaction.cs (63%) rename {Neo4jClient.Shared => Neo4jClient}/Transactions/ITransactionExecutionEnvironment.cs (92%) create mode 100644 Neo4jClient/Transactions/ITransactionManager.cs rename {Neo4jClient.Shared => Neo4jClient}/Transactions/ITransactionResourceManager.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Transactions/ITransactionalGraphClient.cs (97%) rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/Neo4jRestTransaction.cs (81%) rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/Neo4jTransactionProxy.cs (63%) rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/Neo4jTransactionResourceManager.cs (99%) rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/SuppressTransactionProxy.cs (78%) rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/ThreadContextHelpers.cs (92%) rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/TransactionConnectionContext.cs (100%) create mode 100644 Neo4jClient/Transactions/TransactionContext.cs create mode 100644 Neo4jClient/Transactions/TransactionContextBase.cs rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/TransactionExecutionEnvironment.cs (86%) rename {Neo4jClient.Shared => Neo4jClient}/Transactions/TransactionHttpUtils.cs (100%) rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/TransactionManager.cs (65%) rename {Neo4jClient.Full.Shared => Neo4jClient}/Transactions/TransactionScopeProxy.cs (75%) rename {Neo4jClient.Shared => Neo4jClient}/UriCreator.cs (100%) rename {Neo4jClient.Shared => Neo4jClient}/Utilities.cs (100%) delete mode 100644 Neo4jClient/project.json diff --git a/.gitignore b/.gitignore index bdaec83e3..a21fad4b8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,336 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET Core +project.lock.json +project.fragment.lock.json +artifacts/ +**/Properties/launchSettings.json + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* +.ncrunch* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + + [Bb]in [Oo]bj *.user @@ -9,6 +342,7 @@ Neo4jClient.*.nupkg *.crunchsolution.cache packages *.ncrunchsolution +*.ncrunchproject *.Cache *.vs\ *.ghostdoc.xml diff --git a/Neo4jClient.FSharp.Tests/Neo4jClient.FSharp.Tests.fsproj b/Neo4jClient.FSharp.Tests/Neo4jClient.FSharp.Tests.fsproj index f0249ae43..fe3f99cb2 100644 --- a/Neo4jClient.FSharp.Tests/Neo4jClient.FSharp.Tests.fsproj +++ b/Neo4jClient.FSharp.Tests/Neo4jClient.FSharp.Tests.fsproj @@ -1,7 +1,7 @@  - netcoreapp2.0 + netcoreapp2.1 @@ -9,9 +9,14 @@ - - - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -19,7 +24,7 @@ - + diff --git a/Neo4jClient.Full.Shared/BoltGraphClient.cs b/Neo4jClient.Full.Shared/BoltGraphClient.cs deleted file mode 100644 index e2e50e179..000000000 --- a/Neo4jClient.Full.Shared/BoltGraphClient.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Neo4j.Driver.V1; -using Neo4jClient.Execution; -using Neo4jClient.Transactions; -using Newtonsoft.Json; - -//TODO: Logging -//TODO: Config Stuff -//TODO: Transaction Stuff - -namespace Neo4jClient -{ - public partial class BoltGraphClient : IBoltGraphClient, IRawGraphClient, ITransactionalGraphClient - { - /// - /// Creates a new instance of the . - /// - /// - /// If the parameter is provided, this will be treated as a virtual URI - /// , else it will be the URI connected to. - /// - /// - /// A collection of instances to connect to using an - /// . Leave null (or empty) if you don't want to use it. - /// - /// The username to connect to Neo4j with. - /// The password to connect to Neo4j with. - /// The realm to connect to Neo4j with. - public BoltGraphClient(Uri uri, IEnumerable uris, string username = null, string password = null, string realm = null) - { - var localUris = uris?.ToList(); - if (localUris != null && localUris.Any()) - { - if(uri.Scheme.ToLowerInvariant() != "bolt+routing") - throw new NotSupportedException($"To use the {nameof(BoltGraphClient)} you need to provide a 'bolt://' scheme, not '{uri.Scheme}'."); - - addressResolver = new AddressResolver(uri, localUris); - } - else if (uri.Scheme.ToLowerInvariant() != "bolt" && uri.Scheme.ToLowerInvariant() != "bolt+routing") - throw new NotSupportedException($"To use the {nameof(BoltGraphClient)} you need to provide a 'bolt://' or 'bolt+routing://' scheme, not '{uri.Scheme}'."); - - - this.uri = uri; - this.username = username; - this.password = password; - this.realm = realm; - PolicyFactory = new ExecutionPolicyFactory(this); - - JsonConverters = new List(); - JsonConverters.AddRange(DefaultJsonConverters); - JsonContractResolver = DefaultJsonContractResolver; - - ExecutionConfiguration = new ExecutionConfiguration - { - UserAgent = $"Neo4jClient/{GetType().GetTypeInfo().Assembly.GetName().Version}", - UseJsonStreaming = true, - JsonConverters = JsonConverters, - Username = username, - Password = password, - Realm = realm - }; - - transactionManager = new BoltTransactionManager(this); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/Execution/CypherExecutionPolicy.cs b/Neo4jClient.Full.Shared/Execution/CypherExecutionPolicy.cs deleted file mode 100644 index c672457f1..000000000 --- a/Neo4jClient.Full.Shared/Execution/CypherExecutionPolicy.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using Neo4jClient.ApiModels.Cypher; -using Neo4jClient.Cypher; -using Neo4jClient.Transactions; - -namespace Neo4jClient.Execution -{ - /// - /// Describes the behavior for a cypher execution. - /// - internal partial class CypherExecutionPolicy : GraphClientBasedExecutionPolicy - { - - private INeo4jTransaction GetTransactionInScope() - { - // first try to get the Non DTC transaction and if it doesn't succeed then try it with the DTC - var transactionalClient = Client as IInternalTransactionalGraphClient; - if (transactionalClient == null) - { - return null; - } - - var proxiedTransaction = transactionalClient.Transaction as TransactionScopeProxy; - if (proxiedTransaction != null) - { - return proxiedTransaction.TransactionContext;; - } - - var ambientTransaction = transactionalClient.TransactionManager.CurrentDtcTransaction; - if (ambientTransaction != null) - { - return (INeo4jTransaction) ambientTransaction; - } - - return null; - } - - public override Uri BaseEndpoint - { - get - { - if (!InTransaction) - { - return Client.CypherEndpoint; - } - - var proxiedTransaction = GetTransactionInScope(); - var transactionalClient = (ITransactionalGraphClient) Client; - if (proxiedTransaction == null) - { - return transactionalClient.TransactionEndpoint; - } - - var startingReference = proxiedTransaction.Endpoint ?? transactionalClient.TransactionEndpoint; - return startingReference; - } - } - - public override TransactionExecutionPolicy TransactionExecutionPolicy - { - get { return TransactionExecutionPolicy.Allowed; } - } - - - } -} diff --git a/Neo4jClient.Full.Shared/Execution/GraphClientBasedExecutionPolicy.cs b/Neo4jClient.Full.Shared/Execution/GraphClientBasedExecutionPolicy.cs deleted file mode 100644 index e36dc15c8..000000000 --- a/Neo4jClient.Full.Shared/Execution/GraphClientBasedExecutionPolicy.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Transactions; -using Neo4jClient.Transactions; - -namespace Neo4jClient.Execution -{ - internal abstract partial class GraphClientBasedExecutionPolicy : IExecutionPolicy - { - public bool InTransaction - { - get - { - var transactionalGraphClient = Client as ITransactionalGraphClient; - return transactionalGraphClient != null && - (transactionalGraphClient.InTransaction || Transaction.Current != null); - } - } - - public abstract TransactionExecutionPolicy TransactionExecutionPolicy { get; } - } -} \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/GraphClient.cs b/Neo4jClient.Full.Shared/GraphClient.cs deleted file mode 100644 index 6194c415a..000000000 --- a/Neo4jClient.Full.Shared/GraphClient.cs +++ /dev/null @@ -1,113 +0,0 @@ -using System; -using System.Diagnostics; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Neo4jClient.ApiModels; -using Neo4jClient.Cypher; -using Neo4jClient.Execution; -using Neo4jClient.Transactions; - -namespace Neo4jClient -{ - public partial class GraphClient : IRawGraphClient, IInternalTransactionalGraphClient, IDisposable - { - 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, string username = null, string password = null) - : this(rootUri, new HttpClientWrapper(username, password)) - { - ServicePointManager.Expect100Continue = expect100Continue; - ServicePointManager.UseNagleAlgorithm = useNagleAlgorithm; - } - - - public virtual async Task ConnectAsync(NeoServerConfiguration configuration = null) - { - if (IsConnected) - { - return; - } - - var stopwatch = Stopwatch.StartNew(); - var operationCompletedArgs = new OperationCompletedEventArgs - { - QueryText = "Connect", - ResourcesReturned = 0 - }; - - Action stopTimerAndNotifyCompleted = () => - { - stopwatch.Stop(); - operationCompletedArgs.TimeTaken = stopwatch.Elapsed; - OnOperationCompleted(operationCompletedArgs); - }; - - try - { - configuration = configuration ?? await NeoServerConfiguration.GetConfigurationAsync( - RootUri, - ExecutionConfiguration.Username, - ExecutionConfiguration.Password, - ExecutionConfiguration.Realm, - ExecutionConfiguration).ConfigureAwait(false); - - RootApiResponse = configuration.ApiConfig; - - if (!string.IsNullOrWhiteSpace(RootApiResponse.Transaction)) - { - transactionManager = new TransactionManager(this); - } - - rootNode = string.IsNullOrEmpty(RootApiResponse.ReferenceNode) - ? null - : new RootNode(long.Parse(GetLastPathSegment(RootApiResponse.ReferenceNode)), this); - - // http://blog.neo4j.org/2012/04/streaming-rest-api-interview-with.html - ExecutionConfiguration.UseJsonStreaming = ExecutionConfiguration.UseJsonStreaming && - RootApiResponse.Version >= new Version(1, 8); - - if (RootApiResponse.Version < new Version(2, 0)) - cypherCapabilities = CypherCapabilities.Cypher19; - - if (RootApiResponse.Version >= new Version(2, 2)) - cypherCapabilities = CypherCapabilities.Cypher22; - - if (RootApiResponse.Version >= new Version(2, 2, 6)) - cypherCapabilities = CypherCapabilities.Cypher226; - - if (RootApiResponse.Version >= new Version(2, 3)) - cypherCapabilities = CypherCapabilities.Cypher23; - - if (RootApiResponse.Version >= new Version(3, 0)) - cypherCapabilities = CypherCapabilities.Cypher30; - } - catch (AggregateException ex) - { - Exception unwrappedException; - var wasUnwrapped = ex.TryUnwrap(out unwrappedException); - operationCompletedArgs.Exception = wasUnwrapped ? unwrappedException : ex; - - stopTimerAndNotifyCompleted(); - - if (wasUnwrapped) - throw unwrappedException; - - throw; - } - catch (Exception e) - { - operationCompletedArgs.Exception = e; - stopTimerAndNotifyCompleted(); - throw; - } - - stopTimerAndNotifyCompleted(); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/Neo4jClient.Full.Shared.projitems b/Neo4jClient.Full.Shared/Neo4jClient.Full.Shared.projitems deleted file mode 100644 index 52123f6a5..000000000 --- a/Neo4jClient.Full.Shared/Neo4jClient.Full.Shared.projitems +++ /dev/null @@ -1,36 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 8bef4534-c265-41db-916c-a453155fc6a8 - - - Neo4jClient.Full.Shared - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/Neo4jClient.Full.Shared.shproj b/Neo4jClient.Full.Shared/Neo4jClient.Full.Shared.shproj deleted file mode 100644 index db04e3025..000000000 --- a/Neo4jClient.Full.Shared/Neo4jClient.Full.Shared.shproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - 8bef4534-c265-41db-916c-a453155fc6a8 - 14.0 - - - - - - - - diff --git a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionContext.cs b/Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionContext.cs deleted file mode 100644 index c5d72f5bf..000000000 --- a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionContext.cs +++ /dev/null @@ -1,139 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Neo4j.Driver.V1; -using Neo4jClient.Cypher; -using Neo4jClient.Execution; -using Newtonsoft.Json; - -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 BoltTransactionContext : ITransaction - { - /// - /// The Neo4j transaction object. - /// - public ITransaction Transaction { get; protected set; } - - internal BoltNeo4jTransaction BoltTransaction => Transaction as BoltNeo4jTransaction; - - /// - /// The consumer of all the tasks (a single thread) - /// - private Action consumer; - - /// - /// This is where the producer generates all the tasks - /// - private readonly BlockingCollection taskQueue; - - public NameValueCollection CustomHeaders { get; set; } - - /// - /// Where the cancellation token generates - /// - private readonly CancellationTokenSource cancellationTokenSource; - - public BoltTransactionContext(ITransaction transaction) - { - Transaction = transaction; - cancellationTokenSource = new CancellationTokenSource(); - taskQueue = new BlockingCollection(); - } - - public Task EnqueueTask(string commandDescription, BoltGraphClient graphClient, IExecutionPolicy policy, CypherQuery query) - { - var task = new Task(() => - { - var result = BoltTransaction.DriverTransaction.Run(query, graphClient); - - var resp = new BoltResponse{StatementResult = result}; - return resp; - } - ); - 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 tasks have been completed"); - } - if (CustomHeaders != null) - { - Transaction.CustomHeaders = CustomHeaders; - } - Transaction.Commit(); - } - - public void Rollback() - { - taskQueue.CompleteAdding(); - cancellationTokenSource.Cancel(); - Transaction.Rollback(); - } - - public void KeepAlive() - { - Transaction.KeepAlive(); - } - - public bool IsOpen => Transaction.IsOpen; - - } -} diff --git a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionPromotableSinglePhasesNotification.cs b/Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionPromotableSinglePhasesNotification.cs deleted file mode 100644 index 899880b0d..000000000 --- a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionPromotableSinglePhasesNotification.cs +++ /dev/null @@ -1,188 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Transactions; - -namespace Neo4jClient.Transactions -{ - internal class BoltTransactionPromotableSinglePhasesNotification : IPromotableSinglePhaseNotification - { - private readonly ITransactionalGraphClient client; - private readonly ISet enlistedInTransactions = new HashSet(); - private BoltNeo4jTransaction transaction; - - public BoltTransactionPromotableSinglePhasesNotification(ITransactionalGraphClient client) - { - this.client = client; - } - #region Implementation of ITransactionPromoter - - /// - public byte[] Promote() - { - // we have been promoted to MSTDC, so we have to clean the local resources - if (transaction == null) - { - transaction = new BoltNeo4jTransaction(((BoltGraphClient)client).Driver, null ); - } - - transaction.Cancel(); - transactionId = transaction.Id; - var driverTx = transaction.DriverTransaction; - var session = transaction.Session; - var bookmarks = transaction.Bookmarks; - transaction = null; - - // BUG: .NET 4.7.1 introduced a bug where the current transaction wouldn't get restored - // after a call which crosses AppDomain boundaries. We have to restore it ourselves. - // Ref https://github.com/Microsoft/dotnet-framework-early-access/issues/7 - var tx = Transaction.Current; - var res = ResourceManager.Promote(new BoltTransactionExecutionEnvironment(client.ExecutionConfiguration) - { - TransactionId = transactionId, - Session = session, - DriverTransaction = driverTx, - Bookmarks = bookmarks - }); - // Only restore if the bug exists to avoid any potentially side-effects - // of setting the transaction - if (Transaction.Current == null) - { - Transaction.Current = tx; - } - return res; - } - - #endregion - - #region Implementation of IPromotableSinglePhaseNotification - - /// - public void Initialize() - { - transaction = new BoltNeo4jTransaction(((BoltGraphClient)client).Driver, null); - } - - /// - 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.Dispose(); - transaction = null; - } - else if(transactionId != Guid.Empty) - { - ResourceManager.CommitTransaction(transactionId); - } - singlePhaseEnlistment.Committed(); - - enlistedInTransactions.Remove(Transaction.Current); - } - - /// - public void Rollback(SinglePhaseEnlistment singlePhaseEnlistment) - { - if (transaction != null) - { - transaction.Rollback(); - transaction.Dispose(); - transaction = null; - } - else if (transactionId != Guid.Empty) - { - ResourceManager.RollbackTransaction(transactionId); - } - singlePhaseEnlistment.Aborted(); - - enlistedInTransactions.Remove(Transaction.Current); - } - - #endregion - - 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 BoltNeo4jTransaction(((BoltGraphClient)client).Driver, null); - - - var propagationToken = TransactionInterop.GetTransmitterPropagationToken(transaction); - var transactionExecutionEnvironment = new BoltTransactionExecutionEnvironment(client.ExecutionConfiguration) - { - Session = this.transaction.Session, - DriverTransaction = this.transaction.DriverTransaction, - TransactionId = this.transactionId - // TransactionId = localTransaction.Id, - // TransactionBaseEndpoint = client.TransactionEndpoint - }; - ResourceManager.Enlist(transactionExecutionEnvironment, propagationToken); - localTransaction.Cancel(); - } - - enlistedInTransactions.Add(transaction); - } - - public BoltNeo4jTransaction 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 == Guid.Empty) - { - return BoltNeo4jTransaction.FromIdAndClient(transactionId, ((BoltGraphClient)client).Driver); - } - - return transaction; - } - } - //Need to set this for bolt transactions as well. - private Guid transactionId; - private static ITransactionResourceManagerBolt ResourceManager - { - get - { - if (transactionResourceManager == null) - { - sponsor = new System.Runtime.Remoting.Lifetime.ClientSponsor(); - AppDomain rmDomain = AppDomain.CreateDomain(nameof(BoltTransactionResourceManager), AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation); - transactionResourceManager = (ITransactionResourceManagerBolt)rmDomain.CreateInstanceAndUnwrap( - typeof(BoltTransactionResourceManager).Assembly.FullName, - typeof(BoltTransactionResourceManager).FullName); - sponsor.Register((MarshalByRefObject)transactionResourceManager); - } - return transactionResourceManager; - } - } - private static ITransactionResourceManagerBolt transactionResourceManager; - - - // the following was adapted from Npgsql sources (then from TransactionSinglePhaseNotification): - private static System.Runtime.Remoting.Lifetime.ClientSponsor sponsor; - - - } -} \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/Transactions/BoltNeo4jTransaction.cs b/Neo4jClient.Full.Shared/Transactions/BoltNeo4jTransaction.cs deleted file mode 100644 index 0096c4c0a..000000000 --- a/Neo4jClient.Full.Shared/Transactions/BoltNeo4jTransaction.cs +++ /dev/null @@ -1,110 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; -using Neo4j.Driver.V1; - -namespace Neo4jClient.Transactions -{ - internal class BoltNeo4jTransaction : ITransaction - { - internal readonly Neo4j.Driver.V1.ITransaction DriverTransaction; - internal ISession Session { get; } - internal IList Bookmarks { get; set; } - public Guid Id { get; private set; } - - public BoltNeo4jTransaction(IDriver driver, IEnumerable bookmarks, bool isWrite = true) - { - Bookmarks = bookmarks?.ToList(); - Session = driver.Session(isWrite ? AccessMode.Write : AccessMode.Read, Bookmarks); - DriverTransaction = Session.BeginTransaction(); - IsOpen = true; - Id = Guid.NewGuid(); - } - - public BoltNeo4jTransaction(ISession session, Neo4j.Driver.V1.ITransaction transaction) - { - DriverTransaction = transaction; - Session = session; - IsOpen = true; - Id = Guid.NewGuid(); - } - - #region Implementation of IDisposable - - protected virtual void Dispose(bool isDisposing) - { - if (!isDisposing) - return; - - IsOpen = false; - DriverTransaction?.Dispose(); - Session?.Dispose(); - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - #region Implementation of ITransaction - - /// - public void Commit() - { - DriverTransaction?.Success(); - } - - /// - public void Rollback() - { - DriverTransaction?.Failure(); - } - - //TODO: Not needed - /// - public void KeepAlive() - { - /*Not needed for Bolt.*/ - } - - /// - public bool IsOpen { get; private set; } - - //TODO: Not needed - /// - public NameValueCollection CustomHeaders { get; set; } - - #endregion - - - /// - /// Cancels a transaction without closing it in the server - /// - internal void Cancel() - { - IsOpen = false; - } - - public static void DoCommit(ITransactionExecutionEnvironmentBolt transactionExecutionEnvironment) - { - transactionExecutionEnvironment.DriverTransaction.Success(); - transactionExecutionEnvironment.DriverTransaction.Dispose(); - } - - public static void DoRollback(ITransactionExecutionEnvironmentBolt transactionExecutionEnvironment) - { - transactionExecutionEnvironment.DriverTransaction.Failure(); - transactionExecutionEnvironment.DriverTransaction.Dispose(); - } - - public static BoltNeo4jTransaction FromIdAndClient(Guid transactionId, IDriver driver) - { - return new BoltNeo4jTransaction(driver, null){Id = transactionId}; - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/Transactions/ClosedTransactionException.cs b/Neo4jClient.Full.Shared/Transactions/ClosedTransactionException.cs deleted file mode 100644 index cfde8b7e1..000000000 --- a/Neo4jClient.Full.Shared/Transactions/ClosedTransactionException.cs +++ /dev/null @@ -1,22 +0,0 @@ -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.Full.Shared/Transactions/TransactionContext.cs b/Neo4jClient.Full.Shared/Transactions/TransactionContext.cs deleted file mode 100644 index f2960379f..000000000 --- a/Neo4jClient.Full.Shared/Transactions/TransactionContext.cs +++ /dev/null @@ -1,155 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Specialized; -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; - - /// - /// This is where the producer generates all the tasks - /// - private readonly BlockingCollection taskQueue; - - public NameValueCollection CustomHeaders { get; set; } - - /// - /// Where the cancellation token generates - /// - private readonly 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); - CustomHeaders = query.CustomHeaders; - var task = new Task(() => - Request.With(client.ExecutionConfiguration, query.CustomHeaders, query.MaxExecutionTime) - .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 tasks have been completed"); - } - if (CustomHeaders != null) - { - Transaction.CustomHeaders = CustomHeaders; - } - Transaction.Commit(); - } - - public void Rollback() - { - taskQueue.CompleteAdding(); - cancellationTokenSource.Cancel(); - Transaction.Rollback(); - } - - public void KeepAlive() - { - Transaction.KeepAlive(); - } - - public bool IsOpen => Transaction.IsOpen; - - public Uri Endpoint - { - get { return Transaction.Endpoint; } - set { Transaction.Endpoint = value; } - } - } -} diff --git a/Neo4jClient.Full.Shared/Transactions/TransactionSinglePhaseNotification.cs b/Neo4jClient.Full.Shared/Transactions/TransactionSinglePhaseNotification.cs deleted file mode 100644 index 4ea4a4b45..000000000 --- a/Neo4jClient.Full.Shared/Transactions/TransactionSinglePhaseNotification.cs +++ /dev/null @@ -1,180 +0,0 @@ -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 Neo4jRestTransaction transaction; - private readonly ITransactionalGraphClient client; - private static ITransactionResourceManager resourceManager; - private readonly ISet enlistedInTransactions = new HashSet(); - private int transactionId; - - public TransactionPromotableSinglePhaseNotification(ITransactionalGraphClient client) - { - this.client = client; - } - - 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 Neo4jRestTransaction(client); - localTransaction.ForceKeepAlive(); - transactionId = localTransaction.Id; - var resourceManager = GetResourceManager(); - var propagationToken = TransactionInterop.GetTransmitterPropagationToken(transaction); - var transactionExecutionEnvironment = new TransactionExecutionEnvironment(client.ExecutionConfiguration) - { - TransactionId = localTransaction.Id, - TransactionBaseEndpoint = client.TransactionEndpoint - }; - resourceManager.Enlist(transactionExecutionEnvironment, propagationToken); - localTransaction.Cancel(); - } - - enlistedInTransactions.Add(transaction); - } - - public byte[] Promote() - { - // we have been promoted to MSTDC, so we have to clean the local resources - if (transaction == null) - { - transaction = new Neo4jRestTransaction(client); - } - - // do a keep alive in case the promotion takes too long or in case we don't have an ID - transaction.ForceKeepAlive(); - transactionId = transaction.Id; - transaction.Cancel(); - transaction = null; - - if (transactionId == 0) - { - throw new InvalidOperationException("For some reason we don't have a TransactionContext ID"); - } - - // BUG: .NET 4.7.1 introduced a bug where the current transaction wouldn't get restored - // after a call which crosses AppDomain boundaries. We have to restore it ourselves. - // Ref https://github.com/Microsoft/dotnet-framework-early-access/issues/7 - var tx = Transaction.Current; - var resourceManager = GetResourceManager(); - var res = resourceManager.Promote(new TransactionExecutionEnvironment(client.ExecutionConfiguration) - { - TransactionId = transactionId, - TransactionBaseEndpoint = client.TransactionEndpoint - }); - // Only restore if the bug exists to avoid any potentially side-effects - // of setting the transaction - if (Transaction.Current == null) - { - Transaction.Current = tx; - } - return res; - } - - 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 Neo4jRestTransaction(client); - } - - public Neo4jRestTransaction 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 Neo4jRestTransaction.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.Dispose(); - transaction = null; - } - 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.Dispose(); - 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 sponsor; - private static ITransactionResourceManager GetResourceManager() - { - if (resourceManager == null) - { - sponsor = 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); - sponsor.Register((MarshalByRefObject)resourceManager); - } - return resourceManager; - } - } -} diff --git a/Neo4jClient.Full/Neo4jClient.Full.csproj b/Neo4jClient.Full/Neo4jClient.Full.csproj deleted file mode 100644 index b664485e2..000000000 --- a/Neo4jClient.Full/Neo4jClient.Full.csproj +++ /dev/null @@ -1,104 +0,0 @@ - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {343B9889-6DDF-4474-A1EC-05508A652E5A} - Library - Properties - Neo4jClient - Neo4jClient - v4.6 - 512 - ..\ - true - - - - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET46 - prompt - 4 - bin\Debug\Neo4jClient.xml - 1591 - false - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - bin\Release\Neo4jClient.xml - 1591 - false - - - true - bin\Full - Debug\ - DEBUG;TRACE - bin\Debug\Neo4jClient.xml - 1591 - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - true - bin\Portable - Debug\ - DEBUG;TRACE - bin\Debug\Neo4jClient.xml - 1591 - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - - ..\packages\Neo4j.Driver.Signed.1.7.0\lib\net452\Neo4j.Driver.dll - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - Designer - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Full/Neo4jClient.Full.csproj.DotSettings b/Neo4jClient.Full/Neo4jClient.Full.csproj.DotSettings deleted file mode 100644 index c54c126d2..000000000 --- a/Neo4jClient.Full/Neo4jClient.Full.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp70 \ No newline at end of file diff --git a/Neo4jClient.Full/Neo4jClient.Full.ncrunchproject b/Neo4jClient.Full/Neo4jClient.Full.ncrunchproject deleted file mode 100644 index 62257550e..000000000 --- a/Neo4jClient.Full/Neo4jClient.Full.ncrunchproject +++ /dev/null @@ -1,17 +0,0 @@ - - false - false - false - false - false - false - true - true - false - true - true - 60000 - - - AutoDetect - \ No newline at end of file diff --git a/Neo4jClient.Full/Neo4jClient.Full.v2.ncrunchproject b/Neo4jClient.Full/Neo4jClient.Full.v2.ncrunchproject deleted file mode 100644 index edc8256288238d0b7cd6ed5ca34114346acfaf9d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 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 diff --git a/Neo4jClient.Full/Neo4jClient.Full.v3.ncrunchproject b/Neo4jClient.Full/Neo4jClient.Full.v3.ncrunchproject deleted file mode 100644 index 0c829a275..000000000 --- a/Neo4jClient.Full/Neo4jClient.Full.v3.ncrunchproject +++ /dev/null @@ -1,9 +0,0 @@ - - - True - - - - True - - \ No newline at end of file diff --git a/Neo4jClient.Full/Neo4jClient.csproj.DotSettings b/Neo4jClient.Full/Neo4jClient.csproj.DotSettings deleted file mode 100644 index 662f95686..000000000 --- a/Neo4jClient.Full/Neo4jClient.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp50 \ No newline at end of file diff --git a/Neo4jClient.Full/Properties/AssemblyInfo.cs b/Neo4jClient.Full/Properties/AssemblyInfo.cs deleted file mode 100644 index 9b4f5bae2..000000000 --- a/Neo4jClient.Full/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,38 +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("Neo4jClient")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("Neo4jClient")] -[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("255d97a9-6aee-404d-aa63-cf21d4e8f26d")] - -// 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("0.0.0.0")] -[assembly: AssemblyFileVersion("0.0.0.0")] - -[assembly: InternalsVisibleTo("Neo4jClient.Tests")] \ No newline at end of file diff --git a/Neo4jClient.Full/app.config b/Neo4jClient.Full/app.config deleted file mode 100644 index 9440a2268..000000000 --- a/Neo4jClient.Full/app.config +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Full/packages.config b/Neo4jClient.Full/packages.config deleted file mode 100644 index 2958bb89f..000000000 --- a/Neo4jClient.Full/packages.config +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Full452/Neo4jClient.Full452.csproj b/Neo4jClient.Full452/Neo4jClient.Full452.csproj deleted file mode 100644 index 0aea58141..000000000 --- a/Neo4jClient.Full452/Neo4jClient.Full452.csproj +++ /dev/null @@ -1,66 +0,0 @@ - - - - - Debug - AnyCPU - {99599429-A450-4AFB-8AE4-3A114F317D49} - Library - Properties - Neo4jClient - Neo4jClient - v4.5.2 - 512 - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NET45 - prompt - 4 - bin\Debug\Neo4jClient.xml - - - pdbonly - true - bin\Release\ - TRACE;NET45 - prompt - 4 - bin\Release\Neo4jClient.xml - - - - - ..\packages\Neo4j.Driver.Signed.1.7.0\lib\net452\Neo4j.Driver.dll - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - - - - - - - - - - - - - - - - - - - Designer - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Full452/Properties/AssemblyInfo.cs b/Neo4jClient.Full452/Properties/AssemblyInfo.cs deleted file mode 100644 index e409b4b82..000000000 --- a/Neo4jClient.Full452/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +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("Neo4jClient.Full452")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Neo4jClient.Full452")] -[assembly: AssemblyCopyright("Copyright © 2018")] -[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("99599429-a450-4afb-8ae4-3a114f317d49")] - -// 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/Neo4jClient.Full452/app.config b/Neo4jClient.Full452/app.config deleted file mode 100644 index 92c0f40f5..000000000 --- a/Neo4jClient.Full452/app.config +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Full452/packages.config b/Neo4jClient.Full452/packages.config deleted file mode 100644 index 09ebba693..000000000 --- a/Neo4jClient.Full452/packages.config +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Shared/ApiModels/Cypher/CypherTransactionStatement.cs b/Neo4jClient.Shared/ApiModels/Cypher/CypherTransactionStatement.cs deleted file mode 100644 index 0d6aaa65e..000000000 --- a/Neo4jClient.Shared/ApiModels/Cypher/CypherTransactionStatement.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using Neo4jClient.Cypher; -using Newtonsoft.Json; - -namespace Neo4jClient.ApiModels.Cypher -{ - /// - /// Very similar to CypherApiQuery but it's used for opened transactions as their serialization - /// is different - /// - class CypherTransactionStatement - { - private readonly string _queryText; - private readonly IDictionary _queryParameters; - private readonly string[] _formatContents; - - public CypherTransactionStatement(CypherQuery query, bool restFormat) - { - _queryText = query.QueryText; - _queryParameters = query.QueryParameters ?? new Dictionary(); - _formatContents = restFormat ? new[] {"REST"} : new string[] {}; - } - - [JsonProperty("statement")] - public string Statement - { - get { return _queryText; } - } - - [JsonProperty("resultDataContents")] - public IEnumerable ResultDataContents - { - get { return _formatContents; } - } - - [JsonProperty("parameters")] - public IDictionary Parameters - { - get { return _queryParameters; } - } - } -} diff --git a/Neo4jClient.Shared/ApiModels/ExtensionsApiResponse.cs b/Neo4jClient.Shared/ApiModels/ExtensionsApiResponse.cs deleted file mode 100644 index 042b27e6f..000000000 --- a/Neo4jClient.Shared/ApiModels/ExtensionsApiResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Neo4jClient.ApiModels.Gremlin; - -namespace Neo4jClient.ApiModels -{ - class ExtensionsApiResponse - { - public GremlinPluginApiResponse GremlinPlugin { get; set; } - } -} diff --git a/Neo4jClient.Shared/ApiModels/Gremlin/GremlinApiQuery.cs b/Neo4jClient.Shared/ApiModels/Gremlin/GremlinApiQuery.cs deleted file mode 100644 index bff3f2573..000000000 --- a/Neo4jClient.Shared/ApiModels/Gremlin/GremlinApiQuery.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace Neo4jClient.ApiModels.Gremlin -{ - class GremlinApiQuery - { - readonly string query; - readonly IDictionary parameters; - - public GremlinApiQuery(string query, IDictionary parameters) - { - this.query = query; - this.parameters = parameters ?? new Dictionary(); - } - - [JsonProperty("script")] - public string Query - { - get { return query; } - } - - [JsonProperty("params")] - public IDictionary Parameters - { - get { return parameters; } - } - } -} diff --git a/Neo4jClient.Shared/ApiModels/Gremlin/GremlinPluginApiResponse.cs b/Neo4jClient.Shared/ApiModels/Gremlin/GremlinPluginApiResponse.cs deleted file mode 100644 index b4e71b424..000000000 --- a/Neo4jClient.Shared/ApiModels/Gremlin/GremlinPluginApiResponse.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Newtonsoft.Json; - -namespace Neo4jClient.ApiModels.Gremlin -{ - class GremlinPluginApiResponse - { - [JsonProperty("execute_script")] - public string ExecuteScript { get; set; } - } -} diff --git a/Neo4jClient.Shared/ApiModels/Gremlin/GremlinTableCapResponse.cs b/Neo4jClient.Shared/ApiModels/Gremlin/GremlinTableCapResponse.cs deleted file mode 100644 index 862b88188..000000000 --- a/Neo4jClient.Shared/ApiModels/Gremlin/GremlinTableCapResponse.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using Neo4jClient.Mappers; -using Newtonsoft.Json; - -namespace Neo4jClient.ApiModels.Gremlin -{ - internal class GremlinTableCapResponse - { - [JsonProperty("columns")] - public List Columns { get; set; } - - [JsonProperty("data")] - public List> Data { get; set; } - - public static IEnumerable TransferResponseToResult(List> response, ICollection jsonConverters) where TResult : new() - { - var type = typeof(TResult); - var properties = type.GetProperties(); - - var gremlinTableCapResponses = response.SingleOrDefault(); - if (gremlinTableCapResponses != null) - { - var gremlinTableCapResponse = gremlinTableCapResponses[0]; //ToDo Added support for multiple table results. - - if (gremlinTableCapResponse == null || gremlinTableCapResponse.Columns == null || gremlinTableCapResponse.Data == null) - yield break; - - var columns = gremlinTableCapResponse.Columns; - var dataRows = gremlinTableCapResponse.Data.Select(dr => dr); - - foreach (var t in dataRows) - { - var result = new TResult(); - foreach (var prop in properties.Where(p => columns.Any(c => c.ToLowerInvariant() == p.Name.ToLowerInvariant()))) - { - var columnIndex = columns.IndexOf(prop.Name); - if (columnIndex == -1) continue; - var columnData = t; - var columnCellData = columnData[columnIndex]; - result.ConvertAndSetValue(columnCellData, prop, jsonConverters); - } - yield return result; - } - } - } - - - } -} diff --git a/Neo4jClient.Shared/ApiModels/RootApiResponse.cs b/Neo4jClient.Shared/ApiModels/RootApiResponse.cs deleted file mode 100644 index d9f6a530f..000000000 --- a/Neo4jClient.Shared/ApiModels/RootApiResponse.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Text.RegularExpressions; -using Newtonsoft.Json; - -namespace Neo4jClient.ApiModels -{ - class RootApiResponse - { - [JsonProperty("transaction")] - public string Transaction { get; set; } - - [JsonProperty("cypher")] - public string Cypher { get; set; } - - [JsonProperty("batch")] - public string Batch { get; set; } - - [JsonProperty("node")] - public string Node { get; set; } - - [JsonProperty("relationship")] - public string Relationship { get; set; } - - [JsonProperty("node_index")] - public string NodeIndex { get; set; } - - [JsonProperty("relationship_index")] - public string RelationshipIndex { get; set; } - - [JsonProperty("reference_node")] - public string ReferenceNode { get; set; } - - [JsonProperty("extensions_info")] - public string ExtensionsInfo { get; set; } - - [JsonProperty("extensions")] - public ExtensionsApiResponse Extensions { get; set; } - - [JsonProperty("neo4j_version")] - public string Neo4jVersion { get; set; } - - /// - /// Returns a structured representation of the Neo4j server version, but only with partial data. - /// The version type (milestone, preview, release candidate, stable) is not taken in to account, - /// so both 1.9.M01, 1.9.RC1 and 1.9.1 will all return 1.9.0.1. - /// - [JsonIgnore] - public Version Version => GetVersion(Neo4jVersion); - - internal static Version GetVersion(string version) - { - if (string.IsNullOrEmpty(version)) - return new Version(0, 0); - - var numericalVersionString = Regex.Replace( - version, - @"(?\d*)[.](?\d*)[.]?(M(?\d*)|RC(?\d*)?).*", - "${major}.${minor}.0.${build}"); - - numericalVersionString = Regex.Replace( - numericalVersionString, - @"(?\d*)[.](?\d*)-.*", - "${major}.${minor}"); - - Version result; - var parsed = Version.TryParse(numericalVersionString, out result); - - return parsed ? result : new Version(0, 0); - } - } -} diff --git a/Neo4jClient.Shared/BoltGraphClient.cs b/Neo4jClient.Shared/BoltGraphClient.cs deleted file mode 100644 index 07dfc2b71..000000000 --- a/Neo4jClient.Shared/BoltGraphClient.cs +++ /dev/null @@ -1,749 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Neo4j.Driver.V1; -using Neo4jClient.ApiModels; -using Neo4jClient.Cypher; -using Neo4jClient.Execution; -using Neo4jClient.Gremlin; -using Neo4jClient.Serialization; -using Neo4jClient.Transactions; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -using ITransaction = Neo4jClient.Transactions.ITransaction; - -//TODO: Logging -//TODO: Config Stuff -//TODO: Transaction Stuff - -namespace Neo4jClient -{ - /// - /// The is the client for connecting to the Bolt protocol of Neo4j. - /// - public partial class BoltGraphClient : IBoltGraphClient, IRawGraphClient, ITransactionalGraphClient - { - internal const string NotValidForBolt = "This is not available using the BoltGraphClient."; - - internal static readonly JsonConverter[] DefaultJsonConverters = - { - new TypeConverterBasedJsonConverter(), - new NullableEnumValueConverter(), - new TimeZoneInfoConverter(), - new EnumValueConverter(), - new ZonedDateTimeConverter(), - new LocalDateTimeConverter() - }; - - private static readonly DefaultContractResolver DefaultJsonContractResolver = new DefaultContractResolver(); - - private readonly string password; - private readonly string realm; - - private readonly ITransactionManager transactionManager; - private readonly IServerAddressResolver addressResolver; - private readonly string username; - private readonly Uri uri; - - public BoltGraphClient(Uri uri, string username = null, string password = null, string realm = null) - : this(uri, null, username, password, realm) - { } - - public BoltGraphClient(IEnumerable uris, string username = null, string password = null, string realm = null) - : this(new Uri("bolt+routing://virtual.neo4j.uri"), uris?.Select(UriCreator.From).ToList(), username, password, realm) - { } - - public BoltGraphClient(string uri, IEnumerable uris, string username = null, string password = null, string realm = null) - : this(new Uri(uri), uris?.Select(UriCreator.From).ToList(), username, password, realm) - {} - - public BoltGraphClient(string uri, string username = null, string password= null, string realm = null) - : this(new Uri(uri), username, password, realm) - { } - - public BoltGraphClient(IDriver driver) - : this(driver.Uri, null, null, null) - { - Driver = driver; - } - - internal IDriver Driver { get; set; } - internal IServerAddressResolver AddressResolver => addressResolver; - private IExecutionPolicyFactory PolicyFactory { get; } - - #region Implementation of ICypherGraphClient - - /// - public ICypherFluentQuery Cypher => new CypherFluentQuery(this, true); - #endregion - - private void CheckTransactionEnvironmentWithPolicy(IExecutionPolicy policy) - { - var inTransaction = InTransaction; - - transactionManager?.RegisterToTransactionIfNeeded(); - - if (inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Denied) - throw new InvalidOperationException("Cannot be done inside a transaction scope."); - - if (!inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Required) - throw new InvalidOperationException("Cannot be done outside a transaction scope."); - } - - #region ExecutionContext class - - internal class ExecutionContext - { - private readonly Stopwatch stopwatch; - private BoltGraphClient owner; - - private ExecutionContext() - { - stopwatch = Stopwatch.StartNew(); - } - - public IExecutionPolicy Policy { get; set; } - public static bool HasErrors { get; set; } - - public static ExecutionContext Begin(BoltGraphClient owner) - { - var policy = owner.PolicyFactory.GetPolicy(PolicyType.Cypher); - - owner.CheckTransactionEnvironmentWithPolicy(policy); - - var executionContext = new ExecutionContext - { - owner = owner, - Policy = policy - }; - - return executionContext; - } - - public void Complete(CypherQuery query, string lastBookmark) - { - // only parse the events when there's an event handler - Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, lastBookmark, 0, null, identifier: query.Identifier, bookmarks: query.Bookmarks); - } - - public void Complete(CypherQuery query, string lastBookmark, int resultsCount) - { - // only parse the events when there's an event handler - Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, lastBookmark, resultsCount, null, query.CustomHeaders, identifier: query.Identifier, bookmarks: query.Bookmarks); - } - - public void Complete(CypherQuery query, string lastBookmark, Exception exception) - { - // only parse the events when there's an event handler - Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, lastBookmark, -1, exception, identifier:query.Identifier, bookmarks:query.Bookmarks); - } - - public void Complete(string queryText, string lastBookmark, int resultsCount = -1, Exception exception = null, NameValueCollection customHeaders = null, int? maxExecutionTime = null, string identifier = null, IEnumerable bookmarks = null) - { - var args = new OperationCompletedEventArgs - { - LastBookmark = lastBookmark, - QueryText = queryText, - ResourcesReturned = resultsCount, - TimeTaken = stopwatch.Elapsed, - Exception = exception, - CustomHeaders = customHeaders, - MaxExecutionTime = maxExecutionTime, - Identifier = identifier, - BookmarksUsed = bookmarks - }; - - owner.OnOperationCompleted(args); - } - } - - #endregion - - #region Implementation of IDisposable - - /// - protected void Dispose(bool isDisposing) - { - if (!isDisposing) - return; - - Driver?.Dispose(); - } - - /// - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - #endregion - - #region Implementation of IGraphClient - - /// - [Obsolete(NotValidForBolt)] - public RootNode RootNode - { - get { throw new InvalidOperationException(NotValidForBolt); } - } - - /// - [Obsolete(NotValidForBolt)] - public NodeReference Create(TNode node, IEnumerable> relationships, IEnumerable indexEntries) - where TNode : class - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public Node Get(NodeReference reference) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public Task> GetAsync(NodeReference reference) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public Node Get(NodeReference reference) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public RelationshipInstance Get(RelationshipReference reference) - where TData : class, new() - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public RelationshipInstance Get(RelationshipReference reference) where TData : class, new() - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public Task> GetAsync(RelationshipReference reference) where TData : class, new() - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void Update(NodeReference nodeReference, TNode replacementData, IEnumerable indexEntries = null) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public Node Update(NodeReference nodeReference, Action updateCallback, Func> indexEntriesCallback = null, Action> changeCallback = null) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void Update(RelationshipReference relationshipReference, Action updateCallback) where TRelationshipData : class, new() - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void Delete(NodeReference reference, DeleteMode mode) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public RelationshipReference CreateRelationship(NodeReference sourceNodeReference, TRelationship relationship) where TRelationship : Relationship, IRelationshipAllowingSourceNode - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void DeleteRelationship(RelationshipReference reference) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public string ExecuteScalarGremlin(string query, IDictionary parameters) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public IEnumerable ExecuteGetAllProjectionsGremlin(IGremlinQuery query) where TResult : new() - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public IEnumerable> ExecuteGetAllNodesGremlin(IGremlinQuery query) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public IEnumerable ExecuteGetAllRelationshipsGremlin(string query, IDictionary parameters) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public IEnumerable> ExecuteGetAllRelationshipsGremlin(string query, IDictionary parameters) where TData : class, new() - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public Dictionary GetIndexes(IndexFor indexFor) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public bool CheckIndexExists(string indexName, IndexFor indexFor) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void CreateIndex(string indexName, IndexConfiguration config, IndexFor indexFor) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void ReIndex(NodeReference node, IEnumerable indexEntries) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void ReIndex(RelationshipReference relationship, IEnumerable indexEntries) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void DeleteIndex(string indexName, IndexFor indexFor) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void DeleteIndexEntries(string indexName, NodeReference relationshipReference) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void DeleteIndexEntries(string indexName, RelationshipReference relationshipReference) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public IEnumerable> QueryIndex(string indexName, IndexFor indexFor, string query) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public IEnumerable> LookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, long id) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public IEnumerable> LookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, int id) - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public void ShutdownServer() - { - throw new InvalidOperationException(NotValidForBolt); - } - - /// - [Obsolete(NotValidForBolt)] - public IGremlinClient Gremlin - { - get { throw new InvalidOperationException(NotValidForBolt); } - } - - /// - [Obsolete(NotValidForBolt)] - public Uri GremlinEndpoint - { - get { throw new InvalidOperationException(NotValidForBolt); } - } - - /// - [Obsolete(NotValidForBolt)] - public Uri NodeIndexEndpoint - { - get { throw new InvalidOperationException(NotValidForBolt); } - } - - - /// - [Obsolete(NotValidForBolt)] - public Uri RelationshipIndexEndpoint - { - get { throw new InvalidOperationException(NotValidForBolt); } - } - - /// - public void Connect(NeoServerConfiguration configuration = null) - { - var connectTask = ConnectAsync(configuration); - connectTask.Wait(); - } - - /// - public Task ConnectAsync(NeoServerConfiguration configuration = null) - { - if (Driver == null) - { - var driver = configuration == null - ? new DriverWrapper(uri, addressResolver, username, password, realm) - : new DriverWrapper(uri, addressResolver, configuration.Username, configuration.Password, configuration.Realm); - Driver = driver; - } - - using (var session = Driver.Session(AccessMode.Read)) - { - var serverInformation = session.Run("CALL dbms.components()"); - foreach (var record in serverInformation) - { - var name = record["name"].As(); - if (name.ToLowerInvariant() != "neo4j kernel") - continue; - - var version = record["versions"].As>(); - ServerVersion = RootApiResponse.GetVersion(version?.First()?.ToString()); - - if (ServerVersion > new Version(3, 0)) - CypherCapabilities = CypherCapabilities.Cypher30; - } - } - - IsConnected = true; -#if NET45 - return Task.FromResult(0); -#else - return Task.CompletedTask; -#endif - } - - /// - public List JsonConverters { get; } - - /// - public DefaultContractResolver JsonContractResolver { get; set; } - -#endregion - - #region Implementation of IRawGraphClient - - /// - IEnumerable IRawGraphClient.ExecuteGetCypherResults(CypherQuery query) - { - var task = ((IRawGraphClient) this).ExecuteGetCypherResultsAsync(query); - try - { - Task.WaitAll(task); - } - catch (AggregateException ex) - { - Exception unwrappedException; - if (ex.TryUnwrap(out unwrappedException)) - throw unwrappedException; - throw; - } - - return task.Result; - } - - /// - async Task> IRawGraphClient.ExecuteGetCypherResultsAsync(CypherQuery query) - { - if (Driver == null) - throw new InvalidOperationException("Can't execute cypher unless you have connected to the server."); - - var context = ExecutionContext.Begin(this); - List results; - string lastBookmark = null; - try - { - if (InTransaction) - { - var result = await transactionManager.EnqueueCypherRequest($"The query was: {query.QueryText}", this, query).ConfigureAwait(false); - results = ParseResults(result.StatementResult, query); - } - else - { - using (var session = Driver.Session(query.IsWrite ? AccessMode.Write : AccessMode.Read, query.Bookmarks)) - { - var result = query.IsWrite - ? session.WriteTransaction(s => s.Run(query, this)) - : session.ReadTransaction(s => s.Run(query, this)); - - results = ParseResults(result, query); - lastBookmark = session.LastBookmark; - } - } - } - catch (AggregateException aggregateException) - { - Exception unwrappedException; - context.Complete(query, lastBookmark, aggregateException.TryUnwrap(out unwrappedException) ? unwrappedException : aggregateException); - throw; - } - catch (Exception e) - { - context.Complete(query, lastBookmark, e); - throw; - } - - context.Complete(query, lastBookmark, results.Count); - return results; - } - - private List ParseResults(IStatementResult result, CypherQuery query) - { - var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, false, true); - var results = new List(); - if (typeof(TResult).IsAnonymous()) - { - foreach (var record in result) - results.AddRange(deserializer.Deserialize(record.ParseAnonymous(this))); - } - else - { - - StatementResultHelper.JsonSettings = new JsonSerializerSettings - { - Converters = JsonConverters, - ContractResolver = JsonContractResolver - }; - - List> converted = new List>(); - foreach (var record in result) - { - var des = record.Deserialize(deserializer, query.ResultMode); - converted.Add(des); - } - - foreach (var enumerable in converted) - { - results.AddRange(enumerable); - } - } - - return results; - } - - /// - void IRawGraphClient.ExecuteCypher(CypherQuery query) - { - var task = ((IRawGraphClient) this).ExecuteCypherAsync(query); - task.Wait(); - } - - /// - [Obsolete] - void IRawGraphClient.ExecuteMultipleCypherQueriesInTransaction(IEnumerable queries, NameValueCollection customHeaders = null) - { - using (var tx = BeginTransaction()) - { - // the HTTP endpoint executed the transactions in a serial fashion - foreach (var query in queries) - { - ((IRawGraphClient) this).ExecuteCypher(query); - } - - tx.Commit(); - } - } - - /// - Task IRawGraphClient.ExecuteCypherAsync(CypherQuery query) - { - var tx = ExecutionContext.Begin(this); - - if (Driver == null) - throw new InvalidOperationException("Can't execute cypher unless you have connected to the server."); - - if (InTransaction) - return transactionManager.EnqueueCypherRequest($"The query was: {query.QueryText}", this, query) - .ContinueWith(responseTask => OperationCompleted?.Invoke(this, new OperationCompletedEventArgs - { - QueryText = $"BOLT:{query.QueryText}" - })); - - using (var session = Driver.Session(query.IsWrite ? AccessMode.Write : AccessMode.Read, query.Bookmarks)) - { - if (query.IsWrite) - session.WriteTransaction(s => s.Run(query, this)); - else - session.ReadTransaction(s => s.Run(query, this)); - tx.Complete(query, session.LastBookmark); - } -#if NET45 - return Task.FromResult(0); -#else - return Task.CompletedTask; -#endif - } - - #endregion - - #region Implementation of ITransactionalGraphClient - - /// - public ITransaction BeginTransaction() - { - return BeginTransaction((IEnumerable) null); - } - - /// - public ITransaction BeginTransaction(string bookmark) - { - return BeginTransaction(new List{bookmark}); - } - - /// - public ITransaction BeginTransaction(IEnumerable bookmarks) - { - return BeginTransaction(TransactionScopeOption.Join, bookmarks); - } - - /// - public ITransaction BeginTransaction(TransactionScopeOption scopeOption) - { - return BeginTransaction(scopeOption, (IEnumerable) null); - } - - /// - public ITransaction BeginTransaction(TransactionScopeOption scopeOption, string bookmark) - { - return BeginTransaction(scopeOption, new List{bookmark}); - } - - /// - public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmarks) - { - if (transactionManager == null) - throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); - - return transactionManager.BeginTransaction(scopeOption, bookmarks); - } - - /// - public ITransaction Transaction => transactionManager?.CurrentNonDtcTransaction; - - /// - public bool InTransaction => transactionManager != null && transactionManager.InTransaction; - - /// - public void EndTransaction() - { - if (transactionManager == null) - throw new NotSupportedException("Transactions are only supported on Neo4j 2.0 and newer."); - - transactionManager.EndTransaction(); - } - - /// - public Uri TransactionEndpoint - { - get { throw new InvalidOperationException(NotValidForBolt); } - } - -#endregion - - #region Implementation of IBoltGraphClient - - /// - public event OperationCompletedEventHandler OperationCompleted; - - /// - public CypherCapabilities CypherCapabilities { get; private set; } - - /// - public Version ServerVersion { get; private set; } - - /// - [Obsolete(NotValidForBolt)] - public Uri RootEndpoint => throw new InvalidOperationException(NotValidForBolt); - - /// - [Obsolete(NotValidForBolt)] - public Uri BatchEndpoint => throw new InvalidOperationException(NotValidForBolt); - - /// - [Obsolete(NotValidForBolt)] - public Uri CypherEndpoint => throw new InvalidOperationException(NotValidForBolt); - - /// - [Obsolete(NotValidForBolt)] - public ISerializer Serializer => throw new InvalidOperationException(NotValidForBolt); - - /// - public ExecutionConfiguration ExecutionConfiguration { get; } - - /// - public bool IsConnected { get; private set; } - - /// Raises the event. - /// The instance of to send to listeners. - protected void OnOperationCompleted(OperationCompletedEventArgs args) - { - OperationCompleted?.Invoke(this, args); - } - - #endregion - - - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Cypher/CypherStartBit.cs b/Neo4jClient.Shared/Cypher/CypherStartBit.cs deleted file mode 100644 index f9db265bd..000000000 --- a/Neo4jClient.Shared/Cypher/CypherStartBit.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Neo4jClient.Cypher -{ - [Obsolete("Use IGraphClient.Cypher.Start(new { foo = nodeRef1 }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - public class CypherStartBit : ICypherStartBit - { - readonly string identifier; - readonly string lookupType; - readonly IEnumerable lookupIds; - - public CypherStartBit(string identifier, string lookupType, IEnumerable lookupIds) - { - this.identifier = identifier; - this.lookupType = lookupType; - this.lookupIds = lookupIds; - } - - public CypherStartBit(string identifier, params NodeReference[] nodeReferences) - : this(identifier, (IEnumerable)nodeReferences) - { - } - - public CypherStartBit(string identifier, IEnumerable nodeReferences) - { - this.identifier = identifier; - lookupType = "node"; - lookupIds = nodeReferences.Select(r => r.Id).ToArray(); - } - - public CypherStartBit(string identifier, params RelationshipReference[] relationshipReferences) - : this(identifier, (IEnumerable)relationshipReferences) - { - } - - public CypherStartBit(string identifier, IEnumerable relationshipReferences) - { - this.identifier = identifier; - lookupType = "relationship"; - lookupIds = relationshipReferences.Select(r => r.Id).ToArray(); - } - - public string ToCypherText(Func createParameterCallback) - { - var lookupIdParameterNames = lookupIds - .Select(i => createParameterCallback(i)) - .ToArray(); - - var lookupContent = string.Join(", ", lookupIdParameterNames); - return string.Format("{0}={1}({2})", - identifier, - lookupType, - lookupContent); - } - } -} diff --git a/Neo4jClient.Shared/Cypher/CypherStartBitWithNodeIndexLookup.cs b/Neo4jClient.Shared/Cypher/CypherStartBitWithNodeIndexLookup.cs deleted file mode 100644 index 9578280f4..000000000 --- a/Neo4jClient.Shared/Cypher/CypherStartBitWithNodeIndexLookup.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; - -namespace Neo4jClient.Cypher -{ - [Obsolete("Use IGraphClient.Cypher.Start(new { foo = Node.ByIndexLookup() }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - public class CypherStartBitWithNodeIndexLookup : ICypherStartBit - { - readonly string identifier; - readonly string indexName; - readonly string key; - readonly object value; - - public CypherStartBitWithNodeIndexLookup(string identifier, string indexName, string key, object value) - { - this.identifier = identifier; - this.indexName = indexName; - this.key = key; - this.value = value; - } - - public string ToCypherText(Func createParameterCallback) - { - var valueParameter = createParameterCallback(value); - return string.Format("{0}=node:`{1}`({2} = {3})", - identifier, - indexName, - key, - valueParameter); - } - } -} diff --git a/Neo4jClient.Shared/Cypher/CypherStartBitWithNodeIndexLookupWithSingleParameter.cs b/Neo4jClient.Shared/Cypher/CypherStartBitWithNodeIndexLookupWithSingleParameter.cs deleted file mode 100644 index fe97b36ea..000000000 --- a/Neo4jClient.Shared/Cypher/CypherStartBitWithNodeIndexLookupWithSingleParameter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace Neo4jClient.Cypher -{ - [Obsolete("Use IGraphClient.Cypher.Start(new { foo = Node.ByIndexQuery() }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - public class CypherStartBitWithNodeIndexLookupWithSingleParameter : ICypherStartBit - { - readonly string identifier; - readonly string indexName; - readonly string parameter; - - public CypherStartBitWithNodeIndexLookupWithSingleParameter(string identifier, string indexName, string parameter) - { - this.identifier = identifier; - this.indexName = indexName; - this.parameter = parameter; - } - - public string ToCypherText(Func createParameterCallback) - { - var valueParameter = createParameterCallback(parameter); - return string.Format("{0}=node:{1}({2})", - identifier, - indexName, - valueParameter); - } - } -} diff --git a/Neo4jClient.Shared/Cypher/ICypherStartBit.cs b/Neo4jClient.Shared/Cypher/ICypherStartBit.cs deleted file mode 100644 index 8c876bad2..000000000 --- a/Neo4jClient.Shared/Cypher/ICypherStartBit.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; - -namespace Neo4jClient.Cypher -{ - [Obsolete("Use StartBit instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - public interface ICypherStartBit - { - string ToCypherText(Func createParameterCallback); - } -} diff --git a/Neo4jClient.Shared/Cypher/RawCypherStartBit.cs b/Neo4jClient.Shared/Cypher/RawCypherStartBit.cs deleted file mode 100644 index 3ac094405..000000000 --- a/Neo4jClient.Shared/Cypher/RawCypherStartBit.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; - -namespace Neo4jClient.Cypher -{ - [Obsolete("Use IGraphClient.Cypher.Start(new { foo = \"bar\" }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - public class RawCypherStartBit : ICypherStartBit - { - readonly string identifier; - readonly string startText; - - public RawCypherStartBit(string identifier, string startText) - { - this.identifier = identifier; - this.startText = startText; - } - - public string Identifier - { - get { return identifier; } - } - - public string StartText - { - get { return startText; } - } - - public string ToCypherText(Func createParameterCallback) - { - return identifier + "=" + startText; - } - } -} diff --git a/Neo4jClient.Shared/DriverWrapper.cs b/Neo4jClient.Shared/DriverWrapper.cs deleted file mode 100644 index 9227c1c7e..000000000 --- a/Neo4jClient.Shared/DriverWrapper.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Neo4j.Driver.V1; - -namespace Neo4jClient -{ - /* - * - private IDriver CreateDriverWithCustomResolver(string virtualUri, IAuthToken token, - params ServerAddress[] addresses) -{ - return GraphDatabase.Driver(virtualUri, token, - new Config {Resolver = new ListAddressResolver(addresses), EncryptionLevel = EncryptionLevel.None}); -} - -public void AddPerson(string name) -{ - using (var driver = CreateDriverWithCustomResolver("bolt+routing://x.acme.com", - AuthTokens.Basic(Username, Password), - ServerAddress.From("a.acme.com", 7687), ServerAddress.From("b.acme.com", 7877), - ServerAddress.From("c.acme.com", 9092))) - { - using (var session = driver.Session()) - { - session.Run("CREATE (a:Person {name: $name})", new {name}); - } - } -} - -private class ListAddressResolver : IServerAddressResolver -{ - private readonly ServerAddress[] servers; - - public ListAddressResolver(params ServerAddress[] servers) - { - this.servers = servers; - } - - public ISet Resolve(ServerAddress address) - { - return new HashSet(servers); - } -} - * - */ - - internal class DriverWrapper : IDriver - { - private readonly IDriver driver; - public string Username { get; } - public string Password { get; } - public string Realm { get; } - - public DriverWrapper(IDriver driver) - { - this.driver = driver; - } - - public DriverWrapper(string uri, IServerAddressResolver addressResolver, string username, string pass, string realm) - :this(new Uri(uri), addressResolver, username, pass, realm) - { - } - - public DriverWrapper(Uri uri, IServerAddressResolver addressResolver, string username, string pass, string realm) - { - Uri = uri; - Username = username; - Password = pass; - Realm = realm; - - var authToken = GetAuthToken(username, pass, realm); - this.driver = addressResolver == null - ? GraphDatabase.Driver(uri, authToken) - : GraphDatabase.Driver(uri, authToken, new Config { Resolver = addressResolver }); - } - - public ISession Session() - { - return driver.Session(); - } - - public ISession Session(AccessMode defaultMode) - { - return driver.Session(defaultMode); - } - - public ISession Session(string bookmark) - { - return driver.Session(bookmark); - } - - public ISession Session(AccessMode defaultMode, string bookmark) - { - return driver.Session(defaultMode, bookmark); - } - - public ISession Session(AccessMode defaultMode, IEnumerable bookmarks) - { - return driver.Session(defaultMode, bookmarks); - } - - public ISession Session(IEnumerable bookmarks) - { - return driver.Session(bookmarks); - } - - public void Close() - { - driver.Close(); - } - - public Task CloseAsync() - { - return driver.CloseAsync(); - } - - public Uri Uri { get; } - - public IServerAddressResolver AddressResolver { get; } - - private static IAuthToken GetAuthToken(string username, string password, string realm) - { - return string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) - ? AuthTokens.None - : AuthTokens.Basic(username, password, realm); - } - public void Dispose() - { - driver?.Dispose(); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Execution/BatchExecutionPolicy.cs b/Neo4jClient.Shared/Execution/BatchExecutionPolicy.cs deleted file mode 100644 index f7ee315ca..000000000 --- a/Neo4jClient.Shared/Execution/BatchExecutionPolicy.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Neo4jClient.Execution -{ - internal class BatchExecutionPolicy : GraphClientBasedExecutionPolicy - { - public BatchExecutionPolicy(IGraphClient client) : base(client) - { - } - - public override TransactionExecutionPolicy TransactionExecutionPolicy - { - get { return TransactionExecutionPolicy.Denied; } - } - - public override void AfterExecution(IDictionary executionMetadata, object executionContext) - { - } - - public override Uri BaseEndpoint - { - get { return Client.BatchEndpoint; } - } - } -} diff --git a/Neo4jClient.Shared/Execution/CypherExecutionPolicy.cs b/Neo4jClient.Shared/Execution/CypherExecutionPolicy.cs deleted file mode 100644 index b4b7601a9..000000000 --- a/Neo4jClient.Shared/Execution/CypherExecutionPolicy.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Neo4jClient.ApiModels.Cypher; -using Neo4jClient.Cypher; -using Neo4jClient.Transactions; - -//using Neo4jClient.Transactions; - - -namespace Neo4jClient.Execution -{ - /// - /// Describes the behavior for a cypher execution. - /// - internal partial class CypherExecutionPolicy : GraphClientBasedExecutionPolicy - { - public CypherExecutionPolicy(IGraphClient client) : base(client) - { - } - - - - - public override string SerializeRequest(object toSerialize) - { - var query = toSerialize as CypherQuery; - if (query == null) - { - throw new InvalidOperationException( - "Unsupported operation: Attempting to serialize something that was not a query."); - } - - if (InTransaction) - { - return Client - .Serializer - .Serialize(new CypherStatementList - { - new CypherTransactionStatement(query, query.ResultFormat == CypherResultFormat.Rest) - }); - } - return Client.Serializer.Serialize(new CypherApiQuery(query)); - } - - public override void AfterExecution(IDictionary executionMetadata, object executionContext) - { - if (Client == null || executionMetadata == null || executionMetadata.Count == 0) - { - return; - } - - // determine if we need to update the transaction end point - var transaction = executionContext as INeo4jTransaction; - if (transaction == null || transaction.Endpoint != null) - { - return; - } - - object locationValue; - if (!executionMetadata.TryGetValue("Location", out locationValue)) - { - return; - } - - var locationHeader = locationValue as IEnumerable; - if (locationHeader == null) - { - return; - } - - var generatedEndpoint = locationHeader.FirstOrDefault(); - if (!string.IsNullOrEmpty(generatedEndpoint)) - { - transaction.Endpoint = new Uri(generatedEndpoint); - } - } - - } -} diff --git a/Neo4jClient.Shared/Execution/GraphClientBasedExecutionPolicy.cs b/Neo4jClient.Shared/Execution/GraphClientBasedExecutionPolicy.cs deleted file mode 100644 index f493d9d79..000000000 --- a/Neo4jClient.Shared/Execution/GraphClientBasedExecutionPolicy.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Neo4jClient.Execution -{ - internal abstract partial class GraphClientBasedExecutionPolicy : IExecutionPolicy - { - protected IGraphClient Client; - - protected GraphClientBasedExecutionPolicy(IGraphClient client) - { - Client = client; - } - - - public abstract void AfterExecution(IDictionary executionMetadata, object executionContext); - - public virtual string SerializeRequest(object toSerialize) - { - return Client.Serializer.Serialize(toSerialize); - } - - public abstract Uri BaseEndpoint { get; } - - public virtual Uri AddPath(Uri startUri, object startReference) - { - return startUri; - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Execution/GremlinExecutionPolicy.cs b/Neo4jClient.Shared/Execution/GremlinExecutionPolicy.cs deleted file mode 100644 index 0f451f89f..000000000 --- a/Neo4jClient.Shared/Execution/GremlinExecutionPolicy.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace Neo4jClient.Execution -{ - internal class GremlinExecutionPolicy : GraphClientBasedExecutionPolicy - { - public GremlinExecutionPolicy(IGraphClient client) : base(client) - { - } - - public override TransactionExecutionPolicy TransactionExecutionPolicy - { - get { return TransactionExecutionPolicy.Denied; } - } - - public override void AfterExecution(IDictionary executionMetadata, object executionContext) - { - } - - public override Uri BaseEndpoint - { - get { return Client.GremlinEndpoint; } - } - } -} diff --git a/Neo4jClient.Shared/Execution/NodeIndexExecutionPolicy.cs b/Neo4jClient.Shared/Execution/NodeIndexExecutionPolicy.cs deleted file mode 100644 index 98b8d3102..000000000 --- a/Neo4jClient.Shared/Execution/NodeIndexExecutionPolicy.cs +++ /dev/null @@ -1,17 +0,0 @@ -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.Shared/Execution/RelationshipIndexExecutionPolicy.cs b/Neo4jClient.Shared/Execution/RelationshipIndexExecutionPolicy.cs deleted file mode 100644 index 4b87538f8..000000000 --- a/Neo4jClient.Shared/Execution/RelationshipIndexExecutionPolicy.cs +++ /dev/null @@ -1,17 +0,0 @@ -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.Shared/GraphClient.cs b/Neo4jClient.Shared/GraphClient.cs deleted file mode 100644 index 4c910810a..000000000 --- a/Neo4jClient.Shared/GraphClient.cs +++ /dev/null @@ -1,1531 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; -using System.ComponentModel.DataAnnotations; -using System.Diagnostics; -using System.Globalization; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Reflection; -using System.Threading.Tasks; -using Neo4jClient.ApiModels; -using Neo4jClient.ApiModels.Cypher; -using Neo4jClient.ApiModels.Gremlin; -using Neo4jClient.Cypher; -using Neo4jClient.Execution; -using Neo4jClient.Gremlin; -using Neo4jClient.Serialization; -using Neo4jClient.Transactions; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace Neo4jClient -{ - public partial 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 MaxExecutionTimeHeaderKey = "max-execution-time"; - - public static readonly JsonConverter[] DefaultJsonConverters = - { - new TypeConverterBasedJsonConverter(), - new NullableEnumValueConverter(), - new TimeZoneInfoConverter(), - new EnumValueConverter() - }; - - public static readonly DefaultContractResolver DefaultJsonContractResolver = new DefaultContractResolver(); - - private ITransactionManager transactionManager; - private readonly IExecutionPolicyFactory policyFactory; - - public ExecutionConfiguration ExecutionConfiguration { get; private set; } - - internal readonly Uri RootUri; - internal RootApiResponse RootApiResponse; - private RootNode rootNode; - - private CypherCapabilities cypherCapabilities = CypherCapabilities.Default; - - - public bool UseJsonStreamingIfAvailable { get; set; } - - // public GraphClient(Uri rootUri, string username = null, string password = null) - // : this(rootUri, new HttpClientWrapper(username, password)) - // { - // ServicePointManager.Expect100Continue = true; - // ServicePointManager.UseNagleAlgorithm = false; - // } - // - // public GraphClient(Uri rootUri, bool expect100Continue, bool useNagleAlgorithm, string username = null, string password = null) - // : this(rootUri, new HttpClientWrapper(username, password)) - // { - // ServicePointManager.Expect100Continue = expect100Continue; - // ServicePointManager.UseNagleAlgorithm = useNagleAlgorithm; - // } - - public GraphClient(Uri rootUri, IHttpClient httpClient) - { - RootUri = rootUri; - JsonConverters = new List(); - JsonConverters.AddRange(DefaultJsonConverters); - JsonContractResolver = DefaultJsonContractResolver; - ExecutionConfiguration = new ExecutionConfiguration - { - HttpClient = httpClient, - UserAgent = $"Neo4jClient/{GetType().GetTypeInfo().Assembly.GetName().Version}", - UseJsonStreaming = true, - JsonConverters = JsonConverters, - Username = httpClient?.Username, - Password = httpClient?.Password - }; - UseJsonStreamingIfAvailable = true; - policyFactory = new ExecutionPolicyFactory(this); - } - - private Uri BuildUri(string relativeUri) - { - var baseUri = RootUri; - if (!RootUri.AbsoluteUri.EndsWith("/")) - baseUri = new Uri(RootUri.AbsoluteUri + "/"); - - if (relativeUri.StartsWith("/")) - relativeUri = relativeUri.Substring(1); - - return new Uri(baseUri, relativeUri); - } - - private string SerializeAsJson(object contents) - { - return Serializer.Serialize(contents); - } - - public virtual bool IsConnected => RootApiResponse != null; - - public virtual void Connect(NeoServerConfiguration configuration = null) - { - var task = ConnectAsync(configuration); - try - { - Task.WaitAll(task); - } - catch (AggregateException ex) - { - var operationCompleteArgs = new OperationCompletedEventArgs(); - - Exception unwrappedException; - var wasUnwrapped = ex.TryUnwrap(out unwrappedException); - operationCompleteArgs.Exception = wasUnwrapped ? unwrappedException : ex; - - OperationCompleted?.Invoke(this, operationCompleteArgs); - - if (wasUnwrapped) - throw unwrappedException; - - throw; - } - catch (Exception ex) - { - OperationCompleted?.Invoke(this, new OperationCompletedEventArgs { Exception = ex }); - throw; - } - } - - [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 - { - CheckRoot(); - return rootNode; - } - } - - public virtual NodeReference Create( - TNode node, - IEnumerable> relationships, - IEnumerable indexEntries) - where TNode : class - { - if (typeof (TNode).GetTypeInfo().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), - "node"); - } - - if (node == null) - throw new ArgumentNullException("node"); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - relationships = relationships ?? Enumerable.Empty>(); - indexEntries = (indexEntries ?? Enumerable.Empty()).ToArray(); - - var validationContext = new ValidationContext(node, null, null); - Validator.ValidateObject(node, validationContext); - - var calculatedRelationships = relationships - .Cast() - .Select(r => new - { - CalculatedDirection = Relationship.DetermineRelationshipDirection(typeof(TNode), r), - Relationship = r - }) - .ToArray(); - - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Batch); - CheckTransactionEnvironmentWithPolicy(policy); - - var batchSteps = new List(); - - var createNodeStep = batchSteps.Add(HttpMethod.Post, "/node", node); - - foreach (var relationship in calculatedRelationships) - { - var participants = new[] - { - string.Format("{{{0}}}", createNodeStep.Id), - string.Format("/node/{0}", relationship.Relationship.OtherNode.Id) - }; - string sourceNode, targetNode; - switch (relationship.CalculatedDirection) - { - case RelationshipDirection.Outgoing: - sourceNode = participants[0]; - targetNode = participants[1]; - break; - case RelationshipDirection.Incoming: - sourceNode = participants[1]; - targetNode = participants[0]; - break; - default: - throw new NotSupportedException(string.Format( - "The specified relationship direction is not supported: {0}", - relationship.CalculatedDirection)); - } - - var relationshipTemplate = new RelationshipTemplate - { - To = targetNode, - Data = relationship.Relationship.Data, - Type = relationship.Relationship.RelationshipTypeKey - }; - batchSteps.Add(HttpMethod.Post, sourceNode + "/relationships", relationshipTemplate); - } - - var entries = indexEntries - .SelectMany(i => i - .KeyValues - .Select(kv => new - { - IndexAddress = BuildRelativeIndexAddress(i.Name, IndexFor.Node), - kv.Key, - Value = EncodeIndexValue(kv.Value) - }) - .Where(e => !string.IsNullOrEmpty(e.Value))); - foreach (var indexEntry in entries) - { - batchSteps.Add(HttpMethod.Post, indexEntry.IndexAddress, new - { - key = indexEntry.Key, - value = indexEntry.Value, - uri = "{0}" - }); - } - - var batchResponse = ExecuteBatch(batchSteps, policy); - var createResponse = batchResponse[createNodeStep]; - EnsureNodeWasCreated(createResponse); - var nodeId = long.Parse(GetLastPathSegment(createResponse.Location)); - var nodeReference = new NodeReference(nodeId, this); - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs - { - QueryText = string.Format("Create<{0}>", typeof(TNode).Name), - ResourcesReturned = 0, - TimeTaken = stopwatch.Elapsed - }); - - return nodeReference; - } - - private BatchResponse ExecuteBatch(List batchSteps, IExecutionPolicy policy) - { - return Request.With(ExecutionConfiguration) - .Post(policy.BaseEndpoint) - .WithJsonContent(SerializeAsJson(batchSteps)) - .WithExpectedStatusCodes(HttpStatusCode.OK) - .ParseAs() - .Execute(); - } - - public virtual RelationshipReference CreateRelationship( - NodeReference sourceNodeReference, - TRelationship relationship) - where TRelationship : - Relationship, - IRelationshipAllowingSourceNode - { - if (sourceNodeReference == null) - throw new ArgumentNullException("sourceNodeReference"); - - if (relationship.Direction == RelationshipDirection.Incoming) - throw new NotSupportedException("Incoming relationships are not yet supported by this method."); - - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Rest); - CheckTransactionEnvironmentWithPolicy(policy); - - return CreateRelationship( - sourceNodeReference, - relationship.OtherNode, - relationship.RelationshipTypeKey, - relationship.Data, - policy); - } - - private RelationshipReference CreateRelationship(NodeReference sourceNode, NodeReference targetNode, - string relationshipTypeKey, object data, IExecutionPolicy policy) - { - var relationship = new RelationshipTemplate - { - To = policy.BaseEndpoint.AddPath(targetNode, policy).ToString(), - Data = data, - Type = relationshipTypeKey - }; - - var sourceNodeEndpoint = policy.BaseEndpoint - .AddPath(sourceNode, policy) - .AddPath("relationships"); - - return Request.With(ExecutionConfiguration) - .Post(sourceNodeEndpoint) - .WithJsonContent(SerializeAsJson(relationship)) - .WithExpectedStatusCodes(HttpStatusCode.Created, HttpStatusCode.NotFound) - .ParseAs>() - .FailOnCondition(responseMessage => responseMessage.StatusCode == HttpStatusCode.NotFound) - .WithError(responseMessage => new Exception(string.Format( - "One of the nodes referenced in the relationship could not be found. Referenced nodes were {0} and {1}.", - sourceNode.Id, - targetNode.Id)) - ) - .Execute() - //.ReadAsJson>(JsonConverters,JsonContractResolver) - .ToRelationshipReference(this); - } - - CustomJsonSerializer BuildSerializer() - { - return new CustomJsonSerializer { JsonConverters = JsonConverters, JsonContractResolver = JsonContractResolver }; - } - - public ISerializer Serializer => new CustomJsonSerializer { JsonConverters = JsonConverters, JsonContractResolver = JsonContractResolver }; - - public void DeleteRelationship(RelationshipReference reference) - { - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Rest); - CheckTransactionEnvironmentWithPolicy(policy); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - Request.With(ExecutionConfiguration) - .Delete(policy.BaseEndpoint.AddPath(reference, policy)) - .WithExpectedStatusCodes(HttpStatusCode.NoContent, HttpStatusCode.NotFound) - .FailOnCondition(response => response.StatusCode == HttpStatusCode.NotFound) - .WithError(response => new Exception(string.Format( - "Unable to delete the relationship. The response status was: {0} {1}", - (int)response.StatusCode, - response.ReasonPhrase))) - .Execute(); - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs - { - QueryText = "Delete Relationship " + reference.Id, - ResourcesReturned = 0, - TimeTaken = stopwatch.Elapsed - }); - } - - public virtual Node Get(NodeReference reference) - { - var task = GetAsync(reference); - if (task.Exception != null) - { - Exception unwrappedException; - if (task.Exception.TryUnwrap(out unwrappedException)) - throw unwrappedException; - throw task.Exception; - } - Task.WaitAll(task); - return task.Result; - } - - public virtual Task> GetAsync(NodeReference reference) - { - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Rest); - CheckTransactionEnvironmentWithPolicy(policy); - - 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) - { - return Get((NodeReference)reference); - } - - public virtual RelationshipInstance Get(RelationshipReference reference) - where TData : class, new() - { - return Get((RelationshipReference)reference); - } - - public virtual RelationshipInstance Get(RelationshipReference reference) - where TData : class, new() - { - var task = GetAsync(reference); - if (task.Exception != null) - { - Exception unwrappedException; - if (task.Exception.TryUnwrap(out unwrappedException)) - throw unwrappedException; - throw task.Exception; - } - - Task.WaitAll(task); - return task.Result; - } - - public virtual Task> GetAsync(RelationshipReference reference) where TData : class, new() - { - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Rest); - CheckTransactionEnvironmentWithPolicy(policy); - - 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); - CheckTransactionEnvironmentWithPolicy(policy); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - var allIndexEntries = indexEntries == null - ? new IndexEntry[0] - : indexEntries.ToArray(); - - Request.With(ExecutionConfiguration) - .Put(policy.BaseEndpoint.AddPath(nodeReference, policy).AddPath("properties")) - .WithJsonContent(SerializeAsJson(replacementData)) - .WithExpectedStatusCodes(HttpStatusCode.NoContent) - .Execute(); - - if (allIndexEntries.Any()) - ReIndex(nodeReference, allIndexEntries); - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs - { - QueryText = string.Format("Update<{0}> {1}", typeof(TNode).Name, nodeReference.Id), - ResourcesReturned = 0, - TimeTaken = stopwatch.Elapsed - }); - } - - public Node Update(NodeReference nodeReference, Action updateCallback, - Func> indexEntriesCallback = null, - Action> changeCallback = null) - { - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Rest); - CheckTransactionEnvironmentWithPolicy(policy); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - var node = Get(nodeReference); - - var indexEntries = new IndexEntry[] { }; - - if (indexEntriesCallback != null) - { - indexEntries = indexEntriesCallback(node.Data).ToArray(); - } - - var serializer = Serializer; - - var originalValuesString = changeCallback == null ? null : serializer.Serialize(node.Data); - - updateCallback(node.Data); - - if (changeCallback != null) - { - var originalValuesDictionary = - new CustomJsonDeserializer(JsonConverters, resolver: JsonContractResolver).Deserialize>( - originalValuesString); - var newValuesString = serializer.Serialize(node.Data); - var newValuesDictionary = - new CustomJsonDeserializer(JsonConverters, resolver: JsonContractResolver).Deserialize>(newValuesString); - var differences = Utilities.GetDifferencesBetweenDictionaries(originalValuesDictionary, - newValuesDictionary); - changeCallback(differences); - } - - Request.With(ExecutionConfiguration) - .Put(policy.BaseEndpoint.AddPath(nodeReference, policy).AddPath("properties")) - .WithJsonContent(serializer.Serialize(node.Data)) - .WithExpectedStatusCodes(HttpStatusCode.NoContent) - .Execute(); - - if (indexEntriesCallback != null) - { - ReIndex(node.Reference, indexEntries); - } - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs - { - QueryText = string.Format("Update<{0}> {1}", typeof(TNode).Name, nodeReference.Id), - ResourcesReturned = 0, - TimeTaken = stopwatch.Elapsed - }); - - return node; - } - - public void Update(RelationshipReference relationshipReference, - Action updateCallback) - where TRelationshipData : class, new() - { - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Rest); - CheckTransactionEnvironmentWithPolicy(policy); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - var propertiesEndpoint = 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); - - Request.With(ExecutionConfiguration) - .Put(propertiesEndpoint) - .WithJsonContent(SerializeAsJson(payload)) - .WithExpectedStatusCodes(HttpStatusCode.NoContent) - .Execute(); - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs - { - QueryText = string.Format("Update<{0}> {1}", typeof(TRelationshipData).Name, relationshipReference.Id), - ResourcesReturned = 0, - TimeTaken = stopwatch.Elapsed - }); - } - - public virtual void Delete(NodeReference reference, DeleteMode mode) - { - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Rest); - CheckTransactionEnvironmentWithPolicy(policy); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - if (mode == DeleteMode.NodeAndRelationships) - { - DeleteAllRelationships(reference, policy); - } - - Request.With(ExecutionConfiguration) - .Delete(policy.BaseEndpoint.AddPath(reference, policy)) - .WithExpectedStatusCodes(HttpStatusCode.NoContent, HttpStatusCode.Conflict) - .FailOnCondition(response => response.StatusCode == HttpStatusCode.Conflict) - .WithError(response => new Exception(string.Format( - "Unable to delete the node. The node may still have relationships. The response status was: {0} {1}", - (int)response.StatusCode, - response.ReasonPhrase))) - .Execute(); - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs - { - QueryText = "Delete " + reference.Id, - ResourcesReturned = 0, - TimeTaken = stopwatch.Elapsed - }); - } - - private void DeleteAllRelationships(NodeReference reference, IExecutionPolicy policy) - { - //TODO: Make this a dynamic endpoint resolution - 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) - { - Request.With(ExecutionConfiguration) - .Delete(new Uri(relationshipResource)) - .WithExpectedStatusCodes(HttpStatusCode.NoContent, HttpStatusCode.NotFound) - .Execute(); - } - } - - private static string GetLastPathSegment(string uri) - { - var path = new Uri(uri).AbsolutePath; - return path - .Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) - .LastOrDefault(); - } - - public ICypherFluentQuery Cypher - { - 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." - )] - public IGremlinClient Gremlin - { - get { return new GremlinClient(this); } - } - - public Version ServerVersion - { - get - { - CheckRoot(); - return RootApiResponse.Version; - } - } - - 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 void CheckTransactionEnvironmentWithPolicy(IExecutionPolicy policy) - { - bool inTransaction = InTransaction; - - if (transactionManager != null) - { - transactionManager.RegisterToTransactionIfNeeded(); - } - - if (inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Denied) - { - throw new InvalidOperationException("Cannot be done inside a transaction scope."); - } - - if (!inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Required) - { - throw new InvalidOperationException("Cannot be done outside a transaction scope."); - } - } - - public ITransaction BeginTransaction() - { - return BeginTransaction((IEnumerable) null); - } - - public ITransaction BeginTransaction(string bookmark) - { - return BeginTransaction(new List {bookmark}); - } - - public ITransaction BeginTransaction(IEnumerable bookmarks) - { - return BeginTransaction(TransactionScopeOption.Join, bookmarks); - } - - public ITransaction BeginTransaction(TransactionScopeOption scopeOption) - { - return BeginTransaction(scopeOption, (IEnumerable) null); - } - - public ITransaction BeginTransaction(TransactionScopeOption scopeOption, string bookmark) - { - return BeginTransaction(scopeOption, new List{bookmark}); - } - - public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmarks) - { - CheckRoot(); - if (transactionManager == null) - { - throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); - } - - return transactionManager.BeginTransaction(scopeOption, bookmarks); - } - - public ITransaction Transaction => transactionManager?.CurrentNonDtcTransaction; - - public bool InTransaction => transactionManager != null && transactionManager.InTransaction; - - public void EndTransaction() - { - if (transactionManager == null) - { - throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); - } - transactionManager.EndTransaction(); - } - - [Obsolete( - "Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher." - )] - public virtual string ExecuteScalarGremlin(string query, IDictionary parameters) - { - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Gremlin); - CheckTransactionEnvironmentWithPolicy(policy); - - if (RootApiResponse.Extensions.GremlinPlugin == null || - RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) - throw new Exception(GremlinPluginUnavailable); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - var response = Request.With(ExecutionConfiguration) - .Post(policy.BaseEndpoint) - .WithJsonContent(Serializer.Serialize(new GremlinApiQuery(query, parameters))) - .WithExpectedStatusCodes(HttpStatusCode.OK) - .Execute(string.Format("The query was: {0}", query)); - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs - { - QueryText = query, - ResourcesReturned = 1, - TimeTaken = stopwatch.Elapsed - }); - - return response.Content.ReadAsString(); - } - - [Obsolete( - "Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher." - )] - public virtual IEnumerable ExecuteGetAllProjectionsGremlin(IGremlinQuery query) - where TResult : new() - { - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Gremlin); - CheckTransactionEnvironmentWithPolicy(policy); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - var response = 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() }; - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs - { - QueryText = query.ToDebugQueryText(), - ResourcesReturned = responses.Count(), - TimeTaken = stopwatch.Elapsed - }); - - var results = GremlinTableCapResponse.TransferResponseToResult(responses, JsonConverters); - - return results; - } - - public CypherCapabilities CypherCapabilities => 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)] - [EditorBrowsable(EditorBrowsableState.Never)] - public virtual IEnumerable ExecuteGetCypherResults(CypherQuery query) - { - throw new NotImplementedException(); - } - - private Task PrepareCypherRequest(CypherQuery query, IExecutionPolicy policy) - { - if (InTransaction) - { - return transactionManager - .EnqueueCypherRequest(string.Format("The query was: {0}", query.QueryText), this, query) - .ContinueWith(responseTask => - { - // we need to check for errors returned by the transaction. The difference with a normal REST cypher - // query is that the errors are embedded within the result object, instead of having a 400 bad request - // status code. - var response = responseTask.Result; - var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, true); - return new CypherPartialResult - { - DeserializationContext = - deserializer.CheckForErrorsInTransactionResponse(response.Content.ReadAsString()), - ResponseObject = response - }; - }); - } - - int? maxExecutionTime = null; - NameValueCollection customHeaders = null; - if (query != null) - { - maxExecutionTime = query.MaxExecutionTime; - customHeaders = query.CustomHeaders; - } - - return Request.With(ExecutionConfiguration, customHeaders, maxExecutionTime) - .Post(policy.BaseEndpoint) - .WithJsonContent(policy.SerializeRequest(query)) - .WithExpectedStatusCodes(HttpStatusCode.OK) - .ExecuteAsync(response => new CypherPartialResult - { - ResponseObject = response.Result - }); - } - - IEnumerable IRawGraphClient.ExecuteGetCypherResults(CypherQuery query) - { - var task = ((IRawGraphClient)this).ExecuteGetCypherResultsAsync(query); - try - { - Task.WaitAll(task); - } - catch (AggregateException ex) - { - Exception unwrappedException; - if (ex.TryUnwrap(out unwrappedException)) - throw unwrappedException; - throw; - } - - return task.Result; - } - - async Task> IRawGraphClient.ExecuteGetCypherResultsAsync(CypherQuery query) - { - var context = ExecutionContext.Begin(this); - List results; - try - { - // the transaction handling is handled by a thread-local variable (ThreadStatic) so we need - // to know if we are in a transaction right now because our deserializer will run in another thread - bool inTransaction = InTransaction; - - var response = await PrepareCypherRequest(query, context.Policy).ConfigureAwait(false); - var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, - inTransaction); - if (inTransaction) - { - response.DeserializationContext.DeserializationContext.JsonContractResolver = - query.JsonContractResolver; - results = - deserializer.DeserializeFromTransactionPartialContext(response.DeserializationContext).ToList(); - } - else - { - results = deserializer.Deserialize(response.ResponseObject.Content.ReadAsString()).ToList(); - } - - } - catch (AggregateException aggregateException) - { - Exception unwrappedException; - if (aggregateException.TryUnwrap(out unwrappedException)) - { - context.Complete(query, unwrappedException); - } - else - { - context.Complete(query, aggregateException); - } - throw; - } - catch (Exception e) - { - context.Complete(query, e); - throw; - } - - context.Complete(query, results.Count()); - - return results; - } - - void IRawGraphClient.ExecuteCypher(CypherQuery query) - { - var context = ExecutionContext.Begin(this); - - var task = PrepareCypherRequest(query, context.Policy); - try - { - Task.WaitAll(task); - } - catch (AggregateException ex) - { - if (InTransaction) - ExecutionConfiguration.HasErrors = true; - - Exception unwrappedException; - if (ex.TryUnwrap(out unwrappedException)) - { - context.Complete(query, unwrappedException); - throw unwrappedException; - } - - context.Complete(query, ex); - throw; - } - - context.Policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(task.Result.ResponseObject), null); - - context.Complete(query); - } - - async Task IRawGraphClient.ExecuteCypherAsync(CypherQuery query) - { - var context = ExecutionContext.Begin(this); - - var response = await PrepareCypherRequest(query, context.Policy).ConfigureAwait(false); - context.Policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(response.ResponseObject), null); - - context.Complete(query); - } - - void IRawGraphClient.ExecuteMultipleCypherQueriesInTransaction(IEnumerable queries, NameValueCollection customHeaders) - { - var context = ExecutionContext.Begin(this); - - var queryList = queries.ToList(); - string queriesInText = string.Join(", ", queryList.Select(query => query.QueryText)); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - var response = Request.With(ExecutionConfiguration, customHeaders) - .Post(context.Policy.BaseEndpoint) - .WithJsonContent(SerializeAsJson(new CypherStatementList(queryList))) - .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created) - .Execute("Executing multiple queries: " + queriesInText); - - var transactionObject = transactionManager.CurrentNonDtcTransaction ?? - transactionManager.CurrentDtcTransaction; - - if (customHeaders != null && customHeaders.Count > 0) - { - transactionObject.CustomHeaders = customHeaders; - } - - context.Policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(response), transactionObject); - context.Complete(OperationCompleted != null ? string.Join(", ", queryList.Select(query => query.DebugQueryText)) : string.Empty); - context.Policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(response), transactionObject); - } - - [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) - where TData : class, new() - { - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Gremlin); - CheckTransactionEnvironmentWithPolicy(policy); - - if (RootApiResponse.Extensions.GremlinPlugin == null || - RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) - throw new Exception(GremlinPluginUnavailable); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - 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] - : response.Select(r => r.ToRelationshipInstance(this)).ToArray(); - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs - { - QueryText = query, - ResourcesReturned = relationships.Count(), - TimeTaken = stopwatch.Elapsed - }); - - 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) - { - 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) - { - 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." - )] - public virtual IEnumerable> ExecuteGetAllNodesGremlin(IGremlinQuery query) - { - CheckRoot(); - var policy = policyFactory.GetPolicy(PolicyType.Gremlin); - CheckTransactionEnvironmentWithPolicy(policy); - - if (RootApiResponse.Extensions.GremlinPlugin == null || - RootApiResponse.Extensions.GremlinPlugin.ExecuteScript == null) - throw new Exception(GremlinPluginUnavailable); - - var stopwatch = new Stopwatch(); - stopwatch.Start(); - - 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] - : response.Select(r => r.ToNode(this)).ToArray(); - - stopwatch.Stop(); - OnOperationCompleted(new OperationCompletedEventArgs - { - QueryText = query.ToDebugQueryText(), - ResourcesReturned = nodes.Count(), - TimeTaken = stopwatch.Elapsed - }); - - return nodes; - } - - private IExecutionPolicy GetPolicyForIndex(IndexFor indexFor) - { - switch (indexFor) - { - case IndexFor.Node: - return policyFactory.GetPolicy(PolicyType.NodeIndex); - case IndexFor.Relationship: - return policyFactory.GetPolicy(PolicyType.RelationshipIndex); - default: - throw new NotSupportedException(string.Format("GetIndexes does not support indexfor {0}", indexFor)); - } - } - - private Uri GetUriForIndexType(IndexFor indexFor) - { - var policy = GetPolicyForIndex(indexFor); - CheckTransactionEnvironmentWithPolicy(policy); - return policy.BaseEndpoint; - } - - public Dictionary GetIndexes(IndexFor indexFor) - { - CheckRoot(); - - var result = Request.With(ExecutionConfiguration) - .Get(GetUriForIndexType(indexFor)) - .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.NoContent) - .ParseAs>() - .FailOnCondition(response => response.StatusCode == HttpStatusCode.NoContent) - .WithDefault() - .Execute(); - - return result ?? new Dictionary(); - } - - public bool CheckIndexExists(string indexName, IndexFor indexFor) - { - CheckRoot(); - - var baseEndpoint = GetUriForIndexType(indexFor); - var response = Request.With(ExecutionConfiguration) - .Get(baseEndpoint.AddPath(indexName)) - .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.NotFound) - .Execute(); - - return response.StatusCode == HttpStatusCode.OK; - } - - private void CheckRoot() - { - if (RootApiResponse == null) - throw new InvalidOperationException( - "The graph client is not connected to the server. Call the Connect method first."); - } - - public void CreateIndex(string indexName, IndexConfiguration config, IndexFor indexFor) - { - CheckRoot(); - - var baseEndpoint = GetUriForIndexType(indexFor); - var createIndexApiRequest = new - { - name = indexName, - config - }; - - Request.With(ExecutionConfiguration) - .Post(baseEndpoint) - .WithJsonContent(SerializeAsJson(createIndexApiRequest)) - .WithExpectedStatusCodes(HttpStatusCode.Created) - .Execute(); - } - - public void ReIndex(NodeReference node, IEnumerable indexEntries) - { - var restPolicy = policyFactory.GetPolicy(PolicyType.Rest); - var entityUri = restPolicy.BaseEndpoint.AddPath(node, restPolicy); - var entityId = node.Id; - ReIndex(entityUri.ToString(), entityId, IndexFor.Node, indexEntries); - } - - public void ReIndex(RelationshipReference relationship, IEnumerable indexEntries) - { - var restPolicy = policyFactory.GetPolicy(PolicyType.Rest); - var entityUri = restPolicy.BaseEndpoint.AddPath(relationship, restPolicy); - var entityId = relationship.Id; - ReIndex(entityUri.ToString(), entityId, IndexFor.Relationship, indexEntries); - } - - private void ReIndex(string entityUri, long entityId, IndexFor indexFor, IEnumerable indexEntries, - IExecutionPolicy policy) - { - if (indexEntries == null) - throw new ArgumentNullException("indexEntries"); - - CheckRoot(); - CheckTransactionEnvironmentWithPolicy(policy); - - var updates = indexEntries - .SelectMany( - i => i.KeyValues, - (i, kv) => new { IndexName = i.Name, kv.Key, kv.Value }) - .Where(update => update.Value != null) - .ToList(); - - foreach (var indexName in updates.Select(u => u.IndexName).Distinct()) - DeleteIndexEntries(indexName, entityId, GetUriForIndexType(indexFor)); - - foreach (var update in updates) - AddIndexEntry(update.IndexName, update.Key, update.Value, entityUri, indexFor); - } - - public void ReIndex(string entityUri, long entityId, IndexFor indexFor, IEnumerable indexEntries) - { - ReIndex(entityUri, entityId, indexFor, indexEntries, GetPolicyForIndex(indexFor)); - } - - public void DeleteIndex(string indexName, IndexFor indexFor) - { - CheckRoot(); - var policy = GetPolicyForIndex(indexFor); - CheckTransactionEnvironmentWithPolicy(policy); - - Request.With(ExecutionConfiguration) - .Delete(policy.BaseEndpoint.AddPath(indexName)) - .WithExpectedStatusCodes(HttpStatusCode.NoContent) - .Execute(); - } - - public void DeleteIndexEntries(string indexName, NodeReference nodeReference) - { - DeleteIndexEntries(indexName, nodeReference.Id, GetUriForIndexType(IndexFor.Node)); - } - - public void DeleteIndexEntries(string indexName, RelationshipReference relationshipReference) - { - DeleteIndexEntries(indexName, relationshipReference.Id, GetUriForIndexType(IndexFor.Relationship)); - } - - private void DeleteIndexEntries(string indexName, long id, Uri indexUri) - { - var indexAddress = indexUri - .AddPath(Uri.EscapeDataString(indexName)) - .AddPath(Uri.EscapeDataString(id.ToString(CultureInfo.InvariantCulture))); - - Request.With(ExecutionConfiguration) - .Delete(indexAddress) - .WithExpectedStatusCodes(HttpStatusCode.NoContent) - .Execute( - string.Format("Deleting entries from index {0} for node {1}", indexName, id) - ); - } - - private void AddIndexEntry(string indexName, string indexKey, object indexValue, string address, - IndexFor indexFor) - { - var encodedIndexValue = EncodeIndexValue(indexValue); - if (string.IsNullOrWhiteSpace(encodedIndexValue)) - return; - - var indexAddress = BuildIndexAddress(indexName, indexFor); - - var indexEntry = new - { - key = indexKey, - value = encodedIndexValue, - uri = address - }; - - 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)); - } - - private string BuildRelativeIndexAddress(string indexName, IndexFor indexFor) - { - var baseUri = indexFor == IndexFor.Node - ? new UriBuilder() { Path = RootApiResponse.NodeIndex } - : new UriBuilder() { Path = RootApiResponse.RelationshipIndex }; - return baseUri.Uri.AddPath(Uri.EscapeDataString(indexName)).LocalPath; - } - - private Uri BuildIndexAddress(string indexName, IndexFor indexFor) - { - return GetUriForIndexType(indexFor).AddPath(Uri.EscapeDataString(indexName)); - } - - private static string EncodeIndexValue(object value) - { - string indexValue; - if (value is DateTimeOffset) - { - indexValue = ((DateTimeOffset)value).UtcTicks.ToString(CultureInfo.InvariantCulture); - } - else if (value is DateTime) - { - indexValue = ((DateTime)value).Ticks.ToString(CultureInfo.InvariantCulture); - } - else - { - indexValue = value.ToString(); - } - - if (string.IsNullOrWhiteSpace(indexValue) || - !indexValue.Any(char.IsLetterOrDigit)) - return string.Empty; - - return indexValue; - } - - //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." - )] - public IEnumerable> QueryIndex(string indexName, IndexFor indexFor, string query) - { - CheckRoot(); - var indexEndpoint = GetUriForIndexType(indexFor) - .AddPath(indexName) - .AddQuery("query=" + Uri.EscapeDataString(query)); - - 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) - { - return BuildLookupIndex(exactIndexName, indexFor, indexKey, id.ToString(CultureInfo.InvariantCulture)); - } - - public IEnumerable> LookupIndex(string exactIndexName, IndexFor indexFor, string indexKey, - int id) - { - return BuildLookupIndex(exactIndexName, indexFor, indexKey, id.ToString(CultureInfo.InvariantCulture)); - } - - private IEnumerable> BuildLookupIndex(string exactIndexName, IndexFor indexFor, - string indexKey, string id) - { - CheckRoot(); - var indexResource = GetUriForIndexType(indexFor) - .AddPath(exactIndexName) - .AddPath(indexKey) - .AddPath(id.ToString()); - - return Request.With(ExecutionConfiguration) - .Get(indexResource) - .WithExpectedStatusCodes(HttpStatusCode.OK) - .ParseAs>>() - .Execute() - .Select(query => query.ToNode(this)); - } - - [Obsolete( - "This method depends on Gremlin, which is being dropped in Neo4j 2.0. Find an alternate strategy for server lifetime management." - )] - public void ShutdownServer() - { - ExecuteScalarGremlin("g.getRawGraph().shutdown()", null); - } - - public event OperationCompletedEventHandler OperationCompleted; - - protected void OnOperationCompleted(OperationCompletedEventArgs args) - { - var eventInstance = OperationCompleted; - if (eventInstance != null) - eventInstance(this, args); - } - - private void EnsureNodeWasCreated(BatchStepResult createResponse) - { - if (createResponse.Status == HttpStatusCode.BadRequest && createResponse.Body != null) - { - var exceptionResponse = JsonConvert.DeserializeObject(createResponse.Body); - - if (exceptionResponse == null || string.IsNullOrEmpty(exceptionResponse.Message) || string.IsNullOrEmpty(exceptionResponse.Exception)) - throw new Exception(string.Format("Response from Neo4J: {0}", createResponse.Body)); - - throw new NeoException(exceptionResponse); - } - } - - protected virtual void Dispose(bool disposing) - { - if (!disposing) return; - - if (transactionManager != null) - transactionManager.Dispose(); - } - - public void Dispose() - { - Dispose(true); - GC.SuppressFinalize(this); - } - - public DefaultContractResolver JsonContractResolver { get; set; } - - public ITransactionManager TransactionManager - { - get { return transactionManager; } - } - - #region ExecutionContext class - private class ExecutionContext - { - private GraphClient owner; - - private readonly Stopwatch stopwatch; - - public IExecutionPolicy Policy { get; set; } - public static bool HasErrors { get; set; } - - private ExecutionContext() - { - stopwatch = Stopwatch.StartNew(); - } - - public static ExecutionContext Begin(GraphClient owner) - { - owner.CheckRoot(); - var policy = owner.policyFactory.GetPolicy(PolicyType.Cypher); - - owner.CheckTransactionEnvironmentWithPolicy(policy); - - var executionContext = new ExecutionContext - { - owner = owner, - Policy = policy - }; - - return executionContext; - } - - public void Complete(CypherQuery query) - { - // only parse the events when there's an event handler - Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, 0, null); - } - - public void Complete(CypherQuery query, int resultsCount) - { - // only parse the events when there's an event handler - Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, resultsCount, null, query.CustomHeaders); - } - - public void Complete(CypherQuery query, Exception exception) - { - // only parse the events when there's an event handler - Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, -1, exception); - } - - public void Complete(string queryText, int resultsCount = -1, Exception exception = null, NameValueCollection customHeaders = null, int? maxExecutionTime = null) - { - var args = new OperationCompletedEventArgs - { - QueryText = queryText, - ResourcesReturned = resultsCount, - TimeTaken = stopwatch.Elapsed, - Exception = exception, - CustomHeaders = customHeaders, - MaxExecutionTime = maxExecutionTime - }; - - owner.OnOperationCompleted(args); - } - } - - #endregion - } -} diff --git a/Neo4jClient.Shared/GraphClientExtensions.cs b/Neo4jClient.Shared/GraphClientExtensions.cs deleted file mode 100644 index a933d03ab..000000000 --- a/Neo4jClient.Shared/GraphClientExtensions.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System.Linq; - -namespace Neo4jClient -{ - public static class GraphClientExtensions - { - public static NodeReference Create(this IGraphClient graphClient, TNode node, params IRelationshipAllowingParticipantNode[] relationships) - where TNode : class - { - return graphClient.Create(node, relationships, Enumerable.Empty()); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/AggregateStep.cs b/Neo4jClient.Shared/Gremlin/AggregateStep.cs deleted file mode 100644 index f10b6760b..000000000 --- a/Neo4jClient.Shared/Gremlin/AggregateStep.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class AggregateStep - { - public static IGremlinNodeQuery AggregateV(this IGremlinQuery query, string variable) - { - var newQuery = query.AddBlock(string.Format(".aggregate({0})", variable)); - newQuery.QueryDeclarations.Add(string.Format("{0} = [];", variable)); - newQuery = newQuery.PrependVariablesToBlock(newQuery); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery AggregateE(this IGremlinQuery query, string variable) - { - var newQuery = query.AddBlock(string.Format(".aggregate({0})", variable)); - newQuery.QueryDeclarations.Add(string.Format("{0} = [];", variable)); - newQuery = newQuery.PrependVariablesToBlock(newQuery); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery AggregateE(this IGremlinQuery query, string variable) - where TData : class, new() - { - var newQuery = query.AddBlock(string.Format(".aggregate({0})", variable)); - newQuery.QueryDeclarations.Add(string.Format("{0} = [];", variable)); - newQuery = newQuery.PrependVariablesToBlock(newQuery); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/AsStep.cs b/Neo4jClient.Shared/Gremlin/AsStep.cs deleted file mode 100644 index 82bdbe700..000000000 --- a/Neo4jClient.Shared/Gremlin/AsStep.cs +++ /dev/null @@ -1,30 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class AsStep - { - public static IGremlinNodeQuery As(this IGremlinQuery query, string label) - { - var newQuery = query.AddBlock(".as({0})", label); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinNodeQuery As(this IGremlinNodeQuery query, string label) - { - var newQuery = query.AddBlock(".as({0})", label); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery As(this IGremlinRelationshipQuery query, string label) - { - var newQuery = query.AddBlock(".as({0})", label); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery As(this IGremlinRelationshipQuery query, string label) - where TData : class, new() - { - var newQuery = query.AddBlock(".as({0})", label); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} diff --git a/Neo4jClient.Shared/Gremlin/Back.cs b/Neo4jClient.Shared/Gremlin/Back.cs deleted file mode 100644 index f8dd86132..000000000 --- a/Neo4jClient.Shared/Gremlin/Back.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class Back - { - public static IGremlinNodeQuery BackV(this IGremlinQuery query, string label) - { - var newQuery = query.AddBlock(".back({0})", label); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery BackE(this IGremlinQuery query, string label) - { - var newQuery = query.AddBlock(".back({0})", label); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery BackE(this IGremlinQuery query, string label) - where TData : class, new() - { - var newQuery = query.AddBlock(".back({0})", label); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} diff --git a/Neo4jClient.Shared/Gremlin/BasicSteps.cs b/Neo4jClient.Shared/Gremlin/BasicSteps.cs deleted file mode 100644 index 85208f9ca..000000000 --- a/Neo4jClient.Shared/Gremlin/BasicSteps.cs +++ /dev/null @@ -1,353 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; - -namespace Neo4jClient.Gremlin -{ - public static class BasicSteps - { - const string bothV = ".bothV"; - const string outV = ".outV"; - const string inV = ".inV"; - const string bothE = ".bothE"; - const string outE = ".outE"; - const string inE = ".inE"; - const string both = ".both({0})"; - const string @out = ".out({0})"; - const string @in = ".in({0})"; - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery BothV(this IGremlinQuery query) - { - var newQuery = query.AddBlock(bothV); - return new GremlinNodeEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery BothV(this IGremlinQuery query, IEnumerable filters, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var newQuery = query.AddFilterBlock(bothV, filters, comparison); - return new GremlinNodeEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery BothV(this IGremlinQuery query, Expression> filter, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var filters = FilterFormatters.TranslateFilter(filter); - return query.BothV(filters, comparison); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery OutV(this IGremlinQuery query) - { - var newQuery = query.AddBlock(outV); - return new GremlinNodeEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery OutV(this IGremlinQuery query, IEnumerable filters, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var newQuery = query.AddFilterBlock(outV, filters, comparison); - return new GremlinNodeEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery OutV(this IGremlinQuery query, Expression> filter, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var filters = FilterFormatters.TranslateFilter(filter); - return query.OutV(filters, comparison); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery InV(this IGremlinQuery query) - { - var newQuery = query.AddBlock(inV); - return new GremlinNodeEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery InV(this IGremlinQuery query, IEnumerable filters, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var newQuery = query.AddFilterBlock(inV, filters, comparison); - return new GremlinNodeEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery InV(this IGremlinQuery query, Expression> filter, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var filters = FilterFormatters.TranslateFilter(filter); - return query.InV(filters, comparison); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery BothE(this IGremlinQuery query) - { - var newQuery = query.AddBlock(bothE); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery BothE(this IGremlinQuery query, string label) - { - var filter = GetFilter(label); - - var newQuery = query.AddFilterBlock(bothE, new[] { filter }, StringComparison.Ordinal); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery BothE(this IGremlinQuery query) - where TData : class, new() - { - var newQuery = query.AddBlock(bothE); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery BothE(this IGremlinQuery query, IEnumerable filters, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - where TData : class, new() - { - var newQuery = query.AddFilterBlock(bothE, filters, comparison); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery BothE(this IGremlinQuery query, Expression> filter, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - where TData : class, new() - { - var filters = FilterFormatters.TranslateFilter(filter); - return query.BothE(filters, comparison); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery BothE(this IGremlinQuery query, string label) - where TData : class, new() - { - return query.BothE(label, new Filter[0], StringComparison.Ordinal); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery BothE(this IGremlinQuery query, string label, IEnumerable filters, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - where TData : class, new() - { - var filter = GetFilter(label); - - filters = filters.Concat(new[] { filter }); - - var newQuery = query.AddFilterBlock(bothE, filters, comparison); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery BothE(this IGremlinQuery query, string label, Expression> filter, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - where TData : class, new() - { - var filters = FilterFormatters.TranslateFilter(filter); - return query.BothE(label, filters, comparison); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery OutE(this IGremlinQuery query) - { - var newQuery = query.AddBlock(outE); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery OutE(this IGremlinQuery query, string label) - { - var filter = GetFilter(label); - - var newQuery = query.AddFilterBlock(outE, new[] { filter }, StringComparison.Ordinal); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery OutE(this IGremlinQuery query) - where TData : class, new() - { - var newQuery = query.AddBlock(outE); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery OutE(this IGremlinQuery query, IEnumerable filters, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - where TData : class, new() - { - var newQuery = query.AddFilterBlock(outE, filters, comparison); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery OutE(this IGremlinQuery query, Expression> filter, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - where TData : class, new() - { - var filters = FilterFormatters.TranslateFilter(filter); - return query.OutE(filters, comparison); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery OutE(this IGremlinQuery query, string label) - where TData : class, new() - { - return query.OutE(label, new Filter[0], StringComparison.Ordinal); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery OutE(this IGremlinQuery query, string label, IEnumerable filters, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - where TData : class, new() - { - var filter = GetFilter(label); - - filters = filters.Concat(new[] { filter }); - - var newQuery = query.AddFilterBlock(outE, filters, comparison); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery OutE(this IGremlinQuery query, string label, Expression> filter, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - where TData : class, new() - { - var filters = FilterFormatters.TranslateFilter(filter); - return query.OutE(label, filters, comparison); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery InE(this IGremlinQuery query) - { - var newQuery = query.AddBlock(inE); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery InE(this IGremlinQuery query, string label) - { - var filter = GetFilter(label); - - var newQuery = query.AddFilterBlock(inE, new[] { filter }, StringComparison.Ordinal); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery In(this IGremlinQuery query, string label) - { - var newQuery = query.AddBlock(@in, label); - return new GremlinNodeEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery InE(this IGremlinQuery query) - where TData : class, new() - { - var newQuery = query.AddBlock(inE); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinRelationshipQuery InE(this IGremlinQuery query, string label) - where TData : class, new() - { - var filter = GetFilter(label); - - var newQuery = query.AddFilterBlock(inE, new[] { filter }, StringComparison.Ordinal); - return new GremlinRelationshipEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery Both(this IGremlinQuery query, string label) - { - var newQuery = query.AddBlock(both, label); - return new GremlinNodeEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery Both(this IGremlinQuery query, string label, IEnumerable filters, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var newQuery = query.AddBlock(both, label); - var filterQuery = newQuery.AddFilterBlock(string.Empty, filters, comparison); - return new GremlinNodeEnumerable(filterQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery Both(this IGremlinQuery query, string label, Expression> filter, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var newQuery = query.AddBlock(both, label); - var filters = FilterFormatters.TranslateFilter(filter); - var filterQuery = newQuery.AddFilterBlock(string.Empty, filters, comparison); - return new GremlinNodeEnumerable(filterQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery Out(this IGremlinQuery query, string label) - { - var newQuery = query.AddBlock(@out, label); - return new GremlinNodeEnumerable(newQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery Out(this IGremlinQuery query, string label, IEnumerable filters, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var newQuery = query.AddBlock(@out, label); - var filterQuery = newQuery.AddFilterBlock(string.Empty, filters, comparison); - return new GremlinNodeEnumerable(filterQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery Out(this IGremlinQuery query, string label, Expression> filter, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var newQuery = query.AddBlock(@out, label); - var filters = FilterFormatters.TranslateFilter(filter); - var filterQuery = newQuery.AddFilterBlock(string.Empty, filters, comparison); - return new GremlinNodeEnumerable(filterQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery In(this IGremlinQuery query, string label, IEnumerable filters, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var newQuery = query.AddBlock(@in, label); - var filterQuery = newQuery.AddFilterBlock(string.Empty, filters, comparison); - return new GremlinNodeEnumerable(filterQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static IGremlinNodeQuery In(this IGremlinQuery query, string label, Expression> filter, StringComparison comparison = StringComparison.OrdinalIgnoreCase) - { - var newQuery = query.AddBlock(@in, label); - var filters = FilterFormatters.TranslateFilter(filter); - var filterQuery = newQuery.AddFilterBlock(string.Empty, filters, comparison); - return new GremlinNodeEnumerable(filterQuery); - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public static int GremlinCount(this IGremlinQuery query) - { - if (query.Client == null) - throw new DetachedNodeException(); - - var queryText = string.Format("{0}.count()", query.QueryText); - var scalarResult = query.Client.ExecuteScalarGremlin(queryText, query.QueryParameters); - - int result; - if (!int.TryParse(scalarResult, out result)) - throw new Exception(string.Format( - "Query returned an unexpected value. Expected an integer. Received: {0}", - scalarResult)); - - return result; - } - - static Filter GetFilter(string label) - { - // TODO: This filter should always be case sensitive, irrespective of how the rest are compared - var filter = new Filter - { - ExpressionType = ExpressionType.Equal, - PropertyName = "label", - Value = label - }; - return filter; - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/CopySplitStep.cs b/Neo4jClient.Shared/Gremlin/CopySplitStep.cs deleted file mode 100644 index 147b4c862..000000000 --- a/Neo4jClient.Shared/Gremlin/CopySplitStep.cs +++ /dev/null @@ -1,29 +0,0 @@ -using System.Linq; - -namespace Neo4jClient.Gremlin -{ - public static class CopySplitStep - { - public static IGremlinRelationshipQuery CopySplitE(this IGremlinQuery baseQuery, params IGremlinQuery[] queries) - { - foreach (var query in queries.Where(query => query.GetType() == typeof(IdentityPipe))) - { - ((IdentityPipe) query).Client = query.Client; - } - - baseQuery = baseQuery.AddCopySplitBlock("._.copySplit({0}, {1})", queries); - return new GremlinRelationshipEnumerable(baseQuery); - } - - public static IGremlinNodeQuery CopySplitV(this IGremlinQuery baseQuery, params IGremlinQuery[] queries) - { - foreach (var query in queries.Where(query => query.GetType() == typeof(IdentityPipe))) - { - ((IdentityPipe)query).Client = query.Client; - } - - baseQuery = baseQuery.AddCopySplitBlock("._.copySplit({0}, {1})", queries); - return new GremlinNodeEnumerable(baseQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/EmitPropertyStep.cs b/Neo4jClient.Shared/Gremlin/EmitPropertyStep.cs deleted file mode 100644 index e0e46b47f..000000000 --- a/Neo4jClient.Shared/Gremlin/EmitPropertyStep.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class EmitPropertyStep - { - public static IGremlinNodeQuery EmitProperty(this IGremlinNodeQuery query, string propertyName) - { - var newQuery = query.AddBlock(string.Format(".{0}", propertyName)); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery EmitProperty(this IGremlinRelationshipQuery query, string propertyName) - { - var newQuery = query.AddBlock(string.Format(".{0}", propertyName)); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery EmitProperty(this IGremlinRelationshipQuery query, string propertyName) - where TData : class, new() - { - var newQuery = query.AddBlock(string.Format(".{0}", propertyName)); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/ExceptStep.cs b/Neo4jClient.Shared/Gremlin/ExceptStep.cs deleted file mode 100644 index 7ceaacf2b..000000000 --- a/Neo4jClient.Shared/Gremlin/ExceptStep.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class ExceptStep - { - public static IGremlinNodeQuery ExceptV(this IGremlinQuery query, string variable) - { - var newQuery = query.AddBlock(string.Format(".except({0})", variable)); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery ExceptE(this IGremlinQuery query, string variable) - { - var newQuery = query.AddBlock(string.Format(".except({0})", variable)); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery ExceptE(this IGremlinQuery query, string variable) - where TData : class, new() - { - var newQuery = query.AddBlock(string.Format(".except({0})", variable)); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/ExhaustMergeStep.cs b/Neo4jClient.Shared/Gremlin/ExhaustMergeStep.cs deleted file mode 100644 index 3cb3d4380..000000000 --- a/Neo4jClient.Shared/Gremlin/ExhaustMergeStep.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class ExhaustMergeStep - { - public static IGremlinNodeQuery ExhaustMerge(this IGremlinNodeQuery query) - { - var newQuery = query.AddBlock(".exhaustMerge"); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery ExhaustMerge(this IGremlinRelationshipQuery query) - { - var newQuery = query.AddBlock(".exhaustMerge"); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery ExhaustMerge(this IGremlinRelationshipQuery query) - where TData : class, new() - { - var newQuery = query.AddBlock(".exhaustMerge"); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/FairMergeStep.cs b/Neo4jClient.Shared/Gremlin/FairMergeStep.cs deleted file mode 100644 index 89099733d..000000000 --- a/Neo4jClient.Shared/Gremlin/FairMergeStep.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class FairMergeStep - { - public static IGremlinNodeQuery FairMerge(this IGremlinNodeQuery query) - { - var newQuery = query.AddBlock(".fairMerge"); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery FairMerge(this IGremlinRelationshipQuery query) - { - var newQuery = query.AddBlock(".fairMerge"); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery FairMerge(this IGremlinRelationshipQuery query) - where TData : class, new() - { - var newQuery = query.AddBlock(".fairMerge"); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/Filter.cs b/Neo4jClient.Shared/Gremlin/Filter.cs deleted file mode 100644 index 15aac669e..000000000 --- a/Neo4jClient.Shared/Gremlin/Filter.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Linq.Expressions; - -namespace Neo4jClient.Gremlin -{ - public struct Filter - { - public string PropertyName { get; set; } - public object Value { get; set; } - public ExpressionType? ExpressionType { get; set; } - } -} diff --git a/Neo4jClient.Shared/Gremlin/FilterFormatters.cs b/Neo4jClient.Shared/Gremlin/FilterFormatters.cs deleted file mode 100644 index 782ef0260..000000000 --- a/Neo4jClient.Shared/Gremlin/FilterFormatters.cs +++ /dev/null @@ -1,253 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Reflection; - -namespace Neo4jClient.Gremlin -{ - internal static class FilterFormatters - { - internal static FormattedFilter FormatGremlinFilter(IEnumerable filters, StringComparison comparison, IGremlinQuery queryThatTheFilterWillEventuallyBeAddedTo) - { - filters = filters - .Select(f => - { - if (f.Value != null && f.Value.GetType().GetTypeInfo().IsEnum) - f.Value = f.Value.ToString(); - return f; - }) - .ToArray(); - - if (filters.Any(f => !f.ExpressionType.HasValue)) - throw new ArgumentException("ExpressionType must not be null", "filters"); - - var typeFilterFormats = new List - { - new TypeFilter { Type = null, FilterFormat = "it[{0}] == null", ExpressionType = ExpressionType.Equal }, - new TypeFilter { Type = null, FilterFormat = "it[{0}] != null", ExpressionType = ExpressionType.NotEqual }, - new TypeFilter { Type = typeof(int), FilterFormat = "it[{0}] == {1}", ExpressionType = ExpressionType.Equal }, - new TypeFilter { Type = typeof(int), FilterFormat = "it[{0}] != {1}", ExpressionType = ExpressionType.NotEqual }, - new TypeFilter { Type = typeof(int), FilterFormat = "it[{0}] > {1}", ExpressionType = ExpressionType.GreaterThan}, - new TypeFilter { Type = typeof(int), FilterFormat = "it[{0}] < {1}", ExpressionType = ExpressionType.LessThan}, - new TypeFilter { Type = typeof(int), FilterFormat = "it[{0}] >= {1}", ExpressionType = ExpressionType.GreaterThanOrEqual}, - new TypeFilter { Type = typeof(int), FilterFormat = "it[{0}] <= {1}", ExpressionType = ExpressionType.LessThanOrEqual}, - new TypeFilter { Type = typeof(long), FilterFormat = "it[{0}] == {1}", ExpressionType = ExpressionType.Equal }, - new TypeFilter { Type = typeof(long), FilterFormat = "it[{0}] != {1}", ExpressionType = ExpressionType.NotEqual }, - new TypeFilter { Type = typeof(long), FilterFormat = "it[{0}] > {1}", ExpressionType = ExpressionType.GreaterThan}, - new TypeFilter { Type = typeof(long), FilterFormat = "it[{0}] < {1}", ExpressionType = ExpressionType.LessThan}, - new TypeFilter { Type = typeof(long), FilterFormat = "it[{0}] >= {1}", ExpressionType = ExpressionType.GreaterThanOrEqual}, - new TypeFilter { Type = typeof(long), FilterFormat = "it[{0}] <= {1}", ExpressionType = ExpressionType.LessThanOrEqual}, - new TypeFilter { Type = typeof(bool), FilterFormat = "it[{0}] == {1}", ExpressionType = ExpressionType.Equal }, - new TypeFilter { Type = typeof(bool), FilterFormat = "it[{0}] != {1}", ExpressionType = ExpressionType.NotEqual }, - new TypeFilter { Type = typeof(Guid), FilterFormat = "it[{0}] == {1}", ExpressionType = ExpressionType.Equal }, - new TypeFilter { Type = typeof(Guid), FilterFormat = "it[{0}] != {1}", ExpressionType = ExpressionType.NotEqual }, - }; - - const string filterSeparator = " && "; - const string concatenatedFiltersFormat = "{{ {0} }}"; - - switch (comparison) - { - case StringComparison.Ordinal: - typeFilterFormats.Add(new TypeFilter { Type = typeof(string), FilterFormat = "it[{0}].equals({1})", ExpressionType = ExpressionType.Equal }); - typeFilterFormats.Add(new TypeFilter { Type = typeof(string), FilterFormat = "!it[{0}].equals({1})", ExpressionType = ExpressionType.NotEqual }); - break; - case StringComparison.OrdinalIgnoreCase: - typeFilterFormats.Add(new TypeFilter { Type = typeof(string), FilterFormat = "it[{0}].equalsIgnoreCase({1})", ExpressionType = ExpressionType.Equal }); - typeFilterFormats.Add(new TypeFilter { Type = typeof(string), FilterFormat = "!it[{0}].equalsIgnoreCase({1})", ExpressionType = ExpressionType.NotEqual }); - break; - default: - throw new NotSupportedException(string.Format("Comparison mode {0} is not supported.", comparison)); - } - - var parameters = new Dictionary(); - var nextParameterIndex = queryThatTheFilterWillEventuallyBeAddedTo.QueryParameters.Count; - Func createParameter = value => - { - if (value == null) return "null"; - var paramName = string.Format("p{0}", nextParameterIndex); - parameters.Add(paramName, value); - nextParameterIndex++; - return paramName; - }; - - var expandedFilters = - from f in filters - let filterValueType = f.Value == null ? null : f.Value.GetType() - let typeFilter = typeFilterFormats.SingleOrDefault(tf => tf.Type == filterValueType && tf.ExpressionType == f.ExpressionType) - let isFilterSupported = typeFilter != null - let filterFormat = isFilterSupported ? typeFilter.FilterFormat : null - select new - { - f.PropertyName, - PropertyNameParam = createParameter(f.PropertyName), - ValueParam = createParameter(f.Value), - f.ExpressionType, - IsFilterSupported = isFilterSupported, - ValueType = filterValueType, - Format = filterFormat - }; - - expandedFilters = expandedFilters.ToArray(); - - var unsupportedFilters = expandedFilters - .Where(f => !f.IsFilterSupported) - .Select(f => f.ValueType == null - ? string.Format("{0} with null value and expression {1}", f.PropertyName, f.ExpressionType) - : string.Format("{0} of type {1}, with expression {2}", f.PropertyName, f.ValueType.FullName, f.ExpressionType)) - .ToArray(); - if (unsupportedFilters.Any()) - throw new NotSupportedException(string.Format( - "One or more of the supplied filters is of an unsupported type or expression. Unsupported filters were: {0}", - string.Join(", ", unsupportedFilters))); - - var formattedFilters = expandedFilters - .Select(f => string.Format(f.Format, f.PropertyNameParam, f.ValueParam)) - .ToArray(); - var concatenatedFilters = string.Join(filterSeparator, formattedFilters); - if (!string.IsNullOrWhiteSpace(concatenatedFilters)) - concatenatedFilters = string.Format(concatenatedFiltersFormat, concatenatedFilters); - - var filter = string.IsNullOrWhiteSpace(concatenatedFilters) ? string.Empty : ".filter"; - - return new FormattedFilter - { - FilterText = filter + concatenatedFilters, - FilterParameters = parameters - }; - } - - internal static IEnumerable TranslateFilter(Expression> filter) - { - if (filter.Body.Type == typeof(bool)) - { - if (filter.Body.NodeType == ExpressionType.MemberAccess) - { - var expression = filter.Body as MemberExpression; - - if (expression != null && - (expression.Member is PropertyInfo))// && expression.Member.MemberType == MemberTypes.Property)) - { - var newFilter = new Filter - { - ExpressionType = ExpressionType.Equal, - PropertyName = expression.Member.Name, - Value = true - }; - - return new List { newFilter }; - } - } - - if (filter.Body.NodeType == ExpressionType.Not) - { - var expression = filter.Body as UnaryExpression; - - if (expression != null) - { - var operand = expression.Operand as MemberExpression; - if (operand != null) - { - var newFilter = new Filter - { - ExpressionType = ExpressionType.Equal, - PropertyName = operand.Member.Name, - Value = false - }; - - return new List { newFilter }; - } - } - } - } - - var binaryExpression = filter.Body as BinaryExpression; - if (binaryExpression == null) - throw new NotSupportedException("Only binary expressions are supported at this time."); - return TranslateFilterInternal(binaryExpression); - } - - static IEnumerable TranslateFilterInternal(BinaryExpression binaryExpression) - { - switch (binaryExpression.NodeType) - { - case ExpressionType.AndAlso: - case ExpressionType.And: - var leftBinaryExpression = binaryExpression.Left as BinaryExpression; - if (leftBinaryExpression == null) - throw new NotSupportedException(string.Format( - "This expression is not a binary expression: {0}", binaryExpression.Left)); - var firstFilter = TranslateFilterInternal(leftBinaryExpression); - - var rightBinaryExpression = binaryExpression.Right as BinaryExpression; - if (rightBinaryExpression == null) - throw new NotSupportedException(string.Format( - "This expression is not a binary expression: {0}", binaryExpression.Right)); - var secondFilter = TranslateFilterInternal(rightBinaryExpression); - - return firstFilter.Concat(secondFilter).ToArray(); - - case ExpressionType.Or: - case ExpressionType.OrElse: - throw new NotSupportedException(string.Format( - "Oprerator {0} is not yet supported. There's no reason why it can't be; we just haven't done it yet. Feel free to send a pull request if you need this feature. It was used in expression: {1}", - binaryExpression.NodeType, - binaryExpression)); - } - - var key = ParseKeyFromExpression(binaryExpression.Left); - var constantValue = ParseValueFromExpression(binaryExpression.Right); - - var underlyingPropertyType = key.PropertyType; - underlyingPropertyType = Nullable.GetUnderlyingType(key.PropertyType) ?? underlyingPropertyType; - - var convertedValue = underlyingPropertyType.GetTypeInfo().IsEnum - ? Enum.Parse(underlyingPropertyType, constantValue.ToString()) - : constantValue; - - return new[] - { - new Filter - { - PropertyName = key.Name, - Value = convertedValue, - ExpressionType = binaryExpression.NodeType - } - }; - } - - internal static ExpressionKey ParseKeyFromExpression(Expression expression) - { - var unaryExpression = expression as UnaryExpression; - if (unaryExpression != null && - unaryExpression.NodeType == ExpressionType.Convert) - expression = unaryExpression.Operand; - - var memberExpression = expression as MemberExpression; - if (memberExpression != null && - memberExpression.Member is PropertyInfo - //&& memberExpression.Member.MemberType == MemberTypes.Property) - ) - return new ExpressionKey - { - Name = memberExpression.Member.Name, - PropertyType = ((PropertyInfo)memberExpression.Member).PropertyType - }; - - throw new NotSupportedException("Only property accessors are supported for the left-hand side of the expression at this time."); - } - - static object ParseValueFromExpression(Expression expression) - { - var lambdaExpression = Expression.Lambda(expression); - return lambdaExpression.Compile().DynamicInvoke(); - } - - internal class ExpressionKey - { - public string Name { get; set; } - public Type PropertyType { get; set; } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/FormattedFilter.cs b/Neo4jClient.Shared/Gremlin/FormattedFilter.cs deleted file mode 100644 index cdda5dcd7..000000000 --- a/Neo4jClient.Shared/Gremlin/FormattedFilter.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - internal struct FormattedFilter - { - public string FilterText { get; set; } - public IDictionary FilterParameters { get; set; } - } -} diff --git a/Neo4jClient.Shared/Gremlin/GremlinClient.cs b/Neo4jClient.Shared/Gremlin/GremlinClient.cs deleted file mode 100644 index 95f9d1e78..000000000 --- a/Neo4jClient.Shared/Gremlin/GremlinClient.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - internal class GremlinClient : IGremlinClient - { - readonly IGraphClient client; - - // This is internal for now because we ultimately want to remove the dependency - // on IGraphClient once we move the Execute* methods to here - internal GremlinClient(IGraphClient client) - { - this.client = client; - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public IGremlinQuery V - { - get { return new GremlinQuery(client, "g.V", new Dictionary(), new List()); } - } - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public IGremlinQuery E - { - get { return new GremlinQuery(client, "g.E", new Dictionary(), new List()); } - } - } -} diff --git a/Neo4jClient.Shared/Gremlin/GremlinDistinctStep.cs b/Neo4jClient.Shared/Gremlin/GremlinDistinctStep.cs deleted file mode 100644 index 9487385ad..000000000 --- a/Neo4jClient.Shared/Gremlin/GremlinDistinctStep.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class GremlinDistinctStep - { - public static IGremlinNodeQuery GremlinDistinct(this IGremlinNodeQuery query) - { - var newQuery = query.AddBlock(".dedup()"); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery GremlinDistinct(this IGremlinRelationshipQuery query) - { - var newQuery = query.AddBlock(".dedup()"); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery GremlinDistinct(this IGremlinRelationshipQuery query) - where TData : class, new() - { - var newQuery = query.AddBlock(".dedup()"); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/GremlinIterator.cs b/Neo4jClient.Shared/Gremlin/GremlinIterator.cs deleted file mode 100644 index 44f04a80a..000000000 --- a/Neo4jClient.Shared/Gremlin/GremlinIterator.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - public class GremlinIterator: IGremlinQuery - { - readonly IDictionary queryParameters = new Dictionary(); - readonly IList queryDeclarations = new List(); - - public IGraphClient Client { get; set; } - - public string QueryText - { - get { return "it"; } - } - - public IDictionary QueryParameters - { - get { return queryParameters; } - } - - public IList QueryDeclarations - { - get { return queryDeclarations; } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/GremlinNodeEnumerable.cs b/Neo4jClient.Shared/Gremlin/GremlinNodeEnumerable.cs deleted file mode 100644 index dcbce5e50..000000000 --- a/Neo4jClient.Shared/Gremlin/GremlinNodeEnumerable.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Neo4jClient.Gremlin -{ - [DebuggerDisplay("{DebugQueryText}")] - internal class GremlinNodeEnumerable : IGremlinNodeQuery - { - readonly IGraphClient client; - readonly string queryText; - readonly IDictionary queryParameters; - readonly IList queryDeclarations; - - public GremlinNodeEnumerable(IGremlinQuery query) - { - client = query.Client; - queryText = query.QueryText; - queryParameters = query.QueryParameters; - queryDeclarations = query.QueryDeclarations; - } - - public string DebugQueryText - { - get { return this.ToDebugQueryText(); } - } - - IEnumerator> IEnumerable>.GetEnumerator() - { - if (client == null) throw new DetachedNodeException(); - return new GremlinPagedEnumerator>( - client.ExecuteGetAllNodesGremlin, - new GremlinQuery(client, queryText, queryParameters, queryDeclarations)); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)this).GetEnumerator(); - } - - IGraphClient IAttachedReference.Client - { - get { return client; } - } - - string IGremlinQuery.QueryText - { - get { return queryText; } - } - - IDictionary IGremlinQuery.QueryParameters - { - get { return queryParameters; } - } - - IList IGremlinQuery.QueryDeclarations - { - get { return queryDeclarations; } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/GremlinPagedEnumerator.cs b/Neo4jClient.Shared/Gremlin/GremlinPagedEnumerator.cs deleted file mode 100644 index 0f621ad74..000000000 --- a/Neo4jClient.Shared/Gremlin/GremlinPagedEnumerator.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Neo4jClient.Gremlin -{ - internal class GremlinPagedEnumerator : IEnumerator - { - readonly Func> pageLoadCallback; - readonly IGremlinQuery query; - readonly int pageSize; - - int currentPageIndex = -1; - int currentRowIndex = -1; - TResult[] currentPageData; - - public GremlinPagedEnumerator( - Func> pageLoadCallback, - IGremlinQuery query, - int pageSize = 100) - { - this.pageLoadCallback = pageLoadCallback; - this.query = query; - this.pageSize = pageSize; - } - - public bool MoveNext() - { - var hasAPageLoaded = currentPageIndex != -1; - var curentPageIsPartialPage = currentPageData != null && currentPageData.Count() < pageSize; - var currentRecordIsLastOneOnPage = currentPageData != null && currentRowIndex == currentPageData.Count() - 1; - - if (hasAPageLoaded && curentPageIsPartialPage && currentRecordIsLastOneOnPage) - return false; - - if (!hasAPageLoaded || currentRecordIsLastOneOnPage) - LoadNextPage(); - - if (currentPageData == null) - throw new InvalidOperationException("CurrentPageData is null even though we have a page index."); - - currentRowIndex++; - return currentRowIndex < currentPageData.Count(); - } - - void LoadNextPage() - { - currentPageIndex++; - currentRowIndex = -1; - var drop = currentPageIndex * pageSize; - var pageQuery = query.AddBlock(".drop({0}).take({1})._()", drop, pageSize); - currentPageData = pageLoadCallback(pageQuery).ToArray(); - } - - public void Reset() - { - // MSDN says this method only exists for COM interop and that we - // don't need to bother with it otherwise http://l.tath.am/ohpNvS - throw new NotSupportedException(); - } - - public TResult Current - { - get { return currentPageData[currentRowIndex]; } - } - - object IEnumerator.Current - { - get { return Current; } - } - - public void Dispose() - { - } - } -} diff --git a/Neo4jClient.Shared/Gremlin/GremlinProjectionEnumerable.cs b/Neo4jClient.Shared/Gremlin/GremlinProjectionEnumerable.cs deleted file mode 100644 index f5dc66bb9..000000000 --- a/Neo4jClient.Shared/Gremlin/GremlinProjectionEnumerable.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Neo4jClient.Gremlin -{ - [DebuggerDisplay("{DebugQueryText}")] - internal class GremlinProjectionEnumerable :IGremlinQuery, IEnumerable where TResult : new() - { - readonly IGraphClient client; - readonly string queryText; - readonly IDictionary queryParameters; - readonly IList queryDeclarations; - - public GremlinProjectionEnumerable(IGremlinQuery query) - { - queryDeclarations = query.QueryDeclarations; - client = query.Client; - queryText = query.QueryText; - queryParameters = query.QueryParameters; - } - - public string DebugQueryText - { - get { return this.ToDebugQueryText(); } - } - - IEnumerator IEnumerable.GetEnumerator() - { - if (client == null) throw new DetachedNodeException(); - return client.ExecuteGetAllProjectionsGremlin(new GremlinQuery(client, queryText, queryParameters, queryDeclarations)) - .GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)this).GetEnumerator(); - } - - IGraphClient IAttachedReference.Client - { - get { return client; } - } - - string IGremlinQuery.QueryText - { - get { return queryText; } - } - - IDictionary IGremlinQuery.QueryParameters - { - get { return queryParameters; } - } - - IList IGremlinQuery.QueryDeclarations - { - get { return queryDeclarations; } - } - } -} diff --git a/Neo4jClient.Shared/Gremlin/GremlinQuery.cs b/Neo4jClient.Shared/Gremlin/GremlinQuery.cs deleted file mode 100644 index 57e70ea4a..000000000 --- a/Neo4jClient.Shared/Gremlin/GremlinQuery.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - internal class GremlinQuery : IGremlinQuery - { - readonly IGraphClient client; - readonly string queryText; - readonly IDictionary queryParameters; - readonly IList queryDeclarations; - - public GremlinQuery(IGraphClient client, string queryText, IDictionary queryParameters, IList declarations ) - { - this.client = client; - this.queryText = queryText; - this.queryParameters = queryParameters; - this.queryDeclarations = declarations; - } - - public IGraphClient Client - { - get { return client; } - } - - public string QueryText - { - get { return queryText; } - } - - public IDictionary QueryParameters - { - get { return queryParameters; } - } - - public IList QueryDeclarations - { - get { return queryDeclarations; } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/GremlinQueryExtensions.cs b/Neo4jClient.Shared/Gremlin/GremlinQueryExtensions.cs deleted file mode 100644 index d84f3cd64..000000000 --- a/Neo4jClient.Shared/Gremlin/GremlinQueryExtensions.cs +++ /dev/null @@ -1,133 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.RegularExpressions; - -namespace Neo4jClient.Gremlin -{ - static class GremlinQueryExtensions - { - static readonly Regex ParameterReferenceRegex = new Regex(@"(?<=\W)p\d+(?!\w)"); - - public static IGremlinQuery PrependVariablesToBlock(this IGremlinQuery baseQuery, IGremlinQuery query) - { - var declarations = query - .QueryDeclarations - .Aggregate(string.Empty, (current, declaration) => !baseQuery.QueryText.Contains(declaration) ? declaration + current : current); - return new GremlinQuery(baseQuery.Client, declarations + baseQuery.QueryText, baseQuery.QueryParameters, query.QueryDeclarations); - } - - public static IGremlinQuery AddBlock(this IGremlinQuery baseQuery, string text, params object[] parameters) - { - var paramsDictionary = new Dictionary(baseQuery.QueryParameters); - var nextParameterIndex = baseQuery.QueryParameters.Count; - var paramNames = new List(); - foreach (var paramValue in parameters) - { - var paramName = string.Format("p{0}", nextParameterIndex); - paramNames.Add(paramName); - paramsDictionary.Add(paramName, paramValue); - nextParameterIndex++; - } - - var textWithParamNames = parameters.Any() - ? string.Format(text, paramNames.ToArray()) - : text; - - return new GremlinQuery(baseQuery.Client, baseQuery.QueryText + textWithParamNames, paramsDictionary,baseQuery.QueryDeclarations); - } - - public static IGremlinQuery AddCopySplitBlock(this IGremlinQuery baseQuery, string text, IGremlinQuery[] queries) - { - var declarations = new List(); - var rootQuery = baseQuery.QueryText; - var inlineQueries = new List(); - - var paramsDictionary = new Dictionary(baseQuery.QueryParameters); - var nextParameterIndex = baseQuery.QueryParameters.Count; - - foreach (var query in queries) - { - var modifiedQueryText = RebuildParametersAndDeclarations(query, paramsDictionary, declarations, ref nextParameterIndex, ref rootQuery); - inlineQueries.Add(modifiedQueryText); - } - - var splitBlockQueries = string.Format(text, inlineQueries.ToArray()); - - return new GremlinQuery(baseQuery.Client, rootQuery + splitBlockQueries, paramsDictionary, declarations); - } - - public static IGremlinQuery AddIfThenElseBlock(this IGremlinQuery baseQuery, string ifThenElseText, IGremlinQuery ifExpression, IGremlinQuery ifThen, IGremlinQuery ifElse) - { - var declarations = new List(); - var rootQuery = baseQuery.QueryText; - var paramsDictionary = new Dictionary(baseQuery.QueryParameters); - var nextParameterIndex = baseQuery.QueryParameters.Count; - - var modifiedQueryTextifExpression = RebuildParametersAndDeclarations(ifExpression, paramsDictionary, declarations, ref nextParameterIndex, ref rootQuery); - var modifiedQueryTextifThen = RebuildParametersAndDeclarations(ifThen, paramsDictionary, declarations, ref nextParameterIndex, ref rootQuery); - var modifiedQueryTextifElse = RebuildParametersAndDeclarations(ifElse, paramsDictionary, declarations, ref nextParameterIndex, ref rootQuery); - - var newQueryText = string.Format(ifThenElseText, modifiedQueryTextifExpression, modifiedQueryTextifThen, modifiedQueryTextifElse); - - return new GremlinQuery(baseQuery.Client, rootQuery + newQueryText, paramsDictionary, declarations); - } - - public static IGremlinQuery AddFilterBlock(this IGremlinQuery baseQuery, string text, IEnumerable filters, StringComparison comparison) - { - var formattedFilter = FilterFormatters.FormatGremlinFilter(filters, comparison, baseQuery); - - var newQueryText = baseQuery.QueryText + text + formattedFilter.FilterText; - - var newParams = new Dictionary(baseQuery.QueryParameters); - foreach (var key in formattedFilter.FilterParameters.Keys) - newParams.Add(key, formattedFilter.FilterParameters[key]); - - return new GremlinQuery(baseQuery.Client, newQueryText, newParams, baseQuery.QueryDeclarations); - } - - public static string ToDebugQueryText(this IGremlinQuery query) - { - var text = query.QueryText; - if (query.QueryParameters == null) return text; - foreach (var key in query.QueryParameters.Keys.Reverse()) - { - text = text.Replace(key, string.Format("'{0}'", query.QueryParameters[key])); - } - return text; - } - - static string RebuildParametersAndDeclarations(IGremlinQuery query, Dictionary paramsDictionary, - List declarations, ref int nextParamaterIndex, ref string rootQuery) - { - var updatedIndex = nextParamaterIndex; - var paramNames = new List(); - - declarations.AddRange(query.QueryDeclarations); - var modifiedQueryText = ParameterReferenceRegex.Replace( - query.QueryText, - m => - { - var parameterIndex = updatedIndex; - updatedIndex++; - - var oldParamKey = m.Value; - var newParamKey = string.Format("p{0}", parameterIndex); - - paramNames.Add(newParamKey); - paramsDictionary.Add(newParamKey, query.QueryParameters[oldParamKey]); - - return newParamKey; - }); - - nextParamaterIndex = updatedIndex; - - foreach (var declareStatement in query.QueryDeclarations) - { - rootQuery = declareStatement + rootQuery; - modifiedQueryText = modifiedQueryText.Replace(declareStatement, string.Empty); - } - return modifiedQueryText; - } - } -} diff --git a/Neo4jClient.Shared/Gremlin/GremlinRelationshipEnumerable.cs b/Neo4jClient.Shared/Gremlin/GremlinRelationshipEnumerable.cs deleted file mode 100644 index 0013269de..000000000 --- a/Neo4jClient.Shared/Gremlin/GremlinRelationshipEnumerable.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Neo4jClient.Gremlin -{ - [DebuggerDisplay("{DebugQueryText}")] - internal class GremlinRelationshipEnumerable : IGremlinRelationshipQuery - { - readonly IGraphClient client; - readonly string queryText; - readonly IDictionary queryParameters; - readonly IList queryDeclaration; - - public GremlinRelationshipEnumerable(IGremlinQuery query) - { - client = query.Client; - queryText = query.QueryText; - queryParameters = query.QueryParameters; - queryDeclaration = query.QueryDeclarations; - } - - public string DebugQueryText - { - get { return this.ToDebugQueryText(); } - } - - IEnumerator IEnumerable.GetEnumerator() - { - if (client == null) throw new DetachedNodeException(); - var results = client.ExecuteGetAllRelationshipsGremlin(queryText, queryParameters); - return results.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return ((IEnumerable)this).GetEnumerator(); - } - - IGraphClient IAttachedReference.Client - { - get { return client; } - } - - string IGremlinQuery.QueryText - { - get { return queryText; } - } - - IDictionary IGremlinQuery.QueryParameters - { - get { return queryParameters; } - } - - IList IGremlinQuery.QueryDeclarations - { - get { return queryDeclaration; } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/GremlinRelationshipEnumerable`TData.cs b/Neo4jClient.Shared/Gremlin/GremlinRelationshipEnumerable`TData.cs deleted file mode 100644 index 5c5292753..000000000 --- a/Neo4jClient.Shared/Gremlin/GremlinRelationshipEnumerable`TData.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -namespace Neo4jClient.Gremlin -{ - [DebuggerDisplay("{DebugQueryText}")] - internal class GremlinRelationshipEnumerable - : IGremlinRelationshipQuery - where TData : class, new() - { - readonly IGraphClient client; - readonly string queryText; - readonly IDictionary queryParameters; - readonly IList queryDeclarations; - - public GremlinRelationshipEnumerable(IGremlinQuery query) - { - client = query.Client; - queryText = query.QueryText; - queryParameters = query.QueryParameters; - queryDeclarations = query.QueryDeclarations; - } - - public string DebugQueryText - { - get - { - var text = queryText; - foreach (var key in queryParameters.Keys.Reverse()) - { - text = text.Replace(key, string.Format("'{0}'", queryParameters[key])); - } - return text; - } - } - - IEnumerator> GetEnumeratorInternal() - { - if (client == null) throw new DetachedNodeException(); - var results = client.ExecuteGetAllRelationshipsGremlin(queryText, queryParameters); - return results.GetEnumerator(); - } - - IEnumerator> IEnumerable>.GetEnumerator() - { - return GetEnumeratorInternal(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumeratorInternal(); - } - - IGraphClient IAttachedReference.Client - { - get { return client; } - } - - string IGremlinQuery.QueryText - { - get { return queryText; } - } - - IDictionary IGremlinQuery.QueryParameters - { - get { return queryParameters; } - } - - IList IGremlinQuery.QueryDeclarations - { - get { return queryDeclarations; } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/HasNextStep.cs b/Neo4jClient.Shared/Gremlin/HasNextStep.cs deleted file mode 100644 index 3f53a1979..000000000 --- a/Neo4jClient.Shared/Gremlin/HasNextStep.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class HasNextStep - { - public static IGremlinNodeQuery GremlinHasNext(this IGremlinNodeQuery query) - { - var newQuery = query.AddBlock(".hasNext()"); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery GremlinHasNext(this IGremlinRelationshipQuery query) - { - var newQuery = query.AddBlock(".hasNext()"); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery GremlinHasNext(this IGremlinRelationshipQuery query) - where TData : class, new() - { - var newQuery = query.AddBlock(".hasNext()"); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/IGremlinClient.cs b/Neo4jClient.Shared/Gremlin/IGremlinClient.cs deleted file mode 100644 index 792592e0d..000000000 --- a/Neo4jClient.Shared/Gremlin/IGremlinClient.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; - -namespace Neo4jClient.Gremlin -{ - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - public interface IGremlinClient - { - IGremlinQuery V { get; } - IGremlinQuery E { get; } - } -} diff --git a/Neo4jClient.Shared/Gremlin/IGremlinNodeQuery.cs b/Neo4jClient.Shared/Gremlin/IGremlinNodeQuery.cs deleted file mode 100644 index 9c6950ef5..000000000 --- a/Neo4jClient.Shared/Gremlin/IGremlinNodeQuery.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - public interface IGremlinNodeQuery : IEnumerable>, IGremlinQuery - { - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/IGremlinQuery.cs b/Neo4jClient.Shared/Gremlin/IGremlinQuery.cs deleted file mode 100644 index c58c01c1a..000000000 --- a/Neo4jClient.Shared/Gremlin/IGremlinQuery.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - public interface IGremlinQuery : IAttachedReference - { - string QueryText { get;} - IDictionary QueryParameters { get; } - IList QueryDeclarations { get; } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/IGremlinRelationshipQuery.cs b/Neo4jClient.Shared/Gremlin/IGremlinRelationshipQuery.cs deleted file mode 100644 index af5ac3801..000000000 --- a/Neo4jClient.Shared/Gremlin/IGremlinRelationshipQuery.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - public interface IGremlinRelationshipQuery : IEnumerable, IGremlinQuery - { - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/IGremlinRelationshipQuery`TData.cs b/Neo4jClient.Shared/Gremlin/IGremlinRelationshipQuery`TData.cs deleted file mode 100644 index bb965193d..000000000 --- a/Neo4jClient.Shared/Gremlin/IGremlinRelationshipQuery`TData.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - public interface IGremlinRelationshipQuery - : IEnumerable>, IGremlinQuery - where TData : class, new() - { - } -} diff --git a/Neo4jClient.Shared/Gremlin/IdentityPipe.cs b/Neo4jClient.Shared/Gremlin/IdentityPipe.cs deleted file mode 100644 index 1f92ab8d0..000000000 --- a/Neo4jClient.Shared/Gremlin/IdentityPipe.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - public class IdentityPipe : IGremlinQuery - { - readonly IDictionary queryParameters = new Dictionary(); - readonly IList queryDeclarations = new List(); - - public IGraphClient Client { get; set; } - - public string QueryText - { - get { return "_()"; } - } - - public IDictionary QueryParameters - { - get { return queryParameters; } - } - - public IList QueryDeclarations - { - get { return queryDeclarations; } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/IfThenElseStep.cs b/Neo4jClient.Shared/Gremlin/IfThenElseStep.cs deleted file mode 100644 index 00c688bad..000000000 --- a/Neo4jClient.Shared/Gremlin/IfThenElseStep.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - public static class IfThenElseStep - { - public static IGremlinRelationshipQuery IfThenElse(this IGremlinQuery baseQuery, IGremlinQuery ifExpression, IGremlinQuery ifThen, IGremlinQuery ifElse) - { - ifThen = ifThen ?? new GremlinQuery(baseQuery.Client, string.Empty, new Dictionary(), new List()); - ifElse = ifElse ?? new GremlinQuery(baseQuery.Client, string.Empty, new Dictionary(), new List()); - - if (ifExpression.GetType() == typeof(IdentityPipe)) - ((IdentityPipe)ifExpression).Client = ifExpression.Client; - - if (ifExpression.GetType() == typeof(GremlinIterator)) - ((GremlinIterator)ifExpression).Client = ifExpression.Client; - - if (ifThen.GetType() == typeof(IdentityPipe)) - ((IdentityPipe)ifThen).Client = ifThen.Client; - - if (ifThen.GetType() == typeof(GremlinIterator)) - ((GremlinIterator)ifThen).Client = ifThen.Client; - - if (ifElse.GetType() == typeof(IdentityPipe)) - ((IdentityPipe)ifElse).Client = ifElse.Client; - - if (ifElse.GetType() == typeof(GremlinIterator)) - ((GremlinIterator)ifElse).Client = ifElse.Client; - - baseQuery = baseQuery.AddIfThenElseBlock(".ifThenElse{{{0}}}{{{1}}}{{{2}}}", ifExpression, ifThen, ifElse); - return new GremlinRelationshipEnumerable(baseQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/IteratorSteps.cs b/Neo4jClient.Shared/Gremlin/IteratorSteps.cs deleted file mode 100644 index c2eab9334..000000000 --- a/Neo4jClient.Shared/Gremlin/IteratorSteps.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class Iterator - { - public static IGremlinNodeQuery GremlinSkip(this IGremlinNodeQuery query, int count) - { - var newQuery = query.AddBlock(".drop({0})._()", count); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery GremlinSkip(this IGremlinRelationshipQuery query, int count) - { - var newQuery = query.AddBlock(".drop({0})._()", count); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery GremlinSkip(this IGremlinRelationshipQuery query, int count) - where TData : class, new() - { - var newQuery = query.AddBlock(".drop({0})._()", count); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinNodeQuery GremlinTake(this IGremlinNodeQuery query, int count) - { - var newQuery = query.AddBlock(".take({0})._()", count); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery GremlinTake(this IGremlinRelationshipQuery query, int count) - { - var newQuery = query.AddBlock(".take({0})._()", count); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery GremlinTake(this IGremlinRelationshipQuery query, int count) - where TData : class, new() - { - var newQuery = query.AddBlock(".take({0})._()", count); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} diff --git a/Neo4jClient.Shared/Gremlin/LoopStep.cs b/Neo4jClient.Shared/Gremlin/LoopStep.cs deleted file mode 100644 index 81b81f15f..000000000 --- a/Neo4jClient.Shared/Gremlin/LoopStep.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class LoopStep - { - public static IGremlinNodeQuery LoopV(this IGremlinQuery query, string label, uint loopCount) - { - var newQuery = query.AddBlock(".loop({0}){{ it.loops < {1} }}", label, loopCount); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery LoopE(this IGremlinQuery query, string label, uint loopCount) - { - var newQuery = query.AddBlock(".loop({0}){{ it.loops < {1} }}", label, loopCount); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery LoopE(this IGremlinQuery query, string label, uint loopCount) - where TData : class, new() - { - var newQuery = query.AddBlock(".loop({0}){{ it.loops < {1} }}", label, loopCount); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} diff --git a/Neo4jClient.Shared/Gremlin/PrintLineStatement.cs b/Neo4jClient.Shared/Gremlin/PrintLineStatement.cs deleted file mode 100644 index c0ec0934d..000000000 --- a/Neo4jClient.Shared/Gremlin/PrintLineStatement.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class PrintLineStatement - { - public static IGremlinQuery PrintLine(this IGremlinQuery query, string value) - { - var newQuery = query.AddBlock(string.Format("println {0}", value)); - return newQuery; - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/RetainStep.cs b/Neo4jClient.Shared/Gremlin/RetainStep.cs deleted file mode 100644 index 8619bf833..000000000 --- a/Neo4jClient.Shared/Gremlin/RetainStep.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class RetainStep - { - public static IGremlinNodeQuery RetainV(this IGremlinQuery query, string variable) - { - var newQuery = query.AddBlock(string.Format(".retain({0})", variable)); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery RetainE(this IGremlinQuery query, string variable) - { - var newQuery = query.AddBlock(string.Format(".retain({0})", variable)); - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery RetainE(this IGremlinQuery query, string variable) - where TData : class, new() - { - var newQuery = query.AddBlock(string.Format(".retain({0})", variable)); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/Statement.cs b/Neo4jClient.Shared/Gremlin/Statement.cs deleted file mode 100644 index 8361505bc..000000000 --- a/Neo4jClient.Shared/Gremlin/Statement.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Collections; -using System.Collections.Generic; - -namespace Neo4jClient.Gremlin -{ - public class Statement : IGremlinQuery - { - readonly IDictionary queryParameters = new Dictionary(); - readonly IList queryDeclarations = new List(); - - public IGraphClient Client { get; set; } - - public string QueryText - { - get { return ""; } - } - - public IDictionary QueryParameters - { - get { return queryParameters; } - } - - public IList QueryDeclarations - { - get { return queryDeclarations; } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/StoreStep.cs b/Neo4jClient.Shared/Gremlin/StoreStep.cs deleted file mode 100644 index 07b2027cd..000000000 --- a/Neo4jClient.Shared/Gremlin/StoreStep.cs +++ /dev/null @@ -1,31 +0,0 @@ -namespace Neo4jClient.Gremlin -{ - public static class StoreStep - { - public static IGremlinNodeQuery StoreV(this IGremlinQuery query, string variable) - { - var newQuery = query.AddBlock(string.Format(".store({0})", variable)); - newQuery.QueryDeclarations.Add(string.Format("{0} = [];", variable)); - newQuery = newQuery.PrependVariablesToBlock(newQuery); - return new GremlinNodeEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery StoreE(this IGremlinQuery query, string variable) - { - var newQuery = query.AddBlock(string.Format(".store({0})", variable)); - newQuery.QueryDeclarations.Add(string.Format("{0} = [];", variable)); - newQuery = newQuery.PrependVariablesToBlock(newQuery); - - return new GremlinRelationshipEnumerable(newQuery); - } - - public static IGremlinRelationshipQuery StoreE(this IGremlinQuery query, string variable) - where TData : class, new() - { - var newQuery = query.AddBlock(string.Format(".store({0})", variable)); - newQuery.QueryDeclarations.Add(string.Format("{0} = [];", variable)); - newQuery = newQuery.PrependVariablesToBlock(newQuery); - return new GremlinRelationshipEnumerable(newQuery); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Gremlin/TableStep.cs b/Neo4jClient.Shared/Gremlin/TableStep.cs deleted file mode 100644 index a7447fca2..000000000 --- a/Neo4jClient.Shared/Gremlin/TableStep.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; - -namespace Neo4jClient.Gremlin -{ - public static class TableStep - { - public static IEnumerable Table( - this IGremlinQuery query) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table()).cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - public static IEnumerable Table( - this IGremlinQuery query, - Expression> closure1) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table())"); - newQuery = AddClosure(newQuery, closure1); - newQuery = newQuery.AddBlock(".cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - public static IEnumerable Table( - this IGremlinQuery query, - Expression> closure1, - Expression> closure2) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table())"); - newQuery = AddClosure(newQuery, closure1); - newQuery = AddClosure(newQuery, closure2); - newQuery = newQuery.AddBlock(".cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - public static IEnumerable Table( - this IGremlinQuery query, - Expression> closure1, - Expression> closure2, - Expression> closure3) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table())"); - newQuery = AddClosure(newQuery, closure1); - newQuery = AddClosure(newQuery, closure2); - newQuery = AddClosure(newQuery, closure3); - newQuery = newQuery.AddBlock(".cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - public static IEnumerable Table( - this IGremlinQuery query, - Expression> closure1, - Expression> closure2, - Expression> closure3, - Expression> closure4) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table())"); - newQuery = AddClosure(newQuery, closure1); - newQuery = AddClosure(newQuery, closure2); - newQuery = AddClosure(newQuery, closure3); - newQuery = AddClosure(newQuery, closure4); - newQuery = newQuery.AddBlock(".cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - public static IEnumerable Table( - this IGremlinQuery query, - Expression> closure1, - Expression> closure2, - Expression> closure3, - Expression> closure4, - Expression> closure5) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table())"); - newQuery = AddClosure(newQuery, closure1); - newQuery = AddClosure(newQuery, closure2); - newQuery = AddClosure(newQuery, closure3); - newQuery = AddClosure(newQuery, closure4); - newQuery = AddClosure(newQuery, closure5); - newQuery = newQuery.AddBlock(".cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - public static IEnumerable Table( - this IGremlinQuery query, - Expression> closure1, - Expression> closure2, - Expression> closure3, - Expression> closure4, - Expression> closure5, - Expression> closure6) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table())"); - newQuery = AddClosure(newQuery, closure1); - newQuery = AddClosure(newQuery, closure2); - newQuery = AddClosure(newQuery, closure3); - newQuery = AddClosure(newQuery, closure4); - newQuery = AddClosure(newQuery, closure5); - newQuery = AddClosure(newQuery, closure6); - newQuery = newQuery.AddBlock(".cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - public static IEnumerable Table( - this IGremlinQuery query, - Expression> closure1, - Expression> closure2, - Expression> closure3, - Expression> closure4, - Expression> closure5, - Expression> closure6, - Expression> closure7) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table())"); - newQuery = AddClosure(newQuery, closure1); - newQuery = AddClosure(newQuery, closure2); - newQuery = AddClosure(newQuery, closure3); - newQuery = AddClosure(newQuery, closure4); - newQuery = AddClosure(newQuery, closure5); - newQuery = AddClosure(newQuery, closure6); - newQuery = AddClosure(newQuery, closure7); - newQuery = newQuery.AddBlock(".cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - public static IEnumerable Table( - this IGremlinQuery query, - Expression> closure1, - Expression> closure2, - Expression> closure3, - Expression> closure4, - Expression> closure5, - Expression> closure6, - Expression> closure7, - Expression> closure8) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table())"); - newQuery = AddClosure(newQuery, closure1); - newQuery = AddClosure(newQuery, closure2); - newQuery = AddClosure(newQuery, closure3); - newQuery = AddClosure(newQuery, closure4); - newQuery = AddClosure(newQuery, closure5); - newQuery = AddClosure(newQuery, closure6); - newQuery = AddClosure(newQuery, closure7); - newQuery = AddClosure(newQuery, closure8); - newQuery = newQuery.AddBlock(".cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - public static IEnumerable Table( - this IGremlinQuery query, - Expression> closure1, - Expression> closure2, - Expression> closure3, - Expression> closure4, - Expression> closure5, - Expression> closure6, - Expression> closure7, - Expression> closure8, - Expression> closure9) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table())"); - newQuery = AddClosure(newQuery, closure1); - newQuery = AddClosure(newQuery, closure2); - newQuery = AddClosure(newQuery, closure3); - newQuery = AddClosure(newQuery, closure4); - newQuery = AddClosure(newQuery, closure5); - newQuery = AddClosure(newQuery, closure6); - newQuery = AddClosure(newQuery, closure7); - newQuery = AddClosure(newQuery, closure8); - newQuery = AddClosure(newQuery, closure9); - newQuery = newQuery.AddBlock(".cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - public static IEnumerable Table( - this IGremlinQuery query, - Expression> closure1, - Expression> closure2, - Expression> closure3, - Expression> closure4, - Expression> closure5, - Expression> closure6, - Expression> closure7, - Expression> closure8, - Expression> closure9, - Expression> closure10) where TResult : new() - { - var newQuery = query.AddBlock(".table(new Table())"); - newQuery = AddClosure(newQuery, closure1); - newQuery = AddClosure(newQuery, closure2); - newQuery = AddClosure(newQuery, closure3); - newQuery = AddClosure(newQuery, closure4); - newQuery = AddClosure(newQuery, closure5); - newQuery = AddClosure(newQuery, closure6); - newQuery = AddClosure(newQuery, closure7); - newQuery = AddClosure(newQuery, closure8); - newQuery = AddClosure(newQuery, closure9); - newQuery = AddClosure(newQuery, closure10); - newQuery = newQuery.AddBlock(".cap"); - return new GremlinProjectionEnumerable(newQuery); - } - - static IGremlinQuery AddClosure(IGremlinQuery newQuery, Expression> closure) - { - var expressionKey = FilterFormatters.ParseKeyFromExpression(closure.Body); - return newQuery.AddBlock("{{it[{0}]}}", expressionKey.Name); - } - } -} diff --git a/Neo4jClient.Shared/Gremlin/TypeFilter.cs b/Neo4jClient.Shared/Gremlin/TypeFilter.cs deleted file mode 100644 index fd886cab1..000000000 --- a/Neo4jClient.Shared/Gremlin/TypeFilter.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Linq.Expressions; - -namespace Neo4jClient.Gremlin -{ - internal class TypeFilter - { - public ExpressionType ExpressionType { get; set; } - public Type Type { get; set; } - public string FilterFormat { get; set; } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/HttpContentExtensions.cs b/Neo4jClient.Shared/HttpContentExtensions.cs deleted file mode 100644 index 2eeeec5c9..000000000 --- a/Neo4jClient.Shared/HttpContentExtensions.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Collections.Generic; -using System.Net.Http; -using Neo4jClient.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace Neo4jClient -{ - internal static class HttpContentExtensions - { - public static string ReadAsString(this HttpContent content) - { - var readTask = content.ReadAsStringAsync(); - readTask.Wait(); - return readTask.Result; - } - - public static T ReadAsJson(this HttpContent content, IEnumerable jsonConverters, DefaultContractResolver resolver) - where T : new() - { - var stringContent = content.ReadAsString(); - return new CustomJsonDeserializer(jsonConverters, resolver:resolver).Deserialize(stringContent); - } - - public static T ReadAsJson(this HttpContent content, IEnumerable jsonConverters) where T : new() - { - return content.ReadAsJson(jsonConverters, null); - } - } -} diff --git a/Neo4jClient.Shared/IGraphClient.cs b/Neo4jClient.Shared/IGraphClient.cs deleted file mode 100644 index eaead7043..000000000 --- a/Neo4jClient.Shared/IGraphClient.cs +++ /dev/null @@ -1,153 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Neo4jClient.ApiModels; -using Neo4jClient.Cypher; -using Neo4jClient.Execution; -using Neo4jClient.Gremlin; -using Neo4jClient.Serialization; -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; - -namespace Neo4jClient -{ - public interface IGraphClient : ICypherGraphClient - { - event OperationCompletedEventHandler OperationCompleted; - - CypherCapabilities CypherCapabilities { get; } - - Version ServerVersion { get; } - - Uri RootEndpoint { get; } - - Uri BatchEndpoint { get; } - - Uri CypherEndpoint { get; } - - //Uri BoltEndpoint { get; } - - ISerializer Serializer { get; } - - ExecutionConfiguration ExecutionConfiguration { get; } - - bool IsConnected { get; } - - Task ConnectAsync(NeoServerConfiguration configuration = null); - - [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.")] - RootNode RootNode { get; } - - /// - /// Creates a node, relationships and index entries all in a single HTTP call (which also means a single transaction). - /// - NodeReference Create(TNode node, IEnumerable> relationships, IEnumerable indexEntries) - where TNode : class; - - Node Get(NodeReference reference); - - Task> GetAsync(NodeReference reference); - - Node Get(NodeReference reference); - - RelationshipInstance Get(RelationshipReference reference) where TData : class, new(); - - RelationshipInstance Get(RelationshipReference reference) where TData : class, new(); - - Task> GetAsync(RelationshipReference reference) where TData : class, new(); - - /// - /// Retrieves the specified node, gives you an opportunity to mutate it in the callback, then persists the final object back to Neo4j. Results in two calls over the wire: one to retrieve, one to set. - /// - /// POCO type that represents the structure of the node's data - /// The node to retrieve and update - /// Data to replace the node with - /// New index entries that should also be persisted - void Update(NodeReference nodeReference, - TNode replacementData, - IEnumerable indexEntries = null); - - /// - /// Retrieves the specified node, gives you an opportunity to mutate it in the callback, then persists the final object back to Neo4j. Results in two calls over the wire: one to retrieve, one to set. - /// - /// POCO type that represents the structure of the node's data - /// The node to retrieve and update - /// A callback to mutate the values between retrieval and persistence - /// A callback to return new index entries that should also be persisted - /// A callback to respond to the resulting property changes - Node Update(NodeReference nodeReference, - Action updateCallback, - Func> indexEntriesCallback = null, - Action> changeCallback = null); - - void Update(RelationshipReference relationshipReference, Action updateCallback) - where TRelationshipData : class, new(); - - void Delete(NodeReference reference, DeleteMode mode); - - RelationshipReference CreateRelationship(NodeReference sourceNodeReference, TRelationship relationship) - where TRelationship : Relationship, IRelationshipAllowingSourceNode; - - void DeleteRelationship(RelationshipReference reference); - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - string ExecuteScalarGremlin(string query, IDictionary parameters); - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - IEnumerable 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.")] - IEnumerable> ExecuteGetAllNodesGremlin(IGremlinQuery query); - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - 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.")] - IEnumerable> ExecuteGetAllRelationshipsGremlin(string query, IDictionary parameters) where TData : class, new(); - - Dictionary GetIndexes(IndexFor indexFor); - - bool CheckIndexExists(string indexName, IndexFor indexFor); - - void CreateIndex(string indexName, IndexConfiguration config, IndexFor indexFor); - - void ReIndex(NodeReference node, IEnumerable indexEntries); - - void ReIndex(RelationshipReference relationship, IEnumerable indexEntries); - - void DeleteIndex(string indexName, IndexFor indexFor); - - /// - /// Delete all index entries for specified node - /// - void DeleteIndexEntries(string indexName, NodeReference relationshipReference); - - /// - /// Delete all index entries for specified relationship - /// - void DeleteIndexEntries(string indexName, RelationshipReference relationshipReference); - - [Obsolete("There are encoding issues with this method. You should use the newer Cypher aproach 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.")] - IEnumerable> QueryIndex(string indexName, IndexFor indexFor, string query); - - 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 Gremlin, which is being dropped in Neo4j 2.0. Find an alternate strategy for server lifetime management.")] - void ShutdownServer(); - - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - IGremlinClient Gremlin { get; } - - Uri GremlinEndpoint { get; } - - Uri NodeIndexEndpoint { get; } - - Uri RelationshipIndexEndpoint { get; } - - void Connect(NeoServerConfiguration configuration = null); - - List JsonConverters { get; } - DefaultContractResolver JsonContractResolver { get; set; } - } -} diff --git a/Neo4jClient.Shared/IGraphClientFactory.cs b/Neo4jClient.Shared/IGraphClientFactory.cs deleted file mode 100644 index 93fdd1250..000000000 --- a/Neo4jClient.Shared/IGraphClientFactory.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Neo4jClient.Execution; - -namespace Neo4jClient -{ - public interface IGraphClientFactory - { - IGraphClient Create(); - IGraphClient Create(IHttpClient client); - } -} diff --git a/Neo4jClient.Shared/Mappers/MapperHelper.cs b/Neo4jClient.Shared/Mappers/MapperHelper.cs deleted file mode 100644 index bc4f63981..000000000 --- a/Neo4jClient.Shared/Mappers/MapperHelper.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Reflection; -using System.Text.RegularExpressions; -using Neo4jClient.Serialization; -using Newtonsoft.Json; - -namespace Neo4jClient.Mappers -{ - [Obsolete("Gremlin support gets dropped with Neo4j 2.0. Please move to equivalent (but much more powerful and readable!) Cypher.")] - internal static class MapperHelper - { - static readonly Regex DateRegex = new Regex(@"/Date\([-]?\d+([+-]\d+)?\)/"); - - internal static void ConvertAndSetValue(this TResult result, string value, PropertyInfo prop, IEnumerable jsonConverters) - where TResult : new() - { - var deserializer = new CustomJsonDeserializer(jsonConverters); - try - { - var validType = prop.PropertyType; - if (prop.PropertyType.GetTypeInfo().IsGenericType && - prop.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - var nullableConverter = new NullableConverter(prop.PropertyType); - validType = nullableConverter.UnderlyingType; - } - - if (value == null || value == "null" || string.IsNullOrEmpty(value)) - { - prop.SetValue(result, null, null); - return; - } - - object convertedData; - if (validType.GetTypeInfo().IsEnum) - { - convertedData = Enum.Parse(validType, value, false); - } - else if (validType == typeof(DateTimeOffset)) - { - var rawValue = value.Replace("NeoDate", "Date"); - convertedData = DateRegex.IsMatch(rawValue) - ? deserializer.Deserialize(string.Format(@"{{ DateTimeOffset: '{0}'}}", rawValue)).DateTimeOffset - : DateTimeOffset.Parse(rawValue); - } - else - { - convertedData = Convert.ChangeType(value, validType); - } - prop.SetValue(result, convertedData, null); - } - catch (Exception ex) - { - throw new Exception(string.Format("Could not set property {0} to value {1} for type {2}\n {3}", - prop.Name, - value, result.GetType().FullName, ex)); - } - } - } - - internal class DateHolder - { - public DateTimeOffset DateTimeOffset { get; set; } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Neo4jClient.Shared.projitems b/Neo4jClient.Shared/Neo4jClient.Shared.projitems deleted file mode 100644 index ac254e9df..000000000 --- a/Neo4jClient.Shared/Neo4jClient.Shared.projitems +++ /dev/null @@ -1,225 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - c5b928ca-3e8e-4181-bacd-48ced40cefde - - - Neo4jClient - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Shared/Neo4jClient.Shared.shproj b/Neo4jClient.Shared/Neo4jClient.Shared.shproj deleted file mode 100644 index 98731b5b9..000000000 --- a/Neo4jClient.Shared/Neo4jClient.Shared.shproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - c5b928ca-3e8e-4181-bacd-48ced40cefde - 14.0 - - - - - - - - diff --git a/Neo4jClient.Shared/NeoServerConfiguration.cs b/Neo4jClient.Shared/NeoServerConfiguration.cs deleted file mode 100644 index 49e050db1..000000000 --- a/Neo4jClient.Shared/NeoServerConfiguration.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System; -using System.Net; -using System.Reflection; -using System.Threading.Tasks; -using Neo4jClient.ApiModels; -using Neo4jClient.Execution; - -namespace Neo4jClient -{ - public class NeoServerConfiguration - { - internal RootApiResponse ApiConfig { get; private set; } - - internal Uri RootUri { get; private set; } - - internal string Username { get; private set; } - internal string Password { get; private set; } - internal string Realm { get; private set; } - - private NeoServerConfiguration(RootApiResponse apiConfig) - { - ApiConfig = apiConfig; - } - - public static async Task GetConfigurationAsync(Uri rootUri, string username = null, string password = null, string realm = null) - { - return await GetConfigurationAsync(rootUri, username, password, realm, null).ConfigureAwait(false); - } - - internal static async Task GetConfigurationAsync(Uri rootUri, string username, string password, string realm, ExecutionConfiguration executionConfiguration) - { - if (executionConfiguration == null) - { - var httpClient = new HttpClientWrapper(username, password); - - executionConfiguration = new ExecutionConfiguration - { - HttpClient = httpClient, - UserAgent = - string.Format("Neo4jClient/{0}", typeof(NeoServerConfiguration).GetTypeInfo().Assembly.GetName().Version), - UseJsonStreaming = true, - JsonConverters = GraphClient.DefaultJsonConverters, - Username = username, - Password = password, - Realm = realm - }; - } - - if (!rootUri.AbsoluteUri.EndsWith("/")) - rootUri = new Uri(rootUri.AbsoluteUri + "/"); - - rootUri = new Uri(rootUri, ""); - - var result = await Request.With(executionConfiguration) - .Get(rootUri) - .WithExpectedStatusCodes(HttpStatusCode.OK) - .ParseAs() - .ExecuteAsync().ConfigureAwait(false); - - if (result == null) - { - throw new InvalidOperationException("Couldn't obtain server Root API configuration."); - } - - var rootUriWithoutUserInfo = rootUri; - if (!string.IsNullOrEmpty(rootUriWithoutUserInfo.UserInfo)) - { - rootUriWithoutUserInfo = new UriBuilder(rootUri.AbsoluteUri) - { - UserName = "", - Password = "" - }.Uri; - } - - var baseUriLengthToTrim = rootUriWithoutUserInfo.AbsoluteUri.Length - 1; - - result.Batch = result.Batch.Substring(baseUriLengthToTrim); - result.Node = result.Node.Substring(baseUriLengthToTrim); - result.NodeIndex = result.NodeIndex.Substring(baseUriLengthToTrim); - result.Relationship = "/relationship"; //Doesn't come in on the Service Root - result.RelationshipIndex = result.RelationshipIndex.Substring(baseUriLengthToTrim); - result.ExtensionsInfo = result.ExtensionsInfo.Substring(baseUriLengthToTrim); - - if (!string.IsNullOrEmpty(result.Transaction)) - { - result.Transaction = result.Transaction.Substring(baseUriLengthToTrim); - } - - if (result.Extensions != null && result.Extensions.GremlinPlugin != null) - { - result.Extensions.GremlinPlugin.ExecuteScript = - result.Extensions.GremlinPlugin.ExecuteScript.Substring(baseUriLengthToTrim); - } - - if (result.Cypher != null) - { - result.Cypher = result.Cypher.Substring(baseUriLengthToTrim); - } - - return new NeoServerConfiguration(result) - { - RootUri = rootUri, - Username = username, - Password = password, - Realm = realm - }; - } - - public static NeoServerConfiguration GetConfiguration(Uri rootUri, string username = null, string password = null, string realm = null) - { - return GetConfiguration(rootUri, username, password, realm, null); - } - - internal static NeoServerConfiguration GetConfiguration(Uri rootUri, string username, string password, string realm, ExecutionConfiguration executionConfiguration) - { - if (executionConfiguration == null) - { - var httpClient = new HttpClientWrapper(username, password); - - executionConfiguration = new ExecutionConfiguration - { - HttpClient = httpClient, - UserAgent = - string.Format("Neo4jClient/{0}", typeof (NeoServerConfiguration).GetTypeInfo().Assembly.GetName().Version), - UseJsonStreaming = true, - JsonConverters = GraphClient.DefaultJsonConverters, - Username = username, - Password = password, - Realm = realm - }; - } - - if (!rootUri.AbsoluteUri.EndsWith("/")) - rootUri = new Uri(rootUri.AbsoluteUri + "/"); - - rootUri = new Uri(rootUri, ""); - - var result = Request.With(executionConfiguration) - .Get(rootUri) - .WithExpectedStatusCodes(HttpStatusCode.OK) - .ParseAs() - .Execute(); - - if (result == null) - { - throw new InvalidOperationException("Couldn't obtain server Root API configuration."); - } - - var rootUriWithoutUserInfo = rootUri; - if (!string.IsNullOrEmpty(rootUriWithoutUserInfo.UserInfo)) - { - rootUriWithoutUserInfo = new UriBuilder(rootUri.AbsoluteUri) - { - UserName = "", - Password = "" - }.Uri; - } - - var baseUriLengthToTrim = rootUriWithoutUserInfo.AbsoluteUri.Length - 1; - - result.Batch = result.Batch.Substring(baseUriLengthToTrim); - result.Node = result.Node.Substring(baseUriLengthToTrim); - result.NodeIndex = result.NodeIndex.Substring(baseUriLengthToTrim); - result.Relationship = "/relationship"; //Doesn't come in on the Service Root - result.RelationshipIndex = result.RelationshipIndex.Substring(baseUriLengthToTrim); - result.ExtensionsInfo = result.ExtensionsInfo.Substring(baseUriLengthToTrim); - - if (!string.IsNullOrEmpty(result.Transaction)) - { - result.Transaction = result.Transaction.Substring(baseUriLengthToTrim); - } - - if (result.Extensions != null && result.Extensions.GremlinPlugin != null) - { - result.Extensions.GremlinPlugin.ExecuteScript = - result.Extensions.GremlinPlugin.ExecuteScript.Substring(baseUriLengthToTrim); - } - - if (result.Cypher != null) - { - result.Cypher = result.Cypher.Substring(baseUriLengthToTrim); - } - - return new NeoServerConfiguration(result) - { - RootUri = rootUri, - Username = username, - Password = password - }; - } - } -} diff --git a/Neo4jClient.Shared/Node`TNode.cs b/Neo4jClient.Shared/Node`TNode.cs deleted file mode 100644 index 56440af80..000000000 --- a/Neo4jClient.Shared/Node`TNode.cs +++ /dev/null @@ -1,97 +0,0 @@ -using System; -using System.Collections.Generic; -using Neo4jClient.Cypher; -using Neo4jClient.Gremlin; - -namespace Neo4jClient -{ - public class Node : IGremlinQuery, IHasNodeReference - { - readonly TNode data; - readonly NodeReference reference; - - public Node(TNode data, NodeReference reference) - { - if (reference == null) - throw new ArgumentNullException("reference"); - - this.data = data; - this.reference = reference; - } - - public Node(TNode data, long id, IGraphClient client) - { - this.data = data; - reference = new NodeReference(id, client); - } - - public NodeReference Reference - { - get { return reference; } - } - - NodeReference IHasNodeReference.Reference - { - get { return reference; } - } - - public TNode Data - { - get { return data; } - } - - public ICypherFluentQuery StartCypher(string identity) - { - var client = ((IAttachedReference) this).Client; - var query = new CypherFluentQuery(client) - .Start(identity, Reference); - return query; - } - - IGraphClient IAttachedReference.Client - { - get { return ((IAttachedReference)reference).Client; } - } - - string IGremlinQuery.QueryText - { - get { return ((IGremlinQuery)reference).QueryText; } - } - - IDictionary IGremlinQuery.QueryParameters - { - get { return ((IGremlinQuery) reference).QueryParameters; } - } - - IList IGremlinQuery.QueryDeclarations - { - get { return ((IGremlinQuery)reference).QueryDeclarations; } - } - - public static bool operator ==(Node lhs, Node rhs) - { - if (ReferenceEquals(lhs, null) && ReferenceEquals(rhs, null)) - return true; - - return !ReferenceEquals(lhs, null) && lhs.Equals(rhs); - } - - public static bool operator !=(Node lhs, Node rhs) - { - return !(lhs == rhs); - } - - public override bool Equals(object obj) - { - var other = obj as Node; - if (ReferenceEquals(null, other)) return false; - if (ReferenceEquals(this, other)) return true; - return other.Reference == Reference; - } - - public override int GetHashCode() - { - return Reference.Id.GetHashCode(); - } - } -} diff --git a/Neo4jClient.Shared/Properties/AssemblyInfo.cs b/Neo4jClient.Shared/Properties/AssemblyInfo.cs deleted file mode 100644 index 9b4f5bae2..000000000 --- a/Neo4jClient.Shared/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,38 +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("Neo4jClient")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("Microsoft")] -[assembly: AssemblyProduct("Neo4jClient")] -[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("255d97a9-6aee-404d-aa63-cf21d4e8f26d")] - -// 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("0.0.0.0")] -[assembly: AssemblyFileVersion("0.0.0.0")] - -[assembly: InternalsVisibleTo("Neo4jClient.Tests")] \ No newline at end of file diff --git a/Neo4jClient.Shared/RootNode.cs b/Neo4jClient.Shared/RootNode.cs deleted file mode 100644 index 4670f4b6f..000000000 --- a/Neo4jClient.Shared/RootNode.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace Neo4jClient -{ - [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 class RootNode : NodeReference - { - public RootNode() : base(0) { } - public RootNode(long id) : base(id) {} - public RootNode(long id, IGraphClient client) : base(id, client) {} - } -} diff --git a/Neo4jClient.Shared/Transactions/BoltResponse.cs b/Neo4jClient.Shared/Transactions/BoltResponse.cs deleted file mode 100644 index 6fbde579a..000000000 --- a/Neo4jClient.Shared/Transactions/BoltResponse.cs +++ /dev/null @@ -1,9 +0,0 @@ -using Neo4j.Driver.V1; - -namespace Neo4jClient.Transactions -{ - public class BoltResponse - { - public IStatementResult StatementResult { get; set; } - } -} \ No newline at end of file diff --git a/Neo4jClient.Shared/Transactions/ITransactionManager.cs b/Neo4jClient.Shared/Transactions/ITransactionManager.cs deleted file mode 100644 index c29624cdd..000000000 --- a/Neo4jClient.Shared/Transactions/ITransactionManager.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net.Http; -using System.Threading.Tasks; -using Neo4jClient.Cypher; - -namespace Neo4jClient.Transactions -{ - /// - /// Interface that handles all the queries related to transactions that could be needed in a ITransactionalGraphClient - /// for implementation. - /// - public interface ITransactionManager : IDisposable - { - bool InTransaction { get; } - ITransaction CurrentNonDtcTransaction { get; } - ITransaction CurrentDtcTransaction { get; } - ITransaction BeginTransaction(TransactionScopeOption option, IEnumerable bookmarks); - void EndTransaction(); - void RegisterToTransactionIfNeeded(); - Task EnqueueCypherRequest(string commandDescription, IGraphClient graphClient, CypherQuery query); - } -} diff --git a/Neo4jClient.Shared/packages.config b/Neo4jClient.Shared/packages.config deleted file mode 100644 index 408753550..000000000 --- a/Neo4jClient.Shared/packages.config +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Neo4jClient.Tests.Core/Cypher/CypherFluentQueryCustomHeaderTests.cs b/Neo4jClient.Tests.Core/Cypher/CypherFluentQueryCustomHeaderTests.cs deleted file mode 100644 index ae687fe41..000000000 --- a/Neo4jClient.Tests.Core/Cypher/CypherFluentQueryCustomHeaderTests.cs +++ /dev/null @@ -1,69 +0,0 @@ -using System; -using System.Collections.Specialized; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -using NSubstitute; -using Xunit; - -namespace Neo4jClient.Test.Cypher -{ - - public class CypherFluentQueryCustomHeaderTests : IClassFixture - { - [Fact] - public void SetsMaxExecutionTimeAndCustomHeader_WhenUsingAReturnTypeQuery() - { - const string headerName = "HeaderName"; - const string headerValue = "TestHeaderValue"; - var client = Substitute.For(); - var customHeaders = new Neo4jClient.NameValueCollection(); - customHeaders.Add(headerName, headerValue); - - var query = new CypherFluentQuery(client) - .MaxExecutionTime(100) - .CustomHeaders(customHeaders) - .Match("n") - .Return("n") - .Query; - - Assert.Equal(100, query.MaxExecutionTime); - Assert.Equal(customHeaders, query.CustomHeaders); - } - - [Fact] - public void SetsCustomHeader_WhenUsingAReturnTypeQuery() - { - const string headerName = "HeaderName"; - const string headerValue = "TestHeaderValue"; - var client = Substitute.For(); - var customHeaders = new Neo4jClient.NameValueCollection(); - customHeaders.Add(headerName, headerValue); - - var query = new CypherFluentQuery(client) - .CustomHeaders(customHeaders) - .Match("n") - .Return("n") - .Query; - - Assert.Equal(customHeaders, query.CustomHeaders); - } - - [Fact] - public void SetsCustomHeader_WhenUsingANonReturnTypeQuery() - { - const string headerName = "HeaderName"; - const string headerValue = "TestHeaderValue"; - var customHeaders = new Neo4jClient.NameValueCollection(); - customHeaders.Add(headerName, headerValue); - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .CustomHeaders(customHeaders) - .Match("n") - .Set("n.Value = 'value'") - .Query; - - Assert.Equal(customHeaders, query.CustomHeaders); - } - } -} diff --git a/Neo4jClient.Tests.Core/Neo4jClient.Tests.Core.csproj b/Neo4jClient.Tests.Core/Neo4jClient.Tests.Core.csproj deleted file mode 100644 index aa01431c7..000000000 --- a/Neo4jClient.Tests.Core/Neo4jClient.Tests.Core.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - netcoreapp2.0 - - false - - - - - - - - - - - - - - - - - - ..\packages\Moq.4.7.99\lib\net45\Moq.dll - - - - - - diff --git a/Neo4jClient.Tests.Core/Neo4jClient.Tests.Core.v3.ncrunchproject b/Neo4jClient.Tests.Core/Neo4jClient.Tests.Core.v3.ncrunchproject deleted file mode 100644 index bf84ee553..000000000 --- a/Neo4jClient.Tests.Core/Neo4jClient.Tests.Core.v3.ncrunchproject +++ /dev/null @@ -1,6 +0,0 @@ - - - True - True - - \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/ApiModels/GremlinTableCapResponseTests.cs b/Neo4jClient.Tests.Shared/ApiModels/GremlinTableCapResponseTests.cs deleted file mode 100644 index 184111f52..000000000 --- a/Neo4jClient.Tests.Shared/ApiModels/GremlinTableCapResponseTests.cs +++ /dev/null @@ -1,352 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using Xunit; -using Neo4jClient.ApiModels; -using System.Linq; -using Neo4jClient.ApiModels.Gremlin; -using Neo4jClient.Test.Fixtures; -using Newtonsoft.Json; - -namespace Neo4jClient.Test.ApiModels -{ - - public class GremlinTableCapResponseTests : IClassFixture - { - [Fact] - 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.True(response.Any(r => r.Foo == dataforfoo)); - Assert.True(response.Any(r => r.Bar == dataforbar)); - } - - [Fact] - 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.True(response.Any(r => r.Long == 123)); - } - - [Fact] - 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.True(response.Any(r => r.EnumValue == MyEnum.Foo)); - } - - [Fact] - 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.True(response.Any(r => r.EnumValueNullable == MyEnum.Foo)); - } - - [Fact] - 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.True(response.Any(r => r.NullableLong.Value == 123)); - } - - [Fact] - 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.True(response.Any(r => !r.NullableLong.HasValue)); - } - - [Fact] - 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.True(response.Any(r => !r.NullableLong.HasValue)); - } - - [Fact] - 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.True(response.Any(r => !r.NullableLong.HasValue)); - } - - [Fact] - 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.True(response.Any(r => r.DateTimeOffsetNullable.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == expectedDate)); - } - - [Fact] - 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.True(response.Any(r => r.DateTimeOffset.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == "23 Nov 2011")); - } - - [Fact] - 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.True(response.Any(r => r.DateTimeOffset.ToString("dd MMM yyyy", CultureInfo.InvariantCulture) == expectedDate)); - } - - [Fact] - 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.True(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/Neo4jClient.Tests.Shared/ApiUsageIdeas.cs b/Neo4jClient.Tests.Shared/ApiUsageIdeas.cs deleted file mode 100644 index c6f442978..000000000 --- a/Neo4jClient.Tests.Shared/ApiUsageIdeas.cs +++ /dev/null @@ -1,62 +0,0 @@ -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/Neo4jClient.Tests.Shared/BoltGraphClientTests/BoltGraphClientTests.cs b/Neo4jClient.Tests.Shared/BoltGraphClientTests/BoltGraphClientTests.cs deleted file mode 100644 index bc84537ec..000000000 --- a/Neo4jClient.Tests.Shared/BoltGraphClientTests/BoltGraphClientTests.cs +++ /dev/null @@ -1,354 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Linq.Expressions; -using System.Threading.Tasks; -using FluentAssertions; -using Moq; -using Neo4j.Driver.V1; -using Neo4jClient.Test.BoltGraphClientTests; -using Neo4jClient.Test.Fixtures; -using Neo4jClient.Tests.Shared; -using Newtonsoft.Json; -using Xunit; - -namespace Neo4jClient.Test.Extensions -{ - public class BoltGraphClientTests : IClassFixture - { - private class TestNode { } - private class TestRecord : IRecord - { - private IDictionary _contents = new Dictionary(); - - public TestRecord() - { - - } - - public TestRecord(IDictionary items) - { - _contents = items; - } - - object IRecord.this[int index] - { - get { throw new NotImplementedException(); } - } - - object IRecord.this[string key] => _contents[key]; - - public IReadOnlyDictionary Values { get; } - public IReadOnlyList Keys { get; } - - - } - public class ServerInfo : IStatementResult - { - private IList _list = new List(); - - public IEnumerator GetEnumerator() - { - return _list.GetEnumerator(); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } - - public ServerInfo() - { - _list.Add(new TestRecord(new Dictionary - { - {"name", "neo4j kernel"}, - {"versions", new List{"3.2.3"} } - })); - } - - public ServerInfo(IList records) - { - _list = records; - } - - public IRecord Peek() - { - throw new NotImplementedException(); - } - - public IResultSummary Consume() - { - throw new NotImplementedException(); - } - - public IReadOnlyList Keys { get; } - public IResultSummary Summary { get; } - - } - - [Fact] - public void SerializesDateTimesProperly() - { - var mockSession = new Mock(); - mockSession.Setup(s => s.Run("CALL dbms.components()")).Returns(new ServerInfo()); - - var mockDriver = new Mock(); - mockDriver.Setup(d => d.Session(It.IsAny())).Returns(mockSession.Object); - mockDriver.Setup(d => d.Session(It.IsAny(), It.IsAny>())).Returns(mockSession.Object); - mockDriver.Setup(d => d.Uri).Returns(new Uri("bolt://localhost")); - - var bgc = new BoltGraphClient(mockDriver.Object); - bgc.Connect(); - - var cwd = new ClassWithDateTime{Dt = new DateTime(2000, 1, 1)};; - - var cfq = bgc.Cypher.Create("(c)").WithParam("testParam", cwd); - - var expectedParameters = new Dictionary - { - { - "testParam", new Dictionary {{"Dt", JsonConvert.SerializeObject(cwd.Dt).Trim('\"')}} - } - }; - - var query = cfq.Query; - query.ToNeo4jDriverParameters(bgc).IsEqualTo(expectedParameters).Should().BeTrue(); - } - - [Fact] - public void SerializesDateTimeOffsetsProperly() - { - var mockSession = new Mock(); - mockSession.Setup(s => s.Run("CALL dbms.components()")).Returns(new ServerInfo()); - - var mockDriver = new Mock(); - mockDriver.Setup(d => d.Session(It.IsAny())).Returns(mockSession.Object); - mockDriver.Setup(d => d.Session(It.IsAny(), It.IsAny>())).Returns(mockSession.Object); - mockDriver.Setup(d => d.Uri).Returns(new Uri("bolt://localhost")); - - var bgc = new BoltGraphClient(mockDriver.Object); - bgc.Connect(); - - var cwd = new ClassWithDateTimeOffset { Dt = new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(1)) }; ; - - var cfq = bgc.Cypher.Create("(c)").WithParam("testParam", cwd); - - var expectedParameters = new Dictionary - { - { - "testParam", new Dictionary {{"Dt", JsonConvert.SerializeObject(cwd.Dt).Trim('\"')}} - } - }; - - var query = cfq.Query; - query.ToNeo4jDriverParameters(bgc).IsEqualTo(expectedParameters).Should().BeTrue(); - } - - [Fact] - public void SerializesGuidsProperly() - { - var mockSession = new Mock(); - mockSession.Setup(s => s.Run("CALL dbms.components()")).Returns(new ServerInfo()); - - var mockDriver = new Mock(); - mockDriver.Setup(d => d.Session(It.IsAny())).Returns(mockSession.Object); - mockDriver.Setup(d => d.Session(It.IsAny(), It.IsAny>())).Returns(mockSession.Object); - mockDriver.Setup(d => d.Uri).Returns(new Uri("bolt://localhost")); - - var bgc = new BoltGraphClient(mockDriver.Object); - bgc.Connect(); - - var cwg = new ClassWithGuid(); - - var cfq = bgc.Cypher.Create("(c)").WithParam("testParam", cwg); - - var expectedParameters = new Dictionary - { - {"testParam", new Dictionary{{"Id", cwg.Id.ToString()} } - }}; - - var query = cfq.Query; - query.ToNeo4jDriverParameters(bgc).IsEqualTo(expectedParameters).Should().BeTrue(); - } - - [Fact] - public void SerializesGuidsProperlyWhenAutoGeneratingParams() - { - var mockSession = new Mock(); - mockSession.Setup(s => s.Run("CALL dbms.components()")).Returns(new ServerInfo()); - - var mockDriver = new Mock(); - mockDriver.Setup(d => d.Session(It.IsAny())).Returns(mockSession.Object); - mockDriver.Setup(d => d.Session(It.IsAny(), It.IsAny>())).Returns(mockSession.Object); - mockDriver.Setup(d => d.Uri).Returns(new Uri("bolt://localhost")); - - var bgc = new BoltGraphClient( mockDriver.Object); - bgc.Connect(); - - var cwg = new ClassWithGuid(); - - var cfq = bgc.Cypher.Create("(c)").Where((ClassWithGuid c) => c.Id == cwg.Id); - - var expectedParameters = new Dictionary {{"p0", $"{cwg.Id}"}}; - - var query = cfq.Query; - query.ToNeo4jDriverParameters(bgc).IsEqualTo(expectedParameters).Should().BeTrue(); - } - - [Fact] - public void RootNode_ThrowsInvalidOperationException() - { - var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); - var ex = Record.Exception(() => bgc.RootNode); - ex.Should().NotBeNull(); - ex.Should().BeOfType(); - ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); - } - - [Fact] - public void Create_ThrowsInvalidOperationException() - { - var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); - var ex = Record.Exception(() => bgc.Create("value", null)); - ex.Should().NotBeNull(); - ex.Should().BeOfType(); - ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); - } - - [Fact] - public void Get_ThrowsInvalidOperationException() - { - var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); - var ex = Record.Exception(() => bgc.Get(new NodeReference(1))); - ex.Should().NotBeNull(); - ex.Should().BeOfType(); - ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); - } - - [Fact] - public void GetNode_ThrowsInvalidOperationException() - { - var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); - var ex = Record.Exception(() => bgc.Get(new NodeReference(1))); - ex.Should().NotBeNull(); - ex.Should().BeOfType(); - ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); - } - - [Fact] - public void Get_RelationReferenceTNode_ThrowsInvalidOperationException() - { - var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); - var ex = Record.Exception(() => bgc.Get(new RelationshipReference(1))); - ex.Should().NotBeNull(); - ex.Should().BeOfType(); - ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); - } - - [Fact] - public void Get_RelationReference_ThrowsInvalidOperationException() - { - var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); - var ex = Record.Exception(() => bgc.Get(new RelationshipReference(1))); - ex.Should().NotBeNull(); - ex.Should().BeOfType(); - ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); - } - - [Fact] - public async Task GetAsync_ThrowsInvalidOperationException() - { - var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); - var ex = await Record.ExceptionAsync(() => bgc.GetAsync(new NodeReference(1))); - ex.Should().NotBeNull(); - ex.Should().BeOfType(); - ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); - } - - [Fact] - public async Task GetAsync_RelationReference_ThrowsInvalidOperationException() - { - var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); - var ex = await Record.ExceptionAsync(() => bgc.GetAsync(new RelationshipReference(1))); - ex.Should().NotBeNull(); - ex.Should().BeOfType(); - ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); - } - - public class Constructor : IClassFixture - { - [Fact] - public void DoesntUseAddressResolverWhenPassingInOneUri() - { - var bgc = new BoltGraphClient($"bolt+routing://virtual.foo.com"); - bgc.AddressResolver.Should().BeNull(); - } - - [Fact] - public void UsesAddressResolverWhenPassingInMultipleUris() - { - var bgc = new BoltGraphClient($"bolt+routing://virtual.foo.com", new[] {"x.foo.com", "y.foo.com"}); - var resolved = bgc.AddressResolver.Resolve(null); - resolved.Should().HaveCount(2); - } - - - [Fact] - public void ValidForBoltPlusRoutingUris() - { - var ex = Record.Exception(() => new BoltGraphClient($"bolt+routing://virtual.foo.com", new[] {"x.foo.com", "y.foo.com"})); - ex.Should().BeNull(); - } - - [Fact] - public void DoesntNeedVirtualUriToBeSupplied() - { - const string uri = "x.foo.com"; - - var bgc = new BoltGraphClient( new[] { $"{uri}" }); - var resolved = bgc.AddressResolver.Resolve(null); - resolved.Should().HaveCount(1); - resolved.First().Host.Should().Be(uri); - } - - [Theory] - [InlineData("bolt")] - [InlineData("https")] - [InlineData("http")] - [InlineData("ftp")] - public void NotValidForOtherUriSchemes(string scheme) - { - var ex = Record.Exception(() => new BoltGraphClient($"{scheme}://virtual.foo.com", new [] {"x.foo.com", "y.foo.com"} )); - ex.Should().NotBeNull(); - ex.Should().BeOfType(); - } - - [Theory] - [InlineData("bolt")] - [InlineData("https")] - [InlineData("http")] - [InlineData("ftp")] - public void WorksIfYouPassInWholeUris(string schema) - { - const string uri = "x.foo.com"; - - var bgc = new BoltGraphClient($"bolt+routing://virtual.foo.com", new[] { $"{schema}://{uri}" }); - var resolved = bgc.AddressResolver.Resolve(null); - resolved.Should().HaveCount(1); - resolved.First().Host.Should().Be(uri); - } - - [Fact] - public void WorksIfYouPassInUrisWithoutScheme() - { - const string uri = "x.foo.com"; - - var bgc = new BoltGraphClient($"bolt+routing://virtual.foo.com", new[] { uri }); - var resolved = bgc.AddressResolver.Resolve(null); - resolved.Should().HaveCount(1); - resolved.First().Host.Should().Be(uri); - } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/BoltGraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs b/Neo4jClient.Tests.Shared/BoltGraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs deleted file mode 100644 index eef9d54c4..000000000 --- a/Neo4jClient.Tests.Shared/BoltGraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs +++ /dev/null @@ -1,1452 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using FluentAssertions; -using Moq; -using Neo4j.Driver.V1; -using Neo4jClient.ApiModels.Cypher; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.BoltGraphClientTests.Cypher -{ - internal class TestPath : IPath - { - #region Implementation of IEquatable - - public bool Equals(IPath other) - { - throw new NotImplementedException(); - } - - #endregion - - #region Implementation of IPath - - public INode Start { get; set; } - public INode End { get; set; } - public IReadOnlyList Nodes { get; set; } - public IReadOnlyList Relationships { get; set; } - - #endregion - } - - public class TestRelationship : IRelationship - { - #region Implementation of IEntity - - public object this[string key] - { - get { throw new NotImplementedException(); } - } - - public IReadOnlyDictionary Properties { get; set; } - public long Id { get; set; } - - #endregion - - #region Implementation of IEquatable - - public bool Equals(IRelationship other) - { - throw new NotImplementedException(); - } - - #endregion - - #region Implementation of IRelationship - - public string Type { get; set; } - public long StartNodeId { get; set; } - public long EndNodeId { get; set; } - - #endregion - } - - public class TestNode : INode { - #region Implementation of IEntity - - public object this[string key] - { - get { throw new NotImplementedException(); } - } - - public IReadOnlyDictionary Properties { get; set; } - public long Id { get; set; } - - #endregion - - #region Implementation of IEquatable - - public bool Equals(INode other) - { - throw new NotImplementedException(); - } - - #endregion - - #region Implementation of INode - - public IReadOnlyList Labels { get; } - - #endregion - } - - public class ExecuteGetCypherResultsTests : IClassFixture - { - public class SimpleResultDto - { - public string RelationshipType { get; set; } - public string Name { get; set; } - public long? UniqueId { get; set; } - } - - private class ObjectWithId - { - public long Id { get; set; } - } - - private class ObjectWithIds - { - public List Ids { get; set; } - } - - private class RelationType - { - public int Id { get; set; } - } - - private class RelationGrouper - { - public RelationType Rel { get; set; } - } - - [Fact] - public void RelationshipShouldDeserializeInDefinedType() - { - // Arrange - const string queryText = "MATCH (n:Test)-[r]->(t:Test) RETURN r AS Rel"; - - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Transactional); - - using (var testHarness = new BoltTestHarness()) - { - var relationshipMock = new Mock(); - relationshipMock - .Setup(r => r.StartNodeId) - .Returns(1); - relationshipMock - .Setup(r => r.EndNodeId) - .Returns(2); - relationshipMock - .Setup(r => r.Type) - .Returns("Xx"); - relationshipMock - .Setup(r => r.Id) - .Returns(3); - relationshipMock - .Setup(r => r.Properties) - .Returns(new Dictionary() - { - {"Id", 42} - }); - - var recordMock = new Mock(); - recordMock - .Setup(r => r["Rel"]) - .Returns(relationshipMock.Object); - recordMock - .Setup(r => r.Keys) - .Returns(new[] { "Rel" }); - - var testStatementResult = new TestStatementResult(new[] { "Rel" }, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var results = graphClient.ExecuteGetCypherResults(cypherQuery).ToArray(); - - //Assert - results.Length.Should().Be(1); - var relation = results.First().Rel; - relation.Id.Should().Be(42); - } - } - - [Fact] - public void RelationshipShouldDeserializeInAnonymousType() - { - // Arrange - const string queryText = @"MATCH (n:Test)-[r]->(t:Test) RETURN r AS Rel"; - - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Transactional); - - using (var testHarness = new BoltTestHarness()) - { - var relationshipMock = new Mock(); - relationshipMock - .Setup(r => r.StartNodeId) - .Returns(1); - relationshipMock - .Setup(r => r.EndNodeId) - .Returns(2); - relationshipMock - .Setup(r => r.Type) - .Returns("Xx"); - relationshipMock - .Setup(r => r.Id) - .Returns(3); - relationshipMock - .Setup(r => r.Properties) - .Returns(new Dictionary() - { - {"Id", 42} - }); - - var recordMock = new Mock(); - recordMock - .Setup(r => r["Rel"]) - .Returns(relationshipMock.Object); - recordMock - .Setup(r => r.Keys) - .Returns(new[] { "Rel" }); - - var testStatementResult = new TestStatementResult(new[] { "Rel" }, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - //Session mock??? - var dummy = new - { - Rel = new RelationType() - }; - var anonType = dummy.GetType(); - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var genericGetCypherResults = typeof(IRawGraphClient).GetMethod(nameof(graphClient.ExecuteGetCypherResults)); - var anonymousGetCypherResults = genericGetCypherResults.MakeGenericMethod(anonType); - var genericResults = (IEnumerable)anonymousGetCypherResults.Invoke(graphClient, new object[] { cypherQuery }); - - var results = genericResults.Cast().ToArray(); - - //Assert - Assert.Equal(1, results.Length); - var relation = (RelationType)anonType.GetProperty(nameof(dummy.Rel)).GetValue(results.First(), null); - relation.Id.Should().Be(42); - } - } - - [Fact] - public void CollectionShouldDeserializeCorrectly() - { - // simulate a collect() - const string queryText = "MATCH (start:Node) RETURN collect(start) AS data"; - - var queryParams = new Dictionary(); - - var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional); - - using (var testHarness = new BoltTestHarness()) - { - var nodeMock = new Mock(); - nodeMock - .Setup(n => n.Id) - .Returns(1); - nodeMock - .Setup(n => n.Properties) - .Returns(new Dictionary() {{"Ids", new List {1, 2, 3}}}); - - var recordMock = new Mock(); - recordMock - .Setup(r => r["data"]) - .Returns(new List() {nodeMock.Object}); - recordMock - .Setup(r => r.Keys) - .Returns(new[] { "data" }); - - var testStatementResult = new TestStatementResult(new[] { "data" }, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var results = graphClient.ExecuteGetCypherResults>(cypherQuery).ToArray(); - - //Assert - var deserializedObject = results.First().First(); - deserializedObject.Ids.Count.Should().Be(3); - deserializedObject.Ids[0].Should().Be(1); - deserializedObject.Ids[1].Should().Be(2); - deserializedObject.Ids[2].Should().Be(3); - } - } - - [Fact] - public void EmptyCollectionShouldDeserializeCorrectly() - { - const string queryText = "RETURN [] AS data"; - - var queryParams = new Dictionary(); - - var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional); - - using (var testHarness = new BoltTestHarness()) - { - var recordMock = new Mock(); - recordMock - .Setup(r => r["data"]) - .Returns(new List() {}); - recordMock - .Setup(r => r.Keys) - .Returns(new[] { "data" }); - - var testStatementResult = new TestStatementResult(new[] { "data" }, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var results = graphClient.ExecuteGetCypherResults>(cypherQuery).ToArray(); - - results.Should().BeEmpty(); - } - } - - //https://github.com/readify/neo4jclient/issues/266 - [Fact] - public void CollectionOfComplexTypesShouldDeserializeCorrectlyWhenInConjunctionWithAnotherComplexTypeInAContainer() - { - const string queryText = "MATCH (start:Node)-->(next:Node) RETURN start AS Start, collect(next) AS Next"; - - var queryParams = new Dictionary(); - - var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Projection, CypherResultFormat.Transactional); - - using (var testHarness = new BoltTestHarness()) - { - var startNodeMock = new Mock(); - startNodeMock - .Setup(n => n.Id) - .Returns(1); - startNodeMock - .Setup(n => n.Properties) - .Returns(new Dictionary { { "Id", 1 } }); - - var nextNodeMock = new Mock(); - nextNodeMock - .Setup(n => n.Id) - .Returns(2); - nextNodeMock - .Setup(n => n.Properties) - .Returns(new Dictionary { { "Id", 2 } }); - - var recordMock = new Mock(); - recordMock - .Setup(r => r["Next"]) - .Returns(new List { nextNodeMock.Object }); - - recordMock - .Setup(r => r["Start"]) - .Returns(startNodeMock.Object); - - recordMock - .Setup(r => r.Keys) - .Returns(new[] { "Start", "Next" }); - - var testStatementResult = new TestStatementResult(new[] { "Start", "Next" }, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var results = graphClient.ExecuteGetCypherResults(cypherQuery).ToArray(); - - //Assert - var deserializedObject = results.First(); - deserializedObject.Start.Should().NotBeNull(); - deserializedObject.Start.Id.Should().Be(1); - - var deserializedNext = deserializedObject.Next.ToList(); - deserializedNext.Should().HaveCount(1); - deserializedNext.First().Id.Should().Be(2); - } - } - - private class Container - { - public ObjectWithId Start { get; set; } - public IEnumerable Next { get; set; } - } - - //see: https://github.com/DotNet4Neo4j/Neo4jClient/issues/368 - [Fact] - public async Task ShouldBeAbleToCastToObject_WhenUsingReturnAs() - { - const string queryText = "MATCH (user)-[:hasPost]->(post:Post) WHERE(user.Username = 'user1') RETURN user{.Username} AS User, post AS Post"; - - var queryParams = new Dictionary(); - - var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Projection, CypherResultFormat.Transactional); - - using (var testHarness = new BoltTestHarness()) - { - const string username = "User1"; - const string postName = "Post1"; - - var user = new Dictionary { { "Username", username } }; - var postNodeMock = new Mock(); - postNodeMock.Setup(n => n.Id).Returns(1); - postNodeMock.Setup(n => n.Properties) - .Returns(new Dictionary { { "Content", postName } }); - - var recordMock = new Mock(); - recordMock - .Setup(r => r["User"]) - .Returns(user); - - recordMock - .Setup(r => r["Post"]) - .Returns(postNodeMock.Object); - - recordMock - .Setup(r => r.Keys) - .Returns(new[] { "User", "Post" }); - - recordMock.Setup(r => r.Values) - .Returns(new Dictionary { { "User", user }, { "Post", postNodeMock.Object } }); - - var testStatementResult = new TestStatementResult(new[] { "User", "Post" }, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var results = (await graphClient.ExecuteGetCypherResultsAsync(cypherQuery)).ToArray(); - // - //Assert - var deserializedObject = results.First(); - deserializedObject.User.Should().NotBeNull(); - deserializedObject.User.Username.Should().Be(username); - } - - } - private class User - { - public string Username { get; set; } - public string Password { get; set; } - } - - private class Post - { - public string Content { get; set; } - } - - private class PostAndUser - { - public User User { get; set; } - public Post Post { get; set; } - } - - [Fact] - public void CreateWithArrayParametersShouldSerializeAndDeserializeOnReturn() - { - // Arrange - const string queryText = "CREATE (start:Node {obj}) RETURN start"; - - var testNode = new ObjectWithIds() - { - Ids = new List() {1, 2, 3} - }; - - var queryParams = new Dictionary() {{"obj", testNode}}; - - var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional); - - using (var testHarness = new BoltTestHarness()) - { - var recordMock = new Mock(); - recordMock - .Setup(r => r["start"]) - .Returns(testNode); - recordMock - .Setup(r => r.Keys) - .Returns(new[] { "start" }); - - var testStatementResult = new TestStatementResult(new[] { "start" }, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var results = graphClient.ExecuteGetCypherResults(cypherQuery).ToArray(); - - //Assert - Assert.IsAssignableFrom>(results); - results.First().Ids.Count.Should().Be(3); - results.First().Ids[0].Should().Be(1); - results.First().Ids[1].Should().Be(2); - results.First().Ids[2].Should().Be(3); - } - } - - private class ObjectWithNodeWithIds - { - public ObjectWithIds Node { get; set; } - public int Count { get; set; } - } - - [Fact] - public void ShouldDeserializeMapWithAnonymousReturnAsDictionary() - { - // simulates the following query - const string queryText = "MATCH (start:Node) WITH {Node: 3, Count: 1} AS Node, start RETURN Node, start"; - - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, - CypherResultFormat.Transactional); - - using (var testHarness = new BoltTestHarness()) - { - INode start = new TestNode - { - Id = 1337, - Properties = new Dictionary() - { - {"Ids", new List() {1, 2, 3}} - } - }; - IDictionary resultMap = new Dictionary() - { - {"Node", 3}, - {"Count", 1} - }; - - var recordMock = new Mock(); - recordMock - .Setup(r => r["Node"]) - .Returns(resultMap); - recordMock - .Setup(r => r["Start"]) - .Returns(start); - recordMock - .Setup(r => r.Keys) - .Returns(new[] { "Node", "Start" }); - - var testStatementResult = new TestStatementResult(new[] { "Node", "Start" }, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - // the anon type - var dummy = new - { - Node = new Dictionary(), - Start = new ObjectWithIds() - }; - var anonType = dummy.GetType(); - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var genericGetCypherResults = typeof(IRawGraphClient).GetMethod(nameof(graphClient.ExecuteGetCypherResults)); - var anonymousGetCypherResults = genericGetCypherResults.MakeGenericMethod(anonType); - var genericResults = (IEnumerable)anonymousGetCypherResults.Invoke(graphClient, new object[] { cypherQuery }); - - var results = genericResults.Cast().ToArray(); - - //Assert - Assert.Equal(1, results.Length); - - var startNode = (ObjectWithIds)anonType.GetProperty(nameof(dummy.Start)).GetValue(results.First(), null); - startNode.Ids.Count.Should().Be(3); - startNode.Ids[0].Should().Be(1); - startNode.Ids[1].Should().Be(2); - startNode.Ids[2].Should().Be(3); - - var nodeWrapper = (Dictionary)anonType.GetProperty(nameof(dummy.Node)).GetValue(results.First(), null); - nodeWrapper.Keys.Count.Should().Be(2); - nodeWrapper["Node"].Should().Be(3); - nodeWrapper["Count"].Should().Be(1); - } - } - - [Fact] - public void ShouldDeserializeMapWithAnonymousReturn() - { - // simulates the following query - const string queryText = "MATCH (start:Node) WITH {Node: start, Count: 1} AS Node, start RETURN Node, start"; - - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, - CypherResultFormat.Transactional); - - using (var testHarness = new BoltTestHarness()) - { - INode start = new TestNode - { - Id = 1337, - Properties = new Dictionary() - { - {"Ids", new List() {1, 2, 3}} - } - }; - IDictionary resultMap = new Dictionary() - { - {"Node", start}, - {"Count", 1} - }; - - var recordMock = new Mock(); - recordMock - .Setup(r => r["Node"]) - .Returns(resultMap); - recordMock - .Setup(r => r["Start"]) - .Returns(start); - recordMock - .Setup(r => r.Keys) - .Returns(new[] { "Node", "Start" }); - - var testStatementResult = new TestStatementResult(new[] { "Node", "Start" }, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - // the anon type - var dummy = new - { - Node = new ObjectWithNodeWithIds(), - Start = new ObjectWithIds() - }; - var anonType = dummy.GetType(); - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var genericGetCypherResults = typeof(IRawGraphClient).GetMethod(nameof(graphClient.ExecuteGetCypherResults)); - var anonymousGetCypherResults = genericGetCypherResults.MakeGenericMethod(anonType); - var genericResults = (IEnumerable)anonymousGetCypherResults.Invoke(graphClient, new object[] { cypherQuery }); - - var results = genericResults.Cast().ToArray(); - - //Assert - Assert.Equal(1, results.Length); - - var startNode = (ObjectWithIds)anonType.GetProperty(nameof(dummy.Start)).GetValue(results.First(), null); - startNode.Ids.Count.Should().Be(3); - startNode.Ids[0].Should().Be(1); - startNode.Ids[1].Should().Be(2); - startNode.Ids[2].Should().Be(3); - - var nodeWrapper = (ObjectWithNodeWithIds)anonType.GetProperty(nameof(dummy.Node)).GetValue(results.First(), null); - nodeWrapper.Count.Should().Be(1); - startNode = nodeWrapper.Node; - - startNode.Ids.Count.Should().Be(3); - startNode.Ids[0].Should().Be(1); - startNode.Ids[1].Should().Be(2); - startNode.Ids[2].Should().Be(3); - } - - } - - [Fact] - public void ShouldDeserializeCollectionsWithAnonymousReturn() - { - // Arrange - const string queryText = @"MATCH (start:Node) RETURN [start.Id, start.Id] AS Ids"; - - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Transactional); - - using (var testHarness = new BoltTestHarness()) - { - var recordMock = new Mock(); - recordMock - .Setup(r => r["Ids"]) - .Returns(new[] {1, 2, 3}); - recordMock - .Setup(r => r.Keys) - .Returns(new[] { "Ids" }); - - var testStatementResult = new TestStatementResult(new[] { "Ids" }, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - //Session mock??? - var dummy = new - { - Ids = new List() - }; - var anonType = dummy.GetType(); - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var genericGetCypherResults = typeof(IRawGraphClient).GetMethod(nameof(graphClient.ExecuteGetCypherResults)); - var anonymousGetCypherResults = genericGetCypherResults.MakeGenericMethod(anonType); - var genericResults = (IEnumerable)anonymousGetCypherResults.Invoke(graphClient, new object[] {cypherQuery}); - - var results = genericResults.Cast().ToArray(); - - //Assert - Assert.Equal(1, results.Length); - var ids = (List)anonType.GetProperty(nameof(dummy.Ids)).GetValue(results.First(), null); - ids.Count.Should().Be(3); - ids[0].Should().Be(1); - ids[1].Should().Be(2); - ids[2].Should().Be(3); - } - } - - [Fact] - public void ShouldDeserializePathsResultAsSetBased() - { - // Arrange - const string queryText = @"MATCH (start:Node {Id:$p0}),(end:Node {Id: $p1}), p = shortestPath((start)-[*..5]->(end)) RETURN p"; - - var parameters = new Dictionary - { - {"p0", 215}, - {"p1", 219} - }; - - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, CypherResultFormat.Rest); - - using (var testHarness = new BoltTestHarness()) - { - var recordMock = new Mock(); - recordMock - .Setup(r => r["p"]) - .Returns(new TestPath {End = new TestNode{Id = 1}, Start = new TestNode{Id=2}, Relationships = new List {new TestRelationship()}, Nodes = new List {new TestNode(), new TestNode() } }); - recordMock - .Setup(r => r.Keys) - .Returns(new[] {"p"}); - - var testStatementResult = new TestStatementResult(new[] {"p"}, recordMock.Object); - testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - - //Session mock??? - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); - var results = graphClient.ExecuteGetCypherResults(cypherQuery).ToArray(); - - //Assert - Assert.IsAssignableFrom>(results); - Assert.Equal(1, results.First().Length); - results.First().Nodes.Count().Should().Be(2); - results.First().Relationships.Count().Should().Be(1); - results.First().End.Id.Should().Be(1); - results.First().Start.Id.Should().Be(2); - } - } - - - - // [Fact] - // 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.IsAssignableFrom>(results); - // - // var resultsArray = results.ToArray(); - // Assert.Equal(3, resultsArray.Count()); - // - // var firstResult = resultsArray[0]; - // Assert.Equal("HOSTS", firstResult.RelationshipType); - // Assert.Equal("foo", firstResult.Name); - // Assert.Equal(44321, firstResult.UniqueId); - // - // var secondResult = resultsArray[1]; - // Assert.Equal("LIKES", secondResult.RelationshipType); - // Assert.Equal("bar", secondResult.Name); - // Assert.Equal(44311, secondResult.UniqueId); - // - // var thirdResult = resultsArray[2]; - // Assert.Equal("HOSTS", thirdResult.RelationshipType); - // Assert.Equal("baz", thirdResult.Name); - // Assert.Equal(42586, thirdResult.UniqueId); - // } - // } - // - // [Fact] - // 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.IsAssignableFrom>(results); - // - // var resultsArray = results.ToArray(); - // Assert.Equal(1, resultsArray.Count()); - // - // var firstResult = resultsArray[0]; - // Assert.Equal(358, firstResult.ColumnA.Reference.Id); - // Assert.Equal("BHP", firstResult.ColumnA.Data.Bar); - // Assert.Equal("1", firstResult.ColumnA.Data.Baz); - // - // var collectedResults = firstResult.ColumnBFromCollect.ToArray(); - // Assert.Equal(2, collectedResults.Count()); - // - // var firstCollectedResult = collectedResults[0]; - // Assert.Equal(362, firstCollectedResult.Reference.Id); - // Assert.Equal("Board", firstCollectedResult.Data.OpportunityType); - // Assert.Equal("Foo", firstCollectedResult.Data.Description); - // - // var secondCollectedResult = collectedResults[1]; - // Assert.Equal(359, secondCollectedResult.Reference.Id); - // Assert.Equal("Executive", secondCollectedResult.Data.OpportunityType); - // Assert.Equal("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; } - // } - // - // [Fact] - // 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.IsAssignableFrom>(results); - // - // var resultsArray = results.ToArray(); - // Assert.Equal(3, resultsArray.Count()); - // - // var firstResult = resultsArray[0]; - // Assert.Equal(0, firstResult.Fooness.Reference.Id); - // Assert.Equal("bar", firstResult.Fooness.Data.Bar); - // Assert.Equal("baz", firstResult.Fooness.Data.Baz); - // Assert.Equal("HOSTS", firstResult.RelationshipType); - // Assert.Equal("foo", firstResult.Name); - // Assert.Equal(44321, firstResult.UniqueId); - // - // var secondResult = resultsArray[1]; - // Assert.Equal(2, secondResult.Fooness.Reference.Id); - // Assert.Equal("bar", secondResult.Fooness.Data.Bar); - // Assert.Equal("baz", secondResult.Fooness.Data.Baz); - // Assert.Equal("LIKES", secondResult.RelationshipType); - // Assert.Equal("bar", secondResult.Name); - // Assert.Equal(44311, secondResult.UniqueId); - // - // var thirdResult = resultsArray[2]; - // Assert.Equal(12, thirdResult.Fooness.Reference.Id); - // Assert.Equal("bar", thirdResult.Fooness.Data.Bar); - // Assert.Equal("baz", thirdResult.Fooness.Data.Baz); - // Assert.Equal("HOSTS", thirdResult.RelationshipType); - // Assert.Equal("baz", thirdResult.Name); - // Assert.Equal(42586, thirdResult.UniqueId); - // } - // } - // - // [Fact] - // 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.IsAssignableFrom>(results); - // - // var resultsArray = results.ToArray(); - // Assert.Equal(3, resultsArray.Count()); - // - // var firstResult = resultsArray[0]; - // Assert.Equal("bar", firstResult.Fooness.Bar); - // Assert.Equal("baz", firstResult.Fooness.Baz); - // Assert.Equal("HOSTS", firstResult.RelationshipType); - // Assert.Equal("foo", firstResult.Name); - // Assert.Equal(44321, firstResult.UniqueId); - // - // var secondResult = resultsArray[1]; - // Assert.Equal("bar", secondResult.Fooness.Bar); - // Assert.Equal("baz", secondResult.Fooness.Baz); - // Assert.Equal("LIKES", secondResult.RelationshipType); - // Assert.Equal("bar", secondResult.Name); - // Assert.Equal(44311, secondResult.UniqueId); - // - // var thirdResult = resultsArray[2]; - // Assert.Equal("bar", thirdResult.Fooness.Bar); - // Assert.Equal("baz", thirdResult.Fooness.Baz); - // Assert.Equal("HOSTS", thirdResult.RelationshipType); - // Assert.Equal("baz", thirdResult.Name); - // Assert.Equal(42586, thirdResult.UniqueId); - // } - // } - // - // [Fact] - // 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.IsAssignableFrom>(results); - // - // var resultsArray = results.ToArray(); - // Assert.Equal(3, resultsArray.Count()); - // - // var firstResult = resultsArray[0]; - // Assert.Equal(0, firstResult.Fooness.Reference.Id); - // Assert.Equal("bar", firstResult.Fooness.Data.Bar); - // Assert.Equal("baz", firstResult.Fooness.Data.Baz); - // Assert.Equal("HOSTS", firstResult.RelationshipType); - // Assert.Equal("foo", firstResult.Name); - // Assert.Equal(44321, firstResult.UniqueId); - // - // var secondResult = resultsArray[1]; - // Assert.Equal(1, secondResult.Fooness.Reference.Id); - // Assert.Equal("bar", secondResult.Fooness.Data.Bar); - // Assert.Equal("baz", secondResult.Fooness.Data.Baz); - // Assert.Equal("LIKES", secondResult.RelationshipType); - // Assert.Equal("bar", secondResult.Name); - // Assert.Equal(44311, secondResult.UniqueId); - // - // var thirdResult = resultsArray[2]; - // Assert.Equal(2, thirdResult.Fooness.Reference.Id); - // Assert.Equal("bar", thirdResult.Fooness.Data.Bar); - // Assert.Equal("baz", thirdResult.Fooness.Data.Baz); - // Assert.Equal("HOSTS", thirdResult.RelationshipType); - // Assert.Equal("baz", thirdResult.Name); - // Assert.Equal(42586, thirdResult.UniqueId); - // } - // } - // - // [Fact] - // 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.Equal("SyntaxException: expected START or CREATE\n'bad query'\n ^", ex.Message); - // Assert.Equal("expected START or CREATE\n'bad query'\n ^", ex.NeoMessage); - // Assert.Equal("SyntaxException", ex.NeoExceptionName); - // Assert.Equal("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)" - // }; - // Assert.Equal(expectedStack, ex.NeoStackTrace); - // } - // } - // - // [Fact] - // public void SendsCommandWithCorrectTimeout() - // { - // const int expectedMaxExecutionTime = 100; - // - // 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.Transactional ,maxExecutionTime: expectedMaxExecutionTime); - // var cypherApiQuery = new CypherApiQuery(cypherQuery); - // - // using (var testHarness = new RestTestHarness - // { - // { - // MockRequest.Get(""), - // MockResponse.NeoRoot() - // }, - // { - // 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 httpClient = testHarness.GenerateHttpClient(testHarness.BaseUri); - // var graphClient = new GraphClient(new Uri(testHarness.BaseUri), httpClient); - // graphClient.Connect(); - // - // httpClient.ClearReceivedCalls(); - // ((IRawGraphClient)graphClient).ExecuteGetCypherResults(cypherQuery); - // - // var call = httpClient.ReceivedCalls().Single(); - // var requestMessage = (HttpRequestMessage)call.GetArguments()[0]; - // var maxExecutionTimeHeader = requestMessage.Headers.Single(h => h.Key == "max-execution-time"); - // Assert.Equal(expectedMaxExecutionTime.ToString(CultureInfo.InvariantCulture), maxExecutionTimeHeader.Value.Single()); - // } - // } - // - // [Fact] - // public void DoesntSendMaxExecutionTime_WhenNotAddedToQuery() - // { - // const string queryText = @"START d=node($p0), e=node($p1) - // MATCH p = allShortestPaths( d-[*..15]-e ) - // RETURN p"; - // - // var parameters = new Dictionary - // { - // {"p0", 215}, - // {"p1", 219} - // }; - // - // var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); - // var cypherApiQuery = new CypherApiQuery(cypherQuery); - // - // using (var testHarness = new RestTestHarness - // { - // { - // MockRequest.Get(""), - // MockResponse.NeoRoot() - // }, - // { - // 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 httpClient = testHarness.GenerateHttpClient(testHarness.BaseUri); - // var graphClient = new GraphClient(new Uri(testHarness.BaseUri), httpClient); - // graphClient.Connect(); - // - // httpClient.ClearReceivedCalls(); - // ((IRawGraphClient)graphClient).ExecuteGetCypherResults(cypherQuery); - // - // var call = httpClient.ReceivedCalls().Single(); - // var requestMessage = (HttpRequestMessage)call.GetArguments()[0]; - // Assert.False(requestMessage.Headers.Any(h => h.Key == "max-execution-time")); - // } - // } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/BoltTestHarness.cs b/Neo4jClient.Tests.Shared/BoltTestHarness.cs deleted file mode 100644 index d3bb8a5a8..000000000 --- a/Neo4jClient.Tests.Shared/BoltTestHarness.cs +++ /dev/null @@ -1,39 +0,0 @@ -using System; -using System.Collections.Generic; -using Moq; -using Neo4j.Driver.V1; - -namespace Neo4jClient.Test -{ - public class BoltTestHarness : IDisposable - { - public BoltTestHarness() - { - MockSession.Setup(s => s.Run("CALL dbms.components()")).Returns(new Extensions.BoltGraphClientTests.ServerInfo()); - MockDriver.Setup(d => d.Session(It.IsAny())).Returns(MockSession.Object); - MockDriver.Setup(d => d.Session(It.IsAny(), It.IsAny>())).Returns(MockSession.Object); - MockDriver.Setup(d => d.Session(It.IsAny>())).Returns(MockSession.Object); - MockDriver.Setup(d => d.Uri).Returns(new Uri("bolt://localhost")); - } - - public Mock MockDriver { get; } = new Mock(); - public Mock MockSession { get; } = new Mock(); - - public void Dispose() - { - } - - public void SetupCypherRequestResponse(string request, IDictionary cypherQueryQueryParameters, IStatementResult response) - { - MockSession.Setup(s => s.Run(request, It.IsAny>())).Returns(response); - MockSession.Setup(s => s.WriteTransaction(It.IsAny>())).Returns(response); - } - - public IRawGraphClient CreateAndConnectBoltGraphClient() - { - var bgc = new BoltGraphClient(MockDriver.Object); - bgc.Connect(); - return bgc; - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryStartTests.cs b/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryStartTests.cs deleted file mode 100644 index d6b898690..000000000 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryStartTests.cs +++ /dev/null @@ -1,279 +0,0 @@ -using System; -using NSubstitute; -using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Cypher -{ - public class CypherFluentQueryStartTests : IClassFixture - { - [Fact] - [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.Equal("START n=node:`nodes`(name = $p0)\r\nRETURN n", query.QueryText); - Assert.Equal("A", query.QueryParameters["p0"]); - } - - [Fact] - [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.Equal("START a=node:`nodes`(name = $p0), b=node:`nodes`(name = $p1)\r\nRETURN a", query.QueryText); - Assert.Equal("A", query.QueryParameters["p0"]); - Assert.Equal("B", query.QueryParameters["p1"]); - } - - [Fact] - [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.Equal("START a=node:`nodes`(name = $p0), b=node($p1)\r\nRETURN a", query.QueryText); - Assert.Equal("A", query.QueryParameters["p0"]); - Assert.Equal(2L, query.QueryParameters["p1"]); - } - - [Fact] - [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.Equal("START a=node:`nodes`(name = $p0), b=node($p1), c=node:`nodes`(name = $p2)\r\nRETURN a", query.QueryText); - Assert.Equal("A", query.QueryParameters["p0"]); - Assert.Equal(2L, query.QueryParameters["p1"]); - Assert.Equal("C", query.QueryParameters["p2"]); - } - - [Fact] - [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.Equal("START a=node($p0), b=node:`nodes`(name = $p1)\r\nRETURN a", query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); - Assert.Equal("B", query.QueryParameters["p1"]); - } - - [Fact] - [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.Equal("START n=node:`nodes`($p0)\r\nRETURN n", query.QueryText); - Assert.Equal("*.*", query.QueryParameters["p0"]); - } - - [Fact] - 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.Equal("START n=node(*)\r\nRETURN n", query.QueryText); - Assert.Equal(0, query.QueryParameters.Count); - } - - [Fact] - 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.Equal("START n=node(*), b=node(*)\r\nRETURN n", query.QueryText); - Assert.Equal(0, query.QueryParameters.Count); - } - - [Fact] - //[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.Equal(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.Equal("JobTypes", query.QueryParameters["p0"]); - } - - [Fact] - //[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.Equal("START n1=node($p0), n2=node($p1)", query.QueryText); - Assert.Equal(2, query.QueryParameters.Count); - Assert.Equal(1L, query.QueryParameters["p0"]); - Assert.Equal(2L, query.QueryParameters["p1"]); - } - - [Fact] - [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.Equal("START n1=node($p0), n2=node($p1)", query.QueryText); - Assert.Equal(2, query.QueryParameters.Count); - Assert.Equal(1L, query.QueryParameters["p0"]); - Assert.Equal(2L, query.QueryParameters["p1"]); - } - - [Fact] - public void SingleNodeByStaticReferenceInAnonymousType() - { - // START n1=node(1) - - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Start(new - { - n1 = (NodeReference)1 - }) - .Query; - - Assert.Equal("START n1=node($p0)", query.QueryText); - Assert.Equal(1, query.QueryParameters.Count); - Assert.Equal(1L, query.QueryParameters["p0"]); - } - - public class JobType - { - public string Id { get; set; } - public string Name { get; set; } - } - - public class JobSpecialty - { - } - } -} diff --git a/Neo4jClient.Tests.Shared/Cypher/DocumentationExamples.cs b/Neo4jClient.Tests.Shared/Cypher/DocumentationExamples.cs deleted file mode 100644 index 8f8b6c11f..000000000 --- a/Neo4jClient.Tests.Shared/Cypher/DocumentationExamples.cs +++ /dev/null @@ -1,301 +0,0 @@ -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/Neo4jClient.Tests.Shared/GraphClientTests/CreateIndexTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/CreateIndexTests.cs deleted file mode 100644 index a8e172d67..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/CreateIndexTests.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class CreateIndexTests : IClassFixture - { - [Theory] - [InlineData( - IndexFor.Node, - IndexProvider.lucene, - IndexType.fulltext, - "/index/node", - @"{ - 'name': 'foo', - 'config': { 'type': 'fulltext', 'provider': 'lucene' } - }")] - [InlineData( - IndexFor.Node, - IndexProvider.lucene, - IndexType.exact, - "/index/node", - @"{ - 'name': 'foo', - 'config': { 'type': 'exact', 'provider': 'lucene' } - }")] - [InlineData( - IndexFor.Relationship, - IndexProvider.lucene, - IndexType.fulltext, - "/index/relationship", - @"{ - 'name': 'foo', - 'config': { 'type': 'fulltext', 'provider': 'lucene' } - }")] - [InlineData( - 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); - } - } - - [Theory] - [InlineData( - IndexFor.Node, - IndexProvider.lucene, - IndexType.fulltext, - "/index/node", - @"{ - 'name': 'foo', - 'config': { 'type': 'fulltext', 'provider': 'lucene' } - }")] - [InlineData( - IndexFor.Node, - IndexProvider.lucene, - IndexType.exact, - "/index/node", - @"{ - 'name': 'foo', - 'config': { 'type': 'exact', 'provider': 'lucene' } - }")] - [InlineData( - IndexFor.Relationship, - IndexProvider.lucene, - IndexType.fulltext, - "/index/relationship", - @"{ - 'name': 'foo', - 'config': { 'type': 'fulltext', 'provider': 'lucene' } - }")] - [InlineData( - IndexFor.Relationship, - IndexProvider.lucene, - IndexType.exact, - "/index/relationship", - @"{ - 'name': 'foo', - 'config': { 'type': 'exact', 'provider': 'lucene' } - }")] - public void ShouldThrowExceptionIfHttpCodeIsNot201( - 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 - }; - Assert.Throws(() => graphClient.CreateIndex("foo", indexConfiguration, indexFor)); - } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/CreateNodeTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/CreateNodeTests.cs deleted file mode 100644 index 76e2caddc..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/CreateNodeTests.cs +++ /dev/null @@ -1,621 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; -using System.Net; -using System.Net.Http; -using FluentAssertions; -using Xunit; -using Neo4jClient.ApiModels; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class CreateNodeTests : IClassFixture - { - [Fact] - public void ShouldThrowArgumentNullExceptionForNullNode() - { - var client = new GraphClient(new Uri("http://foo")); - Assert.Throws(() => client.Create(null)); - } - - [Fact] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - Assert.Throws(() => client.Create(new object())); - } - - [Fact] - 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"}; - Assert.Throws(() => graphClient.Create(testNode)); - } - - [Fact] - public void ShouldThrowArgumentExceptionForPreemptivelyWrappedNode() - { - var graphClient = new GraphClient(new Uri("http://foo/db/data"), null); - var ex = Assert.Throws(() => graphClient.Create((Node)null)); - ex.Message.Should().Be("You're trying to pass in a Node instance. Just pass the TestNode instance instead.\r\nParameter name: node"); - } - - [Fact] - 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(); - var ex = Assert.Throws(() => graphClient.Create(new NestedTestNode() - { - Foo = "foo", - TestNode2 = new TestNode2() {Bar = "bar", Foo = "foo"} - })); - ex.Message.Should().Be("PropertyValueException: Could not set property \"TestNode2\", unsupported type: {Foo=foo, Bar=bar}"); - } - - [Fact] - 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(); - } - - [Fact] - 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(); - } - - [Fact] - 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(); - } - - [Fact] - 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.Equal(760, node.Id); - testHarness.AssertRequestConstraintsAreMet(); - } - - [Fact] - 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.Equal(2157483647, node.Id); - testHarness.AssertRequestConstraintsAreMet(); - } - - [Fact] - 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.NotNull(((IGremlinQuery)node).Client); - } - - [Fact] - 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(); - } - - [Fact] - 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") - } - } - }); - } - - [Fact] - 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/Neo4jClient.Tests.Shared/GraphClientTests/CreateRelationshipTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/CreateRelationshipTests.cs deleted file mode 100644 index 5d2970020..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/CreateRelationshipTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -using System; -using System.Net; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class CreateRelationshipTests : IClassFixture - { - [Fact] - 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.IsAssignableFrom(relationshipReference); - Assert.IsNotType>(relationshipReference); - Assert.Equal(38, relationshipReference.Id); - } - } - - [Fact] - 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.Equal(graphClient, ((IAttachedReference)relationshipReference).Client); - } - } - - [Fact] - public void ShouldThrowArgumentNullExceptionForNullNodeReference() - { - var client = new GraphClient(new Uri("http://foo")); - Assert.Throws(() => client.CreateRelationship((NodeReference)null, new TestRelationship(10))); - } - - [Fact] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - Assert.Throws(() => client.CreateRelationship(new NodeReference(5), new TestRelationship(10))); - } - - [Fact] - public void ShouldThrowNotSupportedExceptionForIncomingRelationship() - { - using (var testHarness = new RestTestHarness()) - { - var client = testHarness.CreateAndConnectGraphClient(); - Assert.Throws(() => 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/Neo4jClient.Tests.Shared/GraphClientTests/DeleteIndexTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/DeleteIndexTests.cs deleted file mode 100644 index 8c83e4d92..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/DeleteIndexTests.cs +++ /dev/null @@ -1,27 +0,0 @@ -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class DeleteIndexTests : IClassFixture - { - [Fact] - 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/Neo4jClient.Tests.Shared/GraphClientTests/DeleteRelationshipTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/DeleteRelationshipTests.cs deleted file mode 100644 index fdddc5a27..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/DeleteRelationshipTests.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using FluentAssertions; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class DeleteRelationshipTests : IClassFixture - { - [Fact] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - Assert.Throws(() => client.DeleteRelationship(123)); - } - - [Fact] - public void ShouldDeleteRelationship() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Delete("/relationship/456"), - MockResponse.Http(204) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.DeleteRelationship(456); - } - } - - [Fact] - public void ShouldThrowExceptionWhenDeleteFails() - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Delete("/relationship/456"), - MockResponse.Http(404) - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var ex = Assert.Throws(() => graphClient.DeleteRelationship(456)); - ex.Message.Should().Be("Unable to delete the relationship. The response status was: 404 NotFound"); - } - } - } -} diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/GetNodeTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/GetNodeTests.cs deleted file mode 100644 index efec908c8..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/GetNodeTests.cs +++ /dev/null @@ -1,232 +0,0 @@ -using System; -using System.Net; -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class GetNodeTests : IClassFixture - { - [Fact] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - Assert.Throws(() => client.Get((NodeReference)123)); - } - - [Fact] - 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.Equal(456, node.Reference.Id); - Assert.Equal("foo", node.Data.Foo); - Assert.Equal("bar", node.Data.Bar); - Assert.Equal("baz", node.Data.Baz); - } - } - - [Fact] - 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.Equal(21484836470, node.Reference.Id); - Assert.Equal("foo", node.Data.Foo); - Assert.Equal("bar", node.Data.Bar); - Assert.Equal("baz", node.Data.Baz); - } - } - - [Fact] - 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.Equal(456, node.Reference.Id); - Assert.Equal("foo", node.Data.Foo); - Assert.Equal(TestEnum.Value1, node.Data.Status); - } - } - - [Fact] - 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.Equal(graphClient, ((IGremlinQuery) node.Reference).Client); - } - } - - [Fact] - 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.Null(node); - } - } - - [Fact] - 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.NotNull(node.Data.DateOffSet); - Assert.Equal("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/Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs deleted file mode 100644 index b67dacd6b..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteGetAllNodesGremlinTests.cs +++ /dev/null @@ -1,149 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using Xunit; -using System.Linq; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.GraphClientTests.Gremlin -{ - - public class ExecuteGetAllNodesGremlinTests : IClassFixture - { - [Fact] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - Assert.Throws(() => client.ExecuteGetAllNodesGremlin("", null)); - } - - public class Foo - { - public string Bar { get; set; } - public string Baz { get; set; } - } - - [Fact] - 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.Equal(2, nodes.Count()); - Assert.Equal(5, nodes.ElementAt(0).Reference.Id); - Assert.Equal("bar", nodes.ElementAt(0).Data.Bar); - Assert.Equal("baz", nodes.ElementAt(0).Data.Baz); - Assert.Equal(6, nodes.ElementAt(1).Reference.Id); - Assert.Equal("123", nodes.ElementAt(1).Data.Bar); - Assert.Equal("456", nodes.ElementAt(1).Data.Baz); - } - } - - [Fact] - 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.Equal(0, nodes.Count()); - } - } - - [Fact] - 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.Equal(GraphClient.GremlinPluginUnavailable, ex.Message); - } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs deleted file mode 100644 index 554cb82cf..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteGetAllRelationshipsGremlinTests.cs +++ /dev/null @@ -1,287 +0,0 @@ -using System; -using System.Net; -using Xunit; -using System.Linq; -using Neo4jClient.ApiModels.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.GraphClientTests.Gremlin -{ - - public class ExecuteGetAllRelationshipsGremlinTests : IClassFixture - { - [Fact] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - Assert.Throws(() => client.ExecuteGetAllRelationshipsGremlin("", null)); - } - - [Fact] - 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.Equal(1, relationships.Count()); - Assert.Equal(456, relationships.ElementAt(0).Reference.Id); - Assert.Equal(123, relationships.ElementAt(0).StartNodeReference.Id); - Assert.Equal(789, relationships.ElementAt(0).EndNodeReference.Id); - Assert.Equal("KNOWS", relationships.ElementAt(0).TypeKey); - } - } - - [Fact] - 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.Equal(1, relationships.Count()); - Assert.Equal(456, relationships.ElementAt(0).Reference.Id); - Assert.Equal(123, relationships.ElementAt(0).StartNodeReference.Id); - Assert.Equal(789, relationships.ElementAt(0).EndNodeReference.Id); - Assert.Equal("KNOWS", relationships.ElementAt(0).TypeKey); - Assert.Equal("Foo", relationships.ElementAt(0).Data.Foo); - Assert.Equal("Bar", relationships.ElementAt(0).Data.Bar); - } - } - - class TestPayload - { - public string Foo { get; set; } - public string Bar { get; set; } - } - - [Fact] - 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.Equal(0, nodes.Count()); - } - } - - [Fact] - 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.Equal(1, relationships.Count()); - Assert.Equal(21484836470, relationships.ElementAt(0).Reference.Id); - Assert.Equal(123, relationships.ElementAt(0).StartNodeReference.Id); - Assert.Equal(789, relationships.ElementAt(0).EndNodeReference.Id); - Assert.Equal("KNOWS", relationships.ElementAt(0).TypeKey); - } - } - - [Fact] - 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.Equal(1, relationships.Count()); - Assert.Equal(456, relationships.ElementAt(0).Reference.Id); - Assert.Equal(21484836470, relationships.ElementAt(0).StartNodeReference.Id); - Assert.Equal(789, relationships.ElementAt(0).EndNodeReference.Id); - Assert.Equal("KNOWS", relationships.ElementAt(0).TypeKey); - } - } - - [Fact] - 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.Equal(1, relationships.Count()); - Assert.Equal(456, relationships.ElementAt(0).Reference.Id); - Assert.Equal(123, relationships.ElementAt(0).StartNodeReference.Id); - Assert.Equal(21484836470, relationships.ElementAt(0).EndNodeReference.Id); - Assert.Equal("KNOWS", relationships.ElementAt(0).TypeKey); - } - } - - [Fact] - 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.Equal(GraphClient.GremlinPluginUnavailable, ex.Message); - } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs deleted file mode 100644 index a2a2249ae..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/Gremlin/ExecuteScalarGremlinTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Net; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests.Gremlin -{ - - public class ExecuteScalarGremlinTests : IClassFixture - { - [Fact] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() - { - var client = new GraphClient(new Uri("http://foo")); - Assert.Throws(() => client.ExecuteScalarGremlin("", null)); - } - - [Fact] - 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.Equal(1, int.Parse(node)); - } - } - - [Fact] - 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.Equal(GraphClient.GremlinPluginUnavailable, ex.Message); - } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/IndexExistsTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/IndexExistsTests.cs deleted file mode 100644 index fe528b977..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/IndexExistsTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System.Net; -using FluentAssertions; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class IndexExistsTests : IClassFixture - { - [Theory] - [InlineData(IndexFor.Node, "/index/node/MyIndex", HttpStatusCode.OK, true)] - [InlineData(IndexFor.Node, "/index/node/MyIndex", HttpStatusCode.NotFound, false)] - [InlineData(IndexFor.Relationship, "/index/relationship/MyIndex", HttpStatusCode.OK, true)] - [InlineData(IndexFor.Relationship, "/index/relationship/MyIndex", HttpStatusCode.NotFound, false)] - public void ShouldReturnIfIndexIsFound( - IndexFor indexFor, - string indexPath, - HttpStatusCode httpStatusCode, bool expectedResult) - { - using (var testHarness = new RestTestHarness - { - { - MockRequest.Get(indexPath), - MockResponse.Json(httpStatusCode, "") - } - }) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.CheckIndexExists("MyIndex", indexFor).Should().Be(expectedResult); - } - } - } -} diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/LookupIndexTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/LookupIndexTests.cs deleted file mode 100644 index a07b62d8f..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/LookupIndexTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Xunit; -using System.Linq; -using System.Net; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class LookupIndexTests : IClassFixture - { - [Fact] - 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.Equal(1, results.Count()); - var result = results[0]; - Assert.Equal(456, result.Reference.Id); - Assert.Equal(1000, result.Data.Id); - } - } - - public class UserTestNode - { - public long Id { get; set; } - public string Name { get; set; } - } - } -} diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/QueryNodeIndexTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/QueryNodeIndexTests.cs deleted file mode 100644 index 4deccecc1..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/QueryNodeIndexTests.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Linq; -using System.Net; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class QueryNodeIndexTests : IClassFixture - { - [Fact] - [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.Equal(1, results.Count()); - var result = results[0]; - Assert.Equal(456, result.Reference.Id); - Assert.Equal("Foo", result.Data.Name); - } - } - - public class TestNode - { - public string Name { get; set; } - } - } -} diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/ReIndexNodesTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/ReIndexNodesTests.cs deleted file mode 100644 index 9a6f9412d..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/ReIndexNodesTests.cs +++ /dev/null @@ -1,194 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class ReIndexNodesTests : IClassFixture - { - [Fact] - 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 - - } - } - - [Fact] - 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 - - } - } - - [Fact] - 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 - - } - } - - [Fact] - 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 - - } - } - } -} diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/ReIndexRelationshipsTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/ReIndexRelationshipsTests.cs deleted file mode 100644 index 48f3b48e6..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/ReIndexRelationshipsTests.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Net; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class ReIndexRelationshipsTests : IClassFixture - { - [Fact] - 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 - - } - } - - [Fact] - 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 - - } - } - - [Fact] - 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 - - } - } - - [Fact] - 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 - - } - } - - 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/Neo4jClient.Tests.Shared/GraphClientTests/RootNodeTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/RootNodeTests.cs deleted file mode 100644 index 4d793ced9..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/RootNodeTests.cs +++ /dev/null @@ -1,33 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class RootNodeTests : IClassFixture - { - [Fact] - public void RootNodeShouldHaveReferenceBackToClient() - { - using (var testHarness = new RestTestHarness()) - { - var client = testHarness.CreateAndConnectGraphClient(); - var rootNode = client.RootNode; - Assert.Equal(client, ((IGremlinQuery) rootNode).Client); - } - } - - [Fact] - public void RootNodeShouldSupportGremlinQueries() - { - using (var testHarness = new RestTestHarness()) - { - var client = testHarness.CreateAndConnectGraphClient(); - var rootNode = client.RootNode; - Assert.Equal("g.v(p0)", ((IGremlinQuery) rootNode).QueryText); - Assert.Equal(123L, ((IGremlinQuery) rootNode).QueryParameters["p0"]); - } - } - } -} diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/ServerVersionTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/ServerVersionTests.cs deleted file mode 100644 index b5f890a59..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/ServerVersionTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class ServerVersionTests : IClassFixture - { - [Fact] - public void ShouldParse15M02Version() - { - using (var testHarness = new RestTestHarness - { - { MockRequest.Get(""), MockResponse.NeoRoot() } - }) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - Assert.Equal("1.5.0.2", graphClient.ServerVersion.ToString()); - } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/UpdateNodeTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/UpdateNodeTests.cs deleted file mode 100644 index 969ff131c..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/UpdateNodeTests.cs +++ /dev/null @@ -1,299 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class UpdateNodeTests : IClassFixture - { - [Fact] - 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.Equal("fooUpdated", nodeToUpdate.Foo); - Assert.Equal("bazUpdated", nodeToUpdate.Baz); - Assert.Equal("bar", nodeToUpdate.Bar); - } - } - - [Fact] - 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.Equal(pocoReference, updatedNode.Reference); - Assert.Equal("fooUpdated", updatedNode.Data.Foo); - Assert.Equal("bazUpdated", updatedNode.Data.Baz); - Assert.Equal("bar", updatedNode.Data.Bar); - } - } - - [Fact] - 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.Equal("fooUpdated", nodeToUpdate.Foo); - Assert.Equal("bazUpdated", nodeToUpdate.Baz); - Assert.Equal("bar", nodeToUpdate.Bar); - } - } - - [Fact] - 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.True(hasChanged); - } - } - - [Fact] - 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); - } - } - - [Fact] - 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/Neo4jClient.Tests.Shared/GraphClientTests/UpdateRelationshipTests.cs b/Neo4jClient.Tests.Shared/GraphClientTests/UpdateRelationshipTests.cs deleted file mode 100644 index 089bcb472..000000000 --- a/Neo4jClient.Tests.Shared/GraphClientTests/UpdateRelationshipTests.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System.Net; -using Neo4jClient.Test.Fixtures; -using Xunit; - -namespace Neo4jClient.Test.GraphClientTests -{ - - public class UpdateRelationshipTests : IClassFixture - { - [Fact] - 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"; - } - ); - } - } - - [Fact] - 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/Neo4jClient.Tests.Shared/Gremlin/AggregateStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/AggregateStepTests.cs deleted file mode 100644 index d9af266eb..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/AggregateStepTests.cs +++ /dev/null @@ -1,63 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class AggregateStepTests : IClassFixture - { - [Fact] - public void AggregateVShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).AggregateV("foo"); - Assert.Equal("foo = [];g.v(p0).aggregate(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void AggregateVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).AggregateV("foo"); - Assert.IsAssignableFrom>(query); - } - - [Fact] - public void AggregateEShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).AggregateE("foo"); - Assert.Equal("foo = [];g.v(p0).aggregate(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void AggregateEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).AggregateE("foo"); - Assert.IsAssignableFrom(query); - } - - [Fact] - public void AggregateEWithTDataShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).AggregateE("foo"); - Assert.Equal("foo = [];g.v(p0).aggregate(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void AggregateEWithTDataShouldAppendStepAndDeclareVariables() - { - var query = new NodeReference(123).AggregateE("foo").AggregateE("bar"); - Assert.Equal("bar = [];foo = [];g.v(p0).aggregate(foo).aggregate(bar)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void AggregateEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).AggregateE("foo"); - Assert.IsAssignableFrom>(query); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/AsStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/AsStepTests.cs deleted file mode 100644 index aba23d920..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/AsStepTests.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class AsStepTests : IClassFixture - { - [Fact] - public void AsShouldAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().As("foo"); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outV.as(p1)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - } - - [Fact] - public void AsShouldAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().As("foo"); - Assert.IsAssignableFrom(query); - Assert.Equal("g.v(p0).outE.as(p1)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - } - - [Fact] - public void AsShouldAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().As("foo"); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outE.as(p1)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/BackTests.cs b/Neo4jClient.Tests.Shared/Gremlin/BackTests.cs deleted file mode 100644 index f9b5d92a4..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/BackTests.cs +++ /dev/null @@ -1,58 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class BackTests : IClassFixture - { - [Fact] - public void BackVShouldAppendStep() - { - var query = new NodeReference(123).BackV("foo"); - Assert.Equal("g.v(p0).back(p1)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - } - - [Fact] - public void BackVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).BackV("foo"); - Assert.IsAssignableFrom>(query); - } - - [Fact] - public void BackEShouldAppendStep() - { - var query = new NodeReference(123).BackE("foo"); - Assert.Equal("g.v(p0).back(p1)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - } - - [Fact] - public void BackEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).BackE("foo"); - Assert.IsAssignableFrom(query); - } - - [Fact] - public void BackEWithTDataShouldAppendStep() - { - var query = new NodeReference(123).BackE("foo"); - Assert.Equal("g.v(p0).back(p1)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - } - - [Fact] - public void BackEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).BackE("foo"); - Assert.IsAssignableFrom>(query); - } - } -} diff --git a/Neo4jClient.Tests.Shared/Gremlin/BasicStepsTests.cs b/Neo4jClient.Tests.Shared/Gremlin/BasicStepsTests.cs deleted file mode 100644 index 7b4d1353f..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/BasicStepsTests.cs +++ /dev/null @@ -1,382 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using NSubstitute; -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class BasicStepsTests : IClassFixture - { - [Fact] - public void BothVShouldAppendStepToNodeReference() - { - var node = new NodeReference(123); - var query = node.BothV(); - Assert.Equal("g.v(p0).bothV", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void OutVShouldAppendStepToNodeReference() - { - var node = new NodeReference(123); - var query = node.OutV(); - Assert.Equal("g.v(p0).outV", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void OutVShouldAppendStepToGremlinQuery() - { - var query = new NodeReference(123).OutV().OutV(); - Assert.Equal("g.v(p0).outV.outV", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void OutVShouldAppendStepToGremlinQueryWithSingleEqualFilter() - { - var query = new NodeReference(123) - .OutV(new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }, StringComparison.Ordinal); - Assert.Equal("g.v(p0).outV.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("Foo", query.QueryParameters["p1"]); - Assert.Equal("Bar", query.QueryParameters["p2"]); - } - - [Fact] - 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.Equal("g.v(p0).outV.filter{ it[p1].equals(p2) && it[p3].equals(p4) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("Foo", query.QueryParameters["p1"]); - Assert.Equal("Bar", query.QueryParameters["p2"]); - Assert.Equal("Baz", query.QueryParameters["p3"]); - Assert.Equal("Qak", query.QueryParameters["p4"]); - } - - [Fact] - public void OutVShouldReturnTypedGremlinEnumerable() - { - var node = new NodeReference(123); - var query = node.OutV(); - Assert.IsAssignableFrom>(query); - } - - [Fact] - public void OutVShouldCombineWithInE() - { - var query = new NodeReference(123).InE().OutV(); - Assert.Equal("g.v(p0).inE.outV", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void OutShouldAppendStepToGremlinQueryWithNoFilter() - { - var query = new NodeReference(123).Out("REL"); - Assert.Equal("g.v(p0).out(p1)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("REL", query.QueryParameters["p1"]); - } - - [Fact] - public void OutShouldAppendStepToGremlinQueryWithSingleCaseSensitiveEqualFilter() - { - var query = new NodeReference(123) - .Out( - "REL", - new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }, StringComparison.Ordinal); - Assert.Equal("g.v(p0).out(p1).filter{ it[p2].equals(p3) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("REL", query.QueryParameters["p1"]); - Assert.Equal("Foo", query.QueryParameters["p2"]); - Assert.Equal("Bar", query.QueryParameters["p3"]); - } - - [Fact] - public void OutShouldAppendStepToGremlinQueryWithSingleCaseInsensitiveEqualFilter() - { - var query = new NodeReference(123) - .Out( - "REL", - new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }); - Assert.Equal("g.v(p0).out(p1).filter{ it[p2].equalsIgnoreCase(p3) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("REL", query.QueryParameters["p1"]); - Assert.Equal("Foo", query.QueryParameters["p2"]); - Assert.Equal("Bar", query.QueryParameters["p3"]); - } - - [Fact] - public void OutShouldAppendStepToGremlinQueryWithEqualFilterForTextOfEnum() - { - var query = new NodeReference(123) - .Out( - "REL", - x => x.Boo == TestEnum.Bar - ); - Assert.Equal("g.v(p0).out(p1).filter{ it[p2].equalsIgnoreCase(p3) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("REL", query.QueryParameters["p1"]); - Assert.Equal("Boo", query.QueryParameters["p2"]); - Assert.Equal("Bar", query.QueryParameters["p3"]); - } - - [Fact] - public void InShouldAppendStepToGremlinQueryWithNoFilter() - { - var query = new NodeReference(123).In("REL"); - Assert.Equal("g.v(p0).in(p1)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("REL", query.QueryParameters["p1"]); - } - - [Fact] - public void InShouldAppendStepToGremlinQueryWithSingleCaseSensitiveEqualFilter() - { - var query = new NodeReference(123) - .In( - "REL", - new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal} - }, StringComparison.Ordinal); - Assert.Equal("g.v(p0).in(p1).filter{ it[p2].equals(p3) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("REL", query.QueryParameters["p1"]); - Assert.Equal("Foo", query.QueryParameters["p2"]); - Assert.Equal("Bar", query.QueryParameters["p3"]); - } - - [Fact] - public void InShouldAppendStepToGremlinQueryWithSingleCaseInsensitiveEqualFilter() - { - var query = new NodeReference(123) - .In( - "REL", - new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }); - Assert.Equal("g.v(p0).in(p1).filter{ it[p2].equalsIgnoreCase(p3) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("REL", query.QueryParameters["p1"]); - Assert.Equal("Foo", query.QueryParameters["p2"]); - Assert.Equal("Bar", query.QueryParameters["p3"]); - } - - [Fact] - public void InVShouldAppendStepToGremlinQuery() - { - var query = new NodeReference(123).InV(); - Assert.Equal("g.v(p0).inV", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void InVShouldAppendStepToGremlinQueryWithSingleEqualFilter() - { - var query = new NodeReference(123) - .InV(new List - { - new Filter { PropertyName = "Foo", Value = "Bar", ExpressionType = ExpressionType.Equal } - }, StringComparison.Ordinal); - Assert.Equal("g.v(p0).inV.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("Foo", query.QueryParameters["p1"]); - Assert.Equal("Bar", query.QueryParameters["p2"]); - } - - [Fact] - 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.Equal("g.v(p0).inV.filter{ it[p1].equals(p2) && it[p3].equals(p4) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("Foo", query.QueryParameters["p1"]); - Assert.Equal("Bar", query.QueryParameters["p2"]); - Assert.Equal("Baz", query.QueryParameters["p3"]); - Assert.Equal("Qak", query.QueryParameters["p4"]); - } - - [Fact] - public void InVShouldReturnTypedGremlinEnumerable() - { - var node = new NodeReference(123); - var query = node.InV(); - Assert.IsAssignableFrom>(query); - } - - [Fact] - public void InVShouldCombineWithOutE() - { - var query = new NodeReference(123).OutE().InV(); - Assert.Equal("g.v(p0).outE.inV", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void BothEShouldAppendStepToGremlinQuery() - { - var query = new NodeReference(123).BothE(); - Assert.Equal("g.v(p0).bothE", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void OutEShouldAppendStepToGremlinQuery() - { - var query = new NodeReference(123).OutE(); - Assert.Equal("g.v(p0).outE", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void OutEShouldAppendStepToGremlinQueryWithLabel() - { - var query = new NodeReference(123).OutE("FOO"); - Assert.Equal("g.v(p0).outE.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("label", query.QueryParameters["p1"]); - Assert.Equal("FOO", query.QueryParameters["p2"]); - } - - [Fact] - public void TypedOutEShouldAppendStepToGremlinQueryWithLabel() - { - var query = new NodeReference(123).OutE("FOO"); - Assert.Equal("g.v(p0).outE.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("label", query.QueryParameters["p1"]); - Assert.Equal("FOO", query.QueryParameters["p2"]); - } - - [Fact] - public void TypedOutEShouldAppendStepToGremlinQueryWithoutLabel() - { - var query = new NodeReference(123).OutE(); - Assert.Equal("g.v(p0).outE", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void InEShouldAppendStepToGremlinQuery() - { - var query = new NodeReference(123).InE(); - Assert.Equal("g.v(p0).inE", query.QueryText); - } - - [Fact] - public void InEShouldAppendStepToGremlinQueryWithLabel() - { - var query = new NodeReference(123).InE("FOO"); - Assert.Equal("g.v(p0).inE.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("label", query.QueryParameters["p1"]); - Assert.Equal("FOO", query.QueryParameters["p2"]); - } - - [Fact] - public void TypedInEShouldAppendStepToGremlinQueryWithLabel() - { - var query = new NodeReference(123).InE("FOO"); - Assert.Equal("g.v(p0).inE.filter{ it[p1].equals(p2) }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("label", query.QueryParameters["p1"]); - Assert.Equal("FOO", query.QueryParameters["p2"]); - } - - [Fact] - public void TypedInEShouldAppendStepToGremlinQueryWithoutLabel() - { - var query = new NodeReference(123).InE(); - Assert.Equal("g.v(p0).inE", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - 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.Equal("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.Equal(0L, query.QueryParameters["p0"]); - Assert.Equal("label", query.QueryParameters["p1"]); - Assert.Equal("E_FOO", query.QueryParameters["p2"]); - Assert.Equal("Foo", query.QueryParameters["p3"]); - Assert.Equal("Bar", query.QueryParameters["p4"]); - Assert.Equal("label", query.QueryParameters["p5"]); - Assert.Equal("E_BAR", query.QueryParameters["p6"]); - } - - [Fact] - 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(123L, client); - var result = node.GremlinCount(); - Assert.Equal(456, result); - } - - [Fact] - public void GremlinCountShouldThrowDetachedNodeExceptionWhenBaseReferenceClientIsNull() - { - var node = new NodeReference(123); - Assert.Throws(() => 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/Neo4jClient.Tests.Shared/Gremlin/CopySplitStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/CopySplitStepTests.cs deleted file mode 100644 index 040ae3030..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/CopySplitStepTests.cs +++ /dev/null @@ -1,126 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class CopySplitStepTests : IClassFixture - { - [Fact] - public void CopySplitVShouldAppendStepForRelationships() - { - var query = new NodeReference(123).CopySplitE(new IdentityPipe().OutE(), new IdentityPipe().OutE()); - Assert.Equal("g.v(p0)._.copySplit(_().outE, _().outE)", query.QueryText); - } - - [Fact] - public void CopySplitVShouldAppendStepForNodes() - { - var query = new NodeReference(123).CopySplitV(new IdentityPipe().OutV(), new IdentityPipe().OutV()); - Assert.Equal("g.v(p0)._.copySplit(_().outV, _().outV)", query.QueryText); - } - - [Fact] - public void CopySplitEShouldAppendStepAndPreserveOuterQueryParametersWithAllInlineBlocksAsIndentityPipes() - { - var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo"), new IdentityPipe().Out("bar")).Out("baz"); - Assert.Equal("g.v(p0)._.copySplit(_().out(p1), _().out(p2)).out(p3)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - Assert.Equal("bar", query.QueryParameters["p2"]); - Assert.Equal("baz", query.QueryParameters["p3"]); - } - - [Fact] - public void CopySplitVShouldAppendStepAndPreserveOuterQueryParametersWithOneInlineBlocksAsNodeReference() - { - var node = new NodeReference(456); - var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo"), node.Out("bar")).Out("baz"); - Assert.Equal("g.v(p0)._.copySplit(_().out(p1), g.v(p2).out(p3)).out(p4)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - Assert.Equal(456L, query.QueryParameters["p2"]); - Assert.Equal("bar", query.QueryParameters["p3"]); - Assert.Equal("baz", query.QueryParameters["p4"]); - } - - [Fact] - public void CopySplitVShouldMoveInlineBlockVariablesToTheOuterScopeInFinallyQueryUsingAggregateV() - { - var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo").AggregateV("xyz"), new IdentityPipe().Out("bar")).Out("baz"); - Assert.Equal("xyz = [];g.v(p0)._.copySplit(_().out(p1).aggregate(xyz), _().out(p2)).out(p3)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - Assert.Equal("bar", query.QueryParameters["p2"]); - Assert.Equal("baz", query.QueryParameters["p3"]); - } - - [Fact] - public void CopySplitVShouldMoveInlineBlockVariablesToTheOuterScopeInFinallyQueryUsingStoreV() - { - var query = new NodeReference(123).CopySplitE(new IdentityPipe().Out("foo").StoreV("xyz"), new IdentityPipe().Out("bar")).Out("baz"); - Assert.Equal("xyz = [];g.v(p0)._.copySplit(_().out(p1).store(xyz), _().out(p2)).out(p3)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - Assert.Equal("bar", query.QueryParameters["p2"]); - Assert.Equal("baz", query.QueryParameters["p3"]); - } - - [Fact] - 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.Equal("xyz = [];g.v(p0)._.copySplit(_().out(p1).filter{ it[p2] == p3 }.store(xyz), _().out(p4)).out(p5).filter{ it[p6] == p7 }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - Assert.Equal("Flag", query.QueryParameters["p2"]); - Assert.Equal(true, query.QueryParameters["p3"]); - Assert.Equal("bar", query.QueryParameters["p4"]); - Assert.Equal("baz", query.QueryParameters["p5"]); - Assert.Equal("Flag", query.QueryParameters["p6"]); - Assert.Equal(true, query.QueryParameters["p7"]); - } - - [Fact] - 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.Equal("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.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - Assert.Equal("Flag", query.QueryParameters["p2"]); - Assert.Equal(true, query.QueryParameters["p3"]); - Assert.Equal("bar", query.QueryParameters["p4"]); - Assert.Equal("baz", query.QueryParameters["p5"]); - Assert.Equal("Flag", query.QueryParameters["p6"]); - Assert.Equal(true, query.QueryParameters["p7"]); - } - - [Fact] - 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.Equal( - "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/Neo4jClient.Tests.Shared/Gremlin/EmitPropertyStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/EmitPropertyStepTests.cs deleted file mode 100644 index b51ed08b7..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/EmitPropertyStepTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class EmitPropertyStepTests : IClassFixture - { - [Fact] - public void EmitPropertyShouldAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().EmitProperty("foo"); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outV.foo", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void EmitPropertyShouldAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().EmitProperty("foo"); - Assert.IsAssignableFrom(query); - Assert.Equal("g.v(p0).outE.foo", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void EmitPropertyShouldAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().EmitProperty("foo"); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outE.foo", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/ExceptStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/ExceptStepTests.cs deleted file mode 100644 index 289a02bac..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/ExceptStepTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class ExceptStepTests : IClassFixture - { - [Fact] - public void ExceptVShouldAppendStep() - { - var query = new NodeReference(123).ExceptV("foo"); - Assert.Equal("g.v(p0).except(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void ExceptVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).ExceptV("foo"); - Assert.IsAssignableFrom>(query); - } - - [Fact] - public void ExceptEShouldAppendStep() - { - var query = new NodeReference(123).ExceptE("foo"); - Assert.Equal("g.v(p0).except(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void ExceptEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).ExceptE("foo"); - Assert.IsAssignableFrom(query); - } - - [Fact] - public void ExceptEWithTDataShouldAppendStep() - { - var query = new NodeReference(123).ExceptE("foo"); - Assert.Equal("g.v(p0).except(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void ExceptEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).ExceptE("foo"); - Assert.IsAssignableFrom>(query); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/ExhaustMergeStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/ExhaustMergeStepTests.cs deleted file mode 100644 index 7290e47d3..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/ExhaustMergeStepTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class ExhaustMergeStepTests : IClassFixture - { - [Fact] - public void ExhaustMergeAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().ExhaustMerge(); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outV.exhaustMerge", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void ExhaustMergeAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().ExhaustMerge(); - Assert.IsAssignableFrom(query); - Assert.Equal("g.v(p0).outE.exhaustMerge", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void ExhaustMergeAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().ExhaustMerge(); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outE.exhaustMerge", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/FairMergeStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/FairMergeStepTests.cs deleted file mode 100644 index 5c4235770..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/FairMergeStepTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class FairMergeStepTests : IClassFixture - { - [Fact] - public void FairMergeShouldAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().FairMerge(); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outV.fairMerge", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void FairMergeShouldAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().FairMerge(); - Assert.IsAssignableFrom(query); - Assert.Equal("g.v(p0).outE.fairMerge", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void FairMergeShouldAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().FairMerge(); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outE.fairMerge", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/FormatGremlinFilterTests.cs b/Neo4jClient.Tests.Shared/Gremlin/FormatGremlinFilterTests.cs deleted file mode 100644 index eb4ef4f49..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/FormatGremlinFilterTests.cs +++ /dev/null @@ -1,698 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq.Expressions; -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class FormatGremlinFilterTests : IClassFixture - { - [Fact] - 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.Equal(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(guidString, filter.FilterParameters["p1"].ToString()); - } - - [Fact] - 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.Equal(".filter{ it[p0] != p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(guidString, filter.FilterParameters["p1"].ToString()); - } - - [Fact] - 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.Equal(string.Empty, filter.FilterText); - } - - [Fact] - 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.Equal(".filter{ it[p0].equals(p1) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123L, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0].equals(p1) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0].equals(p1) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] == null }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - } - - [Fact] - 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.Equal(".filter{ it[p0].equals(p1) && it[p2].equals(p3) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - Assert.Equal("Baz", filter.FilterParameters["p2"]); - Assert.Equal("Qak", filter.FilterParameters["p3"]); - } - - [Fact] - 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()); - var ex = Assert.Throws(() => FilterFormatters.FormatGremlinFilter(filters, StringComparison.Ordinal, baseQuery)); - Assert.Equal("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", ex.Message); - } - - [Fact] - 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.Equal(string.Empty, filter.FilterText); - } - - [Fact] - 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.Equal(".filter{ it[p0].equalsIgnoreCase(p1) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123L, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0].equalsIgnoreCase(p1) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0].equalsIgnoreCase(p1) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0].equalsIgnoreCase(p1) && it[p2].equalsIgnoreCase(p3) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - Assert.Equal("Baz", filter.FilterParameters["p2"]); - Assert.Equal("Qak", filter.FilterParameters["p3"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] == null }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - } - - [Fact] - 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.Equal(".filter{ !it[p0].equalsIgnoreCase(p1) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] != p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] != p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(0L, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ !it[p0].equals(p1) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] != p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] != p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(0L, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ !it[p0].equals(p1) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ !it[p0].equalsIgnoreCase(p1) }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal("Bar", filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] > p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] > p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] < p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] < p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] > p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] > p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] < p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] < p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] >= p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] >= p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] <= p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] <= p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(123, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] >= p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] >= p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] <= p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] <= p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(9223372036854775807, filter.FilterParameters["p1"]); - } - - [Fact] - 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()); - var ex = Assert.Throws(() => FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery)); - Assert.Equal("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", ex.Message); - } - - [Fact] - 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()); - var ex = Assert.Throws(() => FilterFormatters.FormatGremlinFilter(filters, StringComparison.OrdinalIgnoreCase, baseQuery)); - Assert.Equal("One or more of the supplied filters is of an unsupported type or expression. Unsupported filters were: Foo with null value and expression Divide", ex.Message); - } - - [Fact] - 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.Equal(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(true, filter.FilterParameters["p1"]); - } - - [Fact] - 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.Equal(".filter{ it[p0] == p1 }", filter.FilterText); - Assert.Equal("Foo", filter.FilterParameters["p0"]); - Assert.Equal(false, filter.FilterParameters["p1"]); - } - - enum EnumForTesting - { - Bar - } - } -} diff --git a/Neo4jClient.Tests.Shared/Gremlin/GremlinClientTests.cs b/Neo4jClient.Tests.Shared/Gremlin/GremlinClientTests.cs deleted file mode 100644 index 30aabb133..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/GremlinClientTests.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using NSubstitute; -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class GremlinClientTests : IClassFixture - { - [Fact] - public void VShouldEumerateAllVertexes() - { - var client = Substitute.For(); - var gremlinClient = new GremlinClient(client); - var query = gremlinClient.V; - Assert.Equal("g.V", query.QueryText); - } - - [Fact] - 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.Equal(123, result); - } - - [Fact] - public void EShouldEumerateAllEdges() - { - var client = Substitute.For(); - var gremlinClient = new GremlinClient(client); - var query = gremlinClient.E; - Assert.Equal("g.E", query.QueryText); - } - - [Fact] - 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.Equal(123, result); - } - } -} diff --git a/Neo4jClient.Tests.Shared/Gremlin/GremlinDistinctStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/GremlinDistinctStepTests.cs deleted file mode 100644 index 0415f6b7b..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/GremlinDistinctStepTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class GremlinDistinctStepTests : IClassFixture - { - [Fact] - public void GremlinDistinctAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().GremlinDistinct(); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outV.dedup()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void GremlinDistinctAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().GremlinDistinct(); - Assert.IsAssignableFrom(query); - Assert.Equal("g.v(p0).outE.dedup()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void GremlinDistinctAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().GremlinDistinct(); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outE.dedup()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/GremlinHasNextStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/GremlinHasNextStepTests.cs deleted file mode 100644 index 21f1693ed..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/GremlinHasNextStepTests.cs +++ /dev/null @@ -1,37 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class GremlinHasNextStepTests : IClassFixture - { - [Fact] - public void GremlinHasNextAppendStepToNodeQuery() - { - var query = new NodeReference(123).OutV().GremlinHasNext(); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outV.hasNext()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void GremlinHasNextAppendStepToRelationshipQuery() - { - var query = new NodeReference(123).OutE().GremlinHasNext(); - Assert.IsAssignableFrom(query); - Assert.Equal("g.v(p0).outE.hasNext()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void GremlinHasNextAppendStepToTypedRelationshipQuery() - { - var query = new NodeReference(123).OutE().GremlinHasNext(); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outE.hasNext()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/GremlinNodeEnumerableTests.cs b/Neo4jClient.Tests.Shared/Gremlin/GremlinNodeEnumerableTests.cs deleted file mode 100644 index a9445d819..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/GremlinNodeEnumerableTests.cs +++ /dev/null @@ -1,74 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using NSubstitute; -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class GremlinNodeEnumerableTests : IClassFixture - { - [Fact] - public void GetEnumeratorShouldThrowDetachedNodeExceptionWhenClientNotSet() - { - // ReSharper disable ReturnValueOfPureMethodIsNotUsed - IEnumerable> enumerable = new GremlinNodeEnumerable(new GremlinQuery(null, "abc", null, null)); - Assert.Throws(() => enumerable.GetEnumerator()); - // ReSharper restore ReturnValueOfPureMethodIsNotUsed - } - - [Fact] - 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.Equal(expectedResults, results); - } - - [Fact] - 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.Equal( - "g['val00']['val01']['val02']['val03']['val04']['val05']['val06']['val07']['val08']['val09']['val10']['val11']", - enumerable.DebugQueryText); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/GremlinPagedEnumeratorTests.cs b/Neo4jClient.Tests.Shared/Gremlin/GremlinPagedEnumeratorTests.cs deleted file mode 100644 index ed93d7246..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/GremlinPagedEnumeratorTests.cs +++ /dev/null @@ -1,212 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class GremlinPagedEnumeratorTests : IClassFixture - { - [Fact] - 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.Equal(0, loadedQueries.Count()); - } - - [Fact] - 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.Equal(1, loadedQueries.Count()); - Assert.Equal("g.v(p0).outV.drop(p1).take(p2)._()", loadedQueries[0].QueryText); - Assert.Equal(0, loadedQueries[0].QueryParameters["p1"]); - Assert.Equal(100, loadedQueries[0].QueryParameters["p2"]); - } - - [Fact] - 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.True(enumerator.MoveNext()); - Assert.Equal(results[i], enumerator.Current); - } - } - - [Fact] - 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.False(enumerator.MoveNext()); - } - - [Fact] - 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.False(enumerator.MoveNext()); - } - - [Fact] - 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.False(enumerator.MoveNext()); - } - - [Fact] - 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.Equal(2, loadedQueries.Count()); - Assert.Equal("g.v(p0).outV.drop(p1).take(p2)._()", loadedQueries[0].QueryText); - Assert.Equal(0, loadedQueries[0].QueryParameters["p1"]); - Assert.Equal(100, loadedQueries[0].QueryParameters["p2"]); - Assert.Equal("g.v(p0).outV.drop(p1).take(p2)._()", loadedQueries[1].QueryText); - Assert.Equal(100, loadedQueries[1].QueryParameters["p1"]); - Assert.Equal(100, loadedQueries[1].QueryParameters["p2"]); - } - - [InlineData(1)] - [InlineData(2)] - [InlineData(3)] - [InlineData(4)] - [InlineData(5)] - [InlineData(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.True(enumerator.MoveNext()); - Assert.Equal(i, enumerator.Current); - } - } - } -} diff --git a/Neo4jClient.Tests.Shared/Gremlin/IfThenElseTests.cs b/Neo4jClient.Tests.Shared/Gremlin/IfThenElseTests.cs deleted file mode 100644 index f0938bf0f..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/IfThenElseTests.cs +++ /dev/null @@ -1,87 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class IfThenElseTests : IClassFixture - { - [Fact] - public void IfThenElseVShouldAppendSteps() - { - var query = new NodeReference(123).IfThenElse( - new GremlinIterator().OutV().GremlinHasNext(), - null, - null); - Assert.Equal("g.v(p0).ifThenElse{it.outV.hasNext()}{}{}", query.QueryText); - } - - [Fact] - public void IfThenElseVShouldAppendStepsWithThenQueryAndElseQuery() - { - var query = new NodeReference(123).IfThenElse( - new GremlinIterator().OutV().GremlinHasNext(), - new GremlinIterator().OutV(), - new GremlinIterator().InV()); - Assert.Equal("g.v(p0).ifThenElse{it.outV.hasNext()}{it.outV}{it.inV}", query.QueryText); - } - - [Fact] - 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.Equal("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.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("Flag", query.QueryParameters["p1"]); - Assert.Equal(true, query.QueryParameters["p2"]); - Assert.Equal("Name", query.QueryParameters["p3"]); - Assert.Equal("foo", query.QueryParameters["p4"]); - Assert.Equal("Name", query.QueryParameters["p5"]); - Assert.Equal("bar", query.QueryParameters["p6"]); - } - - [Fact] - 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.Equal("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.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("Flag", query.QueryParameters["p1"]); - Assert.Equal(true, query.QueryParameters["p2"]); - Assert.Equal("Name", query.QueryParameters["p3"]); - Assert.Equal("foo", query.QueryParameters["p4"]); - Assert.Equal("Name", query.QueryParameters["p5"]); - Assert.Equal("bar", query.QueryParameters["p6"]); - } - - [Fact] - 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.Equal("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.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("Flag", query.QueryParameters["p1"]); - Assert.Equal(true, query.QueryParameters["p2"]); - Assert.Equal("Name", query.QueryParameters["p3"]); - Assert.Equal("foo", query.QueryParameters["p4"]); - Assert.Equal("Name", query.QueryParameters["p5"]); - Assert.Equal("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/Neo4jClient.Tests.Shared/Gremlin/IteratorTests.cs b/Neo4jClient.Tests.Shared/Gremlin/IteratorTests.cs deleted file mode 100644 index c41037767..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/IteratorTests.cs +++ /dev/null @@ -1,76 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class IteratorTests : IClassFixture - { - [Fact] - public void GremlinSkipVShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutV().GremlinSkip(5); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outV.drop(p1)._()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal(5, query.QueryParameters["p1"]); - } - - [Fact] - public void GremlinSkipEShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutE().GremlinSkip(5); - Assert.IsAssignableFrom(query); - Assert.Equal("g.v(p0).outE.drop(p1)._()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal(5, query.QueryParameters["p1"]); - } - - [Fact] - public void GremlinSkipEWithTDataShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutE().GremlinSkip(5); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outE.drop(p1)._()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal(5, query.QueryParameters["p1"]); - } - - [Fact] - public void GremlinTakeVShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutV().GremlinTake(5); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outV.take(p1)._()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal(5, query.QueryParameters["p1"]); - } - - [Fact] - public void GremlinTakeEShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutE().GremlinTake(5); - Assert.IsAssignableFrom(query); - Assert.Equal("g.v(p0).outE.take(p1)._()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal(5, query.QueryParameters["p1"]); - } - - [Fact] - public void GremlinTakeEWithTDataShouldAppendStep() - { - var node = new NodeReference(123); - var query = node.OutE().GremlinTake(5); - Assert.IsAssignableFrom>(query); - Assert.Equal("g.v(p0).outE.take(p1)._()", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal(5, query.QueryParameters["p1"]); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/LoopStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/LoopStepTests.cs deleted file mode 100644 index b94e568a8..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/LoopStepTests.cs +++ /dev/null @@ -1,61 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class LoopStepTests : IClassFixture - { - [Fact] - public void LoopVShouldAppendStep() - { - var query = new NodeReference(123).LoopV("foo", 6); - Assert.Equal("g.v(p0).loop(p1){ it.loops < p2 }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - Assert.Equal(6U, query.QueryParameters["p2"]); - } - - [Fact] - public void LoopVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).LoopV("foo", 6); - Assert.IsAssignableFrom>(query); - } - - [Fact] - public void LoopEShouldAppendStep() - { - var query = new NodeReference(123).LoopE("foo", 6); - Assert.Equal("g.v(p0).loop(p1){ it.loops < p2 }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - Assert.Equal(6U, query.QueryParameters["p2"]); - } - - [Fact] - public void LoopEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).LoopE("foo", 6); - Assert.IsAssignableFrom(query); - } - - [Fact] - public void LoopEWithTDataShouldAppendStep() - { - var query = new NodeReference(123).LoopE("foo", 6); - Assert.Equal("g.v(p0).loop(p1){ it.loops < p2 }", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal("foo", query.QueryParameters["p1"]); - Assert.Equal(6U, query.QueryParameters["p2"]); - } - - [Fact] - public void LoopEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).LoopE("foo", 6); - Assert.IsAssignableFrom>(query); - } - } -} diff --git a/Neo4jClient.Tests.Shared/Gremlin/PrintLineStatementTests.cs b/Neo4jClient.Tests.Shared/Gremlin/PrintLineStatementTests.cs deleted file mode 100644 index 3b7934f4b..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/PrintLineStatementTests.cs +++ /dev/null @@ -1,21 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class PrintLineStatementTests : IClassFixture - { - [Fact] - public void PrintLineShouldAppendStepToNodeQuery() - { - var query = new NodeReference(123).IfThenElse( - new GremlinIterator().OutV().GremlinHasNext(), - new Statement().PrintLine("\"{$it} Hello\""), - new Statement().PrintLine("\"{$it} GoodBye\"")); - Assert.Equal("g.v(p0).ifThenElse{it.outV.hasNext()}{println \"{$it} Hello\"}{println \"{$it} GoodBye\"}", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/RetainStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/RetainStepTests.cs deleted file mode 100644 index ff9c9f4b0..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/RetainStepTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class RetainStepTests : IClassFixture - { - [Fact] - public void RetainVShouldAppendStep() - { - var query = new NodeReference(123).RetainV("foo"); - Assert.Equal("g.v(p0).retain(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void RetainVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).RetainV("foo"); - Assert.IsAssignableFrom>(query); - } - - [Fact] - public void RetainEShouldAppendStep() - { - var query = new NodeReference(123).RetainE("foo"); - Assert.Equal("g.v(p0).retain(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void RetainEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).RetainE("foo"); - Assert.IsAssignableFrom(query); - } - - [Fact] - public void RetainEWithTDataShouldAppendStep() - { - var query = new NodeReference(123).RetainE("foo"); - Assert.Equal("g.v(p0).retain(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void RetainEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).RetainE("foo"); - Assert.IsAssignableFrom>(query); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/StoreStepTests.cs b/Neo4jClient.Tests.Shared/Gremlin/StoreStepTests.cs deleted file mode 100644 index 77ee607ad..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/StoreStepTests.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class StoreStepTests : IClassFixture - { - [Fact] - public void StoreVShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).StoreV("foo"); - Assert.Equal("foo = [];g.v(p0).store(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void StoreVShouldReturnTypedNodeEnumerable() - { - var query = new NodeReference(123).StoreV("foo"); - Assert.IsAssignableFrom>(query); - } - - [Fact] - public void StoreEShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).StoreE("foo"); - Assert.Equal("foo = [];g.v(p0).store(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void StoreEShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).StoreE("foo"); - Assert.IsAssignableFrom(query); - } - - [Fact] - public void StoreEWithTDataShouldAppendStepAndDeclareVariable() - { - var query = new NodeReference(123).StoreE("foo"); - Assert.Equal("foo = [];g.v(p0).store(foo)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void StoreEWithTDataShouldReturnRelationshipEnumerable() - { - var query = new NodeReference(123).StoreE("foo"); - Assert.IsAssignableFrom>(query); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Gremlin/TableTests.cs b/Neo4jClient.Tests.Shared/Gremlin/TableTests.cs deleted file mode 100644 index 6934ed00a..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/TableTests.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Xunit; -using Neo4jClient.ApiModels; -using Neo4jClient.ApiModels.Gremlin; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; -using Newtonsoft.Json; - -namespace Neo4jClient.Test.Gremlin -{ - - public class TableTests : IClassFixture - { - [Fact] - public void TableShouldAppendStepToQuery() - { - var query = new NodeReference(123) - .OutV() - .As("foo") - .Table(); - - Assert.IsAssignableFrom>(query); - Assert.IsAssignableFrom>(query); - Assert.IsAssignableFrom(query); - - var enumerable = (IGremlinQuery)query; - Assert.Equal("g.v(p0).outV.as(p1).table(new Table()).cap", enumerable.QueryText); - Assert.Equal(123L, enumerable.QueryParameters["p0"]); - Assert.Equal("foo", enumerable.QueryParameters["p1"]); - } - - [Fact] - public void TableShouldAppendStepToQueryWithClosures() - { - var query = new NodeReference(123) - .OutV() - .As("foo") - .InV() - .As("bar") - .Table( - foo => foo.SomeText, - bar => bar.SomeNumber - ); - - Assert.IsAssignableFrom>(query); - Assert.IsAssignableFrom>(query); - Assert.IsAssignableFrom(query); - - var enumerable = (IGremlinQuery)query; - Assert.Equal("g.v(p0).outV.as(p1).inV.as(p2).table(new Table()){it[p3]}{it[p4]}.cap", enumerable.QueryText); - Assert.Equal(123L, enumerable.QueryParameters["p0"]); - Assert.Equal("foo", enumerable.QueryParameters["p1"]); - Assert.Equal("bar", enumerable.QueryParameters["p2"]); - Assert.Equal("SomeText", enumerable.QueryParameters["p3"]); - Assert.Equal("SomeNumber", enumerable.QueryParameters["p4"]); - } - - [Fact] - 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.Equal("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/Neo4jClient.Tests.Shared/Gremlin/TranslateFilterTests.cs b/Neo4jClient.Tests.Shared/Gremlin/TranslateFilterTests.cs deleted file mode 100644 index 1ec6f4006..000000000 --- a/Neo4jClient.Tests.Shared/Gremlin/TranslateFilterTests.cs +++ /dev/null @@ -1,344 +0,0 @@ -using System; -using System.Globalization; -using System.Linq; -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test.Gremlin -{ - - public class TranslateFilterTests : IClassFixture - { - [Fact] - public void TranslateFilterShouldResolveSinglePropertyEqualsConstantStringExpression() - { - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == "abc" // This must be a constant - do not refactor this line - ) - .ToArray(); - Assert.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal("abc", filters.FirstOrDefault().Value); - } - - [Fact] - public void TranslateFilterShouldResolveSinglePropertyEqualsConstantIntExpression() - { - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == 123 // This must be a constant - do not refactor this line - ) - .ToArray(); - Assert.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal(123, filters.FirstOrDefault().Value); - } - - [Fact] - public void TranslateFilterShouldResolveSinglePropertyEqualsConstantLongExpression() - { - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == 123 // This must be a constant - do not refactor this line - ) - .ToArray(); - Assert.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal((long)123, filters.FirstOrDefault().Value); - } - - [Fact] - public void TranslateFilterShouldResolveSinglePropertyEqualsConstantLongMaxExpression() - { - var filters = FilterFormatters - .TranslateFilter( - f => f.Prop1 == long.MaxValue // This must be a constant - do not refactor this line - ) - .ToArray(); - Assert.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal(long.MaxValue, filters.FirstOrDefault().Value); - } - - [Fact] - 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.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal("abc", filters.FirstOrDefault().Value); - } - - [Fact] - 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.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal(123, filters.FirstOrDefault().Value); - } - - [Fact] - 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.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal(123L, filters.FirstOrDefault().Value); - } - - [Fact] - 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.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal(long.MaxValue, filters.FirstOrDefault().Value); - } - - [Fact] - public void TranslateFilterShouldResolveSinglePropertyEqualsConstantEnumExpression() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == EnumForTesting.Foo) - .ToArray(); - Assert.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal(EnumForTesting.Foo, filters.FirstOrDefault().Value); - } - - [Fact] - 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.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal("def", filters.FirstOrDefault().Value); - } - - [Fact] - 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.Equal(3, filters.Count()); - Assert.Equal("Prop1", filters[0].PropertyName); - Assert.Equal("def", filters[0].Value); - Assert.Equal("Prop2", filters[1].PropertyName); - Assert.Equal("ghi", filters[1].Value); - Assert.Equal("Prop3", filters[2].PropertyName); - Assert.Equal("jkl", filters[2].Value); - } - - [Fact] - 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.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal("ABC.DEF", filters.FirstOrDefault().Value); - } - - [Fact] - public void TranslateFilterShouldResolveSinglePropertyEqualsNull() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == null) - .ToArray(); - Assert.Equal(1, filters.Count()); - Assert.Equal("Prop1", filters.FirstOrDefault().PropertyName); - Assert.Equal(null, filters.FirstOrDefault().Value); - } - - [Fact] - public void TranslateFilterShouldResolvePropertiesEqualBoolean() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop4 == true) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.Equal(1, filters.Count()); - Assert.Equal("Prop4", filters[0].PropertyName); - Assert.Equal(true, filters[0].Value); - } - - [Fact] - public void TranslateFilterShouldResolveBooleanPropertyToDefaultToCompareToTrue() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop4) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.Equal(1, filters.Count()); - Assert.Equal("Prop4", filters[0].PropertyName); - Assert.Equal(true, filters[0].Value); - } - - [Fact] - public void TranslateFilterShouldResolveBooleanPropertyToDefaultToCompareToFalse() - { - var filters = FilterFormatters - .TranslateFilter(f => !f.Prop4) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.Equal(1, filters.Count()); - Assert.Equal("Prop4", filters[0].PropertyName); - Assert.Equal(false, filters[0].Value); - } - - [Fact] - public void TranslateFilterShouldResolveTwoPropertiesEqualNullWithBinaryAnd() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == null & f.Prop2 == null) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.Equal(2, filters.Count()); - Assert.Equal("Prop1", filters[0].PropertyName); - Assert.Equal(null, filters[0].Value); - Assert.Equal("Prop2", filters[1].PropertyName); - Assert.Equal(null, filters[1].Value); - } - - [Fact] - public void TranslateFilterShouldResolveTwoPropertiesEqualNullWithBinaryAndAlso() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == null && f.Prop2 == null) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.Equal(2, filters.Count()); - Assert.Equal("Prop1", filters[0].PropertyName); - Assert.Equal(null, filters[0].Value); - Assert.Equal("Prop2", filters[1].PropertyName); - Assert.Equal(null, filters[1].Value); - } - - [Fact] - public void TranslateFilterShouldResolveThreePropertiesEqualNullWithBinaryAndAlso() - { - var filters = FilterFormatters - .TranslateFilter(f => f.Prop1 == null && f.Prop2 == null && f.Prop3 == null) - .OrderBy(f => f.PropertyName) - .ToArray(); - Assert.Equal(3, filters.Count()); - Assert.Equal("Prop1", filters[0].PropertyName); - Assert.Equal(null, filters[0].Value); - Assert.Equal("Prop2", filters[1].PropertyName); - Assert.Equal(null, filters[1].Value); - Assert.Equal("Prop3", filters[2].PropertyName); - Assert.Equal(null, filters[2].Value); - } - - [Fact] - public void TranslateFilterShouldThrowExceptionIfOuterExpressionIsAndAlsoAndInnerLeftExpressionIsNotABinaryExpression() - { - var testVariable = bool.Parse(bool.TrueString); - var ex = Assert.Throws(() => FilterFormatters.TranslateFilter(f => testVariable && f.Prop2 == null)); - - - Assert.True(ex.Message.StartsWith("This expression is not a binary expression:")); - } - - [Fact] - public void TranslateFilterShouldThrowExceptionIfOuterExpressionIsAndAlsoAndInnerRightExpressionIsNotABinaryExpression() - { - var testVariable = bool.Parse(bool.TrueString); - var ex = Assert.Throws(() => FilterFormatters.TranslateFilter(f => f.Prop2 == null && testVariable)); - - Assert.True(ex.Message.StartsWith("This expression is not a binary expression:")); - } - - [Fact] - public void TranslateFilterShouldThrowExceptionForOrExpression() - { - var testVariable = bool.Parse(bool.TrueString); - var ex = Assert.Throws(() => FilterFormatters.TranslateFilter(f => f.Prop2 == null | testVariable)); - - Assert.True(ex.Message.StartsWith("Oprerator Or is not yet supported.")); - } - - [Fact] - public void TranslateFilterShouldThrowExceptionForOrElseExpression() - { - var testVariable = bool.Parse(bool.TrueString); - var ex = Assert.Throws(() => FilterFormatters.TranslateFilter(f => f.Prop2 == null || testVariable)); - - Assert.True(ex.Message.StartsWith("Oprerator OrElse is not yet supported.")); - } - - 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/Neo4jClient.Tests.Shared/Neo4jClient.Tests.Shared.projitems b/Neo4jClient.Tests.Shared/Neo4jClient.Tests.Shared.projitems deleted file mode 100644 index 7edfc95ea..000000000 --- a/Neo4jClient.Tests.Shared/Neo4jClient.Tests.Shared.projitems +++ /dev/null @@ -1,142 +0,0 @@ - - - - $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - true - 22394fe7-45ca-4f89-bd96-d00511c1205c - - - Neo4jClient.Tests.Shared - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Code - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Neo4jClient.Tests.Shared.shproj b/Neo4jClient.Tests.Shared/Neo4jClient.Tests.Shared.shproj deleted file mode 100644 index 2dd5d6b4b..000000000 --- a/Neo4jClient.Tests.Shared/Neo4jClient.Tests.Shared.shproj +++ /dev/null @@ -1,13 +0,0 @@ - - - - 22394fe7-45ca-4f89-bd96-d00511c1205c - 14.0 - - - - - - - - diff --git a/Neo4jClient.Tests.Shared/NodeTests.cs b/Neo4jClient.Tests.Shared/NodeTests.cs deleted file mode 100644 index de9558d09..000000000 --- a/Neo4jClient.Tests.Shared/NodeTests.cs +++ /dev/null @@ -1,85 +0,0 @@ -using NSubstitute; -using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; - -namespace Neo4jClient.Test -{ - - public class NodeTests : IClassFixture - { - [Fact] - public void ClientShouldReturnClientFromReference() - { - var client = Substitute.For(); - var reference = new NodeReference(123, client); - var node = new Node(new object(), reference); - Assert.Equal(client, ((IGremlinQuery)node).Client); - } - - [Fact] - 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.Equal("g.v(p0)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - 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.Equal("START foo=node($p0)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void CypherQueryShouldIncludeRootNodeAsStartBit() - { - var client = Substitute.For(); - client.RootNode.ReturnsForAnyArgs(new RootNode(4, client)); - var query = client.RootNode.StartCypher("foo").Query; - Assert.Equal("START foo=node($p0)", query.QueryText); - Assert.Equal(4L, query.QueryParameters["p0"]); - } - - [Fact] - 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.Equal(client, queryBuilder.Client); - } - - [Fact] - public void EqualityOperatorShouldReturnTrueForEquivalentReferences() - { - var node1 = new Node(new object(), new NodeReference(123)); - var node2 = new Node(new object(), new NodeReference(123)); - Assert.True(node1 == node2); - } - - [Fact] - public void EqualityOperatorShouldReturnFalseForDifferentReferences() - { - var node1 = new Node(new object(), new NodeReference(123)); - var node2 = new Node(new object(), new NodeReference(456)); - Assert.False(node1 == node2); - } - - [Fact] - public void GetHashCodeTestShouldReturnNodeId() - { - var node = new Node(new object(), new NodeReference(123)); - Assert.Equal(123, node.Reference.Id); - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Relationships/OwnedBy.cs b/Neo4jClient.Tests.Shared/Relationships/OwnedBy.cs deleted file mode 100644 index 1a434ec8e..000000000 --- a/Neo4jClient.Tests.Shared/Relationships/OwnedBy.cs +++ /dev/null @@ -1,19 +0,0 @@ -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/Neo4jClient.Tests.Shared/AddressResolverTests.cs b/Neo4jClient.Tests/AddressResolverTests.cs similarity index 93% rename from Neo4jClient.Tests.Shared/AddressResolverTests.cs rename to Neo4jClient.Tests/AddressResolverTests.cs index 7e9c86b6b..3c6453cbe 100644 --- a/Neo4jClient.Tests.Shared/AddressResolverTests.cs +++ b/Neo4jClient.Tests/AddressResolverTests.cs @@ -1,12 +1,10 @@ using System; -using System.Collections.Generic; using System.Linq; using FluentAssertions; -using Neo4j.Driver.V1; -using Neo4jClient.Test.Fixtures; +using Neo4j.Driver; using Xunit; -namespace Neo4jClient.Tests.Shared +namespace Neo4jClient.Tests { public class AddressResolverTests : IClassFixture { diff --git a/Neo4jClient.Tests.Shared/ApiModels/RootApiResponseTests.cs b/Neo4jClient.Tests/ApiModels/RootApiResponseTests.cs similarity index 94% rename from Neo4jClient.Tests.Shared/ApiModels/RootApiResponseTests.cs rename to Neo4jClient.Tests/ApiModels/RootApiResponseTests.cs index 34cbd5476..9bbdd9f38 100644 --- a/Neo4jClient.Tests.Shared/ApiModels/RootApiResponseTests.cs +++ b/Neo4jClient.Tests/ApiModels/RootApiResponseTests.cs @@ -1,9 +1,8 @@ using FluentAssertions; -using Xunit; using Neo4jClient.ApiModels; -using Neo4jClient.Test.Fixtures; +using Xunit; -namespace Neo4jClient.Test.ApiModels +namespace Neo4jClient.Tests.ApiModels { public class RootApiResponseTests : IClassFixture diff --git a/Neo4jClient.Tests/ApiUsageIdeas.cs b/Neo4jClient.Tests/ApiUsageIdeas.cs new file mode 100644 index 000000000..26bfcd48f --- /dev/null +++ b/Neo4jClient.Tests/ApiUsageIdeas.cs @@ -0,0 +1,63 @@ +// using System; +// using System.Threading.Tasks; +// using Neo4jClient.Tests.Domain; +// using Neo4jClient.Tests.Relationships; +// +// namespace Neo4jClient.Tests +// { +// // 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 +// { +// async Task Foo() +// { +// IGraphClient graph = new GraphClient(new Uri("")); +// +// // Based on http://wiki.neo4j.org/content/Image:Warehouse.png +// +// // Can create nodes from POCOs +// var frameStore = await graph.CreateAsync( +// new StorageLocation { Name = "Frame Store" }); +// var mainStore = await graph.CreateAsync( +// new StorageLocation { Name = "Main Store" }); +// +// // Can create a node with outgoing relationships +// var frame = await graph.CreateAsync( +// new Part { Name = "Frame" }, +// new StoredIn(frameStore)); +// +// // Can create multiple outgoing relationships and relationships with payloads +// await graph.CreateAsync( +// new Product { Name = "Trike", Weight = 2 }, +// new StoredIn(mainStore), +// new Requires(frame, new Requires.Payload { Count = 1 })); +// +// // Can create relationships in both directions +// await graph.CreateAsync( +// new Part { Name = "Pedal" }, +// new StoredIn(frameStore), +// new Requires(frame, new Requires.Payload { Count = 2 }) +// { Direction = RelationshipDirection.Incoming }); +// +// var wheel = await graph.CreateAsync( +// new Part { Name = "Wheel" }, +// new Requires(frame, new Requires.Payload { Count = 2 }) +// { Direction = RelationshipDirection.Incoming }); +// +// // Can create implicit incoming relationships +// await graph.CreateAsync( +// new StorageLocation { Name = "Wheel Store" }, +// new StoredIn(wheel)); +// +// // Can create relationships against the root node +// await graph.CreateAsync( +// new StorageLocation {Name = "Auxillary Store"}, +// new StoredIn(wheel), +// new OwnedBy(graph.RootNode)); +// } +// } +// } \ No newline at end of file diff --git a/Neo4jClient.Tests/BoltGraphClientTests/BoltGraphClientTests.cs b/Neo4jClient.Tests/BoltGraphClientTests/BoltGraphClientTests.cs new file mode 100644 index 000000000..779eab84f --- /dev/null +++ b/Neo4jClient.Tests/BoltGraphClientTests/BoltGraphClientTests.cs @@ -0,0 +1,328 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Moq; +using Neo4j.Driver; +using Neo4jClient.Tests.Extensions; +using Newtonsoft.Json; +using Xunit; + +namespace Neo4jClient.Tests.BoltGraphClientTests +{ + public class BoltGraphClientTests : IClassFixture + { + private class TestNode { } + private class TestRecord : IRecord + { + private IDictionary _contents = new Dictionary(); + + public TestRecord() + { + + } + + public TestRecord(IDictionary items) + { + _contents = items; + } + + object IRecord.this[int index] + { + get { throw new NotImplementedException(); } + } + + object IRecord.this[string key] => _contents[key]; + + public IReadOnlyDictionary Values { get; } + public IReadOnlyList Keys { get; } + + + } + public class ServerInfo : TestStatementResult + { + public ServerInfo(): base(new List {new TestRecord(new Dictionary + { + {"name", "neo4j kernel"}, + {"versions", new List{"3.2.3"} } + })}) + { + } + + public ServerInfo(IList records): base(records) + { + } + } + + public class ClassWithSomeNeo4jIgnoreAttributes + { + public string Text { get; set; } + [Neo4jIgnore] + public string TextIgnore { get; set; } + public int TestInt { get; set; } + [Neo4jIgnore] + public int TestNeo4jIntIgnore { get; set; } + [JsonIgnore] + public int TestJsonIntIgnore { get; set; } + } + + [Fact] + //[Description("test bolt part of https://github.com/Readify/Neo4jClient/issues/336 https://github.com/Readify/Neo4jClient/pull/337 - see UserSuppliedSerializationTests for https part")] + public async Task JsonSerializerShouldNotSerializeNeo4jIgnoreAttribute() + { + var mockSession = new Mock(); + mockSession.Setup(s => s.RunAsync("CALL dbms.components()")).Returns(Task.FromResult(new ServerInfo())); + + var mockDriver = new Mock(); + mockDriver.Setup(d => d.AsyncSession(It.IsAny>())).Returns(mockSession.Object); + + var bgc = new BoltGraphClient(mockDriver.Object); + await bgc.ConnectAsync(); + + var cwa = new ClassWithSomeNeo4jIgnoreAttributes { Text = "foo", TextIgnore = "fooignore", TestInt = 42, TestNeo4jIntIgnore = 42, TestJsonIntIgnore = 42 }; + + var cfq = bgc.Cypher.Create("(c)").WithParam("testParam", cwa); + + var expectedParameters = new Dictionary + { + {"testParam", new Dictionary {{"Text", "foo"}, { "TestInt", 42} } + }}; + + var query = cfq.Query; + query.ToNeo4jDriverParameters(bgc).IsEqualTo(expectedParameters).Should().BeTrue(); + } + + + [Fact] + public async Task SerializesDateTimesProperly() + { + var mockSession = new Mock(); + mockSession.Setup(s => s.RunAsync("CALL dbms.components()")).Returns(Task.FromResult(new ServerInfo())); + + var mockDriver = new Mock(); + mockDriver.Setup(d => d.AsyncSession(It.IsAny>())).Returns(mockSession.Object); + + var bgc = new BoltGraphClient(mockDriver.Object); + await bgc.ConnectAsync(); + + var cwd = new ClassWithDateTime{Dt = new DateTime(2000, 1, 1)};; + + var cfq = bgc.Cypher.Create("(c)").WithParam("testParam", cwd); + + var expectedParameters = new Dictionary + { + { + "testParam", new Dictionary {{"Dt", JsonConvert.SerializeObject(cwd.Dt).Trim('\"')}} + } + }; + + var query = cfq.Query; + query.ToNeo4jDriverParameters(bgc).IsEqualTo(expectedParameters).Should().BeTrue(); + } + + [Fact] + public async Task SerializesDateTimeOffsetsProperly() + { + var mockSession = new Mock(); + mockSession.Setup(s => s.RunAsync("CALL dbms.components()")).Returns(Task.FromResult(new ServerInfo())); + + var mockDriver = new Mock(); + mockDriver.Setup(d => d.AsyncSession(It.IsAny>())).Returns(mockSession.Object); + + var bgc = new BoltGraphClient(mockDriver.Object); + await bgc.ConnectAsync(); + + var cwd = new ClassWithDateTimeOffset { Dt = new DateTimeOffset(new DateTime(2000, 1, 1), TimeSpan.FromHours(1)) }; ; + + var cfq = bgc.Cypher.Create("(c)").WithParam("testParam", cwd); + + var expectedParameters = new Dictionary + { + { + "testParam", new Dictionary {{"Dt", JsonConvert.SerializeObject(cwd.Dt).Trim('\"')}} + } + }; + + var query = cfq.Query; + query.ToNeo4jDriverParameters(bgc).IsEqualTo(expectedParameters).Should().BeTrue(); + } + + [Fact] + public async Task SerializesGuidsProperly() + { + var mockSession = new Mock(); + mockSession.Setup(s => s.RunAsync("CALL dbms.components()")).Returns(Task.FromResult(new ServerInfo())); + + var mockDriver = new Mock(); + mockDriver.Setup(d => d.AsyncSession(It.IsAny>())).Returns(mockSession.Object); + + var bgc = new BoltGraphClient(mockDriver.Object); + await bgc.ConnectAsync(); + + var cwg = new ClassWithGuid(); + + var cfq = bgc.Cypher.Create("(c)").WithParam("testParam", cwg); + + var expectedParameters = new Dictionary + { + {"testParam", new Dictionary{{"Id", cwg.Id.ToString()} } + }}; + + var query = cfq.Query; + query.ToNeo4jDriverParameters(bgc).IsEqualTo(expectedParameters).Should().BeTrue(); + } + + [Fact] + public async Task SerializesGuidsProperlyWhenAutoGeneratingParams() + { + var mockSession = new Mock(); + mockSession.Setup(s => s.RunAsync("CALL dbms.components()")).Returns(Task.FromResult(new ServerInfo())); + + var mockDriver = new Mock(); + mockDriver.Setup(d => d.AsyncSession(It.IsAny>())).Returns(mockSession.Object); + + var bgc = new BoltGraphClient( mockDriver.Object); + await bgc.ConnectAsync(); + + var cwg = new ClassWithGuid(); + + var cfq = bgc.Cypher.Create("(c)").Where((ClassWithGuid c) => c.Id == cwg.Id); + + var expectedParameters = new Dictionary {{"p0", $"{cwg.Id}"}}; + + var query = cfq.Query; + query.ToNeo4jDriverParameters(bgc).IsEqualTo(expectedParameters).Should().BeTrue(); + } + // + // [Fact] + // public void RootNode_ThrowsInvalidOperationException() + // { + // var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); + // var ex = Record.Exception(() => bgc.RootNode); + // ex.Should().NotBeNull(); + // ex.Should().BeOfType(); + // ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); + // } + // + // [Fact] + // public async Task Create_ThrowsInvalidOperationException() + // { + // var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); + // var ex = await Record.ExceptionAsync(async () => await bgc.CreateAsync("value", null)); + // ex.Should().NotBeNull(); + // ex.Should().BeOfType(); + // ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); + // } + + // [Fact] + // public async Task GetAsync_ThrowsInvalidOperationException() + // { + // var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); + // var ex = await Record.ExceptionAsync(async () => await bgc.GetAsync(new NodeReference(1))); + // ex.Should().NotBeNull(); + // ex.Should().BeOfType(); + // ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); + // } + // + // [Fact] + // public async Task GetAsync_RelationReference_ThrowsInvalidOperationException() + // { + // var bgc = new BoltGraphClient(DriverTestHelper.MockDriverWithConnectionSet().Object); + // var ex = await Record.ExceptionAsync(async () => await bgc.GetAsync(new RelationshipReference(1))); + // ex.Should().NotBeNull(); + // ex.Should().BeOfType(); + // ex.Message.Should().Be(BoltGraphClient.NotValidForBolt); + // } + + public class Constructor : IClassFixture + { + [Fact] + public void DoesntUseAddressResolverWhenPassingInOneUri() + { + var bgc = new BoltGraphClient($"neo4j://virtual.foo.com"); + bgc.AddressResolver.Should().BeNull(); + } + + [Theory] + [InlineData("neo4j")] + [InlineData("neo4j+s")] + [InlineData("neo4j+ssc")] + public void UsesAddressResolverWhenPassingInMultipleUris(string scheme) + { + var bgc = new BoltGraphClient($"{scheme}://virtual.foo.com", new[] {"x.foo.com", "y.foo.com"}); + var resolved = bgc.AddressResolver.Resolve(null); + resolved.Should().HaveCount(2); + } + + [Theory] + [InlineData("bolt", true)] + [InlineData("bolt+s", true)] + [InlineData("bolt+ssc", true)] + [InlineData("neo4j", true)] + [InlineData("neo4j+s", true)] + [InlineData("neo4j+ssc", true)] + [InlineData("http", false)] + [InlineData("ftp", false)] + public void ValidityForVariousUriSchemes(string scheme, bool expectedValid) + { + var ex = Record.Exception(() => new BoltGraphClient($"{scheme}://virtual.foo.com")); + if (expectedValid) ex.Should().BeNull(); + else ex.Should().NotBeNull(); + } + + [Fact] + public void DoesntNeedVirtualUriToBeSupplied() + { + const string uri = "x.foo.com"; + + var bgc = new BoltGraphClient( new[] { $"{uri}" }); + var resolved = bgc.AddressResolver.Resolve(null); + resolved.Should().HaveCount(1); + resolved.First().Host.Should().Be(uri); + } + + [Theory] + [InlineData("bolt")] + [InlineData("bolt+s")] + [InlineData("bolt+ssc")] + [InlineData("https")] + [InlineData("http")] + [InlineData("ftp")] + public void NotValidForOtherUriSchemes(string scheme) + { + var ex = Record.Exception(() => new BoltGraphClient($"{scheme}://virtual.foo.com", new [] {"x.foo.com", "y.foo.com"} )); + ex.Should().NotBeNull(); + ex.Should().BeOfType(); + } + + [Theory] + [InlineData("bolt")] + [InlineData("bolt+s")] + [InlineData("bolt+ssc")] + [InlineData("https")] + [InlineData("http")] + [InlineData("ftp")] + public void WorksIfYouPassInWholeUris(string schema) + { + const string uri = "x.foo.com"; + + var bgc = new BoltGraphClient($"neo4j://virtual.foo.com", new[] { $"{schema}://{uri}" }); + var resolved = bgc.AddressResolver.Resolve(null); + resolved.Should().HaveCount(1); + resolved.First().Host.Should().Be(uri); + } + + [Fact] + public void WorksIfYouPassInUrisWithoutScheme() + { + const string uri = "x.foo.com"; + + var bgc = new BoltGraphClient($"neo4j://virtual.foo.com", new[] { uri }); + var resolved = bgc.AddressResolver.Resolve(null); + resolved.Should().HaveCount(1); + resolved.First().Host.Should().Be(uri); + } + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/BoltGraphClientTests/Bookmarks/BookmarkTests.cs b/Neo4jClient.Tests/BoltGraphClientTests/Bookmarks/BookmarkTests.cs similarity index 54% rename from Neo4jClient.Tests.Shared/BoltGraphClientTests/Bookmarks/BookmarkTests.cs rename to Neo4jClient.Tests/BoltGraphClientTests/Bookmarks/BookmarkTests.cs index ac744be59..589d03266 100644 --- a/Neo4jClient.Tests.Shared/BoltGraphClientTests/Bookmarks/BookmarkTests.cs +++ b/Neo4jClient.Tests/BoltGraphClientTests/Bookmarks/BookmarkTests.cs @@ -1,15 +1,14 @@ +using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using FluentAssertions; using Moq; -using Neo4j.Driver.V1; +using Neo4j.Driver; using Neo4jClient.Cypher; -using Neo4jClient.Test.BoltGraphClientTests.Cypher; -using Neo4jClient.Test.Fixtures; -using Neo4jClient.Transactions; using Xunit; -namespace Neo4jClient.Test.BoltGraphClientTests +namespace Neo4jClient.Tests.BoltGraphClientTests.Bookmarks { public class BookmarkTests : IClassFixture { @@ -21,16 +20,27 @@ private class ObjectWithIds } [Fact] - public void ArgsContainBookmarksUsed() + public async Task TestMocking() + { + Mock sessionMock = new Mock(); + Mock driverMock = new Mock(); + driverMock.Setup(d => d.AsyncSession()).Returns(sessionMock.Object); + driverMock.Setup(d => d.AsyncSession(It.IsAny>())).Returns(sessionMock.Object); + + driverMock.Object.AsyncSession(x => x.WithBookmarks(Bookmark.From("x"))).Should().NotBeNull(); + } + + [Fact] + public async Task ArgsContainBookmarksUsed() { // Arrange - var bookmarks = new List {"Bookmark1", "Bookmark2"}; + var bookmarks = new List {Bookmark.From("Bookmark1"), Bookmark.From("Bookmark2")}; const string queryText = "RETURN [] AS data"; var queryParams = new Dictionary(); - var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional) {Bookmarks = bookmarks}; + var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional, "neo4j") {Bookmarks = bookmarks}; using (var testHarness = new BoltTestHarness()) { @@ -41,30 +51,35 @@ public void ArgsContainBookmarksUsed() var testStatementResult = new TestStatementResult(new[] {"data"}, recordMock.Object); testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); graphClient.OperationCompleted += (s, e) => { e.BookmarksUsed.Should().Contain(bookmarks[0]); e.BookmarksUsed.Should().Contain(bookmarks[1]); }; - graphClient.ExecuteGetCypherResults>(cypherQuery); + var driverSess = testHarness.MockDriver.Object.AsyncSession(); + testHarness.MockDriver.Verify(s => s.AsyncSession(), Times.Once); + + driverSess.Should().NotBeNull(); + + await graphClient.ExecuteGetCypherResultsAsync>(cypherQuery); } } [Fact] - public void ArgsContainBookmarkUsed() + public async Task ArgsContainBookmarkUsed() { // Arrange - const string bookmark = "Bookmark1"; - var bookmarks = new List {bookmark}; + var bookmark = Bookmark.From("Bookmark1"); + var bookmarks = new List {bookmark}; const string queryText = "RETURN [] AS data"; var queryParams = new Dictionary(); - var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional) {Bookmarks = bookmarks}; + var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional, "neo4j") {Bookmarks = bookmarks}; using (var testHarness = new BoltTestHarness()) { @@ -75,23 +90,23 @@ public void ArgsContainBookmarkUsed() var testStatementResult = new TestStatementResult(new[] {"data"}, recordMock.Object); testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); graphClient.OperationCompleted += (s, e) => { e.BookmarksUsed.Should().Contain(bookmarks[0]); }; - graphClient.ExecuteGetCypherResults>(cypherQuery); + await graphClient.ExecuteGetCypherResultsAsync>(cypherQuery); } } [Fact] - public void ArgsContainLastBookmark() + public async Task ArgsContainLastBookmark() { - const string lastBookmark = "LastBookmark"; + var lastBookmark = Bookmark.From("LastBookmark"); const string queryText = "RETURN [] AS data"; var queryParams = new Dictionary(); - var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional); + var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional, "neo4j"); using (var testHarness = new BoltTestHarness()) { @@ -104,50 +119,58 @@ public void ArgsContainLastBookmark() var testStatementResult = new TestStatementResult(new[] {"data"}, recordMock.Object); testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); graphClient.OperationCompleted += (s, e) => { e.LastBookmark.Should().Be(lastBookmark); }; - graphClient.ExecuteGetCypherResults>(cypherQuery); + await graphClient.ExecuteGetCypherResultsAsync>(cypherQuery); } } - [Fact] - public void SessionIsCalledWithBookmark() + [Fact(Skip = "Can't test this at the moment, as there is no way to get the SessionConfigBuilder results")] + public async Task SessionIsCalledWithBookmark() { // Arrange - const string bookmark = "Bookmark1"; + var bookmark = Bookmark.From("Bookmark1"); - var cypherQuery = new CypherQuery("RETURN 1", new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Transactional) {Bookmarks = new List {bookmark}}; + var cypherQuery = new CypherQuery("RETURN 1", new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Transactional, "neo4j") {Bookmarks = new List{bookmark}}; using (var testHarness = new BoltTestHarness()) { try { - testHarness.CreateAndConnectBoltGraphClient().ExecuteGetCypherResults(cypherQuery).ToArray(); + (await (await testHarness.CreateAndConnectBoltGraphClient()).ExecuteGetCypherResultsAsync(cypherQuery)).ToArray(); } catch { /*Not interested in actually getting results*/ } + Action scb; + //Assert - testHarness.MockDriver.Verify(d => d.Session(It.IsAny(), It.Is>(s => s.Contains(bookmark))), Times.Once); + // testHarness.MockDriver.Verify(d => d.AsyncSession(It.IsAny(), It.Is>(s => s.Contains(bookmark))), Times.Once); + // testHarness.MockDriver.Verify(d => d.AsyncSession(It.Is>())); + + // testHarness.MockDriver.Object.Config + testHarness.MockDriver.Verify(d => d.AsyncSession(It.IsAny>()), Times.Once); + throw new NotImplementedException(); } } - [Fact] - public void SessionIsCalledWithBookmarks() + + [Fact(Skip = "Can't test this at the moment, as there is no way to get the SessionConfigBuilder results")] + public async Task SessionIsCalledWithBookmarks() { // Arrange - var bookmarks = new List {"Bookmark1", "Bookmark2"}; + var bookmarks = new List { Bookmark.From("Bookmark1"), Bookmark.From("Bookmark2") }; - var cypherQuery = new CypherQuery("RETURN 1", new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Transactional) {Bookmarks = bookmarks}; + var cypherQuery = new CypherQuery("RETURN 1", new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Transactional, "neo4j") {Bookmarks = bookmarks}; using (var testHarness = new BoltTestHarness()) { try { - testHarness.CreateAndConnectBoltGraphClient().ExecuteGetCypherResults(cypherQuery).ToArray(); + (await (await testHarness.CreateAndConnectBoltGraphClient()).ExecuteGetCypherResultsAsync(cypherQuery)).ToArray(); } catch { @@ -155,7 +178,10 @@ public void SessionIsCalledWithBookmarks() } //Assert - testHarness.MockDriver.Verify(d => d.Session(It.IsAny(), It.Is>(s => s.Contains(bookmarks[0]) && s.Contains(bookmarks[1]))), Times.Once); + // testHarness.MockDriver.Verify(d => d.Session(It.IsAny(), It.Is>(s => s.Contains(bookmarks[0]) && s.Contains(bookmarks[1]))), Times.Once); + testHarness.MockDriver.Verify(d => d.AsyncSession(It.IsAny>()), Times.Once); + + throw new NotImplementedException(); } } } diff --git a/Neo4jClient.Tests.Shared/BoltGraphClientTests/ConnectAsyncTests.cs b/Neo4jClient.Tests/BoltGraphClientTests/ConnectAsyncTests.cs similarity index 58% rename from Neo4jClient.Tests.Shared/BoltGraphClientTests/ConnectAsyncTests.cs rename to Neo4jClient.Tests/BoltGraphClientTests/ConnectAsyncTests.cs index 5a3fdab5d..829d7f4d6 100644 --- a/Neo4jClient.Tests.Shared/BoltGraphClientTests/ConnectAsyncTests.cs +++ b/Neo4jClient.Tests/BoltGraphClientTests/ConnectAsyncTests.cs @@ -1,15 +1,12 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; using FluentAssertions; using Moq; -using Neo4j.Driver.V1; -using Neo4jClient.Test.Fixtures; -using NSubstitute; +using Neo4j.Driver; using Xunit; -namespace Neo4jClient.Test.BoltGraphClientTests +namespace Neo4jClient.Tests.BoltGraphClientTests { internal static class DriverTestHelper { @@ -18,25 +15,26 @@ public static Mock MockDriverWithConnectionSet() const string uri = "bolt://localhost"; var recordMock = new Mock(); recordMock.Setup(r => r["name"]).Returns("neo4j kernel"); - recordMock.Setup(r => r["versions"]).Returns(new List { "3.2.3" }); + recordMock.Setup(r => r["versions"]).Returns(new List {"3.2.3"}); - var testSr = new TestStatementResult(new[] { recordMock.Object }); - var sessionMock = new Mock(); + var testSr = new TestStatementResult(new[] {recordMock.Object}); + var sessionMock = new Mock(); sessionMock - .Setup(s => s.Run("CALL dbms.components()")) - .Returns(testSr); + .Setup(s => s.RunAsync("CALL dbms.components()")) + .Returns(Task.FromResult(testSr)); var driverMock = new Mock(); - driverMock.Setup(d => d.Session(It.IsAny())).Returns(sessionMock.Object); - driverMock.Setup(d => d.Session()).Returns(sessionMock.Object); - driverMock.Setup(d => d.Uri).Returns(new Uri(uri)); + driverMock.Setup(d => d.AsyncSession(It.IsAny>())).Returns(sessionMock.Object); + driverMock.Setup(d => d.AsyncSession()).Returns(sessionMock.Object); + //driverMock.Setup(d => d.Uri).Returns(new Uri(uri)); return driverMock; } } - internal class TestStatementResult : IStatementResult + public class TestStatementResult : IResultCursor { private readonly IList records; + private int pos = -1; public TestStatementResult(IEnumerable keys, params IRecord[] records) { @@ -49,31 +47,45 @@ public TestStatementResult(IEnumerable records) this.records = new List(records); } - public IEnumerator GetEnumerator() + public Task SummaryAsync() { - return records.GetEnumerator(); + throw new NotImplementedException(); } - IEnumerator IEnumerable.GetEnumerator() + public Task PeekAsync() { - return GetEnumerator(); + return Task.FromResult(Current); } - - public IRecord Peek() + public Task KeysAsync() { throw new NotImplementedException(); } - public IResultSummary Consume() + public Task ConsumeAsync() { - throw new NotImplementedException(); + var countersMock = new Mock(MockBehavior.Loose); + var mock = new Mock(); + mock.Setup(x => x.Counters).Returns(countersMock.Object); + return Task.FromResult(mock.Object); + } + + public Task FetchAsync() + { + if (pos == records.Count - 1) return Task.FromResult(false); + else + { + pos++; + return Task.FromResult(true); + } } public IReadOnlyList Keys { get; } - public IResultSummary Summary { get; } + public IRecord Current => records[pos]; } + + public class ConnectAsyncTests : IClassFixture { [Fact] @@ -86,18 +98,17 @@ public async Task SkipsOverExtraFieldsInDbmsComponents() var record2Mock = new Mock(); record2Mock.Setup(r => r["name"]).Returns("neo4j kernel"); - record2Mock.Setup(r => r["versions"]).Returns(new List { "3.2.3" }); + record2Mock.Setup(r => r["versions"]).Returns(new List {"3.2.3"}); - var testSr = new TestStatementResult(new[] { record1Mock.Object, record2Mock.Object }); - var sessionMock = new Mock(); + var testSr = new TestStatementResult(new[] {record1Mock.Object, record2Mock.Object}); + var sessionMock = new Mock(); sessionMock - .Setup(s => s.Run("CALL dbms.components()")) - .Returns(testSr); + .Setup(s => s.RunAsync("CALL dbms.components()")) + .Returns(Task.FromResult(testSr)); var driverMock = new Mock(); - driverMock.Setup(d => d.Session(It.IsAny())).Returns(sessionMock.Object); - driverMock.Setup(d => d.Session()).Returns(sessionMock.Object); - driverMock.Setup(d => d.Uri).Returns(new Uri(uri)); + driverMock.Setup(d => d.AsyncSession(It.IsAny>())).Returns(sessionMock.Object); + var bgc = new BoltGraphClient(driverMock.Object); await bgc.ConnectAsync(); @@ -105,7 +116,6 @@ public async Task SkipsOverExtraFieldsInDbmsComponents() } - [Fact] public async Task SetsTheVersionOfTheServer() { diff --git a/Neo4jClient.Tests/BoltGraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs b/Neo4jClient.Tests/BoltGraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs new file mode 100644 index 000000000..e05e1b25d --- /dev/null +++ b/Neo4jClient.Tests/BoltGraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs @@ -0,0 +1,722 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Moq; +using Neo4j.Driver; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; +using Xunit; + +namespace Neo4jClient.Tests.BoltGraphClientTests.Cypher +{ + internal class TestPath : IPath + { + #region Implementation of IEquatable + + public bool Equals(IPath other) + { + throw new NotImplementedException(); + } + + #endregion + + #region Implementation of IPath + + public INode Start { get; set; } + public INode End { get; set; } + public IReadOnlyList Nodes { get; set; } + public IReadOnlyList Relationships { get; set; } + + #endregion + } + + public class TestRelationship : IRelationship + { + #region Implementation of IEntity + + public object this[string key] + { + get { throw new NotImplementedException(); } + } + + public IReadOnlyDictionary Properties { get; set; } + public long Id { get; set; } + + #endregion + + #region Implementation of IEquatable + + public bool Equals(IRelationship other) + { + throw new NotImplementedException(); + } + + #endregion + + #region Implementation of IRelationship + + public string Type { get; set; } + public long StartNodeId { get; set; } + public long EndNodeId { get; set; } + + #endregion + } + + public class TestNode : INode { + #region Implementation of IEntity + + public object this[string key] + { + get { throw new NotImplementedException(); } + } + + public IReadOnlyDictionary Properties { get; set; } + public long Id { get; set; } + + #endregion + + #region Implementation of IEquatable + + public bool Equals(INode other) + { + throw new NotImplementedException(); + } + + #endregion + + #region Implementation of INode + + public IReadOnlyList Labels { get; } + + #endregion + } + + public class ExecuteGetCypherResultsTests : IClassFixture + { + public class SimpleResultDto + { + public string RelationshipType { get; set; } + public string Name { get; set; } + public long? UniqueId { get; set; } + } + + private class ObjectWithId + { + public long Id { get; set; } + } + + private class ObjectWithIds + { + public List Ids { get; set; } + } + + private class RelationType + { + public int Id { get; set; } + } + + private class RelationGrouper + { + public RelationType Rel { get; set; } + } + + [Fact] + public async Task RelationshipShouldDeserializeInDefinedType() + { + // Arrange + const string queryText = "MATCH (n:Test)-[r]->(t:Test) RETURN r AS Rel"; + + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Transactional, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + var relationshipMock = new Mock(); + relationshipMock + .Setup(r => r.StartNodeId) + .Returns(1); + relationshipMock + .Setup(r => r.EndNodeId) + .Returns(2); + relationshipMock + .Setup(r => r.Type) + .Returns("Xx"); + relationshipMock + .Setup(r => r.Id) + .Returns(3); + relationshipMock + .Setup(r => r.Properties) + .Returns(new Dictionary() + { + {"Id", 42} + }); + + var recordMock = new Mock(); + recordMock + .Setup(r => r["Rel"]) + .Returns(relationshipMock.Object); + recordMock + .Setup(r => r.Keys) + .Returns(new[] { "Rel" }); + + var testStatementResult = new TestStatementResult(new[] { "Rel" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var results = (await graphClient.ExecuteGetCypherResultsAsync(cypherQuery)).ToArray(); + + //Assert + results.Length.Should().Be(1); + var relation = results.First().Rel; + relation.Id.Should().Be(42); + } + } + + [Fact] + public async Task RelationshipShouldDeserializeInAnonymousType() + { + // Arrange + const string queryText = @"MATCH (n:Test)-[r]->(t:Test) RETURN r AS Rel"; + + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Transactional, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + var relationshipMock = new Mock(); + relationshipMock + .Setup(r => r.StartNodeId) + .Returns(1); + relationshipMock + .Setup(r => r.EndNodeId) + .Returns(2); + relationshipMock + .Setup(r => r.Type) + .Returns("Xx"); + relationshipMock + .Setup(r => r.Id) + .Returns(3); + relationshipMock + .Setup(r => r.Properties) + .Returns(new Dictionary() + { + {"Id", 42} + }); + + var recordMock = new Mock(); + recordMock + .Setup(r => r["Rel"]) + .Returns(relationshipMock.Object); + recordMock + .Setup(r => r.Keys) + .Returns(new[] { "Rel" }); + + var testStatementResult = new TestStatementResult(new[] { "Rel" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + //Session mock??? + var dummy = new + { + Rel = new RelationType() + }; + var anonType = dummy.GetType(); + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var genericGetCypherResults = typeof(IRawGraphClient).GetMethod(nameof(graphClient.ExecuteGetCypherResultsAsync)); + var anonymousGetCypherResults = genericGetCypherResults.MakeGenericMethod(anonType); + var genericResultsTask = anonymousGetCypherResults.Invoke(graphClient, new object[] { cypherQuery }); + await (Task) genericResultsTask; + var genericResults = (IEnumerable) ((dynamic) genericResultsTask).Result; + + var results = genericResults.Cast().ToArray(); + + //Assert + Assert.Equal(1, results.Length); + var relation = (RelationType)anonType.GetProperty(nameof(dummy.Rel)).GetValue(results.First(), null); + relation.Id.Should().Be(42); + } + } + + [Fact] + public async Task CollectionShouldDeserializeCorrectly() + { + // simulate a collect() + const string queryText = "MATCH (start:Node) RETURN collect(start) AS data"; + + var queryParams = new Dictionary(); + + var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + var nodeMock = new Mock(); + nodeMock + .Setup(n => n.Id) + .Returns(1); + nodeMock + .Setup(n => n.Properties) + .Returns(new Dictionary() {{"Ids", new List {1, 2, 3}}}); + + var recordMock = new Mock(); + recordMock + .Setup(r => r["data"]) + .Returns(new List() {nodeMock.Object}); + recordMock + .Setup(r => r.Keys) + .Returns(new[] { "data" }); + + var testStatementResult = new TestStatementResult(new[] { "data" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var results = (await graphClient.ExecuteGetCypherResultsAsync>(cypherQuery)).ToArray(); + + //Assert + var deserializedObject = results.First().First(); + deserializedObject.Ids.Count.Should().Be(3); + deserializedObject.Ids[0].Should().Be(1); + deserializedObject.Ids[1].Should().Be(2); + deserializedObject.Ids[2].Should().Be(3); + } + } + + [Fact] + public async Task EmptyCollectionShouldDeserializeCorrectly() + { + const string queryText = "RETURN [] AS data"; + + var queryParams = new Dictionary(); + + var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + var recordMock = new Mock(); + recordMock + .Setup(r => r["data"]) + .Returns(new List() {}); + recordMock + .Setup(r => r.Keys) + .Returns(new[] { "data" }); + + var testStatementResult = new TestStatementResult(new[] { "data" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var results = (await graphClient.ExecuteGetCypherResultsAsync>(cypherQuery)).ToArray(); + + results.Should().BeEmpty(); + } + } + + //https://github.com/readify/neo4jclient/issues/266 + [Fact] + public async Task CollectionOfComplexTypesShouldDeserializeCorrectlyWhenInConjunctionWithAnotherComplexTypeInAContainer() + { + const string queryText = "MATCH (start:Node)-->(next:Node) RETURN start AS Start, collect(next) AS Next"; + + var queryParams = new Dictionary(); + + var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Projection, CypherResultFormat.Transactional, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + var startNodeMock = new Mock(); + startNodeMock + .Setup(n => n.Id) + .Returns(1); + startNodeMock + .Setup(n => n.Properties) + .Returns(new Dictionary { { "Id", 1 } }); + + var nextNodeMock = new Mock(); + nextNodeMock + .Setup(n => n.Id) + .Returns(2); + nextNodeMock + .Setup(n => n.Properties) + .Returns(new Dictionary { { "Id", 2 } }); + + var recordMock = new Mock(); + recordMock + .Setup(r => r["Next"]) + .Returns(new List { nextNodeMock.Object }); + + recordMock + .Setup(r => r["Start"]) + .Returns(startNodeMock.Object); + + recordMock + .Setup(r => r.Keys) + .Returns(new[] { "Start", "Next" }); + + var testStatementResult = new TestStatementResult(new[] { "Start", "Next" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var results = (await graphClient.ExecuteGetCypherResultsAsync(cypherQuery)).ToArray(); + + //Assert + var deserializedObject = results.First(); + deserializedObject.Start.Should().NotBeNull(); + deserializedObject.Start.Id.Should().Be(1); + + var deserializedNext = deserializedObject.Next.ToList(); + deserializedNext.Should().HaveCount(1); + deserializedNext.First().Id.Should().Be(2); + } + } + + private class Container + { + public ObjectWithId Start { get; set; } + public IEnumerable Next { get; set; } + } + + //see: https://github.com/DotNet4Neo4j/Neo4jClient/issues/368 + [Fact] + public async Task ShouldBeAbleToCastToObject_WhenUsingReturnAs() + { + const string queryText = "MATCH (user)-[:hasPost]->(post:Post) WHERE(user.Username = 'user1') RETURN user{.Username} AS User, post AS Post"; + + var queryParams = new Dictionary(); + + var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Projection, CypherResultFormat.Transactional, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + const string username = "User1"; + const string postName = "Post1"; + + var user = new Dictionary { { "Username", username } }; + var postNodeMock = new Mock(); + postNodeMock.Setup(n => n.Id).Returns(1); + postNodeMock.Setup(n => n.Properties) + .Returns(new Dictionary { { "Content", postName } }); + + var recordMock = new Mock(); + recordMock + .Setup(r => r["User"]) + .Returns(user); + + recordMock + .Setup(r => r["Post"]) + .Returns(postNodeMock.Object); + + recordMock + .Setup(r => r.Keys) + .Returns(new[] { "User", "Post" }); + + recordMock.Setup(r => r.Values) + .Returns(new Dictionary { { "User", user }, { "Post", postNodeMock.Object } }); + + var testStatementResult = new TestStatementResult(new[] { "User", "Post" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var results = (await graphClient.ExecuteGetCypherResultsAsync(cypherQuery)).ToArray(); + // + //Assert + var deserializedObject = results.First(); + deserializedObject.User.Should().NotBeNull(); + deserializedObject.User.Username.Should().Be(username); + } + + } + private class User + { + public string Username { get; set; } + public string Password { get; set; } + } + + private class Post + { + public string Content { get; set; } + } + + private class PostAndUser + { + public User User { get; set; } + public Post Post { get; set; } + } + + [Fact] + public async Task CreateWithArrayParametersShouldSerializeAndDeserializeOnReturn() + { + // Arrange + const string queryText = "CREATE (start:Node {obj}) RETURN start"; + + var testNode = new ObjectWithIds() + { + Ids = new List() {1, 2, 3} + }; + + var queryParams = new Dictionary() {{"obj", testNode}}; + + var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + var recordMock = new Mock(); + recordMock + .Setup(r => r["start"]) + .Returns(testNode); + recordMock + .Setup(r => r.Keys) + .Returns(new[] { "start" }); + + var testStatementResult = new TestStatementResult(new[] { "start" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var results = (await graphClient.ExecuteGetCypherResultsAsync(cypherQuery)).ToArray(); + + //Assert + Assert.IsAssignableFrom>(results); + results.First().Ids.Count.Should().Be(3); + results.First().Ids[0].Should().Be(1); + results.First().Ids[1].Should().Be(2); + results.First().Ids[2].Should().Be(3); + } + } + + private class ObjectWithNodeWithIds + { + public ObjectWithIds Node { get; set; } + public int Count { get; set; } + } + + [Fact] + public async Task ShouldDeserializeMapWithAnonymousReturnAsDictionary() + { + // simulates the following query + const string queryText = "MATCH (start:Node) WITH {Node: 3, Count: 1} AS Node, start RETURN Node, start"; + + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, + CypherResultFormat.Transactional, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + INode start = new TestNode + { + Id = 1337, + Properties = new Dictionary() + { + {"Ids", new List() {1, 2, 3}} + } + }; + IDictionary resultMap = new Dictionary() + { + {"Node", 3}, + {"Count", 1} + }; + + var recordMock = new Mock(); + recordMock + .Setup(r => r["Node"]) + .Returns(resultMap); + recordMock + .Setup(r => r["Start"]) + .Returns(start); + recordMock + .Setup(r => r.Keys) + .Returns(new[] { "Node", "Start" }); + + var testStatementResult = new TestStatementResult(new[] { "Node", "Start" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + // the anon type + var dummy = new + { + Node = new Dictionary(), + Start = new ObjectWithIds() + }; + var anonType = dummy.GetType(); + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var genericGetCypherResults = typeof(IRawGraphClient).GetMethod(nameof(graphClient.ExecuteGetCypherResultsAsync)); + var anonymousGetCypherResults = genericGetCypherResults.MakeGenericMethod(anonType); + var genericResultsTask = anonymousGetCypherResults.Invoke(graphClient, new object[] { cypherQuery }); + await (Task) genericResultsTask; + var genericResults = (IEnumerable) ((dynamic) genericResultsTask).Result;; + + var results = genericResults.Cast().ToArray(); + + //Assert + Assert.Equal(1, results.Length); + + var startNode = (ObjectWithIds)anonType.GetProperty(nameof(dummy.Start)).GetValue(results.First(), null); + startNode.Ids.Count.Should().Be(3); + startNode.Ids[0].Should().Be(1); + startNode.Ids[1].Should().Be(2); + startNode.Ids[2].Should().Be(3); + + var nodeWrapper = (Dictionary)anonType.GetProperty(nameof(dummy.Node)).GetValue(results.First(), null); + nodeWrapper.Keys.Count.Should().Be(2); + nodeWrapper["Node"].Should().Be(3); + nodeWrapper["Count"].Should().Be(1); + } + } + + [Fact] + public async Task ShouldDeserializeMapWithAnonymousReturn() + { + // simulates the following query + const string queryText = "MATCH (start:Node) WITH {Node: start, Count: 1} AS Node, start RETURN Node, start"; + + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, + CypherResultFormat.Transactional, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + INode start = new TestNode + { + Id = 1337, + Properties = new Dictionary() + { + {"Ids", new List() {1, 2, 3}} + } + }; + IDictionary resultMap = new Dictionary() + { + {"Node", start}, + {"Count", 1} + }; + + var recordMock = new Mock(); + recordMock + .Setup(r => r["Node"]) + .Returns(resultMap); + recordMock + .Setup(r => r["Start"]) + .Returns(start); + recordMock + .Setup(r => r.Keys) + .Returns(new[] { "Node", "Start" }); + + var testStatementResult = new TestStatementResult(new[] { "Node", "Start" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + // the anon type + var dummy = new + { + Node = new ObjectWithNodeWithIds(), + Start = new ObjectWithIds() + }; + var anonType = dummy.GetType(); + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var genericGetCypherResults = typeof(IRawGraphClient).GetMethod(nameof(graphClient.ExecuteGetCypherResultsAsync)); + var anonymousGetCypherResults = genericGetCypherResults.MakeGenericMethod(anonType); + var genericResultsTask = anonymousGetCypherResults.Invoke(graphClient, new object[] { cypherQuery }); + await (Task) genericResultsTask; + var genericResults = (IEnumerable) ((dynamic) genericResultsTask).Result; + + var results = genericResults.Cast().ToArray(); + + //Assert + Assert.Equal(1, results.Length); + + var startNode = (ObjectWithIds)anonType.GetProperty(nameof(dummy.Start)).GetValue(results.First(), null); + startNode.Ids.Count.Should().Be(3); + startNode.Ids[0].Should().Be(1); + startNode.Ids[1].Should().Be(2); + startNode.Ids[2].Should().Be(3); + + var nodeWrapper = (ObjectWithNodeWithIds)anonType.GetProperty(nameof(dummy.Node)).GetValue(results.First(), null); + nodeWrapper.Count.Should().Be(1); + startNode = nodeWrapper.Node; + + startNode.Ids.Count.Should().Be(3); + startNode.Ids[0].Should().Be(1); + startNode.Ids[1].Should().Be(2); + startNode.Ids[2].Should().Be(3); + } + + } + + [Fact] + public async Task ShouldDeserializeCollectionsWithAnonymousReturn() + { + // Arrange + const string queryText = @"MATCH (start:Node) RETURN [start.Id, start.Id] AS Ids"; + + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Transactional, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + var recordMock = new Mock(); + recordMock + .Setup(r => r["Ids"]) + .Returns(new[] {1, 2, 3}); + recordMock + .Setup(r => r.Keys) + .Returns(new[] { "Ids" }); + + var testStatementResult = new TestStatementResult(new[] { "Ids" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + //Session mock??? + var dummy = new + { + Ids = new List() + }; + var anonType = dummy.GetType(); + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var genericGetCypherResults = typeof(IRawGraphClient).GetMethod(nameof(graphClient.ExecuteGetCypherResultsAsync)); + var anonymousGetCypherResults = genericGetCypherResults.MakeGenericMethod(anonType); + var genericResultsTask = anonymousGetCypherResults.Invoke(graphClient, new object[] { cypherQuery }); + await (Task) genericResultsTask; + var genericResults = (IEnumerable) ((dynamic) genericResultsTask).Result; + + var results = genericResults.Cast().ToArray(); + + //Assert + Assert.Equal(1, results.Length); + var ids = (List)anonType.GetProperty(nameof(dummy.Ids)).GetValue(results.First(), null); + ids.Count.Should().Be(3); + ids[0].Should().Be(1); + ids[1].Should().Be(2); + ids[2].Should().Be(3); + } + } + + [Fact] + public async Task ShouldDeserializePathsResultAsSetBased() + { + // Arrange + const string queryText = @"MATCH (start:Node {Id:$p0}),(end:Node {Id: $p1}), p = shortestPath((start)-[*..5]->(end)) RETURN p"; + + var parameters = new Dictionary + { + {"p0", 215}, + {"p1", 219} + }; + + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, CypherResultFormat.Rest, "neo4j"); + + using (var testHarness = new BoltTestHarness()) + { + var recordMock = new Mock(); + recordMock + .Setup(r => r["p"]) + .Returns(new TestPath {End = new TestNode{Id = 1}, Start = new TestNode{Id=2}, Relationships = new List {new TestRelationship()}, Nodes = new List {new TestNode(), new TestNode() } }); + recordMock + .Setup(r => r.Keys) + .Returns(new[] {"p"}); + + var testStatementResult = new TestStatementResult(new[] {"p"}, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); + var results = (await graphClient.ExecuteGetCypherResultsAsync(cypherQuery)).ToArray(); + + //Assert + Assert.IsAssignableFrom>(results); + Assert.Equal(1, results.First().Length); + results.First().Nodes.Count().Should().Be(2); + results.First().Relationships.Count().Should().Be(1); + results.First().End.Id.Should().Be(1); + results.First().Start.Id.Should().Be(2); + } + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests/BoltTestHarness.cs b/Neo4jClient.Tests/BoltTestHarness.cs new file mode 100644 index 000000000..a82e4cef5 --- /dev/null +++ b/Neo4jClient.Tests/BoltTestHarness.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Moq; +using Neo4j.Driver; + +namespace Neo4jClient.Tests +{ + public class BoltTestHarness : IDisposable + { + public BoltTestHarness() + { + var mockResultCursor = new Mock(); + + var mockTransaction = new Mock(); + mockTransaction + .Setup(t => t.RunAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.FromResult(mockResultCursor.Object)); + + var mockSession = new Mock(MockBehavior.Loose); + mockSession + .Setup(s => s.RunAsync("CALL dbms.components()")) + .Returns(Task.FromResult(new BoltGraphClientTests.BoltGraphClientTests.ServerInfo())); + mockSession + .Setup(s => s.RunAsync(It.IsAny(), It.IsAny>())) + .Throws(new Exception("Should never use synchronous method")); + mockSession + .Setup(s => s.WriteTransactionAsync(It.IsAny>())) + .Throws(new Exception("Should never use synchronous method")); + mockSession + .Setup(s => s.ReadTransactionAsync(It.IsAny>())) + .Throws(new Exception("Should never use synchronous method")); + mockSession + .Setup(s => s.BeginTransactionAsync(It.IsAny>())) + .Returns(Task.FromResult(mockTransaction.Object)); + mockSession + .Setup(s => s.BeginTransactionAsync()) + .Returns(Task.FromResult(mockTransaction.Object)); + + var mockDriver = new Mock(MockBehavior.Strict); + mockDriver + .Setup(d => d.AsyncSession()) + .Returns(mockSession.Object); + mockDriver + .Setup(d => d.AsyncSession(It.IsAny>())) + .Returns(mockSession.Object); + + MockSession = mockSession; + MockDriver = mockDriver; + } + + + public Mock MockDriver { get; } + public Mock MockSession { get; } + + public void Dispose() + { + } + + public void SetupCypherRequestResponse(string request, IDictionary cypherQueryQueryParameters, IResultCursor response) + { + MockSession.Setup(s => s.RunAsync(request, It.IsAny>())).Returns(Task.FromResult(response)); + var mockTransaction = new Mock(); + mockTransaction.Setup(s => s.RunAsync(request, It.IsAny>())).Returns(Task.FromResult(response)); + + MockSession.Setup(s => s.WriteTransactionAsync(It.IsAny>>>())) + .Returns>>>(async param => await param(mockTransaction.Object)); + + MockSession.Setup(s => s.ReadTransactionAsync(It.IsAny>>>())) + .Returns>>>(async param => await param(mockTransaction.Object)); + + MockSession + .Setup(s => s.BeginTransactionAsync(It.IsAny>())) + .Returns(Task.FromResult(mockTransaction.Object)); + MockSession + .Setup(s => s.BeginTransactionAsync()) + .Returns(Task.FromResult(mockTransaction.Object)); + } + public async Task CreateAndConnectBoltGraphClient() + { + var bgc = new BoltGraphClient(MockDriver.Object); + await bgc.ConnectAsync(); + MockDriver.Invocations.Clear(); + return bgc; + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests/Bookmarks/BookmarkTests.cs b/Neo4jClient.Tests/Bookmarks/BookmarkTests.cs deleted file mode 100644 index 31fff3076..000000000 --- a/Neo4jClient.Tests/Bookmarks/BookmarkTests.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using Moq; -using Neo4jClient.Test.Fixtures; -using Neo4jClient.Transactions; -using Xunit; - -namespace Neo4jClient.Test.BoltGraphClientTests -{ - public class FullBookmarkTests : IClassFixture - { - public class OperationCompletedEvent : IClassFixture - { - [Fact] - public void TransactionIsCalledWithBookmark() - { - // Arrange - const string bookmark = "Bookmark1"; - var bookmarks = new List {bookmark}; - - using (var testHarness = new BoltTestHarness()) - { - var gc = testHarness.CreateAndConnectBoltGraphClient() as ITransactionalGraphClient; - gc.BeginTransaction(bookmarks); - - //Assert - testHarness.MockDriver.Verify(d => d.Session(It.Is>(s => s.Contains(bookmark))), Times.Once); - } - } - - [Fact] - public void TransactionIsCalledWithBookmarks() - { - // Arrange - var bookmarks = new List {"Bookmark1", "Bookmark2"}; - - using (var testHarness = new BoltTestHarness()) - { - var gc = testHarness.CreateAndConnectBoltGraphClient() as ITransactionalGraphClient; - gc.BeginTransaction(bookmarks); - - //Assert - testHarness.MockDriver.Verify(d => d.Session(It.Is>(s => s.Contains(bookmarks[0]) && s.Contains(bookmarks[1]))), Times.Once); - } - } - } - } -} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/CultureInfoSetupFixture.cs b/Neo4jClient.Tests/CultureInfoSetupFixture.cs similarity index 96% rename from Neo4jClient.Tests.Shared/CultureInfoSetupFixture.cs rename to Neo4jClient.Tests/CultureInfoSetupFixture.cs index 22b63347b..bb9518ddf 100644 --- a/Neo4jClient.Tests.Shared/CultureInfoSetupFixture.cs +++ b/Neo4jClient.Tests/CultureInfoSetupFixture.cs @@ -1,7 +1,7 @@ using System.Globalization; using System.Threading; -namespace Neo4jClient.Test.Fixtures +namespace Neo4jClient.Tests { public class CultureInfoSetupFixture { diff --git a/Neo4jClient.Tests.Shared/Cypher/AggregateTests.cs b/Neo4jClient.Tests/Cypher/AggregateTests.cs similarity index 98% rename from Neo4jClient.Tests.Shared/Cypher/AggregateTests.cs rename to Neo4jClient.Tests/Cypher/AggregateTests.cs index a6831cabe..8ae99bb3d 100644 --- a/Neo4jClient.Tests.Shared/Cypher/AggregateTests.cs +++ b/Neo4jClient.Tests/Cypher/AggregateTests.cs @@ -1,10 +1,9 @@ using System.Linq; +using Neo4jClient.Cypher; using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class AggregateTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryAdvancedTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryAdvancedTests.cs similarity index 71% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryAdvancedTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryAdvancedTests.cs index b01f9f255..6212159e3 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryAdvancedTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryAdvancedTests.cs @@ -1,16 +1,17 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryAdvancedTests : IClassFixture { [Fact] - public void ReturnColumnAlias() + public async Task ReturnColumnAlias() { // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias // START a=node(1) @@ -19,20 +20,20 @@ public void ReturnColumnAlias() var client = Substitute.For(); client - .ExecuteGetCypherResults(Arg.Any()) + .ExecuteGetCypherResultsAsync(Arg.Any()) .Returns(Enumerable.Empty()); var cypher = new CypherFluentQuery(client); var results = cypher - .Start("a", (NodeReference) 1) + .Match("a") .Advanced.Return(new ReturnExpression { ResultFormat = CypherResultFormat.DependsOnEnvironment, ResultMode = CypherResultMode.Projection, Text = "a.Age AS SomethingTotallyDifferent" }); - Assert.Equal("START a=node(1)\r\nRETURN a.Age AS SomethingTotallyDifferent", results.Query.DebugQueryText); - Assert.IsAssignableFrom>(results.Results); + Assert.Equal($"MATCH a{Environment.NewLine}RETURN a.Age AS SomethingTotallyDifferent", results.Query.DebugQueryText); + Assert.IsAssignableFrom>(await results.ResultsAsync); } public class ReturnPropertyQueryResult diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryCallTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryCallTests.cs similarity index 95% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryCallTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryCallTests.cs index cd4138f17..0f9327625 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryCallTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryCallTests.cs @@ -1,10 +1,9 @@ using System; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryCallTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryConstraintTest.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryConstraintTest.cs similarity index 91% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryConstraintTest.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryConstraintTest.cs index 3b5adbeb7..a82777605 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryConstraintTest.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryConstraintTest.cs @@ -1,10 +1,10 @@ -using Xunit; -using NSubstitute; +using System; +using System.Threading.Tasks; using Neo4jClient.Cypher; -using System; -using Neo4jClient.Test.Fixtures; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryConstraintTest : IClassFixture diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryCreateUniqueTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryCreateUniqueTests.cs similarity index 71% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryCreateUniqueTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryCreateUniqueTests.cs index 4cf01a2c5..416f9270b 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryCreateUniqueTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryCreateUniqueTests.cs @@ -1,10 +1,9 @@ using System; +using Neo4jClient.Cypher; using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryCreateUniqueTests : IClassFixture { @@ -19,13 +18,12 @@ public void CreateNodeWithValuesViaCreateUnique() var client = Substitute.For(); client.ServerVersion.Returns(new Version(1, 8)); var query = new CypherFluentQuery(client) - .Start("root", (NodeReference)2) + .Match("root") .CreateUnique("root-[:X]-(leaf {name:'D'} )") .Return("leaf") .Query; - Assert.Equal("START root=node($p0)\r\nCREATE UNIQUE root-[:X]-(leaf {name:'D'} )\r\nRETURN leaf", query.QueryText); - Assert.Equal(2l, query.QueryParameters["p0"]); + Assert.Equal($"MATCH root{Environment.NewLine}CREATE UNIQUE root-[:X]-(leaf {{name:'D'}} ){Environment.NewLine}RETURN leaf", query.QueryText); } [Fact] @@ -39,14 +37,12 @@ public void CreateNodeWithValuesViaCreateUniqueAfterMatch() 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.Equal("START root=node($p0)\r\nMATCH root-[:X]-foo\r\nCREATE UNIQUE foo-[:Y]-(leaf {name:'D'} )\r\nRETURN leaf", query.QueryText); - Assert.Equal(2l, query.QueryParameters["p0"]); + Assert.Equal($"MATCH root-[:X]-foo{Environment.NewLine}CREATE UNIQUE foo-[:Y]-(leaf {{name:'D'}} ){Environment.NewLine}RETURN leaf", query.QueryText); } } } diff --git a/Neo4jClient.Tests/Cypher/CypherFluentQueryCustomHeaderTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryCustomHeaderTests.cs index a1fe09301..1fe222522 100644 --- a/Neo4jClient.Tests/Cypher/CypherFluentQueryCustomHeaderTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryCustomHeaderTests.cs @@ -1,12 +1,10 @@ using System.Collections.Specialized; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { - public class CypherFluentQueryCustomHeaderTests : IClassFixture { [Fact] @@ -34,7 +32,7 @@ public void SetsCustomHeader_WhenUsingAReturnTypeQuery() const string headerName = "HeaderName"; const string headerValue = "TestHeaderValue"; var client = Substitute.For(); - var customHeaders = new NameValueCollection { { headerName, headerValue } }; + var customHeaders = new NameValueCollection {{headerName, headerValue}}; var query = new CypherFluentQuery(client) .CustomHeaders(customHeaders) @@ -50,7 +48,7 @@ public void SetsCustomHeader_WhenUsingANonReturnTypeQuery() { const string headerName = "HeaderName"; const string headerValue = "TestHeaderValue"; - var customHeaders = new NameValueCollection { { headerName, headerValue } }; + var customHeaders = new NameValueCollection {{headerName, headerValue}}; var client = Substitute.For(); var query = new CypherFluentQuery(client) diff --git a/Neo4jClient.Tests/Cypher/CypherFluentQueryDatabaseAdministrationTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryDatabaseAdministrationTests.cs new file mode 100644 index 000000000..aee5fb1ba --- /dev/null +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryDatabaseAdministrationTests.cs @@ -0,0 +1,394 @@ +using System; +using FluentAssertions; +using Neo4jClient.Cypher; +using NSubstitute; +using Xunit; + +namespace Neo4jClient.Tests.Cypher +{ + public class CypherFluentQueryDatabaseAdministrationTests + { + private static IRawGraphClient MockClient + { + get + { + var client = Substitute.For(); + client.CypherCapabilities.Returns(new CypherCapabilities { SupportsMultipleTenancy = true, SupportsShow = true}); + return client; + } + } + + //https://neo4j.com/docs/cypher-manual/current/administration/databases/#administration-databases-create-database + public class CreateDatabaseMethod : IClassFixture + { + [Fact] + public void GeneratesTheCorrectCypher() + { + var query = new CypherFluentQuery(MockClient) + .CreateDatabase("foo") + .Query; + + query.QueryText.Should().Be($"CREATE DATABASE foo"); + } + + [Fact] + public void GeneratesTheCorrectCypher_IfNotExists() + { + var query = new CypherFluentQuery(MockClient) + .CreateDatabase("foo", true) + .Query; + + query.QueryText.Should().Be($"CREATE DATABASE foo IF NOT EXISTS"); + } + + [Fact] + public void ThrowsInvalidOperationException_IfNotOnASupportedVersion() + { + var client = Substitute.For(); + client.CypherCapabilities.Returns(new CypherCapabilities {SupportsMultipleTenancy = false}); + + var ex = Assert.Throws(() => new CypherFluentQuery(client).CreateDatabase("foo", true)); + ex.Should().NotBeNull(); + ex.Message.Should().Be("DATABASE commands are not supported in Neo4j versions older than 4.0"); + } + + [Theory] + [InlineData("FOO")] + [InlineData("foo")] + [InlineData("Foo")] + [InlineData("fOO")] + [InlineData("FoO")] + public void UsesLowerCaseForDatabaseName(string name) + { + var query = new CypherFluentQuery(MockClient) + .CreateDatabase(name) + .Query; + + query.QueryText.Should().Be($"CREATE DATABASE foo"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(null)] + public void ThrowsArgumentException_IfDatabaseNameIsInvalid(string databaseName) + { + var ex = Assert.Throws(() => new CypherFluentQuery(MockClient).CreateDatabase(databaseName)); + ex.Should().NotBeNull(); + } + } + + //https://neo4j.com/docs/cypher-manual/current/administration/databases/#administration-databases-create-database + public class CreateOrReplaceDatabaseMethod : IClassFixture + { + [Fact] + public void GeneratesTheCorrectCypher() + { + var query = new CypherFluentQuery(MockClient) + .CreateOrReplaceDatabase("foo") + .Query; + + query.QueryText.Should().Be($"CREATE OR REPLACE DATABASE foo"); + } + + [Fact] + public void ThrowsInvalidOperationException_IfNotOnASupportedVersion() + { + var client = Substitute.For(); + client.CypherCapabilities.Returns(new CypherCapabilities { SupportsMultipleTenancy = false }); + + var ex = Assert.Throws(() => new CypherFluentQuery(client).CreateOrReplaceDatabase("foo")); + ex.Should().NotBeNull(); + ex.Message.Should().Be("DATABASE commands are not supported in Neo4j versions older than 4.0"); + } + + [Theory] + [InlineData("FOO")] + [InlineData("foo")] + [InlineData("Foo")] + [InlineData("fOO")] + [InlineData("FoO")] + public void UsesLowerCaseForDatabaseName(string name) + { + var query = new CypherFluentQuery(MockClient) + .CreateOrReplaceDatabase(name) + .Query; + + query.QueryText.Should().Be($"CREATE OR REPLACE DATABASE foo"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(null)] + public void ThrowsArgumentException_IfDatabaseNameIsInvalid(string databaseName) + { + var ex = Assert.Throws(() => new CypherFluentQuery(MockClient).CreateOrReplaceDatabase(databaseName)); + ex.Should().NotBeNull(); + } + + } + + //https://neo4j.com/docs/cypher-manual/current/administration/databases/#administration-databases-drop-database + public class DropDatabaseMethod : IClassFixture + { + [Fact] + public void GeneratesTheCorrectCypher() + { + var query = new CypherFluentQuery(MockClient) + .DropDatabase("foo") + .Query; + + query.QueryText.Should().Be($"DROP DATABASE foo"); + } + + [Fact] + public void GeneratesTheCorrectCypher_IfNotExists() + { + var query = new CypherFluentQuery(MockClient) + .DropDatabase("foo", true) + .Query; + + query.QueryText.Should().Be($"DROP DATABASE foo DUMP DATA"); + } + + [Fact] + public void ThrowsInvalidOperationException_IfNotOnASupportedVersion() + { + var client = Substitute.For(); + client.CypherCapabilities.Returns(new CypherCapabilities {SupportsMultipleTenancy = false}); + + var ex = Assert.Throws(() => new CypherFluentQuery(client).DropDatabase("foo")); + ex.Should().NotBeNull(); + ex.Message.Should().Be("DATABASE commands are not supported in Neo4j versions older than 4.0"); + } + + [Theory] + [InlineData("FOO")] + [InlineData("foo")] + [InlineData("Foo")] + [InlineData("fOO")] + [InlineData("FoO")] + public void UsesLowerCaseForDatabaseName(string name) + { + var query = new CypherFluentQuery(MockClient) + .DropDatabase(name) + .Query; + + query.QueryText.Should().Be($"DROP DATABASE foo"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(null)] + public void ThrowsArgumentException_IfDatabaseNameIsInvalid(string databaseName) + { + var ex = Assert.Throws(() => new CypherFluentQuery(MockClient).DropDatabase(databaseName)); + ex.Should().NotBeNull(); + } + } + + //https://neo4j.com/docs/cypher-manual/current/administration/databases/#administration-databases-drop-database + public class DropDatabaseIfExistsMethod : IClassFixture + { + [Fact] + public void GeneratesTheCorrectCypher() + { + var query = new CypherFluentQuery(MockClient) + .DropDatabaseIfExists("foo") + .Query; + + query.QueryText.Should().Be($"DROP DATABASE foo IF EXISTS"); + } + + [Fact] + public void GeneratesTheCorrectCypher_IfNotExists() + { + var query = new CypherFluentQuery(MockClient) + .DropDatabaseIfExists("foo", true) + .Query; + + query.QueryText.Should().Be($"DROP DATABASE foo IF EXISTS DUMP DATA"); + } + + [Fact] + public void ThrowsInvalidOperationException_IfNotOnASupportedVersion() + { + var client = Substitute.For(); + client.CypherCapabilities.Returns(new CypherCapabilities { SupportsMultipleTenancy = false }); + + var ex = Assert.Throws(() => new CypherFluentQuery(client).DropDatabaseIfExists("foo")); + ex.Should().NotBeNull(); + ex.Message.Should().Be("DATABASE commands are not supported in Neo4j versions older than 4.0"); + } + + [Theory] + [InlineData("FOO")] + [InlineData("foo")] + [InlineData("Foo")] + [InlineData("fOO")] + [InlineData("FoO")] + public void UsesLowerCaseForDatabaseName(string name) + { + var query = new CypherFluentQuery(MockClient) + .DropDatabaseIfExists(name) + .Query; + + query.QueryText.Should().Be($"DROP DATABASE foo IF EXISTS"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(null)] + public void ThrowsArgumentException_IfDatabaseNameIsInvalid(string databaseName) + { + var ex = Assert.Throws(() => new CypherFluentQuery(MockClient).DropDatabaseIfExists(databaseName)); + ex.Should().NotBeNull(); + } + } + + //https://neo4j.com/docs/cypher-manual/current/administration/databases/#administration-databases-start-database + public class StartDatabaseMethod : IClassFixture + { + [Fact] + public void GeneratesTheCorrectCypher() + { + var query = new CypherFluentQuery(MockClient) + .StartDatabase("foo") + .Query; + + query.QueryText.Should().Be($"START DATABASE foo"); + } + + [Fact] + public void ThrowsInvalidOperationException_IfNotOnASupportedVersion() + { + var client = Substitute.For(); + client.CypherCapabilities.Returns(new CypherCapabilities { SupportsMultipleTenancy = false }); + + var ex = Assert.Throws(() => new CypherFluentQuery(client).StartDatabase("foo")); + ex.Should().NotBeNull(); + ex.Message.Should().Be("DATABASE commands are not supported in Neo4j versions older than 4.0"); + } + + [Theory] + [InlineData("FOO")] + [InlineData("foo")] + [InlineData("Foo")] + [InlineData("fOO")] + [InlineData("FoO")] + public void UsesLowerCaseForDatabaseName(string name) + { + var query = new CypherFluentQuery(MockClient) + .StartDatabase(name) + .Query; + + query.QueryText.Should().Be($"START DATABASE foo"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(null)] + public void ThrowsArgumentException_IfDatabaseNameIsInvalid(string databaseName) + { + var ex = Assert.Throws(() => new CypherFluentQuery(MockClient).StartDatabase(databaseName)); + ex.Should().NotBeNull(); + } + } + + //https://neo4j.com/docs/cypher-manual/current/administration/databases/#administration-databases-stop-database + public class StopDatabaseMethod : IClassFixture + { + [Fact] + public void GeneratesTheCorrectCypher() + { + var query = new CypherFluentQuery(MockClient) + .StopDatabase("foo") + .Query; + + query.QueryText.Should().Be($"STOP DATABASE foo"); + } + + [Fact] + public void ThrowsInvalidOperationException_IfNotOnASupportedVersion() + { + var client = Substitute.For(); + client.CypherCapabilities.Returns(new CypherCapabilities { SupportsMultipleTenancy = false }); + + var ex = Assert.Throws(() => new CypherFluentQuery(client).StopDatabase("foo")); + ex.Should().NotBeNull(); + ex.Message.Should().Be("DATABASE commands are not supported in Neo4j versions older than 4.0"); + } + + [Theory] + [InlineData("FOO")] + [InlineData("foo")] + [InlineData("Foo")] + [InlineData("fOO")] + [InlineData("FoO")] + public void UsesLowerCaseForDatabaseName(string name) + { + var query = new CypherFluentQuery(MockClient) + .StopDatabase(name) + .Query; + + query.QueryText.Should().Be($"STOP DATABASE foo"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(null)] + public void ThrowsArgumentException_IfDatabaseNameIsInvalid(string databaseName) + { + var ex = Assert.Throws(() => new CypherFluentQuery(MockClient).StopDatabase(databaseName)); + ex.Should().NotBeNull(); + } + } + + public class ShowMethod : IClassFixture + { + [Fact] + public void GeneratesTheCorrectCypher() + { + var query = new CypherFluentQuery(MockClient) + .Show("DATABASES") + .Query; + + query.QueryText.Should().Be($"SHOW DATABASES"); + } + + [Fact] + public void ThrowsInvalidOperationException_IfNotOnASupportedVersion() + { + var client = Substitute.For(); + client.CypherCapabilities.Returns(new CypherCapabilities { SupportsMultipleTenancy = false }); + + var ex = Assert.Throws(() => new CypherFluentQuery(client).Show("DATABASES")); + ex.Should().NotBeNull(); + ex.Message.Should().Be("SHOW commands are not supported in Neo4j versions older than 4.0"); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(" ")] + [InlineData(null)] + public void ThrowsArgumentException_IfDatabaseNameIsInvalid(string command) + { + var ex = Assert.Throws(() => new CypherFluentQuery(MockClient).Show(command)); + ex.Should().NotBeNull(); + } + + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests/Cypher/CypherFluentQueryDatabaseTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryDatabaseTests.cs new file mode 100644 index 000000000..5645a4584 --- /dev/null +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryDatabaseTests.cs @@ -0,0 +1,79 @@ +using System.Threading.Tasks; +using Xunit; + +namespace Neo4jClient.Tests.Cypher +{ + public class CypherFluentQueryDatabaseTests : IClassFixture + { + internal static MockResponse EmptyOKResponse = MockResponse.Json(200, @"{'results':[], 'errors':[] }"); + internal static string EmptyStatements = "{'statements': []}"; + + [Theory] + [InlineData("FOO")] + [InlineData("foo")] + [InlineData("Foo")] + [InlineData("fOO")] + [InlineData("FoO")] + public async Task ShouldAlwaysBeLowercase(string database) + { + var queryRequest = MockRequest.PostJson($"/db/{database.ToLowerInvariant()}/tx/commit", + "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + {queryRequest, EmptyOKResponse} + }) + { + var client = await testHarness.CreateAndConnectGraphClient(RestTestHarness.Neo4jVersion.Neo40); + + // dummy query to generate request + await client.Cypher + .WithDatabase(database) + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + } + } + + [Fact] + public async Task ShouldBeNeo4jIfNotSet() + { + var queryRequest = MockRequest.PostJson("/db/neo4j/tx/commit", + "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + {queryRequest, EmptyOKResponse} + }) + { + var client = await testHarness.CreateAndConnectGraphClient(RestTestHarness.Neo4jVersion.Neo40); + + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + } + } + + [Fact] + public async Task WithDatabase_ShouldOverrideDefaultDatabase() + { + var queryRequest = MockRequest.PostJson("/db/neo4jclient/tx/commit", + "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + {queryRequest, EmptyOKResponse} + }) + { + var client = await testHarness.CreateAndConnectGraphClient(RestTestHarness.Neo4jVersion.Neo40); + client.DefaultDatabase = "neo4j"; + + // dummy query to generate request + await client.Cypher + .WithDatabase("neo4jclient") + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + } + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryDeleteTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryDeleteTests.cs similarity index 62% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryDeleteTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryDeleteTests.cs index f75c49d13..b2099e10f 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryDeleteTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryDeleteTests.cs @@ -1,10 +1,10 @@ using System; +using System.Threading.Tasks; +using Neo4jClient.Cypher; using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryDeleteTests : IClassFixture @@ -19,13 +19,12 @@ public void DeleteMatchedIdentifier() var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Match("n-[r]-()") .Delete("n, r") .Query; - Assert.Equal("START n=node($p0)\r\nMATCH n-[r]-()\r\nDELETE n, r", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "MATCH n-[r]-()" + Environment.NewLine + "DELETE n, r", query.QueryText); } [Fact] @@ -38,13 +37,12 @@ public void DeleteProperty() var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("andres", (NodeReference)3) + .Match("andres") .Delete("andres.age") .Return>("andres") .Query; - Assert.Equal("START andres=node($p0)\r\nDELETE andres.age\r\nRETURN andres", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH andres" + Environment.NewLine + "DELETE andres.age" + Environment.NewLine + "RETURN andres", query.QueryText); } [Fact] @@ -52,34 +50,32 @@ public void DeleteIdentifier() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Delete("n") .Query; - Assert.Equal("START n=node($p0)\r\nDELETE n", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "DELETE n", query.QueryText); } [Fact] - public void DeleteWithoutReturn() + public async Task DeleteWithoutReturn() { // Arrange var client = Substitute.For(); CypherQuery executedQuery = null; client - .When(c => c.ExecuteCypher(Arg.Any())) + .When(c => c.ExecuteCypherAsync(Arg.Any())) .Do(ci => { executedQuery = ci.Arg(); }); // Act - new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + await new CypherFluentQuery(client) + .Match("n") .Delete("n") - .ExecuteWithoutResults(); + .ExecuteWithoutResultsAsync(); // Assert Assert.NotNull(executedQuery); - Assert.Equal("START n=node($p0)\r\nDELETE n", executedQuery.QueryText); - Assert.Equal(3L, executedQuery.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "DELETE n", executedQuery.QueryText); } [Fact] @@ -87,14 +83,13 @@ public void AllowDeleteClauseAfterWhere() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Where("(...)") .Delete("n") .Query; // Assert - Assert.Equal("START n=node($p0)\r\nWHERE (...)\r\nDELETE n", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "WHERE (...)" + Environment.NewLine + "DELETE n", query.QueryText); } } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryDetachDeleteTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryDetachDeleteTests.cs similarity index 81% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryDetachDeleteTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryDetachDeleteTests.cs index 45258a59e..42c25636d 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryDetachDeleteTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryDetachDeleteTests.cs @@ -1,10 +1,10 @@ using System; +using System.Threading.Tasks; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryDetachDeleteTests : IClassFixture @@ -28,7 +28,7 @@ public void DeleteMatchedIdentifier() .DetachDelete("n, r") .Query; - Assert.Equal("MATCH n-[r]-()\r\nDETACH DELETE n, r", query.QueryText); + Assert.Equal("MATCH n-[r]-()" + Environment.NewLine + "DETACH DELETE n, r", query.QueryText); } [Fact] @@ -50,23 +50,23 @@ public void DeleteIdentifier() .DetachDelete("n") .Query; - Assert.Equal("MATCH n\r\nDETACH DELETE n", query.QueryText); + Assert.Equal("MATCH n" + Environment.NewLine + "DETACH DELETE n", query.QueryText); } [Fact] - public void DeleteWithoutReturn() + public async Task DeleteWithoutReturn() { // Arrange var client = GraphClient_230; CypherQuery executedQuery = null; client - .When(c => c.ExecuteCypher(Arg.Any())) + .When(c => c.ExecuteCypherAsync(Arg.Any())) .Do(ci => { executedQuery = ci.Arg(); }); // Act - new CypherFluentQuery(client) + await new CypherFluentQuery(client) .DetachDelete("n") - .ExecuteWithoutResults(); + .ExecuteWithoutResultsAsync(); // Assert Assert.NotNull(executedQuery); @@ -83,7 +83,7 @@ public void AllowDeleteClauseAfterWhere() .Query; // Assert - Assert.Equal("WHERE (...)\r\nDETACH DELETE n", query.QueryText); + Assert.Equal("WHERE (...)" + Environment.NewLine + "DETACH DELETE n", query.QueryText); } [Fact] diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryDropTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryDropTests.cs similarity index 65% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryDropTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryDropTests.cs index a8a74a934..4acdb6a14 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryDropTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryDropTests.cs @@ -1,10 +1,10 @@ using System; +using System.Threading.Tasks; +using Neo4jClient.Cypher; using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryDropTests : IClassFixture @@ -28,19 +28,18 @@ public void DropIndex() public void DeleteProperty() { // http://docs.neo4j.org/chunked/1.8.M06/query-delete.html#delete-remove-a-property - //START andres = node(3) + //MATCH andres = node(3) //DELETE andres.age //RETURN andres var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("andres", (NodeReference)3) + .Match("andres") .Delete("andres.age") .Return>("andres") .Query; - Assert.Equal("START andres=node($p0)\r\nDELETE andres.age\r\nRETURN andres", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH andres" + Environment.NewLine + "DELETE andres.age" + Environment.NewLine + "RETURN andres", query.QueryText); } [Fact] @@ -48,34 +47,32 @@ public void DeleteIdentifier() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Delete("n") .Query; - Assert.Equal("START n=node($p0)\r\nDELETE n", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "DELETE n", query.QueryText); } [Fact] - public void DeleteWithoutReturn() + public async Task DeleteWithoutReturn() { // Arrange var client = Substitute.For(); CypherQuery executedQuery = null; client - .When(c => c.ExecuteCypher(Arg.Any())) + .When(c => c.ExecuteCypherAsync(Arg.Any())) .Do(ci => { executedQuery = ci.Arg(); }); // Act - new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + await new CypherFluentQuery(client) + .Match("n") .Delete("n") - .ExecuteWithoutResults(); + .ExecuteWithoutResultsAsync(); // Assert Assert.NotNull(executedQuery); - Assert.Equal("START n=node($p0)\r\nDELETE n", executedQuery.QueryText); - Assert.Equal(3L, executedQuery.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "DELETE n", executedQuery.QueryText); } [Fact] @@ -83,14 +80,13 @@ public void AllowDeleteClauseAfterWhere() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Where("(...)") .Delete("n") .Query; // Assert - Assert.Equal("START n=node($p0)\r\nWHERE (...)\r\nDELETE n", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "WHERE (...)" + Environment.NewLine + "DELETE n", query.QueryText); } } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryForEachTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryForEachTests.cs similarity index 85% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryForEachTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryForEachTests.cs index b91398c83..75b5c7628 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryForEachTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryForEachTests.cs @@ -1,9 +1,8 @@ -using NSubstitute; +using Neo4jClient.Cypher; +using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryForEachTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryLimitTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryLimitTests.cs similarity index 71% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryLimitTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryLimitTests.cs index 921d65b7a..7ffdeb297 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryLimitTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryLimitTests.cs @@ -1,11 +1,10 @@ -using Xunit; -using NSubstitute; -using Neo4jClient.Cypher; -using System; +using System; using System.Linq; -using Neo4jClient.Test.Fixtures; +using Neo4jClient.Cypher; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryLimitTests : IClassFixture @@ -16,16 +15,15 @@ public void LimitClause() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Return>("n") .Limit(2) .Query; // Assert - Assert.Equal("START n=node($p0)\r\nRETURN n\r\nLIMIT $p1", query.QueryText); - Assert.Equal(2, query.QueryParameters.Count); - Assert.Equal(3L, query.QueryParameters["p0"]); - Assert.Equal(2, query.QueryParameters["p1"]); + Assert.Equal($"MATCH n{Environment.NewLine}RETURN n{Environment.NewLine}LIMIT $p0", query.QueryText); + Assert.Equal(1, query.QueryParameters.Count); + Assert.Equal(2, query.QueryParameters["p0"]); } [Fact] @@ -35,7 +33,7 @@ public void LimitClauseAfterReturnClauseIsTyped() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) + .Match("n") .Return>("n") .Limit(2); @@ -49,15 +47,14 @@ public void NullLimitDoesNotWriteClause() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Return>("n") .Limit(null) .Query; // Assert - Assert.Equal("START n=node($p0)\r\nRETURN n", query.QueryText); - Assert.Equal(1, query.QueryParameters.Count); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal($"MATCH n{Environment.NewLine}RETURN n", query.QueryText); + Assert.Equal(0, query.QueryParameters.Count); } [Fact] @@ -67,17 +64,16 @@ public void LimitClauseAfterWithClause() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) + .Match("n") .With("foo") .Limit(2) .Query; // Assert - Assert.Equal("START n=node($p0)\r\nWITH foo\r\nLIMIT $p1", query.QueryText); - Assert.Equal(2, query.QueryParameters.Count); - Assert.Equal(3L, query.QueryParameters["p0"]); - Assert.Equal(2, query.QueryParameters["p1"]); + Assert.Equal("MATCH n" + Environment.NewLine + "WITH foo" + Environment.NewLine + "LIMIT $p0", query.QueryText); + Assert.Equal(1, query.QueryParameters.Count); + Assert.Equal(2, query.QueryParameters["p0"]); } [Fact] @@ -87,7 +83,7 @@ public void LimitClauseAfterWithClauseIsUntyped() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) + .Match("n") .With("foo") .Limit(2); @@ -98,7 +94,7 @@ public void LimitClauseAfterWithClauseIsUntyped() .Where(i => i.IsGenericType) .Select(i => i.GetGenericTypeDefinition()) .Any(t => t == typeof(ICypherFluentQuery<>)); - Assert.False(implementsTypedQueryInterface, "Implementes ICypherFluentQuery<>"); + Assert.False(implementsTypedQueryInterface, "Implements ICypherFluentQuery<>"); } } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryLoadCsvTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryLoadCsvTests.cs similarity index 93% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryLoadCsvTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryLoadCsvTests.cs index 1603ff75e..4ec0876f8 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryLoadCsvTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryLoadCsvTests.cs @@ -1,11 +1,9 @@ using System; -using System.Security.Cryptography.X509Certificates; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { /// /// Tests for the LOAD CSV command @@ -34,7 +32,7 @@ public void TestLoadCsvAfterWithTResultVariant() .LoadCsv(new Uri("file://localhost/c:/foo/bar.csv"), "row") .Query; - Assert.Equal("WITH n\r\nLOAD CSV FROM 'file://localhost/c:/foo/bar.csv' AS row", query.QueryText); + Assert.Equal("WITH n" + Environment.NewLine + "LOAD CSV FROM 'file://localhost/c:/foo/bar.csv' AS row", query.QueryText); } [Fact] diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryMatchTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryMatchTests.cs similarity index 80% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryMatchTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryMatchTests.cs index c9213662a..abe767b0a 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryMatchTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryMatchTests.cs @@ -1,31 +1,29 @@ -using Xunit; -using NSubstitute; -using Neo4jClient.Cypher; +using System; using System.Linq; -using Neo4jClient.Test.Fixtures; +using Neo4jClient.Cypher; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { - public class CypherFluentQueryMatchTests : IClassFixture { [Fact] public void MatchRelatedNodes() { // http://docs.neo4j.org/chunked/1.6/query-match.html#match-related-nodes - // START n=node(3) + // MATCH n // MATCH (n)--(x) // RETURN x var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Match("(n)--(x)") .Return("x") .Query; - Assert.Equal("START n=node($p0)\r\nMATCH (n)--(x)\r\nRETURN x", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "MATCH (n)--(x)" + Environment.NewLine + "RETURN x", query.QueryText); } [Fact] @@ -59,7 +57,7 @@ public void MultipleMatchClauses() .OptionalMatch("(x)--(a)") .Query; - Assert.Equal("MATCH (n)\r\nOPTIONAL MATCH (n)--(x)\r\nOPTIONAL MATCH (x)--(a)", query.QueryText); + Assert.Equal("MATCH (n)" + Environment.NewLine + "OPTIONAL MATCH (n)--(x)" + Environment.NewLine + "OPTIONAL MATCH (x)--(a)", query.QueryText); Assert.Equal(0, query.QueryParameters.Count()); } @@ -84,7 +82,7 @@ public void MultipleMatchClausesWithPairedWhereClauses() .Where((FooBarBaz a) => a.Baz == "ghi") .Query; - const string expected = "MATCH (n)\r\nWHERE (n.Foo = $p0)\r\nOPTIONAL MATCH (n)--(x)\r\nWHERE (x.Bar = $p1)\r\nOPTIONAL MATCH (x)--(a)\r\nWHERE (a.Baz = $p2)"; + string expected = "MATCH (n)" + Environment.NewLine + "WHERE (n.Foo = $p0)" + Environment.NewLine + "OPTIONAL MATCH (n)--(x)" + Environment.NewLine + "WHERE (x.Bar = $p1)" + Environment.NewLine + "OPTIONAL MATCH (x)--(a)" + Environment.NewLine + "WHERE (a.Baz = $p2)"; Assert.Equal(expected, query.QueryText); Assert.Equal(3, query.QueryParameters.Count()); diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryMaxExecutionTimeTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryMaxExecutionTimeTests.cs similarity index 89% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryMaxExecutionTimeTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryMaxExecutionTimeTests.cs index e2c3ff822..8f2061efe 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryMaxExecutionTimeTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryMaxExecutionTimeTests.cs @@ -1,9 +1,8 @@ -using NSubstitute; +using Neo4jClient.Cypher; +using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryMaxExecutionTimeTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryMergeTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryMergeTests.cs similarity index 72% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryMergeTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryMergeTests.cs index e82fb6d5f..7e48b2512 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryMergeTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryMergeTests.cs @@ -1,10 +1,9 @@ -using Xunit; -using NSubstitute; +using System; using Neo4jClient.Cypher; -using System; -using Neo4jClient.Test.Fixtures; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryMergeTests : IClassFixture @@ -34,7 +33,7 @@ public void MergeOnCreate() .Query; // Assert - Assert.Equal("MERGE (robert:Person)\r\nON CREATE\r\nSET robert.Created = timestamp()", query.QueryText); + Assert.Equal("MERGE (robert:Person)" + Environment.NewLine + "ON CREATE" + Environment.NewLine + "SET robert.Created = timestamp()", query.QueryText); } [Fact] @@ -49,7 +48,7 @@ public void MergeOnMatch() .Query; // Assert - Assert.Equal("MERGE (robert:Person)\r\nON MATCH\r\nSET robert.LastSeen = timestamp()", query.QueryText); + Assert.Equal("MERGE (robert:Person)" + Environment.NewLine + "ON MATCH" + Environment.NewLine + "SET robert.LastSeen = timestamp()", query.QueryText); } [Fact] @@ -66,7 +65,7 @@ public void MergeOnCreateOnMatch() .Query; // Assert - Assert.Equal("MERGE (robert:Person)\r\nON CREATE\r\nSET robert.Created = timestamp()\r\nON MATCH\r\nSET robert.LastSeen = timestamp()", query.QueryText); + Assert.Equal("MERGE (robert:Person)" + Environment.NewLine + "ON CREATE" + Environment.NewLine + "SET robert.Created = timestamp()" + Environment.NewLine + "ON MATCH" + Environment.NewLine + "SET robert.LastSeen = timestamp()", query.QueryText); } } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryParserVersionTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryParserVersionTests.cs similarity index 66% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryParserVersionTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryParserVersionTests.cs index 606d71aa1..7b23e9fd6 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryParserVersionTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryParserVersionTests.cs @@ -1,10 +1,9 @@ using System; +using Neo4jClient.Cypher; using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryParserVersionTests : IClassFixture { @@ -14,14 +13,11 @@ public void SetsVersionToFreeTextGiven() var client = Substitute.For(); var query = new CypherFluentQuery(client) .ParserVersion("2.1.experimental") - .Start(new - { - n = All.Nodes, - }) + .Match("n") .Return("n") .Query; - Assert.Equal("CYPHER 2.1.experimental\r\nSTART n=node(*)\r\nRETURN n", query.QueryText); + Assert.Equal("CYPHER 2.1.experimental" + Environment.NewLine + "MATCH n" + Environment.NewLine + "RETURN n", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } @@ -31,14 +27,11 @@ public void SetsVersion_WhenUsingVersionOverload() var client = Substitute.For(); var query = new CypherFluentQuery(client) .ParserVersion(new Version(1, 9)) - .Start(new - { - n = All.Nodes, - }) + .Match("n") .Return("n") .Query; - Assert.Equal("CYPHER 1.9\r\nSTART n=node(*)\r\nRETURN n", query.QueryText); + Assert.Equal("CYPHER 1.9" + Environment.NewLine + "MATCH n" + Environment.NewLine + "RETURN n", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } @@ -48,14 +41,11 @@ public void SetsVersion_WhenUsingIntOverload() var client = Substitute.For(); var query = new CypherFluentQuery(client) .ParserVersion(1, 9) - .Start(new - { - n = All.Nodes, - }) + .Match("n") .Return("n") .Query; - Assert.Equal("CYPHER 1.9\r\nSTART n=node(*)\r\nRETURN n", query.QueryText); + Assert.Equal("CYPHER 1.9" + Environment.NewLine + "MATCH n" + Environment.NewLine + "RETURN n", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } @@ -65,14 +55,11 @@ public void UsesLegacy_WhenVersionRequestedIsLessThan1_9() var client = Substitute.For(); var query = new CypherFluentQuery(client) .ParserVersion(1, 8) - .Start(new - { - n = All.Nodes, - }) + .Match("n") .Return("n") .Query; - Assert.Equal("CYPHER LEGACY\r\nSTART n=node(*)\r\nRETURN n", query.QueryText); + Assert.Equal("CYPHER LEGACY" + Environment.NewLine + "MATCH n" + Environment.NewLine + "RETURN n", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryPlannerTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryPlannerTests.cs similarity index 96% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryPlannerTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryPlannerTests.cs index 280226672..0b92c2a9d 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryPlannerTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryPlannerTests.cs @@ -1,10 +1,9 @@ using System; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { /// /// Tests for the UNWIND operator diff --git a/Neo4jClient.Tests/Cypher/CypherFluentQueryQueryStatsTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryQueryStatsTests.cs new file mode 100644 index 000000000..f2e03e92f --- /dev/null +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryQueryStatsTests.cs @@ -0,0 +1,22 @@ +using Neo4jClient.Cypher; +using NSubstitute; +using Xunit; + +namespace Neo4jClient.Tests.Cypher +{ + public class CypherFluentQueryQueryStatsTests : IClassFixture + { + [Fact] + public void SetsIncludeQueryStatsToTrueWhenUsingQueryStatsProperty() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .WithQueryStats + .Create("(n:Foo)") + .Query; + + Assert.Equal("CREATE (n:Foo)", query.QueryText); + Assert.True(query.IncludeQueryStats); + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryReadWriteTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryReadWriteTests.cs similarity index 95% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryReadWriteTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryReadWriteTests.cs index 1781fd766..1f980f2fc 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryReadWriteTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryReadWriteTests.cs @@ -1,10 +1,10 @@ using System; +using FluentAssertions; +using Neo4jClient.Cypher; using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryReadWriteTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryRemoveTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryRemoveTests.cs similarity index 62% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryRemoveTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryRemoveTests.cs index b4be07dbf..7399cb723 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryRemoveTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryRemoveTests.cs @@ -1,10 +1,9 @@ -using Xunit; -using NSubstitute; +using System; using Neo4jClient.Cypher; -using System; -using Neo4jClient.Test.Fixtures; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryRemoveTests : IClassFixture @@ -15,14 +14,13 @@ public void RemoveProperty() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Remove("n.age") .Return>("n") .Query; // Assert - Assert.Equal("START n=node($p0)\r\nREMOVE n.age\r\nRETURN n", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "REMOVE n.age" + Environment.NewLine + "RETURN n", query.QueryText); } [Fact] @@ -31,14 +29,13 @@ public void RemoveLabel() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Remove("n:Person") .Return>("n") .Query; // Assert - Assert.Equal("START n=node($p0)\r\nREMOVE n:Person\r\nRETURN n", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "REMOVE n:Person" + Environment.NewLine + "RETURN n", query.QueryText); } } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryResultsTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryResultsTests.cs similarity index 69% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryResultsTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryResultsTests.cs index 490f8ece2..d81ec1716 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryResultsTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryResultsTests.cs @@ -1,17 +1,17 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Neo4jClient.Cypher; using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryResultsTests : IClassFixture { [Fact] - public void ReturnColumnAlias() + public async Task ReturnColumnAlias() { // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias // START a=node(1) @@ -20,24 +20,24 @@ public void ReturnColumnAlias() var client = Substitute.For(); client - .ExecuteGetCypherResults(Arg.Any()) + .ExecuteGetCypherResultsAsync(Arg.Any()) .Returns(Enumerable.Empty()); var cypher = new CypherFluentQuery(client); - var results = cypher - .Start("a", (NodeReference)1) + var results = await cypher + .Match("a") .Return(a => new ReturnPropertyQueryResult { SomethingTotallyDifferent = a.As().Age, }) - .Results; + .ResultsAsync; Assert.IsAssignableFrom>(results); } [Fact] - public void ReturnColumnAliasOfTypeEnum() + public async Task ReturnColumnAliasOfTypeEnum() { // http://docs.neo4j.org/chunked/1.6/query-return.html#return-column-alias // START a=node(1) @@ -46,74 +46,74 @@ public void ReturnColumnAliasOfTypeEnum() var client = Substitute.For(); client - .ExecuteGetCypherResults(Arg.Any()) + .ExecuteGetCypherResultsAsync(Arg.Any()) .Returns(Enumerable.Empty()); var cypher = new CypherFluentQuery(client); - var results = cypher - .Start("a", (NodeReference)1) + var results = await cypher + .Match("a") .Return(a => new FooNode { TheType = a.As().TheType, }) - .Results; + .ResultsAsync; Assert.IsAssignableFrom>(results); } [Fact] - public void ReturnNodeAsSet() + public async Task ReturnNodeAsSet() { var client = Substitute.For(); var set = new[] { new Node(new FooNode(), new NodeReference(123)) }; client - .ExecuteGetCypherResults>( + .ExecuteGetCypherResultsAsync>( Arg.Is(q => q.ResultMode == CypherResultMode.Set)) .Returns(set); var cypher = new CypherFluentQuery(client); - var results = cypher - .Start("a", (NodeReference)1) + var results = await cypher + .Match("a") .Return>("a") - .Results; + .ResultsAsync; Assert.Equal(set, results); } [Fact] - public void ReturnRelationshipWithDataAsSet() + public async Task ReturnRelationshipWithDataAsSet() { var client = Substitute.For(); var set = new[] { new RelationshipInstance(new RelationshipReference(1), new NodeReference(0), new NodeReference(2),"Type", new FooNode()) }; client - .ExecuteGetCypherResults>( + .ExecuteGetCypherResultsAsync>( Arg.Is(q => q.ResultMode == CypherResultMode.Set)) .Returns(set); var cypher = new CypherFluentQuery(client); - var results = cypher - .Start("a", (RelationshipReference)1) + var results = await cypher + .Match("a") .Return>("a") - .Results; + .ResultsAsync; Assert.Equal(set, results); } [Fact] - public void ReturnRelationshipAsSet() + public async Task ReturnRelationshipAsSet() { var client = Substitute.For(); var set = new[] { new RelationshipInstance(new RelationshipReference(1), new NodeReference(0), new NodeReference(2), "Type") }; client - .ExecuteGetCypherResults( + .ExecuteGetCypherResultsAsync( Arg.Is(q => q.ResultMode == CypherResultMode.Set)) .Returns(set); var cypher = new CypherFluentQuery(client); - var results = cypher - .Start("a", (RelationshipReference)1) + var results = await cypher + .Match("a") .Return("a") - .Results; + .ResultsAsync; Assert.Equal(set, results); } @@ -124,25 +124,24 @@ public void ExecutingQueryMultipleTimesShouldResetParameters() var client = Substitute.For(); client - .ExecuteGetCypherResults(Arg.Any()) + .ExecuteGetCypherResultsAsync(Arg.Any()) .Returns(Enumerable.Empty()); var cypher = new CypherFluentQuery(client); var query1 = cypher - .Start("a", (NodeReference)1) + .Match("a") .Return("a.Name") .Query; - Assert.Equal(1, query1.QueryParameters.Count()); - Assert.Equal(1L, query1.QueryParameters["p0"]); + Assert.Equal(0, query1.QueryParameters.Count()); + var query2 = cypher - .Start("b", (NodeReference)2) - .Return("a.Name") + .Match("b") + .Return("b.Name") .Query; - Assert.Equal(1, query2.QueryParameters.Count()); - Assert.Equal(2L, query2.QueryParameters["p0"]); + Assert.Equal(0, query2.QueryParameters.Count()); } public enum MyType {Type1, Type2} diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryReturnTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs similarity index 52% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryReturnTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs index a6cb4725f..85c6285a7 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryReturnTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryReturnTests.cs @@ -3,15 +3,15 @@ using System.Linq; using System.Linq.Expressions; using System.Net; -using Newtonsoft.Json.Serialization; -using NSubstitute; -using Xunit; +using System.Threading.Tasks; using Neo4jClient.ApiModels.Cypher; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { class FooWithJsonProperties @@ -28,12 +28,11 @@ public void ReturnDistinct() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .ReturnDistinct("n") .Query; - Assert.Equal("START n=node($p0)\r\nRETURN distinct n", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "RETURN distinct n", query.QueryText); Assert.Equal(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } @@ -42,14 +41,13 @@ public void ReturnDistinctWithLimit() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .ReturnDistinct("n") .Limit(5) .Query; - Assert.Equal("START n=node($p0)\r\nRETURN distinct n\r\nLIMIT $p1", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); - Assert.Equal(5, query.QueryParameters["p1"]); + Assert.Equal("MATCH n" + Environment.NewLine + "RETURN distinct n" + Environment.NewLine + "LIMIT $p0", query.QueryText); + Assert.Equal(5, query.QueryParameters["p0"]); Assert.Equal(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } @@ -58,15 +56,14 @@ public void ReturnDistinctWithLimitAndOrderBy() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .ReturnDistinct("n") .OrderBy("n.Foo") .Limit(5) .Query; - Assert.Equal("START n=node($p0)\r\nRETURN distinct n\r\nORDER BY n.Foo\r\nLIMIT $p1", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); - Assert.Equal(5, query.QueryParameters["p1"]); + Assert.Equal("MATCH n" + Environment.NewLine + "RETURN distinct n" + Environment.NewLine + "ORDER BY n.Foo" + Environment.NewLine + "LIMIT $p0", query.QueryText); + Assert.Equal(5, query.QueryParameters["p0"]); Assert.Equal(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } @@ -75,12 +72,11 @@ public void ReturnIdentity() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Return("n") .Query; - Assert.Equal("START n=node($p0)\r\nRETURN n", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "RETURN n", query.QueryText); Assert.Equal(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } @@ -100,14 +96,13 @@ public void ReturnWithLimit() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Return("n") .Limit(5) .Query; - Assert.Equal("START n=node($p0)\r\nRETURN n\r\nLIMIT $p1", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); - Assert.Equal(5, query.QueryParameters["p1"]); + Assert.Equal("MATCH n" + Environment.NewLine + "RETURN n" + Environment.NewLine + "LIMIT $p0", query.QueryText); + Assert.Equal(5, query.QueryParameters["p0"]); Assert.Equal(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } @@ -116,15 +111,14 @@ public void ReturnWithLimitAndOrderBy() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Return("n") .OrderBy("n.Foo") .Limit(5) .Query; - Assert.Equal("START n=node($p0)\r\nRETURN n\r\nORDER BY n.Foo\r\nLIMIT $p1", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); - Assert.Equal(5, query.QueryParameters["p1"]); + Assert.Equal("MATCH n" + Environment.NewLine + "RETURN n" + Environment.NewLine + "ORDER BY n.Foo" + Environment.NewLine + "LIMIT $p0", query.QueryText); + Assert.Equal(5, query.QueryParameters["p0"]); Assert.Equal(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } @@ -134,21 +128,15 @@ public void ShouldCombineWithLimitAndOrderBy() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start(new - { - me = (NodeReference)123, - viewer = (NodeReference)456 - }) + .Match( "me, viewer") .Match("me-[:FRIEND]-common-[:FRIEND]-viewer") .Return>("common") .Limit(5) .OrderBy("common.FirstName") .Query; - Assert.Equal(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.Equal(123L, query.QueryParameters["p0"]); - Assert.Equal(456L, query.QueryParameters["p1"]); - Assert.Equal(5, query.QueryParameters["p2"]); + Assert.Equal(string.Format("MATCH me, viewer{0}MATCH me-[:FRIEND]-common-[:FRIEND]-viewer{0}RETURN common{0}LIMIT $p0{0}ORDER BY common.FirstName", Environment.NewLine), query.QueryText); + Assert.Equal(5, query.QueryParameters["p0"]); Assert.Equal(CypherResultFormat.Rest, query.ResultFormat); } @@ -173,7 +161,7 @@ public void ReturnsWhenIdentityIsACollection() .Return>("[n0,n1]") .Query; - Assert.Equal("MATCH (n0),(n1)\r\nRETURN [n0,n1]", query.QueryText); + Assert.Equal("MATCH (n0),(n1)" + Environment.NewLine + "RETURN [n0,n1]", query.QueryText); Assert.Equal(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } @@ -186,7 +174,7 @@ public void ReturnsWhenIdentityIsACollectionRegardlessOfPadding() .Return>(" [n0,n1] ") .Query; - Assert.Equal("MATCH (n0),(n1)\r\nRETURN [n0,n1]", query.QueryText); + Assert.Equal("MATCH (n0),(n1)" + Environment.NewLine + "RETURN [n0,n1]", query.QueryText); Assert.Equal(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } @@ -396,223 +384,6 @@ public void ShouldUseProjectionResultModeForNamedObjectReturnCamel() Assert.Equal(CypherResultMode.Projection, query.ResultMode); } - [Fact] - 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.Equal(3L, results.Count()); - - var result = results[0]; - Assert.Equal("Antimony", result.Foo.Name); - Assert.Equal(38, result.Foo.UniqueId); - - result = results[1]; - Assert.Equal("Bauxite", result.Foo.Name); - Assert.Equal(24, result.Foo.UniqueId); - - result = results[2]; - Assert.Equal("Bismuth", result.Foo.Name); - Assert.Equal(37, result.Foo.UniqueId); - } - } - - [Fact] - 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.Equal(3L, results.Count()); - - var result = results[0]; - Assert.Equal("Antimony", result.Foo.Name); - Assert.Equal(38, result.Foo.UniqueId); - - result = results[1]; - Assert.Equal("Bauxite", result.Foo.Name); - Assert.Equal(24, result.Foo.UniqueId); - - result = results[2]; - Assert.Equal("Bismuth", result.Foo.Name); - Assert.Equal(37, result.Foo.UniqueId); - } - } [Fact] public void BinaryExpressionIsNotNull() @@ -623,7 +394,7 @@ public void BinaryExpressionIsNotNull() .Return(a => new {NotNull = a != null}) .Query; - Assert.Equal("MATCH (a)\r\nRETURN a IS NOT NULL AS NotNull", query.QueryText); + Assert.Equal("MATCH (a)" + Environment.NewLine + "RETURN a IS NOT NULL AS NotNull", query.QueryText); Assert.Equal(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } @@ -636,7 +407,7 @@ public void BinaryExpressionIsNull() .Return(a => new { IsNull = a == null }) .Query; - Assert.Equal("MATCH (a)\r\nRETURN a IS NULL AS IsNull", query.QueryText); + Assert.Equal("MATCH (a)" + Environment.NewLine + "RETURN a IS NULL AS IsNull", query.QueryText); Assert.Equal(CypherResultFormat.DependsOnEnvironment, query.ResultFormat); } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQuerySetClientTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQuerySetClientTests.cs similarity index 96% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQuerySetClientTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQuerySetClientTests.cs index 05fb6912f..e1ee6a4fa 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQuerySetClientTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQuerySetClientTests.cs @@ -1,9 +1,8 @@ using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQuerySetClientTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQuerySetTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQuerySetTests.cs similarity index 61% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQuerySetTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQuerySetTests.cs index 61e3be90a..d9aeea093 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQuerySetTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQuerySetTests.cs @@ -1,10 +1,9 @@ -using Xunit; -using NSubstitute; +using System; using Neo4jClient.Cypher; -using System; -using Neo4jClient.Test.Fixtures; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQuerySetTests : IClassFixture @@ -15,14 +14,13 @@ public void SetProperty() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Set("n.age = 30") .Return>("n") .Query; // Assert - Assert.Equal("START n=node($p0)\r\nSET n.age = 30\r\nRETURN n", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "SET n.age = 30" + Environment.NewLine + "RETURN n", query.QueryText); } [Fact] @@ -31,13 +29,12 @@ public void SetWithoutReturn() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference) 3) + .Match("n") .Set("n.name = \"Ted\"") .Query; // Assert - Assert.Equal("START n=node($p0)\r\nSET n.name = \"Ted\"", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "SET n.name = \"Ted\"", query.QueryText); } } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQuerySkipTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQuerySkipTests.cs similarity index 72% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQuerySkipTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQuerySkipTests.cs index 06d8d6062..e1be54e12 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQuerySkipTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQuerySkipTests.cs @@ -1,11 +1,10 @@ -using Xunit; -using NSubstitute; -using Neo4jClient.Cypher; -using System; +using System; using System.Linq; -using Neo4jClient.Test.Fixtures; +using Neo4jClient.Cypher; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQuerySkipTests : IClassFixture @@ -16,16 +15,15 @@ public void SkipClause() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Return>("n") .Skip(2) .Query; // Assert - Assert.Equal("START n=node($p0)\r\nRETURN n\r\nSKIP $p1", query.QueryText); - Assert.Equal(2, query.QueryParameters.Count); - Assert.Equal(3L, query.QueryParameters["p0"]); - Assert.Equal(2, query.QueryParameters["p1"]); + Assert.Equal("MATCH n" + Environment.NewLine + "RETURN n" + Environment.NewLine + "SKIP $p0", query.QueryText); + Assert.Equal(1, query.QueryParameters.Count); + Assert.Equal(2, query.QueryParameters["p0"]); } [Fact] @@ -35,7 +33,7 @@ public void SkipClauseAfterReturnClauseIsTyped() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) + .Match("n") .Return>("n") .Skip(2); @@ -49,15 +47,14 @@ public void NullSkipDoesNotWriteClause() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .Return>("n") .Skip(null) .Query; // Assert - Assert.Equal("START n=node($p0)\r\nRETURN n", query.QueryText); - Assert.Equal(1, query.QueryParameters.Count); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "RETURN n", query.QueryText); + Assert.Equal(0, query.QueryParameters.Count); } [Fact] @@ -67,17 +64,16 @@ public void SkipClauseAfterWithClause() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) + .Match("n") .With("foo") .Skip(2) .Query; // Assert - Assert.Equal("START n=node($p0)\r\nWITH foo\r\nSKIP $p1", query.QueryText); - Assert.Equal(2, query.QueryParameters.Count); - Assert.Equal(3L, query.QueryParameters["p0"]); - Assert.Equal(2, query.QueryParameters["p1"]); + Assert.Equal("MATCH n" + Environment.NewLine + "WITH foo" + Environment.NewLine + "SKIP $p0", query.QueryText); + Assert.Equal(1, query.QueryParameters.Count); + Assert.Equal(2, query.QueryParameters["p0"]); } [Fact] @@ -87,7 +83,7 @@ public void SkipClauseAfterWithClauseIsUntyped() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start(new { n = new NodeReference(3) }) + .Match("n") .With("foo") .Skip(2); diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs similarity index 77% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs index 5b65b1478..9ea41a065 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryTests.cs @@ -1,37 +1,34 @@ using System; +using System.Threading.Tasks; +using Neo4jClient.Cypher; +using Neo4jClient.Tests.GraphClientTests; using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -using Neo4jClient.Test.GraphClientTests; -//using Neo4jClient.Test.GraphClientTests; - -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryTests : IClassFixture { [Fact] - public void ExecutesQuery() + public async Task ExecutesQuery() { // Arrange var client = Substitute.For(); CypherQuery executedQuery = null; client - .When(c => c.ExecuteCypher(Arg.Any())) + .When(c => c.ExecuteCypherAsync(Arg.Any())) .Do(ci => { executedQuery = ci.Arg(); }); // Act - new CypherFluentQuery(client) - .Start("n", (NodeReference) 5) + await new CypherFluentQuery(client) + .Match("n") .Delete("n") - .ExecuteWithoutResults(); + .ExecuteWithoutResultsAsync(); // Assert Assert.NotNull(executedQuery); - Assert.Equal("START n=node($p0)\r\nDELETE n", executedQuery.QueryText); - Assert.Equal(5L, executedQuery.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "DELETE n", executedQuery.QueryText); } [Fact] @@ -46,15 +43,14 @@ public void ExecutesQueryAsync() // Act var task = new CypherFluentQuery(client) - .Start("n", (NodeReference) 5) + .Match("n") .Delete("n") .ExecuteWithoutResultsAsync(); task.Wait(); // Assert Assert.NotNull(executedQuery); - Assert.Equal("START n=node($p0)\r\nDELETE n", executedQuery.QueryText); - Assert.Equal(5L, executedQuery.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "DELETE n", executedQuery.QueryText); } [Fact] @@ -62,103 +58,17 @@ public void ShouldBuildQueriesAsImmutableStepsInsteadOfCorruptingPreviousOnes() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)1) + .Match("n") .Return("n"); var query1 = query.Query; query = query.OrderBy("n.Foo"); var query2 = query.Query; - Assert.Equal("START n=node($p0)\r\nRETURN n", query1.QueryText); - Assert.Equal(1L, query1.QueryParameters["p0"]); - - Assert.Equal("START n=node($p0)\r\nRETURN n\r\nORDER BY n.Foo", query2.QueryText); - Assert.Equal(1L, query2.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "RETURN n", query1.QueryText); + Assert.Equal("MATCH n" + Environment.NewLine + "RETURN n" + Environment.NewLine + "ORDER BY n.Foo", query2.QueryText); } - - - [Fact] - 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.Equal("START a=node($p0)", query1.QueryText); - Assert.Equal(1L, query1.QueryParameters.Count); - Assert.Equal(1L, query1.QueryParameters["p0"]); - - Assert.Equal("START b=node($p0)", query2.QueryText); - Assert.Equal(1L, query2.QueryParameters.Count); - Assert.Equal(2L, query2.QueryParameters["p0"]); - } - - [Fact] - 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.Equal("START n=node($p0)\r\nRETURN n", query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); - } - - [Fact] - 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.Equal("START a=node($p0), b=node($p1)", query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); - Assert.Equal(2L, query.QueryParameters["p1"]); - } - - [Fact] - [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.Equal("START a=node($p0), b=node($p1)", query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); - Assert.Equal(2L, query.QueryParameters["p1"]); - } - + [Fact] public void ReturnFirstPart() { @@ -173,7 +83,7 @@ public void ReturnFirstPart() .Limit(3) .Query; - Assert.Equal("RETURN n\r\nLIMIT $p0", query.QueryText); + Assert.Equal("RETURN n" + Environment.NewLine + "LIMIT $p0", query.QueryText); Assert.Equal(1L, query.QueryParameters.Count); Assert.Equal(3, query.QueryParameters["p0"]); } @@ -194,7 +104,7 @@ public void SkipFirstThree() .Skip(3) .Query; - Assert.Equal("RETURN n\r\nORDER BY n.name\r\nSKIP $p0", query.QueryText); + Assert.Equal("RETURN n" + Environment.NewLine + "ORDER BY n.name" + Environment.NewLine + "SKIP $p0", query.QueryText); Assert.Equal(1L, query.QueryParameters.Count); Assert.Equal(3, query.QueryParameters["p0"]); } @@ -217,7 +127,7 @@ public void ReturnMiddleTwo() .Limit(2) .Query; - Assert.Equal("RETURN n\r\nORDER BY n.name\r\nSKIP $p0\r\nLIMIT $p1", query.QueryText); + Assert.Equal("RETURN n" + Environment.NewLine + "ORDER BY n.name" + Environment.NewLine + "SKIP $p0" + Environment.NewLine + "LIMIT $p1", query.QueryText); Assert.Equal(2, query.QueryParameters.Count); Assert.Equal(1, query.QueryParameters["p0"]); Assert.Equal(2, query.QueryParameters["p1"]); @@ -236,7 +146,7 @@ public void OrderNodesByNull() .OrderBy("n.length?") .Query; - Assert.Equal("RETURN n\r\nORDER BY n.length?", query.QueryText); + Assert.Equal("RETURN n" + Environment.NewLine + "ORDER BY n.length?", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } @@ -254,7 +164,7 @@ public void OrderNodesByProperty() .OrderBy("n.name") .Query; - Assert.Equal("RETURN n\r\nORDER BY n.name", query.QueryText); + Assert.Equal("RETURN n" + Environment.NewLine + "ORDER BY n.name", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } @@ -272,7 +182,7 @@ public void OrderNodesByMultipleProperties() .OrderBy("n.age", "n.name") .Query; - Assert.Equal("RETURN n\r\nORDER BY n.age, n.name", query.QueryText); + Assert.Equal("RETURN n" + Environment.NewLine + "ORDER BY n.age, n.name", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } @@ -290,7 +200,7 @@ public void OrderNodesByPropertyDescending() .OrderByDescending("n.name") .Query; - Assert.Equal("RETURN n\r\nORDER BY n.name DESC", query.QueryText); + Assert.Equal("RETURN n" + Environment.NewLine + "ORDER BY n.name DESC", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } @@ -308,7 +218,7 @@ public void OrderNodesByMultiplePropertiesDescending() .OrderByDescending("n.age", "n.name") .Query; - Assert.Equal("RETURN n\r\nORDER BY n.age DESC, n.name DESC", query.QueryText); + Assert.Equal("RETURN n" + Environment.NewLine + "ORDER BY n.age DESC, n.name DESC", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } @@ -328,7 +238,7 @@ public void OrderNodesByMultiplePropertiesWithDifferentOrders() .ThenBy("n.lastName") .Query; - Assert.Equal("RETURN n\r\nORDER BY n.age, n.name, n.number DESC, n.male DESC, n.lastName", query.QueryText); + Assert.Equal("RETURN n" + Environment.NewLine + "ORDER BY n.age, n.name, n.number DESC, n.male DESC, n.lastName", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } @@ -336,20 +246,18 @@ public void OrderNodesByMultiplePropertiesWithDifferentOrders() 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) + .Match("a") .Return(a => new ReturnPropertyQueryResult { SomethingTotallyDifferent = a.As().Age }) .Query; - Assert.Equal("START a=node($p0)\r\nRETURN a.Age AS SomethingTotallyDifferent", query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); + Assert.Equal("MATCH a" + Environment.NewLine + "RETURN a.Age AS SomethingTotallyDifferent", query.QueryText); } [Fact] @@ -362,13 +270,12 @@ public void ReturnUniqueResults() var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) + .Match("a") .Match("(a)-->(b)") .ReturnDistinct("b") .Query; - Assert.Equal("START a=node($p0)\r\nMATCH (a)-->(b)\r\nRETURN distinct b", query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); + Assert.Equal("MATCH a" + Environment.NewLine + "MATCH (a)-->(b)" + Environment.NewLine + "RETURN distinct b", query.QueryText); } [Fact] @@ -381,7 +288,7 @@ public void ReturnUniqueResultsWithExpression() var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) + .Match("a") .Match("(a)-->(b)") .ReturnDistinct(b => new FooData { @@ -389,8 +296,7 @@ public void ReturnUniqueResultsWithExpression() }) .Query; - Assert.Equal("START a=node($p0)\r\nMATCH (a)-->(b)\r\nRETURN distinct b.Age AS Age", query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); + Assert.Equal("MATCH a" + Environment.NewLine + "MATCH (a)-->(b)" + Environment.NewLine + "RETURN distinct b.Age AS Age", query.QueryText); } [Fact] @@ -398,7 +304,7 @@ public void ReturnPropertiesIntoAnonymousType() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) + .Match("a") .Match("(a)-->(b)") .Return(b => new { @@ -407,10 +313,9 @@ public void ReturnPropertiesIntoAnonymousType() }) .Query; - string expected = string.Format("START a=node($p0){0}MATCH (a)-->(b){0}RETURN b.Age AS SomeAge, b.Name AS SomeName", Environment.NewLine); + string expected = string.Format("MATCH a{0}MATCH (a)-->(b){0}RETURN b.Age AS SomeAge, b.Name AS SomeName", Environment.NewLine); - Assert.Equal(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); + Assert.Equal(expected.TrimStart('\r', '\n'), query.QueryText); Assert.Equal(CypherResultMode.Projection, query.ResultMode); } @@ -419,7 +324,7 @@ public void ReturnPropertiesIntoAnonymousTypeWithAutoNames() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) + .Match("a") .Match("(a)-->(b)") .Return(b => new { @@ -428,10 +333,9 @@ public void ReturnPropertiesIntoAnonymousTypeWithAutoNames() }) .Query; - string expected = string.Format("START a=node($p0){0}MATCH (a)-->(b){0}RETURN b.Age AS Age, b.Name AS Name", Environment.NewLine); + string expected = string.Format("MATCH a{0}MATCH (a)-->(b){0}RETURN b.Age AS Age, b.Name AS Name", Environment.NewLine); - Assert.Equal(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); + Assert.Equal(expected.TrimStart('\r', '\n'), query.QueryText); Assert.Equal(CypherResultMode.Projection, query.ResultMode); } @@ -440,7 +344,7 @@ public void ReturnPropertiesFromMultipleNodesIntoAnonymousTypeWithAutoNames() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) + .Match("a") .Match("(a)-->(b)-->(c)") .Return((b, c) => new { @@ -449,10 +353,9 @@ public void ReturnPropertiesFromMultipleNodesIntoAnonymousTypeWithAutoNames() }) .Query; - string expected = "START a=node($p0)" + Environment.NewLine + "MATCH (a)-->(b)-->(c)" + Environment.NewLine + "RETURN b.Age AS Age, c.Name AS Name"; + string expected = "MATCH a" + Environment.NewLine + "MATCH (a)-->(b)-->(c)" + Environment.NewLine + "RETURN b.Age AS Age, c.Name AS Name"; - Assert.Equal(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); + Assert.Equal(expected.TrimStart('\r', '\n'), query.QueryText); Assert.Equal(CypherResultMode.Projection, query.ResultMode); } @@ -461,7 +364,7 @@ public void ReturnNodeDataIntoAnonymousType() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) + .Match("a") .Match("(a)-->(b)") .Return((b, c) => new { @@ -469,10 +372,9 @@ public void ReturnNodeDataIntoAnonymousType() }) .Query; - string expected = "START a=node($p0)" + Environment.NewLine + "MATCH (a)-->(b)" + Environment.NewLine + "RETURN b AS NodeB"; + string expected = "MATCH a" + Environment.NewLine + "MATCH (a)-->(b)" + Environment.NewLine + "RETURN b AS NodeB"; - Assert.Equal(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); + Assert.Equal(expected.TrimStart('\r', '\n'), query.QueryText); Assert.Equal(CypherResultMode.Projection, query.ResultMode); } @@ -481,7 +383,7 @@ public void ReturnEntireNodeDataAndReferenceIntoAnonymousType() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) + .Match("a") .Match("(a)-->(b)") .Return((b, c) => new { @@ -489,10 +391,9 @@ public void ReturnEntireNodeDataAndReferenceIntoAnonymousType() }) .Query; - string expected = string.Format("START a=node($p0){0}MATCH (a)-->(b){0}RETURN b AS NodeB", Environment.NewLine); + string expected = string.Format("MATCH a{0}MATCH (a)-->(b){0}RETURN b AS NodeB", Environment.NewLine); - Assert.Equal(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); + Assert.Equal(expected.TrimStart('\r', '\n'), query.QueryText); Assert.Equal(CypherResultMode.Projection, query.ResultMode); } @@ -501,7 +402,7 @@ public void ReturnEntireNodeDataAndReferenceIntoProjectionType() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)1) + .Match("a") .Match("(a)-->(b)") .Return((b, c) => new ReturnEntireNodeDataAndReferenceIntoProjectionTypeResult { @@ -509,10 +410,9 @@ public void ReturnEntireNodeDataAndReferenceIntoProjectionType() }) .Query; - string expected = string.Format("START a=node($p0){0}MATCH (a)-->(b){0}RETURN b AS NodeB", Environment.NewLine); + string expected = string.Format("MATCH a{0}MATCH (a)-->(b){0}RETURN b AS NodeB", Environment.NewLine); - Assert.Equal(expected.TrimStart(new[] { '\r', '\n' }), query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); + Assert.Equal(expected.TrimStart('\r', '\n'), query.QueryText); Assert.Equal(CypherResultMode.Projection, query.ResultMode); } @@ -872,7 +772,7 @@ public void WhereWithAnd() .WithParam("Hosts", "HOSTS") .Query; - Assert.Equal("WHERE (n.Name = $p0)\r\nAND (type(r) = {Hosts})", query.QueryText); + Assert.Equal("WHERE (n.Name = $p0)" + Environment.NewLine + "AND (type(r) = {Hosts})", query.QueryText); Assert.Equal(2L, query.QueryParameters.Count); Assert.Equal("bob", query.QueryParameters["p0"]); Assert.Equal("HOSTS", query.QueryParameters["Hosts"]); @@ -888,7 +788,7 @@ public void WhereWithOr() .WithParam("Hosts", "HOSTS") .Query; - Assert.Equal("WHERE (n.Name = $p0)\r\nOR (type(r) = {Hosts})", query.QueryText); + Assert.Equal("WHERE (n.Name = $p0)" + Environment.NewLine + "OR (type(r) = {Hosts})", query.QueryText); Assert.Equal(2L, query.QueryParameters.Count); Assert.Equal("bob", query.QueryParameters["p0"]); Assert.Equal("HOSTS", query.QueryParameters["Hosts"]); @@ -905,7 +805,7 @@ public void WhereWithOrAnd() .AndWhere(n => n.Id == 10) .Query; - Assert.Equal("WHERE (n.Name = $p0)\r\nOR (type(r) = {Hosts})\r\nAND (n.Id = $p2)", query.QueryText); + Assert.Equal("WHERE (n.Name = $p0)" + Environment.NewLine + "OR (type(r) = {Hosts})" + Environment.NewLine + "AND (n.Id = $p2)", query.QueryText); Assert.Equal(3, query.QueryParameters.Count); Assert.Equal("Bob", query.QueryParameters["p0"]); Assert.Equal(10, query.QueryParameters["p2"]); @@ -956,17 +856,12 @@ public void CreateRelationshipBetweenTwoNodes() var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start(new { - a = (NodeReference)1, - b = (NodeReference)2 - }) + .Match("a,b") .Create("a-[r:REL]->b") .Return("r") .Query; - Assert.Equal("START a=node($p0), b=node($p1)\r\nCREATE a-[r:REL]->b\r\nRETURN r", query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); - Assert.Equal(2L, query.QueryParameters["p1"]); + Assert.Equal("MATCH a,b" + Environment.NewLine + "CREATE a-[r:REL]->b" + Environment.NewLine + "RETURN r", query.QueryText); } [Fact] @@ -982,7 +877,7 @@ public void CreateNode() .Create("a", data) .Return("a") .Query; - Assert.Equal("CREATE (a $p0)\r\nRETURN a", query.QueryText); + Assert.Equal("CREATE (a $p0)" + Environment.NewLine + "RETURN a", query.QueryText); Assert.Equal(data, query.QueryParameters["p0"]); } @@ -997,13 +892,14 @@ public void CreateAFullPath() { 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) + .Match("n") + .Create("n-[r:REL]->(a $p0)-[r:REL]->(b $p1)") + .WithParams(new {p0=data1, p1=data2 }) .Return("a") .Query; - Assert.Equal("START n=node($p0)\r\nCREATE n-[r:REL]->(a $p1)-[r:REL]->(b $p2)\r\nRETURN a", query.QueryText); - Assert.Equal(data1, query.QueryParameters["p1"]); - Assert.Equal(data2, query.QueryParameters["p2"]); + Assert.Equal("MATCH n" + Environment.NewLine + "CREATE n-[r:REL]->(a $p0)-[r:REL]->(b $p1)" + Environment.NewLine + "RETURN a", query.QueryText); + Assert.Equal(data1, query.QueryParameters["p0"]); + Assert.Equal(data2, query.QueryParameters["p1"]); } [Fact] @@ -1016,17 +912,12 @@ public void CreateRelationshipAndSetProperties() var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start(new { - a = (NodeReference)1, - b = (NodeReference)2 - }) + .Match("a,b") .Create("a-[r:REL {name : a.name + '<->' + b.name }]->b") .Return("r") .Query; - Assert.Equal("START a=node($p0), b=node($p1)\r\nCREATE a-[r:REL {name : a.name + '<->' + b.name }]->b\r\nRETURN r", query.QueryText); - Assert.Equal(1L, query.QueryParameters["p0"]); - Assert.Equal(2L, query.QueryParameters["p1"]); + Assert.Equal("MATCH a,b" + Environment.NewLine + "CREATE a-[r:REL {name : a.name + '<->' + b.name }]->b" + Environment.NewLine + "RETURN r", query.QueryText); } [Fact] @@ -1039,14 +930,13 @@ public void ComplexMatching() var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("a", (NodeReference)3) + .Match("a") .Match( "(a)-[:KNOWS]->(b)-[:KNOWS]->(c)", "(a)-[:BLOCKS]-(d)-[:KNOWS]-(c)") .Query; - Assert.Equal("START a=node($p0)\r\nMATCH (a)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:BLOCKS]-(d)-[:KNOWS]-(c)", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH a" + Environment.NewLine + "MATCH (a)-[:KNOWS]->(b)-[:KNOWS]->(c), (a)-[:BLOCKS]-(d)-[:KNOWS]-(c)", query.QueryText); } [Fact] @@ -1067,7 +957,7 @@ public void SupportsFlexibleOrderOfClauses() var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start(new { me = Node.ByIndexLookup("node_auto_index", "name", "Bob") }) + .Match("me") .Match("me-[r?:STATUS]-secondlatestupdate") .Delete("r") .With("me, secondlatestupdate") @@ -1081,8 +971,7 @@ public void SupportsFlexibleOrderOfClauses() // ReSharper restore InconsistentNaming .Query; - Assert.Equal(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.Equal("Bob", query.QueryParameters["p0"]); + Assert.Equal(string.Format("MATCH me{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.Equal(update, query.QueryParameters["update"]); } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryUnwindTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryUnwindTests.cs similarity index 87% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryUnwindTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryUnwindTests.cs index ea75d55ca..74927c36c 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryUnwindTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryUnwindTests.cs @@ -1,9 +1,9 @@ -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; +using System; +using Neo4jClient.Cypher; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { /// /// Tests for the UNWIND operator @@ -31,7 +31,7 @@ public void TestUnwindAfterWithTResultVariant() .Unwind("collection", "column") .Query; - Assert.Equal("WITH collection\r\nUNWIND collection AS column", query.QueryText); + Assert.Equal("WITH collection" + Environment.NewLine + "UNWIND collection AS column", query.QueryText); } [Fact] diff --git a/Neo4jClient.Tests/Cypher/CypherFluentQueryUseTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryUseTests.cs new file mode 100644 index 000000000..a71cdb5eb --- /dev/null +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryUseTests.cs @@ -0,0 +1,25 @@ +using System; +using FluentAssertions; +using Neo4jClient.Cypher; +using NSubstitute; +using Xunit; + +namespace Neo4jClient.Tests.Cypher +{ + public class CypherFluentQueryUseTests : IClassFixture + { + // https://neo4j.com/docs/cypher-manual/current/clauses/use/ + [Fact] + public void GeneratesTheCorrectCypher() + { + var client = Substitute.For(); + var query = new CypherFluentQuery(client) + .Use("neo4jclient") + .Match("(n)") + .Return("n") + .Query; + + query.QueryText.Should().Be($"USE neo4jclient{Environment.NewLine}MATCH (n){Environment.NewLine}RETURN n"); + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryUsingIndexTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryUsingIndexTests.cs similarity index 84% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryUsingIndexTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryUsingIndexTests.cs index 715c9bac7..c84a867a4 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryUsingIndexTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryUsingIndexTests.cs @@ -1,10 +1,9 @@ using System; -using Xunit; -using NSubstitute; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryUsingIndexTests : IClassFixture @@ -19,7 +18,7 @@ public void UsesIndex() .Return(foo => new { qux = foo.As() } ) .Query; - Assert.Equal("MATCH (foo:Bar { id: 123 })\r\nUSING INDEX :Bar(id)\r\nRETURN foo AS qux", query.QueryText); + Assert.Equal("MATCH (foo:Bar { id: 123 })" + Environment.NewLine + "USING INDEX :Bar(id)" + Environment.NewLine + "RETURN foo AS qux", query.QueryText); } [Theory] diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWhereTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryWhereTests.cs similarity index 98% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWhereTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryWhereTests.cs index 5535fa0af..4664fc78e 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWhereTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryWhereTests.cs @@ -1,13 +1,12 @@ using System; -using Newtonsoft.Json.Serialization; -using NSubstitute; -using Xunit; using Neo4jClient.Cypher; using Neo4jClient.Extensions; -using Neo4jClient.Test.Fixtures; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryWhereTests : IClassFixture @@ -170,7 +169,7 @@ public void NestOrAndAndCorrectly() .AndWhere((Foo c) => c.Bar == 789) .Query; - Assert.Equal("WHERE ((a.Bar = $p0) OR (b.Bar = $p1))\r\nAND (c.Bar = $p2)", query.QueryText); + Assert.Equal("WHERE ((a.Bar = $p0) OR (b.Bar = $p1))" + Environment.NewLine + "AND (c.Bar = $p2)", query.QueryText); Assert.Equal(3, query.QueryParameters.Count); } @@ -210,7 +209,7 @@ public void NestOrAndAndCorrectlyCamel() .AndWhere((FooCamel c) => c.B == 789) .Query; - Assert.Equal("WHERE ((a.longBar = $p0) OR (b.bar = $p1))\r\nAND (c.b = $p2)", query.QueryText); + Assert.Equal("WHERE ((a.longBar = $p0) OR (b.bar = $p1))" + Environment.NewLine + "AND (c.b = $p2)", query.QueryText); Assert.Equal(3, query.QueryParameters.Count); } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithBookmarkTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithBookmarkTests.cs similarity index 83% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithBookmarkTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryWithBookmarkTests.cs index 143a67ccd..b7db1299b 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithBookmarkTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithBookmarkTests.cs @@ -1,12 +1,11 @@ using System.Collections.Generic; +using System.Linq; using FluentAssertions; using Moq; -using NSubstitute; -using Xunit; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class WithBookmarkMethod : IClassFixture { @@ -51,7 +50,7 @@ public void SetsBookmark_InQuery() var query = cfq.Query; query.Bookmarks.Should().HaveCount(1); - query.Bookmarks.Should().Contain(bookmarkName); + query.Bookmarks.SelectMany(b => b.Values).Should().Contain(bookmarkName); } [Fact] @@ -66,8 +65,9 @@ public void SetsBookmarks_InQuery1() var query = cfq.Query; query.Bookmarks.Should().HaveCount(2); - query.Bookmarks.Should().Contain(bookmarkName1); - query.Bookmarks.Should().Contain(bookmarkName2); + var bmarks = query.Bookmarks.SelectMany(b => b.Values).ToList(); + bmarks.Should().Contain(bookmarkName1); + bmarks.Should().Contain(bookmarkName2); } [Fact] @@ -83,8 +83,9 @@ public void SetsBookmarks_InQuery2() var query = cfq.Query; query.Bookmarks.Should().HaveCount(2); - query.Bookmarks.Should().Contain(bookmarkName1); - query.Bookmarks.Should().Contain(bookmarkName2); + var bmarks = query.Bookmarks.SelectMany(b => b.Values).ToList(); + bmarks.Should().Contain(bookmarkName1); + bmarks.Should().Contain(bookmarkName2); } } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithIdentifierTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithIdentifierTests.cs similarity index 80% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithIdentifierTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryWithIdentifierTests.cs index 88022466d..0eb651ee4 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithIdentifierTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithIdentifierTests.cs @@ -1,14 +1,13 @@ using System.Collections.Generic; +using System.Threading.Tasks; using FluentAssertions; using Moq; -using Neo4j.Driver.V1; +using Neo4j.Driver; using Neo4jClient.Cypher; -using Neo4jClient.Test.BoltGraphClientTests; -using Neo4jClient.Test.BoltGraphClientTests.Cypher; -using Neo4jClient.Test.Fixtures; +using Neo4jClient.Tests.BoltGraphClientTests; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class WithIdentifierMethod : IClassFixture { @@ -44,7 +43,7 @@ public void SetsTheIdentifer_WhenValid() } [Fact] - public void ArgsContainIdentifier() + public async Task ArgsContainIdentifier() { const string identifier = "identifier"; @@ -52,7 +51,7 @@ public void ArgsContainIdentifier() var queryParams = new Dictionary(); - var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional){Identifier = identifier}; + var cypherQuery = new CypherQuery(queryText, queryParams, CypherResultMode.Set, CypherResultFormat.Transactional, "neo4j") {Identifier = identifier}; using (var testHarness = new BoltTestHarness()) { @@ -63,10 +62,10 @@ public void ArgsContainIdentifier() var testStatementResult = new TestStatementResult(new[] { "data" }, recordMock.Object); testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); - var graphClient = testHarness.CreateAndConnectBoltGraphClient(); + var graphClient = await testHarness.CreateAndConnectBoltGraphClient(); graphClient.OperationCompleted += (s, e) => { e.Identifier.Should().Be(identifier); }; - graphClient.ExecuteGetCypherResults>(cypherQuery); + await graphClient.ExecuteGetCypherResultsAsync>(cypherQuery); } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithParamTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithParamTests.cs similarity index 67% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithParamTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryWithParamTests.cs index 1785fb92d..0960e0212 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithParamTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithParamTests.cs @@ -1,13 +1,10 @@ -using System.Globalization; -using Neo4jClient.Serialization; +using System; +using Neo4jClient.Cypher; using Newtonsoft.Json.Serialization; -using Xunit; using NSubstitute; -using Neo4jClient.Cypher; -using System; -using Neo4jClient.Test.Fixtures; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryWithParamTests : IClassFixture @@ -18,14 +15,13 @@ public void WithParam() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .WithParam("foo", 123) .Query; // Assert - Assert.Equal("START n=node($p0)", query.QueryText); - Assert.Equal(2, query.QueryParameters.Count); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n", query.QueryText); + Assert.Equal(1, query.QueryParameters.Count); Assert.Equal(123, query.QueryParameters["foo"]); } @@ -39,14 +35,13 @@ public void WithParams() // Act const string bar = "string value"; var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .WithParams(new {foo = 123, bar}) .Query; // Assert - Assert.Equal("START n=node($p0)", query.QueryText); - Assert.Equal(3, query.QueryParameters.Count); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n", query.QueryText); + Assert.Equal(2, query.QueryParameters.Count); Assert.Equal(123, query.QueryParameters["foo"]); Assert.Equal("string value", query.QueryParameters["bar"]); } @@ -57,7 +52,7 @@ public void ThrowsExceptionForDuplicateManualKey() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .WithParam("foo", 123); // Assert @@ -65,7 +60,7 @@ public void ThrowsExceptionForDuplicateManualKey() () => query.WithParam("foo", 456) ); Assert.Equal("key", ex.ParamName); - Assert.Equal("A parameter with the given key is already defined in the query.\r\nParameter name: key", ex.Message); + Assert.Equal("A parameter with the given key is already defined in the query." + Environment.NewLine + "Parameter name: key", ex.Message); } [Fact] @@ -74,14 +69,15 @@ public void ThrowsExceptionForDuplicateOfAutoKey() // Arrange var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3); + .Match("n") + .Where((FooWithJsonProperties n) => n.Bar == "test"); // Assert var ex = Assert.Throws( () => query.WithParam("p0", 456) ); Assert.Equal("key", ex.ParamName); - Assert.Equal("A parameter with the given key is already defined in the query.\r\nParameter name: key", ex.Message); + Assert.Equal("A parameter with the given key is already defined in the query." + Environment.NewLine + "Parameter name: key", ex.Message); } public class ComplexObjForWithParamTest @@ -93,7 +89,6 @@ public class ComplexObjForWithParamTest } [Theory] - [InlineData("{obj}")] [InlineData("$obj")] public void ComplexObjectInWithParam(string param) { @@ -102,20 +97,20 @@ public void ComplexObjectInWithParam(string param) // Act var query = new CypherFluentQuery(client) - .Start("n", (NodeReference) 3) + .Match("n") .CreateUnique($"n-[:X]-(leaf {param})") .WithParam("obj", CreateComplexObjForWithParamTest()) .Query; // Assert - Assert.Equal("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.Equal(2, query.QueryParameters.Count); + Assert.Equal("MATCH n" + + Environment.NewLine + "CREATE UNIQUE n-[:X]-(leaf {" + + Environment.NewLine + " \"Id\": 123," + + Environment.NewLine + " \"Name\": \"Bar\"," + + Environment.NewLine + " \"Currency\": 12.143," + + Environment.NewLine + " \"CamelCaseProperty\": \"Foo\"" + + Environment.NewLine + "})", query.DebugQueryText); + Assert.Equal(1, query.QueryParameters.Count); } private ComplexObjForWithParamTest CreateComplexObjForWithParamTest() @@ -130,7 +125,6 @@ private ComplexObjForWithParamTest CreateComplexObjForWithParamTest() } [Theory] - [InlineData("{obj}")] [InlineData("$obj")] public void ComplexObjectInWithParamCamelCase(string param) { @@ -140,20 +134,20 @@ public void ComplexObjectInWithParamCamelCase(string param) // Act var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .CreateUnique($"n-[:X]-(leaf {param})") .WithParam("obj", CreateComplexObjForWithParamTest()) .Query; // Assert - Assert.Equal("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.Equal(2, query.QueryParameters.Count); + Assert.Equal("MATCH n" + + Environment.NewLine + "CREATE UNIQUE n-[:X]-(leaf {" + + Environment.NewLine + " \"id\": 123," + + Environment.NewLine + " \"name\": \"Bar\"," + + Environment.NewLine + " \"currency\": 12.143," + + Environment.NewLine + " \"camelCaseProperty\": \"Foo\"" + + Environment.NewLine + "})", query.DebugQueryText); + Assert.Equal(1, query.QueryParameters.Count); } } } diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs similarity index 96% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs index 91ff7a292..9948496a0 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryWithTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryWithTests.cs @@ -1,12 +1,11 @@ using System; +using Neo4jClient.Cypher; +using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -using Newtonsoft.Json; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryWithTests : IClassFixture @@ -16,12 +15,11 @@ public void With() { var client = Substitute.For(); var query = new CypherFluentQuery(client) - .Start("n", (NodeReference)3) + .Match("n") .With("foo") .Query; - Assert.Equal("START n=node($p0)\r\nWITH foo", query.QueryText); - Assert.Equal(3L, query.QueryParameters["p0"]); + Assert.Equal("MATCH n" + Environment.NewLine + "WITH foo", query.QueryText); } [Fact] diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryYieldTests.cs b/Neo4jClient.Tests/Cypher/CypherFluentQueryYieldTests.cs similarity index 95% rename from Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryYieldTests.cs rename to Neo4jClient.Tests/Cypher/CypherFluentQueryYieldTests.cs index c21163a99..cc90cd562 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherFluentQueryYieldTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherFluentQueryYieldTests.cs @@ -1,10 +1,9 @@ using System; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherFluentQueryYieldTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherQueryTests.cs b/Neo4jClient.Tests/Cypher/CypherQueryTests.cs similarity index 75% rename from Neo4jClient.Tests.Shared/Cypher/CypherQueryTests.cs rename to Neo4jClient.Tests/Cypher/CypherQueryTests.cs index 486891b71..fe029a35c 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherQueryTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherQueryTests.cs @@ -1,9 +1,9 @@ -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; +using System; +using Neo4jClient.Cypher; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherQueryTests : IClassFixture @@ -11,7 +11,7 @@ public class CypherQueryTests : IClassFixture [Fact] public void DebugQueryShouldBeSuccessfulWithNullAsParameters() { - var query = new CypherQuery("MATCH (n) RETURN (n)", null, CypherResultMode.Set); + var query = new CypherQuery("MATCH (n) RETURN (n)", null, CypherResultMode.Set, "neo4j"); const string expected = "MATCH (n) RETURN (n)"; Assert.Equal(expected, query.DebugQueryText); @@ -26,12 +26,12 @@ public void DebugQueryTextShouldPreserveNewLines() .CreateUnique("bar") .Query; - const string expected = "MATCH foo\r\nCREATE UNIQUE bar"; + string expected = "MATCH foo" + Environment.NewLine + "CREATE UNIQUE bar"; Assert.Equal(expected, query.DebugQueryText); } [Theory] - [InlineData("{param}")] + [InlineData("$param")] public void DebugQueryTextShouldSubstituteNumericParameters(string match) { @@ -49,7 +49,6 @@ public void DebugQueryTextShouldSubstituteNumericParameters(string match) } [Theory] - [InlineData("{param}")] [InlineData("$param")] public void DebugQueryTextShouldSubstituteStringParametersWithEncoding(string match) { @@ -67,7 +66,6 @@ public void DebugQueryTextShouldSubstituteStringParametersWithEncoding(string ma } [Theory] - [InlineData("{param}")] [InlineData("$param")] public void DebugQueryTextShouldSubstituteStringParametersWithEncodingOfSpecialCharacters(string match) { @@ -85,7 +83,6 @@ public void DebugQueryTextShouldSubstituteStringParametersWithEncodingOfSpecialC } [Theory] - [InlineData("{param}")] [InlineData("$param")] //[Description("https://github.com/Readify/Neo4jClient/issues/50")] public void DebugQueryTextShouldSubstituteNullParameters(string match) @@ -102,26 +99,5 @@ public void DebugQueryTextShouldSubstituteNullParameters(string match) const string expected = "MATCH null"; Assert.Equal(expected, query.DebugQueryText); } - - [Fact] - public void DebugQueryTextShouldSubstituteBothParameterSyntaxStyles() - { - var client = Substitute.For(); - var query = new CypherFluentQuery(client) - .Match("$paramDollar") - .WithParams(new - { - paramDollar = 123 - }) - .Match("{paramBraces}") - .WithParams(new - { - paramBraces = 456 - }) - .Query; - - const string expected = "MATCH 123\r\nMATCH 456"; - Assert.Equal(expected, query.DebugQueryText); - } } } \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherReturnExpressionBuilderTests.cs b/Neo4jClient.Tests/Cypher/CypherReturnExpressionBuilderTests.cs similarity index 99% rename from Neo4jClient.Tests.Shared/Cypher/CypherReturnExpressionBuilderTests.cs rename to Neo4jClient.Tests/Cypher/CypherReturnExpressionBuilderTests.cs index 06fdf2c50..6bd189487 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherReturnExpressionBuilderTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherReturnExpressionBuilderTests.cs @@ -2,11 +2,10 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; -using Xunit; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherReturnExpressionBuilderTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/Cypher/CypherWhereExpressionBuilderTests.cs b/Neo4jClient.Tests/Cypher/CypherWhereExpressionBuilderTests.cs similarity index 99% rename from Neo4jClient.Tests.Shared/Cypher/CypherWhereExpressionBuilderTests.cs rename to Neo4jClient.Tests/Cypher/CypherWhereExpressionBuilderTests.cs index 23543c2b6..96348e864 100644 --- a/Neo4jClient.Tests.Shared/Cypher/CypherWhereExpressionBuilderTests.cs +++ b/Neo4jClient.Tests/Cypher/CypherWhereExpressionBuilderTests.cs @@ -1,11 +1,10 @@ -using Xunit; -using Neo4jClient.Cypher; +using System; using System.Collections.Generic; -using System; using System.Linq.Expressions; -using Neo4jClient.Test.Fixtures; +using Neo4jClient.Cypher; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class CypherWhereExpressionBuilderTests : IClassFixture { @@ -686,7 +685,7 @@ public void GetsValueFromSuperNestedProperty() static string CreateParameter(IDictionary parameters, object paramValue) { - var paramName = string.Format("p{0}", parameters.Count); + var paramName = $"p{parameters.Count}"; parameters.Add(paramName, paramValue); return $"${paramName}"; } diff --git a/Neo4jClient.Tests/Cypher/DocumentationExamples.cs b/Neo4jClient.Tests/Cypher/DocumentationExamples.cs new file mode 100644 index 000000000..f633d311b --- /dev/null +++ b/Neo4jClient.Tests/Cypher/DocumentationExamples.cs @@ -0,0 +1,181 @@ +using System.Threading.Tasks; +using Neo4jClient.Cypher; + +// ReSharper disable ClassNeverInstantiated.Local +// ReSharper disable UnusedAutoPropertyAccessor.Local +// ReSharper disable UnusedVariable + +namespace Neo4jClient.Tests.Cypher +{ + public class DocumentationExamples + { + static IGraphClient BuildClient() + { + return null; + } + + public async Task Example2() + { + // ##start Cypher + // MATCH n + // WHERE n.Name = 'B' + // RETURN n + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = await client.Cypher + .Match("n") + .Where(n => n.Name == "B") + .Return(n => n.As()) + .ResultsAsync; + // ##end C# + } + + public async Task Example3() + { + // ##start Cypher + // MATCH n + // WHERE n.Name = 'B' + // RETURN n.Age + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = await client.Cypher + .Match("n") + .Where(n => n.Name == "B") + .Return(n => n.As().Age) + .ResultsAsync; + // ##end C# + } + + public async Task Example4() + { + // ##start Cypher + // MATCH a-->b + // WHERE a.Name = 'A' + // RETURN DISTINCT b + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = await client.Cypher + .Match("a-->b") + .Where(a => a.Name == "A") + .ReturnDistinct(b => b.As()) + .ResultsAsync; + // ##end C# + } + + public async Task Example5() + { + // ##start Cypher + // MATCH n + // RETURN n + // SKIP 1 + // LIMIT 2 + // ##end Cypher + + var client = BuildClient(); + + // ##start C# + var results = await client.Cypher + .Match("n") + .Return(n => n.As()) + .Skip(1) + .Limit(2) + .ResultsAsync; + // ##end C# + } + + public async Task 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 = await client.Cypher + .Match("david--otherPerson-->()") + .Where(david => david.Name == "David") + .With(otherPerson => new + { + otherPerson, + foaf = "count(*)" + }) + .Where(foaf => foaf > 1) + .Return(otherPerson => otherPerson.As()) + .ResultsAsync; + // ##end C# + } + + public async Task 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 = await client.Cypher + .Match("n") + .With("n") + .OrderByDescending("n.name") + .Limit(3) + .Return(n => n.CollectAs()) + .ResultsAsync; + // ##end C# + } + + public async Task 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 = await client.Cypher + .Match("n:Actor") + .Return(n => n.As().Name) + .UnionAll() + .Match("n:Movie") + .Return(n => new { + Name = n.As().Title + }) + .ResultsAsync; + // ##end C# + } + + class Person + { + public string Name { get; set; } + public int Age { get; set; } + } + + class Movie + { + public string Title { get; set; } + } + } +} diff --git a/Neo4jClient.Tests.Shared/Cypher/QueryWriterTests.cs b/Neo4jClient.Tests/Cypher/QueryWriterTests.cs similarity index 95% rename from Neo4jClient.Tests.Shared/Cypher/QueryWriterTests.cs rename to Neo4jClient.Tests/Cypher/QueryWriterTests.cs index e0c59cd2b..5553518a3 100644 --- a/Neo4jClient.Tests.Shared/Cypher/QueryWriterTests.cs +++ b/Neo4jClient.Tests/Cypher/QueryWriterTests.cs @@ -1,9 +1,8 @@ using System; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class QueryWriterTests : IClassFixture { @@ -55,7 +54,7 @@ public void AppendMultipleClauses() writer.AppendClause("bar"); var query = writer.ToCypherQuery(); - Assert.Equal("foo\r\nbar", query.QueryText); + Assert.Equal("foo" + Environment.NewLine + "bar", query.QueryText); Assert.Equal(0, query.QueryParameters.Count); } @@ -120,7 +119,7 @@ public void ToCypherQueryShouldNotLeakNewParamsIntoPreviouslyBuiltQuery() Assert.Equal(1, query1.QueryParameters.Count); Assert.Equal("bar", query1.QueryParameters["p0"]); - Assert.Equal("foo $p0\r\nbaz $p1", query2.QueryText); + Assert.Equal("foo $p0" + Environment.NewLine + "baz $p1", query2.QueryText); Assert.Equal(2, query2.QueryParameters.Count); Assert.Equal("bar", query2.QueryParameters["p0"]); Assert.Equal("qak", query2.QueryParameters["p1"]); diff --git a/Neo4jClient.Tests.Shared/Cypher/StartBitFormatterTests.cs b/Neo4jClient.Tests/Cypher/StartBitFormatterTests.cs similarity index 90% rename from Neo4jClient.Tests.Shared/Cypher/StartBitFormatterTests.cs rename to Neo4jClient.Tests/Cypher/StartBitFormatterTests.cs index f3425cca9..16f73aee2 100644 --- a/Neo4jClient.Tests.Shared/Cypher/StartBitFormatterTests.cs +++ b/Neo4jClient.Tests/Cypher/StartBitFormatterTests.cs @@ -2,11 +2,10 @@ using System.Collections.Generic; using System.Linq; using FluentAssertions; -using Xunit; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; +using Xunit; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class StartBitFormatterTests : IClassFixture @@ -51,18 +50,7 @@ public void EnumerableOfNodes() Assert.Equal(new[] {123L, 456L}, cypher.QueryParameters["p0"]); } - [Fact] - public void RootNodeReference() - { - var cypher = ToCypher(new - { - n1 = new RootNode(123) - }); - - Assert.Equal("n1=node($p0)", cypher.QueryText); - Assert.Equal(1, cypher.QueryParameters.Count); - Assert.Equal(123L, cypher.QueryParameters["p0"]); - } + [Fact] //[Description("http://docs.neo4j.org/chunked/2.0.0-M01/query-start.html#start-node-by-id")] @@ -161,19 +149,6 @@ public void EnumerableOfRelationshipReferences() Assert.Equal(new[] { 1L, 2L }, cypher.QueryParameters["p0"]); } - [Fact] - //[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.Equal("n=node(*)", cypher.QueryText); - Assert.Equal(0, cypher.QueryParameters.Count); - } - [Fact] public void CustomString() { @@ -258,7 +233,7 @@ public void Mixed() moreRels = new[] { relRef, relRef2 }, r2 = Relationship.ByIndexLookup("indexName", "property", "value"), r3 = Relationship.ByIndexQuery("indexName", "query"), - all = All.Nodes + }); const string expected = @@ -269,10 +244,9 @@ public void Mixed() "r1=relationship($p3), " + "moreRels=relationship($p4), " + "r2=relationship:`indexName`(property = $p5), " + - "r3=relationship:`indexName`($p6), " + - "all=node(*)"; + "r3=relationship:`indexName`($p6)"; - Assert.Equal(expected, cypher.QueryText); + Assert.Equal(expected, cypher.QueryText); Assert.Equal(7, cypher.QueryParameters.Count); Assert.Equal(2L, cypher.QueryParameters["p0"]); Assert.Equal("value", cypher.QueryParameters["p1"]); @@ -349,19 +323,20 @@ public void ArgumentExceptionForNullValueIncludesPropertyName() Assert.Contains("foo", ex.Message); } - static CypherQuery ToCypher(object startBits) + private static CypherQuery ToCypher(object startBits) { var parameters = new Dictionary(); - Func createParameter = value => + + string CreateParameter(object value) { var name = "p" + parameters.Count; parameters.Add(name, value); return $"${name}"; - }; + } - var cypherText = StartBitFormatter.FormatAsCypherText(startBits, createParameter); + var cypherText = StartBitFormatter.FormatAsCypherText(startBits, CreateParameter); - var query = new CypherQuery(cypherText, parameters, CypherResultMode.Projection, CypherResultFormat.Rest); + var query = new CypherQuery(cypherText, parameters, CypherResultMode.Projection, CypherResultFormat.Rest, "neo4j"); return query; } } diff --git a/Neo4jClient.Tests.Shared/Cypher/UnionTests.cs b/Neo4jClient.Tests/Cypher/UnionTests.cs similarity index 94% rename from Neo4jClient.Tests.Shared/Cypher/UnionTests.cs rename to Neo4jClient.Tests/Cypher/UnionTests.cs index 91fe5c8e5..179db3b2f 100644 --- a/Neo4jClient.Tests.Shared/Cypher/UnionTests.cs +++ b/Neo4jClient.Tests/Cypher/UnionTests.cs @@ -1,10 +1,9 @@ using System.Linq; +using Neo4jClient.Cypher; using NSubstitute; using Xunit; -using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test.Cypher +namespace Neo4jClient.Tests.Cypher { public class UnionTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/Domain/Product.cs b/Neo4jClient.Tests/Domain/Product.cs similarity index 76% rename from Neo4jClient.Tests.Shared/Domain/Product.cs rename to Neo4jClient.Tests/Domain/Product.cs index dd8adeb0b..3beeaaca9 100644 --- a/Neo4jClient.Tests.Shared/Domain/Product.cs +++ b/Neo4jClient.Tests/Domain/Product.cs @@ -1,4 +1,4 @@ -namespace Neo4jClient.Test.Domain +namespace Neo4jClient.Tests.Domain { public class Product { diff --git a/Neo4jClient.Tests.Shared/Domain/StorageLocation.cs b/Neo4jClient.Tests/Domain/StorageLocation.cs similarity index 70% rename from Neo4jClient.Tests.Shared/Domain/StorageLocation.cs rename to Neo4jClient.Tests/Domain/StorageLocation.cs index 648a52738..0b864e544 100644 --- a/Neo4jClient.Tests.Shared/Domain/StorageLocation.cs +++ b/Neo4jClient.Tests/Domain/StorageLocation.cs @@ -1,4 +1,4 @@ -namespace Neo4jClient.Test.Domain +namespace Neo4jClient.Tests.Domain { public class StorageLocation { diff --git a/Neo4jClient.Tests.Shared/Domain/User.cs b/Neo4jClient.Tests/Domain/User.cs similarity index 67% rename from Neo4jClient.Tests.Shared/Domain/User.cs rename to Neo4jClient.Tests/Domain/User.cs index b8cc8a0f8..103f51e26 100644 --- a/Neo4jClient.Tests.Shared/Domain/User.cs +++ b/Neo4jClient.Tests/Domain/User.cs @@ -1,4 +1,4 @@ -namespace Neo4jClient.Test.Domain +namespace Neo4jClient.Tests.Domain { public class Part { diff --git a/Neo4jClient.Tests.Shared/Extensions/MemberInfoExtensionsTests.cs b/Neo4jClient.Tests/Extensions/MemberInfoExtensionsTests.cs similarity index 93% rename from Neo4jClient.Tests.Shared/Extensions/MemberInfoExtensionsTests.cs rename to Neo4jClient.Tests/Extensions/MemberInfoExtensionsTests.cs index 9b76dce5a..bbf9c8f02 100644 --- a/Neo4jClient.Tests.Shared/Extensions/MemberInfoExtensionsTests.cs +++ b/Neo4jClient.Tests/Extensions/MemberInfoExtensionsTests.cs @@ -1,13 +1,10 @@ using System; using System.Linq; -using System.Linq.Expressions; -using System.Reflection; using Neo4jClient.Extensions; -using Neo4jClient.Test.Fixtures; using Newtonsoft.Json; using Xunit; -namespace Neo4jClient.Test.Extensions +namespace Neo4jClient.Tests.Extensions { public class MemberInfoExtensionsTests diff --git a/Neo4jClient.Tests.Shared/Extensions/Neo4jDriverExtensionsTests.cs b/Neo4jClient.Tests/Extensions/Neo4jDriverExtensionsTests.cs similarity index 99% rename from Neo4jClient.Tests.Shared/Extensions/Neo4jDriverExtensionsTests.cs rename to Neo4jClient.Tests/Extensions/Neo4jDriverExtensionsTests.cs index 2f80b4491..6ebf02eaf 100644 --- a/Neo4jClient.Tests.Shared/Extensions/Neo4jDriverExtensionsTests.cs +++ b/Neo4jClient.Tests/Extensions/Neo4jDriverExtensionsTests.cs @@ -5,11 +5,10 @@ using FluentAssertions; using Moq; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using Newtonsoft.Json; using Xunit; -namespace Neo4jClient.Test.Extensions +namespace Neo4jClient.Tests.Extensions { internal class ClassWithGuid { diff --git a/Neo4jClient.Tests.Shared/Extensions/ObjectExtensionTests.cs b/Neo4jClient.Tests/Extensions/ObjectExtensionTests.cs similarity index 94% rename from Neo4jClient.Tests.Shared/Extensions/ObjectExtensionTests.cs rename to Neo4jClient.Tests/Extensions/ObjectExtensionTests.cs index b027392f9..f468bffe6 100644 --- a/Neo4jClient.Tests.Shared/Extensions/ObjectExtensionTests.cs +++ b/Neo4jClient.Tests/Extensions/ObjectExtensionTests.cs @@ -1,10 +1,9 @@ using System.Collections.Generic; using FluentAssertions; using Neo4jClient.Extensions; -using Neo4jClient.Test.Fixtures; using Xunit; -namespace Neo4jClient.Test.Extensions +namespace Neo4jClient.Tests.Extensions { public class ObjectExtensionTests { diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/ConnectAsyncTests.cs b/Neo4jClient.Tests/GraphClientTests/ConnectAsyncTests.cs similarity index 78% rename from Neo4jClient.Tests.Shared/GraphClientTests/ConnectAsyncTests.cs rename to Neo4jClient.Tests/GraphClientTests/ConnectAsyncTests.cs index 9dcd7b058..d6c11e013 100644 --- a/Neo4jClient.Tests.Shared/GraphClientTests/ConnectAsyncTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/ConnectAsyncTests.cs @@ -3,13 +3,13 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; +using FluentAssertions; using Neo4jClient.Execution; -using Neo4jClient.Test.Fixtures; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; -namespace Neo4jClient.Test.GraphClientTests +namespace Neo4jClient.Tests.GraphClientTests { public class ConnectAsyncTests : IClassFixture @@ -102,7 +102,7 @@ public async Task PassesCorrectStreamHeader_WhenUseStreamIsTrue() [Fact] - public void ShouldFireOnCompletedEvenWhenException() + public async Task ShouldFireOnCompletedEvenWhenException() { var httpClient = Substitute.For(); httpClient @@ -128,7 +128,7 @@ public async Task ShouldParseRootApiResponseFromAuthenticatedConnection() { using (var testHarness = new RestTestHarness { - {MockRequest.Get(""), MockResponse.NeoRoot()} + {MockRequest.Get(""), MockResponse.NeoRoot20()} }) { var httpClient = testHarness.GenerateHttpClient("http://foo/db/data"); @@ -181,5 +181,44 @@ public async Task ShouldSendCustomUserAgent() // Act await graphClient.ConnectAsync(); } + + [Fact] + public async Task ShouldParseRootApiResponseFromA4xServer() + { + // Arrange + var httpClient = Substitute.For(); + var graphClient = new GraphClient(new Uri("http://localhost:7474/"), httpClient); + + httpClient + .SendAsync(Arg.Any()) + .Returns(ci => + { + var response = new HttpResponseMessage(HttpStatusCode.OK) + { + Content = new StringContent(@"{ + 'bolt_direct': 'neo4j://localhost:7687/bolt', + 'bolt_routing': 'neo4j://localhost:7687/route', + 'cluster': 'http://localhost:7474/db/{databaseName}/cluster', + 'transaction': 'http://localhost:7474/db/{databaseName}/tx', + 'neo4j_version': '4.0.0', + 'neo4j_edition': 'enterprise' + }") + }; + var task = new Task(() => response); + task.Start(); + return task; + }); + + // Act + await graphClient.ConnectAsync(); + + graphClient.RootApiResponse.BoltDirect.Should().Be("neo4j://localhost:7687/bolt"); + graphClient.RootApiResponse.BoltRouting.Should().Be("neo4j://localhost:7687/route"); + + graphClient.RootApiResponse.Cluster.Should().Be("/db/{databaseName}/cluster"); + graphClient.RootApiResponse.Transaction.Should().Be("/db/{databaseName}/tx"); + graphClient.RootApiResponse.Neo4jVersion.Should().Be("4.0.0"); + graphClient.RootApiResponse.Neo4jEdition.Should().Be("enterprise"); + } } } \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/ConnectTests.cs b/Neo4jClient.Tests/GraphClientTests/ConnectTests.cs similarity index 72% rename from Neo4jClient.Tests.Shared/GraphClientTests/ConnectTests.cs rename to Neo4jClient.Tests/GraphClientTests/ConnectTests.cs index 9d7a3a94f..1e4866689 100644 --- a/Neo4jClient.Tests.Shared/GraphClientTests/ConnectTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/ConnectTests.cs @@ -5,20 +5,20 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using Neo4jClient.ApiModels; using Neo4jClient.Cypher; using Neo4jClient.Execution; -using Neo4jClient.Test.Fixtures; using NSubstitute; using NSubstitute.ExceptionExtensions; using Xunit; -namespace Neo4jClient.Test.GraphClientTests +namespace Neo4jClient.Tests.GraphClientTests { public class ConnectTests : IClassFixture { [Fact] - public void ShouldThrowConnectionExceptionFor500Response() + public async Task ShouldThrowConnectionExceptionFor500Response() { using (var testHarness = new RestTestHarness { @@ -28,17 +28,17 @@ public void ShouldThrowConnectionExceptionFor500Response() } }) { - var ex = Assert.Throws(() => testHarness.CreateAndConnectGraphClient()); + var ex = await Assert.ThrowsAsync(async () => await testHarness.CreateAndConnectGraphClient()); Assert.Equal("Received an unexpected HTTP status when executing the request.\r\n\r\nThe response status was: 500 InternalServerError", ex.Message); } } [Fact] - public void ShouldRetrieveApiEndpoints() + public async Task ShouldRetrieveApiEndpoints() { using (var testHarness = new RestTestHarness()) { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); + var graphClient = (GraphClient)await testHarness.CreateAndConnectGraphClient(); Assert.Equal("/node", graphClient.RootApiResponse.Node); Assert.Equal("/index/node", graphClient.RootApiResponse.NodeIndex); Assert.Equal("/index/relationship", graphClient.RootApiResponse.RelationshipIndex); @@ -48,85 +48,38 @@ public void ShouldRetrieveApiEndpoints() } [Fact] - - public void RootNode_ShouldThrowInvalidOperationException_WhenNotConnectedYet() + public async Task ShouldParse15M02Version() { - var graphClient = new GraphClient(new Uri("http://foo/db/data"), null); - - var ex = Assert.Throws(() => graphClient.RootNode.ToString()); - Assert.Equal("The graph client is not connected to the server. Call the Connect method first.", ex.Message); - } - - [Fact] - public void RootNode_ShouldReturnReferenceNode() - { - using (var testHarness = new RestTestHarness()) - { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - Assert.NotNull(graphClient.RootNode); - Assert.Equal(123, graphClient.RootNode.Id); - } - } - - [Fact] - 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' : { - } - }") - } - }) + RootApiResponse rar = new RootApiResponse { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - Assert.Null(graphClient.RootNode); - } - } - - [Fact] - public void ShouldParse15M02Version() - { - using (var testHarness = new RestTestHarness()) - { - var graphClient = (GraphClient)testHarness.CreateAndConnectGraphClient(); - - Assert.Equal("1.5.0.2", graphClient.RootApiResponse.Version.ToString()); - } + Neo4jVersion = "1.5.M02" + }; + + Assert.Equal("1.5.0.2", rar.Version.ToString()); } [Fact] - public void ShouldReturnCypher19CapabilitiesForPre20Version() + public async Task ShouldReturnCypher19CapabilitiesForPre20Version() { using (var testHarness = new RestTestHarness()) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); Assert.Equal(CypherCapabilities.Cypher19, graphClient.CypherCapabilities); } } [Fact] - public void ShouldSetCypher22CapabilitiesForPost22Version() + public async Task ShouldSetCypher22CapabilitiesForPost22Version() { using (var testHarness = new RestTestHarness()) { - var graphClient = testHarness.CreateAndConnectGraphClient(RestTestHarness.Neo4jVersion.Neo22); + var graphClient = await testHarness.CreateAndConnectGraphClient(RestTestHarness.Neo4jVersion.Neo22); Assert.Equal(CypherCapabilities.Cypher22, graphClient.CypherCapabilities); } } [Fact] - public void ShouldReturnCypher19CapabilitiesForVersion20() + public async Task ShouldReturnCypher19CapabilitiesForVersion20() { using (var testHarness = new RestTestHarness { @@ -136,7 +89,7 @@ public void ShouldReturnCypher19CapabilitiesForVersion20() } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); Assert.Equal(CypherCapabilities.Cypher20, graphClient.CypherCapabilities); } } @@ -150,7 +103,7 @@ public void UserInfoPreservedInRootUri() } [Fact] - public void CredentialsPreservedAllTheWayThroughToHttpStack() + public async Task CredentialsPreservedAllTheWayThroughToHttpStack() { var httpClient = Substitute.For(); httpClient @@ -161,7 +114,7 @@ public void CredentialsPreservedAllTheWayThroughToHttpStack() try { - graphClient.Connect(); + await graphClient.ConnectAsync(); } // ReSharper disable EmptyGeneralCatchClause catch (NotImplementedException) @@ -179,7 +132,7 @@ public void CredentialsPreservedAllTheWayThroughToHttpStack() } [Fact] - public void PassesCorrectStreamHeader_WhenUseStreamIsTrue() + public async Task PassesCorrectStreamHeader_WhenUseStreamIsTrue() { var httpClient = Substitute.For(); httpClient @@ -190,7 +143,7 @@ public void PassesCorrectStreamHeader_WhenUseStreamIsTrue() try { - graphClient.Connect(); + await graphClient.ConnectAsync(); } // ReSharper disable EmptyGeneralCatchClause catch (NotImplementedException) @@ -208,7 +161,7 @@ public void PassesCorrectStreamHeader_WhenUseStreamIsTrue() } [Fact] - public void PassesCorrectStreamHeader_WhenUseStreamIsFalse() + public async Task PassesCorrectStreamHeader_WhenUseStreamIsFalse() { var httpClient = Substitute.For(); httpClient @@ -219,7 +172,7 @@ public void PassesCorrectStreamHeader_WhenUseStreamIsFalse() graphClient.ExecutionConfiguration.UseJsonStreaming = false; try { - graphClient.Connect(); + await graphClient.ConnectAsync(); } // ReSharper disable EmptyGeneralCatchClause catch (NotImplementedException) @@ -237,22 +190,22 @@ public void PassesCorrectStreamHeader_WhenUseStreamIsFalse() [Fact] - public void ShouldParseRootApiResponseFromAuthenticatedConnection() + public async Task ShouldParseRootApiResponseFromAuthenticatedConnection() { using (var testHarness = new RestTestHarness() { - { MockRequest.Get(""), MockResponse.NeoRoot() } + { MockRequest.Get(""), MockResponse.NeoRoot20() } }) { var httpClient = testHarness.GenerateHttpClient("http://foo/db/data"); var graphClient = new GraphClient(new Uri("http://username:password@foo/db/data"), httpClient); - graphClient.Connect(); + await graphClient.ConnectAsync(); Assert.Equal("/node", graphClient.RootApiResponse.Node); } } [Fact] - public void ShouldSendCustomUserAgent() + public async Task ShouldSendCustomUserAgent() { // Arrange var httpClient = Substitute.For(); @@ -291,7 +244,7 @@ public void ShouldSendCustomUserAgent() }); // Act - graphClient.Connect(); + await graphClient.ConnectAsync(); } [Fact] @@ -316,7 +269,7 @@ public void ShouldFormatAuthorisationHeaderCorrectly() } [Fact] - public void ShouldFireOnCompletedEvenWhenException() + public async Task ShouldFireOnCompletedEvenWhenException() { var httpClient = Substitute.For(); httpClient @@ -332,7 +285,7 @@ public void ShouldFireOnCompletedEvenWhenException() }; // act - Assert.Throws(() => graphClient.Connect()); + await Assert.ThrowsAsync(async () => await graphClient.ConnectAsync()); Assert.NotNull(operationCompletedArgs); Assert.True(operationCompletedArgs.HasException); diff --git a/Neo4jClient.Tests/GraphClientTests/CreateIndexTests.cs b/Neo4jClient.Tests/GraphClientTests/CreateIndexTests.cs new file mode 100644 index 000000000..bfe4ddb39 --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/CreateIndexTests.cs @@ -0,0 +1,138 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +namespace Neo4jClient.Tests.GraphClientTests +{ + + // public class CreateIndexTests : IClassFixture + // { + // [Theory] + // [InlineData( + // IndexFor.Node, + // IndexProvider.lucene, + // IndexType.fulltext, + // "/index/node", + // @"{ + // 'name': 'foo', + // 'config': { 'type': 'fulltext', 'provider': 'lucene' } + // }")] + // [InlineData( + // IndexFor.Node, + // IndexProvider.lucene, + // IndexType.exact, + // "/index/node", + // @"{ + // 'name': 'foo', + // 'config': { 'type': 'exact', 'provider': 'lucene' } + // }")] + // [InlineData( + // IndexFor.Relationship, + // IndexProvider.lucene, + // IndexType.fulltext, + // "/index/relationship", + // @"{ + // 'name': 'foo', + // 'config': { 'type': 'fulltext', 'provider': 'lucene' } + // }")] + // [InlineData( + // IndexFor.Relationship, + // IndexProvider.lucene, + // IndexType.exact, + // "/index/relationship", + // @"{ + // 'name': 'foo', + // 'config': { 'type': 'exact', 'provider': 'lucene' } + // }")] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // + // var indexConfiguration = new IndexConfiguration + // { + // Provider = indexProvider, + // Type = indexType + // }; + // await graphClient.CreateIndexAsync("foo", indexConfiguration, indexFor); + // } + // } + // + // [Theory] + // [InlineData( + // IndexFor.Node, + // IndexProvider.lucene, + // IndexType.fulltext, + // "/index/node", + // @"{ + // 'name': 'foo', + // 'config': { 'type': 'fulltext', 'provider': 'lucene' } + // }")] + // [InlineData( + // IndexFor.Node, + // IndexProvider.lucene, + // IndexType.exact, + // "/index/node", + // @"{ + // 'name': 'foo', + // 'config': { 'type': 'exact', 'provider': 'lucene' } + // }")] + // [InlineData( + // IndexFor.Relationship, + // IndexProvider.lucene, + // IndexType.fulltext, + // "/index/relationship", + // @"{ + // 'name': 'foo', + // 'config': { 'type': 'fulltext', 'provider': 'lucene' } + // }")] + // [InlineData( + // IndexFor.Relationship, + // IndexProvider.lucene, + // IndexType.exact, + // "/index/relationship", + // @"{ + // 'name': 'foo', + // 'config': { 'type': 'exact', 'provider': 'lucene' } + // }")] + // public async Task ShouldThrowExceptionIfHttpCodeIsNot201( + // 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 = await testHarness.CreateAndConnectGraphClient(); + // + // var indexConfiguration = new IndexConfiguration + // { + // Provider = indexProvider, + // Type = indexType + // }; + // await Assert.ThrowsAsync(async () => await graphClient.CreateIndexAsync("foo", indexConfiguration, indexFor)); + // } + // } + // } +} \ No newline at end of file diff --git a/Neo4jClient.Tests/GraphClientTests/CreateNodeTests.cs b/Neo4jClient.Tests/GraphClientTests/CreateNodeTests.cs new file mode 100644 index 000000000..76e526540 --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/CreateNodeTests.cs @@ -0,0 +1,620 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using FluentAssertions; +using Neo4jClient.ApiModels; +using Xunit; + +namespace Neo4jClient.Tests.GraphClientTests +{ + + public class CreateNodeTests : IClassFixture + { + // [Fact] + // public async Task ShouldThrowArgumentNullExceptionForNullNode() + // { + // var client = new GraphClient(new Uri("http://foo")); + // await Assert.ThrowsAsync(async () => await client.CreateAsync(null)); + // } + // + // [Fact] + // public async Task ShouldThrowInvalidOperationExceptionIfNotConnected() + // { + // var client = new GraphClient(new Uri("http://foo")); + // await Assert.ThrowsAsync(() => client.CreateAsync(new object())); + // } + // + // [Fact] + // public async Task 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"}; + // await Assert.ThrowsAsync(() => graphClient.CreateAsync(testNode)); + // } + // + // [Fact] + // public async Task ShouldThrowArgumentExceptionForPreemptivelyWrappedNode() + // { + // var graphClient = new GraphClient(new Uri("http://foo/db/data"), null); + // var ex = await Assert.ThrowsAsync(() => graphClient.CreateAsync((Node)null)); + // ex.Message.Should().Be($"You're trying to pass in a Node instance. Just pass the TestNode instance instead.{Environment.NewLine}Parameter name: node"); + // } + // + // [Fact] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // var ex = await Assert.ThrowsAsync(async () => await graphClient.CreateAsync(new NestedTestNode() + // { + // Foo = "foo", + // TestNode2 = new TestNode2() {Bar = "bar", Foo = "foo"} + // })); + // ex.Message.Should().Be("PropertyValueException: Could not set property \"TestNode2\", unsupported type: {Foo=foo, Bar=bar}"); + // } + // + // [Fact] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // + // await graphClient.CreateAsync(testNode, null, null); + // + // testHarness.AssertRequestConstraintsAreMet(); + // } + // + // [Fact] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // + // await graphClient.CreateAsync(new TestNode { Foo = "foo", Bar = "bar", Baz = "baz" }); + // + // testHarness.AssertRequestConstraintsAreMet(); + // } + // + // [Fact] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // + // await graphClient.CreateAsync(new TestNode { Foo = "foo東京", Bar = "bar", Baz = "baz" }); + // + // testHarness.AssertRequestConstraintsAreMet(); + // } + // + // [Fact] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // + // var node = await graphClient.CreateAsync(testNode); + // + // Assert.Equal(760, node.Id); + // testHarness.AssertRequestConstraintsAreMet(); + // } + // + // [Fact] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // + // var node = await graphClient.CreateAsync(testNode); + // + // Assert.Equal(2157483647, node.Id); + // testHarness.AssertRequestConstraintsAreMet(); + // } + // + // [Fact] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // + // var node = await graphClient.CreateAsync(testNode); + // + // Assert.NotNull(((IAttachedReference)node).Client); + // } + // + // [Fact] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // + // await graphClient.CreateAsync( + // testNode, + // new TestRelationship(789, testRelationshipPayload)); + // + // testHarness.AssertRequestConstraintsAreMet(); + // } + // + // [Fact] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // + // await graphClient.CreateAsync( + // testNode, + // null, + // new[] + // { + // new IndexEntry + // { + // Name = "my_index", + // KeyValues = new[] + // { + // new KeyValuePair("key", "value"), + // new KeyValuePair("key2", ""), + // new KeyValuePair("key3", "value3") + // } + // } + // }); + // } + // + // [Fact] + // public async Task 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 = await testHarness.CreateAndConnectGraphClient(); + // + // await graphClient.CreateAsync( + // 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/Neo4jClient.Tests/GraphClientTests/CreateRelationshipTests.cs b/Neo4jClient.Tests/GraphClientTests/CreateRelationshipTests.cs new file mode 100644 index 000000000..047c32376 --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/CreateRelationshipTests.cs @@ -0,0 +1,132 @@ +// using System; +// using System.Net; +// using System.Threading.Tasks; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class CreateRelationshipTests : IClassFixture +// { +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// var testRelationship = new TestRelationship(81); +// var relationshipReference = await graphClient.CreateRelationshipAsync(new NodeReference(81), testRelationship); +// +// Assert.IsAssignableFrom(relationshipReference); +// Assert.IsNotType>(relationshipReference); +// Assert.Equal(38, relationshipReference.Id); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// var testRelationship = new TestRelationship(81); +// var relationshipReference = await graphClient.CreateRelationshipAsync(new NodeReference(81), testRelationship); +// +// Assert.Equal(graphClient, ((IAttachedReference)relationshipReference).Client); +// } +// } +// +// [Fact] +// public async Task ShouldThrowArgumentNullExceptionForNullNodeReference() +// { +// var client = new GraphClient(new Uri("http://foo")); +// await Assert.ThrowsAsync(async() => await client.CreateRelationshipAsync((NodeReference)null, new TestRelationship(10))); +// } +// +// [Fact] +// public async Task ShouldThrowInvalidOperationExceptionIfNotConnected() +// { +// var client = new GraphClient(new Uri("http://foo")); +// await Assert.ThrowsAsync(async () => await client.CreateRelationshipAsync(new NodeReference(5), new TestRelationship(10))); +// } +// +// [Fact] +// public async Task ShouldThrowNotSupportedExceptionForIncomingRelationship() +// { +// using (var testHarness = new RestTestHarness()) +// { +// var client = await testHarness.CreateAndConnectGraphClient(); +// await Assert.ThrowsAsync(async () => await client.CreateRelationshipAsync(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/Neo4jClient.Tests.Shared/GraphClientTests/Cypher/ExecuteCypherTests.cs b/Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteCypherTests.cs similarity index 57% rename from Neo4jClient.Tests.Shared/GraphClientTests/Cypher/ExecuteCypherTests.cs rename to Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteCypherTests.cs index 39c4a2c17..03ad46fa5 100644 --- a/Neo4jClient.Tests.Shared/GraphClientTests/Cypher/ExecuteCypherTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteCypherTests.cs @@ -5,45 +5,53 @@ using System.Linq; using System.Net; using System.Net.Http; -using Xunit; +using System.Threading.Tasks; +using FluentAssertions; +using Neo4j.Driver; using Neo4jClient.ApiModels.Cypher; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.GraphClientTests.Cypher +namespace Neo4jClient.Tests.GraphClientTests.Cypher { - public class ExecuteCypherTests : IClassFixture { + internal static MockResponse EmptyErrorResponse = MockResponse.Json(200, + @"{'results':[], 'errors':[{ + 'code': 'Error 1', + 'message': 'Unable to do Cypher' + }] }"); + /// - /// When executing cypher queries when no parameters are needed, the REST interface doesn't care if we don't send parameters. + /// When executing cypher queries when no parameters are needed, the REST interface doesn't care if we don't send + /// parameters. /// [Fact] - public void SendingNullParametersShouldNotRaiseExceptionWhenExecutingCypher() + public async Task SendingNullParametersShouldNotRaiseExceptionWhenExecutingCypher() { const string queryText = @"MATCH (d) RETURN d"; - - var cypherQuery = new CypherQuery(queryText, null, CypherResultMode.Set, CypherResultFormat.Rest); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + + var cypherQuery = new CypherQuery(queryText, null, CypherResultMode.Set, CypherResultFormat.Rest, "neo4j"); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Http((int)HttpStatusCode.OK) + MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), + MockResponse.Http((int) HttpStatusCode.OK) } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); // execute cypher with "null" parameters - graphClient.ExecuteCypher(cypherQuery); + await graphClient.ExecuteCypherAsync(cypherQuery); } } [Fact] - public void ShouldSendCommandAndNotCareAboutResults() + public async Task ShouldSendCommandAndNotCareAboutResults() { // Arrange const string queryText = @"START d=node($p0), e=node($p1) CREATE UNIQUE d-[:foo]->e"; @@ -53,45 +61,45 @@ public void ShouldSendCommandAndNotCareAboutResults() {"p1", 219} }; - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, CypherResultFormat.Rest); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, CypherResultFormat.Rest, "neo4j"); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Http((int)HttpStatusCode.OK) + MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), + MockResponse.Http((int) HttpStatusCode.OK) } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); //Act - graphClient.ExecuteCypher(cypherQuery); + await graphClient.ExecuteCypherAsync(cypherQuery); } } [Fact] - public void ShouldSendCommandAndNotCareAboutResultsAsync() + public async Task ShouldSendCommandAndNotCareAboutResultsAsync() { // Arrange const string queryText = @"return 1"; var parameters = new Dictionary(); - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, "neo4j"); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Http((int)HttpStatusCode.OK) + MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), + MockResponse.Http((int) HttpStatusCode.OK) } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); - bool raisedEvent = false; + var raisedEvent = false; graphClient.OperationCompleted += (sender, e) => { raisedEvent = true; }; @@ -103,80 +111,62 @@ public void ShouldSendCommandAndNotCareAboutResultsAsync() } } - /// - /// This predates #106. Given Tatham's guidance that the event should fire irrespective is this test proving correct behaviour? - /// In any case the sync method calls async so it might be hard to avoid double firing an event. - /// [Fact] - public void WhenAsyncCommandFails_ShouldNotRaiseCompleted() + public async Task ThrowsClientException_WhenGettingErrorResponse() { - // Arrange const string queryText = @"return 1"; var parameters = new Dictionary(); - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, "neo4j"); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), - MockResponse.Throws() + MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), + EmptyErrorResponse } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); - - bool raisedEvent = false; - - graphClient.OperationCompleted += (sender, e) => { raisedEvent = true; }; + var graphClient = await testHarness.CreateAndConnectGraphClient(); //Act - var task = graphClient.ExecuteCypherAsync(cypherQuery) - .ContinueWith(t => - { - Assert.True(t.IsFaulted); - Assert.IsAssignableFrom(t.Exception.Flatten().InnerException); - }); - task.Wait(); - - Assert.False(raisedEvent, "Raised OperationCompleted"); + var ex = await Assert.ThrowsAsync(async () => await graphClient.ExecuteCypherAsync(cypherQuery)); + ex.Code.Should().Be("Error 1"); + ex.Message.Should().Be("Unable to do Cypher"); } } /// - /// #106 + /// #106 /// [Fact] - public void WhenExecuteGetCypherResultsFails_ShouldRaiseCompletedWithException() + public async Task WhenExecuteGetCypherResultsFails_ShouldRaiseCompletedWithException() { // Arrange const string queryText = @"return 1"; var parameters = new Dictionary(); - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, "neo4j"); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), MockResponse.Throws() } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); OperationCompletedEventArgs eventArgs = null; graphClient.OperationCompleted += (sender, e) => { eventArgs = e; }; //Act - Assert.Throws(() => - { - graphClient.ExecuteGetCypherResults(cypherQuery); - }); - + await Assert.ThrowsAsync(async () => { await graphClient.ExecuteGetCypherResultsAsync(cypherQuery); }); + Assert.NotNull(eventArgs); Assert.True(eventArgs.HasException); Assert.Equal(typeof(MockResponseThrowsException), eventArgs.Exception.GetType()); @@ -185,34 +175,34 @@ public void WhenExecuteGetCypherResultsFails_ShouldRaiseCompletedWithException() } /// - /// #106 + /// #106 /// [Fact] - public void WhenExecuteCypherFails_ShouldRaiseCompletedWithException() + public async Task WhenExecuteCypherFails_ShouldRaiseCompletedWithException() { // Arrange const string queryText = @"bad cypher"; var parameters = new Dictionary(); - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, "neo4j"); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), MockResponse.Throws() } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); OperationCompletedEventArgs eventArgs = null; graphClient.OperationCompleted += (sender, e) => { eventArgs = e; }; //Act - Assert.Throws(() => { graphClient.ExecuteCypher(cypherQuery); }); + await Assert.ThrowsAsync(async () => { await graphClient.ExecuteCypherAsync(cypherQuery); }); Assert.NotNull(eventArgs); Assert.True(eventArgs.HasException); @@ -222,84 +212,84 @@ public void WhenExecuteCypherFails_ShouldRaiseCompletedWithException() } /// - /// #75 + /// #75 /// [Fact] - public void SendsCommandWithCorrectTimeout() + public async Task SendsCommandWithCorrectTimeout() { const string queryText = "MATCH n SET n.Value = 'value'"; const int expectedMaxExecutionTime = 100; - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set,CypherResultFormat.DependsOnEnvironment , maxExecutionTime: expectedMaxExecutionTime); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment, "neo4j", maxExecutionTime: expectedMaxExecutionTime); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; using (var testHarness = new RestTestHarness { { MockRequest.Get(""), - MockResponse.NeoRoot() + MockResponse.NeoRoot20() }, { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), MockResponse.Http((int) HttpStatusCode.OK) } }) { var httpClient = testHarness.GenerateHttpClient(testHarness.BaseUri); var graphClient = new GraphClient(new Uri(testHarness.BaseUri), httpClient); - graphClient.Connect(); + await graphClient.ConnectAsync(); httpClient.ClearReceivedCalls(); - ((IRawGraphClient)graphClient).ExecuteCypher(cypherQuery); + await ((IRawGraphClient) graphClient).ExecuteCypherAsync(cypherQuery); var call = httpClient.ReceivedCalls().Single(); - var requestMessage = (HttpRequestMessage)call.GetArguments()[0]; + var requestMessage = (HttpRequestMessage) call.GetArguments()[0]; var maxExecutionTimeHeader = requestMessage.Headers.Single(h => h.Key == "max-execution-time"); Assert.Equal(expectedMaxExecutionTime.ToString(CultureInfo.InvariantCulture), maxExecutionTimeHeader.Value.Single()); } } /// - /// #75 + /// #75 /// [Fact] - public void DoesntSetMaxExecutionTime_WhenNotSet() + public async Task DoesntSetMaxExecutionTime_WhenNotSet() { const string queryText = "MATCH n SET n.Value = 'value'"; - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set, "neo4j"); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; using (var testHarness = new RestTestHarness { { MockRequest.Get(""), - MockResponse.NeoRoot() + MockResponse.NeoRoot20() }, { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), MockResponse.Http((int) HttpStatusCode.OK) } }) { var httpClient = testHarness.GenerateHttpClient(testHarness.BaseUri); var graphClient = new GraphClient(new Uri(testHarness.BaseUri), httpClient); - graphClient.Connect(); + await graphClient.ConnectAsync(); httpClient.ClearReceivedCalls(); - ((IRawGraphClient)graphClient).ExecuteCypher(cypherQuery); + await ((IRawGraphClient) graphClient).ExecuteCypherAsync(cypherQuery); var call = httpClient.ReceivedCalls().Single(); - var requestMessage = (HttpRequestMessage)call.GetArguments()[0]; + var requestMessage = (HttpRequestMessage) call.GetArguments()[0]; Assert.False(requestMessage.Headers.Any(h => h.Key == "max-execution-time")); } } /// - /// #141 + /// #141 /// [Fact] - public void SendsCommandWithCustomHeaders() + public async Task SendsCommandWithCustomHeaders() { const string queryText = "MATCH n SET n.Value = 'value'"; const int expectedMaxExecutionTime = 100; @@ -308,31 +298,30 @@ public void SendsCommandWithCustomHeaders() var customHeaders = new NameValueCollection(); customHeaders.Add(headerName, headerValue); - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment, maxExecutionTime: expectedMaxExecutionTime, customHeaders: customHeaders); - - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment, "neo4j", maxExecutionTime: expectedMaxExecutionTime, customHeaders: customHeaders); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; using (var testHarness = new RestTestHarness { { MockRequest.Get(""), - MockResponse.NeoRoot() + MockResponse.NeoRoot20() }, { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), MockResponse.Http((int) HttpStatusCode.OK) } }) { var httpClient = testHarness.GenerateHttpClient(testHarness.BaseUri); var graphClient = new GraphClient(new Uri(testHarness.BaseUri), httpClient); - graphClient.Connect(); + await graphClient.ConnectAsync(); httpClient.ClearReceivedCalls(); - ((IRawGraphClient)graphClient).ExecuteCypher(cypherQuery); + await ((IRawGraphClient) graphClient).ExecuteCypherAsync(cypherQuery); var call = httpClient.ReceivedCalls().Single(); - var requestMessage = (HttpRequestMessage)call.GetArguments()[0]; + var requestMessage = (HttpRequestMessage) call.GetArguments()[0]; var maxExecutionTimeHeader = requestMessage.Headers.Single(h => h.Key == "max-execution-time"); Assert.Equal(expectedMaxExecutionTime.ToString(CultureInfo.InvariantCulture), maxExecutionTimeHeader.Value.Single()); var customHeader = requestMessage.Headers.Single(h => h.Key == headerName); @@ -343,39 +332,41 @@ public void SendsCommandWithCustomHeaders() /// - /// #141 + /// #141 /// [Fact] - public void DoesntSetHeaders_WhenNotSet() + public async Task DoesntSetHeaders_WhenNotSet() { const string queryText = "MATCH n SET n.Value = 'value'"; - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set, "neo4j"); + + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; + using (var testHarness = new RestTestHarness { { MockRequest.Get(""), - MockResponse.NeoRoot() + MockResponse.NeoRoot20() }, { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), MockResponse.Http((int) HttpStatusCode.OK) } }) { var httpClient = testHarness.GenerateHttpClient(testHarness.BaseUri); var graphClient = new GraphClient(new Uri(testHarness.BaseUri), httpClient); - graphClient.Connect(); + await graphClient.ConnectAsync(); httpClient.ClearReceivedCalls(); - ((IRawGraphClient)graphClient).ExecuteCypher(cypherQuery); + await ((IRawGraphClient) graphClient).ExecuteCypherAsync(cypherQuery); var call = httpClient.ReceivedCalls().Single(); - var requestMessage = (HttpRequestMessage)call.GetArguments()[0]; + var requestMessage = (HttpRequestMessage) call.GetArguments()[0]; Assert.False(requestMessage.Headers.Any(h => h.Key == "max-execution-time")); } } } -} +} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs b/Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs similarity index 86% rename from Neo4jClient.Tests.Shared/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs rename to Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs index 6f5fe57ac..197bd2d66 100644 --- a/Neo4jClient.Tests.Shared/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/Cypher/ExecuteGetCypherResultsTests.cs @@ -4,13 +4,15 @@ using System.Linq; using System.Net; using System.Net.Http; -using Xunit; +using System.Threading.Tasks; +using FluentAssertions; using Neo4jClient.ApiModels.Cypher; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; +using Neo4jClient.Tests.Transactions; using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.GraphClientTests.Cypher +namespace Neo4jClient.Tests.GraphClientTests.Cypher { public class ExecuteGetCypherResultsTests : IClassFixture @@ -23,34 +25,34 @@ public class SimpleResultDto } [Fact] - public void EmptyCollectionShouldDeserializeCorrectly() + public async Task EmptyCollectionShouldDeserializeCorrectly() { const string queryText = @"RETURN [] AS p"; - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set, CypherResultFormat.Rest); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set, CypherResultFormat.Rest, "neo4j"); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), MockResponse.Json(HttpStatusCode.OK, - @"{'columns' : [ 'p' ], 'data' : [[ ]]}") + @"{'results': [{'columns' : [ 'p' ], 'data' : []}]}") } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); - var results = graphClient - .ExecuteGetCypherResults(cypherQuery) + var results = (await graphClient + .ExecuteGetCypherResultsAsync(cypherQuery)) .ToArray(); Assert.Empty(results); } } - [Fact] - public void ShouldDeserializePathsResultAsSetBased() + [Fact(Skip = "Doesn't Reflect Current Response from Neo4j")] + public async Task ShouldDeserializePathsResultAsSetBased() { // Arrange const string queryText = @"START d=node($p0), e=node($p1) @@ -63,13 +65,13 @@ public void ShouldDeserializePathsResultAsSetBased() {"p1", 219} }; - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, CypherResultFormat.Rest); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, CypherResultFormat.Rest, "neo4j"); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), MockResponse.Json(HttpStatusCode.OK, @"{ 'data' : [ [ { @@ -90,11 +92,11 @@ public void ShouldDeserializePathsResultAsSetBased() } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); //Act - var results = graphClient - .ExecuteGetCypherResults(cypherQuery) + var results = (await graphClient + .ExecuteGetCypherResultsAsync(cypherQuery)) .ToArray(); //Assert @@ -108,8 +110,8 @@ public void ShouldDeserializePathsResultAsSetBased() } } - [Fact] - public void ShouldDeserializeSimpleTableStructure() + [Fact(Skip = "Doesn't Reflect Current Response from Neo4j")] + public async Task ShouldDeserializeSimpleTableStructure() { // Arrange const string queryText = @" @@ -124,14 +126,14 @@ RETURN type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId {"p0", 123} }, CypherResultMode.Projection, - CypherResultFormat.Rest); + CypherResultFormat.Rest, "neo4j"); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using(var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), MockResponse.Json(HttpStatusCode.OK, @"{ 'data' : [ [ 'HOSTS', 'foo', 44321 ], [ 'LIKES', 'bar', 44311 ], [ 'HOSTS', 'baz', 42586 ] ], 'columns' : [ 'RelationshipType', 'Name', 'UniqueId' ] @@ -139,10 +141,10 @@ RETURN type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); //Act - var results = graphClient.ExecuteGetCypherResults(cypherQuery); + var results = await graphClient.ExecuteGetCypherResultsAsync(cypherQuery); //Assert Assert.IsAssignableFrom>(results); @@ -167,22 +169,22 @@ RETURN type(r) AS RelationshipType, n.Name? AS Name, n.UniqueId? AS UniqueId } } - [Fact] - public void ShouldDeserializeArrayOfNodesInPropertyAsResultOfCollectFunctionInCypherQuery() + [Fact(Skip = "Doesn't Reflect Current Response from Neo4j")] + public async Task 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); + CypherResultFormat.Rest, "neo4j"); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), MockResponse.Json(HttpStatusCode.OK, @"{ 'columns' : [ 'ColumnA', 'ColumnBFromCollect' ], 'data' : [ [ { @@ -247,10 +249,10 @@ public void ShouldDeserializeArrayOfNodesInPropertyAsResultOfCollectFunctionInCy } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); //Act - var results = graphClient.ExecuteGetCypherResults(cypherQuery); + var results = await graphClient.ExecuteGetCypherResultsAsync(cypherQuery); //Assert Assert.IsAssignableFrom>(results); @@ -321,8 +323,8 @@ public class ResultWithRelationshipDto public long? UniqueId { get; set; } } - [Fact] - public void ShouldDeserializeTableStructureWithNodes() + [Fact(Skip = "Doesn't Reflect Current Response from Neo4j")] + public async Task ShouldDeserializeTableStructureWithNodes() { // Arrange const string queryText = @" @@ -337,14 +339,14 @@ MATCH x-[r]->n {"p0", 123} }, CypherResultMode.Projection, - CypherResultFormat.Rest); + CypherResultFormat.Rest, "neo4j"); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), MockResponse.Json(HttpStatusCode.OK, @"{ 'data' : [ [ { 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', @@ -409,10 +411,10 @@ MATCH x-[r]->n } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); //Act - var results = graphClient.ExecuteGetCypherResults(cypherQuery); + var results = await graphClient.ExecuteGetCypherResultsAsync(cypherQuery); //Assert Assert.IsAssignableFrom>(results); @@ -446,8 +448,8 @@ MATCH x-[r]->n } } - [Fact] - public void ShouldDeserializeTableStructureWithNodeDataObjects() + [Fact(Skip = "Doesn't Reflect Current Response from Neo4j")] + public async Task ShouldDeserializeTableStructureWithNodeDataObjects() { // Arrange const string queryText = @" @@ -462,14 +464,14 @@ MATCH x-[r]->n {"p0", 123} }, CypherResultMode.Projection, - CypherResultFormat.Rest); + CypherResultFormat.Rest, "neo4j"); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), MockResponse.Json(HttpStatusCode.OK, @"{ 'data' : [ [ { 'outgoing_relationships' : 'http://foo/db/data/node/0/relationships/out', @@ -534,10 +536,10 @@ MATCH x-[r]->n } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); //Act - var results = graphClient.ExecuteGetCypherResults(cypherQuery); + var results = await graphClient.ExecuteGetCypherResultsAsync(cypherQuery); //Assert Assert.IsAssignableFrom>(results); @@ -568,8 +570,8 @@ MATCH x-[r]->n } } - [Fact] - public void ShouldDeserializeTableStructureWithRelationships() + [Fact(Skip = "Doesn't Reflect Current Response from Neo4j")] + public async Task ShouldDeserializeTableStructureWithRelationships() { // Arrange const string queryText = @" @@ -584,14 +586,14 @@ MATCH x-[r]->n {"p0", 123} }, CypherResultMode.Projection, - CypherResultFormat.Rest); + CypherResultFormat.Rest, "neo4j"); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), MockResponse.Json(HttpStatusCode.OK, @"{ 'data' : [ [ { 'start' : 'http://foo/db/data/node/0', @@ -638,11 +640,11 @@ MATCH x-[r]->n } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); //Act //Act - var results = graphClient.ExecuteGetCypherResults(cypherQuery); + var results = await graphClient.ExecuteGetCypherResultsAsync(cypherQuery); //Assert Assert.IsAssignableFrom>(results); @@ -677,17 +679,17 @@ MATCH x-[r]->n } [Fact] - public void ShouldPromoteBadQueryResponseToNiceException() + public async Task ShouldPromoteBadQueryResponseToNiceException() { // Arrange const string queryText = @"broken query"; - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Rest); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, CypherResultFormat.Rest, "neo4j"); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness { { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), MockResponse.Json(HttpStatusCode.BadRequest, @"{ 'message' : 'expected START or CREATE\n\'bad query\'\n ^', 'exception' : 'SyntaxException', @@ -697,9 +699,9 @@ public void ShouldPromoteBadQueryResponseToNiceException() } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); + var graphClient = await testHarness.CreateAndConnectGraphClient(); - var ex = Assert.Throws(() => graphClient.ExecuteGetCypherResults(cypherQuery)); + var ex = await Assert.ThrowsAsync(async () => await graphClient.ExecuteGetCypherResultsAsync(cypherQuery)); Assert.Equal("SyntaxException: expected START or CREATE\n'bad query'\n ^", ex.Message); Assert.Equal("expected START or CREATE\n'bad query'\n ^", ex.NeoMessage); Assert.Equal("SyntaxException", ex.NeoExceptionName); @@ -724,8 +726,8 @@ public void ShouldPromoteBadQueryResponseToNiceException() } } - [Fact] - public void SendsCommandWithCorrectTimeout() + [Fact(Skip = "Doesn't Reflect Current Response from Neo4j")] + public async Task SendsCommandWithCorrectTimeout() { const int expectedMaxExecutionTime = 100; @@ -740,17 +742,17 @@ public void SendsCommandWithCorrectTimeout() }; - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set,CypherResultFormat.Transactional ,maxExecutionTime: expectedMaxExecutionTime); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set,CypherResultFormat.Transactional, "neo4j", maxExecutionTime: expectedMaxExecutionTime); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness { { MockRequest.Get(""), - MockResponse.NeoRoot() + MockResponse.NeoRoot20() }, { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), MockResponse.Json(HttpStatusCode.OK, @"{ 'data' : [ [ { @@ -773,10 +775,10 @@ public void SendsCommandWithCorrectTimeout() { var httpClient = testHarness.GenerateHttpClient(testHarness.BaseUri); var graphClient = new GraphClient(new Uri(testHarness.BaseUri), httpClient); - graphClient.Connect(); + await graphClient.ConnectAsync(); httpClient.ClearReceivedCalls(); - ((IRawGraphClient)graphClient).ExecuteGetCypherResults(cypherQuery); + await ((IRawGraphClient)graphClient).ExecuteGetCypherResultsAsync(cypherQuery); var call = httpClient.ReceivedCalls().Single(); var requestMessage = (HttpRequestMessage)call.GetArguments()[0]; @@ -785,8 +787,8 @@ public void SendsCommandWithCorrectTimeout() } } - [Fact] - public void DoesntSendMaxExecutionTime_WhenNotAddedToQuery() + [Fact (Skip="Doesn't Reflect Current Response from Neo4j")] + public async Task DoesntSendMaxExecutionTime_WhenNotAddedToQuery() { const string queryText = @"START d=node($p0), e=node($p1) MATCH p = allShortestPaths( d-[*..15]-e ) @@ -798,17 +800,17 @@ public void DoesntSendMaxExecutionTime_WhenNotAddedToQuery() {"p1", 219} }; - var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, parameters, CypherResultMode.Set, "neo4j"); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness { { MockRequest.Get(""), - MockResponse.NeoRoot() + MockResponse.NeoRoot20() }, { - MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), + MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), MockResponse.Json(HttpStatusCode.OK, @"{ 'data' : [ [ { @@ -831,10 +833,10 @@ public void DoesntSendMaxExecutionTime_WhenNotAddedToQuery() { var httpClient = testHarness.GenerateHttpClient(testHarness.BaseUri); var graphClient = new GraphClient(new Uri(testHarness.BaseUri), httpClient); - graphClient.Connect(); + await graphClient.ConnectAsync(); httpClient.ClearReceivedCalls(); - ((IRawGraphClient)graphClient).ExecuteGetCypherResults(cypherQuery); + await ((IRawGraphClient)graphClient).ExecuteGetCypherResultsAsync(cypherQuery); var call = httpClient.ReceivedCalls().Single(); var requestMessage = (HttpRequestMessage)call.GetArguments()[0]; diff --git a/Neo4jClient.Tests/GraphClientTests/DefaultDatabaseTests.cs b/Neo4jClient.Tests/GraphClientTests/DefaultDatabaseTests.cs new file mode 100644 index 000000000..ffdea1704 --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/DefaultDatabaseTests.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using FluentAssertions; +using Xunit; + +namespace Neo4jClient.Tests.GraphClientTests +{ + + public class DefaultDatabaseTests : IClassFixture + { + [Theory] + [InlineData("FOO")] + [InlineData("foo")] + [InlineData("Foo")] + [InlineData("fOO")] + [InlineData("FoO")] + public void ShouldAlwaysBeLowercase(string database) + { + const string expected = "foo"; + var client = new GraphClient(new Uri("http://foo")){DefaultDatabase = database}; + client.DefaultDatabase.Should().Be(expected); + } + + [Fact] + public void ShouldBeNeo4jIfNotSet() + { + const string expected = "neo4j"; + var client = new GraphClient(new Uri("http://foo")); + client.DefaultDatabase.Should().Be(expected); + } + } +} diff --git a/Neo4jClient.Tests/GraphClientTests/DeleteIndexTests.cs b/Neo4jClient.Tests/GraphClientTests/DeleteIndexTests.cs new file mode 100644 index 000000000..28162890c --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/DeleteIndexTests.cs @@ -0,0 +1,27 @@ +// using System.Threading.Tasks; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class DeleteIndexTests : IClassFixture +// { +// [Fact] +// public async Task ShouldExecuteSilentlyForSuccessfulDelete() +// { +// using (var testHarness = new RestTestHarness +// { +// { +// MockRequest.Delete("/index/node/MyIndex"), +// MockResponse.Http(204) +// } +// }) +// { +// var graphClient = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// await graphClient.DeleteIndexAsync("MyIndex", IndexFor.Node); +// } +// } +// } +// } diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/DeleteNodeTests.cs b/Neo4jClient.Tests/GraphClientTests/DeleteNodeTests.cs similarity index 73% rename from Neo4jClient.Tests.Shared/GraphClientTests/DeleteNodeTests.cs rename to Neo4jClient.Tests/GraphClientTests/DeleteNodeTests.cs index 677863146..5916af5eb 100644 --- a/Neo4jClient.Tests.Shared/GraphClientTests/DeleteNodeTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/DeleteNodeTests.cs @@ -1,23 +1,24 @@ -using System; +/* +using System; using System.Net; +using System.Threading.Tasks; using FluentAssertions; -using Neo4jClient.Test.Fixtures; using Xunit; -namespace Neo4jClient.Test.GraphClientTests +namespace Neo4jClient.Tests.GraphClientTests { public class DeleteNodeTests : IClassFixture { [Fact] - public void ShouldThrowInvalidOperationExceptionIfNotConnected() + public async Task ShouldThrowInvalidOperationExceptionIfNotConnected() { var client = new GraphClient(new Uri("http://foo")); - Assert.Throws(() => client.Delete(123, DeleteMode.NodeOnly)); + await Assert.ThrowsAsync(async () => await client.DeleteAsync(123, DeleteMode.NodeOnly)); } [Fact] - public void ShouldDeleteNodeOnly() + public async Task ShouldDeleteNodeOnly() { using (var testHarness = new RestTestHarness { @@ -27,13 +28,13 @@ public void ShouldDeleteNodeOnly() } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.Delete(456, DeleteMode.NodeOnly); + var graphClient = await testHarness.CreateAndConnectGraphClient(); + await graphClient.DeleteAsync(456, DeleteMode.NodeOnly); } } [Fact] - public void ShouldDeleteAllRelationshipsFirst() + public async Task ShouldDeleteAllRelationshipsFirst() { using (var testHarness = new RestTestHarness { @@ -73,13 +74,13 @@ public void ShouldDeleteAllRelationshipsFirst() } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); - graphClient.Delete(456, DeleteMode.NodeAndRelationships); + var graphClient = await testHarness.CreateAndConnectGraphClient(); + await graphClient.DeleteAsync(456, DeleteMode.NodeAndRelationships); } } [Fact] - public void ShouldThrowExceptionWhenDeleteFails() + public async Task ShouldThrowExceptionWhenDeleteFails() { using (var testHarness = new RestTestHarness { @@ -89,11 +90,12 @@ public void ShouldThrowExceptionWhenDeleteFails() } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var ex = Assert.Throws(() => graphClient.Delete(456, DeleteMode.NodeOnly)); + var graphClient = await testHarness.CreateAndConnectGraphClient(); + var ex = await Assert.ThrowsAsync(async () => await graphClient.DeleteAsync(456, DeleteMode.NodeOnly)); ex.Message.Should().Be("Unable to delete the node. The node may still have relationships. The response status was: 409 Conflict"); } } } } +*/ diff --git a/Neo4jClient.Tests/GraphClientTests/DeleteRelationshipTests.cs b/Neo4jClient.Tests/GraphClientTests/DeleteRelationshipTests.cs new file mode 100644 index 000000000..cbc4d617c --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/DeleteRelationshipTests.cs @@ -0,0 +1,51 @@ +// using System; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class DeleteRelationshipTests : IClassFixture +// { +// [Fact] +// public async Task ShouldThrowInvalidOperationExceptionIfNotConnected() +// { +// var client = new GraphClient(new Uri("http://foo")); +// await Assert.ThrowsAsync(async () => await client.DeleteRelationshipAsync(123)); +// } +// +// [Fact] +// public async Task ShouldDeleteRelationship() +// { +// using (var testHarness = new RestTestHarness +// { +// { +// MockRequest.Delete("/relationship/456"), +// MockResponse.Http(204) +// } +// }) +// { +// var graphClient = await testHarness.CreateAndConnectGraphClient(); +// await graphClient.DeleteRelationshipAsync(456); +// } +// } +// +// [Fact] +// public async Task ShouldThrowExceptionWhenDeleteFails() +// { +// using (var testHarness = new RestTestHarness +// { +// { +// MockRequest.Delete("/relationship/456"), +// MockResponse.Http(404) +// } +// }) +// { +// var graphClient = await testHarness.CreateAndConnectGraphClient(); +// var ex = await Assert.ThrowsAsync(async () => await graphClient.DeleteRelationshipAsync(456)); +// ex.Message.Should().Be("Unable to delete the relationship. The response status was: 404 NotFound"); +// } +// } +// } +// } diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/FactoryTests.cs b/Neo4jClient.Tests/GraphClientTests/FactoryTests.cs similarity index 53% rename from Neo4jClient.Tests.Shared/GraphClientTests/FactoryTests.cs rename to Neo4jClient.Tests/GraphClientTests/FactoryTests.cs index 75bdf8022..e0aa1b712 100644 --- a/Neo4jClient.Tests.Shared/GraphClientTests/FactoryTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/FactoryTests.cs @@ -1,13 +1,13 @@ using System; +using System.Collections.Generic; using System.Net; using System.Threading.Tasks; using Neo4jClient.ApiModels.Cypher; using Neo4jClient.Cypher; using Neo4jClient.Execution; -using Neo4jClient.Test.Fixtures; using Xunit; -namespace Neo4jClient.Test.GraphClientTests +namespace Neo4jClient.Tests.GraphClientTests { public class FactoryTests : IClassFixture @@ -19,7 +19,7 @@ public void ShouldThrowExceptionIfConfigurationIsNotDefined() } [Fact] - public void ShouldThrowExceptionIfRootApiIsNotDefined() + public async Task ShouldThrowExceptionIfRootApiIsNotDefined() { using (var testHarness = new RestTestHarness { @@ -31,28 +31,27 @@ public void ShouldThrowExceptionIfRootApiIsNotDefined() var executeConfiguration = new ExecutionConfiguration { HttpClient = httpClient, - UserAgent = - string.Format("Neo4jClient/{0}", typeof(NeoServerConfiguration).Assembly.GetName().Version), + UserAgent = $"Neo4jClient/{typeof(NeoServerConfiguration).Assembly.GetName().Version}", UseJsonStreaming = true, JsonConverters = GraphClient.DefaultJsonConverters }; - Assert.Throws(() => NeoServerConfiguration.GetConfiguration(new Uri(testHarness.BaseUri), null, null, null, executeConfiguration)); + await Assert.ThrowsAsync(async () => await NeoServerConfiguration.GetConfigurationAsync(new Uri(testHarness.BaseUri), null, null, null, null, executeConfiguration)); } } [Fact] - public void GraphClientFactoryUseCase() + public async Task GraphClientFactoryUseCase() { - const string queryText = @"MATCH (d) RETURN d"; + const string queryText = @"RETURN d"; - var cypherQuery = new CypherQuery(queryText, null, CypherResultMode.Set, CypherResultFormat.Rest); - var cypherApiQuery = new CypherApiQuery(cypherQuery); + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Set, CypherResultFormat.Rest, "neo4j"); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness { - { MockRequest.Get("/"), MockResponse.NeoRoot() }, - { MockRequest.PostObjectAsJson("/cypher", cypherApiQuery), new MockResponse { StatusCode = HttpStatusCode.OK } } + { MockRequest.Get("/"), MockResponse.NeoRoot20() }, + { MockRequest.PostObjectAsJson("/transaction/commit", cypherApiQuery), new MockResponse { StatusCode = HttpStatusCode.OK } } }) { var httpClient = testHarness.GenerateHttpClient(testHarness.BaseUri); @@ -60,19 +59,18 @@ public void GraphClientFactoryUseCase() var executeConfiguration = new ExecutionConfiguration { HttpClient = httpClient, - UserAgent = - string.Format("Neo4jClient/{0}", typeof(NeoServerConfiguration).Assembly.GetName().Version), + UserAgent = $"Neo4jClient/{typeof(NeoServerConfiguration).Assembly.GetName().Version}", UseJsonStreaming = true, JsonConverters = GraphClient.DefaultJsonConverters }; - var configuration = NeoServerConfiguration.GetConfiguration(new Uri(testHarness.BaseUri), null, null,null, executeConfiguration); + var configuration = await NeoServerConfiguration.GetConfigurationAsync(new Uri(testHarness.BaseUri), null, null,null,null, executeConfiguration); var factory = new GraphClientFactory(configuration); - using (var client = factory.Create(httpClient)) + using (var client = await factory.CreateAsync(httpClient)) { - client.Cypher.Match("(d)").Return("d").ExecuteWithoutResults(); + await client.Cypher.Return("d").ExecuteWithoutResultsAsync(); } } } diff --git a/Neo4jClient.Tests.Shared/GraphClientTests/GetIndexesTests.cs b/Neo4jClient.Tests/GraphClientTests/GetIndexesTests.cs similarity index 81% rename from Neo4jClient.Tests.Shared/GraphClientTests/GetIndexesTests.cs rename to Neo4jClient.Tests/GraphClientTests/GetIndexesTests.cs index 770924ad2..6d94c9179 100644 --- a/Neo4jClient.Tests.Shared/GraphClientTests/GetIndexesTests.cs +++ b/Neo4jClient.Tests/GraphClientTests/GetIndexesTests.cs @@ -1,15 +1,16 @@ -using System.Linq; +/* +using System.Linq; using System.Net; -using Neo4jClient.Test.Fixtures; +using System.Threading.Tasks; using Xunit; -namespace Neo4jClient.Test.GraphClientTests +namespace Neo4jClient.Tests.GraphClientTests { public class GetIndexesTests : IClassFixture { [Fact] - public void ShouldReturnNodeIndexes() + public async Task ShouldReturnNodeIndexes() { using (var testHarness = new RestTestHarness { @@ -35,8 +36,8 @@ public void ShouldReturnNodeIndexes() } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var indexes = graphClient.GetIndexes(IndexFor.Node); + var graphClient = await testHarness.CreateAndConnectGraphClient(); + var indexes = await graphClient.GetIndexesAsync(IndexFor.Node); Assert.Equal(2, indexes.Count()); var index = indexes.ElementAt(0); @@ -56,7 +57,7 @@ public void ShouldReturnNodeIndexes() } [Fact] - public void ShouldReturnEmptyDictionaryOfIndexesForHttpResponse204() + public async Task ShouldReturnEmptyDictionaryOfIndexesForHttpResponse204() { using (var testHarness = new RestTestHarness { @@ -66,10 +67,11 @@ public void ShouldReturnEmptyDictionaryOfIndexesForHttpResponse204() } }) { - var graphClient = testHarness.CreateAndConnectGraphClient(); - var indexes = graphClient.GetIndexes(IndexFor.Node); + var graphClient = await testHarness.CreateAndConnectGraphClient(); + var indexes = await graphClient.GetIndexesAsync(IndexFor.Node); Assert.False(indexes.Any()); } } } } +*/ diff --git a/Neo4jClient.Tests/GraphClientTests/GetNodeTests.cs b/Neo4jClient.Tests/GraphClientTests/GetNodeTests.cs new file mode 100644 index 000000000..46fd4544c --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/GetNodeTests.cs @@ -0,0 +1,231 @@ +// using System; +// using System.Net; +// using System.Threading.Tasks; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class GetNodeTests : IClassFixture +// { +// [Fact] +// public async Task ShouldThrowInvalidOperationExceptionIfNotConnected() +// { +// var client = new GraphClient(new Uri("http://foo")); +// await Assert.ThrowsAsync(async () => await client.GetAsync((NodeReference)123)); +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// var node = await graphClient.GetAsync((NodeReference)456); +// +// Assert.Equal(456, node.Reference.Id); +// Assert.Equal("foo", node.Data.Foo); +// Assert.Equal("bar", node.Data.Bar); +// Assert.Equal("baz", node.Data.Baz); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// var node = await graphClient.GetAsync((NodeReference)21484836470); +// +// Assert.Equal(21484836470, node.Reference.Id); +// Assert.Equal("foo", node.Data.Foo); +// Assert.Equal("bar", node.Data.Bar); +// Assert.Equal("baz", node.Data.Baz); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// var node = await graphClient.GetAsync((NodeReference)456); +// +// Assert.Equal(456, node.Reference.Id); +// Assert.Equal("foo", node.Data.Foo); +// Assert.Equal(TestEnum.Value1, node.Data.Status); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// var node = await graphClient.GetAsync((NodeReference)456); +// +// Assert.Equal(graphClient, ((IAttachedReference) node.Reference).Client); +// } +// } +// +// [Fact] +// public async Task ShouldReturnNullWhenNodeDoesntExist() +// { +// using (var testHarness = new RestTestHarness +// { +// { +// MockRequest.Get("/node/456"), +// MockResponse.Http(404) +// } +// }) +// { +// var graphClient = await testHarness.CreateAndConnectGraphClient(); +// var node = await graphClient.GetAsync((NodeReference)456); +// +// Assert.Null(node); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// var node = await graphClient.GetAsync((NodeReference)456); +// +// Assert.NotNull(node.Data.DateOffSet); +// Assert.Equal("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/Neo4jClient.Tests/GraphClientTests/IndexExistsTests.cs b/Neo4jClient.Tests/GraphClientTests/IndexExistsTests.cs new file mode 100644 index 000000000..12742df49 --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/IndexExistsTests.cs @@ -0,0 +1,34 @@ +// using System.Net; +// using System.Threading.Tasks; +// using FluentAssertions; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class IndexExistsTests : IClassFixture +// { +// [Theory] +// [InlineData(IndexFor.Node, "/index/node/MyIndex", HttpStatusCode.OK, true)] +// [InlineData(IndexFor.Node, "/index/node/MyIndex", HttpStatusCode.NotFound, false)] +// [InlineData(IndexFor.Relationship, "/index/relationship/MyIndex", HttpStatusCode.OK, true)] +// [InlineData(IndexFor.Relationship, "/index/relationship/MyIndex", HttpStatusCode.NotFound, false)] +// public async Task ShouldReturnIfIndexIsFound( +// IndexFor indexFor, +// string indexPath, +// HttpStatusCode httpStatusCode, bool expectedResult) +// { +// using (var testHarness = new RestTestHarness +// { +// { +// MockRequest.Get(indexPath), +// MockResponse.Json(httpStatusCode, "") +// } +// }) +// { +// var graphClient = await testHarness.CreateAndConnectGraphClient(); +// (await graphClient.CheckIndexExistsAsync("MyIndex", indexFor)).Should().Be(expectedResult); +// } +// } +// } +// } diff --git a/Neo4jClient.Tests/GraphClientTests/LookupIndexTests.cs b/Neo4jClient.Tests/GraphClientTests/LookupIndexTests.cs new file mode 100644 index 000000000..6f7bfb64f --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/LookupIndexTests.cs @@ -0,0 +1,55 @@ +// using System.Linq; +// using System.Net; +// using System.Threading.Tasks; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class LookupIndexTests : IClassFixture +// { +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// var results = (await graphClient +// .LookupIndexAsync("users", IndexFor.Node, "Id", 1000)) +// .ToArray(); +// +// Assert.Equal(1, results.Count()); +// var result = results[0]; +// Assert.Equal(456, result.Reference.Id); +// Assert.Equal(1000, result.Data.Id); +// } +// } +// +// public class UserTestNode +// { +// public long Id { get; set; } +// public string Name { get; set; } +// } +// } +// } diff --git a/Neo4jClient.Tests/GraphClientTests/ReIndexNodesTests.cs b/Neo4jClient.Tests/GraphClientTests/ReIndexNodesTests.cs new file mode 100644 index 000000000..0a6a081e1 --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/ReIndexNodesTests.cs @@ -0,0 +1,194 @@ +// using System; +// using System.Collections.Generic; +// using System.Net; +// using System.Threading.Tasks; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class ReIndexNodesTests : IClassFixture +// { +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// await graphClient.ReIndexAsync((NodeReference)123, indexEntries); +// +// // Assert +// +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// await graphClient.ReIndexAsync((NodeReference)123, indexEntries); +// +// // Assert +// +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// await graphClient.ReIndexAsync((NodeReference)123, indexEntries); +// +// // Assert +// +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// await graphClient.ReIndexAsync((NodeReference)123, indexEntries); +// +// // Assert +// +// } +// } +// } +// } diff --git a/Neo4jClient.Tests/GraphClientTests/ReIndexRelationshipsTests.cs b/Neo4jClient.Tests/GraphClientTests/ReIndexRelationshipsTests.cs new file mode 100644 index 000000000..2add510d9 --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/ReIndexRelationshipsTests.cs @@ -0,0 +1,212 @@ +// using System; +// using System.Collections.Generic; +// using System.Net; +// using System.Threading.Tasks; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class ReIndexRelationshipsTests : IClassFixture +// { +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// var relReference = new RelationshipReference(1234); +// await graphClient.ReIndexAsync(relReference, indexEntries); +// +// // Assert +// +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// var relReference = new RelationshipReference(1234); +// await graphClient.ReIndexAsync(relReference, indexEntries); +// +// // Assert +// +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// var relReference = new RelationshipReference(1234); +// await graphClient.ReIndexAsync(relReference, indexEntries); +// +// // Assert +// +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// var relReference = new RelationshipReference(123); +// await graphClient.ReIndexAsync(relReference, indexEntries); +// +// // Assert +// +// } +// } +// +// 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/Neo4jClient.Tests/GraphClientTests/RootNodeTests.cs b/Neo4jClient.Tests/GraphClientTests/RootNodeTests.cs new file mode 100644 index 000000000..634c2b2ed --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/RootNodeTests.cs @@ -0,0 +1,20 @@ +// using System.Threading.Tasks; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class RootNodeTests : IClassFixture +// { +// [Fact] +// public async Task RootNodeShouldHaveReferenceBackToClient() +// { +// using (var testHarness = new RestTestHarness()) +// { +// var client = await testHarness.CreateAndConnectGraphClient(); +// var rootNode = client.RootNode; +// Assert.Equal(client, ((IAttachedReference) rootNode).Client); +// } +// } +// } +// } diff --git a/Neo4jClient.Tests/GraphClientTests/UpdateNodeTests.cs b/Neo4jClient.Tests/GraphClientTests/UpdateNodeTests.cs new file mode 100644 index 000000000..ab7aa133b --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/UpdateNodeTests.cs @@ -0,0 +1,299 @@ +// using System.Collections.Generic; +// using System.Linq; +// using System.Net; +// using System.Threading.Tasks; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class UpdateNodeTests : IClassFixture +// { +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// var pocoReference = new NodeReference(456); +// await graphClient.UpdateAsync( +// pocoReference, nodeFromDb => +// { +// nodeFromDb.Foo = "fooUpdated"; +// nodeFromDb.Baz = "bazUpdated"; +// nodeToUpdate = nodeFromDb; +// } +// ); +// +// Assert.Equal("fooUpdated", nodeToUpdate.Foo); +// Assert.Equal("bazUpdated", nodeToUpdate.Baz); +// Assert.Equal("bar", nodeToUpdate.Bar); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// var pocoReference = new NodeReference(456); +// var updatedNode = await graphClient.UpdateAsync( +// pocoReference, nodeFromDb => +// { +// nodeFromDb.Foo = "fooUpdated"; +// nodeFromDb.Baz = "bazUpdated"; +// }); +// +// Assert.Equal(pocoReference, updatedNode.Reference); +// Assert.Equal("fooUpdated", updatedNode.Data.Foo); +// Assert.Equal("bazUpdated", updatedNode.Data.Baz); +// Assert.Equal("bar", updatedNode.Data.Bar); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// // Act +// var pocoReference = new NodeReference(456); +// await graphClient.UpdateAsync( +// pocoReference, nodeFromDb => +// { +// nodeFromDb.Foo = "fooUpdated"; +// nodeFromDb.Baz = "bazUpdated"; +// nodeToUpdate = nodeFromDb; +// }, nodeFromDb => new List +// { +// new IndexEntry +// { +// Name = "foo", +// KeyValues = new Dictionary {{"foo", "bar"}}, +// } +// }); +// +// Assert.Equal("fooUpdated", nodeToUpdate.Foo); +// Assert.Equal("bazUpdated", nodeToUpdate.Baz); +// Assert.Equal("bar", nodeToUpdate.Bar); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// var hasChanged = false; +// +// var pocoReference = new NodeReference(456); +// await graphClient.UpdateAsync( +// pocoReference, nodeFromDb => +// { +// nodeFromDb.Foo = "fooUpdated"; +// nodeFromDb.Baz = "bazUpdated"; +// nodeToUpdate = nodeFromDb; +// }, +// null, +// diff => { hasChanged = diff.Any(); } +// ); +// +// Assert.True(hasChanged); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// //Act +// var pocoReference = new NodeReference(456); +// await graphClient.UpdateAsync(pocoReference, newData); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// // Act +// var pocoReference = new NodeReference(456); +// await graphClient.UpdateAsync( +// 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/Neo4jClient.Tests/GraphClientTests/UpdateRelationshipTests.cs b/Neo4jClient.Tests/GraphClientTests/UpdateRelationshipTests.cs new file mode 100644 index 000000000..81176e98c --- /dev/null +++ b/Neo4jClient.Tests/GraphClientTests/UpdateRelationshipTests.cs @@ -0,0 +1,77 @@ +// using System.Net; +// using System.Threading.Tasks; +// using Xunit; +// +// namespace Neo4jClient.Tests.GraphClientTests +// { +// +// public class UpdateRelationshipTests : IClassFixture +// { +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// await graphClient.UpdateAsync( +// new RelationshipReference(456), +// payloadFromDb => +// { +// payloadFromDb.Foo = "fooUpdated"; +// payloadFromDb.Baz = "bazUpdated"; +// } +// ); +// } +// } +// +// [Fact] +// public async Task 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 = await testHarness.CreateAndConnectGraphClient(); +// +// await graphClient.UpdateAsync( +// 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/Neo4jClient.Tests.Shared/HttpClientWrapperTests.cs b/Neo4jClient.Tests/HttpClientWrapperTests.cs similarity index 97% rename from Neo4jClient.Tests.Shared/HttpClientWrapperTests.cs rename to Neo4jClient.Tests/HttpClientWrapperTests.cs index deb0dbe1a..5e160345d 100644 --- a/Neo4jClient.Tests.Shared/HttpClientWrapperTests.cs +++ b/Neo4jClient.Tests/HttpClientWrapperTests.cs @@ -1,11 +1,10 @@ using System; using System.Net.Http; using System.Text; -using Neo4jClient.Test.Fixtures; using NSubstitute; using Xunit; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class HttpClientWrapperTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/IndexEntryTests.cs b/Neo4jClient.Tests/IndexEntryTests.cs similarity index 98% rename from Neo4jClient.Tests.Shared/IndexEntryTests.cs rename to Neo4jClient.Tests/IndexEntryTests.cs index 5081578ad..af2e3dc38 100644 --- a/Neo4jClient.Tests.Shared/IndexEntryTests.cs +++ b/Neo4jClient.Tests/IndexEntryTests.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; using System.Linq; -using Neo4jClient.Test.Fixtures; using Xunit; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class IndexEntryTests : IClassFixture diff --git a/Neo4jClient.Tests.Shared/MockRequest.cs b/Neo4jClient.Tests/MockRequest.cs similarity index 83% rename from Neo4jClient.Tests.Shared/MockRequest.cs rename to Neo4jClient.Tests/MockRequest.cs index 9eb749663..48210e3fa 100644 --- a/Neo4jClient.Tests.Shared/MockRequest.cs +++ b/Neo4jClient.Tests/MockRequest.cs @@ -1,7 +1,7 @@ using System.Net.Http; using Neo4jClient.Serialization; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class MockRequest { @@ -29,6 +29,13 @@ public static MockRequest PostJson(string uri, string json) public static MockRequest PostObjectAsJson(string uri, object body) { + var test = new MockRequest + { + Resource = uri, + Method = HttpMethod.Post, + Body = new CustomJsonSerializer { JsonConverters = GraphClient.DefaultJsonConverters }.Serialize(body) + }; + return new MockRequest { Resource = uri, diff --git a/Neo4jClient.Tests.Shared/MockResponse.cs b/Neo4jClient.Tests/MockResponse.cs similarity index 69% rename from Neo4jClient.Tests.Shared/MockResponse.cs rename to Neo4jClient.Tests/MockResponse.cs index 8cadec283..2256fbe42 100644 --- a/Neo4jClient.Tests.Shared/MockResponse.cs +++ b/Neo4jClient.Tests/MockResponse.cs @@ -1,7 +1,6 @@ -using System; -using System.Net; +using System.Net; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class MockResponse { @@ -42,24 +41,24 @@ public static MockResponse Json(HttpStatusCode statusCode, string json, string l }; } - 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 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() { @@ -79,7 +78,10 @@ public static MockResponse NeoRoot20() public static MockResponse NeoRoot(int v1, int v2, int v3) { - var version = string.Format("{0}.{1}.{2}", v1, v2, v3); + if (v1 >= 4) + return NeoRoot40(v1, v2, v3); + + var version = $"{v1}.{v2}.{v3}"; return Json(HttpStatusCode.OK, string.Format(@"{{ 'cypher' : 'http://foo/db/data/cypher', 'batch' : 'http://foo/db/data/batch', @@ -94,6 +96,19 @@ public static MockResponse NeoRoot(int v1, int v2, int v3) }}", version)); } + public static MockResponse NeoRoot40(int v1, int v2, int v3) + { + var version = $"{v1}.{v2}.{v3}"; + return Json(HttpStatusCode.OK, string.Format(@"{{ + 'bolt_direct': 'bolt://foo:7687', + 'bolt_routing': 'neo4j://foo:7687', + 'cluster': 'http://foo:7474/db/{{databaseName}}/cluster', + 'transaction': 'http://foo:7474/db/{{databaseName}}/tx', + 'neo4j_version' : '{0}', + 'neo4j_edition': 'enterprise' + }}", version)); + } + public static MockResponse NeoRootPre15M02() { return Json(HttpStatusCode.OK, @"{ diff --git a/Neo4jClient.Tests.Shared/MockResponseThrows.cs b/Neo4jClient.Tests/MockResponseThrows.cs similarity index 92% rename from Neo4jClient.Tests.Shared/MockResponseThrows.cs rename to Neo4jClient.Tests/MockResponseThrows.cs index c4092030a..817453361 100644 --- a/Neo4jClient.Tests.Shared/MockResponseThrows.cs +++ b/Neo4jClient.Tests/MockResponseThrows.cs @@ -1,5 +1,5 @@  -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class MockResponseThrows : MockResponse { diff --git a/Neo4jClient.Tests.Shared/MockResponseThrowsException.cs b/Neo4jClient.Tests/MockResponseThrowsException.cs similarity index 88% rename from Neo4jClient.Tests.Shared/MockResponseThrowsException.cs rename to Neo4jClient.Tests/MockResponseThrowsException.cs index 45827a1e7..2934ec3ef 100644 --- a/Neo4jClient.Tests.Shared/MockResponseThrowsException.cs +++ b/Neo4jClient.Tests/MockResponseThrowsException.cs @@ -1,6 +1,6 @@ using System; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class MockResponseThrowsException : Exception { diff --git a/Neo4jClient.Tests/Neo4jClient.Tests.csproj b/Neo4jClient.Tests/Neo4jClient.Tests.csproj index 7a4c7914b..758f51573 100644 --- a/Neo4jClient.Tests/Neo4jClient.Tests.csproj +++ b/Neo4jClient.Tests/Neo4jClient.Tests.csproj @@ -1,242 +1,25 @@ - - - - - Debug - AnyCPU - 8.0.30703 - 2.0 - {2724A871-4B4F-4C83-8E0F-2439F69CADA2} - Library - Properties - Neo4jClient.Test - Neo4jClient.Tests - v4.6.1 - 512 - ..\ - true - - - - - - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - true - false - 7 - - - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - false - - - true - bin\Full - Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - true - bin\Portable - Debug\ - DEBUG;TRACE - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - ..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll - - - ..\packages\FluentAssertions.5.2.0\lib\net45\FluentAssertions.dll - - - ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll - True - - - ..\packages\Moq.4.8.2\lib\net45\Moq.dll - - - ..\packages\Neo4j.Driver.Signed.1.7.0\lib\net452\Neo4j.Driver.dll - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - - - ..\packages\NSubstitute.2.0.3\lib\net45\NSubstitute.dll - - - - ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll - True - - - - - - ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll - True - - - - ..\packages\System.Diagnostics.DiagnosticSource.4.4.1\lib\net46\System.Diagnostics.DiagnosticSource.dll - - - - ..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll - True - - - ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll - True - - - - ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll - True - - - ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll - True - - - ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - True - - - ..\packages\System.Net.Http.4.3.2\lib\net46\System.Net.Http.dll - - - - ..\packages\System.Net.NameResolution.4.3.0\lib\net46\System.Net.NameResolution.dll - - - ..\packages\System.Net.Security.4.3.1\lib\net46\System.Net.Security.dll - - - ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll - True - - - - ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - True - - - - ..\packages\System.Runtime.Serialization.Primitives.4.3.0\lib\net46\System.Runtime.Serialization.Primitives.dll - - - ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll - True - - - ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - - - ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - - - ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - True - - - ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - - - - ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll - - - - - - - - ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll - True - - - ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll - - - ..\packages\xunit.assert.2.3.1\lib\netstandard1.1\xunit.assert.dll - - - ..\packages\xunit.extensibility.core.2.3.1\lib\netstandard1.1\xunit.core.dll - - - ..\packages\xunit.extensibility.execution.2.3.1\lib\net452\xunit.execution.desktop.dll - - - - - - - - - - - - - - - - - - Designer - - - - - {343B9889-6DDF-4474-A1EC-05508A652E5A} - Neo4jClient.Full - - - - - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - \ No newline at end of file + + + + netcoreapp2.1 + Microsoft + Copyright © Microsoft 2011 + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/Neo4jClient.Tests/Neo4jClient.Tests.csproj.DotSettings b/Neo4jClient.Tests/Neo4jClient.Tests.csproj.DotSettings deleted file mode 100644 index 73e96563f..000000000 --- a/Neo4jClient.Tests/Neo4jClient.Tests.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp60 \ No newline at end of file diff --git a/Neo4jClient.Tests/Neo4jClient.Tests.ncrunchproject b/Neo4jClient.Tests/Neo4jClient.Tests.ncrunchproject deleted file mode 100644 index 62257550e..000000000 --- a/Neo4jClient.Tests/Neo4jClient.Tests.ncrunchproject +++ /dev/null @@ -1,17 +0,0 @@ - - false - false - false - false - false - false - true - true - false - true - true - 60000 - - - AutoDetect - \ No newline at end of file diff --git a/Neo4jClient.Tests/Neo4jClient.Tests.v2.ncrunchproject b/Neo4jClient.Tests/Neo4jClient.Tests.v2.ncrunchproject deleted file mode 100644 index dd3ad2c34..000000000 --- a/Neo4jClient.Tests/Neo4jClient.Tests.v2.ncrunchproject +++ /dev/null @@ -1,26 +0,0 @@ - - true - 1000 - true - false - false - true - false - false - false - false - false - true - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - \ No newline at end of file diff --git a/Neo4jClient.Tests/Neo4jClient.Tests.v3.ncrunchproject b/Neo4jClient.Tests/Neo4jClient.Tests.v3.ncrunchproject deleted file mode 100644 index 55fdc2c86..000000000 --- a/Neo4jClient.Tests/Neo4jClient.Tests.v3.ncrunchproject +++ /dev/null @@ -1,6 +0,0 @@ - - - True - True - - \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/NodeReferenceTests.cs b/Neo4jClient.Tests/NodeReferenceTests.cs similarity index 66% rename from Neo4jClient.Tests.Shared/NodeReferenceTests.cs rename to Neo4jClient.Tests/NodeReferenceTests.cs index 7095dbdf8..1eb023449 100644 --- a/Neo4jClient.Tests.Shared/NodeReferenceTests.cs +++ b/Neo4jClient.Tests/NodeReferenceTests.cs @@ -2,10 +2,8 @@ using FluentAssertions; using NSubstitute; using Xunit; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class NodeReferenceTests : IClassFixture @@ -91,38 +89,7 @@ public void EqualsShouldReturnFalseWhenComparingWithDifferentType() Assert.False(lhs.Equals(new object())); } - [Fact] - public void EqualsShouldReturnTrueWhenComparingRootNodeWithNodeReferenceOfSameId() - { - var lhs = new RootNode(123); - var rhs = new NodeReference(123); - Assert.True(lhs == rhs); - } - - [Fact] - public void EqualsShouldReturnTrueWhenComparingRootNodeWithRootNodeOfSameId() - { - var lhs = new RootNode(123); - var rhs = new RootNode(123); - Assert.True(lhs == rhs); - } - - [Fact] - public void EqualsShouldReturnFalseWhenComparingRootNodeWithRootNodeOfDifferentId() - { - var lhs = new RootNode(123); - var rhs = new RootNode(456); - Assert.False(lhs == rhs); - } - - [Fact] - public void EqualsShouldReturnFalseWhenComparingRootNodeWithNodeReferenceOfDifferentId() - { - var lhs = new RootNode(123); - var rhs = new NodeReference(4); - Assert.False(lhs == rhs); - } - + [Fact] public void NodeTypeShouldReturnTypedNodeType() { @@ -145,24 +112,5 @@ public void TypedNodeReferenceShouldThrowExceptionIfTNodeIsWrappedAgain() Assert.Equal("You're trying 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.)", ex.Message); // ReSharper restore ObjectCreationAsStatement } - - [Fact] - public void GremlinQueryTextShouldReturnSimpleVectorStep() - { - var reference = new NodeReference(123); - var query = ((IGremlinQuery) reference); - Assert.Equal("g.v(p0)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } - - [Fact] - public void CypherShouldStartQueryFromCurrentNodeReference() - { - var graphClient = Substitute.For(); - var reference = new NodeReference(123, graphClient); - var query = reference.StartCypher("foo").Query; - Assert.Equal("START foo=node($p0)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } } } \ No newline at end of file diff --git a/Neo4jClient.Tests/NodeTests.cs b/Neo4jClient.Tests/NodeTests.cs new file mode 100644 index 000000000..155d8ff15 --- /dev/null +++ b/Neo4jClient.Tests/NodeTests.cs @@ -0,0 +1,72 @@ +// using NSubstitute; +// using Xunit; +// +// namespace Neo4jClient.Tests +// { +// +// public class NodeTests : IClassFixture +// { +// [Fact] +// public void ClientShouldReturnClientFromReference() +// { +// var client = Substitute.For(); +// var reference = new NodeReference(123, client); +// var node = new Node(new object(), reference); +// Assert.Equal(client, ((IAttachedReference)node).Client); +// } +// +// [Fact] +// 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.Equal("START foo=node($p0)", query.QueryText); +// Assert.Equal(123L, query.QueryParameters["p0"]); +// } +// +// [Fact] +// public void CypherQueryShouldIncludeRootNodeAsStartBit() +// { +// var client = Substitute.For(); +// client.RootNode.ReturnsForAnyArgs(new RootNode(4, client)); +// var query = client.RootNode.StartCypher("foo").Query; +// Assert.Equal("START foo=node($p0)", query.QueryText); +// Assert.Equal(4L, query.QueryParameters["p0"]); +// } +// +// [Fact] +// 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.Equal(client, queryBuilder.Client); +// } +// +// [Fact] +// public void EqualityOperatorShouldReturnTrueForEquivalentReferences() +// { +// var node1 = new Node(new object(), new NodeReference(123)); +// var node2 = new Node(new object(), new NodeReference(123)); +// Assert.True(node1 == node2); +// } +// +// [Fact] +// public void EqualityOperatorShouldReturnFalseForDifferentReferences() +// { +// var node1 = new Node(new object(), new NodeReference(123)); +// var node2 = new Node(new object(), new NodeReference(456)); +// Assert.False(node1 == node2); +// } +// +// [Fact] +// public void GetHashCodeTestShouldReturnNodeId() +// { +// var node = new Node(new object(), new NodeReference(123)); +// Assert.Equal(123, node.Reference.Id); +// } +// } +// } \ No newline at end of file diff --git a/Neo4jClient.Tests/Properties/AssemblyInfo.cs b/Neo4jClient.Tests/Properties/AssemblyInfo.cs deleted file mode 100644 index 4dbfc98dc..000000000 --- a/Neo4jClient.Tests/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,36 +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/Neo4jClient.Tests/QueryStatsTests.cs b/Neo4jClient.Tests/QueryStatsTests.cs new file mode 100644 index 000000000..a9f958d0c --- /dev/null +++ b/Neo4jClient.Tests/QueryStatsTests.cs @@ -0,0 +1,439 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Moq; +using Neo4j.Driver; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; +using Neo4jClient.Tests.BoltGraphClientTests; +using Neo4jClient.Tests.Transactions; +using Neo4jClient.Transactions; +using Xunit; + +namespace Neo4jClient.Tests +{ + public class QueryStatsTests + { + public class Bolt + { + public class ReturningResults : IClassFixture + { + [Fact] + public async Task ReturnsQueryStats_WhenNotInTransaction() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out _); + + bool completedRaised = false; + QueryStats stats = null; + + var cypherQuery = new CypherQuery("MATCH (n) RETURN id(n)", new Dictionary(), CypherResultMode.Set, CypherResultFormat.Transactional, "neo4j", includeQueryStats:true); + + using (var testHarness = new BoltTestHarness()) + { + var value = new Dictionary {{"id(n)", 4}}; + var recordMock = new Mock(); + recordMock.Setup(x => x.Values).Returns(value); + recordMock.Setup(x => x["id(n)"]).Returns(4); + recordMock.Setup(x => x.Keys).Returns(new[] {"id(n)"}); + + var testStatementResult = new TestStatementResult(new[] { "id(n)" }, recordMock.Object); + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + var client = await testHarness.CreateAndConnectBoltGraphClient(); + client.OperationCompleted += (o, e) => + { + stats = e.QueryStats; + completedRaised = true; + }; + + + var results = (await client.ExecuteGetCypherResultsAsync(cypherQuery)).ToArray(); + results.First().Should().Be(4); + } + + completedRaised.Should().BeTrue(); + stats.Should().NotBeNull(); + } + + [Fact] + public async Task ReturnsQueryStats_WhenInTransaction() + { + // BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out _); + + bool completedRaised = false; + QueryStats stats = null; + + var cypherQuery = new CypherQuery("MATCH (n) RETURN id(n)", new Dictionary(), CypherResultMode.Set, CypherResultFormat.Transactional, "neo4j", includeQueryStats: true); + + var value = new Dictionary { { "id(n)", 4 } }; + var recordMock = new Mock(); + recordMock.Setup(x => x.Values).Returns(value); + recordMock.Setup(x => x["id(n)"]).Returns(4); + recordMock.Setup(x => x.Keys).Returns(new[] { "id(n)" }); + var testStatementResult = new TestStatementResult(new[] { "id(n)" }, recordMock.Object); + + using (var testHarness = new BoltTestHarness()) + { + testHarness.SetupCypherRequestResponse(cypherQuery.QueryText, cypherQuery.QueryParameters, testStatementResult); + + var client = await testHarness.CreateAndConnectBoltGraphClient(); + client.OperationCompleted += (o, e) => + { + stats = e.QueryStats; + completedRaised = true; + }; + + using (var tx = ((ITransactionalGraphClient) client).BeginTransaction()) + { + var results = (await client.ExecuteGetCypherResultsAsync(cypherQuery)).ToArray(); + results.First().Should().Be(4); + } + } + + completedRaised.Should().BeTrue(); + stats.Should().NotBeNull(); + } + } + + public class NoResults : IClassFixture + { + [Fact] + public async Task ReturnsQueryStats_WhenNotInTransaction() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out _); + + bool completedRaised = false; + QueryStats stats = null; + + var client = new BoltGraphClient(mockDriver); + await client.ConnectAsync(); + + client.OperationCompleted += (o, e) => + { + stats = e.QueryStats; + completedRaised = true; + }; + + await client.Cypher.WithQueryStats.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + completedRaised.Should().BeTrue(); + stats.Should().NotBeNull(); + } + + [Fact] + public async Task ReturnsQueryStats_WhenInTransaction() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out _); + + bool completedRaised = false; + QueryStats stats = null; + + var client = new BoltGraphClient(mockDriver); + client.OperationCompleted += (o, e) => + { + stats = e.QueryStats; + completedRaised = true; + }; + + + await client.ConnectAsync(); + using (var tx = client.BeginTransaction()) + { + await client.Cypher.WithQueryStats.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + completedRaised.Should().BeTrue(); + stats.Should().NotBeNull(); + } + } + } + } + + public class Http + { + public class ReturningResults : IClassFixture + { + [Fact] + public async Task ReturnsQueryStats_WhenNotInTransaction() + { + var response = MockResponse.Json(200, @"{ + 'results' : [ { + 'columns' : [ 'id(n)' ], + 'data' : [ { + 'row' : [ 4 ], + 'meta' : [ null ] + } ], + 'stats' : { + 'contains_updates' : true, + 'nodes_created' : 1, + 'nodes_deleted' : 0, + 'properties_set' : 0, + 'relationships_created' : 0, + 'relationship_deleted' : 0, + 'labels_added' : 0, + 'labels_removed' : 0, + 'indexes_added' : 0, + 'indexes_removed' : 0, + 'constraints_added' : 0, + 'constraints_removed' : 0, + 'contains_system_updates' : false, + 'system_updates' : 0 + } + } ], + 'errors' : [ ] + } + "); + const string queryText = @"MATCH (n) RETURN id(n)"; + + var cypherQuery = new CypherQuery(queryText, null, CypherResultMode.Set, CypherResultFormat.Rest, "neo4j", includeQueryStats: true); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; + + using (var testHarness = new RestTestHarness + { + {MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), response} + }) + { + var graphClient = await testHarness.CreateAndConnectGraphClient(); + var completedRaised = false; + QueryStats stats = null; + graphClient.OperationCompleted += (o, e) => + { + stats = e.QueryStats; + completedRaised = true; + }; + + var results = await graphClient.ExecuteGetCypherResultsAsync(cypherQuery); + results.First().Should().Be(4); + + completedRaised.Should().BeTrue(); + stats.Should().NotBeNull(); + stats.NodesCreated.Should().Be(1); + } + } + + [Fact] + public async Task ReturnsQueryStats_WhenInTransaction() + { + var response = MockResponse.Json(201, @"{ + 'results': [ + { + 'columns': [ + 'id(n)' + ], + 'data': [ + { + 'row': [ + 4 + ], + 'meta': [ + null + ] + } + ], + 'stats': { + 'contains_updates': false, + 'nodes_created': 0, + 'nodes_deleted': 0, + 'properties_set': 0, + 'relationships_created': 0, + 'relationship_deleted': 0, + 'labels_added': 0, + 'labels_removed': 0, + 'indexes_added': 0, + 'indexes_removed': 0, + 'constraints_added': 0, + 'constraints_removed': 0, + 'contains_system_updates': false, + 'system_updates': 0 + } + } + ], + 'errors': [], + 'commit': 'http://localhost:7474/db/neo4j/tx/2/commit', + 'transaction': { + 'expires': 'Wed, 16 Sep 2020 09:21:48 GMT' + } +}"); + const string database = "neo4j"; + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", @"{ + 'statements': [ + { + 'statement': 'MATCH (n)\r\nRETURN id(n)', + 'resultDataContents': [], + 'parameters': {}, + 'includeStats': true + } + ] +}"); + var rollbackTransactionRequest = MockRequest.Delete($"/db/{database}/tx/1"); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, response + }, + {rollbackTransactionRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }")} + }) + { + var graphClient = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + var completedRaised = false; + QueryStats stats = null; + graphClient.OperationCompleted += (o, e) => + { + stats = e.QueryStats; + completedRaised = true; + }; + + using (var tx = graphClient.BeginTransaction()) + { + var query = graphClient.Cypher.WithQueryStats.Match("(n)").Return(n => n.Id()); + query.Query.IncludeQueryStats.Should().BeTrue(); + + var results = await query.ResultsAsync; + results.First().Should().Be(4); + + completedRaised.Should().BeTrue(); + stats.Should().NotBeNull(); + } + } + } + } + + public class NoResults : IClassFixture + { + [Fact] + public async Task ReturnsQueryStats_WhenNotInTransaction() + { + var response = MockResponse.Json(200, @"{ + 'results' : [ { + 'columns' : [ 'id(n)' ], + 'data' : [ { + 'row' : [ 4 ], + 'meta' : [ null ] + } ], + 'stats' : { + 'contains_updates' : true, + 'nodes_created' : 1, + 'nodes_deleted' : 0, + 'properties_set' : 0, + 'relationships_created' : 0, + 'relationship_deleted' : 0, + 'labels_added' : 0, + 'labels_removed' : 0, + 'indexes_added' : 0, + 'indexes_removed' : 0, + 'constraints_added' : 0, + 'constraints_removed' : 0, + 'contains_system_updates' : false, + 'system_updates' : 0 + } + } ], + 'errors' : [ ] + } + "); + const string queryText = @"MATCH (n) RETURN id(n)"; + + var cypherQuery = new CypherQuery(queryText, null, CypherResultMode.Set, CypherResultFormat.Rest, "neo4j", includeQueryStats: true); + var transactionApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; + + using (var testHarness = new RestTestHarness + { + {MockRequest.PostObjectAsJson("/transaction/commit", transactionApiQuery), response} + }) + { + var graphClient = await testHarness.CreateAndConnectGraphClient(); + QueryStats stats = null; + graphClient.OperationCompleted += (o, e) => { stats = e.QueryStats; }; + await graphClient.ExecuteCypherAsync(cypherQuery); + + stats.Should().NotBeNull(); + stats.NodesCreated.Should().Be(1); + } + } + + [Fact] + public async Task ReturnsQueryStats_WhenInTransaction() + { + var response = MockResponse.Json(201, @"{ + 'results': [ + { + 'columns': [ + 'id(n)' + ], + 'data': [ + { + 'row': [ + 4 + ], + 'meta': [ + null + ] + } + ], + 'stats': { + 'contains_updates': false, + 'nodes_created': 0, + 'nodes_deleted': 0, + 'properties_set': 0, + 'relationships_created': 0, + 'relationship_deleted': 0, + 'labels_added': 0, + 'labels_removed': 0, + 'indexes_added': 0, + 'indexes_removed': 0, + 'constraints_added': 0, + 'constraints_removed': 0, + 'contains_system_updates': false, + 'system_updates': 0 + } + } + ], + 'errors': [], + 'commit': 'http://localhost:7474/db/neo4j/tx/2/commit', + 'transaction': { + 'expires': 'Wed, 16 Sep 2020 09:21:48 GMT' + } +}"); + const string database = "neo4j"; + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", @"{ + 'statements': [ + { + 'statement': 'MATCH (n)\r\nRETURN id(n)', + 'resultDataContents': [], + 'parameters': {}, + 'includeStats': true + } + ] +}"); + var rollbackTransactionRequest = MockRequest.Delete($"/db/{database}/tx/1"); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, response + }, + {rollbackTransactionRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }")} + }) + { + var graphClient = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + var completedRaised = false; + QueryStats stats = null; + graphClient.OperationCompleted += (o, e) => + { + stats = e.QueryStats; + completedRaised = true; + }; + + using (var tx = graphClient.BeginTransaction()) + { + var query = graphClient.Cypher.WithQueryStats.Match("(n)").Return(n => n.Id()); + query.Query.IncludeQueryStats.Should().BeTrue(); + + await query.ExecuteWithoutResultsAsync(); + + completedRaised.Should().BeTrue(); + stats.Should().NotBeNull(); + } + } + } + } + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/RelationshipReferenceTests.cs b/Neo4jClient.Tests/RelationshipReferenceTests.cs similarity index 83% rename from Neo4jClient.Tests.Shared/RelationshipReferenceTests.cs rename to Neo4jClient.Tests/RelationshipReferenceTests.cs index cbe1702b6..4c2d8962d 100644 --- a/Neo4jClient.Tests.Shared/RelationshipReferenceTests.cs +++ b/Neo4jClient.Tests/RelationshipReferenceTests.cs @@ -1,9 +1,7 @@ using FluentAssertions; -using Neo4jClient.Gremlin; -using Neo4jClient.Test.Fixtures; using Xunit; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class RelationshipReferenceTests : IClassFixture @@ -66,14 +64,5 @@ public void EqualsShouldReturnFalseWhenComparingWithDifferentType() var lhs = new RelationshipReference(3); Assert.False(lhs.Equals(new object())); } - - [Fact] - public void GremlinQueryTextShouldReturnSimpleEdgeStep() - { - var reference = new RelationshipReference(123); - var query = ((IGremlinQuery)reference); - Assert.Equal("g.e(p0)", query.QueryText); - Assert.Equal(123L, query.QueryParameters["p0"]); - } } } \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/RelationshipTests.cs b/Neo4jClient.Tests/RelationshipTests.cs similarity index 98% rename from Neo4jClient.Tests.Shared/RelationshipTests.cs rename to Neo4jClient.Tests/RelationshipTests.cs index 1ae74d375..2f8dfbe16 100644 --- a/Neo4jClient.Tests.Shared/RelationshipTests.cs +++ b/Neo4jClient.Tests/RelationshipTests.cs @@ -1,10 +1,9 @@ using System; using System.Linq; using FluentAssertions; -using Neo4jClient.Test.Fixtures; using Xunit; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class RelationshipTests : IClassFixture diff --git a/Neo4jClient.Tests/Relationships/OwnedBy.cs b/Neo4jClient.Tests/Relationships/OwnedBy.cs new file mode 100644 index 000000000..db93862d2 --- /dev/null +++ b/Neo4jClient.Tests/Relationships/OwnedBy.cs @@ -0,0 +1,19 @@ +// using Neo4jClient.Tests.Domain; +// +// namespace Neo4jClient.Tests.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/Neo4jClient.Tests.Shared/Relationships/Requires.cs b/Neo4jClient.Tests/Relationships/Requires.cs similarity index 87% rename from Neo4jClient.Tests.Shared/Relationships/Requires.cs rename to Neo4jClient.Tests/Relationships/Requires.cs index 4656d67ca..fb88d7964 100644 --- a/Neo4jClient.Tests.Shared/Relationships/Requires.cs +++ b/Neo4jClient.Tests/Relationships/Requires.cs @@ -1,6 +1,6 @@ -using Neo4jClient.Test.Domain; +using Neo4jClient.Tests.Domain; -namespace Neo4jClient.Test.Relationships +namespace Neo4jClient.Tests.Relationships { public class Requires : Relationship, diff --git a/Neo4jClient.Tests.Shared/Relationships/StoredIn.cs b/Neo4jClient.Tests/Relationships/StoredIn.cs similarity index 84% rename from Neo4jClient.Tests.Shared/Relationships/StoredIn.cs rename to Neo4jClient.Tests/Relationships/StoredIn.cs index 9e24ab5cf..d896bd4b7 100644 --- a/Neo4jClient.Tests.Shared/Relationships/StoredIn.cs +++ b/Neo4jClient.Tests/Relationships/StoredIn.cs @@ -1,6 +1,6 @@ -using Neo4jClient.Test.Domain; +using Neo4jClient.Tests.Domain; -namespace Neo4jClient.Test.Relationships +namespace Neo4jClient.Tests.Relationships { public class StoredIn : Relationship, diff --git a/Neo4jClient.Tests.Shared/RestTestHarness.cs b/Neo4jClient.Tests/RestTestHarness.cs similarity index 82% rename from Neo4jClient.Tests.Shared/RestTestHarness.cs rename to Neo4jClient.Tests/RestTestHarness.cs index 4a391c96b..fb759cccd 100644 --- a/Neo4jClient.Tests.Shared/RestTestHarness.cs +++ b/Neo4jClient.Tests/RestTestHarness.cs @@ -3,12 +3,13 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using Neo4jClient.Execution; using Neo4jClient.Transactions; using NSubstitute; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class RestTestHarness : IEnumerable, IDisposable { @@ -20,23 +21,31 @@ public enum Neo4jVersion Neo225, Neo226, Neo23, - Neo30 + Neo30, + Neo40 } 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"; + public string BaseUri { get; } private readonly bool assertConstraintsAreMet; - public RestTestHarness() : this(true) + public RestTestHarness() + : this(true) { } - public RestTestHarness(bool assertConstraintsAreMet) + public RestTestHarness(bool assertConstraintsAreMet) + : this(assertConstraintsAreMet, "http://foo/db/data") + { + } + + public RestTestHarness(bool assertConstraintsAreMet, string baseUri) { this.assertConstraintsAreMet = assertConstraintsAreMet; + BaseUri = baseUri; } public void Add(MockRequest request, MockResponse response) @@ -58,7 +67,7 @@ public GraphClient CreateGraphClient(Neo4jVersion neoVersion) switch (neoVersion) { case Neo4jVersion.Neo19: - response = MockResponse.NeoRoot(); + response = MockResponse.NeoRoot(1,9,0); break; case Neo4jVersion.Neo20: response = MockResponse.NeoRoot20(); @@ -75,6 +84,12 @@ public GraphClient CreateGraphClient(Neo4jVersion neoVersion) case Neo4jVersion.Neo23: response = MockResponse.NeoRoot(2,3,0); break; + case Neo4jVersion.Neo30: + response = MockResponse.NeoRoot(3,0,0); + break; + case Neo4jVersion.Neo40: + response = MockResponse.NeoRoot(4,0,0); + break; default: throw new ArgumentOutOfRangeException(nameof(neoVersion), neoVersion, null); } @@ -87,17 +102,17 @@ public GraphClient CreateGraphClient(Neo4jVersion neoVersion) return graphClient; } - public ITransactionalGraphClient CreateAndConnectTransactionalGraphClient() + public async Task CreateAndConnectTransactionalGraphClient(Neo4jVersion version = Neo4jVersion.Neo20) { - var graphClient = CreateGraphClient(Neo4jVersion.Neo20); - graphClient.Connect(); + var graphClient = CreateGraphClient(version); + await graphClient.ConnectAsync(); return graphClient; } - public IRawGraphClient CreateAndConnectGraphClient(Neo4jVersion version = Neo4jVersion.Neo19) + public async Task CreateAndConnectGraphClient(Neo4jVersion version = Neo4jVersion.Neo19) { var graphClient = CreateGraphClient(version); - graphClient.Connect(); + await graphClient.ConnectAsync(); return graphClient; } @@ -133,15 +148,13 @@ public IHttpClient GenerateHttpClient(string baseUri) httpClient.SendAsync(Arg.Any()).ReturnsForAnyArgs(ci => { var request = ci.Arg(); - var task = new Task(() => HandleRequest(request, baseUri)); - task.Start(); - return task; + return HandleRequest(request, baseUri); }); return httpClient; } - protected virtual HttpResponseMessage HandleRequest(HttpRequestMessage request, string baseUri) + protected virtual async Task 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; @@ -154,8 +167,7 @@ protected virtual HttpResponseMessage HandleRequest(HttpRequestMessage request, if (request.Content != null) { var requestBodyTask = request.Content.ReadAsStringAsync(); - requestBodyTask.Wait(); - requestBody = requestBodyTask.Result; + requestBody = await requestBodyTask; } if (request.Method == HttpMethod.Post) @@ -173,7 +185,7 @@ protected virtual HttpResponseMessage HandleRequest(HttpRequestMessage request, 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); + var message = $"No corresponding request-response pair was defined in the test harness for: {request.Method} {requestUri.AbsoluteUri}"; if (!string.IsNullOrEmpty(requestBody)) { message += "\r\n\r\n" + requestBody; @@ -182,11 +194,9 @@ protected virtual HttpResponseMessage HandleRequest(HttpRequestMessage request, throw new InvalidOperationException(message); } - var result = results.Single(); - - processedRequests.Add(result.Key); + var (key, response) = results.Single(); - var response = result.Value; + processedRequests.Add(key); var httpResponse = new HttpResponseMessage { diff --git a/Neo4jClient.Tests.Shared/Serialization/CustomJsonDeserializerTests.cs b/Neo4jClient.Tests/Serialization/CustomJsonDeserializerTests.cs similarity index 94% rename from Neo4jClient.Tests.Shared/Serialization/CustomJsonDeserializerTests.cs rename to Neo4jClient.Tests/Serialization/CustomJsonDeserializerTests.cs index 9fab198da..4d7978368 100644 --- a/Neo4jClient.Tests.Shared/Serialization/CustomJsonDeserializerTests.cs +++ b/Neo4jClient.Tests/Serialization/CustomJsonDeserializerTests.cs @@ -3,19 +3,16 @@ using System.Globalization; using System.Linq; using System.Threading; -using System.Transactions; using FluentAssertions; -using Newtonsoft.Json.Serialization; -using NSubstitute; -using Xunit; -using Neo4jClient.ApiModels.Gremlin; using Neo4jClient.Serialization; -using Neo4jClient.Test.Fixtures; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Serialization +namespace Neo4jClient.Tests.Serialization { public class CustomJsonDeserializerTests @@ -205,15 +202,18 @@ public void DeserializeTimeZoneInfoWithDefaultJsonConverters() { // Arrange var deserializer = new CustomJsonDeserializer(GraphClient.DefaultJsonConverters); - const string ausEasternStandardTime = "AUS Eastern Standard Time"; - var content = string.Format("{{'Foo':'{0}'}}", ausEasternStandardTime); + var chosenTimeZone = new [] {"AUS Eastern Standard Time", "Australia/Perth"} + .Where(s => TimeZoneInfo.GetSystemTimeZones().Any(tz => tz.Id.Equals(s))) + .FirstOrDefault(tz => tz != null); + Assert.NotNull(chosenTimeZone); // if this fails try adding a timezone that your platform has to the list above + var content = $"{{'Foo':'{chosenTimeZone}'}}"; // Act var result = deserializer.Deserialize(content); // Assert Assert.NotNull(result.Foo); - Assert.Equal(TimeZoneInfo.FindSystemTimeZoneById(ausEasternStandardTime).DisplayName, + Assert.Equal(TimeZoneInfo.FindSystemTimeZoneById(chosenTimeZone).DisplayName, result.Foo.DisplayName); } @@ -258,26 +258,6 @@ public class TimeSpanModel public TimeSpan Foo { get; set; } } - [Fact] - 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.True(result.Columns.Any(c => c == "ColumnA")); - Assert.True(data.Any(d => d == "DataA")); - Assert.True(data.Any(d => d == "DataB")); - } - public class EnumModel { [JsonProperty] diff --git a/Neo4jClient.Tests.Shared/Serialization/CustomJsonSerializerTests.cs b/Neo4jClient.Tests/Serialization/CustomJsonSerializerTests.cs similarity index 81% rename from Neo4jClient.Tests.Shared/Serialization/CustomJsonSerializerTests.cs rename to Neo4jClient.Tests/Serialization/CustomJsonSerializerTests.cs index 2ade27c91..5056dcd24 100644 --- a/Neo4jClient.Tests.Shared/Serialization/CustomJsonSerializerTests.cs +++ b/Neo4jClient.Tests/Serialization/CustomJsonSerializerTests.cs @@ -1,12 +1,12 @@ using System; using System.Globalization; +using System.Linq; using FluentAssertions; using Neo4jClient.Serialization; -using Neo4jClient.Test.Fixtures; using Newtonsoft.Json; using Xunit; -namespace Neo4jClient.Test.Serialization +namespace Neo4jClient.Tests.Serialization { public class CustomJsonSerializerTests : IClassFixture { @@ -28,7 +28,7 @@ public void ShouldSerializeDateTimeInCorrectStringFormat(string dateTimeStr) //Assert var expected = - "{\r\n \"DateTime\": \"" + dateTimeStr + "\",\r\n \"DateTimeNullable\": \"" + dateTimeStr + "\"\r\n}"; + $"{{{Environment.NewLine} \"DateTime\": \"{dateTimeStr}\",{Environment.NewLine} \"DateTimeNullable\": \"{dateTimeStr}\"{Environment.NewLine}}}"; Assert.Equal(expected, actual); } @@ -106,7 +106,7 @@ public void JsonSerializerShouldNotSerializeNullProperties() // Act var result = serializer.Serialize(testNode); - const string expectedValue = "{\r\n \"Foo\": \"foo\"\r\n}"; + string expectedValue = $"{{{Environment.NewLine} \"Foo\": \"foo\"{Environment.NewLine}}}"; // Assert Assert.Equal(expectedValue, result); @@ -125,7 +125,7 @@ public void JsonSerializerShouldSerializeAllProperties() // Act var result = serializer.Serialize(testNode); - const string expectedValue = "{\r\n \"Foo\": \"foo\",\r\n \"Bar\": \"bar\"\r\n}"; + string expectedValue = $"{{{Environment.NewLine} \"Foo\": \"foo\",{Environment.NewLine} \"Bar\": \"bar\"{Environment.NewLine}}}"; // Assert Assert.Equal(expectedValue, result); @@ -145,7 +145,7 @@ public void JsonSerializerShouldSerializeEnumToString() // Act var result = serializer.Serialize(testNode); - const string expectedValue = "{\r\n \"Status\": \"Value1\"\r\n}"; + string expectedValue = $"{{{Environment.NewLine} \"Status\": \"Value1\"{Environment.NewLine}}}"; // Assert Assert.Equal(expectedValue, result); @@ -160,14 +160,17 @@ public void JsonSerializerShouldSerializeTimeZoneInfo() JsonConverters = GraphClient.DefaultJsonConverters }; - const string ausEasternStandardTime = "AUS Eastern Standard Time"; - var timeZoneData = TimeZoneInfo.FindSystemTimeZoneById(ausEasternStandardTime); + var chosenTimeZone = new [] {"AUS Eastern Standard Time", "Australia/Perth"} + .Where(s => TimeZoneInfo.GetSystemTimeZones().Any(tz => tz.Id.Equals(s))) + .FirstOrDefault(tz => tz != null); + Assert.NotNull(chosenTimeZone); // if this fails try adding a timezone that your platform has to the list above + var timeZoneData = TimeZoneInfo.FindSystemTimeZoneById(chosenTimeZone); // Act var result = serializer.Serialize(timeZoneData); // Assert - Assert.Equal(ausEasternStandardTime, result.Replace("\"", "")); + Assert.Equal(chosenTimeZone, result.Replace("\"", "")); } [Fact] @@ -189,7 +192,7 @@ public void JsonSerializerWithEnumConverterShouldConvertEnumToStringValues() } }; - const string expected = "{\r\n \"Gender\": \"Female\",\r\n \"GenderNullable\": \"Male\"\r\n}"; + string expected = $"{{{Environment.NewLine} \"Gender\": \"Female\",{Environment.NewLine} \"GenderNullable\": \"Male\"{Environment.NewLine}}}"; // Act var result = serializer.Serialize(testClass); @@ -212,7 +215,7 @@ public void SerializeTimeSpan() // Act var result = serializer.Serialize(model); - const string expected = "{\r\n \"Foo\": \"400.13:03:02.0100000\"\r\n}"; + string expected = $"{{{Environment.NewLine} \"Foo\": \"400.13:03:02.0100000\"{Environment.NewLine}}}"; result.Should().Be(expected); } @@ -232,8 +235,8 @@ public void ShouldSerializeDateTimeOffsetInCorrectStringFormat() 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}"; + string expected = + $"{{{Environment.NewLine} \"DateTime\": \"2012-08-31T00:11:00.3642578+10:00\",{Environment.NewLine} \"DateTimeNullable\": \"2012-08-31T00:11:00.3642578+10:00\"{Environment.NewLine}}}"; Assert.Equal(expected, actual); } @@ -250,7 +253,7 @@ public void ShouldSerializeNullableBoolToJsonBooleanUsingDefaultJsonConverters() // Act var result = serializer.Serialize(testNode); - const string expectedValue = "{\r\n \"Bar\": true\r\n}"; + string expectedValue = $"{{{Environment.NewLine} \"Bar\": true{Environment.NewLine}}}"; // Assert Assert.Equal(expectedValue, result); @@ -269,7 +272,7 @@ public void ShouldSerializeNullableInt32ToJsonNumberUsingDefaultJsonConverters() // Act var result = serializer.Serialize(testNode); - const string expectedValue = "{\r\n \"Foo\": 123\r\n}"; + string expectedValue = $"{{{Environment.NewLine} \"Foo\": 123{Environment.NewLine}}}"; // Assert Assert.Equal(expectedValue, result); diff --git a/Neo4jClient.Tests.Shared/Serialization/CypherJsonDeserializerTests.cs b/Neo4jClient.Tests/Serialization/CypherJsonDeserializerTests.cs similarity index 96% rename from Neo4jClient.Tests.Shared/Serialization/CypherJsonDeserializerTests.cs rename to Neo4jClient.Tests/Serialization/CypherJsonDeserializerTests.cs index 08debad10..7a92e02b7 100644 --- a/Neo4jClient.Tests.Shared/Serialization/CypherJsonDeserializerTests.cs +++ b/Neo4jClient.Tests/Serialization/CypherJsonDeserializerTests.cs @@ -1,15 +1,13 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Linq; -using NSubstitute; -using Xunit; using Neo4jClient.Cypher; using Neo4jClient.Serialization; -using Neo4jClient.Test.Fixtures; using Newtonsoft.Json; +using NSubstitute; +using Xunit; -namespace Neo4jClient.Test.Serialization +namespace Neo4jClient.Tests.Serialization { public class CypherJsonDeserializerTests : IClassFixture @@ -89,7 +87,7 @@ public void DeserializeShouldPreserveOffsetValues(CypherResultMode resultMode, C var content = string.Format(contentFormat, input); // Act - var result = deserializer.Deserialize(content).Single(); + var result = deserializer.Deserialize(content, false).Single(); // Assert if (expectedResult == null) @@ -112,7 +110,7 @@ public void DeserializeDateShouldPreserveKind(string dateTime, DateTimeKind kind var content = string.Format(ProjectionModeContentFormat, dateTime); //Act - var result = deserializer.Deserialize(content).Single(); + var result = deserializer.Deserialize(content, false).Single(); //Assert Assert.Equal(result.Foo.Kind, kind); @@ -128,7 +126,7 @@ public void DeserializeDateShouldPreservePointInTime(string dateTime, DateTimeKi var content = string.Format(ProjectionModeContentFormat, dateTime); //Act - var result = deserializer.Deserialize(content).Single(); + var result = deserializer.Deserialize(content, false).Single(); //Assert Assert.Equal(result.Foo.ToUniversalTime(), DateTime.Parse(dateTime).ToUniversalTime()); @@ -213,7 +211,7 @@ public void DeserializeShouldMapNodesInSetMode() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(3, results.Count()); @@ -279,7 +277,7 @@ public void DeserializeShouldMapRelationshipsInSetMode() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(3, results.Count()); @@ -411,7 +409,7 @@ public void DeserializeShouldMapIEnumerableOfRelationshipsInAProjectionMode() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert var result = results[0]; @@ -481,7 +479,7 @@ public void DeserializeShouldMapIEnumerableOfNodesReturnedByCollectInAProjection }".Replace('\'', '"'); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); Assert.IsAssignableFrom>(results); Assert.Equal(1, results.Count()); @@ -604,7 +602,7 @@ ORDER BY statusupdates.PostTime DESC ]]}".Replace('\'', '"'); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); Assert.Equal(1, results.Count()); Assert.NotNull(results[0].Post); @@ -634,7 +632,7 @@ public void DeserializeShouldMapNullIEnumerableOfNodesReturnedByCollectInInAProj }" .Replace('\'', '"'); - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); Assert.IsAssignableFrom>(results); Assert.Equal(1, results.Count()); @@ -654,7 +652,7 @@ public void DeserializeShouldMapIEnumerableOfStringsInAProjectionMode() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray().First().Names.ToArray(); + var results = deserializer.Deserialize(content, false).ToArray().First().Names.ToArray(); // Assert Assert.Equal("Ben Tu", results[0]); @@ -673,7 +671,7 @@ public void DeserializeShouldMapIEnumerableOfStringsThatAreEmptyInAProjectionMod }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(0, results.First().Names.Count()); @@ -691,7 +689,7 @@ public void DeserializeShouldMapIEnumerableOfStringsInSetMode() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray().First().ToArray(); + var results = deserializer.Deserialize(content, false).ToArray().First().ToArray(); // Assert Assert.Equal("Ben Tu", results[0]); @@ -710,7 +708,7 @@ public void DeserializeShouldMapArrayOfStringsInSetMode() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray().First().ToArray(); + var results = deserializer.Deserialize(content, false).ToArray().First().ToArray(); // Assert Assert.Equal("Ben Tu", results[0]); @@ -729,7 +727,7 @@ public void DeserializeShouldMapArrayOfIntegersInSetMode() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray().First().ToArray(); + var results = deserializer.Deserialize(content, false).ToArray().First().ToArray(); // Assert Assert.Equal(666, results[0]); @@ -748,7 +746,7 @@ public void DeserializeShouldMapIntegerInSetMode() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(666, results.First()); @@ -785,7 +783,7 @@ public void DeserializeShouldRespectJsonPropertyAttribute() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal("Bob", results.Single().Name); @@ -822,7 +820,7 @@ public void DeserializeShouldMapNullCollectResultsWithOtherProperties() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(1, results.Count()); @@ -864,7 +862,7 @@ public void DeserializeShouldMapNullCollectResultsWithOtherProperties_Test2() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(1, results.Count()); @@ -883,7 +881,7 @@ public void DeserializeShouldMapNullCollectResultsWithOtherProperties_Test3() var content = @"{'columns':['Fans'],'data':[[[null,null]]]}".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(1, results.Count()); @@ -900,7 +898,7 @@ public void DeserializeShouldMapNullResult() var content = @"{ 'columns' : [ 'db' ], 'data' : [ [ null ] ] }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(1, results.Count()); @@ -930,7 +928,7 @@ static void DeserializeShouldMapProjectionIntoAnonymousType(TAnon dummy) }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(3, results.Count()); @@ -970,7 +968,7 @@ static void DeserializeShouldMapProjectionIntoAnonymousTypeWithNullCollectResult }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(1, results.Count()); @@ -1087,7 +1085,7 @@ private class StateCityAndLabelWithNode public IEnumerable> Cities { get; set; } } - [Fact] + [Fact (Skip = "Not valid anymore?")] public void DeserializeNestedObjectsInTransactionReturningNode() { var client = Substitute.For(); @@ -1148,7 +1146,7 @@ public void DeserializeNestedObjectsInTransactionReturningNode() ] } ]}"; - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); Assert.Equal(1, results.Length); var result = results[0]; Assert.Equal("Baja California", result.State.Name); @@ -1190,7 +1188,7 @@ public void DeserializeNestedObjectsInTransaction() ] } ]}"; - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); Assert.Equal(1, results.Length); var result = results[0]; Assert.Equal("Baja California", result.State.Name); @@ -1229,7 +1227,7 @@ public void DeserializerTwoLevelProjectionInTransaction() ] } ]}"; - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); Assert.Equal(2, results.Length); var city = results[0]; Assert.Equal("Sydney", city.City.Name); @@ -1261,7 +1259,7 @@ public void DeserializerProjectionInTransaction() ] } ]}"; - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); Assert.Equal(2, results.Length); var city = results[0]; Assert.Equal("Sydney", city.Name); @@ -1288,7 +1286,7 @@ public void DeserializeSimpleSetInTransaction() ] } ]}"; - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); Assert.Equal(1, results.Length); Assert.Equal(3, results[0]); } @@ -1312,7 +1310,7 @@ public void DeserializeResultsSetInTransaction() ] } ]}"; - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); Assert.Equal(1, results.Length); var city = results[0]; Assert.Equal("Sydney", city.Name); @@ -1335,7 +1333,7 @@ public void DeserializeShouldPreserveUtf8Characters() }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert Assert.Equal(3, results.Count()); @@ -1404,7 +1402,7 @@ public void DeserializeShouldMapNodesToObjectsInSetModeWhenTheSourceLooksLikeANo }".Replace("'", "\""); // Act - var results = deserializer.Deserialize(content).ToArray(); + var results = deserializer.Deserialize(content, false).ToArray(); // Assert var resultsArray = results.ToArray(); @@ -1427,7 +1425,7 @@ public void BadJsonShouldThrowExceptionThatIncludesJson() const string content = @"xyz-json-zyx"; var ex = Assert.Throws(() => - deserializer.Deserialize(content) + deserializer.Deserialize(content, false) ); Assert.Contains(content, ex.Message); } @@ -1439,7 +1437,7 @@ public void BadJsonShouldThrowExceptionThatIncludesFullNameOfTargetType() var deserializer = new CypherJsonDeserializer(client, CypherResultMode.Set, CypherResultFormat.DependsOnEnvironment); var ex = Assert.Throws(() => - deserializer.Deserialize("xyz-json-zyx") + deserializer.Deserialize("xyz-json-zyx", true) ); Assert.Contains(typeof(Asset).FullName, ex.Message); } @@ -1458,7 +1456,7 @@ public void ClassWithoutDefaultPublicConstructorShouldThrowExceptionThatExplains 'columns' : [ 'Cities' ] }".Replace("'", "\""); - var ex = Assert.Throws(() => deserializer.Deserialize(content)); + var ex = Assert.Throws(() => deserializer.Deserialize(content, false)); Assert.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); } @@ -1491,7 +1489,7 @@ public void DeserializeInt64IntoNullableInt64() }".Replace("'", "\""); // Act - var result = deserializer.Deserialize(content).First(); + var result = deserializer.Deserialize(content, false).First(); // Assert Assert.Equal(123, result); @@ -1515,7 +1513,7 @@ public void DeserializeBase64StringIntoByteArrayInProjectionResultMode() }".Replace("'", "\""); // Act - var result = deserializer.Deserialize(content).First(); + var result = deserializer.Deserialize(content, false).First(); // Assert Assert.Equal(new byte[] {1, 2, 3, 4}, result.Array); @@ -1534,7 +1532,7 @@ public void DeserializeBase64StringIntoByteArrayInSetResultMode() }".Replace("'", "\""); // Act - var result = deserializer.Deserialize(content).First(); + var result = deserializer.Deserialize(content, false).First(); // Assert Assert.Equal(new byte[] { 1, 2, 3, 4 }, result); diff --git a/Neo4jClient.Tests.Shared/Serialization/SerializesDateTimeAsNeoDateTimeTests.cs b/Neo4jClient.Tests/Serialization/SerializesDateTimeAsNeoDateTimeTests.cs similarity index 59% rename from Neo4jClient.Tests.Shared/Serialization/SerializesDateTimeAsNeoDateTimeTests.cs rename to Neo4jClient.Tests/Serialization/SerializesDateTimeAsNeoDateTimeTests.cs index a3f3b5cdf..357f1ca33 100644 --- a/Neo4jClient.Tests.Shared/Serialization/SerializesDateTimeAsNeoDateTimeTests.cs +++ b/Neo4jClient.Tests/Serialization/SerializesDateTimeAsNeoDateTimeTests.cs @@ -1,15 +1,12 @@ using System; using System.Collections.Generic; -using System.Text; +using System.Threading.Tasks; using FluentAssertions; using Moq; -using Neo4j.Driver.V1; -using Neo4jClient.Test.Extensions; -using Neo4jClient.Test.Fixtures; -using Newtonsoft.Json; +using Neo4j.Driver; using Xunit; -namespace Neo4jClient.Tests.Shared.Serialization +namespace Neo4jClient.Tests.Serialization { public class SerializesDateTimeAsNeoDateTimeTests : IClassFixture { @@ -20,19 +17,19 @@ private class ClassWithNeo4jDateTime } [Fact] - public void SerializesDateTimeAsNeoDateInBolt() + public async Task SerializesDateTimeAsNeoDateInBolt() { - var mockSession = new Mock(); - mockSession.Setup(s => s.Run("CALL dbms.components()")).Returns(new BoltGraphClientTests.ServerInfo()); + var mockSession = new Mock(); + mockSession.Setup(s => s.RunAsync("CALL dbms.components()")).Returns(Task.FromResult(new BoltGraphClientTests.BoltGraphClientTests.ServerInfo())); var dt = new DateTime(2000, 1, 1, 0, 0, 0); var mockDriver = new Mock(); - mockDriver.Setup(d => d.Session(It.IsAny())).Returns(mockSession.Object); - mockDriver.Setup(d => d.Session(It.IsAny(), It.IsAny>())).Returns(mockSession.Object); - mockDriver.Setup(d => d.Uri).Returns(new Uri("bolt://localhost")); + mockDriver.Setup(d => d.AsyncSession(It.IsAny>())).Returns(mockSession.Object); + + // mockDriver.Setup(d => d.Uri).Returns(new Uri("bolt://localhost")); var bgc = new BoltGraphClient(mockDriver.Object); - bgc.Connect(); + await bgc.ConnectAsync(); var cwd = new ClassWithNeo4jDateTime { Dt = dt }; ; diff --git a/Neo4jClient.Tests.Shared/Serialization/UserSuppliedSerializationTests.cs b/Neo4jClient.Tests/Serialization/UserSuppliedSerializationTests.cs similarity index 77% rename from Neo4jClient.Tests.Shared/Serialization/UserSuppliedSerializationTests.cs rename to Neo4jClient.Tests/Serialization/UserSuppliedSerializationTests.cs index 4edaa8e12..de3fa5ecb 100644 --- a/Neo4jClient.Tests.Shared/Serialization/UserSuppliedSerializationTests.cs +++ b/Neo4jClient.Tests/Serialization/UserSuppliedSerializationTests.cs @@ -1,11 +1,10 @@ using System; using System.Globalization; -using Xunit; using Neo4jClient.Serialization; -using Neo4jClient.Test.Fixtures; using Newtonsoft.Json; +using Xunit; -namespace Neo4jClient.Test.Serialization +namespace Neo4jClient.Tests.Serialization { public class UserSuppliedSerializationTests : IClassFixture @@ -116,8 +115,8 @@ public void ShouldSerializeCustomValueWithCustomJsonConverter() var rawResult = serializer.Serialize(model); //Assert - const string expectedRawOutput = - "{\r\n \"CustomValue\": \"op\"\r\n}"; + string expectedRawOutput = + $"{{{Environment.NewLine} \"CustomValue\": \"op\"{Environment.NewLine}}}"; Assert.Equal(expectedRawOutput, rawResult); } @@ -127,8 +126,8 @@ public void ShouldSerializeCustomValueWithCustomJsonConverter() public void ShouldDeserializeCustomValueWithCustomJsonConverter() { //Arrange - const string rawInput = - "{\r\n \"CustomValue\": \"op\"\r\n}"; + string rawInput = + $"{{{Environment.NewLine} \"CustomValue\": \"op\"{Environment.NewLine}}}"; var serializer = new CustomJsonDeserializer(new []{new TestValueAConverter()}); @@ -165,8 +164,8 @@ public void ShouldSerializeCustomTypeThatHasTypeConverterUsingTypeConverterBased var rawResult = serializer.Serialize(model); //Assert - const string expectedRawOutput = - "{\r\n \"CustomValue\": \"op\"\r\n}"; + string expectedRawOutput = + $"{{{Environment.NewLine} \"CustomValue\": \"op\"{Environment.NewLine}}}"; Assert.Equal(expectedRawOutput, rawResult); } @@ -176,8 +175,8 @@ public void ShouldSerializeCustomTypeThatHasTypeConverterUsingTypeConverterBased public void ShouldDeserializeCustomTypeThatHasTypeConverterUsingTypeConverterBasedJsonConverter() { //Arrange - const string rawInput = - "{\r\n \"CustomValue\": \"op\"\r\n}"; + string rawInput = + $"{{{Environment.NewLine} \"CustomValue\": \"op\"{Environment.NewLine}}}"; var serializer = new CustomJsonDeserializer(new JsonConverter[]{new TypeConverterBasedJsonConverter()}); @@ -210,8 +209,7 @@ public void ShouldSerializeBuiltInTypeThatHasTypeConverterUsingTypeConverterBase var rawResult = serializer.Serialize(model); //Assert - const string expectedRawOutput = - "{\r\n \"MyPoint\": \"100, 200\"\r\n}"; + string expectedRawOutput = $"{{{Environment.NewLine} \"MyPoint\": \"100, 200\"{Environment.NewLine}}}"; Assert.Equal(expectedRawOutput, rawResult); } @@ -221,8 +219,7 @@ public void ShouldSerializeBuiltInTypeThatHasTypeConverterUsingTypeConverterBase public void ShouldDeserializeBuiltInTypeThatHasTypeConverterUsingTypeConverterBasedJsonConverter() { //Arrange - const string rawInput = - "{\r\n \"MyPoint\": \"100, 200\"\r\n}"; + string rawInput = $"{{{Environment.NewLine} \"MyPoint\": \"100, 200\"{Environment.NewLine}}}"; var serializer = new CustomJsonDeserializer(new JsonConverter[] { new TypeConverterBasedJsonConverter() }); @@ -234,5 +231,39 @@ public void ShouldDeserializeBuiltInTypeThatHasTypeConverterUsingTypeConverterBa Assert.NotNull(model.MyPoint); Assert.Equal(new System.Drawing.Point(100, 200), model.MyPoint); } + + + public class TestNodeWithSomeNeo4jIgnoreAttributes + { + public string Text { get; set; } + [Neo4jIgnore] + public string TextIgnore { get; set; } + public int TestInt { get; set; } + [Neo4jIgnore] + public int TestNeo4jIntIgnore { get; set; } + [JsonIgnore] + public int TestJsonIntIgnore { get; set; } + } + + [Fact] + //[Description("test https part of https://github.com/Readify/Neo4jClient/issues/336 https://github.com/Readify/Neo4jClient/pull/337 - see BoltGraphClientTests for bolt part")] + public void JsonSerializerShouldNotSerializeNeo4JIgnoreAttribute() + { + // Arrange + var testNode = new TestNodeWithSomeNeo4jIgnoreAttributes { Text = "foo", TextIgnore = "fooignore", TestInt = 42, TestNeo4jIntIgnore = 42, TestJsonIntIgnore = 42 }; + var serializer = new CustomJsonSerializer + { + NullHandling = NullValueHandling.Ignore, + JsonConverters = GraphClient.DefaultJsonConverters + }; + + // Act + var result = serializer.Serialize(testNode); + + const string expectedValue = "{\r\n \"Text\": \"foo\",\r\n \"TestInt\": 42\r\n}"; + + // Assert + Assert.Equal(expectedValue, result); + } } } diff --git a/Neo4jClient.Tests.Shared/StatementResultHelperTests.cs b/Neo4jClient.Tests/StatementResultHelperTests.cs similarity index 98% rename from Neo4jClient.Tests.Shared/StatementResultHelperTests.cs rename to Neo4jClient.Tests/StatementResultHelperTests.cs index 3d6ad6921..573fc3b0e 100644 --- a/Neo4jClient.Tests.Shared/StatementResultHelperTests.cs +++ b/Neo4jClient.Tests/StatementResultHelperTests.cs @@ -4,17 +4,16 @@ using System.Linq; using FluentAssertions; using Moq; -using Neo4j.Driver.V1; +using Neo4j.Driver; using Neo4jClient.Cypher; using Neo4jClient.Serialization; -using Neo4jClient.Test.Fixtures; using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Newtonsoft.Json.Serialization; using NSubstitute; using Xunit; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class StatementResultHelperTests { @@ -189,11 +188,11 @@ public void DeserializeRecordWithListCorrectly() var mockDeserializer = new Mock>(); mockDeserializer - .Setup(d => d.Deserialize(It.IsAny())) + .Setup(d => d.Deserialize(It.IsAny(), false)) .Returns(new List()); record.Deserialize(mockDeserializer.Object, CypherResultMode.Projection); - mockDeserializer.Verify(d => d.Deserialize(expectedContent), Times.Once); + mockDeserializer.Verify(d => d.Deserialize(expectedContent, false), Times.Once); } [Fact] @@ -207,11 +206,11 @@ public void DeserializeSetCorrectly() var mockDeserializer = new Mock>(); mockDeserializer - .Setup(d => d.Deserialize(It.IsAny())) + .Setup(d => d.Deserialize(It.IsAny(), false)) .Returns(new List()); record.Deserialize(mockDeserializer.Object, CypherResultMode.Set); - mockDeserializer.Verify(d => d.Deserialize(expectedContent), Times.Once); + mockDeserializer.Verify(d => d.Deserialize(expectedContent, false), Times.Once); } [Fact] @@ -237,11 +236,11 @@ public void DeserializesAnonymousNestedObjectCorrectly() var expectedContent = "{ \"columns\":[\"x\"], \"data\":[[ [{{ \"Info\":{\"A\":\"a\",\"B\":\"b\"} }}] ]] }"; var mockDeserializer = new Mock>(); mockDeserializer - .Setup(d => d.Deserialize(It.IsAny())) + .Setup(d => d.Deserialize(It.IsAny(), false)) .Returns(new List()); record.Deserialize(mockDeserializer.Object, CypherResultMode.Set); - mockDeserializer.Verify(d => d.Deserialize(expectedContent), Times.Once); + mockDeserializer.Verify(d => d.Deserialize(expectedContent, false), Times.Once); } // [Fact] @@ -262,11 +261,11 @@ public void DeserializesPlainObjectCorrectly() var mockDeserializer = new Mock>(); mockDeserializer - .Setup(d => d.Deserialize(It.IsAny())) + .Setup(d => d.Deserialize(It.IsAny(), false)) .Returns(new List()); record.Deserialize(mockDeserializer.Object, CypherResultMode.Projection); - mockDeserializer.Verify(d => d.Deserialize(expectedContent), Times.Once); + mockDeserializer.Verify(d => d.Deserialize(expectedContent, false), Times.Once); } [Fact] @@ -281,11 +280,11 @@ public void DeserializesListOfObjectsCorrectly() var mockDeserializer = new Mock>(); mockDeserializer - .Setup(d => d.Deserialize(It.IsAny())) + .Setup(d => d.Deserialize(It.IsAny(), false)) .Returns(new List()); record.Deserialize(mockDeserializer.Object, CypherResultMode.Projection); - mockDeserializer.Verify(d => d.Deserialize(expectedContent), Times.Once); + mockDeserializer.Verify(d => d.Deserialize(expectedContent, false), Times.Once); } [Fact] @@ -310,11 +309,11 @@ public void DeserilizesProjectedListCorrectly() var mockDeserializer = new Mock>(); mockDeserializer - .Setup(d => d.Deserialize(It.IsAny())) + .Setup(d => d.Deserialize(It.IsAny(), false)) .Returns(new List()); record.Deserialize(mockDeserializer.Object, CypherResultMode.Projection); - mockDeserializer.Verify(d => d.Deserialize(expectedContent), Times.Once); + mockDeserializer.Verify(d => d.Deserialize(expectedContent, false), Times.Once); } [Fact] @@ -327,11 +326,11 @@ public void DeserializesStringContentCorrectly() var mockDeserializer = new Mock>(); mockDeserializer - .Setup(d => d.Deserialize(It.IsAny())) + .Setup(d => d.Deserialize(It.IsAny(), false)) .Returns(new List()); record.Deserialize(mockDeserializer.Object, CypherResultMode.Set); - mockDeserializer.Verify(d => d.Deserialize(expectedContent), Times.Once); + mockDeserializer.Verify(d => d.Deserialize(expectedContent, false), Times.Once); } [Fact] @@ -344,11 +343,11 @@ public void DeserializesIntContentCorrectly() var mockDeserializer = new Mock>(); mockDeserializer - .Setup(d => d.Deserialize(It.IsAny())) + .Setup(d => d.Deserialize(It.IsAny(), false)) .Returns(new List()); record.Deserialize(mockDeserializer.Object, CypherResultMode.Set); - mockDeserializer.Verify(d => d.Deserialize(expectedContent), Times.Once); + mockDeserializer.Verify(d => d.Deserialize(expectedContent, false), Times.Once); } [Fact] @@ -362,11 +361,11 @@ public void HandlesNullValuesCorrectly() var mockDeserializer = new Mock>(); mockDeserializer - .Setup(d => d.Deserialize(It.IsAny())) + .Setup(d => d.Deserialize(It.IsAny(), false)) .Returns(new List()); record.Deserialize(mockDeserializer.Object, CypherResultMode.Projection); - mockDeserializer.Verify(d => d.Deserialize(expectedContent), Times.Once); + mockDeserializer.Verify(d => d.Deserialize(expectedContent, false), Times.Once); } } diff --git a/Neo4jClient.Tests.Shared/TestUtilities.cs b/Neo4jClient.Tests/TestUtilities.cs similarity index 96% rename from Neo4jClient.Tests.Shared/TestUtilities.cs rename to Neo4jClient.Tests/TestUtilities.cs index 7281c698a..24a1f2307 100644 --- a/Neo4jClient.Tests.Shared/TestUtilities.cs +++ b/Neo4jClient.Tests/TestUtilities.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; -namespace Neo4jClient.Tests.Shared +namespace Neo4jClient.Tests { public static class TestUtilities { diff --git a/Neo4jClient.Tests.Shared/TestsImplementFixture.cs b/Neo4jClient.Tests/TestsImplementFixture.cs similarity index 96% rename from Neo4jClient.Tests.Shared/TestsImplementFixture.cs rename to Neo4jClient.Tests/TestsImplementFixture.cs index db6d824b9..112e3f1ec 100644 --- a/Neo4jClient.Tests.Shared/TestsImplementFixture.cs +++ b/Neo4jClient.Tests/TestsImplementFixture.cs @@ -1,10 +1,9 @@ using System; using System.Linq; using System.Reflection; -using Neo4jClient.Test.Fixtures; using Xunit; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class TestsImplementFixture : IClassFixture { diff --git a/Neo4jClient.Tests/Transactions/BoltClientTestHelper.cs b/Neo4jClient.Tests/Transactions/BoltClientTestHelper.cs new file mode 100644 index 000000000..7b1ece18c --- /dev/null +++ b/Neo4jClient.Tests/Transactions/BoltClientTestHelper.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using FluentAssertions; +using Neo4j.Driver; +using Neo4jClient.Tests.BoltGraphClientTests; +using NSubstitute; + +namespace Neo4jClient.Tests.Transactions +{ + public static class BoltClientTestHelper + { + public static IResultCursor GetDbmsComponentsResponse() + { + var record = Substitute.For(); + record["name"].Returns("neo4j kernel"); + record["versions"].Returns(new List { "3.1.0" }); + + var response = new List { record }; + + var statementResult = new TestStatementResult(response); + return statementResult; + } + + public static void GetDriverAndSession(out IDriver driver, out IAsyncSession session, out IAsyncTransaction transaction) + { + var mockNode = Substitute.For(); + mockNode["Name"].Returns("Value"); + mockNode.Labels.Returns(new List { "Node" }); + mockNode.Properties.Returns(new Dictionary { { "Name", "Value" } }); + + var mockRecord = Substitute.For(); + mockRecord.Keys.Returns(new List { "Node" }); + mockRecord["Node"].Returns(mockNode); + mockRecord.Values["Node"].Returns(mockNode); + + var mockStatementResult = new TestStatementResult(new List(new[] { mockRecord })); + + var mockTransaction = Substitute.For(); + mockTransaction.RunAsync(Arg.Any(), Arg.Any>()).Returns(mockStatementResult); + + var task = Task.FromResult(mockTransaction); + task.Result.Should().NotBe(null); + + var mockSession = Substitute.For(); + var dbmsReturn = GetDbmsComponentsResponse(); + mockSession.RunAsync("CALL dbms.components()").Returns(dbmsReturn); + mockSession.BeginTransactionAsync().Returns(Task.FromResult(mockTransaction)); + mockSession.BeginTransactionAsync(Arg.Any>()).Returns(Task.FromResult(mockTransaction)); + + var mockDriver = Substitute.For(); + mockDriver.AsyncSession().Returns(mockSession); + mockDriver.AsyncSession(Arg.Any>()).Returns(mockSession); + + + driver = mockDriver; + session = mockSession; + transaction = mockTransaction; + } + + public static void GetAndConnectGraphClient(out IGraphClient graphClient, out IDriver driver, out IAsyncSession session, out IAsyncTransaction transaction) + { + GetDriverAndSession(out driver, out session, out transaction); + var client = new BoltGraphClient(driver); + client.ConnectAsync().Wait(); + + driver.ClearReceivedCalls(); + session.ClearReceivedCalls(); + graphClient = client; + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests/Transactions/BoltQueriesInTransactionTests.cs b/Neo4jClient.Tests/Transactions/BoltQueriesInTransactionTests.cs index 1b19153e9..59c0cd552 100644 --- a/Neo4jClient.Tests/Transactions/BoltQueriesInTransactionTests.cs +++ b/Neo4jClient.Tests/Transactions/BoltQueriesInTransactionTests.cs @@ -1,127 +1,25 @@ using System; -using System.Collections; -using System.Collections.Generic; using System.Linq; -using System.Transactions; +using System.Threading.Tasks; using FluentAssertions; -using Neo4j.Driver.V1; -using Neo4jClient.Test.Fixtures; +using Moq; +using Neo4j.Driver; using Neo4jClient.Transactions; using NSubstitute; using Xunit; -namespace Neo4jClient.Test.Transactions +namespace Neo4jClient.Tests.Transactions { - public class BoltQueriesInTransactionTests : IClassFixture { - private class Foo { } - #region Helper Methods - - private static IStatementResult GetDbmsComponentsResponse() - { - var record = Substitute.For(); - record["name"].Returns("neo4j kernel"); - record["versions"].Returns(new List {"3.1.0"}); - - var response = new List {record}; - - var statementResult = Substitute.For(); - statementResult.GetEnumerator().Returns(response.GetEnumerator()); - return statementResult; - } - - private static void GetDriverAndSession(out IDriver driver, out ISession session, out Neo4j.Driver.V1.ITransaction transaction) - { - var mockNode = Substitute.For(); - mockNode["Name"].Returns("Value"); - mockNode.Labels.Returns(new List() {"Node"}); - mockNode.Properties.Returns(new Dictionary() {{"Name", "Value"}}); - - var mockRecord = Substitute.For(); - mockRecord.Keys.Returns(new List(){"Node"}); - mockRecord["Node"].Returns(mockNode); - mockRecord.Values["Node"].Returns(mockNode); - - var mockStatementResult = Substitute.For(); - mockStatementResult.GetEnumerator().Returns(new List(new[]{mockRecord}).GetEnumerator()); - - var mockTransaction = Substitute.For(); - mockTransaction.Run(Arg.Any(), Arg.Any>()).Returns(mockStatementResult); - - var mockSession = Substitute.For(); - var dbmsReturn = GetDbmsComponentsResponse(); - mockSession.Run("CALL dbms.components()").Returns(dbmsReturn); - mockSession.BeginTransaction().Returns(mockTransaction); - - var mockDriver = Substitute.For(); - mockDriver.Session().Returns(mockSession); - mockDriver.Session(Arg.Any()).Returns(mockSession); - mockDriver.Session(Arg.Any(), Arg.Any>()).Returns(mockSession); - mockDriver.Session(Arg.Any>()).Returns(mockSession); - mockDriver.Uri.Returns(new Uri("bolt://localhost")); - - driver = mockDriver; - session = mockSession; - transaction = mockTransaction; - } - - private static void GetAndConnectGraphClient(out IGraphClient graphClient, out IDriver driver, out ISession session, out Neo4j.Driver.V1.ITransaction transaction) + private class Foo { - GetDriverAndSession(out driver, out session, out transaction); - var client = new BoltGraphClient(driver); - client.Connect(); - - driver.ClearReceivedCalls(); - session.ClearReceivedCalls(); - graphClient = client; } - #endregion Helper Methods public class TransactionGraphClientTests : IClassFixture { - [Fact] - public void SimulateMultipleQueries_AsSingleTransaction() - { - ISession session; - IDriver driver; - Neo4j.Driver.V1.ITransaction transaction; - IGraphClient graphClient; - - GetAndConnectGraphClient(out graphClient, out driver, out session, out transaction); - - var query = graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").Query; - - var rawGraphClient = (IRawGraphClient) graphClient; - rawGraphClient.ExecuteMultipleCypherQueriesInTransaction(new[]{query}); - - driver.Received(1).Session((IEnumerable) null); - session.Received(1).BeginTransaction(); - transaction.Received(1).Success(); - } - - [Fact] - public void SimpleTransaction_AsTransactionalGc_1Query() - { - ISession session; - IDriver driver; - Neo4j.Driver.V1.ITransaction transaction; - IGraphClient graphClient; - GetAndConnectGraphClient(out graphClient, out driver, out session, out transaction); - - ITransactionalGraphClient txGc = (ITransactionalGraphClient) graphClient; - using (var tx = txGc.BeginTransaction()) - { - txGc.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - tx.Commit(); - } - - driver.Received(1).Session((IEnumerable)null); - session.Received(1).BeginTransaction(); - transaction.Received(1).Success(); - } private class MockNode { @@ -129,199 +27,58 @@ private class MockNode } [Fact] - public void SimpleTransaction_RetrieveAndSerializeAnonymousResult() + public async Task SimpleTransaction_AsTransactionalGc_1Query() { - ISession session; - IDriver driver; - Neo4j.Driver.V1.ITransaction transaction; - IGraphClient graphClient; - - GetAndConnectGraphClient(out graphClient, out driver, out session, out transaction); + BoltClientTestHelper.GetAndConnectGraphClient(out var graphClient, out var driver, out var session, out var transaction); - ITransactionalGraphClient txGc = (ITransactionalGraphClient)graphClient; + var txGc = (ITransactionalGraphClient) graphClient; using (var tx = txGc.BeginTransaction()) { - var node = txGc.Cypher.Match("(n:Node)").Return(n => new {Node = n.As()}).Results.SingleOrDefault(); - - node.Node.Name.Should().Be("Value"); - - tx.Commit(); + await txGc.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResultsAsync(); + await tx.CommitAsync(); } - driver.Received(1).Session((IEnumerable) null); - session.Received(1).BeginTransaction(); - transaction.Received(1).Success(); + driver.Received(1).AsyncSession(Arg.Any>()); + await session.Received(1).BeginTransactionAsync(); + await transaction.Received(1).CommitAsync(); } - } - public class TransactionScopeTests : IClassFixture - { [Fact] - public void NestedJoinedTransactionScope() + public async Task SimpleTransaction_AsTransactionalGc_1Query_Moq() { - ISession session; - IDriver driver; - Neo4j.Driver.V1.ITransaction transaction; - IGraphClient graphClient; - - GetAndConnectGraphClient(out graphClient, out driver, out session, out transaction); - using (var scope = new TransactionScope()) + using (var harness = new BoltTestHarness()) { - using (var scope2 = new TransactionScope()) - { - graphClient.Cypher - .Match("n") - .Return(n => n.Count()) - .ExecuteWithoutResults(); + var graphClient = await harness.CreateAndConnectBoltGraphClient(); - // this will not commit - scope2.Complete(); + var txGc = (ITransactionalGraphClient) graphClient; + using (var tx = txGc.BeginTransaction()) + { + var query = txGc.Cypher.Match("(n)").Set("n.Value = 'test'"); + await query.ExecuteWithoutResultsAsync(); + await tx.CommitAsync(); } - // this should generate a request to the known transaction ID - graphClient.Cypher - .Match("n") - .Return(n => n.Count()) - .ExecuteWithoutResults(); - } - - driver.Received(1).Session(Arg.Any(),(IEnumerable) null); - transaction.Received(1).Failure(); - transaction.Received(1).Dispose(); - } - - [Fact] - public void SimpleTransaction_1Query() - { - ISession session; - IDriver driver; - Neo4j.Driver.V1.ITransaction transaction; - IGraphClient graphClient; - - GetAndConnectGraphClient(out graphClient, out driver, out session, out transaction); - - using (var scope = new TransactionScope()) - { - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - scope.Complete(); - } - - driver.Received(1).Session(Arg.Any(), (IEnumerable)null); - session.Received(1).BeginTransaction(); - transaction.Received(1).Run(Arg.Any(), Arg.Any>()); - transaction.Received(1).Success(); - } - - - - [Fact] - public void SimpleTransaction_2Queries() - { - ISession session; - IDriver driver; - Neo4j.Driver.V1.ITransaction transaction; - IGraphClient graphClient; - - GetAndConnectGraphClient(out graphClient, out driver, out session, out transaction); - - using (var scope = new TransactionScope()) - { - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - scope.Complete(); - } - - driver.Received(1).Session(Arg.Any(), (IEnumerable)null); - session.Received(1).BeginTransaction(); - transaction.Received(1).Success(); - transaction.Received(1).Dispose(); - session.Received(1).Dispose(); - } - - [Fact] - public void SimpleTransaction_Subsequent_2Queries() - { - ISession session; - IDriver driver; - Neo4j.Driver.V1.ITransaction transaction; - IGraphClient graphClient; - - GetAndConnectGraphClient(out graphClient, out driver, out session, out transaction); - - using (var scope = new TransactionScope()) - { - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - scope.Complete(); - } - - using (var scope = new TransactionScope()) - { - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - scope.Complete(); - } - - driver.Received(2).Session(Arg.Any(), (IEnumerable)null); - session.Received(2).BeginTransaction(); - transaction.Received(2).Success(); - transaction.Received(2).Dispose(); - session.Received(2).Dispose(); - } - - [Fact] - public void SimpleTransaction_Subsequent_WithResults_2Queries() - { - ISession session; - IDriver driver; - Neo4j.Driver.V1.ITransaction transaction; - IGraphClient graphClient; - - GetAndConnectGraphClient(out graphClient, out driver, out session, out transaction); - - using (var scope = new TransactionScope()) - { - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - var res = graphClient.Cypher.Match("(n)").Return(n => n.As()).Results; - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - scope.Complete(); + harness.MockDriver.Verify(md => md.AsyncSession(It.IsAny>()), Times.Exactly(1)); } - - using (var scope = new TransactionScope()) - { - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - var res = graphClient.Cypher.Match("(n)").Return(n => n.As()).Results; - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); - scope.Complete(); - } - - driver.Received(2).Session(Arg.Any(), (IEnumerable)null); - session.Received(2).BeginTransaction(); - transaction.Received(2).Success(); - transaction.Received(2).Dispose(); - session.Received(2).Dispose(); - driver.Received(0).Dispose(); } [Fact] - public void SimpleTransaction_1Query_Rollback() + public async Task SimpleTransaction_RetrieveAndSerializeAnonymousResult() { - ISession session; - IDriver driver; - Neo4j.Driver.V1.ITransaction transaction; - IGraphClient graphClient; - - GetAndConnectGraphClient(out graphClient, out driver, out session, out transaction); + BoltClientTestHelper.GetAndConnectGraphClient(out var graphClient, out var driver, out var session, out var transaction); - using (var scope = new TransactionScope()) + var txGc = (ITransactionalGraphClient) graphClient; + using (var tx = txGc.BeginTransaction()) { - graphClient.Cypher.Match("(n)").Set("n.Value = 'test'").ExecuteWithoutResults(); + var node = (await txGc.Cypher.Match("(n:Node)").Return(n => new {Node = n.As()}).ResultsAsync).SingleOrDefault(); + node?.Node.Name.Should().Be("Value"); + await tx.CommitAsync(); } - driver.Received(1).Session(Arg.Any(), (IEnumerable)null); - transaction.Received(1).Failure(); + driver.Received(1).AsyncSession(Arg.Any>()); + await session.Received(1).BeginTransactionAsync(); + await transaction.Received(1).CommitAsync(); } } } -} - +} \ No newline at end of file diff --git a/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs b/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs index 3b6a4231c..380ac1ffe 100644 --- a/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs +++ b/Neo4jClient.Tests/Transactions/Neo4jTransactionResourceManagerTests.cs @@ -1,14 +1,4 @@ -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 Xunit; - -namespace Neo4jClient.Test.Transactions +namespace Neo4jClient.Tests.Transactions { public class Neo4jTransactionResourceManagerTests diff --git a/Neo4jClient.Tests/Transactions/QueriesInTransactionTests.cs b/Neo4jClient.Tests/Transactions/QueriesInTransactionTests.cs index da7ac7067..a075f1a1a 100644 --- a/Neo4jClient.Tests/Transactions/QueriesInTransactionTests.cs +++ b/Neo4jClient.Tests/Transactions/QueriesInTransactionTests.cs @@ -1,33 +1,23 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Collections.Specialized; -using System.Diagnostics; -using System.Globalization; using System.Linq; -using System.Net; using System.Net.Http; -using System.Threading; using System.Threading.Tasks; -using System.Transactions; using FluentAssertions; using Neo4jClient.ApiModels.Cypher; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using Neo4jClient.Transactions; -using Newtonsoft.Json.Serialization; -using NSubstitute; using Xunit; -using TransactionScopeOption = System.Transactions.TransactionScopeOption; -namespace Neo4jClient.Test.Transactions +namespace Neo4jClient.Tests.Transactions { public partial class QueriesInTransactionTests : IClassFixture { [Fact] - public void CommitWithoutRequestsShouldNotGenerateMessage() + public async Task CommitWithoutRequestsShouldNotGenerateMessage() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{'statements': []}"); var commitTransactionRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); @@ -41,17 +31,17 @@ public void CommitWithoutRequestsShouldNotGenerateMessage() } }.ShouldNotBeCalled(initTransactionRequest, commitTransactionRequest)) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { // no requests - transaction.Commit(); + await transaction.CommitAsync(); } } } [Fact] - public void KeepAliveWithoutRequestsShouldNotGenerateMessage() + public async Task KeepAliveWithoutRequestsShouldNotGenerateMessage() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{'statements': []}"); var keepAliveRequest = MockRequest.PostJson("/transaction/1", @"{'statements': []}"); @@ -66,17 +56,17 @@ public void KeepAliveWithoutRequestsShouldNotGenerateMessage() } }.ShouldNotBeCalled(initTransactionRequest, keepAliveRequest)) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { // no requests - transaction.KeepAlive(); + await transaction.KeepAliveAsync(); } } } [Fact] - public void RollbackWithoutRequestsShouldNotGenerateMessage() + public async Task RollbackWithoutRequestsShouldNotGenerateMessage() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{'statements': []}"); var rollbackTransactionRequest = MockRequest.Delete("/transaction/1"); @@ -91,17 +81,17 @@ public void RollbackWithoutRequestsShouldNotGenerateMessage() } }.ShouldNotBeCalled(initTransactionRequest, rollbackTransactionRequest)) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { // no requests - transaction.Rollback(); + await transaction.RollbackAsync(); } } } [Fact] - public void UpdateTransactionEndpointAfterFirstRequest() + public async Task UpdateTransactionEndpointAfterFirstRequest() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); @@ -117,14 +107,14 @@ public void UpdateTransactionEndpointAfterFirstRequest() } }) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { // dummy query to generate request - client.Cypher + await client.Cypher .Match("n") .Return(n => n.Count()) - .ExecuteWithoutResults(); + .ExecuteWithoutResultsAsync(); Assert.Equal( new Uri("http://foo/db/data/transaction/1"), @@ -134,126 +124,7 @@ public void UpdateTransactionEndpointAfterFirstRequest() } [Fact] - public void ExecuteMultipleStatementInOneRequest() - { - var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}, {'statement': 'MATCH t\r\nRETURN count(t)', 'resultDataContents':[], 'parameters': {}}]}"); - var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); - using (var testHarness = new RestTestHarness - { - { - initTransactionRequest, - MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") - }, - { - commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") - } - }) - { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); - using (var transaction = client.BeginTransaction()) - { - // dummy query to generate request - var rawClient = client as IRawGraphClient; - if (rawClient == null) - { - throw new Exception("ITransactionalGraphClient is not IRawGraphClient"); - } - - var queries = new List() - { - client.Cypher - .Match("n") - .Return(n => n.Count()) - .Query, - client.Cypher - .Match("t") - .Return(t => t.Count()) - .Query - }; - - rawClient.ExecuteMultipleCypherQueriesInTransaction(queries); - transaction.Commit(); - } - } - } - - [Fact] - public void ExecuteMultipleStatementInOneRequestHttpRequest() - { - const string headerName = "MyTestHeader"; - const string headerValue = "myTestHeaderValue"; - var customHeaders = new NameValueCollection { {headerName, headerValue} }; - - - var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}, {'statement': 'MATCH t\r\nRETURN count(t)', 'resultDataContents':[], 'parameters': {}}]}"); - var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); - using (var testHarness = new RestTestHarness - { - { - initTransactionRequest, - MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") - }, - { - commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") - } - }) - { - var response = MockResponse.NeoRoot20(); - testHarness.Add(MockRequest.Get(""), response); - var httpClient = testHarness.GenerateHttpClient("http://foo/db/data"); - testHarness.CreateAndConnectTransactionalGraphClient(); - ITransactionalGraphClient client = new GraphClient(new Uri("http://foo/db/data"), httpClient); - client.Connect(); - using (var transaction = client.BeginTransaction()) - { - // dummy query to generate request - var rawClient = client as IRawGraphClient; - if (rawClient == null) - { - throw new Exception("ITransactionalGraphClient is not IRawGraphClient"); - } - - var queries = new List() - { - client.Cypher - .Match("n") - .Return(n => n.Count()) - .Query, - client.Cypher - .Match("t") - .Return(t => t.Count()) - .Query - }; - httpClient.ClearReceivedCalls(); - rawClient.ExecuteMultipleCypherQueriesInTransaction(queries, customHeaders); - transaction.Commit(); - - var calls = httpClient.ReceivedCalls().ToList(); - Assert.NotEmpty(calls); - - HttpRequestMessage requestMessage = null; - - foreach (var call in calls) - { - if (call.GetArguments().Single().GetType() == typeof (HttpRequestMessage)) - { - requestMessage = (HttpRequestMessage) call.GetArguments().Single(); - } - } - - Assert.NotNull(requestMessage); - - var customHeader = requestMessage.Headers.Single(h => h.Key == headerName); - Assert.NotNull(customHeader); - Assert.Equal(headerValue, customHeader.Value.Single()); - } - } - } - - [Fact] - public void TransactionCommit() + public async Task TransactionCommit() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); @@ -269,530 +140,25 @@ public void TransactionCommit() } }) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { // dummy query to generate request - client.Cypher - .Match("n") - .Return(n => n.Count()) - .ExecuteWithoutResults(); - - transaction.Commit(); - } - } - } - - [Fact(Skip="Flakey")] -// [Timeout(5000)] - public void PromoteDurableInAmbientTransaction() - { - // when two durables are registered they get promoted - - // this request will be made by the ForceKeepAlive() call when PSPE registration fails for the second client - var afterPspeFailRequest = MockRequest.PostJson("/transaction", @"{'statements': []}"); - // this request will be mode in Promote() after second durable enlistment - var promoteRequest = MockRequest.PostJson("/transaction/1", @"{'statements': []}"); - var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); - - var secondClientRequest = MockRequest.PostJson("/transaction/2", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); - - // there are no delete requests because those will be made in another app domain - - using (var testHarness = new RestTestHarness - { - { - initTransactionRequest, - MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), "http://foo/db/data/transaction/1") - }, - { - secondClientRequest, - MockResponse.Json(200, @"{'results':[], 'errors':[] }") - }, - { - afterPspeFailRequest, - MockResponse.Json(201, TransactionRestResponseHelper.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); - } - } - - [Fact] - public void SuppressTransactionScopeShouldNotEmitTransactionalQuery() - { - var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); - - var nonTransactionalRequest = MockRequest.PostJson("/cypher", @"{'query': 'MATCH n\r\nRETURN count(n)', 'params': {}}"); - - var commitTransactionRequest = MockRequest.PostJson("/transaction/1/commit", @"{ - 'statements': []}"); - var deleteRequest = MockRequest.Delete("/transaction/1"); - using (var testHarness = new RestTestHarness - { - { - initTransactionRequest, - MockResponse.Json(201, TransactionRestResponseHelper.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(); - } - } - } - - [Fact] - public void NestedRequiresNewTransactionScope() - { - var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); - var commitTransactionRequest = MockRequest.PostJson("/transaction/1/commit", @"{ - 'statements': []}"); - var deleteRequest = MockRequest.Delete("/transaction/1"); - using (var testHarness = new RestTestHarness - { - { - initTransactionRequest, - MockResponse.Json(201, TransactionRestResponseHelper.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 - } - } - } - - [Fact] - public void NestedJoinedTransactionScope() - { - var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); - var secondRequest = MockRequest.PostJson("/transaction/1", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); - var deleteRequest = MockRequest.Delete("/transaction/1"); - using (var testHarness = new RestTestHarness - { - { - initTransactionRequest, - MockResponse.Json(201, TransactionRestResponseHelper.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(); - } - } - } - - [Fact] - public void TransactionRollbackInTransactionScope() - { - var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); - var deleteRequest = MockRequest.Delete("/transaction/1"); - using (var testHarness = new RestTestHarness - { - { - initTransactionRequest, - MockResponse.Json(201, TransactionRestResponseHelper.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 + await client.Cypher .Match("n") .Return(n => n.Count()) - .ExecuteWithoutResults(); - } - } - } - - [Fact] - public void NestedTransactionWithTransactionScopeQueryFirst() - { - const string queryTextMsTransaction = @"MATCH (n) RETURN count(n)"; - const string queryTextTx = @"MATCH (t) RETURN count(t)"; - const string resultColumn = @"{'columns':['count(n)'], 'data':[{'row':[1]}]}"; - var cypherQueryMsTx = new CypherQuery(queryTextMsTransaction, new Dictionary(), CypherResultMode.Projection); - var cypherQueryMsTxStatement = new CypherStatementList { new CypherTransactionStatement(cypherQueryMsTx, false) }; - var cypherQueryTx = new CypherQuery(queryTextTx, new Dictionary(), CypherResultMode.Projection); - var cypherQueryTxStatement = new CypherStatementList { new CypherTransactionStatement(cypherQueryTx, false) }; - var deleteRequest = MockRequest.Delete("/transaction/1"); - var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); - var commitRequestTx = MockRequest.PostJson("/transaction/2/commit", @"{'statements': []}"); - - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/transaction", cypherQueryMsTxStatement), - MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1, resultColumn), "http://foo/db/data/transaction/1") - }, - { - MockRequest.PostObjectAsJson("/transaction", cypherQueryTxStatement), - MockResponse.Json(201, TransactionRestResponseHelper.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.True(client.InTransaction); - - long totalMsTx = client.Cypher - .Match("(n)") - .Return(n => n.Count()) - .Results - .SingleOrDefault(); - Assert.Equal(1, totalMsTx); - - using (var tx = client.BeginTransaction()) - { - long total = client.Cypher - .Match("(t)") - .Return(t => t.Count()) - .Results - .SingleOrDefault(); - - Assert.Equal(1, total); - - // should not be called - tx.Commit(); - } - } - } - } - - [Fact] - 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, TransactionRestResponseHelper.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.True(client.InTransaction); - - using (var tx = client.BeginTransaction()) - { - long total = client.Cypher - .Match("(n)") - .Return(n => n.Count()) - .Results - .SingleOrDefault(); - - Assert.Equal(1, total); - - // should not be called - tx.Commit(); - } - - msTransaction.Complete(); - } - } - - Assert.False(client.InTransaction); - } - } - - [Fact] - 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, TransactionRestResponseHelper.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.True(client.InTransaction); - - long total = client.Cypher - .Match("(n)") - .Return(n => n.Count()) - .Results - .SingleOrDefault(); - - Assert.Equal(1, total); - - msTransaction.Complete(); - } - - Assert.False(client.InTransaction); - } - } - - [Fact] - public void TestTransactionScopeWithComplexDeserialization() - { - const string queryText = @"MATCH (dt:DummyTotal) RETURN dt"; - const string resultColumn = @"{'columns':['dt'],'data':[{'row':[{'total':1234}]}]}"; - 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': []}"); + .ExecuteWithoutResultsAsync(); - using (var testHarness = new RestTestHarness - { - { - MockRequest.PostObjectAsJson("/transaction", cypherApiQuery), - MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1, resultColumn), "http://foo/db/data/transaction/1") - }, - { - commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") + await transaction.CommitAsync(); } - }) - { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); - client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - using (var msTransaction = new TransactionScope()) - { - Assert.True(client.InTransaction); - - var results = client.Cypher.Match("(dt:DummyTotal)") - .Return(dt => dt.As()) - .Results - .ToList(); - - Assert.Equal(1, results.Count()); - Assert.Equal(1234, results.First().Total); - - msTransaction.Complete(); - } - - Assert.False(client.InTransaction); } } private class DateHolder { public DateTime Date { get; set; } } - [Fact] - public void TestTransactionScopeWithComplexDeserialization_WithCulture() - { - Thread.CurrentThread.CurrentCulture = new CultureInfo("nb-NO"); - - const string queryText = @"MATCH (dt:DummyTotal) RETURN dt"; - const string resultColumn = @"{'columns':['dt'],'data':[{'row':[{'date':'2015-07-27T22:30:35Z'}]}]}"; - 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, TransactionRestResponseHelper.GenerateInitTransactionResponse(1, resultColumn), "http://foo/db/data/transaction/1") - }, - { - commitRequest, MockResponse.Json(200, @"{'results':[], 'errors':[] }") - } - }) - { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); - client.JsonContractResolver = new CamelCasePropertyNamesContractResolver(); - using (var msTransaction = new TransactionScope()) - { - Assert.True(client.InTransaction); - - var query = client.Cypher.Match("(dt:DummyTotal)") - .Return(dt => dt.As()); - - var eResults = query.Results; - var results = eResults.ToList(); - - Assert.Equal(1, results.Count); - var date = results.First().Date; - - Assert.Equal(date.Kind, DateTimeKind.Utc); - Assert.Equal(new DateTime(2015,7,27,22,30,35), results.First().Date); - - msTransaction.Complete(); - } - - Assert.False(client.InTransaction); - } - } [Fact] - public void TransactionCommitInTransactionScope() - { - var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ - 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); - var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); - using (var testHarness = new RestTestHarness - { - { - initTransactionRequest, - MockResponse.Json(201, TransactionRestResponseHelper.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(); - } - } - } - - [Fact] - public void SecondRequestDoesntReturnCreateHttpStatus() + public async Task SecondRequestDoesntReturnCreateHttpStatus() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); @@ -813,27 +179,27 @@ public void SecondRequestDoesntReturnCreateHttpStatus() } }) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { // dummy queries to generate request - client.Cypher + await client.Cypher .Match("n") .Return(n => n.Count()) - .ExecuteWithoutResults(); + .ExecuteWithoutResultsAsync(); - client.Cypher + await client.Cypher .Match("t") .Return(t => t.Count()) - .ExecuteWithoutResults(); + .ExecuteWithoutResultsAsync(); - transaction.Commit(); + await transaction.CommitAsync(); } } } [Fact] - public void KeepAliveAfterFirstRequest() + public async Task KeepAliveAfterFirstRequest() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); @@ -852,22 +218,22 @@ public void KeepAliveAfterFirstRequest() } }) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { // dummy query to generate request - client.Cypher - .Match("n") - .Return(n => n.Count()) - .ExecuteWithoutResults(); + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync(); - transaction.KeepAlive(); + await transaction.KeepAliveAsync(); } } } [Fact] - public void DeserializeResultsFromTransaction() + public async Task DeserializeResultsFromTransaction() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); @@ -889,20 +255,20 @@ public void DeserializeResultsFromTransaction() } }) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { // dummy query to generate request - client.Cypher + await client.Cypher .Match("n") .Return(n => n.Count()) - .ExecuteWithoutResults(); + .ExecuteWithoutResultsAsync(); // this query will hit the deserializer - var count = client.Cypher + var count = await client.Cypher .Match("n") .Return(n => n.Count()) - .Results; + .ResultsAsync; Assert.Equal(count.First(), 0); } @@ -910,7 +276,7 @@ public void DeserializeResultsFromTransaction() } [Fact] - public void OnTransactionDisposeCallRollback() + public async Task OnTransactionDisposeCallRollback() { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); @@ -926,14 +292,14 @@ public void OnTransactionDisposeCallRollback() } }) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { // dummy query to generate request - client.Cypher + await client.Cypher .Match("n") .Return(n => n.Count()) - .ExecuteWithoutResults(); + .ExecuteWithoutResultsAsync(); } } } @@ -944,13 +310,13 @@ public class DummyTotal } [Fact] - public void ExecuteAsyncRequestInTransaction() + public async Task 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 cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, "neo4j"); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; var commitRequest = MockRequest.PostJson("/transaction/1/commit", @"{'statements': []}"); using (var testHarness = new RestTestHarness { @@ -963,13 +329,13 @@ public void ExecuteAsyncRequestInTransaction() } }) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); var rawClient = (IRawGraphClient) client; using (var tran = client.BeginTransaction()) { var totalObj = rawClient.ExecuteGetCypherResultsAsync(cypherQuery).Result.Single(); Assert.Equal(1, totalObj.Total); - tran.Commit(); + await tran.CommitAsync(); } } @@ -984,11 +350,11 @@ public RestHarnessWithCounter() Queue = new ConcurrentQueue(); } - protected override HttpResponseMessage HandleRequest(HttpRequestMessage request, string baseUri) + protected override async Task HandleRequest(HttpRequestMessage request, string baseUri) { if (request.Method == HttpMethod.Post) { - var content = request.Content.ReadAsString(); + var content = await request.Content.ReadAsStringAsync(); int totalIndex = content.IndexOf("RETURN ", StringComparison.InvariantCultureIgnoreCase); if (totalIndex > 0) { @@ -999,12 +365,12 @@ protected override HttpResponseMessage HandleRequest(HttpRequestMessage request, } } - return base.HandleRequest(request, baseUri); + return await base.HandleRequest(request, baseUri); } } [Fact] - public void AsyncRequestsInTransactionShouldBeExecutedInOrder() + public async Task AsyncRequestsInTransactionShouldBeExecutedInOrder() { const string queryTextBase = @"MATCH (n) RETURN {0} as Total"; const string resultColumnBase = @"{{'columns':['Total'], 'data':[{{'row':[{0}]}}]}}"; @@ -1018,8 +384,8 @@ public void AsyncRequestsInTransactionShouldBeExecutedInOrder() 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)}; + CypherResultMode.Projection, "neo4j"); + apiQueries[i] = new CypherStatementList {new CypherTransactionStatement(queries[i])}; responses[i] = MockResponse.Json(200, @"{'results':[" + string.Format(resultColumnBase, i) + @"], 'errors':[] }"); if (i > 0) @@ -1039,7 +405,7 @@ public void AsyncRequestsInTransactionShouldBeExecutedInOrder() ); try { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); var rawClient = (IRawGraphClient)client; var tasks = new Task[asyncRequests]; using (var tran = client.BeginTransaction()) @@ -1053,8 +419,8 @@ public void AsyncRequestsInTransactionShouldBeExecutedInOrder() }); } - Task.WaitAll(tasks); - tran.Commit(); + await Task.WhenAll(tasks); + await tran.CommitAsync(); } } finally @@ -1081,13 +447,13 @@ public void AsyncRequestsInTransactionShouldBeExecutedInOrder() /// Perhaps need to insert simulated delay into MockResponse? /// [Fact(Skip="Flakey")] - public void CommitFailsOnPendingAsyncRequests() + public async Task 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) }; + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, "neo4j"); + var cypherApiQuery = new CypherStatementList { new CypherTransactionStatement(cypherQuery) }; using (var testHarness = new RestTestHarness(false) { @@ -1097,12 +463,12 @@ public void CommitFailsOnPendingAsyncRequests() } }) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); var rawClient = (IRawGraphClient) client; using (var tran = client.BeginTransaction()) { - rawClient.ExecuteGetCypherResultsAsync(cypherQuery); - var ex = Assert.Throws(() => tran.Commit()); + await rawClient.ExecuteGetCypherResultsAsync(cypherQuery); + var ex = await Assert.ThrowsAsync(async () => await tran.CommitAsync()); Assert.Equal("Cannot commit unless all tasks have been completed", ex.Message); } diff --git a/Neo4jClient.Tests/Transactions/RestCallScenarioTests.cs b/Neo4jClient.Tests/Transactions/RestCallScenarioTests.cs index bd14a0d94..5d17289e9 100644 --- a/Neo4jClient.Tests/Transactions/RestCallScenarioTests.cs +++ b/Neo4jClient.Tests/Transactions/RestCallScenarioTests.cs @@ -1,187 +1,186 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using Neo4jClient.Test.Fixtures; -using Neo4jClient.Test.Relationships; -using Neo4jClient.Transactions; -using Xunit; - -namespace Neo4jClient.Test.Transactions -{ - - public class RestCallScenarioTests : IClassFixture - { - private class TestNode - { - public string Foo { get; set; } - } - - private void ExecuteRestMethodUnderTransaction( - Action restAction, - TransactionScopeOption option = TransactionScopeOption.Join, - IEnumerable> requests = null) - { - requests = requests ?? Enumerable.Empty>(); - using (var testHarness = new RestTestHarness()) - { - foreach (var request in requests) - { - testHarness.Add(request.Key, request.Value); - } - var client = testHarness.CreateAndConnectTransactionalGraphClient(); - using (var transaction = client.BeginTransaction(option)) - { - restAction(client); - } - } - } - - [Fact] - public void CreateNodeIndexShouldFailUnderTransaction() - { - Assert.Throws(() => ExecuteRestMethodUnderTransaction( - client => client.CreateIndex("node", new IndexConfiguration(), IndexFor.Node))); - } - - [Fact] - public void CreateRelationshipIndexShouldFailUnderTransaction() - { - Assert.Throws(() => ExecuteRestMethodUnderTransaction( - client => client.CreateIndex("rel", new IndexConfiguration(), IndexFor.Relationship))); - } - - [Fact] - public void CreateRelationshipShouldFailUnderTransaction() - { - var nodeReference = new NodeReference(1); - var rel = new OwnedBy(new NodeReference(3)); - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.CreateRelationship(nodeReference, rel))); - } - - [Fact] - public void CreateShouldFailUnderTransaction() - { - Assert.Throws(() => - ExecuteRestMethodUnderTransaction(client => client.Create(new object()))); - } - - [Fact] - public void DeleteAndRelationshipsShouldFailUnderTransaction() - { - var pocoReference = new NodeReference(456); - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.Delete(pocoReference, DeleteMode.NodeAndRelationships))); - } - - [Fact] - public void DeleteIndexEntriesShouldFailUnderTransaction() - { - var pocoReference = new NodeReference(456); - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.DeleteIndexEntries("rel", pocoReference))); - } - - [Fact] - public void DeleteNodeIndexShouldFailUnderTransaction() - { - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.DeleteIndex("node", IndexFor.Node))); - } - - [Fact] - public void DeleteNodeShouldFailUnderTransaction() - { - var pocoReference = new NodeReference(456); - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.Delete(pocoReference, DeleteMode.NodeOnly))); - } - - [Fact] - public void DeleteRelationshipIndexShouldFailUnderTransaction() - { - Assert.Throws(() => - ExecuteRestMethodUnderTransaction(client => client.DeleteIndex("rel", IndexFor.Relationship))); - } - - [Fact] - public void DeleteRelationshipShouldFailUnderTransaction() - { - var relReference = new RelationshipReference(1); - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.DeleteRelationship(relReference))); - } - - [Fact] - public void GetNodeIndexesShouldFailUnderTransaction() - { - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.GetIndexes(IndexFor.Node))); - } - - [Fact] - public void GetNodeShouldFailUnderTransaction() - { - var nodeReference = new NodeReference(1); - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.Get(nodeReference))); - } - - [Fact] - public void GetRelationshipIndexesShouldUnderTransaction() - { - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.GetIndexes(IndexFor.Relationship))); - } - - [Fact] - public void GetRelationshipShouldFailUnderTransaction() - { - var relReference = new RelationshipReference(1); - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.Get(relReference))); - } - - [Fact] - 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); - } - - [Fact] - public void ReIndexShouldFailUnderTransaction() - { - var nodeReference = new NodeReference(1); - var indexEntries = new[] {new IndexEntry("node")}; - Assert.Throws(() => ExecuteRestMethodUnderTransaction(client => client.ReIndex(nodeReference, indexEntries))); - } - - [Fact] - public void UpdateShouldFailUnderTransaction() - { - Assert.Throws(() => - ExecuteRestMethodUnderTransaction(client => - { - var pocoReference = new NodeReference(456); - var updatedNode = client.Update( - pocoReference, nodeFromDb => { nodeFromDb.Foo = "fooUpdated"; }); - })); - } - } -} \ No newline at end of file +// using System; +// using System.Collections.Generic; +// using System.Linq; +// using System.Net; +// using System.Threading.Tasks; +// using Neo4jClient.Tests.Relationships; +// using Neo4jClient.Transactions; +// using Xunit; +// +// namespace Neo4jClient.Tests.Transactions +// { +// +// public class RestCallScenarioTests : IClassFixture +// { +// private class TestNode +// { +// public string Foo { get; set; } +// } +// +// private async Task ExecuteRestMethodUnderTransaction( +// Func 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 = await testHarness.CreateAndConnectTransactionalGraphClient(); +// using (var transaction = client.BeginTransaction(option)) +// { +// await restAction(client); +// } +// } +// } +// +// [Fact] +// public async Task CreateNodeIndexShouldFailUnderTransaction() +// { +// await Assert.ThrowsAsync(() => ExecuteRestMethodUnderTransaction( +// async client => await client.CreateIndexAsync("node", new IndexConfiguration(), IndexFor.Node))); +// } +// +// [Fact] +// public async Task CreateRelationshipIndexShouldFailUnderTransaction() +// { +// await Assert.ThrowsAsync(() => ExecuteRestMethodUnderTransaction( +// async client => await client.CreateIndexAsync("rel", new IndexConfiguration(), IndexFor.Relationship))); +// } +// +// [Fact] +// public async Task CreateRelationshipShouldFailUnderTransaction() +// { +// var nodeReference = new NodeReference(1); +// var rel = new OwnedBy(new NodeReference(3)); +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.CreateRelationshipAsync(nodeReference, rel))); +// } +// +// [Fact] +// public async Task CreateShouldFailUnderTransaction() +// { +// await Assert.ThrowsAsync(async () => +// await ExecuteRestMethodUnderTransaction(async client => await client.CreateAsync(new object()))); +// } +// +// [Fact] +// public async Task DeleteAndRelationshipsShouldFailUnderTransaction() +// { +// var pocoReference = new NodeReference(456); +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.DeleteAsync(pocoReference, DeleteMode.NodeAndRelationships))); +// } +// +// [Fact] +// public async Task DeleteIndexEntriesShouldFailUnderTransaction() +// { +// var pocoReference = new NodeReference(456); +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.DeleteIndexEntriesAsync("rel", pocoReference))); +// } +// +// [Fact] +// public async Task DeleteNodeIndexShouldFailUnderTransaction() +// { +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.DeleteIndexAsync("node", IndexFor.Node))); +// } +// +// [Fact] +// public async Task DeleteNodeShouldFailUnderTransaction() +// { +// var pocoReference = new NodeReference(456); +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.DeleteAsync(pocoReference, DeleteMode.NodeOnly))); +// } +// +// [Fact] +// public async Task DeleteRelationshipIndexShouldFailUnderTransaction() +// { +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.DeleteIndexAsync("rel", IndexFor.Relationship))); +// } +// +// [Fact] +// public async Task DeleteRelationshipShouldFailUnderTransaction() +// { +// var relReference = new RelationshipReference(1); +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.DeleteRelationshipAsync(relReference))); +// } +// +// [Fact] +// public async Task GetNodeIndexesShouldFailUnderTransaction() +// { +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.GetIndexesAsync(IndexFor.Node))); +// } +// +// [Fact] +// public async Task GetNodeShouldFailUnderTransaction() +// { +// var nodeReference = new NodeReference(1); +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.GetAsync(nodeReference))); +// } +// +// [Fact] +// public async Task GetRelationshipIndexesShouldUnderTransaction() +// { +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.GetIndexesAsync(IndexFor.Relationship))); +// } +// +// [Fact] +// public async Task GetRelationshipShouldFailUnderTransaction() +// { +// var relReference = new RelationshipReference(1); +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.GetAsync(relReference))); +// } +// +// [Fact] +// public async Task 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}' +// }") +// ) +// }; +// await ExecuteRestMethodUnderTransaction( +// async client => await client.GetAsync(nodeReference), +// TransactionScopeOption.Suppress, +// requests); +// } +// +// [Fact] +// public async Task ReIndexShouldFailUnderTransaction() +// { +// var nodeReference = new NodeReference(1); +// var indexEntries = new[] {new IndexEntry("node")}; +// await Assert.ThrowsAsync(async () => await ExecuteRestMethodUnderTransaction(async client => await client.ReIndexAsync(nodeReference, indexEntries))); +// } +// +// [Fact] +// public async Task UpdateShouldFailUnderTransaction() +// { +// await Assert.ThrowsAsync(async () => +// await ExecuteRestMethodUnderTransaction(async client => +// { +// var pocoReference = new NodeReference(456); +// var updatedNode = await client.UpdateAsync( +// pocoReference, nodeFromDb => { nodeFromDb.Foo = "fooUpdated"; }); +// })); +// } +// } +// } \ No newline at end of file diff --git a/Neo4jClient.Tests/Transactions/TransactionExecutionEnvironmentTests.cs b/Neo4jClient.Tests/Transactions/TransactionExecutionEnvironmentTests.cs index 16298854a..9e4fbf0de 100644 --- a/Neo4jClient.Tests/Transactions/TransactionExecutionEnvironmentTests.cs +++ b/Neo4jClient.Tests/Transactions/TransactionExecutionEnvironmentTests.cs @@ -1,10 +1,9 @@ using System; using Neo4jClient.Execution; -using Neo4jClient.Test.Fixtures; using Neo4jClient.Transactions; using Xunit; -namespace Neo4jClient.Test.Transactions +namespace Neo4jClient.Tests.Transactions { public class TransactionExecutionEnvironmentTests : IClassFixture diff --git a/Neo4jClient.Tests/Transactions/TransactionManagementTests.cs b/Neo4jClient.Tests/Transactions/TransactionManagementTests.cs index b7f9b01c4..d8e641016 100644 --- a/Neo4jClient.Tests/Transactions/TransactionManagementTests.cs +++ b/Neo4jClient.Tests/Transactions/TransactionManagementTests.cs @@ -6,13 +6,12 @@ using System.Transactions; using Neo4jClient.ApiModels.Cypher; using Neo4jClient.Cypher; -using Neo4jClient.Test.Fixtures; using Neo4jClient.Transactions; using Xunit; using TransactionManager = Neo4jClient.Transactions.TransactionManager; using TransactionScopeOption = Neo4jClient.Transactions.TransactionScopeOption; -namespace Neo4jClient.Test.Transactions +namespace Neo4jClient.Tests.Transactions { public class TransactionManagementTests : IClassFixture @@ -22,7 +21,7 @@ public class TransactionManagementTests : IClassFixture [InlineData(RestTestHarness.Neo4jVersion.Neo226)] [InlineData(RestTestHarness.Neo4jVersion.Neo23)] //https://github.com/Readify/Neo4jClient/issues/127 - public void ReturnsThe404_WhenVersionIs_2_2_6_Plus_WhenActuallyTimingOut(RestTestHarness.Neo4jVersion version) + public async Task ReturnsThe404_WhenVersionIs_2_2_6_Plus_WhenActuallyTimingOut(RestTestHarness.Neo4jVersion version) { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); @@ -39,12 +38,12 @@ public void ReturnsThe404_WhenVersionIs_2_2_6_Plus_WhenActuallyTimingOut(RestTes }) { var client = testHarness.CreateGraphClient(version); - client.Connect(); + await client.ConnectAsync(); try { using (var transaction = client.BeginTransaction()) { - client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResults(); + await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); } throw new Exception("Should not reach this code, as there is an expected exception."); } @@ -59,7 +58,7 @@ public void ReturnsThe404_WhenVersionIs_2_2_6_Plus_WhenActuallyTimingOut(RestTes [Theory] [InlineData(RestTestHarness.Neo4jVersion.Neo226)] //https://github.com/Readify/Neo4jClient/issues/127 - public void ReturnsCorrectError_WhenTransactionIsAutomaticallyRolledBack_ViaNeo4j_2_2_6_Plus(RestTestHarness.Neo4jVersion version) + public async Task ReturnsCorrectError_WhenTransactionIsAutomaticallyRolledBack_ViaNeo4j_2_2_6_Plus(RestTestHarness.Neo4jVersion version) { /* In 2.2.6 ClientErrors (Constraint Violations etc) were changed to Automatically rollback. This created a 404 error when *we* tried to rollback on an error, as the transaction no longer existed. */ var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ @@ -77,10 +76,10 @@ public void ReturnsCorrectError_WhenTransactionIsAutomaticallyRolledBack_ViaNeo4 }) { var client = testHarness.CreateGraphClient(version); - client.Connect(); + await client.ConnectAsync(); using (var transaction = client.BeginTransaction()) { - Assert.Throws(() => client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResults()); + await Assert.ThrowsAsync(async () => await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync()); } } } @@ -90,7 +89,7 @@ public void ReturnsCorrectError_WhenTransactionIsAutomaticallyRolledBack_ViaNeo4 [InlineData(RestTestHarness.Neo4jVersion.Neo22)] [InlineData(RestTestHarness.Neo4jVersion.Neo225)] //https://github.com/Readify/Neo4jClient/issues/127 - public void ReturnsThe404_WhenVersionIsLessThan_2_2_6(RestTestHarness.Neo4jVersion version) + public async Task ReturnsThe404_WhenVersionIsLessThan_2_2_6(RestTestHarness.Neo4jVersion version) { var initTransactionRequest = MockRequest.PostJson("/transaction", @"{ 'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); @@ -107,12 +106,12 @@ public void ReturnsThe404_WhenVersionIsLessThan_2_2_6(RestTestHarness.Neo4jVersi }) { var client = testHarness.CreateGraphClient(version); - client.Connect(); + await client.ConnectAsync(); try { using (var transaction = client.BeginTransaction()) { - client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResults(); + await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); } } catch (Exception ex) @@ -123,12 +122,12 @@ public void ReturnsThe404_WhenVersionIsLessThan_2_2_6(RestTestHarness.Neo4jVersi } [Fact] - public void EndTransaction_DoesntThrowAnyExceptions_WhenScopedTransactionsIsEmpty() + public async Task EndTransaction_DoesntThrowAnyExceptions_WhenScopedTransactionsIsEmpty() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); - client.Connect(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); + await client.ConnectAsync(); var tm = new TransactionManager(client); TransactionManager.ScopedTransactions = new ThreadContextWrapper(); @@ -139,12 +138,12 @@ public void EndTransaction_DoesntThrowAnyExceptions_WhenScopedTransactionsIsEmpt } [Fact] - public void EndTransaction_DoesntThrowAnyExceptions_WhenScopedTransactionsIsNull() + public async Task EndTransaction_DoesntThrowAnyExceptions_WhenScopedTransactionsIsNull() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); - client.Connect(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); + await client.ConnectAsync(); var tm = new TransactionManager(client); TransactionManager.ScopedTransactions = null; @@ -154,12 +153,12 @@ public void EndTransaction_DoesntThrowAnyExceptions_WhenScopedTransactionsIsNull } [Fact] - public void CurrentInternalTransaction_ReturnsNullWhenEmpty() + public async Task CurrentInternalTransaction_ReturnsNullWhenEmpty() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); - client.Connect(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); + await client.ConnectAsync(); var tm = new TransactionManager(client); TransactionManager.ScopedTransactions = new ThreadContextWrapper(); @@ -170,12 +169,12 @@ public void CurrentInternalTransaction_ReturnsNullWhenEmpty() } [Fact] - public void CurrentInternalTransaction_ReturnsNullWhenScopedTransactionsIsNull() + public async Task CurrentInternalTransaction_ReturnsNullWhenScopedTransactionsIsNull() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); - client.Connect(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); + await client.ConnectAsync(); var tm = new TransactionManager(client); TransactionManager.ScopedTransactions = null; @@ -185,30 +184,30 @@ public void CurrentInternalTransaction_ReturnsNullWhenScopedTransactionsIsNull() } [Fact] - public void BeginTransactionShouldFailWithLower20Versions() + public async Task BeginTransactionShouldFailWithLower20Versions() { using (var testHarness = new RestTestHarness()) { var client = testHarness.CreateGraphClient(RestTestHarness.Neo4jVersion.Neo19); - client.Connect(); + await client.ConnectAsync(); Assert.Throws(() => client.BeginTransaction()); } } [Fact] - public void BeginTransactionShouldFailWithoutConnectingFirst() + public async Task BeginTransactionShouldFailWithoutConnectingFirst() { var client = new GraphClient(new Uri("http://foo/db/data"), null); Assert.Throws(() => client.BeginTransaction()); } [Fact] - public void ShouldBeAbleToGetTransactionObjectAfterBeginTransaction() + public async Task ShouldBeAbleToGetTransactionObjectAfterBeginTransaction() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); - client.Connect(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); + await client.ConnectAsync(); using (var transaction = client.BeginTransaction()) { Assert.Same(transaction, client.Transaction); @@ -217,11 +216,11 @@ public void ShouldBeAbleToGetTransactionObjectAfterBeginTransaction() } [Fact] - public void IgnoreSystemTransactionsIfInsideInternalTransaction() + public async Task IgnoreSystemTransactionsIfInsideInternalTransaction() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { using (var msTransaction = new TransactionScope()) @@ -235,11 +234,11 @@ public void IgnoreSystemTransactionsIfInsideInternalTransaction() } [Fact] - public void ShouldNotBeAbleToGetTransactionAfterTransactionScope() + public async Task ShouldNotBeAbleToGetTransactionAfterTransactionScope() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { @@ -256,11 +255,11 @@ private ITransaction GetRealTransaction(ITransaction proxiedTransaction) } [Fact] - public void ShouldNotBeInATransactionScopeWhileSuppressed() + public async Task ShouldNotBeInATransactionScopeWhileSuppressed() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { using (var tran2 = client.BeginTransaction(TransactionScopeOption.Suppress)) @@ -273,11 +272,11 @@ public void ShouldNotBeInATransactionScopeWhileSuppressed() } [Fact] - public void TransactionJoinedShouldBeTheSame() + public async Task TransactionJoinedShouldBeTheSame() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { @@ -290,11 +289,11 @@ public void TransactionJoinedShouldBeTheSame() } [Fact] - public void RequiresNewCreateNewTransaction() + public async Task RequiresNewCreateNewTransaction() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var transaction = client.BeginTransaction()) { using (var tran2 = client.BeginTransaction(TransactionScopeOption.RequiresNew)) @@ -309,11 +308,11 @@ public void RequiresNewCreateNewTransaction() } [Fact] - public void JoinTransactionAfterSuppressCreatesNewTransaction() + public async Task JoinTransactionAfterSuppressCreatesNewTransaction() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var tran = client.BeginTransaction()) { using (var tran2 = client.BeginTransaction(TransactionScopeOption.Suppress)) @@ -334,16 +333,16 @@ public void JoinTransactionAfterSuppressCreatesNewTransaction() } [Fact] - public void JoinedTransactionsCommitAfterAllEmitVote() + public async Task JoinedTransactionsCommitAfterAllEmitVote() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var tran = client.BeginTransaction()) { using (var tran2 = client.BeginTransaction()) { - tran2.Commit(); + await tran2.CommitAsync(); } Assert.True(tran.IsOpen); @@ -357,7 +356,7 @@ public void JoinedTransactionsCommitAfterAllEmitVote() Assert.True(client.InTransaction); Assert.True(tran.IsOpen); - tran.Commit(); + await tran.CommitAsync(); Assert.False(tran.IsOpen); } Assert.False(client.InTransaction); @@ -365,16 +364,16 @@ public void JoinedTransactionsCommitAfterAllEmitVote() } [Fact] - public void RollbackInJoinedTransactionClosesAllJoinedScopes() + public async Task RollbackInJoinedTransactionClosesAllJoinedScopes() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var tran = client.BeginTransaction()) { using (var tran2 = client.BeginTransaction()) { - tran2.Rollback(); + await tran2.RollbackAsync(); } Assert.False(tran.IsOpen); @@ -384,16 +383,16 @@ public void RollbackInJoinedTransactionClosesAllJoinedScopes() } [Fact] - public void CommitInRequiresNewDoesntAffectParentScope() + public async Task CommitInRequiresNewDoesntAffectParentScope() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var tran = client.BeginTransaction()) { using (var tran2 = client.BeginTransaction(TransactionScopeOption.RequiresNew)) { - tran2.Commit(); + await tran2.CommitAsync(); Assert.False(tran2.IsOpen); } @@ -404,16 +403,16 @@ public void CommitInRequiresNewDoesntAffectParentScope() } [Fact] - public void RollbackInRequiresNewDoesntAffectParentScope() + public async Task RollbackInRequiresNewDoesntAffectParentScope() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var tran = client.BeginTransaction()) { using (var tran2 = client.BeginTransaction(TransactionScopeOption.RequiresNew)) { - tran2.Rollback(); + await tran2.RollbackAsync(); Assert.False(tran2.IsOpen); } @@ -424,14 +423,14 @@ public void RollbackInRequiresNewDoesntAffectParentScope() } [Fact] - public void CannotJoinAfterClosedTransaction() + public async Task CannotJoinAfterClosedTransaction() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var tran = client.BeginTransaction()) { - tran.Commit(); + await tran.CommitAsync(); Assert.False(tran.IsOpen); // should fail here @@ -442,72 +441,42 @@ public void CannotJoinAfterClosedTransaction() } [Fact] - public void FailsForCommitInSuppressMode() + public async Task FailsForCommitInSuppressMode() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var tran = client.BeginTransaction(TransactionScopeOption.Suppress)) { - Assert.Throws(() => tran.Commit()); + await Assert.ThrowsAsync(async () => await tran.CommitAsync()); } } } [Fact] - public void FailsForRollbackInSuppressMode() + public async Task FailsForRollbackInSuppressMode() { using (var testHarness = new RestTestHarness()) { - var client = testHarness.CreateAndConnectTransactionalGraphClient(); + var client = await testHarness.CreateAndConnectTransactionalGraphClient(); using (var tran = client.BeginTransaction(TransactionScopeOption.Suppress)) { - Assert.Throws(() => tran.Rollback()); + await Assert.ThrowsAsync(async () => await tran.RollbackAsync()); } } } - [Fact] - public void ShouldNotBeAbleToCommitTwice() - { - var transaction = new Neo4jRestTransaction(new GraphClient(new Uri("http://foo/db/data"))); - transaction.Commit(); - Assert.Throws(() => transaction.Commit()); - } - - [Fact] - public void ShouldNotBeAbleToRollbackTwice() - { - var transaction = new Neo4jRestTransaction(new GraphClient(new Uri("http://foo/db/data"))); - transaction.Rollback(); - Assert.Throws(() => transaction.Rollback()); - } - - [Fact] - public void ShouldNotBeAbleToCommitAfterRollback() - { - var transaction = new Neo4jRestTransaction(new GraphClient(new Uri("http://foo/db/data"))); - transaction.Rollback(); - Assert.Throws(() => transaction.Commit()); - } - - [Fact] - public void ShouldNotBeAbleToRollbackAfterCommit() - { - var transaction = new Neo4jRestTransaction(new GraphClient(new Uri("http://foo/db/data"))); - transaction.Commit(); - Assert.Throws(() => transaction.Rollback()); - } + [Fact] - public void TwoThreadsShouldNotHaveTheSameTransactionObject() + public async Task 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 client = await testHarness.CreateAndConnectTransactionalGraphClient(); var firstTransactionSet = new EventWaitHandle(false, EventResetMode.AutoReset); var secondTransactionSet = new EventWaitHandle(false, EventResetMode.AutoReset); var t1 = new Task(() => @@ -557,12 +526,12 @@ public void TwoThreadsShouldNotHaveTheSameTransactionObject() } [Fact] - public void ShouldPromoteBadQueryResponseToNiceException() + public async Task ShouldPromoteBadQueryResponseToNiceException() { // Arrange const string queryText = @"broken query"; - var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection); - var cypherApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery, false)}; + var cypherQuery = new CypherQuery(queryText, new Dictionary(), CypherResultMode.Projection, "neo4j"); + var cypherApiQuery = new CypherStatementList {new CypherTransactionStatement(cypherQuery)}; using (var testHarness = new RestTestHarness { @@ -575,13 +544,13 @@ public void ShouldPromoteBadQueryResponseToNiceException() } }) { - var graphClient = testHarness.CreateAndConnectTransactionalGraphClient(); + var graphClient = await testHarness.CreateAndConnectTransactionalGraphClient(); var rawClient = (IRawGraphClient) graphClient; using (graphClient.BeginTransaction()) { - var ex = Assert.Throws(() => rawClient.ExecuteCypher(cypherQuery)); + var ex = await Assert.ThrowsAsync(async () => await rawClient.ExecuteCypherAsync(cypherQuery)); Assert.Equal("InvalidSyntax: Invalid input b: expected SingleStatement (line 1, column 1)\nThis is not a valid Cypher Statement.\n ^", ex.Message); Assert.Equal("Invalid input b: expected SingleStatement (line 1, column 1)\nThis is not a valid Cypher Statement.\n ^", ex.NeoMessage); Assert.Equal("InvalidSyntax", ex.NeoExceptionName); diff --git a/Neo4jClient.Tests/Transactions/TransactionRestResponseHelper.cs b/Neo4jClient.Tests/Transactions/TransactionRestResponseHelper.cs index fee6ca698..50d077cc5 100644 --- a/Neo4jClient.Tests/Transactions/TransactionRestResponseHelper.cs +++ b/Neo4jClient.Tests/Transactions/TransactionRestResponseHelper.cs @@ -1,6 +1,6 @@ using System; -namespace Neo4jClient.Test.Transactions +namespace Neo4jClient.Tests.Transactions { internal static class TransactionRestResponseHelper { diff --git a/Neo4jClient.Tests/Transactions/TransactionTests.cs b/Neo4jClient.Tests/Transactions/TransactionTests.cs new file mode 100644 index 000000000..20a58c6b8 --- /dev/null +++ b/Neo4jClient.Tests/Transactions/TransactionTests.cs @@ -0,0 +1,754 @@ +using System; +using System.Threading.Tasks; +using FluentAssertions; +using Neo4jClient.Transactions; +using NSubstitute; +using Xunit; + +namespace Neo4jClient.Tests.Transactions +{ + public class TransactionTests + { + /// + /// Transaction tests against the HTTP API + /// + public class Http : IClassFixture + { + internal static MockResponse EmptyOkResponse = MockResponse.Json(200, @"{'results':[], 'errors':[] }"); + internal static string EmptyStatements = "{'statements': []}"; + + [Fact] + public async Task QueryInTransaction_ThrowsExceptionWhen_ExecutingCypherQueryAgainstADifferentDatabaseName() + { + const string database = "neo4jclient"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction(TransactionScopeOption.Join, null, database)) + { + // dummy query to generate request + var ex = Assert.Throws( + () => client.Cypher + .WithDatabase("neo4j") + .Match("n") + .Return(n => n.Count())); + + ex.Should().NotBeNull(); + } + } + } + + public class KeepAliveAsyncMethod : IClassFixture + { + [Fact] + public async Task ThrowsClosedTransactionException_WhenTransactionAlreadyClosed() + { + const string database = "neo4jclient"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction(TransactionScopeOption.Join, null, database)) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + + await transaction.CommitAsync().ConfigureAwait(false); + var ex = await Assert.ThrowsAsync(async () => await transaction.KeepAliveAsync()); + ex.Should().NotBeNull(); + } + } + } + + [Fact] + public async Task UsesTheSetDatabase() + { + const string database = "neo4jclient"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var rollbackTransactionRequest = MockRequest.Delete($"/db/{database}/tx/1"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + var keepAliveTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1", EmptyStatements); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse}, + {rollbackTransactionRequest, EmptyOkResponse}, + {keepAliveTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction(TransactionScopeOption.Join, null, database)) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + + await transaction.KeepAliveAsync(); + } + } + } + + [Fact] + public async Task UsesTheDefaultDatabase_WhenNoneSet() + { + const string database = "neo4j"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var rollbackTransactionRequest = MockRequest.Delete($"/db/{database}/tx/1"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + var keepAliveTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1", EmptyStatements); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse}, + {rollbackTransactionRequest, EmptyOkResponse}, + {keepAliveTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + + await transaction.KeepAliveAsync(); + } + } + } + } + + public class DisposeMethod : IClassFixture + { + [Fact] + public async Task CallsRollsback_IfTransactionIsOpen() + { + const string database = "neo4j"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + var rollbackTransactionRequest = MockRequest.Delete($"/db/{database}/tx/1"); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse}, + {rollbackTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + } + } + } + + [Fact] + public async Task DoesntCallRollback_IfTransactionIsntOpen() + { + const string database = "neo4j"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + await transaction.CommitAsync(); + } + } + } + } + + public class RollbackAsyncMethod : IClassFixture + { + [Fact] + public async Task ThrowsClosedTransactionException_WhenTransactionAlreadyClosed() + { + const string database = "neo4jclient"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var rollbackTransactionRequest = MockRequest.Delete($"/db/{database}/tx/1"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse}, + {rollbackTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction(TransactionScopeOption.Join, null, database)) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + + await transaction.CommitAsync().ConfigureAwait(false); + var ex = await Assert.ThrowsAsync(async () => await transaction.RollbackAsync()); + ex.Should().NotBeNull(); + } + } + } + + [Fact] + public async Task UsesTheSetDatabase() + { + const string database = "neo4jclient"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var rollbackTransactionRequest = MockRequest.Delete($"/db/{database}/tx/1"); + using (var testHarness = new RestTestHarness(true, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + { + rollbackTransactionRequest, EmptyOkResponse + } + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction(TransactionScopeOption.Join, null, database)) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync(); + + await transaction.RollbackAsync(); + } + } + } + + [Fact] + public async Task UsesTheDefaultDatabase_WhenNoneSet() + { + const string database = "neo4j"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var rollbackTransactionRequest = MockRequest.Delete($"/db/{database}/tx/1"); + using (var testHarness = new RestTestHarness(true, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + { + rollbackTransactionRequest, EmptyOkResponse + } + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync(); + + await transaction.RollbackAsync(); + } + } + } + } + + public class IsOpenProperty : IClassFixture + { + [Fact] + public async Task IsTrueWhen_Open() + { + const string database = "neo4j"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + var rollbackTransactionRequest = MockRequest.Delete($"/db/{database}/tx/1"); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse}, + {rollbackTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + + transaction.IsOpen.Should().BeTrue(); + } + } + } + + [Fact] + public async Task IsFalseWhen_Rolledback() + { + const string database = "neo4j"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + var rollbackTransactionRequest = MockRequest.Delete($"/db/{database}/tx/1"); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse}, + {rollbackTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + + await transaction.RollbackAsync(); + transaction.IsOpen.Should().BeFalse(); + } + } + } + + [Fact] + public async Task IsFalseWhen_Committed() + { + const string database = "neo4j"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + + await transaction.CommitAsync(); + transaction.IsOpen.Should().BeFalse(); + } + } + } + } + + public class CommitAsyncMethod : IClassFixture + { + [Fact] + public async Task ThrowsClosedTransactionException_WhenTransactionAlreadyClosed() + { + const string database = "neo4jclient"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction(TransactionScopeOption.Join, null, database)) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + + await transaction.CommitAsync().ConfigureAwait(false); + var ex = await Assert.ThrowsAsync(async () => await transaction.CommitAsync()); + ex.Should().NotBeNull(); + } + } + } + + [Fact] + public async Task UsesTheSetDatabase() + { + const string database = "neo4jclient"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction(TransactionScopeOption.Join, null, database)) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + + await transaction.CommitAsync().ConfigureAwait(false); + } + } + } + + [Fact] + public async Task UsesTheDefaultDatabase_WhenNoneSet() + { + const string database = "neo4j"; + + var initTransactionRequest = MockRequest.PostJson($"/db/{database}/tx", "{'statements': [{'statement': 'MATCH n\r\nRETURN count(n)', 'resultDataContents':[], 'parameters': {}}]}"); + var commitTransactionRequest = MockRequest.PostJson($"/db/{database}/tx/1/commit", EmptyStatements); + using (var testHarness = new RestTestHarness(false, "http://foo:7474") + { + { + initTransactionRequest, + MockResponse.Json(201, TransactionRestResponseHelper.GenerateInitTransactionResponse(1), $"http://foo:7474/db/{database}/tx/1") + }, + {commitTransactionRequest, EmptyOkResponse} + }) + { + var client = await testHarness.CreateAndConnectTransactionalGraphClient(RestTestHarness.Neo4jVersion.Neo40); + using (var transaction = client.BeginTransaction()) + { + // dummy query to generate request + await client.Cypher + .Match("n") + .Return(n => n.Count()) + .ExecuteWithoutResultsAsync().ConfigureAwait(false); + + await transaction.CommitAsync().ConfigureAwait(false); + var ex = await Assert.ThrowsAsync(async () => await transaction.CommitAsync()); + ex.Should().NotBeNull(); + } + } + } + } + } + + /// + /// Transaction tests against the Bolt API + /// + public class Bolt : IClassFixture + { + [Fact] + public async Task QueryInTransaction_ThrowsExceptionWhen_ExecutingCypherQueryAgainstADifferentDatabaseName() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out var mockTransaction); + + var client = new BoltGraphClient(mockDriver); + await client.ConnectAsync(); + using (var tx = client.BeginTransaction()) + { + var ex = Assert.Throws(() => client.Cypher.WithDatabase("foo").Match("n").Return(n => n.Count())); + ex.Should().NotBeNull(); + } + } + + public class KeepAliveAsyncMethod : IClassFixture + { + [Fact] + public async Task DoesntDoAnything() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out _); + + var client = new BoltGraphClient(mockDriver); + await client.ConnectAsync(); + using (var tx = client.BeginTransaction()) + { + await tx.KeepAliveAsync(); + } + } + } + + public class DisposeMethod : IClassFixture + { + [Fact] + public async Task CallsRollsback_IfTransactionIsOpen() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out var mockTransaction); + + var client = new BoltGraphClient(mockDriver); + await client.ConnectAsync(); + using (var tx = client.BeginTransaction()) + { + await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + } + + await mockTransaction.Received(1).RollbackAsync(); + } + + [Fact] + public async Task DoesntCallRollback_IfTransactionIsntOpen() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out var mockTransaction); + + var client = new BoltGraphClient(mockDriver); + await client.ConnectAsync(); + using (var tx = client.BeginTransaction()) + { + await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + await tx.CommitAsync(); + } + + await mockTransaction.Received(0).RollbackAsync(); + } + } + + public class RollbackAsyncMethod : IClassFixture + { + [Fact] + public async Task ThrowsClosedTransactionException_WhenTransactionAlreadyClosed() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out var mockTransaction); + + var client = new BoltGraphClient(mockDriver); + await client.ConnectAsync(); + using (var tx = client.BeginTransaction()) + { + await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + await tx.CommitAsync(); + var ex = await Assert.ThrowsAsync(async () => await tx.RollbackAsync()); + ex.Should().NotBeNull(); + } + } + + [Fact(Skip = "I can't figure a way to check that the session is created correctly.")] + public async Task UsesTheSetDatabase() + { + //I need a proper async session.... not a fake one. + + + // BoltClientTestHelper.GetDriverAndSession(out IDriver mockDriver, out IAsyncSession mockSession, out IAsyncTransaction mockTransaction); + // + // const string database = "foo"; + // + // var client = new BoltGraphClient(mockDriver); + // await client.ConnectAsync(); + // mockDriver.ClearReceivedCalls(); + // using (var tx = client.BeginTransaction(TransactionScopeOption.Join, null, database)) + // { + // await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + // } + + // mockDriver.Received(1).AsyncSession(Arg.Is>(x => CheckSessionConfig(x))); + } + + [Fact(Skip = "I can't figure a way to check that the session is created correctly.")] + public async Task UsesTheSetDatabase2() + { + // //I need a proper async session.... not a fake one. + // const string database = "foo"; + // + // var realDriver = GraphDatabase.Driver("neo4j://foo:7687"); + // BoltClientTestHelper.GetDriverAndSession(out IDriver mockDriver, out IAsyncSession mockSession, out IAsyncTransaction mockTransaction); + // + // var client = new BoltGraphClient(mockDriver); + // await client.ConnectAsync(); + // + // mockDriver.AsyncSession(Arg.Any>()).ClearSubstitute(); + // mockDriver.AsyncSession(Arg.Any>()).Returns(x => realDriver.AsyncSession(x.Arg>())); + // + // + // try + // { + // client.BeginTransaction(TransactionScopeOption.Join, null, database); + // } + // catch(AggregateException) + // { + // /**/ + // } + // + // + // + // //mockDriver.ClearReceivedCalls(); + // using (var tx = client.BeginTransaction(TransactionScopeOption.Join, null, database)) + // { + // + // await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + // } + // + // mockDriver.Received(1).AsyncSession(Arg.Is>(x => CheckSessionConfig(x))); + } + // + // + // private static bool CheckSessionConfig(Action action) + // { + // + // return true; + // } + + + [Fact(Skip = "I can't figure a way to check that the session is created correctly.")] + public async Task UsesTheDefaultDatabase_WhenNoneSet() + { + throw new NotImplementedException(); + } + } + + public class IsOpenProperty : IClassFixture + { + [Fact] + public async Task IsTrueWhen_Open() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out _); + + var client = new BoltGraphClient(mockDriver); + await client.ConnectAsync(); + using (var tx = client.BeginTransaction()) + { + await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + tx.IsOpen.Should().BeTrue(); + } + } + + [Fact] + public async Task IsFalseWhen_Rolledback() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out var mockTransaction); + + var client = new BoltGraphClient(mockDriver); + await client.ConnectAsync(); + using (var tx = client.BeginTransaction()) + { + await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + await tx.RollbackAsync(); + tx.IsOpen.Should().BeFalse(); + } + } + + [Fact] + public async Task IsFalseWhen_Committed() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out var mockTransaction); + + var client = new BoltGraphClient(mockDriver); + await client.ConnectAsync(); + using (var tx = client.BeginTransaction()) + { + await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + await tx.CommitAsync(); + tx.IsOpen.Should().BeFalse(); + } + } + } + + public class CommitAsyncMethod : IClassFixture + { + [Fact] + public async Task ThrowsClosedTransactionException_WhenTransactionAlreadyClosed() + { + BoltClientTestHelper.GetDriverAndSession(out var mockDriver, out _, out var mockTransaction); + + var client = new BoltGraphClient(mockDriver); + await client.ConnectAsync(); + using (var tx = client.BeginTransaction()) + { + await client.Cypher.Match("n").Return(n => n.Count()).ExecuteWithoutResultsAsync(); + await tx.CommitAsync(); + var ex = await Assert.ThrowsAsync(async () => await tx.CommitAsync()); + ex.Should().NotBeNull(); + } + } + + [Fact(Skip = "I can't figure a way to check that the session is created correctly.")] + public async Task UsesTheSetDatabase() + { + throw new NotImplementedException(); + } + + [Fact(Skip = "I can't figure a way to check that the session is created correctly.")] + public async Task UsesTheDefaultDatabase_WhenNoneSet() + { + throw new NotImplementedException(); + } + } + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Tests.Shared/UtilitiesTests.cs b/Neo4jClient.Tests/UtilitiesTests.cs similarity index 95% rename from Neo4jClient.Tests.Shared/UtilitiesTests.cs rename to Neo4jClient.Tests/UtilitiesTests.cs index 229577e3b..b2609dba7 100644 --- a/Neo4jClient.Tests.Shared/UtilitiesTests.cs +++ b/Neo4jClient.Tests/UtilitiesTests.cs @@ -1,10 +1,8 @@ using System.Collections.Generic; using System.Linq; -using Neo4jClient.Test.Fixtures; -using Xunit; using Xunit; -namespace Neo4jClient.Test +namespace Neo4jClient.Tests { public class UtilitiesTests : IClassFixture diff --git a/Neo4jClient.Tests/app.config b/Neo4jClient.Tests/app.config deleted file mode 100644 index db61750fb..000000000 --- a/Neo4jClient.Tests/app.config +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Tests/packages.config b/Neo4jClient.Tests/packages.config deleted file mode 100644 index 9d2289e89..000000000 --- a/Neo4jClient.Tests/packages.config +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Vb.Tests/My Project/Application.Designer.vb b/Neo4jClient.Vb.Tests/My Project/Application.Designer.vb deleted file mode 100644 index 88dd01c78..000000000 --- a/Neo4jClient.Vb.Tests/My Project/Application.Designer.vb +++ /dev/null @@ -1,13 +0,0 @@ -'------------------------------------------------------------------------------ -' -' This code was generated by a tool. -' Runtime Version:4.0.30319.42000 -' -' Changes to this file may cause incorrect behavior and will be lost if -' the code is regenerated. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - diff --git a/Neo4jClient.Vb.Tests/My Project/Application.myapp b/Neo4jClient.Vb.Tests/My Project/Application.myapp deleted file mode 100644 index 758895def..000000000 --- a/Neo4jClient.Vb.Tests/My Project/Application.myapp +++ /dev/null @@ -1,10 +0,0 @@ - - - false - false - 0 - true - 0 - 1 - true - diff --git a/Neo4jClient.Vb.Tests/My Project/AssemblyInfo.vb b/Neo4jClient.Vb.Tests/My Project/AssemblyInfo.vb deleted file mode 100644 index 1c512b699..000000000 --- a/Neo4jClient.Vb.Tests/My Project/AssemblyInfo.vb +++ /dev/null @@ -1,35 +0,0 @@ -Imports System -Imports System.Reflection -Imports 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. - -' Review the values of the assembly attributes - - - - - - - - - - -'The following GUID is for the ID of the typelib if this project is exposed to COM - - -' 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: -' - - - diff --git a/Neo4jClient.Vb.Tests/My Project/Resources.Designer.vb b/Neo4jClient.Vb.Tests/My Project/Resources.Designer.vb deleted file mode 100644 index d031f5d7b..000000000 --- a/Neo4jClient.Vb.Tests/My Project/Resources.Designer.vb +++ /dev/null @@ -1,63 +0,0 @@ -'------------------------------------------------------------------------------ -' -' This code was generated by a tool. -' Runtime Version:4.0.30319.42000 -' -' Changes to this file may cause incorrect behavior and will be lost if -' the code is regenerated. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - -Imports System - -Namespace My.Resources - - 'This class was auto-generated by the StronglyTypedResourceBuilder - 'class via a tool like ResGen or Visual Studio. - 'To add or remove a member, edit your .ResX file then rerun ResGen - 'with the /str option, or rebuild your VS project. - ''' - ''' A strongly-typed resource class, for looking up localized strings, etc. - ''' - _ - Friend Module Resources - - Private resourceMan As Global.System.Resources.ResourceManager - - Private resourceCulture As Global.System.Globalization.CultureInfo - - ''' - ''' Returns the cached ResourceManager instance used by this class. - ''' - _ - Friend ReadOnly Property ResourceManager() As Global.System.Resources.ResourceManager - Get - If Object.ReferenceEquals(resourceMan, Nothing) Then - Dim temp As Global.System.Resources.ResourceManager = New Global.System.Resources.ResourceManager("Neo4jClient.Vb.Tests.Resources", GetType(Resources).Assembly) - resourceMan = temp - End If - Return resourceMan - End Get - End Property - - ''' - ''' Overrides the current thread's CurrentUICulture property for all - ''' resource lookups using this strongly typed resource class. - ''' - _ - Friend Property Culture() As Global.System.Globalization.CultureInfo - Get - Return resourceCulture - End Get - Set - resourceCulture = value - End Set - End Property - End Module -End Namespace diff --git a/Neo4jClient.Vb.Tests/My Project/Resources.resx b/Neo4jClient.Vb.Tests/My Project/Resources.resx deleted file mode 100644 index af7dbebba..000000000 --- a/Neo4jClient.Vb.Tests/My Project/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/Neo4jClient.Vb.Tests/My Project/Settings.Designer.vb b/Neo4jClient.Vb.Tests/My Project/Settings.Designer.vb deleted file mode 100644 index 2fac1af9f..000000000 --- a/Neo4jClient.Vb.Tests/My Project/Settings.Designer.vb +++ /dev/null @@ -1,73 +0,0 @@ -'------------------------------------------------------------------------------ -' -' This code was generated by a tool. -' Runtime Version:4.0.30319.42000 -' -' Changes to this file may cause incorrect behavior and will be lost if -' the code is regenerated. -' -'------------------------------------------------------------------------------ - -Option Strict On -Option Explicit On - - -Namespace My - - _ - Partial Friend NotInheritable Class MySettings - Inherits Global.System.Configuration.ApplicationSettingsBase - - Private Shared defaultInstance As MySettings = CType(Global.System.Configuration.ApplicationSettingsBase.Synchronized(New MySettings()),MySettings) - -#Region "My.Settings Auto-Save Functionality" -#If _MyType = "WindowsForms" Then - Private Shared addedHandler As Boolean - - Private Shared addedHandlerLockObject As New Object - - _ - Private Shared Sub AutoSaveSettings(ByVal sender As Global.System.Object, ByVal e As Global.System.EventArgs) - If My.Application.SaveMySettingsOnExit Then - My.Settings.Save() - End If - End Sub -#End If -#End Region - - Public Shared ReadOnly Property [Default]() As MySettings - Get - -#If _MyType = "WindowsForms" Then - If Not addedHandler Then - SyncLock addedHandlerLockObject - If Not addedHandler Then - AddHandler My.Application.Shutdown, AddressOf AutoSaveSettings - addedHandler = True - End If - End SyncLock - End If -#End If - Return defaultInstance - End Get - End Property - End Class -End Namespace - -Namespace My - - _ - Friend Module MySettingsProperty - - _ - Friend ReadOnly Property Settings() As Global.Neo4jClient.Vb.Tests.My.MySettings - Get - Return Global.Neo4jClient.Vb.Tests.My.MySettings.Default - End Get - End Property - End Module -End Namespace diff --git a/Neo4jClient.Vb.Tests/My Project/Settings.settings b/Neo4jClient.Vb.Tests/My Project/Settings.settings deleted file mode 100644 index 85b890b3c..000000000 --- a/Neo4jClient.Vb.Tests/My Project/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.v2.ncrunchproject b/Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.v2.ncrunchproject deleted file mode 100644 index 1f00ab7c7..000000000 --- a/Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.v2.ncrunchproject +++ /dev/null @@ -1,26 +0,0 @@ - - true - 1000 - false - false - false - true - false - false - false - false - false - true - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - \ No newline at end of file diff --git a/Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.v3.ncrunchproject b/Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.v3.ncrunchproject deleted file mode 100644 index 6800b4a3f..000000000 --- a/Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.v3.ncrunchproject +++ /dev/null @@ -1,5 +0,0 @@ - - - True - - \ No newline at end of file diff --git a/Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.vbproj b/Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.vbproj index 71050ed2a..051b8ba64 100644 --- a/Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.vbproj +++ b/Neo4jClient.Vb.Tests/Neo4jClient.Vb.Tests.vbproj @@ -1,290 +1,29 @@ - - - + + - Debug - AnyCPU - {D87DA5F1-DA55-43BF-8A6C-F2BF7BEA0940} - Library Neo4jClient.Vb.Tests - Neo4jClient.Vb.Tests - 512 - Windows - v4.6.1 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{F184B08F-C81C-45F6-A57F-5ABD9991F28F} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - - - + netcoreapp3.0 + + false - - true - full - true - true - bin\Debug\ - Neo4jClient.Vb.Tests.xml - 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 - - - pdbonly - false - true - true - bin\Release\ - Neo4jClient.Vb.Tests.xml - 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 - - - On - - - Binary - - - Off - - - On - - - true - true - true - bin\Full - Debug\ - Neo4jClient.Vb.Tests.xml - 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 - full - AnyCPU - MinimumRecommendedRules.ruleset - - - true - true - true - bin\Portable - Debug\ - Neo4jClient.Vb.Tests.xml - 42016,41999,42017,42018,42019,42032,42036,42020,42021,42022 - full - AnyCPU - MinimumRecommendedRules.ruleset - - - - ..\packages\Castle.Core.4.2.1\lib\net45\Castle.Core.dll - - - ..\packages\FluentAssertions.5.2.0\lib\net45\FluentAssertions.dll - - - ..\packages\Microsoft.Win32.Primitives.4.3.0\lib\net46\Microsoft.Win32.Primitives.dll - True - - - ..\packages\Moq.4.8.2\lib\net45\Moq.dll - - - ..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - - - - ..\packages\System.AppContext.4.3.0\lib\net46\System.AppContext.dll - True - - - - - ..\packages\System.Console.4.3.0\lib\net46\System.Console.dll - True - - - - ..\packages\System.Diagnostics.DiagnosticSource.4.4.1\lib\net46\System.Diagnostics.DiagnosticSource.dll - - - ..\packages\System.Globalization.Calendars.4.3.0\lib\net46\System.Globalization.Calendars.dll - True - - - ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll - True - - - - ..\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll - True - - - ..\packages\System.IO.FileSystem.4.3.0\lib\net46\System.IO.FileSystem.dll - True - - - ..\packages\System.IO.FileSystem.Primitives.4.3.0\lib\net46\System.IO.FileSystem.Primitives.dll - True - - - ..\packages\System.Net.Http.4.3.2\lib\net46\System.Net.Http.dll - - - - ..\packages\System.Net.Sockets.4.3.0\lib\net46\System.Net.Sockets.dll - True - - - - ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - True - - - ..\packages\System.Security.Cryptography.Algorithms.4.3.0\lib\net461\System.Security.Cryptography.Algorithms.dll - True - - - ..\packages\System.Security.Cryptography.Encoding.4.3.0\lib\net46\System.Security.Cryptography.Encoding.dll - True - - - ..\packages\System.Security.Cryptography.Primitives.4.3.0\lib\net46\System.Security.Cryptography.Primitives.dll - True - - - ..\packages\System.Security.Cryptography.X509Certificates.4.3.0\lib\net461\System.Security.Cryptography.X509Certificates.dll - True - - - ..\packages\System.Threading.Tasks.Extensions.4.3.0\lib\portable-net45+win8+wp8+wpa81\System.Threading.Tasks.Extensions.dll - - - ..\packages\System.ValueTuple.4.4.0\lib\net461\System.ValueTuple.dll - - - - - - - ..\packages\System.Xml.ReaderWriter.4.3.0\lib\net46\System.Xml.ReaderWriter.dll - True - - - ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll - - - ..\packages\xunit.assert.2.3.1\lib\netstandard1.1\xunit.assert.dll - - - ..\packages\xunit.extensibility.core.2.3.1\lib\netstandard1.1\xunit.core.dll - - - ..\packages\xunit.extensibility.execution.2.3.1\lib\net452\xunit.execution.desktop.dll - - - - - - - - - - - - - - - - - - - - - - - - - - True - Application.myapp - - - True - True - Resources.resx - - - True - Settings.settings - True - - - - - - VbMyResourcesResXFileCodeGenerator - Resources.Designer.vb - My.Resources - Designer - - - - - - MyApplicationCodeGenerator - Application.Designer.vb - - - SettingsSingleFileGenerator - My - Settings.Designer.vb - - - + - - {343b9889-6ddf-4474-a1ec-05508a652e5a} - Neo4jClient.Full - + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + - + - - - - - False - - - False - - - False - - - False - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - \ No newline at end of file + + diff --git a/Neo4jClient.Vb.Tests/app.config b/Neo4jClient.Vb.Tests/app.config deleted file mode 100644 index 02111d4b1..000000000 --- a/Neo4jClient.Vb.Tests/app.config +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.Vb.Tests/packages.config b/Neo4jClient.Vb.Tests/packages.config deleted file mode 100644 index fdd0fe249..000000000 --- a/Neo4jClient.Vb.Tests/packages.config +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/Neo4jClient.nuspec b/Neo4jClient.nuspec index f4195d6a7..0b68bbe1a 100644 --- a/Neo4jClient.nuspec +++ b/Neo4jClient.nuspec @@ -8,38 +8,29 @@ https://github.com/Readify/Neo4jClient/blob/master/LICENSE https://github.com/Readify/Neo4jClient A .NET client for neo4j: an open source, transactional graph database. It's pretty awesome. - neo4j nosql cypher + neo4j nosql cypher bolt graph - - - - - - - + + - - - + + - - + + - - - - - - - - - + + + + + + diff --git a/Neo4jClient.sln b/Neo4jClient.sln index c22d07909..ca76a587a 100644 --- a/Neo4jClient.sln +++ b/Neo4jClient.sln @@ -1,55 +1,27 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 -VisualStudioVersion = 16.0.30204.135 +VisualStudioVersion = 16.0.29424.173 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient.Full", "Neo4jClient.Full\Neo4jClient.Full.csproj", "{343B9889-6DDF-4474-A1EC-05508A652E5A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo4jClient", "Neo4jClient\Neo4jClient.csproj", "{40041343-896B-47F0-BC20-98B8AE23291A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient.Tests", "Neo4jClient.Tests\Neo4jClient.Tests.csproj", "{2724A871-4B4F-4C83-8E0F-2439F69CADA2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo4jClient.Tests", "Neo4jClient.Tests\Neo4jClient.Tests.csproj", "{7BDF43D1-C655-4912-888E-90DA18428427}" EndProject -Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Neo4jClient.Vb.Tests", "Neo4jClient.Vb.Tests\Neo4jClient.Vb.Tests.vbproj", "{D87DA5F1-DA55-43BF-8A6C-F2BF7BEA0940}" +Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Neo4jClient.FSharp.Tests", "Neo4jClient.FSharp.Tests\Neo4jClient.FSharp.Tests.fsproj", "{612B22D2-0799-44A7-87D6-D35A81303D73}" EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Neo4jClient.Shared", "Neo4jClient.Shared\Neo4jClient.Shared.shproj", "{C5B928CA-3E8E-4181-BACD-48CED40CEFDE}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{BDE80529-C2DE-4E79-86AB-C514A29A0C7A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient", "Neo4jClient\Neo4jClient.csproj", "{E568B54B-F463-4346-818E-9EC8348C6470}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A6744547-9D7F-4FF5-B7C5-A184CFBF1E06}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A9638492-937A-43FC-B377-D5D51107C565}" ProjectSection(SolutionItems) = preProject - build.bat = build.bat + .gitignore = .gitignore LICENSE = LICENSE Neo4jClient.nuspec = Neo4jClient.nuspec README.md = README.md EndProjectSection EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Neo4jClient.Tests.Core", "Neo4jClient.Tests.Core\Neo4jClient.Tests.Core.csproj", "{F627D73A-CC89-48E8-B603-89035FEEAB2B}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Neo4jClient.Tests.Shared", "Neo4jClient.Tests.Shared\Neo4jClient.Tests.Shared.shproj", "{22394FE7-45CA-4F89-BD96-D00511C1205C}" -EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Neo4jClient.Full.Shared", "Neo4jClient.Full.Shared\Neo4jClient.Full.Shared.shproj", "{8BEF4534-C265-41DB-916C-A453155FC6A8}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo4jClient.Full452", "Neo4jClient.Full452\Neo4jClient.Full452.csproj", "{99599429-A450-4AFB-8AE4-3A114F317D49}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Full .NET Framework", "Full .NET Framework", "{E26DF486-F202-4077-8342-6D7F3814F78F}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core, NetStandard Framework", "Core, NetStandard Framework", "{CDA6EECF-5EA2-42E8-9A23-9CB8ABCDADDB}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{50240473-4B2D-4623-8CE9-7DFF91140574}" -EndProject -Project("{6EC3EE1D-3C4E-46DD-8F32-0CC8E7565705}") = "Neo4jClient.FSharp.Tests", "Neo4jClient.FSharp.Tests\Neo4jClient.FSharp.Tests.fsproj", "{94869E02-1F43-4891-BD6C-0143A273802D}" +Project("{F184B08F-C81C-45F6-A57F-5ABD9991F28F}") = "Neo4jClient.Vb.Tests", "Neo4jClient.Vb.Tests\Neo4jClient.Vb.Tests.vbproj", "{B626C23A-F801-4C65-8D7B-2BEC04C1B0B9}" EndProject Global - GlobalSection(SharedMSBuildProjectFiles) = preSolution - Neo4jClient.Tests.Shared\Neo4jClient.Tests.Shared.projitems*{22394fe7-45ca-4f89-bd96-d00511c1205c}*SharedItemsImports = 13 - Neo4jClient.Tests.Shared\Neo4jClient.Tests.Shared.projitems*{2724a871-4b4f-4c83-8e0f-2439f69cada2}*SharedItemsImports = 4 - Neo4jClient.Full.Shared\Neo4jClient.Full.Shared.projitems*{343b9889-6ddf-4474-a1ec-05508a652e5a}*SharedItemsImports = 4 - Neo4jClient.Shared\Neo4jClient.Shared.projitems*{343b9889-6ddf-4474-a1ec-05508a652e5a}*SharedItemsImports = 4 - Neo4jClient.Full.Shared\Neo4jClient.Full.Shared.projitems*{8bef4534-c265-41db-916c-a453155fc6a8}*SharedItemsImports = 13 - Neo4jClient.Full.Shared\Neo4jClient.Full.Shared.projitems*{99599429-a450-4afb-8ae4-3a114f317d49}*SharedItemsImports = 4 - Neo4jClient.Shared\Neo4jClient.Shared.projitems*{99599429-a450-4afb-8ae4-3a114f317d49}*SharedItemsImports = 4 - Neo4jClient.Shared\Neo4jClient.Shared.projitems*{c5b928ca-3e8e-4181-bacd-48ced40cefde}*SharedItemsImports = 13 - Neo4jClient.Shared\Neo4jClient.Shared.projitems*{e568b54b-f463-4346-818e-9ec8348c6470}*SharedItemsImports = 4 - Neo4jClient.Tests.Shared\Neo4jClient.Tests.Shared.projitems*{f627d73a-cc89-48e8-b603-89035feeab2b}*SharedItemsImports = 5 - EndGlobalSection GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Full - Debug|Any CPU = Full - Debug|Any CPU @@ -57,72 +29,48 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {343B9889-6DDF-4474-A1EC-05508A652E5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {343B9889-6DDF-4474-A1EC-05508A652E5A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {343B9889-6DDF-4474-A1EC-05508A652E5A}.Full - Debug|Any CPU.ActiveCfg = Full - Debug|Any CPU - {343B9889-6DDF-4474-A1EC-05508A652E5A}.Full - Debug|Any CPU.Build.0 = Full - Debug|Any CPU - {343B9889-6DDF-4474-A1EC-05508A652E5A}.Portable - Debug|Any CPU.ActiveCfg = Portable - Debug|Any CPU - {343B9889-6DDF-4474-A1EC-05508A652E5A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {343B9889-6DDF-4474-A1EC-05508A652E5A}.Release|Any CPU.Build.0 = Release|Any CPU - {2724A871-4B4F-4C83-8E0F-2439F69CADA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2724A871-4B4F-4C83-8E0F-2439F69CADA2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2724A871-4B4F-4C83-8E0F-2439F69CADA2}.Full - Debug|Any CPU.ActiveCfg = Full - Debug|Any CPU - {2724A871-4B4F-4C83-8E0F-2439F69CADA2}.Portable - Debug|Any CPU.ActiveCfg = Portable - Debug|Any CPU - {2724A871-4B4F-4C83-8E0F-2439F69CADA2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2724A871-4B4F-4C83-8E0F-2439F69CADA2}.Release|Any CPU.Build.0 = Release|Any CPU - {D87DA5F1-DA55-43BF-8A6C-F2BF7BEA0940}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D87DA5F1-DA55-43BF-8A6C-F2BF7BEA0940}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D87DA5F1-DA55-43BF-8A6C-F2BF7BEA0940}.Full - Debug|Any CPU.ActiveCfg = Full - Debug|Any CPU - {D87DA5F1-DA55-43BF-8A6C-F2BF7BEA0940}.Portable - Debug|Any CPU.ActiveCfg = Portable - Debug|Any CPU - {D87DA5F1-DA55-43BF-8A6C-F2BF7BEA0940}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D87DA5F1-DA55-43BF-8A6C-F2BF7BEA0940}.Release|Any CPU.Build.0 = Release|Any CPU - {E568B54B-F463-4346-818E-9EC8348C6470}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E568B54B-F463-4346-818E-9EC8348C6470}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E568B54B-F463-4346-818E-9EC8348C6470}.Full - Debug|Any CPU.ActiveCfg = Full - Debug|Any CPU - {E568B54B-F463-4346-818E-9EC8348C6470}.Portable - Debug|Any CPU.ActiveCfg = Portable - Debug|Any CPU - {E568B54B-F463-4346-818E-9EC8348C6470}.Portable - Debug|Any CPU.Build.0 = Portable - Debug|Any CPU - {E568B54B-F463-4346-818E-9EC8348C6470}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E568B54B-F463-4346-818E-9EC8348C6470}.Release|Any CPU.Build.0 = Release|Any CPU - {F627D73A-CC89-48E8-B603-89035FEEAB2B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F627D73A-CC89-48E8-B603-89035FEEAB2B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F627D73A-CC89-48E8-B603-89035FEEAB2B}.Full - Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F627D73A-CC89-48E8-B603-89035FEEAB2B}.Full - Debug|Any CPU.Build.0 = Debug|Any CPU - {F627D73A-CC89-48E8-B603-89035FEEAB2B}.Portable - Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F627D73A-CC89-48E8-B603-89035FEEAB2B}.Portable - Debug|Any CPU.Build.0 = Debug|Any CPU - {F627D73A-CC89-48E8-B603-89035FEEAB2B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F627D73A-CC89-48E8-B603-89035FEEAB2B}.Release|Any CPU.Build.0 = Release|Any CPU - {99599429-A450-4AFB-8AE4-3A114F317D49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99599429-A450-4AFB-8AE4-3A114F317D49}.Debug|Any CPU.Build.0 = Debug|Any CPU - {99599429-A450-4AFB-8AE4-3A114F317D49}.Full - Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99599429-A450-4AFB-8AE4-3A114F317D49}.Full - Debug|Any CPU.Build.0 = Debug|Any CPU - {99599429-A450-4AFB-8AE4-3A114F317D49}.Portable - Debug|Any CPU.ActiveCfg = Debug|Any CPU - {99599429-A450-4AFB-8AE4-3A114F317D49}.Portable - Debug|Any CPU.Build.0 = Debug|Any CPU - {99599429-A450-4AFB-8AE4-3A114F317D49}.Release|Any CPU.ActiveCfg = Release|Any CPU - {99599429-A450-4AFB-8AE4-3A114F317D49}.Release|Any CPU.Build.0 = Release|Any CPU - {94869E02-1F43-4891-BD6C-0143A273802D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94869E02-1F43-4891-BD6C-0143A273802D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94869E02-1F43-4891-BD6C-0143A273802D}.Full - Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94869E02-1F43-4891-BD6C-0143A273802D}.Full - Debug|Any CPU.Build.0 = Debug|Any CPU - {94869E02-1F43-4891-BD6C-0143A273802D}.Portable - Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94869E02-1F43-4891-BD6C-0143A273802D}.Portable - Debug|Any CPU.Build.0 = Debug|Any CPU - {94869E02-1F43-4891-BD6C-0143A273802D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {94869E02-1F43-4891-BD6C-0143A273802D}.Release|Any CPU.Build.0 = Release|Any CPU + {40041343-896B-47F0-BC20-98B8AE23291A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40041343-896B-47F0-BC20-98B8AE23291A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {40041343-896B-47F0-BC20-98B8AE23291A}.Full - Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40041343-896B-47F0-BC20-98B8AE23291A}.Full - Debug|Any CPU.Build.0 = Debug|Any CPU + {40041343-896B-47F0-BC20-98B8AE23291A}.Portable - Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40041343-896B-47F0-BC20-98B8AE23291A}.Portable - Debug|Any CPU.Build.0 = Debug|Any CPU + {40041343-896B-47F0-BC20-98B8AE23291A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {40041343-896B-47F0-BC20-98B8AE23291A}.Release|Any CPU.Build.0 = Release|Any CPU + {7BDF43D1-C655-4912-888E-90DA18428427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BDF43D1-C655-4912-888E-90DA18428427}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BDF43D1-C655-4912-888E-90DA18428427}.Full - Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BDF43D1-C655-4912-888E-90DA18428427}.Full - Debug|Any CPU.Build.0 = Debug|Any CPU + {7BDF43D1-C655-4912-888E-90DA18428427}.Portable - Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BDF43D1-C655-4912-888E-90DA18428427}.Portable - Debug|Any CPU.Build.0 = Debug|Any CPU + {7BDF43D1-C655-4912-888E-90DA18428427}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BDF43D1-C655-4912-888E-90DA18428427}.Release|Any CPU.Build.0 = Release|Any CPU + {612B22D2-0799-44A7-87D6-D35A81303D73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {612B22D2-0799-44A7-87D6-D35A81303D73}.Debug|Any CPU.Build.0 = Debug|Any CPU + {612B22D2-0799-44A7-87D6-D35A81303D73}.Full - Debug|Any CPU.ActiveCfg = Debug|Any CPU + {612B22D2-0799-44A7-87D6-D35A81303D73}.Full - Debug|Any CPU.Build.0 = Debug|Any CPU + {612B22D2-0799-44A7-87D6-D35A81303D73}.Portable - Debug|Any CPU.ActiveCfg = Debug|Any CPU + {612B22D2-0799-44A7-87D6-D35A81303D73}.Portable - Debug|Any CPU.Build.0 = Debug|Any CPU + {612B22D2-0799-44A7-87D6-D35A81303D73}.Release|Any CPU.ActiveCfg = Release|Any CPU + {612B22D2-0799-44A7-87D6-D35A81303D73}.Release|Any CPU.Build.0 = Release|Any CPU + {B626C23A-F801-4C65-8D7B-2BEC04C1B0B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B626C23A-F801-4C65-8D7B-2BEC04C1B0B9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B626C23A-F801-4C65-8D7B-2BEC04C1B0B9}.Full - Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B626C23A-F801-4C65-8D7B-2BEC04C1B0B9}.Full - Debug|Any CPU.Build.0 = Debug|Any CPU + {B626C23A-F801-4C65-8D7B-2BEC04C1B0B9}.Portable - Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B626C23A-F801-4C65-8D7B-2BEC04C1B0B9}.Portable - Debug|Any CPU.Build.0 = Debug|Any CPU + {B626C23A-F801-4C65-8D7B-2BEC04C1B0B9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B626C23A-F801-4C65-8D7B-2BEC04C1B0B9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {343B9889-6DDF-4474-A1EC-05508A652E5A} = {E26DF486-F202-4077-8342-6D7F3814F78F} - {2724A871-4B4F-4C83-8E0F-2439F69CADA2} = {50240473-4B2D-4623-8CE9-7DFF91140574} - {D87DA5F1-DA55-43BF-8A6C-F2BF7BEA0940} = {50240473-4B2D-4623-8CE9-7DFF91140574} - {E568B54B-F463-4346-818E-9EC8348C6470} = {CDA6EECF-5EA2-42E8-9A23-9CB8ABCDADDB} - {F627D73A-CC89-48E8-B603-89035FEEAB2B} = {50240473-4B2D-4623-8CE9-7DFF91140574} - {22394FE7-45CA-4F89-BD96-D00511C1205C} = {50240473-4B2D-4623-8CE9-7DFF91140574} - {8BEF4534-C265-41DB-916C-A453155FC6A8} = {E26DF486-F202-4077-8342-6D7F3814F78F} - {99599429-A450-4AFB-8AE4-3A114F317D49} = {E26DF486-F202-4077-8342-6D7F3814F78F} - {94869E02-1F43-4891-BD6C-0143A273802D} = {50240473-4B2D-4623-8CE9-7DFF91140574} + {7BDF43D1-C655-4912-888E-90DA18428427} = {BDE80529-C2DE-4E79-86AB-C514A29A0C7A} + {612B22D2-0799-44A7-87D6-D35A81303D73} = {BDE80529-C2DE-4E79-86AB-C514A29A0C7A} + {B626C23A-F801-4C65-8D7B-2BEC04C1B0B9} = {BDE80529-C2DE-4E79-86AB-C514A29A0C7A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {D760067A-9B0B-4311-8BD9-D1B4D9B00BA5} + SolutionGuid = {60B01A44-2FCA-4100-A3AE-B5E07605DC23} EndGlobalSection EndGlobal diff --git a/Neo4jClient.Shared/AddressResolver.cs b/Neo4jClient/AddressResolver.cs similarity index 98% rename from Neo4jClient.Shared/AddressResolver.cs rename to Neo4jClient/AddressResolver.cs index c33a6d85f..3ab4172ac 100644 --- a/Neo4jClient.Shared/AddressResolver.cs +++ b/Neo4jClient/AddressResolver.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; -using Neo4j.Driver.V1; +using Neo4j.Driver; namespace Neo4jClient { diff --git a/Neo4jClient.Shared/AggregateExceptionExtensions.cs b/Neo4jClient/AggregateExceptionExtensions.cs similarity index 100% rename from Neo4jClient.Shared/AggregateExceptionExtensions.cs rename to Neo4jClient/AggregateExceptionExtensions.cs diff --git a/Neo4jClient.Shared/AmbiguousRelationshipDirectionException.cs b/Neo4jClient/AmbiguousRelationshipDirectionException.cs similarity index 100% rename from Neo4jClient.Shared/AmbiguousRelationshipDirectionException.cs rename to Neo4jClient/AmbiguousRelationshipDirectionException.cs diff --git a/Neo4jClient.Shared/ApiModels/BatchResponse.cs b/Neo4jClient/ApiModels/BatchResponse.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/BatchResponse.cs rename to Neo4jClient/ApiModels/BatchResponse.cs diff --git a/Neo4jClient.Shared/ApiModels/BatchStep.cs b/Neo4jClient/ApiModels/BatchStep.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/BatchStep.cs rename to Neo4jClient/ApiModels/BatchStep.cs diff --git a/Neo4jClient.Shared/ApiModels/BatchStepExtensions.cs b/Neo4jClient/ApiModels/BatchStepExtensions.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/BatchStepExtensions.cs rename to Neo4jClient/ApiModels/BatchStepExtensions.cs diff --git a/Neo4jClient.Shared/ApiModels/BatchStepResult.cs b/Neo4jClient/ApiModels/BatchStepResult.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/BatchStepResult.cs rename to Neo4jClient/ApiModels/BatchStepResult.cs diff --git a/Neo4jClient.Shared/ApiModels/Cypher/CypherApiQuery.cs b/Neo4jClient/ApiModels/Cypher/CypherApiQuery.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/Cypher/CypherApiQuery.cs rename to Neo4jClient/ApiModels/Cypher/CypherApiQuery.cs diff --git a/Neo4jClient.Shared/ApiModels/Cypher/CypherStatementList.cs b/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs similarity index 93% rename from Neo4jClient.Shared/ApiModels/Cypher/CypherStatementList.cs rename to Neo4jClient/ApiModels/Cypher/CypherStatementList.cs index ac6a7ab57..f46d58a03 100644 --- a/Neo4jClient.Shared/ApiModels/Cypher/CypherStatementList.cs +++ b/Neo4jClient/ApiModels/Cypher/CypherStatementList.cs @@ -22,15 +22,12 @@ public CypherStatementList() public CypherStatementList(IEnumerable queries) { _statements = queries - .Select(query => new CypherTransactionStatement(query, query.ResultFormat == CypherResultFormat.Rest)) + .Select(query => new CypherTransactionStatement(query)) .ToList(); } [JsonProperty("statements")] - public IList Statements - { - get { return _statements; } - } + public IList Statements => _statements; public IEnumerator GetEnumerator() { diff --git a/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs b/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs new file mode 100644 index 000000000..e99443f32 --- /dev/null +++ b/Neo4jClient/ApiModels/Cypher/CypherTransactionStatement.cs @@ -0,0 +1,36 @@ +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 + /// + internal class CypherTransactionStatement + { + private readonly string[] formatContents; + + public CypherTransactionStatement(CypherQuery query) + { + Statement = query.QueryText; + Parameters = query.QueryParameters ?? new Dictionary(); + formatContents = new string[] {}; + if (query.IncludeQueryStats) + IncludeStats = query.IncludeQueryStats; + } + + [JsonProperty("statement")] + public string Statement { get; } + + [JsonProperty("resultDataContents")] + public IEnumerable ResultDataContents => formatContents; + + [JsonProperty("parameters")] + public IDictionary Parameters { get; } + + [JsonProperty("includeStats", NullValueHandling = NullValueHandling.Ignore)] + public bool? IncludeStats { get; } + } +} diff --git a/Neo4jClient.Shared/ApiModels/Cypher/PathsResult.cs b/Neo4jClient/ApiModels/Cypher/PathsResult.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/Cypher/PathsResult.cs rename to Neo4jClient/ApiModels/Cypher/PathsResult.cs diff --git a/Neo4jClient.Shared/ApiModels/Cypher/PathsResultBolt.cs b/Neo4jClient/ApiModels/Cypher/PathsResultBolt.cs similarity index 99% rename from Neo4jClient.Shared/ApiModels/Cypher/PathsResultBolt.cs rename to Neo4jClient/ApiModels/Cypher/PathsResultBolt.cs index bc7050a6a..291307ec4 100644 --- a/Neo4jClient.Shared/ApiModels/Cypher/PathsResultBolt.cs +++ b/Neo4jClient/ApiModels/Cypher/PathsResultBolt.cs @@ -1,6 +1,6 @@ using System.Collections.Generic; using System.Linq; -using Neo4j.Driver.V1; +using Neo4j.Driver; using Neo4jClient.Extensions; using Newtonsoft.Json; diff --git a/Neo4jClient/ApiModels/Cypher/QueryStats.cs b/Neo4jClient/ApiModels/Cypher/QueryStats.cs new file mode 100644 index 000000000..8dc3e7dc6 --- /dev/null +++ b/Neo4jClient/ApiModels/Cypher/QueryStats.cs @@ -0,0 +1,41 @@ +using Newtonsoft.Json; + +namespace Neo4jClient.ApiModels.Cypher +{ + public class QueryStats + { + [JsonProperty("contains_updates")] public bool ContainsUpdates { get; set; } + [JsonProperty("nodes_created")] public int NodesCreated { get; set; } + [JsonProperty("nodes_deleted")] public int NodesDeleted { get; set; } + [JsonProperty("properties_set")] public int PropertiesSet { get; set; } + [JsonProperty("relationships_created")] public int RelationshipsCreated { get; set; } + [JsonProperty("relationship_deleted")] public int RelationshipsDeleted { get; set; } + [JsonProperty("labels_added")] public int LabelsAdded { get; set; } + [JsonProperty("labels_removed")] public int LabelsRemoved { get; set; } + [JsonProperty("indexes_added")] public int IndexesAdded { get; set; } + [JsonProperty("indexes_removed")] public int IndexesRemoved { get; set; } + [JsonProperty("constraints_added")] public int ConstraintsAdded { get; set; } + [JsonProperty("constraints_removed")] public int ConstraintsRemoved { get; set; } + [JsonProperty("contains_system_updates")] public bool ContainsSystemUpdates { get; set; } + [JsonProperty("system_updates")] public int SystemUpdates { get; set; } + + public QueryStats(){} + public QueryStats(Neo4j.Driver.ICounters counters) + { + ContainsUpdates = counters.ContainsUpdates; + NodesCreated = counters.NodesCreated; + NodesDeleted = counters.NodesDeleted; + PropertiesSet = counters.PropertiesSet; + RelationshipsCreated = counters.RelationshipsCreated; + RelationshipsDeleted = counters.RelationshipsDeleted; + LabelsAdded = counters.LabelsAdded; + LabelsRemoved = counters.LabelsRemoved; + IndexesAdded = counters.IndexesAdded; + IndexesRemoved = counters.IndexesRemoved; + ConstraintsAdded = counters.ConstraintsAdded; + ConstraintsRemoved = counters.ConstraintsRemoved; + ContainsSystemUpdates = counters.ContainsSystemUpdates; + SystemUpdates = counters.SystemUpdates; + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Shared/ApiModels/ExceptionResponse.cs b/Neo4jClient/ApiModels/ExceptionResponse.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/ExceptionResponse.cs rename to Neo4jClient/ApiModels/ExceptionResponse.cs diff --git a/Neo4jClient.Shared/ApiModels/FieldChange.cs b/Neo4jClient/ApiModels/FieldChange.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/FieldChange.cs rename to Neo4jClient/ApiModels/FieldChange.cs diff --git a/Neo4jClient.Shared/ApiModels/NodeApiResponse.cs b/Neo4jClient/ApiModels/NodeApiResponse.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/NodeApiResponse.cs rename to Neo4jClient/ApiModels/NodeApiResponse.cs diff --git a/Neo4jClient.Shared/ApiModels/NodeOrRelationshipApiResponse.cs b/Neo4jClient/ApiModels/NodeOrRelationshipApiResponse.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/NodeOrRelationshipApiResponse.cs rename to Neo4jClient/ApiModels/NodeOrRelationshipApiResponse.cs diff --git a/Neo4jClient.Shared/ApiModels/RelationshipApiResponse.cs b/Neo4jClient/ApiModels/RelationshipApiResponse.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/RelationshipApiResponse.cs rename to Neo4jClient/ApiModels/RelationshipApiResponse.cs diff --git a/Neo4jClient.Shared/ApiModels/RelationshipTemplate.cs b/Neo4jClient/ApiModels/RelationshipTemplate.cs similarity index 100% rename from Neo4jClient.Shared/ApiModels/RelationshipTemplate.cs rename to Neo4jClient/ApiModels/RelationshipTemplate.cs diff --git a/Neo4jClient/ApiModels/RootApiResponse.cs b/Neo4jClient/ApiModels/RootApiResponse.cs new file mode 100644 index 000000000..929c49799 --- /dev/null +++ b/Neo4jClient/ApiModels/RootApiResponse.cs @@ -0,0 +1,118 @@ +using System; +using System.Text.RegularExpressions; +using Newtonsoft.Json; + +namespace Neo4jClient.ApiModels +{ + class RootApiResponse + { + private string _transactionFormat; + private string _transaction; + + [JsonProperty("transaction")] + public string Transaction + { + get => _transaction; + set + { + _transaction = value; + _transactionFormat = null; + } + } + + internal string TransactionFormat + { + get + { + if (string.IsNullOrWhiteSpace(_transactionFormat)) + _transactionFormat = Transaction?.Replace("databaseName", "0"); + + return _transactionFormat; + } + } + + // [JsonProperty("cypher")] + // public string Cypher { get; set; } + + [JsonProperty("batch")] + public string Batch { get; set; } + + [JsonProperty("node")] + public string Node { get; set; } + + [JsonProperty("relationship")] + public string Relationship { get; set; } + + [JsonProperty("node_index")] + public string NodeIndex { get; set; } + + [JsonProperty("relationship_index")] + public string RelationshipIndex { get; set; } + + [JsonProperty("reference_node")] + public string ReferenceNode { get; set; } + + [JsonProperty("extensions_info")] + public string ExtensionsInfo { get; set; } + + [JsonProperty("neo4j_version")] + public string Neo4jVersion { get; set; } + + [JsonProperty("neo4j_edition")] + public string Neo4jEdition { get; set; } + + [JsonProperty("bolt_direct")] + public string BoltDirect { get; set; } + + [JsonProperty("bolt_routing")] + public string BoltRouting { get; set; } + + [JsonProperty("cluster")] + public string Cluster { get; set; } + + /// + /// Returns a structured representation of the Neo4j server version, but only with partial data. + /// The version type (milestone, preview, release candidate, stable) is not taken in to account, + /// so both 1.9.M01, 1.9.RC1 and 1.9.1 will all return 1.9.0.1. + /// + [JsonIgnore] + public Version Version => GetVersion(Neo4jVersion); + + internal static Version GetVersion(string version) + { + if (string.IsNullOrEmpty(version)) + return new Version(0, 0); + + var numericalVersionString = Regex.Replace( + version, + @"(?\d*)[.](?\d*)[.]?(M(?\d*)|RC(?\d*)?).*", + "${major}.${minor}.0.${build}"); + + numericalVersionString = Regex.Replace( + numericalVersionString, + @"(?\d*)[.](?\d*)-.*", + "${major}.${minor}"); + + Version result; + var parsed = Version.TryParse(numericalVersionString, out result); + + return parsed ? result : new Version(0, 0); + } + + internal void TrimUriFromProperties(string absoluteUri) + { + var baseUriLengthToTrim = absoluteUri.Length - 1; + + Batch = (string.IsNullOrWhiteSpace(Batch)) ? null : Batch.Substring(baseUriLengthToTrim); + Node = (string.IsNullOrWhiteSpace(Node)) ? null : Node.Substring(baseUriLengthToTrim); + NodeIndex = (string.IsNullOrWhiteSpace(NodeIndex)) ? null : NodeIndex.Substring(baseUriLengthToTrim); + Relationship = "/relationship"; //Doesn't come in on the Service Root + RelationshipIndex = (string.IsNullOrWhiteSpace(RelationshipIndex)) ? null : RelationshipIndex.Substring(baseUriLengthToTrim); + ExtensionsInfo = (string.IsNullOrWhiteSpace(ExtensionsInfo)) ? null : ExtensionsInfo.Substring(baseUriLengthToTrim); + + Transaction = (string.IsNullOrWhiteSpace(Transaction)) ? null : Transaction.Substring(baseUriLengthToTrim); + //Cypher = (string.IsNullOrWhiteSpace(Cypher)) ? string.Empty : Cypher.Substring(baseUriLengthToTrim); + Cluster = (string.IsNullOrWhiteSpace(Cluster)) ? null : Cluster.Substring(baseUriLengthToTrim); + } + } +} diff --git a/Neo4jClient.Shared/Attributes/Neo4jDateTimeAttribute.cs b/Neo4jClient/Attributes/Neo4jDateTimeAttribute.cs similarity index 100% rename from Neo4jClient.Shared/Attributes/Neo4jDateTimeAttribute.cs rename to Neo4jClient/Attributes/Neo4jDateTimeAttribute.cs diff --git a/Neo4jClient/Attributes/Neo4jIgnoreAttribute.cs b/Neo4jClient/Attributes/Neo4jIgnoreAttribute.cs new file mode 100644 index 000000000..74ffc15f8 --- /dev/null +++ b/Neo4jClient/Attributes/Neo4jIgnoreAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace Neo4jClient +{ + [AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)] + public class Neo4jIgnoreAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/Neo4jClient/BoltGraphClient.cs b/Neo4jClient/BoltGraphClient.cs index 7ff56878d..a31267017 100644 --- a/Neo4jClient/BoltGraphClient.cs +++ b/Neo4jClient/BoltGraphClient.cs @@ -1,20 +1,61 @@ -using System; +using System; using System.Collections.Generic; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Diagnostics.SymbolStore; using System.Linq; using System.Reflection; -using Neo4j.Driver.V1; +using System.Threading.Tasks; +using Neo4j.Driver; +using Neo4jClient.ApiModels; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; using Neo4jClient.Execution; using Neo4jClient.Serialization; using Neo4jClient.Transactions; +using Neo4jClient.Transactions.Bolt; using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using ITransaction = Neo4jClient.Transactions.ITransaction; //TODO: Logging //TODO: Config Stuff +//TODO: Transaction Stuff namespace Neo4jClient { - public partial class BoltGraphClient : IBoltGraphClient, IRawGraphClient, ITransactionalGraphClient + using Neo4jClient.Extensions; + + /// + /// The is the client for connecting to the Bolt protocol of Neo4j. + /// + public class BoltGraphClient : IBoltGraphClient, IRawGraphClient, ITransactionalGraphClient { + public ITransactionalGraphClient Tx => this; + + internal const string NotValidForBolt = "This is not available using the BoltGraphClient."; + + internal static readonly JsonConverter[] DefaultJsonConverters = + { + new TypeConverterBasedJsonConverter(), + new NullableEnumValueConverter(), + new TimeZoneInfoConverter(), + new EnumValueConverter(), + new ZonedDateTimeConverter(), + new LocalDateTimeConverter() + }; + + private static readonly DefaultContractResolver DefaultJsonContractResolver = new DefaultContractResolver(); + + private readonly string password; + private readonly string realm; + + private readonly ITransactionManager transactionManager; + private readonly IServerAddressResolver addressResolver; + private readonly string username; + private readonly Uri uri; + private readonly EncryptionLevel? encryptionLevel; + /// /// Creates a new instance of the . /// @@ -29,25 +70,27 @@ public partial class BoltGraphClient : IBoltGraphClient, IRawGraphClient, ITrans /// The username to connect to Neo4j with. /// The password to connect to Neo4j with. /// The realm to connect to Neo4j with. - public BoltGraphClient(Uri uri, IEnumerable uris, string username = null, string password = null, string realm = null) + public BoltGraphClient(Uri uri, IEnumerable uris, string username = null, string password = null, string realm = null, EncryptionLevel? encryptionLevel = null) { var localUris = uris?.ToList(); if (localUris != null && localUris.Any()) { - if (uri.Scheme.ToLowerInvariant() != "bolt+routing") + //TODO - const/etc these + if (!new [] {"neo4j", "neo4j+s", "neo4j+ssc"}.Contains(uri.Scheme.ToLowerInvariant())) throw new NotSupportedException($"To use the {nameof(BoltGraphClient)} you need to provide a 'bolt://' scheme, not '{uri.Scheme}'."); addressResolver = new AddressResolver(uri, localUris); } - else if (uri.Scheme.ToLowerInvariant() != "bolt" && uri.Scheme.ToLowerInvariant() != "bolt+routing") + else if (!new [] {"neo4j", "neo4j+s", "neo4j+ssc", "bolt", "bolt+s", "bolt+ssc"}.Contains(uri.Scheme.ToLowerInvariant())) { - throw new NotSupportedException($"To use the {nameof(BoltGraphClient)} you need to provide a 'bolt://' or 'bolt+routing://' scheme, not '{uri.Scheme}'."); + throw new NotSupportedException($"To use the {nameof(BoltGraphClient)} you need to provide a 'bolt://' or 'neo4j://' scheme, not '{uri.Scheme}'."); } this.uri = uri; this.username = username; this.password = password; this.realm = realm; + this.encryptionLevel = encryptionLevel; PolicyFactory = new ExecutionPolicyFactory(this); JsonConverters = new List(); @@ -64,7 +107,459 @@ public BoltGraphClient(Uri uri, IEnumerable uris, string username = null, s Realm = realm }; -// transactionManager = new TransactionManager(this); + transactionManager = new BoltTransactionManager(this); + } + + public BoltGraphClient(Uri uri, string username = null, string password = null, string realm = null, EncryptionLevel? encryptionLevel = null) + : this(uri, null, username, password, realm, encryptionLevel) + { } + + public BoltGraphClient(IEnumerable uris, string username = null, string password = null, string realm = null, EncryptionLevel? encryptionLevel = null) + : this(new Uri("neo4j://virtual.neo4j.uri"), uris?.Select(UriCreator.From).ToList(), username, password, realm, encryptionLevel) + { } + + public BoltGraphClient(string uri, IEnumerable uris, string username = null, string password = null, string realm = null, EncryptionLevel? encryptionLevel = null) + : this(new Uri(uri), uris?.Select(UriCreator.From).ToList(), username, password, realm, encryptionLevel) + {} + + public BoltGraphClient(string uri, string username = null, string password= null, string realm = null, EncryptionLevel? encryptionLevel = null) + : this(new Uri(uri), username, password, realm, encryptionLevel) + { } + + public BoltGraphClient(IDriver driver) + : this(new Uri("neo4j://Neo4j-Driver-Does-Not-Supply-This/"), null, null, null, null) + { + Driver = driver; + } + + internal IDriver Driver { get; set; } + internal IServerAddressResolver AddressResolver => addressResolver; + private IExecutionPolicyFactory PolicyFactory { get; } + + #region Implementation of ICypherGraphClient + + /// + public ICypherFluentQuery Cypher => new CypherFluentQuery(this, true); + #endregion + + private void CheckTransactionEnvironmentWithPolicy(IExecutionPolicy policy) + { + var inTransaction = InTransaction; + + if (inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Denied) + throw new InvalidOperationException("Cannot be done inside a transaction scope."); + + if (!inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Required) + throw new InvalidOperationException("Cannot be done outside a transaction scope."); + } + + #region ExecutionContext class + + internal class ExecutionContext + { + public string Database { get; set; } + private readonly Stopwatch stopwatch; + private BoltGraphClient owner; + + private ExecutionContext() + { + stopwatch = Stopwatch.StartNew(); + } + + public IExecutionPolicy Policy { get; set; } + public static bool HasErrors { get; set; } + + public static ExecutionContext Begin(BoltGraphClient owner) + { + var policy = owner.PolicyFactory.GetPolicy(PolicyType.Cypher); + + owner.CheckTransactionEnvironmentWithPolicy(policy); + + var executionContext = new ExecutionContext + { + owner = owner, + Policy = policy + }; + + return executionContext; + } + + public void Complete(CypherQuery query, Bookmark lastBookmark, QueryStats queryStats) + { + Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, lastBookmark, 0, null, identifier: query.Identifier, bookmarks: query.Bookmarks, stats:queryStats); + } + + public void Complete(CypherQuery query, Bookmark lastBookmark) + { + // only parse the events when there's an event handler + Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, lastBookmark, 0, null, identifier: query.Identifier, bookmarks: query.Bookmarks); + } + + public void Complete(CypherQuery query, Bookmark lastBookmark, int resultsCount) + { + // only parse the events when there's an event handler + Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, lastBookmark, resultsCount, null, query.CustomHeaders, identifier: query.Identifier, bookmarks: query.Bookmarks); + } + + public void Complete(CypherQuery query, Bookmark lastBookmark, int resultsCount, QueryStats stats) + { + // only parse the events when there's an event handler + Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, lastBookmark, resultsCount, null, query.CustomHeaders, identifier: query.Identifier, bookmarks: query.Bookmarks, stats: stats); + } + + public void Complete(CypherQuery query, Bookmark lastBookmark, Exception exception) + { + // only parse the events when there's an event handler + Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, lastBookmark, -1, exception, identifier:query.Identifier, bookmarks:query.Bookmarks); + } + + public void Complete(string queryText, Bookmark lastBookmark, int resultsCount = -1, Exception exception = null, NameValueCollection customHeaders = null, int? maxExecutionTime = null, string identifier = null, IEnumerable bookmarks = null, QueryStats stats = null) + { + var args = new OperationCompletedEventArgs + { + LastBookmark = lastBookmark, + QueryText = queryText, + ResourcesReturned = resultsCount, + TimeTaken = stopwatch.Elapsed, + Exception = exception, + CustomHeaders = customHeaders, + MaxExecutionTime = maxExecutionTime, + Identifier = identifier, + BookmarksUsed = bookmarks, + QueryStats = stats + }; + + owner.OnOperationCompleted(args); + } + } + + #endregion + + #region Implementation of IDisposable + + /// + protected void Dispose(bool isDisposing) + { + if (!isDisposing) + return; + + Driver?.Dispose(); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + #region Implementation of IGraphClient + + + /// + public async Task ConnectAsync(NeoServerConfiguration configuration = null) + { + if (Driver == null) + { + var driver = configuration == null + ? new DriverWrapper(uri, addressResolver, username, password, realm, encryptionLevel) + : new DriverWrapper(uri, addressResolver, configuration.Username, configuration.Password, configuration.Realm, configuration.EncryptionLevel); + Driver = driver; + } + + var session = Driver.AsyncSession(x => x.WithDefaultAccessMode(AccessMode.Read)); + + var serverInformation = await session.RunAsync("CALL dbms.components()").ConfigureAwait(false); + foreach (var record in await serverInformation.ToListAsync().ConfigureAwait(false)) + { + var name = record["name"].As(); + if (name.ToLowerInvariant() != "neo4j kernel") + continue; + + var version = record["versions"].As>(); + ServerVersion = RootApiResponse.GetVersion(version?.First()?.ToString()); + + if (ServerVersion > new Version(3, 0)) + CypherCapabilities = CypherCapabilities.Cypher30; + if(ServerVersion >= new Version(4,0)) + CypherCapabilities = CypherCapabilities.Cypher40; + } + + await session.CloseAsync(); + + IsConnected = true; } + + /// + public List JsonConverters { get; } + + /// + public DefaultContractResolver JsonContractResolver { get; set; } + + public Uri GetTransactionEndpoint(string database, bool autoCommit = false) + { + throw new InvalidOperationException(NotValidForBolt); + } + + #endregion + + #region Implementation of IRawGraphClient + + /// + async Task> IRawGraphClient.ExecuteGetCypherResultsAsync(CypherQuery query) + { + if (Driver == null) + throw new InvalidOperationException("Can't execute cypher unless you have connected to the server."); + + var context = ExecutionContext.Begin(this); + List results; + Bookmark lastBookmark = null; + QueryStats stats = null; + + async Task GetQueryStats(IResultCursor resultCursor) + { + if (!query.IncludeQueryStats) return null; + var summary = await resultCursor.ConsumeAsync(); + stats = new QueryStats(summary.Counters); + + return stats; + } + + try + { + if (InTransaction) + { + context.Database = Transaction.Database; + var result = await transactionManager.EnqueueCypherRequest($"The query was: {query.QueryText}", this, query).ConfigureAwait(false); + results = ParseResults(await result.StatementResult.ToListAsync().ConfigureAwait(false), query); + if (query.IncludeQueryStats) + { + var summary = await result.StatementResult.ConsumeAsync(); + stats = new QueryStats(summary.Counters); + } + } + else + { + var session = Driver.AsyncSession(ServerVersion, query.Database, query.IsWrite, query.Bookmarks); + + async Task> Records(IAsyncTransaction asyncTransaction) + { + var cursor = await asyncTransaction.RunAsync(query, this).ConfigureAwait(false); + var output = await cursor.ToListAsync().ConfigureAwait(false); + stats = await GetQueryStats(cursor); + return output; + } + + var result = query.IsWrite + ? await session.WriteTransactionAsync(async s => await Records(s)).ConfigureAwait(false) + : await session.ReadTransactionAsync(async s => await Records(s)).ConfigureAwait(false); + + results = ParseResults(result, query); + + + + lastBookmark = session.LastBookmark; + await session.CloseAsync(); + } + } + catch (AggregateException aggregateException) + { + context.Complete(query, lastBookmark, aggregateException.TryUnwrap(out var unwrappedException) ? unwrappedException : aggregateException); + throw; + } + catch (Exception e) + { + context.Complete(query, lastBookmark, e); + throw; + } + + context.Complete(query, lastBookmark, results.Count, stats); + return results; + } + + private List ParseResults(IEnumerable result, CypherQuery query) + { + var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, false, true); + var results = new List(); + if (typeof(TResult).IsAnonymous()) + { + foreach (var record in result) + results.AddRange(deserializer.Deserialize(record.ParseAnonymous(this), false)); + } + else + { + StatementResultHelper.JsonSettings = new JsonSerializerSettings + { + Converters = JsonConverters, + ContractResolver = JsonContractResolver + }; + + List> converted = new List>(); + foreach (var record in result) + { + var des = record.Deserialize(deserializer, query.ResultMode); + converted.Add(des); + } + + foreach (var enumerable in converted) + { + results.AddRange(enumerable); + } + } + + return results; + } + + + /// + async Task IRawGraphClient.ExecuteCypherAsync(CypherQuery query) + { + var executionContext = ExecutionContext.Begin(this); + + if (Driver == null) + throw new InvalidOperationException("Can't execute cypher unless you have connected to the server."); + + if (InTransaction) + { + executionContext.Database = Transaction.Database; + var response = await transactionManager.EnqueueCypherRequest($"The query was: {query.QueryText}", this, query).ConfigureAwait(false); + QueryStats stats = null; + if (query.IncludeQueryStats) + { + var summary = await response.StatementResult.ConsumeAsync().ConfigureAwait(false); + stats = new QueryStats(summary.Counters); + } + + OnOperationCompleted(new OperationCompletedEventArgs {QueryText = $"BOLT:{query.QueryText}", LastBookmark = transactionManager.LastBookmark, QueryStats = stats}); + } + else + { + var session = Driver.AsyncSession(ServerVersion, query.Database, query.IsWrite, query.Bookmarks); + IResultCursor cursor; + if (query.IsWrite) + cursor = await session.WriteTransactionAsync(async s => await s.RunAsync(query, this)).ConfigureAwait(false); + else + cursor = await session.ReadTransactionAsync(async s => await s.RunAsync(query, this)).ConfigureAwait(false); + + if (query.IncludeQueryStats) + { + var summary = await cursor.ConsumeAsync().ConfigureAwait(false); + executionContext.Complete(query, session.LastBookmark, new QueryStats(summary.Counters)); + } + else executionContext.Complete(query, session.LastBookmark); + } + } + + #endregion + + #region Implementation of ITransactionalGraphClient + + /// + public ITransaction BeginTransaction() + { + return BeginTransaction((IEnumerable) null); + } + + /// + public ITransaction BeginTransaction(string bookmark) + { + return BeginTransaction(new List{bookmark}); + } + + /// + public ITransaction BeginTransaction(IEnumerable bookmarks) + { + return BeginTransaction(TransactionScopeOption.Join, bookmarks, DefaultDatabase); + } + + /// + public ITransaction BeginTransaction(TransactionScopeOption scopeOption) + { + return BeginTransaction(scopeOption, null, DefaultDatabase); + } + + /// + public ITransaction BeginTransaction(TransactionScopeOption scopeOption, string bookmark) + { + return BeginTransaction(scopeOption, new List{bookmark}, DefaultDatabase); + } + + public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmark) + { + return BeginTransaction(scopeOption, bookmark, DefaultDatabase); + } + + + /// + public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmarks, string database) + { + return transactionManager.BeginTransaction(scopeOption, bookmarks, database); + } + + /// + public ITransaction Transaction => transactionManager?.CurrentTransaction; + + /// + public bool InTransaction => transactionManager != null && transactionManager.InTransaction; + + /// + public void EndTransaction() + { + transactionManager.EndTransaction(); + } + + /// + public Uri TransactionEndpoint => throw new InvalidOperationException(NotValidForBolt); + + #endregion + + #region Implementation of IBoltGraphClient + + /// + public event OperationCompletedEventHandler OperationCompleted; + + /// + public string DefaultDatabase { get; set; } = "neo4j"; + + /// + public CypherCapabilities CypherCapabilities { get; private set; } + + /// + public Version ServerVersion { get; private set; } + + /// + [Obsolete(NotValidForBolt)] + public Uri RootEndpoint => throw new InvalidOperationException(NotValidForBolt); + + /// + [Obsolete(NotValidForBolt)] + public Uri BatchEndpoint => throw new InvalidOperationException(NotValidForBolt); + + /// + [Obsolete(NotValidForBolt)] + public Uri CypherEndpoint => throw new InvalidOperationException(NotValidForBolt); + + /// + [Obsolete(NotValidForBolt)] + public ISerializer Serializer => throw new InvalidOperationException(NotValidForBolt); + + /// + public ExecutionConfiguration ExecutionConfiguration { get; } + + /// + public bool IsConnected { get; private set; } + + /// Raises the event. + /// The instance of to send to listeners. + protected void OnOperationCompleted(OperationCompletedEventArgs args) + { + OperationCompleted?.Invoke(this, args); + } + + #endregion + + } } \ No newline at end of file diff --git a/Neo4jClient.Shared/Cypher/All.cs b/Neo4jClient/Cypher/All.cs similarity index 81% rename from Neo4jClient.Shared/Cypher/All.cs rename to Neo4jClient/Cypher/All.cs index 61245e691..04b6dfb68 100644 --- a/Neo4jClient.Shared/Cypher/All.cs +++ b/Neo4jClient/Cypher/All.cs @@ -20,11 +20,5 @@ public static long Count() { throw new InvalidOperationException("This method can't be executed directly: it has no .NET implementation. You need to use it as part of a Cypher return expression, like .Return(() => new { Count = All.Count() });"); } - - /// - /// Equivalent to node(*), for use in START clauses - /// such as Start(new { n = All.Nodes }) - /// - public const string Nodes = "node(*)"; } } diff --git a/Neo4jClient.Shared/Cypher/CypherCapabilities.cs b/Neo4jClient/Cypher/CypherCapabilities.cs similarity index 74% rename from Neo4jClient.Shared/Cypher/CypherCapabilities.cs rename to Neo4jClient/Cypher/CypherCapabilities.cs index c316616b0..877d7c6a1 100644 --- a/Neo4jClient.Shared/Cypher/CypherCapabilities.cs +++ b/Neo4jClient/Cypher/CypherCapabilities.cs @@ -11,6 +11,9 @@ public CypherCapabilities(CypherCapabilities cypherCapabilities) SupportsNullComparisonsWithIsOperator = cypherCapabilities.SupportsNullComparisonsWithIsOperator; SupportsPropertySuffixesForControllingNullComparisons = cypherCapabilities.SupportsPropertySuffixesForControllingNullComparisons; AutoRollsBackOnError = cypherCapabilities.AutoRollsBackOnError; + SupportsStoredProcedures = cypherCapabilities.SupportsStoredProcedures; + SupportsHasFunction = cypherCapabilities.SupportsHasFunction; + SupportsMultipleTenancy = cypherCapabilities.SupportsMultipleTenancy; } public static readonly CypherCapabilities Cypher19 = new CypherCapabilities @@ -29,13 +32,16 @@ public CypherCapabilities(CypherCapabilities cypherCapabilities) public static readonly CypherCapabilities Cypher22 = new CypherCapabilities(Cypher20){SupportsPlanner = true}; public static readonly CypherCapabilities Cypher226 = new CypherCapabilities(Cypher22) { AutoRollsBackOnError = true }; public static readonly CypherCapabilities Cypher23 = new CypherCapabilities(Cypher226) {SupportsStartsWith = true}; - public static readonly CypherCapabilities Cypher30 = new CypherCapabilities(Cypher23) - { - SupportsStoredProcedures = true, - SupportsHasFunction = false - }; + public static readonly CypherCapabilities Cypher30 = new CypherCapabilities(Cypher23) { SupportsStoredProcedures = true, SupportsHasFunction = false }; + public static readonly CypherCapabilities Cypher40 = new CypherCapabilities(Cypher30) { SupportsMultipleTenancy = true, SupportsShow = true }; + public static readonly CypherCapabilities Default = Cypher20; - + + /// + /// Neo4j 4.0 provides support for multiple tenancy, which means commands like CREATE DATABASE etc + /// + public bool SupportsMultipleTenancy { get; set; } + public bool SupportsShow { get; set; } public bool SupportsPlanner { get; set; } public bool SupportsPropertySuffixesForControllingNullComparisons { get; set; } public bool SupportsNullComparisonsWithIsOperator { get; set; } @@ -52,5 +58,7 @@ public CypherCapabilities(CypherCapabilities cypherCapabilities) /// Cypher 3.0 no longer has the HAS() function, as it has been now superseded by EXISTS() /// public bool SupportsHasFunction { get; set; } + + } } diff --git a/Neo4jClient.Shared/Cypher/CypherFluentQuery.cs b/Neo4jClient/Cypher/CypherFluentQuery.cs similarity index 70% rename from Neo4jClient.Shared/Cypher/CypherFluentQuery.cs rename to Neo4jClient/Cypher/CypherFluentQuery.cs index e8daf4cc7..6fe7aa416 100644 --- a/Neo4jClient.Shared/Cypher/CypherFluentQuery.cs +++ b/Neo4jClient/Cypher/CypherFluentQuery.cs @@ -20,8 +20,41 @@ public partial class CypherFluentQuery : internal readonly IRawGraphClient Client; protected readonly QueryWriter QueryWriter; protected readonly bool CamelCaseProperties; + protected bool IncludeQueryStats { get; private set; } internal bool IsWrite { get; private set; } + public string Database + { + get => QueryWriter.DatabaseName; + set => QueryWriter.DatabaseName = value; + } + + /// + public ICypherFluentQuery Show(string command) + { + if(string.IsNullOrWhiteSpace(command)) + throw new ArgumentException("You have to supply a command to SHOW", nameof(command)); + + if(!Client.CypherCapabilities.SupportsShow) + throw new InvalidOperationException("SHOW commands are not supported in Neo4j versions older than 4.0"); + + return Mutate(w => w.AppendClause($"SHOW {command}")); + } + + public ICypherFluentQuery WithDatabase(string databaseName) + { + if(!Client.CypherCapabilities.SupportsMultipleTenancy) + throw new InvalidOperationException("Multi-tenancy is only available on Neo4j Servers > 4.0"); + + if(Client.InTransaction) + throw new InvalidOperationException("This query is in a Transaction, you can't set the database for individual queries within a Transaction."); + + databaseName = databaseName?.ToLowerInvariant(); + Database = databaseName; + QueryWriter.DatabaseName = databaseName; + return this; + } + public ICypherFluentQuery Read { get @@ -31,6 +64,15 @@ public ICypherFluentQuery Read } } + public ICypherFluentQuery WithQueryStats + { + get + { + IncludeQueryStats = true; + return this; + } + } + public ICypherFluentQuery Write { get @@ -40,47 +82,48 @@ public ICypherFluentQuery Write } } - public CypherFluentQuery(IGraphClient client, bool isWrite = true) - : this(client, new QueryWriter(), isWrite) + public CypherFluentQuery(IGraphClient client, bool isWrite = true, bool includeQueryStats = false) + : this(client, new QueryWriter(client.DefaultDatabase), isWrite, includeQueryStats) { IsWrite = isWrite; } - internal CypherFluentQuery(IGraphClient client, QueryWriter queryWriter, bool isWrite = true) + internal CypherFluentQuery(IGraphClient client, QueryWriter queryWriter, bool isWrite = true, bool includeQueryStats = false) { Client = client as IRawGraphClient ?? throw new ArgumentException("The supplied graph client also needs to implement IRawGraphClient", nameof(client)); QueryWriter = queryWriter; CamelCaseProperties = Client.JsonContractResolver is CamelCasePropertyNamesContractResolver; - Advanced = new CypherFluentQueryAdvanced(Client, QueryWriter, isWrite); + Advanced = new CypherFluentQueryAdvanced(Client, QueryWriter, isWrite, includeQueryStats); IsWrite = isWrite; + IncludeQueryStats = includeQueryStats; } - IOrderedCypherFluentQuery MutateOrdered(Action callback) + private IOrderedCypherFluentQuery MutateOrdered(Action callback) { var newWriter = QueryWriter.Clone(); callback(newWriter); - return new CypherFluentQuery(Client, newWriter, IsWrite); + return new CypherFluentQuery(Client, newWriter, IsWrite, IncludeQueryStats); } protected IOrderedCypherFluentQuery MutateOrdered(Action callback) { var newWriter = QueryWriter.Clone(); callback(newWriter); - return new CypherFluentQuery(Client, newWriter, IsWrite); + return new CypherFluentQuery(Client, newWriter, IsWrite, IncludeQueryStats); } - ICypherFluentQuery Mutate(Action callback) + private ICypherFluentQuery Mutate(Action callback) { var newWriter = QueryWriter.Clone(); callback(newWriter); - return new CypherFluentQuery(Client, newWriter, IsWrite); + return new CypherFluentQuery(Client, newWriter, IsWrite, IncludeQueryStats); } protected ICypherFluentQuery Mutate(Action callback) { var newWriter = QueryWriter.Clone(); callback(newWriter); - return new CypherFluentQuery(Client, newWriter, IsWrite); + return new CypherFluentQuery(Client, newWriter, IsWrite, IncludeQueryStats); } public ICypherFluentQuery WithParam(string key, object value) @@ -113,85 +156,16 @@ public ICypherFluentQuery WithParams(object parameters) return WithParams(keyValuePairs); } - [Obsolete("Use Start(new { identity = startText }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - public ICypherFluentQuery Start(string identity, string startText) + /// + public ICypherFluentQuery Use(string database) { - return Mutate(w => - w.AppendClause($"START {identity}={startText}")); - } - - public ICypherFluentQuery Start(object startBits) - { - return Mutate(w => - { - var startBitsText = StartBitFormatter.FormatAsCypherText(startBits, w.CreateParameter); - var startText = "START " + startBitsText; - w.AppendClause(startText); - }); - } - - public ICypherFluentQuery Start(IDictionary startBits) - { - return Mutate(w => - { - var startBitsText = StartBitFormatter.FormatAsCypherText(startBits, w.CreateParameter); - var startText = "START " + startBitsText; - w.AppendClause(startText); - }); - } - - [Obsolete("Use Start(new { foo = nodeRef1, bar = All.Nodes }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - public ICypherFluentQuery Start(params ICypherStartBit[] startBits) - { - return Mutate(w => - { - var startBitsText = startBits - .Select(b => b.ToCypherText(w.CreateParameter)) - .ToArray(); - var startText = "START " + string.Join(", ", startBitsText); - - w.AppendClause(startText); - }); - } - - public ICypherFluentQuery Start(string identity, params NodeReference[] nodeReferences) - { - return Start(new Dictionary - { - { identity, nodeReferences } - }); - } - - public ICypherFluentQuery Start(string identity, params RelationshipReference[] relationshipReferences) - { - return Start(new Dictionary - { - { identity, relationshipReferences } - }); - } - - [Obsolete("Use Start(new { foo = Node.ByIndexLookup(…) }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - public ICypherFluentQuery StartWithNodeIndexLookup(string identity, string indexName, string key, object value) - { - return Start(new Dictionary - { - {identity, Node.ByIndexLookup(indexName, key, value)} - }); - } - - [Obsolete("Use Start(new { foo = Node.ByIndexQuery(…) }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - public ICypherFluentQuery StartWithNodeIndexLookup(string identity, string indexName, string parameter) - { - return Start(new Dictionary - { - {identity, Node.ByIndexQuery(indexName, parameter)} - }); + return Mutate(w => w.AppendClause($"USE {database}")); } public ICypherFluentQuery Match(params string[] matchText) { return Mutate(w => - w.AppendClause("MATCH " + string.Join(", ", matchText))); + w.AppendClause($"MATCH {string.Join(", ", matchText)}")); } public ICypherFluentQuery UsingIndex(string index) @@ -202,18 +176,18 @@ public ICypherFluentQuery UsingIndex(string index) } return Mutate(w => - w.AppendClause("USING INDEX " + index)); + w.AppendClause($"USING INDEX {index}")); } public ICypherFluentQuery OptionalMatch(string pattern) { return Mutate(w => - w.AppendClause("OPTIONAL MATCH " + pattern)); + w.AppendClause($"OPTIONAL MATCH {pattern}")); } public ICypherFluentQuery Merge(string mergeText) { - return Mutate(w => w.AppendClause("MERGE " + mergeText)); + return Mutate(w => w.AppendClause($"MERGE {mergeText}")); } public ICypherFluentQuery OnCreate() @@ -226,6 +200,7 @@ public ICypherFluentQuery OnMatch() return Mutate(w => w.AppendClause("ON MATCH")); } + public ICypherFluentQuery Call(string storedProcedureText) { if (!Client.CypherCapabilities.SupportsStoredProcedures) @@ -252,39 +227,12 @@ public ICypherFluentQuery Yield(string yieldText) public ICypherFluentQuery CreateUnique(string createUniqueText) { - return Mutate(w => w.AppendClause("CREATE UNIQUE " + createUniqueText)); + return Mutate(w => w.AppendClause($"CREATE UNIQUE {createUniqueText}")); } public ICypherFluentQuery Create(string createText) { - return Mutate(w => w.AppendClause("CREATE " + createText)); - } - - [Obsolete("Use Create(string) with explicitly named params instead. For example, instead of Create(\"(c:Customer {0})\", customer), use Create(\"(c:Customer {customer})\").WithParams(new { customer }).")] - public ICypherFluentQuery Create(string createText, params object[] objects) - { - objects - .ToList() - .ForEach(o => - { - if (o == null) - throw new ArgumentException("Array includes a null entry", nameof(objects)); - - var objectType = o.GetType(); - if (objectType.GetTypeInfo().IsGenericType && - objectType.GetGenericTypeDefinition() == typeof(Node<>)) - { - throw new ArgumentException(string.Format( - "You're trying to pass in a Node<{0}> instance. Just pass the {0} instance instead.", - objectType.GetGenericArguments()[0].Name), - nameof(objects)); - } - - var validationContext = new ValidationContext(o, null, null); - Validator.ValidateObject(o, validationContext); - }); - - return Mutate(w => w.AppendClause("CREATE " + createText, objects)); + return Mutate(w => w.AppendClause($"CREATE {createText}")); } public ICypherFluentQuery Create(string identity, TNode node) @@ -444,12 +392,7 @@ public IOrderedCypherFluentQuery ThenByDescending(params string[] properties) w.AppendToClause($", {string.Join(" DESC, ", properties)} DESC")); } - public CypherQuery Query => QueryWriter.ToCypherQuery(Client.JsonContractResolver ?? GraphClient.DefaultJsonContractResolver, IsWrite); - - public void ExecuteWithoutResults() - { - Client.ExecuteCypher(Query); - } + public CypherQuery Query => QueryWriter.ToCypherQuery(Client.JsonContractResolver ?? GraphClient.DefaultJsonContractResolver, IsWrite, IncludeQueryStats); public Task ExecuteWithoutResultsAsync() { @@ -457,11 +400,9 @@ public Task ExecuteWithoutResultsAsync() } public ICypherFluentQueryAdvanced Advanced { get; } + - IGraphClient IAttachedReference.Client - { - get { return Client; } - } + IGraphClient IAttachedReference.Client => Client; public ICypherFluentQuery ParserVersion(string version) { @@ -515,6 +456,8 @@ public ICypherFluentQuery CustomHeaders(NameValueCollection headers) return this; } + + public static string ApplyCamelCase(bool isCamelCase, string propertyName) { return isCamelCase diff --git a/Neo4jClient.Shared/Cypher/CypherFluentQueryAdvanced.cs b/Neo4jClient/Cypher/CypherFluentQueryAdvanced.cs similarity index 81% rename from Neo4jClient.Shared/Cypher/CypherFluentQueryAdvanced.cs rename to Neo4jClient/Cypher/CypherFluentQueryAdvanced.cs index d34954ff1..05fd65669 100644 --- a/Neo4jClient.Shared/Cypher/CypherFluentQueryAdvanced.cs +++ b/Neo4jClient/Cypher/CypherFluentQueryAdvanced.cs @@ -7,12 +7,14 @@ public class CypherFluentQueryAdvanced : ICypherFluentQueryAdvanced private readonly IGraphClient client; private readonly QueryWriter queryWriter; private readonly bool isWrite; + private readonly bool includeQueryStats; - public CypherFluentQueryAdvanced(IGraphClient client, QueryWriter queryWriter, bool isWrite = true) + public CypherFluentQueryAdvanced(IGraphClient client, QueryWriter queryWriter, bool isWrite, bool includeQueryStats) { this.client = client; this.queryWriter = queryWriter; this.isWrite = isWrite; + this.includeQueryStats = includeQueryStats; } public ICypherFluentQuery Return(ReturnExpression returnExpression) @@ -21,7 +23,7 @@ public ICypherFluentQuery Return(ReturnExpression returnExpres { w.ResultMode = returnExpression.ResultMode; w.ResultFormat = returnExpression.ResultFormat; - w.AppendClause("RETURN " + returnExpression.Text); + w.AppendClause($"RETURN {returnExpression.Text}"); }); } @@ -31,7 +33,7 @@ public ICypherFluentQuery ReturnDistinct(ReturnExpression retu { w.ResultMode = returnExpression.ResultMode; w.ResultFormat = returnExpression.ResultFormat; - w.AppendClause("RETURN distinct " + returnExpression.Text); + w.AppendClause($"RETURN distinct {returnExpression.Text}"); }); } @@ -40,7 +42,7 @@ public ICypherFluentQuery SetClient(IGraphClient graphClient) if (!(client is IRawGraphClient)) throw new ArgumentException("The supplied graph client also needs to implement IRawGraphClient", nameof(graphClient)); - return new CypherFluentQuery(graphClient, queryWriter, isWrite); + return new CypherFluentQuery(graphClient, queryWriter, isWrite, includeQueryStats); } public ICypherFluentQuery SetClient(IGraphClient graphClient) @@ -48,14 +50,14 @@ public ICypherFluentQuery SetClient(IGraphClient graphClient) if (!(client is IRawGraphClient)) throw new ArgumentException("The supplied graph client also needs to implement IRawGraphClient", nameof(graphClient)); - return new CypherFluentQuery(graphClient, queryWriter, isWrite); + return new CypherFluentQuery(graphClient, queryWriter, isWrite, includeQueryStats); } protected ICypherFluentQuery Mutate(Action callback) { var newWriter = queryWriter.Clone(); callback(newWriter); - return new CypherFluentQuery(client, newWriter, isWrite); + return new CypherFluentQuery(client, newWriter, isWrite, includeQueryStats); } } } diff --git a/Neo4jClient/Cypher/CypherFluentQuery`DatabaseAdministration.cs b/Neo4jClient/Cypher/CypherFluentQuery`DatabaseAdministration.cs new file mode 100644 index 000000000..c7df23f0e --- /dev/null +++ b/Neo4jClient/Cypher/CypherFluentQuery`DatabaseAdministration.cs @@ -0,0 +1,65 @@ +using System; + +namespace Neo4jClient.Cypher +{ + public partial class CypherFluentQuery + { + private const string MultipleTenancyExceptionMessage = "DATABASE commands are not supported in Neo4j versions older than 4.0"; + + private string DatabaseAdministrationChecks(string databaseName = null, bool ignoreDatabaseName = false) + { + if (!ignoreDatabaseName && string.IsNullOrWhiteSpace(databaseName)) + throw new ArgumentException("You have to supply a name for the database.", nameof(databaseName)); + + if (!Client.CypherCapabilities.SupportsMultipleTenancy) + throw new InvalidOperationException(MultipleTenancyExceptionMessage); + + return databaseName?.ToLowerInvariant(); + } + + /// + public ICypherFluentQuery CreateDatabase(string databaseName, bool ifNotExists = false) + { + databaseName = DatabaseAdministrationChecks(databaseName); + return Mutate(w => w.AppendClause($"CREATE DATABASE {databaseName}{(ifNotExists ? " IF NOT EXISTS" : "")}")); + } + + /// + public ICypherFluentQuery CreateOrReplaceDatabase(string databaseName) + { + databaseName = DatabaseAdministrationChecks(databaseName); + return Mutate(w => w.AppendClause($"CREATE OR REPLACE DATABASE {databaseName}")); + } + + /// + public ICypherFluentQuery DropDatabase(string databaseName, bool dumpData = false) + { + databaseName = DatabaseAdministrationChecks(databaseName); + return Mutate(w => w.AppendClause($"DROP DATABASE {databaseName}{(dumpData ? " DUMP DATA" : "")}")); + } + + /// + public ICypherFluentQuery DropDatabaseIfExists(string databaseName, bool dumpData = false) + { + databaseName = DatabaseAdministrationChecks(databaseName); + return Mutate(w => w.AppendClause($"DROP DATABASE {databaseName} IF EXISTS{(dumpData ? " DUMP DATA" : "")}")); + } + + /// + public ICypherFluentQuery StartDatabase(string databaseName) + { + databaseName = DatabaseAdministrationChecks(databaseName); + return Mutate(w => w.AppendClause($"START DATABASE {databaseName}")); + } + + /// + public ICypherFluentQuery StopDatabase(string databaseName) + { + databaseName = DatabaseAdministrationChecks(databaseName); + return Mutate(w => w.AppendClause($"STOP DATABASE {databaseName}")); + } + + + + } +} \ No newline at end of file diff --git a/Neo4jClient.Shared/Cypher/CypherFluentQuery`Return.cs b/Neo4jClient/Cypher/CypherFluentQuery`Return.cs similarity index 90% rename from Neo4jClient.Shared/Cypher/CypherFluentQuery`Return.cs rename to Neo4jClient/Cypher/CypherFluentQuery`Return.cs index 4508f518d..7d92d3009 100644 --- a/Neo4jClient.Shared/Cypher/CypherFluentQuery`Return.cs +++ b/Neo4jClient/Cypher/CypherFluentQuery`Return.cs @@ -16,10 +16,10 @@ public ICypherFluentQuery Return(string identity) var identityIsCollection = identity.StartsWith("[") && identity.EndsWith("]"); if (identity.Contains(",") && !identityIsCollection) - throw new ArgumentException(IdentityLooksLikeAMultiColumnStatementExceptionMessage, "identity"); + throw new ArgumentException(IdentityLooksLikeAMultiColumnStatementExceptionMessage, nameof(identity)); - if (identityIsCollection && !typeof (TResult).GetInterfaces().Any(i => i.Name == "IEnumerable")) - throw new ArgumentException(IdentityLooksLikeACollectionButTheResultIsNotEnumerableMessage, "identity"); + if (identityIsCollection && typeof(TResult).GetInterfaces().All(i => i.Name != "IEnumerable")) + throw new ArgumentException(IdentityLooksLikeACollectionButTheResultIsNotEnumerableMessage, nameof(identity)); var resultType = typeof (TResult); @@ -34,19 +34,13 @@ public ICypherFluentQuery Return(string identity) { w.ResultFormat = CypherResultFormat.Rest; } - w.AppendClause("RETURN " + identity); + 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)] - ICypherFluentQuery ICypherFluentQuery.Return(string statement, CypherResultMode resultMode) - { - throw new NotSupportedException("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."); - } - public ICypherFluentQuery ReturnDistinct(string identity) { - return Mutate(w => w.AppendClause("RETURN distinct " + identity)); + return Mutate(w => w.AppendClause($"RETURN distinct {identity}")); } ICypherFluentQuery Return(LambdaExpression expression) diff --git a/Neo4jClient.Shared/Cypher/CypherFluentQuery`TResult.cs b/Neo4jClient/Cypher/CypherFluentQuery`TResult.cs similarity index 92% rename from Neo4jClient.Shared/Cypher/CypherFluentQuery`TResult.cs rename to Neo4jClient/Cypher/CypherFluentQuery`TResult.cs index 4c91e1b0e..a9c06d528 100644 --- a/Neo4jClient.Shared/Cypher/CypherFluentQuery`TResult.cs +++ b/Neo4jClient/Cypher/CypherFluentQuery`TResult.cs @@ -9,8 +9,8 @@ public class CypherFluentQuery : CypherFluentQuery, IOrderedCypherFluentQuery { - public CypherFluentQuery(IGraphClient client, QueryWriter writer, bool isWrite = true) - : base(client, writer, isWrite) + public CypherFluentQuery(IGraphClient client, QueryWriter writer, bool isWrite = true, bool includeQueryStats = false) + : base(client, writer, isWrite, includeQueryStats) {} public new ICypherFluentQuery Unwind(string collectionName, string columnName) @@ -56,8 +56,6 @@ public CypherFluentQuery(IGraphClient client, QueryWriter writer, bool isWrite = w.AppendToClause($", {string.Join(" DESC, ", properties)} DESC")); } - public IEnumerable Results => Client.ExecuteGetCypherResults(Query); - public Task> ResultsAsync => Client.ExecuteGetCypherResultsAsync(Query); } } diff --git a/Neo4jClient.Shared/Cypher/CypherFluentQuery`Where.cs b/Neo4jClient/Cypher/CypherFluentQuery`Where.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/CypherFluentQuery`Where.cs rename to Neo4jClient/Cypher/CypherFluentQuery`Where.cs diff --git a/Neo4jClient.Shared/Cypher/CypherFluentQuery`With.cs b/Neo4jClient/Cypher/CypherFluentQuery`With.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/CypherFluentQuery`With.cs rename to Neo4jClient/Cypher/CypherFluentQuery`With.cs diff --git a/Neo4jClient.Shared/Cypher/CypherFluentQuery`WithBookmark.cs b/Neo4jClient/Cypher/CypherFluentQuery`WithBookmark.cs similarity index 71% rename from Neo4jClient.Shared/Cypher/CypherFluentQuery`WithBookmark.cs rename to Neo4jClient/Cypher/CypherFluentQuery`WithBookmark.cs index 031f66725..ea003f368 100644 --- a/Neo4jClient.Shared/Cypher/CypherFluentQuery`WithBookmark.cs +++ b/Neo4jClient/Cypher/CypherFluentQuery`WithBookmark.cs @@ -1,4 +1,5 @@ using System.Linq; +using Neo4j.Driver; namespace Neo4jClient.Cypher { @@ -10,14 +11,17 @@ public ICypherFluentQuery WithBookmark(string bookmark) if (string.IsNullOrWhiteSpace(bookmark)) return this; - QueryWriter.Bookmarks.Add(bookmark); + QueryWriter.Bookmarks.Add(Bookmark.From(bookmark)); return this; } /// public ICypherFluentQuery WithBookmarks(params string[] bookmarks) { - QueryWriter.Bookmarks.AddRange(bookmarks.Where(b => !string.IsNullOrWhiteSpace(b))); + if (bookmarks == null) + return this; + + QueryWriter.Bookmarks.AddRange(bookmarks.Where(b => !string.IsNullOrWhiteSpace(b)).Select(b => Bookmark.From(b))); return this; } } diff --git a/Neo4jClient.Shared/Cypher/CypherPlanner.cs b/Neo4jClient/Cypher/CypherPlanner.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/CypherPlanner.cs rename to Neo4jClient/Cypher/CypherPlanner.cs diff --git a/Neo4jClient.Shared/Cypher/CypherQuery.cs b/Neo4jClient/Cypher/CypherQuery.cs similarity index 64% rename from Neo4jClient.Shared/Cypher/CypherQuery.cs rename to Neo4jClient/Cypher/CypherQuery.cs index 6b4c9286c..6f8ef6dbd 100644 --- a/Neo4jClient.Shared/Cypher/CypherQuery.cs +++ b/Neo4jClient/Cypher/CypherQuery.cs @@ -2,6 +2,7 @@ using System.Collections.Specialized; using System.Diagnostics; using System.Linq; +using Neo4j.Driver; using Neo4jClient.Serialization; using Newtonsoft.Json.Serialization; @@ -17,21 +18,16 @@ public enum CypherResultFormat [DebuggerDisplay("{DebugQueryText}")] public class CypherQuery { - - readonly string queryText; - readonly IDictionary queryParameters; - readonly CypherResultMode resultMode; - readonly CypherResultFormat resultFormat; - readonly IContractResolver jsonContractResolver; - readonly int? maxExecutionTime; - private readonly NameValueCollection customHeaders; - + private readonly string queryText; + private readonly IDictionary queryParameters; + public CypherQuery( string queryText, IDictionary queryParameters, - CypherResultMode resultMode, + CypherResultMode resultMode, + string database, IContractResolver contractResolver = null) : - this(queryText, queryParameters, resultMode, CypherResultFormat.DependsOnEnvironment, contractResolver) + this(queryText, queryParameters, resultMode, CypherResultFormat.DependsOnEnvironment, database, contractResolver) { } @@ -40,53 +36,60 @@ public CypherQuery( IDictionary queryParameters, CypherResultMode resultMode, CypherResultFormat resultFormat, + string database, IContractResolver contractResolver = null, int? maxExecutionTime = null, NameValueCollection customHeaders = null, bool isWrite = true, - IEnumerable bookmarks = null, - string identifier = null + IEnumerable bookmarks = null, + string identifier = null, + bool includeQueryStats = false ) { this.queryText = queryText; this.queryParameters = queryParameters; - this.resultMode = resultMode; - this.resultFormat = resultFormat; - jsonContractResolver = contractResolver ?? GraphClient.DefaultJsonContractResolver; - this.maxExecutionTime = maxExecutionTime; - this.customHeaders = customHeaders; + this.ResultMode = resultMode; + this.ResultFormat = resultFormat; + JsonContractResolver = contractResolver ?? GraphClient.DefaultJsonContractResolver; + this.MaxExecutionTime = maxExecutionTime; + this.CustomHeaders = customHeaders; IsWrite = isWrite; + IncludeQueryStats = includeQueryStats; Bookmarks = bookmarks; Identifier = identifier; + Database = database; } public bool IsWrite { get; } + public bool IncludeQueryStats { get; } public string Identifier { get; set; } - public IEnumerable Bookmarks { get; set; } + public IEnumerable Bookmarks { get; set; } public IDictionary QueryParameters => queryParameters; public string QueryText => queryText; - public CypherResultFormat ResultFormat => resultFormat; + public CypherResultFormat ResultFormat { get; } + + public CypherResultMode ResultMode { get; } - public CypherResultMode ResultMode => resultMode; + public IContractResolver JsonContractResolver { get; } - public IContractResolver JsonContractResolver => jsonContractResolver; + public string Database { get; } - public int? MaxExecutionTime => maxExecutionTime; + public int? MaxExecutionTime { get; } /// /// Custom headers to add to REST calls to Neo4j server. /// Example usage: This can be used to provide extra information to a Neo4j Loadbalancer. /// - public NameValueCollection CustomHeaders => customHeaders; + public NameValueCollection CustomHeaders { get; } - CustomJsonSerializer BuildSerializer() + private CustomJsonSerializer BuildSerializer() { - return new CustomJsonSerializer { JsonConverters = GraphClient.DefaultJsonConverters, JsonContractResolver = jsonContractResolver }; + return new CustomJsonSerializer { JsonConverters = GraphClient.DefaultJsonConverters, JsonContractResolver = JsonContractResolver }; } public string DebugQueryText @@ -107,7 +110,7 @@ public string DebugQueryText { var value = queryParameters[paramName]; value = serializer.Serialize(value); - return current.Replace("$" + paramName, value.ToString()).Replace("{" + paramName + "}", value.ToString()); + return current.Replace($"${paramName}", value.ToString()); }); return text; diff --git a/Neo4jClient.Shared/Cypher/CypherResultMode.cs b/Neo4jClient/Cypher/CypherResultMode.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/CypherResultMode.cs rename to Neo4jClient/Cypher/CypherResultMode.cs diff --git a/Neo4jClient.Shared/Cypher/CypherReturnExpressionBuilder.cs b/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs similarity index 98% rename from Neo4jClient.Shared/Cypher/CypherReturnExpressionBuilder.cs rename to Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs index 6db206537..1d4e09b4f 100644 --- a/Neo4jClient.Shared/Cypher/CypherReturnExpressionBuilder.cs +++ b/Neo4jClient/Cypher/CypherReturnExpressionBuilder.cs @@ -349,9 +349,22 @@ static ExpressionBuild BuildStatement( if (isNullable) optionalIndicator = "?"; } - return new ExpressionBuild(string.Format("{0}.{1}{2} AS {3}", targetObject.Name, CypherFluentQuery.ApplyCamelCase(camelCaseProperties, memberInfo.Name), optionalIndicator, targetMember.Name)); + return new ExpressionBuild($"{targetObject.Name}.{ParseMemberName(memberInfo, camelCaseProperties)}{optionalIndicator} AS {targetMember.Name}"); } + private static string ParseMemberName(MemberInfo memberInfo, bool camelCaseProperties) + { + var att = memberInfo.GetCustomAttribute(); + + if (att != null && !string.IsNullOrWhiteSpace(att.PropertyName)) + { + return att.PropertyName; + } + + return CypherFluentQuery.ApplyCamelCase(camelCaseProperties, memberInfo.Name); + } + + static ExpressionBuild BuildStatement( MethodCallExpression expression, MemberInfo targetMember, diff --git a/Neo4jClient.Shared/Cypher/CypherWhereExpressionBuilder.cs b/Neo4jClient/Cypher/CypherWhereExpressionBuilder.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/CypherWhereExpressionBuilder.cs rename to Neo4jClient/Cypher/CypherWhereExpressionBuilder.cs diff --git a/Neo4jClient.Shared/Cypher/CypherWhereExpressionVisitor.cs b/Neo4jClient/Cypher/CypherWhereExpressionVisitor.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/CypherWhereExpressionVisitor.cs rename to Neo4jClient/Cypher/CypherWhereExpressionVisitor.cs diff --git a/Neo4jClient.Shared/Cypher/CypherWithExpressionBuilder.cs b/Neo4jClient/Cypher/CypherWithExpressionBuilder.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/CypherWithExpressionBuilder.cs rename to Neo4jClient/Cypher/CypherWithExpressionBuilder.cs diff --git a/Neo4jClient.Shared/Cypher/ICypherFluentQuery.cs b/Neo4jClient/Cypher/ICypherFluentQuery.cs similarity index 86% rename from Neo4jClient.Shared/Cypher/ICypherFluentQuery.cs rename to Neo4jClient/Cypher/ICypherFluentQuery.cs index 0ee54b2ae..f46e4e172 100644 --- a/Neo4jClient.Shared/Cypher/ICypherFluentQuery.cs +++ b/Neo4jClient/Cypher/ICypherFluentQuery.cs @@ -4,17 +4,20 @@ using System.Collections.Specialized; using System.Linq.Expressions; using System.Threading.Tasks; +using Neo4j.Driver; namespace Neo4jClient.Cypher { public partial interface ICypherFluentQuery { CypherQuery Query { get; } - void ExecuteWithoutResults(); Task ExecuteWithoutResultsAsync(); ICypherFluentQueryAdvanced Advanced { get; } - + /// + /// When the result is returned, the event will contain the if requested. + /// + ICypherFluentQuery WithQueryStats { get; } ICypherFluentQuery WithParam(string key, object value); ICypherFluentQuery WithParams(IDictionary parameters); ICypherFluentQuery WithParams(object parameters); @@ -24,6 +27,13 @@ public partial interface ICypherFluentQuery ICypherFluentQuery ParserVersion(int major, int minor); ICypherFluentQuery MaxExecutionTime(int milliseconds); + + // /// + // /// Calls 'SHOW' on the database + // /// + // /// The command to execute after SHOW - for example 'DATABASES' + // /// An instance to continue the query with. + // ICypherFluentQuery Show(string command); /// /// Custom headers to add to REST calls to Neo4j server. @@ -39,25 +49,14 @@ public partial interface ICypherFluentQuery ICypherFluentQuery Planner(string planner); ICypherFluentQuery Planner(CypherPlanner planner); - ICypherFluentQuery Start(object startBits); - ICypherFluentQuery Start(IDictionary startBits); - [Obsolete("Use Start(new { identity = startText }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - ICypherFluentQuery Start(string identity, string startText); - [Obsolete("Use Start(new { foo = nodeRef1, bar = All.Nodes }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - ICypherFluentQuery Start(params ICypherStartBit[] startBits); - ICypherFluentQuery Start(string identity, params NodeReference[] nodeReferences); - ICypherFluentQuery Start(string identity, params RelationshipReference[] relationshipReferences); - [Obsolete("Use Start(new { foo = Node.ByIndexLookup() }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - ICypherFluentQuery StartWithNodeIndexLookup(string identity, string indexName, string key, object value); - [Obsolete("Use Start(new { foo = Node.ByIndexQuery() }) instead. See https://bitbucket.org/Readify/neo4jclient/issue/74/support-nicer-cypher-start-notation for more details about this change.")] - ICypherFluentQuery StartWithNodeIndexLookup(string identity, string indexName, string parameterText); + ICypherFluentQuery Match(params string[] matchText); ICypherFluentQuery UsingIndex(string index); ICypherFluentQuery OptionalMatch(string pattern); ICypherFluentQuery Merge(string mergeText); ICypherFluentQuery OnCreate(); ICypherFluentQuery OnMatch(); - + /// /// [Neo4j 3.0+] Calls a Stored Procedure on the Database. /// @@ -79,11 +78,21 @@ public partial interface ICypherFluentQuery ICypherFluentQuery Yield(string yieldText); ICypherFluentQuery CreateUnique(string createUniqueText); + + /// + /// Creates a UNIQUE CONSTRAINT in the database. + /// + /// Executes CREATE CONSTRAINT ON (identity) ASSERT property IS UNIQUE + /// The identity to use to create the constraint, you must include the Label, example: "u:User" + /// The property to index, this should use the defined, example: "u.id" + /// An instance to continue the query with. ICypherFluentQuery CreateUniqueConstraint(string identity, string property); + + + //ICypherFluentQuery CreateUniqueConstraint(string nameOfConstraint, string label, string identifier, string property); + ICypherFluentQuery DropUniqueConstraint(string identity, string property); ICypherFluentQuery Create(string createText); - [Obsolete("Use Create(string) with explicitly named params instead. For example, instead of Create(\"(c:Customer {0})\", customer), use Create(\"(c:Customer {customer})\").WithParams(new { customer }).")] - ICypherFluentQuery Create(string createText, params object[] objects); ICypherFluentQuery Delete(string identities); ICypherFluentQuery DetachDelete(string identities); @@ -110,8 +119,6 @@ public partial interface ICypherFluentQuery IOrderedCypherFluentQuery OrderBy(params string[] properties); IOrderedCypherFluentQuery OrderByDescending(params string[] properties); ICypherFluentQuery Return(string identity); - [Obsolete("This was an internal that never should have been exposed. If you want to create a projection, you should be using the lambda overload instead. See the 'Using Functions in Return Clauses' and 'Using Custom Text in Return Clauses' sections of https://bitbucket.org/Readify/neo4jclient/wiki/cypher for details of how to do this.", true)] - ICypherFluentQuery Return(string identity, CypherResultMode resultMode); ICypherFluentQuery Return(Expression> expression); ICypherFluentQuery Return(Expression> expression); ICypherFluentQuery Return(Expression> expression); @@ -163,6 +170,13 @@ public partial interface ICypherFluentQuery /// A instance to continue querying with. ICypherFluentQuery Write { get; } + /// + /// Sets the database instance to use to run the queries against. + /// + /// The name of the database instance. + /// An instance to continue querying with. + ICypherFluentQuery WithDatabase(string databaseName); + /// /// Set an identifier for your query. You don't need to set this, it's intended to allow you to link an event to a specific query. /// @@ -185,5 +199,14 @@ public partial interface ICypherFluentQuery /// The bookmarks to use. /// A instance to continue querying with. ICypherFluentQuery WithBookmarks(params string[] bookmarks); + + /// + /// The USE clause determines which graph a query, or query part, is executed against. + /// It is supported for queries and schema commands. + /// + /// Neo4j 4.x+ + /// The name of the graph to run the next commands against. + /// A instance to continue querying with. + ICypherFluentQuery Use(string graphName); } } diff --git a/Neo4jClient.Shared/Cypher/ICypherFluentQueryAdvanced.cs b/Neo4jClient/Cypher/ICypherFluentQueryAdvanced.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/ICypherFluentQueryAdvanced.cs rename to Neo4jClient/Cypher/ICypherFluentQueryAdvanced.cs diff --git a/Neo4jClient/Cypher/ICypherFluentQuery`DatabaseAdministration.cs b/Neo4jClient/Cypher/ICypherFluentQuery`DatabaseAdministration.cs new file mode 100644 index 000000000..0cae0bcd2 --- /dev/null +++ b/Neo4jClient/Cypher/ICypherFluentQuery`DatabaseAdministration.cs @@ -0,0 +1,82 @@ +using System; +using Neo4j.Driver; +using Neo4jClient.ApiModels; + +namespace Neo4jClient.Cypher +{ + public partial interface ICypherFluentQuery + { + /// + /// Creates a new Database in Neo4j + /// + /// 4.x Server only. + /// The name of the database instance to create. + /// + /// If true no exception will be thrown if the database already exists, false + /// (default) will throw an exception if the database already exists.. + /// + /// An instance to continue the query with. + /// Thrown if this is called against a server version without multiple tenancy. + /// Thrown if the given is null or whitespace. + ICypherFluentQuery CreateDatabase(string databaseName, bool ifNotExists = false); + + /// + /// This will create a new Database in Neo4j, regardless of one already existing. + /// + /// It will DELETE the existing database if it exists. + /// The name of the database instance to create. + /// An instance to continue the query with. + /// Thrown if this is called against a server version without multiple tenancy. + /// Thrown if the given is null or whitespace. + ICypherFluentQuery CreateOrReplaceDatabase(string databaseName); + + /// + /// Deletes the given database from Neo4j. + /// + /// The name of the database to delete. + /// + /// If true the data will be dumped to the dbms.directories.dumps.root setting of your + /// server. false (default) will mean the data is destroyed. + /// + /// An instance to continue the query with. + /// + /// Thrown if the database doesn't exist. Use to + /// avoid the exception. + /// + /// Thrown if this is called against a server version without multiple tenancy. + /// Thrown if the given is null or whitespace. + ICypherFluentQuery DropDatabase(string databaseName, bool dumpData = false); + + /// + /// Deletes the given database from Neo4j if it exists. + /// + /// The name of the database to delete. + /// + /// If true the data will be dumped to the dbms.directories.dumps.root setting of your + /// server. false (default) will mean the data is destroyed. + /// + /// An instance to continue the query with. + /// Thrown if this is called against a server version without multiple tenancy. + /// Thrown if the given is null or whitespace. + ICypherFluentQuery DropDatabaseIfExists(string databaseName, bool dumpData = false); + + /// + /// Starts the given database instance on the server. + /// + /// The name of the database to start. + /// An instance to continue the query with. + /// Thrown if this is called against a server version without multiple tenancy. + /// Thrown if the given is null or whitespace. + ICypherFluentQuery StartDatabase(string databaseName); + + /// + /// Stops the given database instance on the server. + /// + /// The name of the database to stop. + /// An instance to continue the query with. + /// Thrown if this is called against a server version without multiple tenancy. + /// Thrown if the given is null or whitespace. + ICypherFluentQuery StopDatabase(string databaseName); + } + +} \ No newline at end of file diff --git a/Neo4jClient.Shared/Cypher/ICypherFluentQuery`TResult.cs b/Neo4jClient/Cypher/ICypherFluentQuery`TResult.cs similarity index 93% rename from Neo4jClient.Shared/Cypher/ICypherFluentQuery`TResult.cs rename to Neo4jClient/Cypher/ICypherFluentQuery`TResult.cs index 726bb8094..2a7b48bca 100644 --- a/Neo4jClient.Shared/Cypher/ICypherFluentQuery`TResult.cs +++ b/Neo4jClient/Cypher/ICypherFluentQuery`TResult.cs @@ -5,7 +5,6 @@ namespace Neo4jClient.Cypher { public interface ICypherFluentQuery : ICypherFluentQuery { - IEnumerable Results { get; } Task> ResultsAsync { get; } new ICypherFluentQuery Unwind(string collectionName, string columnName); diff --git a/Neo4jClient.Shared/Cypher/ICypherFluentQuery`Where.cs b/Neo4jClient/Cypher/ICypherFluentQuery`Where.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/ICypherFluentQuery`Where.cs rename to Neo4jClient/Cypher/ICypherFluentQuery`Where.cs diff --git a/Neo4jClient.Shared/Cypher/ICypherFluentQuery`With.cs b/Neo4jClient/Cypher/ICypherFluentQuery`With.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/ICypherFluentQuery`With.cs rename to Neo4jClient/Cypher/ICypherFluentQuery`With.cs diff --git a/Neo4jClient.Shared/Cypher/ICypherResultItem.cs b/Neo4jClient/Cypher/ICypherResultItem.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/ICypherResultItem.cs rename to Neo4jClient/Cypher/ICypherResultItem.cs diff --git a/Neo4jClient.Shared/Cypher/IFluentCypherResultItem.cs b/Neo4jClient/Cypher/IFluentCypherResultItem.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/IFluentCypherResultItem.cs rename to Neo4jClient/Cypher/IFluentCypherResultItem.cs diff --git a/Neo4jClient.Shared/Cypher/IOrderedCypherFluentQuery.cs b/Neo4jClient/Cypher/IOrderedCypherFluentQuery.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/IOrderedCypherFluentQuery.cs rename to Neo4jClient/Cypher/IOrderedCypherFluentQuery.cs diff --git a/Neo4jClient.Shared/Cypher/IOrderedCypherFluentQuery`TResult.cs b/Neo4jClient/Cypher/IOrderedCypherFluentQuery`TResult.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/IOrderedCypherFluentQuery`TResult.cs rename to Neo4jClient/Cypher/IOrderedCypherFluentQuery`TResult.cs diff --git a/Neo4jClient.Shared/Cypher/Node.cs b/Neo4jClient/Cypher/Node.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/Node.cs rename to Neo4jClient/Cypher/Node.cs diff --git a/Neo4jClient.Shared/Cypher/OrderByType.cs b/Neo4jClient/Cypher/OrderByType.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/OrderByType.cs rename to Neo4jClient/Cypher/OrderByType.cs diff --git a/Neo4jClient.Shared/Cypher/QueryWriter.cs b/Neo4jClient/Cypher/QueryWriter.cs similarity index 86% rename from Neo4jClient.Shared/Cypher/QueryWriter.cs rename to Neo4jClient/Cypher/QueryWriter.cs index d9198ea06..c262132b1 100644 --- a/Neo4jClient.Shared/Cypher/QueryWriter.cs +++ b/Neo4jClient/Cypher/QueryWriter.cs @@ -3,6 +3,7 @@ using System.Collections.Specialized; using System.Linq; using System.Text; +using Neo4j.Driver; using Newtonsoft.Json.Serialization; namespace Neo4jClient.Cypher @@ -13,14 +14,15 @@ public class QueryWriter readonly IDictionary queryParameters; CypherResultMode resultMode; private CypherResultFormat resultFormat; - private readonly List bookmarks = new List(); + private readonly List bookmarks = new List(); - public QueryWriter() + public QueryWriter(string databaseName = null) { queryTextBuilder = new StringBuilder(); queryParameters = new Dictionary(); resultMode = CypherResultMode.Set; resultFormat = CypherResultFormat.DependsOnEnvironment; + DatabaseName = databaseName; } QueryWriter( @@ -28,7 +30,7 @@ public QueryWriter() IDictionary queryParameters, CypherResultMode resultMode, CypherResultFormat resultFormat, - List bookmarks, + List bookmarks, string identifier) { this.queryTextBuilder = queryTextBuilder; @@ -39,7 +41,7 @@ public QueryWriter() this.bookmarks = bookmarks; } - public List Bookmarks => bookmarks; + public List Bookmarks => bookmarks; public CypherResultMode ResultMode { @@ -58,20 +60,24 @@ public CypherResultFormat ResultFormat public NameValueCollection CustomHeaders { get; set; } public string Identifier { get; set; } + //TODO: Do I want this here? Largely I don't care about QW + public string DatabaseName { get; set; } + public QueryWriter Clone() { var clonedQueryTextBuilder = new StringBuilder(queryTextBuilder.ToString()); var clonedParameters = new Dictionary(queryParameters); - var clonedBookmarks = new List(bookmarks); + var clonedBookmarks = new List(bookmarks); return new QueryWriter(clonedQueryTextBuilder, clonedParameters, resultMode, resultFormat, clonedBookmarks, Identifier) { MaxExecutionTime = MaxExecutionTime, - CustomHeaders = CustomHeaders + CustomHeaders = CustomHeaders, + DatabaseName = DatabaseName }; } - public CypherQuery ToCypherQuery(IContractResolver contractResolver = null, bool isWrite = true) + public CypherQuery ToCypherQuery(IContractResolver contractResolver = null, bool isWrite = true, bool includeQueryStats = false) { var queryText = queryTextBuilder .ToString() @@ -82,12 +88,14 @@ public CypherQuery ToCypherQuery(IContractResolver contractResolver = null, bool new Dictionary(queryParameters), resultMode, resultFormat, - contractResolver, + DatabaseName, + contractResolver, MaxExecutionTime, CustomHeaders, isWrite, Bookmarks, - Identifier + Identifier, + includeQueryStats ); } @@ -160,7 +168,7 @@ public QueryWriter AppendToClause(string appendedData, params object[] paramValu string CreateParameterAndReturnPlaceholder(object paramValue) { - var paramName = string.Format("p{0}", queryParameters.Count); + var paramName = $"p{queryParameters.Count}"; queryParameters.Add(paramName, paramValue); return $"${paramName}"; } diff --git a/Neo4jClient.Shared/Cypher/Relationship.cs b/Neo4jClient/Cypher/Relationship.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/Relationship.cs rename to Neo4jClient/Cypher/Relationship.cs diff --git a/Neo4jClient.Shared/Cypher/Return.cs b/Neo4jClient/Cypher/Return.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/Return.cs rename to Neo4jClient/Cypher/Return.cs diff --git a/Neo4jClient.Shared/Cypher/ReturnExpression.cs b/Neo4jClient/Cypher/ReturnExpression.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/ReturnExpression.cs rename to Neo4jClient/Cypher/ReturnExpression.cs diff --git a/Neo4jClient.Shared/Cypher/StartBit.cs b/Neo4jClient/Cypher/StartBit.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/StartBit.cs rename to Neo4jClient/Cypher/StartBit.cs diff --git a/Neo4jClient.Shared/Cypher/StartBitFormatter.cs b/Neo4jClient/Cypher/StartBitFormatter.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/StartBitFormatter.cs rename to Neo4jClient/Cypher/StartBitFormatter.cs diff --git a/Neo4jClient.Shared/Cypher/VbComparer.cs b/Neo4jClient/Cypher/VbComparer.cs similarity index 100% rename from Neo4jClient.Shared/Cypher/VbComparer.cs rename to Neo4jClient/Cypher/VbComparer.cs diff --git a/Neo4jClient.Shared/CypherPartialResult.cs b/Neo4jClient/CypherPartialResult.cs similarity index 100% rename from Neo4jClient.Shared/CypherPartialResult.cs rename to Neo4jClient/CypherPartialResult.cs diff --git a/Neo4jClient.Shared/DeleteMode.cs b/Neo4jClient/DeleteMode.cs similarity index 100% rename from Neo4jClient.Shared/DeleteMode.cs rename to Neo4jClient/DeleteMode.cs diff --git a/Neo4jClient.Shared/DetachedNodeException.cs b/Neo4jClient/DetachedNodeException.cs similarity index 100% rename from Neo4jClient.Shared/DetachedNodeException.cs rename to Neo4jClient/DetachedNodeException.cs diff --git a/Neo4jClient.Shared/Diagrams/Batching.cd b/Neo4jClient/Diagrams/Batching.cd similarity index 100% rename from Neo4jClient.Shared/Diagrams/Batching.cd rename to Neo4jClient/Diagrams/Batching.cd diff --git a/Neo4jClient.Shared/Diagrams/GraphClientCore.cd b/Neo4jClient/Diagrams/GraphClientCore.cd similarity index 100% rename from Neo4jClient.Shared/Diagrams/GraphClientCore.cd rename to Neo4jClient/Diagrams/GraphClientCore.cd diff --git a/Neo4jClient.Shared/Diagrams/Responses.cd b/Neo4jClient/Diagrams/Responses.cd similarity index 100% rename from Neo4jClient.Shared/Diagrams/Responses.cd rename to Neo4jClient/Diagrams/Responses.cd diff --git a/Neo4jClient/DriverWrapper.cs b/Neo4jClient/DriverWrapper.cs new file mode 100644 index 000000000..7bd30f34e --- /dev/null +++ b/Neo4jClient/DriverWrapper.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Neo4j.Driver; + +namespace Neo4jClient +{ + internal class DriverWrapper : IDriver + { + private readonly IDriver driver; + public string Username { get; } + public string Password { get; } + public string Realm { get; } + public EncryptionLevel? EncryptionLevel { get; } + + public DriverWrapper(IDriver driver) + { + this.driver = driver; + } + + public DriverWrapper(string uri, IServerAddressResolver addressResolver, string username, string pass, string realm, EncryptionLevel? encryptionLevel) + :this(new Uri(uri), addressResolver, username, pass, realm, encryptionLevel) + { + } + + public DriverWrapper(Uri uri, IServerAddressResolver addressResolver, string username, string pass, string realm, EncryptionLevel? encryptionLevel) + { + Uri = uri; + Username = username; + Password = pass; + Realm = realm; + EncryptionLevel = encryptionLevel; + + var authToken = GetAuthToken(username, pass, realm); + if (addressResolver != null) + { + driver = encryptionLevel == null + ? GraphDatabase.Driver(uri, authToken, builder => builder.WithResolver(addressResolver)) + : GraphDatabase.Driver(uri, authToken, builder => builder.WithResolver(addressResolver).WithEncryptionLevel(encryptionLevel.Value)); + } + else + { + driver = GraphDatabase.Driver(uri, authToken); + } + } + + public IAsyncSession Session() + { + return driver.AsyncSession(); + } + + public IAsyncSession Session(AccessMode defaultMode) + { + return driver.AsyncSession(x => x.WithDefaultAccessMode(defaultMode)); + } + + public IAsyncSession Session(string bookmark) + { + return driver.AsyncSession(x => x.WithBookmarks(Bookmark.From(bookmark))); + } + + public IAsyncSession Session(AccessMode defaultMode, string bookmark) + { + return driver.AsyncSession(x => x.WithDefaultAccessMode(defaultMode).WithBookmarks(Bookmark.From(bookmark))); + } + + public IAsyncSession Session(AccessMode defaultMode, IEnumerable bookmarks) + { + return driver.AsyncSession(x => + { + x.WithDefaultAccessMode(defaultMode); + if (bookmarks != null) x.WithBookmarks(Bookmark.From(bookmarks.ToArray())); + }); + } + + public IAsyncSession Session(IEnumerable bookmarks) + { + return driver.AsyncSession(x => + { + if (bookmarks != null) x.WithBookmarks(Bookmark.From(bookmarks.ToArray())); + }); + } + + public IAsyncSession AsyncSession() + { + return driver.AsyncSession(); + } + + public IAsyncSession AsyncSession(Action action) + { + return driver.AsyncSession(action); + } + + public Task CloseAsync() + { + return driver.CloseAsync(); + } + + public Task VerifyConnectivityAsync() + { + return driver.VerifyConnectivityAsync(); + } + + public Task SupportsMultiDbAsync() + { + return driver.SupportsMultiDbAsync(); + } + + public Config Config => driver.Config; + + public Uri Uri { get; private set; } + + public IServerAddressResolver AddressResolver => Config.Resolver; + + private static IAuthToken GetAuthToken(string username, string password, string realm) + { + return string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(password) + ? AuthTokens.None + : AuthTokens.Basic(username, password, realm); + } + public void Dispose() + { + driver?.Dispose(); + } + } +} \ No newline at end of file diff --git a/Neo4jClient/Execution/BatchExecutionPolicy.cs b/Neo4jClient/Execution/BatchExecutionPolicy.cs new file mode 100644 index 000000000..ba837b476 --- /dev/null +++ b/Neo4jClient/Execution/BatchExecutionPolicy.cs @@ -0,0 +1,28 @@ +// using System; +// using System.Collections.Generic; +// using System.Linq; +// using System.Text; +// +// namespace Neo4jClient.Execution +// { +// internal class BatchExecutionPolicy : GraphClientBasedExecutionPolicy +// { +// public BatchExecutionPolicy(IGraphClient client) : base(client) +// { +// } +// +// public override TransactionExecutionPolicy TransactionExecutionPolicy +// { +// get { return TransactionExecutionPolicy.Denied; } +// } +// +// public override void AfterExecution(IDictionary executionMetadata, object executionContext) +// { +// } +// +// public override Uri BaseEndpoint(string database = null) +// { +// return Replace(Client.BatchEndpoint, database); +// } +// } +// } diff --git a/Neo4jClient/Execution/CypherExecutionPolicy.cs b/Neo4jClient/Execution/CypherExecutionPolicy.cs index 36519e7ec..d4e819e1c 100644 --- a/Neo4jClient/Execution/CypherExecutionPolicy.cs +++ b/Neo4jClient/Execution/CypherExecutionPolicy.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net.Http; using Neo4jClient.ApiModels.Cypher; using Neo4jClient.Cypher; using Neo4jClient.Transactions; @@ -10,30 +11,77 @@ namespace Neo4jClient.Execution /// /// Describes the behavior for a cypher execution. /// - internal partial class CypherExecutionPolicy : GraphClientBasedExecutionPolicy + internal class CypherExecutionPolicy : GraphClientBasedExecutionPolicy { + public CypherExecutionPolicy(IGraphClient client) : base(client) + { + } private INeo4jTransaction GetTransactionInScope() { + // first try to get the Non DTC transaction and if it doesn't succeed then try it with the DTC + if (!(Client is IInternalTransactionalGraphClient transactionalClient)) + return null; + + if (transactionalClient.Transaction is TransactionScopeProxy proxiedTransaction) + return proxiedTransaction.TransactionContext; + return null; } - public override Uri BaseEndpoint + public override Uri BaseEndpoint(string database = null, bool autoCommit = false) { - get - { - if(InTransaction) - throw new NotImplementedException("Not implemented in the PCL version at present."); + if (!InTransaction && Client.TransactionEndpoint != null) + return Client.GetTransactionEndpoint(database, autoCommit); - return Client.CypherEndpoint; + var proxiedTransaction = GetTransactionInScope(); + var transactionalClient = (ITransactionalGraphClient) Client; + if (proxiedTransaction == null) + { + return Replace(transactionalClient.TransactionEndpoint, database); } + + var startingReference = proxiedTransaction.Endpoint ?? Client.GetTransactionEndpoint(database, autoCommit); + return startingReference; } - public override TransactionExecutionPolicy TransactionExecutionPolicy + + + public override TransactionExecutionPolicy TransactionExecutionPolicy => TransactionExecutionPolicy.Allowed; + + public override string SerializeRequest(object toSerialize) { - get { return TransactionExecutionPolicy.Allowed; } + if (!(toSerialize is CypherQuery query)) + throw new InvalidOperationException("Unsupported operation: Attempting to serialize something that was not a query."); + + return Client + .Serializer + .Serialize(new CypherStatementList + { + new CypherTransactionStatement(query) + }); } - + public override string Database { get; set; } + + public override void AfterExecution(IDictionary executionMetadata, object executionContext) + { + if (Client == null || executionMetadata == null || executionMetadata.Count == 0) + return; + + // determine if we need to update the transaction end point + if (!(executionContext is INeo4jTransaction transaction) || transaction.Endpoint != null) + return; + + if (!executionMetadata.TryGetValue("Location", out var locationValue)) + return; + + if (!(locationValue is IEnumerable locationHeader)) + return; + + var generatedEndpoint = locationHeader.FirstOrDefault(); + if (!string.IsNullOrEmpty(generatedEndpoint)) + transaction.Endpoint = new Uri(generatedEndpoint); + } } -} +} \ No newline at end of file diff --git a/Neo4jClient.Shared/Execution/CypherTransactionExecutionPolicy.cs b/Neo4jClient/Execution/CypherTransactionExecutionPolicy.cs similarity index 75% rename from Neo4jClient.Shared/Execution/CypherTransactionExecutionPolicy.cs rename to Neo4jClient/Execution/CypherTransactionExecutionPolicy.cs index 4cb12b25d..f2d13b206 100644 --- a/Neo4jClient.Shared/Execution/CypherTransactionExecutionPolicy.cs +++ b/Neo4jClient/Execution/CypherTransactionExecutionPolicy.cs @@ -7,9 +7,6 @@ public CypherTransactionExecutionPolicy(IGraphClient client) { } - public override TransactionExecutionPolicy TransactionExecutionPolicy - { - get { return TransactionExecutionPolicy.Required; } - } + public override TransactionExecutionPolicy TransactionExecutionPolicy => TransactionExecutionPolicy.Required; } } \ No newline at end of file diff --git a/Neo4jClient.Shared/Execution/EndpointBuilderExtension.cs b/Neo4jClient/Execution/EndpointBuilderExtension.cs similarity index 100% rename from Neo4jClient.Shared/Execution/EndpointBuilderExtension.cs rename to Neo4jClient/Execution/EndpointBuilderExtension.cs diff --git a/Neo4jClient.Shared/Execution/ErrorGenerator.cs b/Neo4jClient/Execution/ErrorGenerator.cs similarity index 100% rename from Neo4jClient.Shared/Execution/ErrorGenerator.cs rename to Neo4jClient/Execution/ErrorGenerator.cs diff --git a/Neo4jClient.Shared/Execution/ExecutionConfiguration.cs b/Neo4jClient/Execution/ExecutionConfiguration.cs similarity index 90% rename from Neo4jClient.Shared/Execution/ExecutionConfiguration.cs rename to Neo4jClient/Execution/ExecutionConfiguration.cs index d457dfc0f..f410af697 100644 --- a/Neo4jClient.Shared/Execution/ExecutionConfiguration.cs +++ b/Neo4jClient/Execution/ExecutionConfiguration.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using Neo4j.Driver; using Newtonsoft.Json; namespace Neo4jClient.Execution @@ -20,5 +21,6 @@ public ExecutionConfiguration() public bool HasErrors { get; set; } public Guid ResourceManagerId { get; set; } public string Realm { get; set; } + public EncryptionLevel? EncryptionLevel { get; set; } } } \ No newline at end of file diff --git a/Neo4jClient.Shared/Execution/ExecutionPolicyFactory.cs b/Neo4jClient/Execution/ExecutionPolicyFactory.cs similarity index 66% rename from Neo4jClient.Shared/Execution/ExecutionPolicyFactory.cs rename to Neo4jClient/Execution/ExecutionPolicyFactory.cs index 66ffb63f4..c1fd806f7 100644 --- a/Neo4jClient.Shared/Execution/ExecutionPolicyFactory.cs +++ b/Neo4jClient/Execution/ExecutionPolicyFactory.cs @@ -26,18 +26,16 @@ public IExecutionPolicy GetPolicy(PolicyType 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.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); + // 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 index 202222011..d02b8f043 100644 --- a/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs +++ b/Neo4jClient/Execution/GraphClientBasedExecutionPolicy.cs @@ -1,12 +1,45 @@ -namespace Neo4jClient.Execution +using System; +using System.Collections.Generic; +using System.Transactions; +using Neo4jClient.Transactions; + +namespace Neo4jClient.Execution { - internal abstract partial class GraphClientBasedExecutionPolicy : IExecutionPolicy + internal abstract class GraphClientBasedExecutionPolicy : IExecutionPolicy { - public bool InTransaction + protected IGraphClient Client; + + protected GraphClientBasedExecutionPolicy(IGraphClient client) { - get { return false; } + Client = client; } + public bool InTransaction => + Client is ITransactionalGraphClient transactionalGraphClient && + (transactionalGraphClient.InTransaction || Transaction.Current != null); + public abstract TransactionExecutionPolicy TransactionExecutionPolicy { get; } + + public abstract void AfterExecution(IDictionary executionMetadata, object executionContext); + + public virtual string SerializeRequest(object toSerialize) + { + return Client.Serializer.Serialize(toSerialize); + } + + public abstract string Database { get; set; } + + public abstract Uri BaseEndpoint(string database = null, bool autoCommit = false); + + public virtual Uri AddPath(Uri startUri, object startReference) + { + return startUri; + } + + protected Uri Replace(Uri toReplace, string replaceWith) + { + //TODO: Less CopyPasta + return new Uri(toReplace.AbsoluteUri.Replace("{databaseName}", replaceWith ?? Client.DefaultDatabase)); + } } } \ No newline at end of file diff --git a/Neo4jClient.Shared/Execution/HttpResponseMessageExtensions.cs b/Neo4jClient/Execution/HttpResponseMessageExtensions.cs similarity index 73% rename from Neo4jClient.Shared/Execution/HttpResponseMessageExtensions.cs rename to Neo4jClient/Execution/HttpResponseMessageExtensions.cs index cc1747db3..f4605bf26 100644 --- a/Neo4jClient.Shared/Execution/HttpResponseMessageExtensions.cs +++ b/Neo4jClient/Execution/HttpResponseMessageExtensions.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Net; using System.Net.Http; +using System.Threading.Tasks; using Neo4jClient.ApiModels; using Newtonsoft.Json; @@ -9,19 +10,19 @@ namespace Neo4jClient.Execution { internal static class HttpResponseMessageExtensions { - internal static void EnsureExpectedStatusCode(this HttpResponseMessage response, params HttpStatusCode[] expectedStatusCodes) + internal static Task EnsureExpectedStatusCode(this HttpResponseMessage response, params HttpStatusCode[] expectedStatusCodes) { - response.EnsureExpectedStatusCode(null, expectedStatusCodes); + return response.EnsureExpectedStatusCode(null, expectedStatusCodes); } - internal static void EnsureExpectedStatusCode(this HttpResponseMessage response, string commandDescription, params HttpStatusCode[] expectedStatusCodes) + internal static async Task EnsureExpectedStatusCode(this HttpResponseMessage response, string commandDescription, params HttpStatusCode[] expectedStatusCodes) { if (expectedStatusCodes.Contains(response.StatusCode)) return; if (response.StatusCode == HttpStatusCode.BadRequest) { - var neoException = TryBuildNeoException(response); + var neoException = await TryBuildNeoException(response).ConfigureAwait(false); if (neoException != null) throw neoException; } @@ -46,12 +47,12 @@ internal static void EnsureExpectedStatusCode(this HttpResponseMessage response, rawBody)); } - static NeoException TryBuildNeoException(HttpResponseMessage response) + static async Task TryBuildNeoException(HttpResponseMessage response) { var isJson = response.Content.Headers.ContentType.MediaType.Equals("application/json", StringComparison.OrdinalIgnoreCase); if (!isJson) return null; - var exceptionResponse = response.Content.ReadAsJson(new JsonConverter[0]); + var exceptionResponse = await response.Content.ReadAsJsonAsync(new JsonConverter[0]).ConfigureAwait(false); if (string.IsNullOrEmpty(exceptionResponse.Message) || string.IsNullOrEmpty(exceptionResponse.Exception)) diff --git a/Neo4jClient.Shared/Execution/IExecutionPolicy.cs b/Neo4jClient/Execution/IExecutionPolicy.cs similarity index 85% rename from Neo4jClient.Shared/Execution/IExecutionPolicy.cs rename to Neo4jClient/Execution/IExecutionPolicy.cs index e64d5442f..24293b860 100644 --- a/Neo4jClient.Shared/Execution/IExecutionPolicy.cs +++ b/Neo4jClient/Execution/IExecutionPolicy.cs @@ -9,8 +9,9 @@ internal interface IExecutionPolicy TransactionExecutionPolicy TransactionExecutionPolicy { get; } void AfterExecution(IDictionary executionMetadata, object executionContext); string SerializeRequest(object toSerialize); - Uri BaseEndpoint { get; } + Uri BaseEndpoint(string database, bool autoCommit); Uri AddPath(Uri startUri, object startReference); + string Database { get; set; } } internal enum TransactionExecutionPolicy diff --git a/Neo4jClient.Shared/Execution/IExecutionPolicyFactory.cs b/Neo4jClient/Execution/IExecutionPolicyFactory.cs similarity index 97% rename from Neo4jClient.Shared/Execution/IExecutionPolicyFactory.cs rename to Neo4jClient/Execution/IExecutionPolicyFactory.cs index 161825ee5..f3a7bb530 100644 --- a/Neo4jClient.Shared/Execution/IExecutionPolicyFactory.cs +++ b/Neo4jClient/Execution/IExecutionPolicyFactory.cs @@ -14,7 +14,6 @@ internal interface IExecutionPolicyFactory public enum PolicyType { Cypher, - Gremlin, Rest, Batch, Transaction, diff --git a/Neo4jClient.Shared/Execution/IHttpClient.cs b/Neo4jClient/Execution/IHttpClient.cs similarity index 100% rename from Neo4jClient.Shared/Execution/IHttpClient.cs rename to Neo4jClient/Execution/IHttpClient.cs diff --git a/Neo4jClient.Shared/Execution/IRequestTypeBuilder.cs b/Neo4jClient/Execution/IRequestTypeBuilder.cs similarity index 100% rename from Neo4jClient.Shared/Execution/IRequestTypeBuilder.cs rename to Neo4jClient/Execution/IRequestTypeBuilder.cs diff --git a/Neo4jClient.Shared/Execution/IRequestWithPendingContentBuilder.cs b/Neo4jClient/Execution/IRequestWithPendingContentBuilder.cs similarity index 100% rename from Neo4jClient.Shared/Execution/IRequestWithPendingContentBuilder.cs rename to Neo4jClient/Execution/IRequestWithPendingContentBuilder.cs diff --git a/Neo4jClient.Shared/Execution/IResponseBuilder.cs b/Neo4jClient/Execution/IResponseBuilder.cs similarity index 63% rename from Neo4jClient.Shared/Execution/IResponseBuilder.cs rename to Neo4jClient/Execution/IResponseBuilder.cs index 326bce3f5..2329c06f1 100644 --- a/Neo4jClient.Shared/Execution/IResponseBuilder.cs +++ b/Neo4jClient/Execution/IResponseBuilder.cs @@ -11,15 +11,12 @@ internal interface IResponseBuilder 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 continuationFunction); + Task ExecuteAsync(string commandDescription, Func continuationFunction); + Task ExecuteAsync(Func continuationFunction); + Task ExecuteAsync(string commandDescription, Func continuationFunction); - Task ExecuteAsync(Func, TExpected> continuationFunction); - Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction); - - HttpResponseMessage Execute(); - HttpResponseMessage Execute(string commandDescription); IResponseBuilder ParseAs() where TParse : new(); } diff --git a/Neo4jClient.Shared/Execution/IResponseBuilder`TResult.cs b/Neo4jClient/Execution/IResponseBuilder`TResult.cs similarity index 63% rename from Neo4jClient.Shared/Execution/IResponseBuilder`TResult.cs rename to Neo4jClient/Execution/IResponseBuilder`TResult.cs index 1c13540fb..42f29b908 100644 --- a/Neo4jClient.Shared/Execution/IResponseBuilder`TResult.cs +++ b/Neo4jClient/Execution/IResponseBuilder`TResult.cs @@ -11,14 +11,11 @@ namespace Neo4jClient.Execution 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(Func continuationFunction); + Task ExecuteAsync(string commandDescription, Func continuationFunction); Task ExecuteAsync(); - Task ExecuteAsync(Func, TExpected> continuationFunction); - Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction); - - TResult Execute(string commandDescription); - TResult Execute(); + Task ExecuteAsync(Func continuationFunction); + Task ExecuteAsync(string commandDescription, Func continuationFunction); } } diff --git a/Neo4jClient.Shared/Execution/IResponseFailBuilder.cs b/Neo4jClient/Execution/IResponseFailBuilder.cs similarity index 100% rename from Neo4jClient.Shared/Execution/IResponseFailBuilder.cs rename to Neo4jClient/Execution/IResponseFailBuilder.cs diff --git a/Neo4jClient/Execution/NodeIndexExecutionPolicy.cs b/Neo4jClient/Execution/NodeIndexExecutionPolicy.cs new file mode 100644 index 000000000..59c3196c6 --- /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(string database = null) +// { +// return Replace(Client.NodeIndexEndpoint, database); +// } +// } +// } \ No newline at end of file diff --git a/Neo4jClient/Execution/RelationshipIndexExecutionPolicy.cs b/Neo4jClient/Execution/RelationshipIndexExecutionPolicy.cs new file mode 100644 index 000000000..6f3b2e104 --- /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(string database = null) +// { +// return Replace(Client.RelationshipIndexEndpoint, database); +// } +// } +// } \ No newline at end of file diff --git a/Neo4jClient.Shared/Execution/Request.cs b/Neo4jClient/Execution/Request.cs similarity index 100% rename from Neo4jClient.Shared/Execution/Request.cs rename to Neo4jClient/Execution/Request.cs diff --git a/Neo4jClient.Shared/Execution/RequestTypeBuilder.cs b/Neo4jClient/Execution/RequestTypeBuilder.cs similarity index 100% rename from Neo4jClient.Shared/Execution/RequestTypeBuilder.cs rename to Neo4jClient/Execution/RequestTypeBuilder.cs diff --git a/Neo4jClient.Shared/Execution/RequestWithPendingContentBuilder.cs b/Neo4jClient/Execution/RequestWithPendingContentBuilder.cs similarity index 100% rename from Neo4jClient.Shared/Execution/RequestWithPendingContentBuilder.cs rename to Neo4jClient/Execution/RequestWithPendingContentBuilder.cs diff --git a/Neo4jClient.Shared/Execution/ResponseBuilder.cs b/Neo4jClient/Execution/ResponseBuilder.cs similarity index 72% rename from Neo4jClient.Shared/Execution/ResponseBuilder.cs rename to Neo4jClient/Execution/ResponseBuilder.cs index 16030b496..41f0d5f40 100644 --- a/Neo4jClient.Shared/Execution/ResponseBuilder.cs +++ b/Neo4jClient/Execution/ResponseBuilder.cs @@ -124,79 +124,51 @@ public Task ExecuteAsync(string commandDescription) } - public Task ExecuteAsync(Func, HttpResponseMessage> continuationFunction) + public Task ExecuteAsync(Func continuationFunction) { return ExecuteAsync(null, continuationFunction); } - public Task ExecuteAsync(string commandDescription, Func, HttpResponseMessage> continuationFunction) + public async Task ExecuteAsync(string commandDescription, Func continuationFunction) { - var executionTask = PrepareAsync().ContinueWith(requestTask => + var response = await PrepareAsync().ConfigureAwait(false); + if (string.IsNullOrEmpty(commandDescription)) { - var response = requestTask.Result; - if (string.IsNullOrEmpty(commandDescription)) - { - response.EnsureExpectedStatusCode(_expectedStatusCodes.ToArray()); - } - else - { - response.EnsureExpectedStatusCode(commandDescription, _expectedStatusCodes.ToArray()); - } + await response.EnsureExpectedStatusCode(_expectedStatusCodes.ToArray()).ConfigureAwait(false); + } + else + { + await response.EnsureExpectedStatusCode(commandDescription, _expectedStatusCodes.ToArray()).ConfigureAwait(false); + } - // 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 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.Condition(response)) + if (errorGenerator.Generator != null) { - if (errorGenerator.Generator != null) - { - throw errorGenerator.Generator(response); - } - - return null; + throw errorGenerator.Generator(response); } - } - return response; - }); + return null; + } + } return continuationFunction != null ? - executionTask.ContinueWith(continuationFunction) : - executionTask; + continuationFunction(response) : + response; } - public Task ExecuteAsync(Func, TExpected> continuationFunction) + public Task ExecuteAsync(Func continuationFunction) { return ExecuteAsync(null, continuationFunction); } - public Task ExecuteAsync(string commandDescription, Func, TExpected> continuationFunction) + public async Task ExecuteAsync(string commandDescription, Func continuationFunction) { - return ExecuteAsync(commandDescription, null).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) - { - Exception e; - if (ex.TryUnwrap(out e)) - throw e; - throw; - } - return task.Result; + return continuationFunction(await ExecuteAsync(commandDescription, null).ConfigureAwait(false)); } public IResponseBuilder ParseAs() where TParse : new() diff --git a/Neo4jClient.Shared/Execution/ResponseBuilder`TParse.cs b/Neo4jClient/Execution/ResponseBuilder`TParse.cs similarity index 60% rename from Neo4jClient.Shared/Execution/ResponseBuilder`TParse.cs rename to Neo4jClient/Execution/ResponseBuilder`TParse.cs index febd7c481..aac185bcb 100644 --- a/Neo4jClient.Shared/Execution/ResponseBuilder`TParse.cs +++ b/Neo4jClient/Execution/ResponseBuilder`TParse.cs @@ -21,11 +21,11 @@ public ResponseBuilder(HttpRequestMessage request, ISet expected { } - private TParse CastIntoResult(HttpResponseMessage response) + private async Task CastIntoResult(HttpResponseMessage response) { return response == null || response.Content == null ? default(TParse) : - response.Content.ReadAsJson(_executionConfiguration.JsonConverters); + await response.Content.ReadAsJsonAsync(_executionConfiguration.JsonConverters).ConfigureAwait(false); } public new IResponseBuilder WithExpectedStatusCodes(params HttpStatusCode[] statusCodes) @@ -43,19 +43,16 @@ private TParse CastIntoResult(HttpResponseMessage response) return ExecuteAsync(commandDescription, null); } - public Task ExecuteAsync(Func, TParse> continuationFunction) + public Task ExecuteAsync(Func continuationFunction) { return ExecuteAsync(null, continuationFunction); } - - - public Task ExecuteAsync(string commandDescription, Func, TParse> continuationFunction) + + public async Task ExecuteAsync(string commandDescription, Func continuationFunction) { - var executionTask = base.ExecuteAsync(commandDescription, null) - .ContinueWith( - responseAction => - responseAction.Result == null ? default(TParse) : CastIntoResult(responseAction.Result)); - return continuationFunction == null ? executionTask : executionTask.ContinueWith(continuationFunction); + var response = await base.ExecuteAsync(commandDescription, null).ConfigureAwait(false); + var parsed = response == null ? default(TParse) : await CastIntoResult(response).ConfigureAwait(false); + return continuationFunction == null ? parsed : continuationFunction(parsed); } public new Task ExecuteAsync() @@ -63,24 +60,14 @@ public Task ExecuteAsync(string commandDescription, Func, T return ExecuteAsync(null, null); } - public Task ExecuteAsync(Func, TExpected> continuationFunction) + public Task ExecuteAsync(Func 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() + public async Task ExecuteAsync(string commandDescription, Func continuationFunction) { - return Execute(null); + return continuationFunction(await ExecuteAsync(commandDescription).ConfigureAwait(false)); } } } diff --git a/Neo4jClient.Shared/Execution/ResponseFailBuilder.cs b/Neo4jClient/Execution/ResponseFailBuilder.cs similarity index 100% rename from Neo4jClient.Shared/Execution/ResponseFailBuilder.cs rename to Neo4jClient/Execution/ResponseFailBuilder.cs diff --git a/Neo4jClient.Shared/Execution/ResponseFailBuilder`TParse.cs b/Neo4jClient/Execution/ResponseFailBuilder`TParse.cs similarity index 100% rename from Neo4jClient.Shared/Execution/ResponseFailBuilder`TParse.cs rename to Neo4jClient/Execution/ResponseFailBuilder`TParse.cs diff --git a/Neo4jClient.Shared/Execution/RestExecutionPolicy.cs b/Neo4jClient/Execution/RestExecutionPolicy.cs similarity index 83% rename from Neo4jClient.Shared/Execution/RestExecutionPolicy.cs rename to Neo4jClient/Execution/RestExecutionPolicy.cs index db24e50bc..849e6e6f5 100644 --- a/Neo4jClient.Shared/Execution/RestExecutionPolicy.cs +++ b/Neo4jClient/Execution/RestExecutionPolicy.cs @@ -5,24 +5,24 @@ namespace Neo4jClient.Execution { + //TODO : Remove this internal class RestExecutionPolicy : GraphClientBasedExecutionPolicy { + + public RestExecutionPolicy(IGraphClient client) : base(client) { } - public override TransactionExecutionPolicy TransactionExecutionPolicy - { - get { return TransactionExecutionPolicy.Denied; } - } + public override TransactionExecutionPolicy TransactionExecutionPolicy => TransactionExecutionPolicy.Denied; public override void AfterExecution(IDictionary executionMetadata, object executionContext) { } - public override Uri BaseEndpoint + public override Uri BaseEndpoint(string database = null, bool autoCommit = false) { - get { return Client.RootEndpoint; } + return Replace(Client.RootEndpoint, database); } public override Uri AddPath(Uri startUri, object startReference) @@ -45,6 +45,8 @@ public override Uri AddPath(Uri startUri, object startReference) throw new NotImplementedException("Unknown startReference parameter for REST policy"); } + public override string Database { get; set; } + private Uri AddPath(Uri startUri, NodeReference node) { return startUri.AddPath("node").AddPath(node.Id.ToString()); diff --git a/Neo4jClient/Extensions/DriverExtensions.cs b/Neo4jClient/Extensions/DriverExtensions.cs new file mode 100644 index 000000000..451c0124b --- /dev/null +++ b/Neo4jClient/Extensions/DriverExtensions.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Neo4jClient.Extensions +{ + using System.Linq; + using Neo4j.Driver; + + public static class DriverExtensions + { + + public static IAsyncSession AsyncSession(this IDriver driver, Version databaseVersion, string database, bool isWrite, IEnumerable bookmarks) + { + IEnumerable properBookmarks = null; + if (bookmarks != null) + properBookmarks = bookmarks.Select(b => Bookmark.From(b)); + + return driver.AsyncSession(databaseVersion, database, isWrite, properBookmarks); + } + + public static IAsyncSession AsyncSession(this IDriver driver, Version databaseVersion, string database, bool isWrite, IEnumerable bookmarks) + { + return driver.AsyncSession(builder => + { + if(databaseVersion.Major >= 4) + builder.WithDatabase(database); + builder.WithDefaultAccessMode(isWrite ? AccessMode.Write : AccessMode.Read); + if (bookmarks != null) + builder.WithBookmarks(bookmarks.ToArray()); + }); + } + } +} diff --git a/Neo4jClient.Shared/Extensions/EnumerableExtensions.cs b/Neo4jClient/Extensions/EnumerableExtensions.cs similarity index 100% rename from Neo4jClient.Shared/Extensions/EnumerableExtensions.cs rename to Neo4jClient/Extensions/EnumerableExtensions.cs diff --git a/Neo4jClient.Shared/Extensions/MemberInfoExtensions.cs b/Neo4jClient/Extensions/MemberInfoExtensions.cs similarity index 100% rename from Neo4jClient.Shared/Extensions/MemberInfoExtensions.cs rename to Neo4jClient/Extensions/MemberInfoExtensions.cs diff --git a/Neo4jClient.Shared/Extensions/ObjectExtensions.cs b/Neo4jClient/Extensions/ObjectExtensions.cs similarity index 100% rename from Neo4jClient.Shared/Extensions/ObjectExtensions.cs rename to Neo4jClient/Extensions/ObjectExtensions.cs diff --git a/Neo4jClient/GraphClient.cs b/Neo4jClient/GraphClient.cs index cf4a42b77..8869b2e88 100644 --- a/Neo4jClient/GraphClient.cs +++ b/Neo4jClient/GraphClient.cs @@ -1,109 +1,588 @@ -using System; -using System.Diagnostics; -using System.Net.Http; -using System.Threading.Tasks; -using Neo4jClient.Cypher; -using Neo4jClient.Transactions; - -namespace Neo4jClient -{ - public partial class GraphClient : IRawGraphClient, IInternalTransactionalGraphClient, IDisposable - { - 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, string username = null, string password = null) - : this(rootUri, new HttpClientWrapper(username, password)) - { -// ServicePointManager.Expect100Continue = expect100Continue; -// ServicePointManager.UseNagleAlgorithm = useNagleAlgorithm; - } - - public virtual async Task ConnectAsync(NeoServerConfiguration configuration = null) - { - if (IsConnected) - { - return; - } - - var stopwatch = Stopwatch.StartNew(); - var operationCompletedArgs = new OperationCompletedEventArgs - { - QueryText = "Connect", - ResourcesReturned = 0 - }; - - Action stopTimerAndNotifyCompleted = () => - { - stopwatch.Stop(); - operationCompletedArgs.TimeTaken = stopwatch.Elapsed; - OnOperationCompleted(operationCompletedArgs); - }; - - try - { - configuration = configuration ?? await NeoServerConfiguration.GetConfigurationAsync( - RootUri, - ExecutionConfiguration.Username, - ExecutionConfiguration.Password, - ExecutionConfiguration.Realm, - ExecutionConfiguration).ConfigureAwait(false); - - RootApiResponse = configuration.ApiConfig; - - if (!string.IsNullOrWhiteSpace(RootApiResponse.Transaction)) - { - // transactionManager = new TransactionManager(this); - } - - rootNode = string.IsNullOrEmpty(RootApiResponse.ReferenceNode) - ? null - : new RootNode(long.Parse(GetLastPathSegment(RootApiResponse.ReferenceNode)), this); - - // http://blog.neo4j.org/2012/04/streaming-rest-api-interview-with.html - ExecutionConfiguration.UseJsonStreaming = ExecutionConfiguration.UseJsonStreaming && - RootApiResponse.Version >= new Version(1, 8); - - if (RootApiResponse.Version < new Version(2, 0)) - cypherCapabilities = CypherCapabilities.Cypher19; - - if (RootApiResponse.Version >= new Version(2, 2)) - cypherCapabilities = CypherCapabilities.Cypher22; - - if (RootApiResponse.Version >= new Version(2, 2, 6)) - cypherCapabilities = CypherCapabilities.Cypher226; - - if (RootApiResponse.Version >= new Version(2, 3)) - cypherCapabilities = CypherCapabilities.Cypher23; - - if (RootApiResponse.Version >= new Version(3, 0)) - cypherCapabilities = CypherCapabilities.Cypher30; - } - catch (AggregateException ex) - { - Exception unwrappedException; - var wasUnwrapped = ex.TryUnwrap(out unwrappedException); - operationCompletedArgs.Exception = wasUnwrapped ? unwrappedException : ex; - - stopTimerAndNotifyCompleted(); - - if (wasUnwrapped) - throw unwrappedException; - - throw; - } - catch (Exception e) - { - operationCompletedArgs.Exception = e; - stopTimerAndNotifyCompleted(); - throw; - } - - stopTimerAndNotifyCompleted(); - } - } -} \ No newline at end of file +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Reflection; +using System.Threading.Tasks; +using Neo4j.Driver; +using Neo4jClient.ApiModels; +using Neo4jClient.ApiModels.Cypher; +using Neo4jClient.Cypher; +using Neo4jClient.Execution; +using Neo4jClient.Serialization; +using Neo4jClient.Transactions; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Neo4jClient +{ + public class GraphClient : IRawGraphClient, IInternalTransactionalGraphClient, IDisposable + { + public ITransactionalGraphClient Tx => this; + + 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 MaxExecutionTimeHeaderKey = "max-execution-time"; + + public static readonly JsonConverter[] DefaultJsonConverters = + { + new TypeConverterBasedJsonConverter(), + new NullableEnumValueConverter(), + new TimeZoneInfoConverter(), + new EnumValueConverter() + }; + + public static readonly DefaultContractResolver DefaultJsonContractResolver = new Neo4jContractResolver(); + + private ITransactionManager transactionManager; + private readonly IExecutionPolicyFactory policyFactory; + + public ExecutionConfiguration ExecutionConfiguration { get; private set; } + + internal readonly Uri RootUri; + internal RootApiResponse RootApiResponse; + private string defaultDatabase = "neo4j"; + + + public bool UseJsonStreamingIfAvailable { get; set; } + + public GraphClient(Uri rootUri, string username = null, string password = null) + : this(rootUri, new HttpClientWrapper(username, password)) + { + } + + public virtual async Task ConnectAsync(NeoServerConfiguration configuration = null) + { + if (IsConnected) + { + return; + } + + var stopwatch = Stopwatch.StartNew(); + var operationCompletedArgs = new OperationCompletedEventArgs + { + QueryText = "Connect", + ResourcesReturned = 0 + }; + + void StopTimerAndNotifyCompleted() + { + stopwatch.Stop(); + operationCompletedArgs.TimeTaken = stopwatch.Elapsed; + OnOperationCompleted(operationCompletedArgs); + } + + try + { + configuration = configuration ?? await NeoServerConfiguration.GetConfigurationAsync( + RootUri, + ExecutionConfiguration.Username, + ExecutionConfiguration.Password, + ExecutionConfiguration.Realm, + ExecutionConfiguration.EncryptionLevel, + ExecutionConfiguration).ConfigureAwait(false); + + RootApiResponse = configuration.ApiConfig; + + if (!string.IsNullOrWhiteSpace(RootApiResponse.Transaction)) + { + transactionManager = new TransactionManager(this); + } + + + // http://blog.neo4j.org/2012/04/streaming-rest-api-interview-with.html + ExecutionConfiguration.UseJsonStreaming = ExecutionConfiguration.UseJsonStreaming && + RootApiResponse.Version >= new Version(1, 8); + + var version = RootApiResponse.Version; + if (version < new Version(2, 0)) + CypherCapabilities = CypherCapabilities.Cypher19; + + if (version >= new Version(2, 2)) + CypherCapabilities = CypherCapabilities.Cypher22; + + if (version >= new Version(2, 2, 6)) + CypherCapabilities = CypherCapabilities.Cypher226; + + if (version >= new Version(2, 3)) + CypherCapabilities = CypherCapabilities.Cypher23; + + if (version >= new Version(3, 0)) + CypherCapabilities = CypherCapabilities.Cypher30; + + if (version >= new Version(4, 0)) + CypherCapabilities = CypherCapabilities.Cypher40; + } + catch (AggregateException ex) + { + var wasUnwrapped = ex.TryUnwrap(out var unwrappedException); + operationCompletedArgs.Exception = wasUnwrapped ? unwrappedException : ex; + + StopTimerAndNotifyCompleted(); + + if (wasUnwrapped) + throw unwrappedException; + + throw; + } + catch (Exception e) + { + operationCompletedArgs.Exception = e; + StopTimerAndNotifyCompleted(); + throw; + } + + StopTimerAndNotifyCompleted(); + } + + public GraphClient(Uri rootUri, IHttpClient httpClient) + { + RootUri = rootUri; + JsonConverters = new List(); + JsonConverters.AddRange(DefaultJsonConverters); + JsonContractResolver = DefaultJsonContractResolver; + ExecutionConfiguration = new ExecutionConfiguration + { + HttpClient = httpClient, + UserAgent = $"Neo4jClient/{GetType().GetTypeInfo().Assembly.GetName().Version}", + UseJsonStreaming = true, + JsonConverters = JsonConverters, + Username = httpClient?.Username, + Password = httpClient?.Password + }; + UseJsonStreamingIfAvailable = true; + policyFactory = new ExecutionPolicyFactory(this); + } + + + // This is where the issue comes in - the 'Cypher' endpoint doesn't exist on a 4.x db - so + // + private Uri BuildUri(string relativeUri) + { + var baseUri = RootUri; + if (!RootUri.AbsoluteUri.EndsWith("/")) + baseUri = new Uri(RootUri.AbsoluteUri + "/"); + + if (relativeUri.StartsWith("/")) + relativeUri = relativeUri.Substring(1); + + return new Uri(baseUri, relativeUri); + } + + private Uri BuildUri(string relativeUri, string database, bool supportsMultipleTenancy, string end) //todo bad name + { + var baseUri = RootUri; + if (!RootUri.AbsoluteUri.EndsWith("/")) + baseUri = new Uri(RootUri.AbsoluteUri + "/"); + + if (supportsMultipleTenancy && relativeUri.Contains("{databaseName}")) //TODO Const + { + if (string.IsNullOrWhiteSpace(database)) + database = DefaultDatabase; //TODO Const + + relativeUri = relativeUri.Replace("{databaseName}", database); + } + + if (relativeUri.StartsWith("/")) + relativeUri = relativeUri.Substring(1); + if (!string.IsNullOrWhiteSpace(end)) + { + if (end.StartsWith("/")) + end = end.Substring(1); + relativeUri += $"/{end}"; + } + + return new Uri(baseUri, relativeUri); + } + + + private string SerializeAsJson(object contents) + { + return Serializer.Serialize(contents); + } + + public virtual bool IsConnected => RootApiResponse != null; + + + CustomJsonSerializer BuildSerializer() + { + return new CustomJsonSerializer { JsonConverters = JsonConverters, JsonContractResolver = JsonContractResolver }; + } + + public ISerializer Serializer => new CustomJsonSerializer { JsonConverters = JsonConverters, JsonContractResolver = JsonContractResolver }; + + private static string GetLastPathSegment(string uri) + { + var path = new Uri(uri).AbsolutePath; + return path + .Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) + .LastOrDefault(); + } + + public ICypherFluentQuery Cypher => new CypherFluentQuery(this); + + /// + public string DefaultDatabase + { + get => defaultDatabase; + set => defaultDatabase = value?.ToLowerInvariant() ?? "neo4j"; + } + + public Version ServerVersion + { + get + { + CheckRoot(); + return RootApiResponse.Version; + } + } + + public Uri RootEndpoint + { + get + { + CheckRoot(); + return BuildUri(""); + } + } + + public Uri TransactionEndpoint + { + get + { + CheckRoot(); + return BuildUri(RootApiResponse.Transaction); + } + } + + public List JsonConverters { get; } + + private void CheckTransactionEnvironmentWithPolicy(IExecutionPolicy policy) + { + bool inTransaction = InTransaction; + + if (inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Denied) + { + throw new InvalidOperationException("Cannot be done inside a transaction scope."); + } + + if (!inTransaction && policy.TransactionExecutionPolicy == TransactionExecutionPolicy.Required) + { + throw new InvalidOperationException("Cannot be done outside a transaction scope."); + } + } + + public ITransaction BeginTransaction() + { + return BeginTransaction((IEnumerable) null); + } + + public ITransaction BeginTransaction(string bookmark) + { + return BeginTransaction(new List {bookmark}); + } + + public ITransaction BeginTransaction(IEnumerable bookmarks) + { + return BeginTransaction(TransactionScopeOption.Join, bookmarks, DefaultDatabase); + } + + public ITransaction BeginTransaction(TransactionScopeOption scopeOption) + { + return BeginTransaction(scopeOption, null, DefaultDatabase); + } + + public ITransaction BeginTransaction(TransactionScopeOption scopeOption, string bookmark) + { + return BeginTransaction(scopeOption, new List{bookmark}, DefaultDatabase); + } + + public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmark) + { + return BeginTransaction(scopeOption, bookmark, DefaultDatabase); + } + + public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmarks, string database) + { + CheckRoot(); + if (transactionManager == null) + { + throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); + } + + return transactionManager.BeginTransaction(scopeOption, bookmarks, database); + } + + public ITransaction Transaction => transactionManager?.CurrentTransaction; + + public bool InTransaction => transactionManager != null && transactionManager.InTransaction; + + public void EndTransaction() + { + if (transactionManager == null) + { + throw new NotSupportedException("HTTP Transactions are only supported on Neo4j 2.0 and newer."); + } + transactionManager.EndTransaction(); + } + + public CypherCapabilities CypherCapabilities { get; private set; } = CypherCapabilities.Default; + + private async Task PrepareCypherRequest(CypherQuery query, IExecutionPolicy policy) + { + if (InTransaction) + { + var response = await transactionManager + .EnqueueCypherRequest($"The query was: {query.QueryText}", this, query) + .ConfigureAwait(false); + + var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, true); + return new CypherPartialResult + { + DeserializationContext = deserializer.CheckForErrorsInTransactionResponse(await response.Content.ReadAsStringAsync().ConfigureAwait(false)), + ResponseObject = response + }; + } + + int? maxExecutionTime = null; + NameValueCollection customHeaders = null; + if (query != null) + { + maxExecutionTime = query.MaxExecutionTime; + customHeaders = query.CustomHeaders; + } + + return await Request.With(ExecutionConfiguration, customHeaders, maxExecutionTime) + .Post(policy.BaseEndpoint(query?.Database, true)) + .WithJsonContent(policy.SerializeRequest(query)) + .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created) + .ExecuteAsync(response => new CypherPartialResult + { + ResponseObject = response + }).ConfigureAwait(false); + } + + async Task> IRawGraphClient.ExecuteGetCypherResultsAsync(CypherQuery query) + { + var context = ExecutionContext.Begin(this); + List results; + QueryStats stats = null; + try + { + bool inTransaction = InTransaction; + + var response = await PrepareCypherRequest(query, context.Policy).ConfigureAwait(false); + var deserializer = new CypherJsonDeserializer(this, query.ResultMode, query.ResultFormat, inTransaction); + if (inTransaction) + { + response.DeserializationContext.DeserializationContext.JsonContractResolver = query.JsonContractResolver; + results = deserializer.DeserializeFromTransactionPartialContext(response.DeserializationContext, true).ToList(); + + } + else + { + + results = deserializer.Deserialize(await response.ResponseObject.Content.ReadAsStringAsync().ConfigureAwait(false), true).ToList(); + + } + if (query.IncludeQueryStats) + { + var responseString = await response.ResponseObject.Content.ReadAsStringAsync().ConfigureAwait(false); + var statsContainer = JsonConvert.DeserializeObject(await response.ResponseObject.Content.ReadAsStringAsync().ConfigureAwait(false)); + if (statsContainer != null) + stats = statsContainer?.Results?.FirstOrDefault()?.Stats; + } + } + catch (AggregateException aggregateException) + { + context.Complete(query, aggregateException.TryUnwrap(out var unwrappedException) ? unwrappedException : aggregateException); + throw; + } + catch (Exception e) + { + context.Complete(query, e); + throw; + } + + context.Complete(query, results.Count, stats); + return results; + } + + async Task IRawGraphClient.ExecuteCypherAsync(CypherQuery query) + { + var context = ExecutionContext.Begin(this); + + CypherPartialResult response; + try + { + response = await PrepareCypherRequest(query, context.Policy).ConfigureAwait(false); + } + catch (Exception e) + { + if (InTransaction) + ExecutionConfiguration.HasErrors = true; + + context.Complete(query, e); + throw; + } + context.Policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(response.ResponseObject), null); + QueryStats stats = null; + if (response.ResponseObject?.Content != null) + { + var responseString = await response.ResponseObject.Content.ReadAsStringAsync(); + var errors = JsonConvert.DeserializeObject(responseString); + if(errors.Errors.Any()) + throw new ClientException(errors.Errors.First().Code, errors.Errors.First().Message); + + if (query.IncludeQueryStats) + { + var statsContainer = JsonConvert.DeserializeObject(responseString); + if (statsContainer != null) + stats = statsContainer?.Results?.FirstOrDefault()?.Stats; + } + } + + context.Complete(query, stats); + } + private class QueryStatsContainer + { + [JsonProperty("results")] + public IList Results { get; set; } + } + + private class ResultsContainer + { + [JsonProperty("stats")] + public QueryStats Stats { get; set; } + } + + private class ErrorsContainer + { + public IList Errors { get; set; } + } + + private class Error + { + [JsonProperty("code")] + public string Code { get; set; } + [JsonProperty("message")] + public string Message { get; set; } + } + private void CheckRoot() + { + if (RootApiResponse == null) + throw new InvalidOperationException("The graph client is not connected to the server. Call the Connect method first."); + } + + public event OperationCompletedEventHandler OperationCompleted; + + protected void OnOperationCompleted(OperationCompletedEventArgs args) + { + OperationCompleted?.Invoke(this, args); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposing) + return; + + transactionManager?.Dispose(); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public DefaultContractResolver JsonContractResolver { get; set; } + public Uri GetTransactionEndpoint(string database, bool autoCommit = false) + { + CheckRoot(); + var uri = BuildUri(RootApiResponse.Transaction, database, RootApiResponse.Version.Major >= 4, autoCommit ? "commit" : ""); + return uri; + } + + public ITransactionManager TransactionManager => transactionManager; + +#region ExecutionContext class + private class ExecutionContext + { + private GraphClient owner; + + private readonly Stopwatch stopwatch; + + public IExecutionPolicy Policy { get; set; } + public static bool HasErrors { get; set; } + + private ExecutionContext() + { + stopwatch = Stopwatch.StartNew(); + } + + public static ExecutionContext Begin(GraphClient owner) + { + owner.CheckRoot(); + var policy = owner.policyFactory.GetPolicy(PolicyType.Cypher); + + owner.CheckTransactionEnvironmentWithPolicy(policy); + + var executionContext = new ExecutionContext + { + owner = owner, + Policy = policy + }; + + return executionContext; + } + + public void Complete(CypherQuery query, QueryStats stats) + { + Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, 0, null, queryStats:stats); + } + + public void Complete(CypherQuery query) + { + // only parse the events when there's an event handler + Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, 0, null); + } + + public void Complete(CypherQuery query, int resultsCount, QueryStats stats = null) + { + // only parse the events when there's an event handler + Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, resultsCount, null, query.CustomHeaders, queryStats:stats); + } + + public void Complete(CypherQuery query, Exception exception) + { + // only parse the events when there's an event handler + Complete(owner.OperationCompleted != null ? query.DebugQueryText : string.Empty, -1, exception); + } + + private void Complete(string queryText, int resultsCount = -1, Exception exception = null, NameValueCollection customHeaders = null, int? maxExecutionTime = null, QueryStats queryStats = null) + { + var args = new OperationCompletedEventArgs + { + QueryText = queryText, + ResourcesReturned = resultsCount, + TimeTaken = stopwatch.Elapsed, + Exception = exception, + CustomHeaders = customHeaders, + MaxExecutionTime = maxExecutionTime, + QueryStats = queryStats + }; + + owner.OnOperationCompleted(args); + } + } + +#endregion + } +} diff --git a/Neo4jClient/GraphClientExtensions.cs b/Neo4jClient/GraphClientExtensions.cs new file mode 100644 index 000000000..7e38feca8 --- /dev/null +++ b/Neo4jClient/GraphClientExtensions.cs @@ -0,0 +1,14 @@ +// using System.Linq; +// using System.Threading.Tasks; +// +// namespace Neo4jClient +// { +// public static class GraphClientExtensions +// { +// public static Task> CreateAsync(this IGraphClient graphClient, TNode node, params IRelationshipAllowingParticipantNode[] relationships) +// where TNode : class +// { +// return graphClient.CreateAsync(node, relationships, Enumerable.Empty()); +// } +// } +// } \ No newline at end of file diff --git a/Neo4jClient.Shared/GraphClientFactory.cs b/Neo4jClient/GraphClientFactory.cs similarity index 72% rename from Neo4jClient.Shared/GraphClientFactory.cs rename to Neo4jClient/GraphClientFactory.cs index 6f1b8051b..fea8923bd 100644 --- a/Neo4jClient.Shared/GraphClientFactory.cs +++ b/Neo4jClient/GraphClientFactory.cs @@ -1,4 +1,5 @@ using System; +using System.Threading.Tasks; using Neo4jClient.Execution; namespace Neo4jClient @@ -15,23 +16,23 @@ public GraphClientFactory(NeoServerConfiguration configuration) _configuration = configuration; } - public IGraphClient Create() + public async Task CreateAsync() { var client = new GraphClient( _configuration.RootUri, _configuration.Username, _configuration.Password); - client.Connect(_configuration); + await client.ConnectAsync(_configuration).ConfigureAwait(false); return client; } - public IGraphClient Create(IHttpClient httpClient) + public async Task CreateAsync(IHttpClient httpClient) { var client = new GraphClient(_configuration.RootUri, httpClient); - client.Connect(_configuration); + await client.ConnectAsync(_configuration).ConfigureAwait(false); return client; } diff --git a/Neo4jClient.Shared/HttpClient.cs b/Neo4jClient/HttpClient.cs similarity index 100% rename from Neo4jClient.Shared/HttpClient.cs rename to Neo4jClient/HttpClient.cs diff --git a/Neo4jClient/HttpContentExtensions.cs b/Neo4jClient/HttpContentExtensions.cs new file mode 100644 index 000000000..4eef8bf58 --- /dev/null +++ b/Neo4jClient/HttpContentExtensions.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Neo4jClient.Serialization; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Neo4jClient +{ + internal static class HttpContentExtensions + { + public static Task ReadAsStringAsync(this HttpContent content) + { + return content.ReadAsStringAsync(); + } + + public static async Task ReadAsJsonAsync(this HttpContent content, IEnumerable jsonConverters, DefaultContractResolver resolver) + where T : new() + { + var stringContent = await content.ReadAsStringAsync().ConfigureAwait(false); + return new CustomJsonDeserializer(jsonConverters, resolver:resolver).Deserialize(stringContent); + } + + public static Task ReadAsJsonAsync(this HttpContent content, IEnumerable jsonConverters) where T : new() + { + return content.ReadAsJsonAsync(jsonConverters, null); + } + } +} diff --git a/Neo4jClient.Shared/IAttachedReference.cs b/Neo4jClient/IAttachedReference.cs similarity index 100% rename from Neo4jClient.Shared/IAttachedReference.cs rename to Neo4jClient/IAttachedReference.cs diff --git a/Neo4jClient.Shared/IBoltGraphClient.cs b/Neo4jClient/IBoltGraphClient.cs similarity index 100% rename from Neo4jClient.Shared/IBoltGraphClient.cs rename to Neo4jClient/IBoltGraphClient.cs diff --git a/Neo4jClient.Shared/ICypherGraphClient.cs b/Neo4jClient/ICypherGraphClient.cs similarity index 100% rename from Neo4jClient.Shared/ICypherGraphClient.cs rename to Neo4jClient/ICypherGraphClient.cs diff --git a/Neo4jClient/IGraphClient.cs b/Neo4jClient/IGraphClient.cs new file mode 100644 index 000000000..a7c3f78ab --- /dev/null +++ b/Neo4jClient/IGraphClient.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Neo4jClient.ApiModels; +using Neo4jClient.Cypher; +using Neo4jClient.Execution; +using Neo4jClient.Serialization; +using Neo4jClient.Transactions; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Neo4jClient +{ + public interface IGraphClient : ICypherGraphClient + { + event OperationCompletedEventHandler OperationCompleted; + + /// + /// Sets the default database to use. You can still override with the method on a per query basis. + /// + /// The default if this is not set, is "neo4j" + string DefaultDatabase { get; set; } + + // ReSharper disable once InconsistentNaming + CypherCapabilities CypherCapabilities { get; } + + Uri RootEndpoint { get; } + + Version ServerVersion { get; } + + Uri TransactionEndpoint { get; } + + ISerializer Serializer { get; } + + ExecutionConfiguration ExecutionConfiguration { get; } + + bool IsConnected { get; } + + Task ConnectAsync(NeoServerConfiguration configuration = null); + + List JsonConverters { get; } + DefaultContractResolver JsonContractResolver { get; set; } + Uri GetTransactionEndpoint(string database, bool autoCommit); + ITransactionalGraphClient Tx { get; } + } +} diff --git a/Neo4jClient/IGraphClientFactory.cs b/Neo4jClient/IGraphClientFactory.cs new file mode 100644 index 000000000..bfd066988 --- /dev/null +++ b/Neo4jClient/IGraphClientFactory.cs @@ -0,0 +1,11 @@ +using System.Threading.Tasks; +using Neo4jClient.Execution; + +namespace Neo4jClient +{ + public interface IGraphClientFactory + { + Task CreateAsync(); + Task CreateAsync(IHttpClient client); + } +} diff --git a/Neo4jClient.Shared/IHasNodeReference.cs b/Neo4jClient/IHasNodeReference.cs similarity index 100% rename from Neo4jClient.Shared/IHasNodeReference.cs rename to Neo4jClient/IHasNodeReference.cs diff --git a/Neo4jClient.Shared/IRawGraphClient.cs b/Neo4jClient/IRawGraphClient.cs similarity index 70% rename from Neo4jClient.Shared/IRawGraphClient.cs rename to Neo4jClient/IRawGraphClient.cs index 0de499af7..eee6dc2b3 100644 --- a/Neo4jClient.Shared/IRawGraphClient.cs +++ b/Neo4jClient/IRawGraphClient.cs @@ -12,10 +12,8 @@ namespace Neo4jClient /// public interface IRawGraphClient : IGraphClient { - IEnumerable ExecuteGetCypherResults(CypherQuery query); Task> ExecuteGetCypherResultsAsync(CypherQuery query); - void ExecuteCypher(CypherQuery query); - void ExecuteMultipleCypherQueriesInTransaction(IEnumerable queries, NameValueCollection customHeaders = null); Task ExecuteCypherAsync(CypherQuery query); + bool InTransaction { get; } } } diff --git a/Neo4jClient.Shared/IRelationshipAllowingParticipantNode`TNode.cs b/Neo4jClient/IRelationshipAllowingParticipantNode`TNode.cs similarity index 100% rename from Neo4jClient.Shared/IRelationshipAllowingParticipantNode`TNode.cs rename to Neo4jClient/IRelationshipAllowingParticipantNode`TNode.cs diff --git a/Neo4jClient.Shared/IRelationshipAllowingSourceNode`TNode.cs b/Neo4jClient/IRelationshipAllowingSourceNode`TNode.cs similarity index 100% rename from Neo4jClient.Shared/IRelationshipAllowingSourceNode`TNode.cs rename to Neo4jClient/IRelationshipAllowingSourceNode`TNode.cs diff --git a/Neo4jClient.Shared/IRelationshipAllowingTargetNode`TNode.cs b/Neo4jClient/IRelationshipAllowingTargetNode`TNode.cs similarity index 100% rename from Neo4jClient.Shared/IRelationshipAllowingTargetNode`TNode.cs rename to Neo4jClient/IRelationshipAllowingTargetNode`TNode.cs diff --git a/Neo4jClient.Shared/ITypedNodeReference.cs b/Neo4jClient/ITypedNodeReference.cs similarity index 100% rename from Neo4jClient.Shared/ITypedNodeReference.cs rename to Neo4jClient/ITypedNodeReference.cs diff --git a/Neo4jClient.Shared/IndexConfiguration.cs b/Neo4jClient/IndexConfiguration.cs similarity index 100% rename from Neo4jClient.Shared/IndexConfiguration.cs rename to Neo4jClient/IndexConfiguration.cs diff --git a/Neo4jClient.Shared/IndexEntry.cs b/Neo4jClient/IndexEntry.cs similarity index 100% rename from Neo4jClient.Shared/IndexEntry.cs rename to Neo4jClient/IndexEntry.cs diff --git a/Neo4jClient.Shared/IndexFor.cs b/Neo4jClient/IndexFor.cs similarity index 100% rename from Neo4jClient.Shared/IndexFor.cs rename to Neo4jClient/IndexFor.cs diff --git a/Neo4jClient.Shared/IndexMetaData.cs b/Neo4jClient/IndexMetaData.cs similarity index 100% rename from Neo4jClient.Shared/IndexMetaData.cs rename to Neo4jClient/IndexMetaData.cs diff --git a/Neo4jClient.Shared/IndexProvider.cs b/Neo4jClient/IndexProvider.cs similarity index 100% rename from Neo4jClient.Shared/IndexProvider.cs rename to Neo4jClient/IndexProvider.cs diff --git a/Neo4jClient.Shared/IndexType.cs b/Neo4jClient/IndexType.cs similarity index 100% rename from Neo4jClient.Shared/IndexType.cs rename to Neo4jClient/IndexType.cs diff --git a/Neo4jClient.Shared/Indexing.cd b/Neo4jClient/Indexing.cd similarity index 100% rename from Neo4jClient.Shared/Indexing.cd rename to Neo4jClient/Indexing.cd diff --git a/Neo4jClient.Shared/JTokenExtensions.cs b/Neo4jClient/JTokenExtensions.cs similarity index 100% rename from Neo4jClient.Shared/JTokenExtensions.cs rename to Neo4jClient/JTokenExtensions.cs diff --git a/Neo4jClient/NameValueCollection.cs b/Neo4jClient/NameValueCollection.cs deleted file mode 100644 index 136269a96..000000000 --- a/Neo4jClient/NameValueCollection.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.Linq; - -namespace Neo4jClient -{ - public class NameValueCollection - { - private readonly IDictionary contents = new Dictionary(); - - public string[] AllKeys => contents.Keys.ToArray(); - public long Count => contents.Count; - - public string Get(string customHeaderKey) - { - return contents[customHeaderKey]; - } - - public void Add(string headerName, string headerValue) - { - contents.Add(headerName, headerValue); - } - } -} \ No newline at end of file diff --git a/Neo4jClient/Neo4jClient.Pcl.csproj.DotSettings b/Neo4jClient/Neo4jClient.Pcl.csproj.DotSettings deleted file mode 100644 index 662f95686..000000000 --- a/Neo4jClient/Neo4jClient.Pcl.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp50 \ No newline at end of file diff --git a/Neo4jClient/Neo4jClient.Pcl.nuget.targets b/Neo4jClient/Neo4jClient.Pcl.nuget.targets deleted file mode 100644 index 07f3c7988..000000000 --- a/Neo4jClient/Neo4jClient.Pcl.nuget.targets +++ /dev/null @@ -1,9 +0,0 @@ - - - - $(UserProfile)\.nuget\packages\ - - - - - \ No newline at end of file diff --git a/Neo4jClient.Shared/Neo4jClient.Shared.v3.ncrunchproject b/Neo4jClient/Neo4jClient.Shared.v3.ncrunchproject similarity index 100% rename from Neo4jClient.Shared/Neo4jClient.Shared.v3.ncrunchproject rename to Neo4jClient/Neo4jClient.Shared.v3.ncrunchproject diff --git a/Neo4jClient/Neo4jClient.csproj b/Neo4jClient/Neo4jClient.csproj index 8d7881bdc..b09befb33 100644 --- a/Neo4jClient/Neo4jClient.csproj +++ b/Neo4jClient/Neo4jClient.csproj @@ -1,81 +1,49 @@ - - - - - 14.0 - Debug - AnyCPU - {E568B54B-F463-4346-818E-9EC8348C6470} - Library - Properties - Neo4jClient - Neo4jClient - en-US - 512 - {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - - - v5.0 - - - true - full - false - bin\Debug\ - TRACE;DEBUG;NETSTANDARD1_3 - prompt - 4 - bin\Debug\Neo4jClient.XML - 1591 - - - pdbonly - true - bin\Release\ - TRACE;NET46 - prompt - 4 - bin\Release\Neo4jClient.xml - - - true - bin\Full - Debug\ - DEBUG;TRACE - true - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - true - bin\Portable - Debug\ - DEBUG;TRACE - true - full - AnyCPU - prompt - MinimumRecommendedRules.ruleset - - - - - - - - - - - - - - - - - \ No newline at end of file + + + + netstandard2.0 + + 2020 Readify, Tatham Oddie, Chris Skardon + Readify, Tatham Oddie, Chris Skardon + https://github.com/Readify/Neo4jClient + LICENSE + A .NET client for neo4j: an open source, transactional graph database. It's pretty awesome. + neo4j, nosql, cypher, bolt, graph + 4.0.4 + + + + 4.0.0 + https://github.com/Readify/neo4jclient + git + true + + + + + + + + + + + + + + + + + + <_Parameter1>$(MSBuildProjectName).Tests + + + + + + True + + + + + + diff --git a/Neo4jClient/Neo4jClient.csproj.DotSettings b/Neo4jClient/Neo4jClient.csproj.DotSettings deleted file mode 100644 index c54c126d2..000000000 --- a/Neo4jClient/Neo4jClient.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - CSharp70 \ No newline at end of file diff --git a/Neo4jClient/Neo4jClient.v2.ncrunchproject b/Neo4jClient/Neo4jClient.v2.ncrunchproject deleted file mode 100644 index 90339be16..000000000 --- a/Neo4jClient/Neo4jClient.v2.ncrunchproject +++ /dev/null @@ -1,29 +0,0 @@ - - true - 1000 - true - false - false - true - false - false - false - false - false - true - true - false - true - true - true - 60000 - - - - AutoDetect - STA - x86 - - - - \ No newline at end of file diff --git a/Neo4jClient/Neo4jClient.v3.ncrunchproject b/Neo4jClient/Neo4jClient.v3.ncrunchproject deleted file mode 100644 index 5315a345e..000000000 --- a/Neo4jClient/Neo4jClient.v3.ncrunchproject +++ /dev/null @@ -1,10 +0,0 @@ - - - True - - - - True - True - - \ No newline at end of file diff --git a/Neo4jClient.Shared/Neo4jDriverExtensions.cs b/Neo4jClient/Neo4jDriverExtensions.cs similarity index 80% rename from Neo4jClient.Shared/Neo4jDriverExtensions.cs rename to Neo4jClient/Neo4jDriverExtensions.cs index ce1c964e2..423b081a6 100644 --- a/Neo4jClient.Shared/Neo4jDriverExtensions.cs +++ b/Neo4jClient/Neo4jDriverExtensions.cs @@ -4,9 +4,11 @@ using System.Linq; using System.Reflection; using System.Globalization; -using Neo4j.Driver.V1; +using System.Threading.Tasks; +using Neo4j.Driver; using Neo4jClient.Cypher; using Neo4jClient.Serialization; +using Neo4jClient.Transactions; using Newtonsoft.Json; namespace Neo4jClient @@ -17,16 +19,31 @@ public static class Neo4jDriverExtensions private const string DefaultTimeSpanFormat = @"d\.hh\:mm\:ss\.fffffff"; - public static IStatementResult Run(this ISession session, CypherQuery query, IGraphClient gc) + public static async Task Run(this IAsyncSession session, CypherQuery query, IGraphClient gc) { - return session.Run(query.QueryText, query.ToNeo4jDriverParameters(gc)); + return await session.RunAsync(query.QueryText, query.ToNeo4jDriverParameters(gc)); } - public static IStatementResult Run(this ITransaction session, CypherQuery query, IGraphClient gc) + public static async Task RunAsync(this IAsyncTransaction transaction, CypherQuery query, IGraphClient gc) { - return session.Run(query.QueryText, query.ToNeo4jDriverParameters(gc)); + return await transaction.RunAsync(query.QueryText, query.ToNeo4jDriverParameters(gc)); } + // public static IStatementResult Run(this ITransaction transaction, CypherQuery query, IGraphClient gc) + // { + // return transaction.Run(query.QueryText, query.ToNeo4jDriverParameters(gc)); + // } + // + // public static async Task RunAsync(this IAsyncSession session, CypherQuery query, IGraphClient gc) + // { + // return await session.RunAsync(query.QueryText, query.ToNeo4jDriverParameters(gc)).ConfigureAwait(false); + // } + // + // public static async Task RunAsync(this ITransaction session, CypherQuery query, IGraphClient gc) + // { + // return await session.RunAsync(query.QueryText, query.ToNeo4jDriverParameters(gc)).ConfigureAwait(false); + // } + // ReSharper disable once InconsistentNaming public static Dictionary ToNeo4jDriverParameters(this CypherQuery query, IGraphClient gc) { @@ -83,7 +100,7 @@ private static object Serialize(object value, IList converters, I private static object SerializeObject(Type type, object value, IList converters, IGraphClient gc) { return type.GetProperties(BindingFlags.Instance | BindingFlags.Public) - .Where(pi => !(pi.GetIndexParameters().Any() || pi.IsDefined(typeof(JsonIgnoreAttribute)))) + .Where(pi => !(pi.GetIndexParameters().Any() || pi.IsDefined(typeof(JsonIgnoreAttribute)) || pi.IsDefined(typeof(Neo4jIgnoreAttribute)))) .ToDictionary(pi => pi.Name, pi => Serialize(pi.GetValue(value), converters, gc, pi.CustomAttributes)); } diff --git a/Neo4jClient.Shared/NeoException.cs b/Neo4jClient/NeoException.cs similarity index 100% rename from Neo4jClient.Shared/NeoException.cs rename to Neo4jClient/NeoException.cs diff --git a/Neo4jClient/NeoServerConfiguration.cs b/Neo4jClient/NeoServerConfiguration.cs new file mode 100644 index 000000000..c134bac7e --- /dev/null +++ b/Neo4jClient/NeoServerConfiguration.cs @@ -0,0 +1,88 @@ +using System; +using System.Net; +using System.Reflection; +using System.Threading.Tasks; +using Neo4j.Driver; +using Neo4jClient.ApiModels; +using Neo4jClient.Execution; + +namespace Neo4jClient +{ + public class NeoServerConfiguration + { + internal RootApiResponse ApiConfig { get; private set; } + + internal Uri RootUri { get; private set; } + + internal EncryptionLevel? EncryptionLevel { get; } + internal string Username { get; private set; } + internal string Password { get; private set; } + internal string Realm { get; private set; } + + private NeoServerConfiguration(RootApiResponse apiConfig) + { + ApiConfig = apiConfig; + } + + public static async Task GetConfigurationAsync(Uri rootUri, string username = null, string password = null, string realm = null, EncryptionLevel? encryptionLevel = null) + { + return await GetConfigurationAsync(rootUri, username, password, realm, null, null).ConfigureAwait(false); + } + + internal static async Task GetConfigurationAsync(Uri rootUri, string username, string password, string realm, EncryptionLevel? encryptionLevel, ExecutionConfiguration executionConfiguration) + { + if (executionConfiguration == null) + { + var httpClient = new HttpClientWrapper(username, password); + + executionConfiguration = new ExecutionConfiguration + { + HttpClient = httpClient, + UserAgent = $"Neo4jClient/{typeof(NeoServerConfiguration).GetTypeInfo().Assembly.GetName().Version}", + UseJsonStreaming = true, + JsonConverters = GraphClient.DefaultJsonConverters, + Username = username, + Password = password, + Realm = realm, + EncryptionLevel = encryptionLevel + }; + } + + if (!rootUri.AbsoluteUri.EndsWith("/")) + rootUri = new Uri(rootUri.AbsoluteUri + "/"); + + rootUri = new Uri(rootUri, ""); + + var result = await Request.With(executionConfiguration) + .Get(rootUri) + .WithExpectedStatusCodes(HttpStatusCode.OK) + .ParseAs() + .ExecuteAsync().ConfigureAwait(false); + + if (result == null) + { + throw new InvalidOperationException("Couldn't obtain server Root API configuration."); + } + + var rootUriWithoutUserInfo = rootUri; + if (!string.IsNullOrEmpty(rootUriWithoutUserInfo.UserInfo)) + { + rootUriWithoutUserInfo = new UriBuilder(rootUri.AbsoluteUri) + { + UserName = "", + Password = "" + }.Uri; + } + + result.TrimUriFromProperties(rootUriWithoutUserInfo.AbsoluteUri); + + return new NeoServerConfiguration(result) + { + RootUri = rootUri, + Username = username, + Password = password, + Realm = realm + }; + } + } +} diff --git a/Neo4jClient.Shared/NodeReference.cs b/Neo4jClient/NodeReference.cs similarity index 50% rename from Neo4jClient.Shared/NodeReference.cs rename to Neo4jClient/NodeReference.cs index 6956e8a84..a5b9a64e6 100644 --- a/Neo4jClient.Shared/NodeReference.cs +++ b/Neo4jClient/NodeReference.cs @@ -1,36 +1,36 @@ using System; -using System.Collections.Generic; using System.Diagnostics; -using Neo4jClient.Cypher; -using Neo4jClient.Gremlin; namespace Neo4jClient { - [DebuggerDisplay("Node {id}")] - public class NodeReference : IGremlinQuery, IAttachedReference + [DebuggerDisplay("Node {Id}")] + public class NodeReference : IAttachedReference { - readonly long id; - readonly IGraphClient client; + private readonly IGraphClient client; - public NodeReference(long id) : this(id, null) {} + public NodeReference(long id) : this(id, null) + { + } public NodeReference(long id, IGraphClient client) { - this.id = id; + this.Id = id; this.client = client; } - public long Id { get { return id; } } + public long Id { get; } public Type NodeType { get { var typedThis = this as ITypedNodeReference; - return typedThis == null ? null : typedThis.NodeType; + return typedThis?.NodeType; } } + IGraphClient IAttachedReference.Client => client; + public static implicit operator NodeReference(long nodeId) { return new NodeReference(nodeId); @@ -54,39 +54,12 @@ public override bool Equals(object obj) var other = obj as NodeReference; if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return other.id == id; + return other.Id == Id; } public override int GetHashCode() { - return id.GetHashCode(); - } - - IGraphClient IAttachedReference.Client - { - get { return client; } - } - - string IGremlinQuery.QueryText - { - get { return "g.v(p0)"; } - } - - IDictionary IGremlinQuery.QueryParameters - { - get { return new Dictionary {{"p0", Id}}; } - } - - IList IGremlinQuery.QueryDeclarations - { - get { return new List(); } - } - - public ICypherFluentQuery StartCypher(string identity) - { - var query = new CypherFluentQuery(client, true) - .Start(identity, this); - return query; + return Id.GetHashCode(); } } -} +} \ No newline at end of file diff --git a/Neo4jClient.Shared/NodeReference`TNode.cs b/Neo4jClient/NodeReference`TNode.cs similarity index 100% rename from Neo4jClient.Shared/NodeReference`TNode.cs rename to Neo4jClient/NodeReference`TNode.cs diff --git a/Neo4jClient/Node`TNode.cs b/Neo4jClient/Node`TNode.cs new file mode 100644 index 000000000..5a1ca2050 --- /dev/null +++ b/Neo4jClient/Node`TNode.cs @@ -0,0 +1,57 @@ +using System; + +namespace Neo4jClient +{ + public class Node : IHasNodeReference, IAttachedReference + { + public Node(TNode data, NodeReference reference) + { + if (reference == null) + throw new ArgumentNullException("reference"); + + this.Data = data; + this.Reference = reference; + } + + public Node(TNode data, long id, IGraphClient client) + { + this.Data = data; + Reference = new NodeReference(id, client); + } + + public NodeReference Reference { get; } + + public TNode Data { get; } + + + IGraphClient IAttachedReference.Client => ((IAttachedReference) Reference).Client; + + NodeReference IHasNodeReference.Reference => Reference; + + public static bool operator ==(Node lhs, Node rhs) + { + if (ReferenceEquals(lhs, null) && ReferenceEquals(rhs, null)) + return true; + + return !ReferenceEquals(lhs, null) && lhs.Equals(rhs); + } + + public static bool operator !=(Node lhs, Node rhs) + { + return !(lhs == rhs); + } + + public override bool Equals(object obj) + { + var other = obj as Node; + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return other.Reference == Reference; + } + + public override int GetHashCode() + { + return Reference.Id.GetHashCode(); + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Shared/OperationCompletedEventHandler.cs b/Neo4jClient/OperationCompletedEventHandler.cs similarity index 62% rename from Neo4jClient.Shared/OperationCompletedEventHandler.cs rename to Neo4jClient/OperationCompletedEventHandler.cs index 3e732f1b9..db98b311d 100644 --- a/Neo4jClient.Shared/OperationCompletedEventHandler.cs +++ b/Neo4jClient/OperationCompletedEventHandler.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using Neo4j.Driver; +using Neo4jClient.ApiModels.Cypher; namespace Neo4jClient { @@ -8,20 +10,25 @@ namespace Neo4jClient public class OperationCompletedEventArgs : EventArgs { + public string Database { get; set; } public string Identifier { get; set; } - public string LastBookmark { get; set; } + public Bookmark LastBookmark { get; set; } public string QueryText { get; set; } public int ResourcesReturned { get; set; } public TimeSpan TimeTaken { get; set; } public Exception Exception { get; set; } - public bool HasException { get { return Exception != null; } } + public bool HasException => Exception != null; public int? MaxExecutionTime { get; set; } public NameValueCollection CustomHeaders { get; set; } - public IEnumerable BookmarksUsed { get; set; } + public IEnumerable BookmarksUsed { get; set; } + public QueryStats QueryStats { get; set; } + + /// This is only set with the . + public Config ConfigUsed { get; set; } public override string ToString() { return $"HasException={HasException}, QueryText={QueryText}"; } } -} +} \ No newline at end of file diff --git a/Neo4jClient.Shared/OrphanedTransactionException.cs b/Neo4jClient/OrphanedTransactionException.cs similarity index 100% rename from Neo4jClient.Shared/OrphanedTransactionException.cs rename to Neo4jClient/OrphanedTransactionException.cs diff --git a/Neo4jClient/Properties/AssemblyInfo.cs b/Neo4jClient/Properties/AssemblyInfo.cs deleted file mode 100644 index 30a3619a7..000000000 --- a/Neo4jClient/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Resources; -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("Neo4jClient.Pcl")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("Neo4jClient.Pcl")] -[assembly: AssemblyCopyright("Copyright © 2016")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] -[assembly: NeutralResourcesLanguage("en")] - -// 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")] -[assembly: InternalsVisibleTo("Neo4jClient.Tests.Core")] diff --git a/Neo4jClient.Shared/Relationship.cs b/Neo4jClient/Relationship.cs similarity index 100% rename from Neo4jClient.Shared/Relationship.cs rename to Neo4jClient/Relationship.cs diff --git a/Neo4jClient.Shared/RelationshipDirection.cs b/Neo4jClient/RelationshipDirection.cs similarity index 100% rename from Neo4jClient.Shared/RelationshipDirection.cs rename to Neo4jClient/RelationshipDirection.cs diff --git a/Neo4jClient.Shared/RelationshipEnd.cs b/Neo4jClient/RelationshipEnd.cs similarity index 100% rename from Neo4jClient.Shared/RelationshipEnd.cs rename to Neo4jClient/RelationshipEnd.cs diff --git a/Neo4jClient.Shared/RelationshipInstance.cs b/Neo4jClient/RelationshipInstance.cs similarity index 100% rename from Neo4jClient.Shared/RelationshipInstance.cs rename to Neo4jClient/RelationshipInstance.cs diff --git a/Neo4jClient.Shared/RelationshipInstance`TData.cs b/Neo4jClient/RelationshipInstance`TData.cs similarity index 100% rename from Neo4jClient.Shared/RelationshipInstance`TData.cs rename to Neo4jClient/RelationshipInstance`TData.cs diff --git a/Neo4jClient.Shared/RelationshipReference.cs b/Neo4jClient/RelationshipReference.cs similarity index 76% rename from Neo4jClient.Shared/RelationshipReference.cs rename to Neo4jClient/RelationshipReference.cs index 211eccca7..46de2e018 100644 --- a/Neo4jClient.Shared/RelationshipReference.cs +++ b/Neo4jClient/RelationshipReference.cs @@ -1,11 +1,10 @@ using System.Collections.Generic; using System.Diagnostics; -using Neo4jClient.Gremlin; namespace Neo4jClient { [DebuggerDisplay("Relationship {id}")] - public class RelationshipReference : IGremlinQuery, IAttachedReference + public class RelationshipReference : IAttachedReference { readonly long id; readonly IGraphClient client; @@ -55,20 +54,5 @@ IGraphClient IAttachedReference.Client { get { return client; } } - - string IGremlinQuery.QueryText - { - get { return "g.e(p0)"; } - } - - IDictionary IGremlinQuery.QueryParameters - { - get { return new Dictionary { { "p0", Id }}; } - } - - IList IGremlinQuery.QueryDeclarations - { - get { return new List(); } - } } } diff --git a/Neo4jClient.Shared/RelationshipReference`TData.cs b/Neo4jClient/RelationshipReference`TData.cs similarity index 100% rename from Neo4jClient.Shared/RelationshipReference`TData.cs rename to Neo4jClient/RelationshipReference`TData.cs diff --git a/Neo4jClient.Shared/Relationship`TData.cs b/Neo4jClient/Relationship`TData.cs similarity index 100% rename from Neo4jClient.Shared/Relationship`TData.cs rename to Neo4jClient/Relationship`TData.cs diff --git a/Neo4jClient.Shared/Serialization/CommonDeserializerMethods.cs b/Neo4jClient/Serialization/CommonDeserializerMethods.cs similarity index 92% rename from Neo4jClient.Shared/Serialization/CommonDeserializerMethods.cs rename to Neo4jClient/Serialization/CommonDeserializerMethods.cs index 58c01b678..beeca55eb 100644 --- a/Neo4jClient.Shared/Serialization/CommonDeserializerMethods.cs +++ b/Neo4jClient/Serialization/CommonDeserializerMethods.cs @@ -16,7 +16,14 @@ class CommonDeserializerMethods static readonly Regex DateRegex = new Regex(@"/Date\([-]?\d+([+-]\d+)?\)/"); static readonly Regex DateTypeNameRegex = new Regex(@"(?<=(?['""])/)Date(?=\(.*?\)/\k)"); - public static string ReplaceAllDateInstacesWithNeoDates(string content) + public static string RemoveResultsFromJson(string content) + { + var root = JToken.Parse(content); + var output = root.SelectTokens("$.results[0]").Select(j => j.ToString(Formatting.None)).FirstOrDefault(); + return output; + } + + public static string ReplaceAllDateInstancesWithNeoDates(string content) { // Replace all /Date(1234+0200)/ instances with /NeoDate(1234+0200)/ return DateTypeNameRegex.Replace(content, "NeoDate"); @@ -24,8 +31,7 @@ public static string ReplaceAllDateInstacesWithNeoDates(string content) public static DateTimeOffset? ParseDateTimeOffset(JToken value) { - var jValue = value as JValue; - if (jValue != null) + if (value is JValue jValue) { if (jValue.Value == null) return null; @@ -43,12 +49,11 @@ public static string ReplaceAllDateInstacesWithNeoDates(string content) if (!DateRegex.IsMatch(rawValue)) { - DateTimeOffset parsed; - if (!DateTimeOffset.TryParse(rawValue, out parsed)) + if (!DateTimeOffset.TryParse(rawValue, out _)) return null; } - var text = string.Format("{{\"a\":\"{0}\"}}", rawValue); + var text = $"{{\"a\":\"{rawValue}\"}}"; var reader = new JsonTextReader(new StringReader(text)) {DateParseHandling = DateParseHandling.DateTimeOffset}; reader.Read(); // JsonToken.StartObject reader.Read(); // JsonToken.PropertyName @@ -66,14 +71,13 @@ public static string ReplaceAllDateInstacesWithNeoDates(string content) if (!DateRegex.IsMatch(rawValue)) { - DateTime parsed; - if (!DateTime.TryParse(rawValue, out parsed)) + if (!DateTime.TryParse(rawValue, out var parsed)) return null; return rawValue.EndsWith("Z", StringComparison.OrdinalIgnoreCase) ? parsed.ToUniversalTime() : parsed; } - var text = string.Format("{{\"a\":\"{0}\"}}", rawValue); + var text = $"{{\"a\":\"{rawValue}\"}}"; var reader = new JsonTextReader(new StringReader(text)); reader.Read(); // JsonToken.StartObject reader.Read(); // JsonToken.PropertyName @@ -87,8 +91,7 @@ public static object CoerceValue(DeserializationContext context, PropertyInfo pr var propertyType = propertyInfo.PropertyType; var typeInfo = propertyType.GetTypeInfo(); - object jsonConversionResult; - if (TryJsonConverters(context, propertyType, value, out jsonConversionResult)) + if (TryJsonConverters(context, propertyType, value, out var jsonConversionResult)) return jsonConversionResult; Type genericTypeDef = null; @@ -142,9 +145,7 @@ public static object CoerceValue(DeserializationContext context, PropertyInfo pr if (propertyType == typeof(DateTimeOffset)) { var dateTimeOffset = ParseDateTimeOffset(value); - if (dateTimeOffset.HasValue) - return dateTimeOffset.Value; - return null; + return dateTimeOffset; } if (propertyType == typeof(Decimal)) @@ -185,7 +186,7 @@ public static object CoerceValue(DeserializationContext context, PropertyInfo pr // only supports Dict() if (keyType != typeof (string)) { - throw new NotSupportedException("Value coersion only supports dictionaries with a key of type System.String"); + throw new NotSupportedException("Value coercion only supports dictionaries with a key of type System.String"); } var dict = BuildDictionary(context, propertyType, value.Children(), typeMappings, nestingLevel + 1); @@ -298,7 +299,7 @@ public static object CreateAndMap(DeserializationContext context, Type type, JTo catch (MissingMethodException ex) { throw new DeserializationException( - string.Format("We expected a default public constructor on {0} so that we could create instances of it to deserialize data into, however this constructor does not exist or is inaccessible.", type.Name), + $"We expected a default public constructor on {type.Name} so that we could create instances of it to deserialize data into, however this constructor does not exist or is inaccessible.", ex); } Map(context, instance, element, typeMappings, nestingLevel); @@ -309,8 +310,7 @@ public static object CreateAndMap(DeserializationContext context, Type type, JTo static bool TryJsonConverters(DeserializationContext context, Type type, JToken element, out object instance) { instance = null; - if (context.JsonConverters == null) return false; - var converter = context.JsonConverters.FirstOrDefault(c => c.CanConvert(type)); + var converter = context.JsonConverters?.FirstOrDefault(c => c.CanConvert(type)); if (converter == null) return false; using (var reader = element.CreateReader()) { diff --git a/Neo4jClient.Shared/Serialization/CustomJsonDeserializer.cs b/Neo4jClient/Serialization/CustomJsonDeserializer.cs similarity index 98% rename from Neo4jClient.Shared/Serialization/CustomJsonDeserializer.cs rename to Neo4jClient/Serialization/CustomJsonDeserializer.cs index 37cd91a20..788d28370 100644 --- a/Neo4jClient.Shared/Serialization/CustomJsonDeserializer.cs +++ b/Neo4jClient/Serialization/CustomJsonDeserializer.cs @@ -35,7 +35,7 @@ public CustomJsonDeserializer(IEnumerable jsonConverters, Culture JsonContractResolver = jsonResolver }; - content = CommonDeserializerMethods.ReplaceAllDateInstacesWithNeoDates(content); + content = CommonDeserializerMethods.ReplaceAllDateInstancesWithNeoDates(content); var reader = new JsonTextReader(new StringReader(content)) { diff --git a/Neo4jClient.Shared/Serialization/CustomJsonSerializer.cs b/Neo4jClient/Serialization/CustomJsonSerializer.cs similarity index 100% rename from Neo4jClient.Shared/Serialization/CustomJsonSerializer.cs rename to Neo4jClient/Serialization/CustomJsonSerializer.cs diff --git a/Neo4jClient.Shared/Serialization/CypherJsonDeserializer.cs b/Neo4jClient/Serialization/CypherJsonDeserializer.cs similarity index 81% rename from Neo4jClient.Shared/Serialization/CypherJsonDeserializer.cs rename to Neo4jClient/Serialization/CypherJsonDeserializer.cs index df88766c9..a6aee57c1 100644 --- a/Neo4jClient.Shared/Serialization/CypherJsonDeserializer.cs +++ b/Neo4jClient/Serialization/CypherJsonDeserializer.cs @@ -51,7 +51,7 @@ public CypherJsonDeserializer(IGraphClient client, CypherResultMode resultMode, } } - public IEnumerable Deserialize(string content) + public IEnumerable Deserialize(string content, bool isHttp) { try { @@ -61,7 +61,9 @@ public IEnumerable Deserialize(string content) JsonConverters = Enumerable.Reverse(client.JsonConverters ?? new List(0)).ToArray(), JsonContractResolver = client.JsonContractResolver }; - content = CommonDeserializerMethods.ReplaceAllDateInstacesWithNeoDates(content); + + if(isHttp) content = CommonDeserializerMethods.RemoveResultsFromJson(content); + content = CommonDeserializerMethods.ReplaceAllDateInstancesWithNeoDates(content); var reader = new JsonTextReader(new StringReader(content)) { @@ -72,8 +74,8 @@ public IEnumerable Deserialize(string content) // not much value to deferred execution here and we'd like to know // about any errors now return inTransaction - ? FullDeserializationFromTransactionResponse(reader, context).ToArray() - : DeserializeFromRoot(content, reader, context).ToArray(); + ? FullDeserializationFromTransactionResponse(reader, context, isHttp).ToArray() + : DeserializeFromRoot(content, reader, context, isHttp).ToArray(); } catch (Exception ex) { @@ -109,7 +111,7 @@ Include the full type definition of {0}. } } - IEnumerable DeserializeInternal(string content) + private IEnumerable DeserializeInternal(string content, bool isHttp) { var context = new DeserializationContext { @@ -117,7 +119,7 @@ IEnumerable DeserializeInternal(string content) JsonConverters = Enumerable.Reverse(client.JsonConverters ?? new List(0)).ToArray(), JsonContractResolver = client.JsonContractResolver }; - content = CommonDeserializerMethods.ReplaceAllDateInstacesWithNeoDates(content); + content = CommonDeserializerMethods.ReplaceAllDateInstancesWithNeoDates(content); var reader = new JsonTextReader(new StringReader(content)) { @@ -161,27 +163,25 @@ IEnumerable DeserializeInternal(string content) switch (resultMode) { - case CypherResultMode.Set: - return ParseInSingleColumnMode(context, root, columnNames, jsonTypeMappings.ToArray()); + case CypherResultMode.Set: return ParseInSingleColumnMode(context, root, columnNames, jsonTypeMappings.ToArray(), isHttp); case CypherResultMode.Projection: jsonTypeMappings.Add(new TypeMapping { - ShouldTriggerForPropertyType = (nestingLevel, type) => - nestingLevel == 0 && type.GetTypeInfo().IsClass, - DetermineTypeToParseJsonIntoBasedOnPropertyType = t => - typeof(NodeOrRelationshipApiResponse<>).MakeGenericType(new[] { t }), - MutationCallback = n => - n.GetType().GetProperty("Data").GetGetMethod().Invoke(n, new object[0]) + ShouldTriggerForPropertyType = (nestingLevel, type) => nestingLevel == 0 && type.GetTypeInfo().IsClass, + DetermineTypeToParseJsonIntoBasedOnPropertyType = t => typeof(NodeOrRelationshipApiResponse<>).MakeGenericType(new[] { t }), + MutationCallback = n => n.GetType().GetProperty("Data").GetGetMethod().Invoke(n, new object[0]) }); - return ParseInProjectionMode(context, root, columnNames, jsonTypeMappings.ToArray()); + return ParseInProjectionMode(context, root, columnNames, jsonTypeMappings.ToArray(), isHttp); default: - throw new NotSupportedException(string.Format("Unrecognised result mode of {0}.", resultMode)); + throw new NotSupportedException($"Unrecognised result mode of {resultMode}."); } } - IEnumerable DeserializeResultSet(JToken resultRoot, DeserializationContext context) + private IEnumerable DeserializeResultSet(JToken resultRoot, DeserializationContext context, bool isHttp) { - var columnsArray = (JArray)resultRoot["columns"]; + var columnsArray = isHttp ? (JArray)resultRoot.SelectToken("$.results[0].columns") : (JArray)resultRoot["columns"]; + if(columnsArray == null) //This is a hack prior to shifting the Bolt deserialization entirely away from this. + columnsArray = !isHttp ? (JArray)resultRoot.SelectToken("$.results[0].columns") : (JArray)resultRoot["columns"]; var columnNames = columnsArray .Children() .Select(c => c.AsString()) @@ -218,34 +218,32 @@ IEnumerable DeserializeResultSet(JToken resultRoot, DeserializationCont switch (resultMode) { case CypherResultMode.Set: - return ParseInSingleColumnMode(context, resultRoot, columnNames, jsonTypeMappings.ToArray()); + return ParseInSingleColumnMode(context, resultRoot, columnNames, jsonTypeMappings.ToArray(), isHttp); case CypherResultMode.Projection: // if we are in transaction and we have an object we dont need a mutation if (!inTransaction && !inBolt) { - jsonTypeMappings.Add(new TypeMapping - { - ShouldTriggerForPropertyType = (nestingLevel, type) => - nestingLevel == 0 && type.GetTypeInfo().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.GetTypeInfo().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()); + return ParseInProjectionMode(context, resultRoot, columnNames, jsonTypeMappings.ToArray(), isHttp); default: - throw new NotSupportedException(string.Format("Unrecognised result mode of {0}.", resultMode)); + throw new NotSupportedException($"Unrecognised result mode of {resultMode}."); } } private string GetStringPropertyFromObject(JObject obj, string propertyName) { - JToken propValue; - if (obj.TryGetValue(propertyName, out propValue)) - { + if (obj.TryGetValue(propertyName, out var propValue)) return (string)(propValue as JValue); - } + return null; } @@ -288,7 +286,7 @@ public PartialDeserializationContext CheckForErrorsInTransactionResponse(string Culture = culture, JsonConverters = Enumerable.Reverse(client.JsonConverters ?? new List(0)).ToArray() }; - content = CommonDeserializerMethods.ReplaceAllDateInstacesWithNeoDates(content); + content = CommonDeserializerMethods.ReplaceAllDateInstancesWithNeoDates(content); var reader = new JsonTextReader(new StringReader(content)) { @@ -341,7 +339,7 @@ private JToken GetRootResultInTransaction(JObject root) return results.FirstOrDefault(); } - public IEnumerable DeserializeFromTransactionPartialContext(PartialDeserializationContext context) + public IEnumerable DeserializeFromTransactionPartialContext(PartialDeserializationContext context, bool isHttp) { if (context.RootResult == null) { @@ -350,10 +348,10 @@ public IEnumerable DeserializeFromTransactionPartialContext(PartialDese 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); + return DeserializeResultSet(context.RootResult, context.DeserializationContext, isHttp); } - private IEnumerable FullDeserializationFromTransactionResponse(JsonTextReader reader, DeserializationContext context) + private IEnumerable FullDeserializationFromTransactionResponse(JsonTextReader reader, DeserializationContext context, bool isHttp) { var root = JToken.ReadFrom(reader).Root as JObject; @@ -368,21 +366,21 @@ private IEnumerable FullDeserializationFromTransactionResponse(JsonText This means no query was emitted, so a method that doesn't care about getting results should have been called." ); } - return DeserializeResultSet(resultSet, context); + return DeserializeResultSet(resultSet, context, isHttp); } - IEnumerable DeserializeFromRoot(string content, JsonTextReader reader, DeserializationContext context) + IEnumerable DeserializeFromRoot(string content, JsonTextReader reader, DeserializationContext context, bool isHttp) { var root = JToken.ReadFrom(reader).Root; if (!(root is JObject)) { throw new InvalidOperationException("Root expected to be a JSON object."); } - return DeserializeResultSet(root, context); + return DeserializeResultSet(root, context, isHttp); } // ReSharper disable UnusedParameter.Local - IEnumerable ParseInSingleColumnMode(DeserializationContext context, JToken root, string[] columnNames, TypeMapping[] jsonTypeMappings) + private IEnumerable ParseInSingleColumnMode(DeserializationContext context, JToken root, string[] columnNames, TypeMapping[] jsonTypeMappings, bool isHttp) // ReSharper restore UnusedParameter.Local { if (columnNames.Count() != 1) @@ -395,41 +393,37 @@ IEnumerable ParseInSingleColumnMode(DeserializationContext context, JTo var mapping = jsonTypeMappings.SingleOrDefault(m => m.ShouldTriggerForPropertyType(0, resultType)); var newType = mapping == null ? resultType : mapping.DetermineTypeToParseJsonIntoBasedOnPropertyType(resultType); - var dataArray = (JArray)root["data"]; + var dataArray = isHttp ? (JArray)root.SelectToken("$.results[0].data") : (JArray)root["data"]; + if(dataArray == null) //Hack prior to swapping out deserialization completely. + dataArray = !isHttp ? (JArray)root.SelectToken("$.results[0].data") : (JArray)root["data"]; var rows = dataArray.Children(); - var dataPropertyNameInTransaction = resultFormat == CypherResultFormat.Rest ? "rest" : "row"; + //var x = resultFormat == CypherResultFormat.Rest ? "rest" : "row"; + var dataPropertyNameInTransaction = "row"; var results = rows.Select(row => { - if (inTransaction) + if (inTransaction || isHttp) + //All HTTP messages are now in txs -- This is a temporary measure. { var rowObject = row as JObject; if (rowObject == null) - { - throw new InvalidOperationException( - "Expected the row to be a JSON object, but it wasn't."); - } - - JToken rowProperty; - if (!rowObject.TryGetValue(dataPropertyNameInTransaction, out rowProperty)) - { + throw new InvalidOperationException("Expected the row to be a JSON object, but it wasn't."); + + if (!rowObject.TryGetValue(dataPropertyNameInTransaction, out var 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."); + throw new InvalidOperationException("Expected the row to be a JSON array of values, but it wasn't."); } 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)); + throw new InvalidOperationException($"Expected the row to only have a single array value, but it had {rowAsArray.Count}."); return rowAsArray; } @@ -460,7 +454,7 @@ IEnumerable ParseInSingleColumnMode(DeserializationContext context, JTo return results; } - IEnumerable ParseInProjectionMode(DeserializationContext context, JToken root, string[] columnNames, TypeMapping[] jsonTypeMappings) + IEnumerable ParseInProjectionMode(DeserializationContext context, JToken root, string[] columnNames, TypeMapping[] jsonTypeMappings, bool isHttp) { var properties = typeof(TResult).GetProperties(); var propertiesDictionary = properties @@ -498,11 +492,8 @@ IEnumerable ParseInProjectionMode(DeserializationContext context, JToke { // wasn't able to build TResult via constructor var columnsWhichDontHaveSettablePropertiesCommaSeparated = string.Join(", ", columnsWhichDontHaveSettableProperties); - throw new ArgumentException(string.Format( - "The query response contains columns {0} however {1} does not contain publically settable properties to receive this data.", - columnsWhichDontHaveSettablePropertiesCommaSeparated, - typeof(TResult).FullName), - "columnNames"); + throw new ArgumentException($"The query response contains columns {columnsWhichDontHaveSettablePropertiesCommaSeparated} however {typeof(TResult).FullName} does not contain publicly settable properties to receive this data.", + nameof(columnNames)); } } else @@ -510,11 +501,17 @@ IEnumerable ParseInProjectionMode(DeserializationContext context, JToke getRow = token => ReadProjectionRowUsingProperties(context, token, propertiesDictionary, columnNames, jsonTypeMappings); } - var dataArray = (JArray)root["data"]; + // var dataArray = (JArray)root["data"]; + var dataArray = isHttp ? (JArray)root.SelectToken("$.results[0].data") : (JArray)root["data"]; + if (dataArray == null) //Hack prior to swapping out deserialization completely. + dataArray = !isHttp ? (JArray)root.SelectToken("$.results[0].data") : (JArray)root["data"]; + var rows = dataArray.Children(); - var dataPropertyNameInTransaction = resultFormat == CypherResultFormat.Rest ? "rest" : "row"; - return inTransaction ? rows.Select(row => row[dataPropertyNameInTransaction]).Select(getRow) : rows.Select(getRow); + //var dataPropertyNameInTransaction = resultFormat == CypherResultFormat.Rest ? "rest" : "row"; + var dataPropertyNameInTransaction = "row"; + + return inTransaction || isHttp ? rows.Select(row => row[dataPropertyNameInTransaction]).Select(getRow) : rows.Select(getRow); } TResult ReadProjectionRowUsingCtor( diff --git a/Neo4jClient.Shared/Serialization/DeserializationContext.cs b/Neo4jClient/Serialization/DeserializationContext.cs similarity index 100% rename from Neo4jClient.Shared/Serialization/DeserializationContext.cs rename to Neo4jClient/Serialization/DeserializationContext.cs diff --git a/Neo4jClient.Shared/Serialization/DeserializationException.cs b/Neo4jClient/Serialization/DeserializationException.cs similarity index 100% rename from Neo4jClient.Shared/Serialization/DeserializationException.cs rename to Neo4jClient/Serialization/DeserializationException.cs diff --git a/Neo4jClient.Shared/Serialization/EnumValueConverter.cs b/Neo4jClient/Serialization/EnumValueConverter.cs similarity index 100% rename from Neo4jClient.Shared/Serialization/EnumValueConverter.cs rename to Neo4jClient/Serialization/EnumValueConverter.cs diff --git a/Neo4jClient.Shared/Serialization/ICypherJsonDeserializer.cs b/Neo4jClient/Serialization/ICypherJsonDeserializer.cs similarity index 69% rename from Neo4jClient.Shared/Serialization/ICypherJsonDeserializer.cs rename to Neo4jClient/Serialization/ICypherJsonDeserializer.cs index 1b17c35a0..3fc277c97 100644 --- a/Neo4jClient.Shared/Serialization/ICypherJsonDeserializer.cs +++ b/Neo4jClient/Serialization/ICypherJsonDeserializer.cs @@ -5,7 +5,7 @@ namespace Neo4jClient.Serialization public interface ICypherJsonDeserializer { PartialDeserializationContext CheckForErrorsInTransactionResponse(string content); - IEnumerable Deserialize(string content); - IEnumerable DeserializeFromTransactionPartialContext(PartialDeserializationContext context); + IEnumerable Deserialize(string content, bool isHttp); + IEnumerable DeserializeFromTransactionPartialContext(PartialDeserializationContext context, bool isHttp); } } \ No newline at end of file diff --git a/Neo4jClient.Shared/Serialization/ISerializer.cs b/Neo4jClient/Serialization/ISerializer.cs similarity index 100% rename from Neo4jClient.Shared/Serialization/ISerializer.cs rename to Neo4jClient/Serialization/ISerializer.cs diff --git a/Neo4jClient/Serialization/Neo4jContractSerializer.cs b/Neo4jClient/Serialization/Neo4jContractSerializer.cs new file mode 100644 index 000000000..48cfe2a8a --- /dev/null +++ b/Neo4jClient/Serialization/Neo4jContractSerializer.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Neo4jClient.Serialization +{ + public class Neo4jContractResolver : DefaultContractResolver + { + private readonly Dictionary> renames; + + public Neo4jContractResolver() + { + renames = new Dictionary>(); + } + + protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) + { + var property = base.CreateProperty(member, memberSerialization); + + PropertyInfo prop = property.DeclaringType.GetProperty(property.PropertyName); + if (prop != null && prop.CustomAttributes.Any(a => a.AttributeType == typeof(Neo4jIgnoreAttribute))) + { + property.ShouldSerialize = i => false; + } + + if (IsRenamed(property.DeclaringType, property.PropertyName, out var newJsonPropertyName)) + property.PropertyName = newJsonPropertyName; + + return property; + } + + private bool IsRenamed(Type type, string jsonPropertyName, out string newJsonPropertyName) + { + if (renames.TryGetValue(type, out var dictionary) && + dictionary.TryGetValue(jsonPropertyName, out newJsonPropertyName)) + return true; + + newJsonPropertyName = null; + return false; + + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Shared/Serialization/NullableEnumValueConverter.cs b/Neo4jClient/Serialization/NullableEnumValueConverter.cs similarity index 100% rename from Neo4jClient.Shared/Serialization/NullableEnumValueConverter.cs rename to Neo4jClient/Serialization/NullableEnumValueConverter.cs diff --git a/Neo4jClient.Shared/Serialization/PartialDeserializationContext.cs b/Neo4jClient/Serialization/PartialDeserializationContext.cs similarity index 100% rename from Neo4jClient.Shared/Serialization/PartialDeserializationContext.cs rename to Neo4jClient/Serialization/PartialDeserializationContext.cs diff --git a/Neo4jClient.Shared/Serialization/TimeZoneInfoConverter.cs b/Neo4jClient/Serialization/TimeZoneInfoConverter.cs similarity index 100% rename from Neo4jClient.Shared/Serialization/TimeZoneInfoConverter.cs rename to Neo4jClient/Serialization/TimeZoneInfoConverter.cs diff --git a/Neo4jClient.Shared/Serialization/TypeConverterBasedJsonConverter.cs b/Neo4jClient/Serialization/TypeConverterBasedJsonConverter.cs similarity index 100% rename from Neo4jClient.Shared/Serialization/TypeConverterBasedJsonConverter.cs rename to Neo4jClient/Serialization/TypeConverterBasedJsonConverter.cs diff --git a/Neo4jClient.Shared/Serialization/TypeMapping.cs b/Neo4jClient/Serialization/TypeMapping.cs similarity index 100% rename from Neo4jClient.Shared/Serialization/TypeMapping.cs rename to Neo4jClient/Serialization/TypeMapping.cs diff --git a/Neo4jClient.Shared/Serialization/ZonedDateTimeConverter.cs b/Neo4jClient/Serialization/ZonedDateTimeConverter.cs similarity index 98% rename from Neo4jClient.Shared/Serialization/ZonedDateTimeConverter.cs rename to Neo4jClient/Serialization/ZonedDateTimeConverter.cs index 9f7d9e4c6..5f373f366 100644 --- a/Neo4jClient.Shared/Serialization/ZonedDateTimeConverter.cs +++ b/Neo4jClient/Serialization/ZonedDateTimeConverter.cs @@ -1,7 +1,7 @@ using System; using System.ComponentModel; using System.Diagnostics; -using Neo4j.Driver.V1; +using Neo4j.Driver; using Newtonsoft.Json; namespace Neo4jClient.Serialization diff --git a/Neo4jClient.Shared/StatementResultHelper.cs b/Neo4jClient/StatementResultHelper.cs similarity index 98% rename from Neo4jClient.Shared/StatementResultHelper.cs rename to Neo4jClient/StatementResultHelper.cs index 882f1fac8..eec906511 100644 --- a/Neo4jClient.Shared/StatementResultHelper.cs +++ b/Neo4jClient/StatementResultHelper.cs @@ -2,11 +2,12 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.ComponentModel; using System.Dynamic; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; -using Neo4j.Driver.V1; +using Neo4j.Driver; using Neo4jClient.ApiModels.Cypher; using Neo4jClient.Cypher; using Neo4jClient.Serialization; @@ -212,7 +213,7 @@ public static IEnumerable Deserialize(this IRecord record, ICypherJsonDese } var property = typeT.GetProperties().FirstOrDefault(p => p.Name == key); - var isClass = property != null && !property.PropertyType.IsAnonymous() && !property.PropertyType.IsPrimitive() && !property.PropertyType.IsArray; + var isClass = property != null && property.PropertyType.IsClass; data.Add(o.ToJsonString(convertMode == CypherResultMode.Set, record.Keys.Count > 1, false, isClass)); } @@ -237,7 +238,7 @@ public static IEnumerable Deserialize(this IRecord record, ICypherJsonDese throw new ArgumentOutOfRangeException(nameof(mode), mode, null); } - return deserializer.Deserialize(json); + return deserializer.Deserialize(json, false); } public static T Parse(this IRecord record, IGraphClient graphClient) @@ -576,6 +577,7 @@ public Neo4jClientRecord(IRecord record, string identifier) : new Dictionary {{identifier, record[identifier]}}; } + [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete("This should not be called internally.")] object IRecord.this[int index] => throw new NotImplementedException("This should not be called."); diff --git a/Neo4jClient/Transactions/Bolt/BoltNeo4jTransaction.cs b/Neo4jClient/Transactions/Bolt/BoltNeo4jTransaction.cs new file mode 100644 index 000000000..dd16331a2 --- /dev/null +++ b/Neo4jClient/Transactions/Bolt/BoltNeo4jTransaction.cs @@ -0,0 +1,132 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.Linq; +using System.Threading.Tasks; +using Neo4j.Driver; +using Neo4jClient.Extensions; + +namespace Neo4jClient.Transactions.Bolt +{ + internal class BoltNeo4jTransaction : ITransaction + { + internal readonly IAsyncTransaction DriverTransaction; + internal IAsyncSession Session { get; } + internal IList Bookmarks { get; set; } + public Guid Id { get; private set; } + + //TODO: Who uses this constructor?? + public BoltNeo4jTransaction(Version version, IDriver driver, IEnumerable bookmarks, string database, bool isWrite = true) + { + Database = database; + Bookmarks = bookmarks?.ToList(); + Session = driver.AsyncSession(version, database, isWrite, Bookmarks); + + var tx = Session.BeginTransactionAsync(); + tx.Wait(); + DriverTransaction = tx.Result; + IsOpen = true; + Id = Guid.NewGuid(); + } + + public BoltNeo4jTransaction(IAsyncSession session, IAsyncTransaction transaction, string database) + { + Database = database; + DriverTransaction = transaction; + Session = session; + IsOpen = true; + Id = Guid.NewGuid(); + } + + #region Implementation of IDisposable + + protected virtual void Dispose(bool isDisposing) + { + if (!isDisposing) + return; + + if(IsOpen) + RollbackAsync().Wait(); + } + + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + #endregion + + #region Implementation of ITransaction + + public string Database { get; set; } + + /// + public async Task CommitAsync() + { + CheckOpenStatus(); + + if (DriverTransaction != null) + await DriverTransaction.CommitAsync(); + + + IsOpen = false; + } + + private void CheckOpenStatus() + { + if(!IsOpen) + throw new ClosedTransactionException(null); + } + + /// + public async Task RollbackAsync() + { + CheckOpenStatus(); + + if (DriverTransaction != null) + await DriverTransaction.RollbackAsync(); + + IsOpen = false; + } + + //TODO: Not needed + /// + #pragma warning disable 1998 + public async Task KeepAliveAsync() { } + #pragma warning restore 1998 + + /// + public bool IsOpen { get; private set; } + + //TODO: Not needed + /// + public NameValueCollection CustomHeaders { get; set; } + + /// + /// Gets the bookmark received following the last successfully completed Transaction. + /// If no bookmark was received or if this transaction was rolled back, the bookmark value will not be changed. + /// + public Bookmark LastBookmark => Session?.LastBookmark; + + #endregion + + public static void DoCommit(ITransactionExecutionEnvironmentBolt transactionExecutionEnvironment) + { + transactionExecutionEnvironment.DriverTransaction.CommitAsync().Wait(); + // transactionExecutionEnvironment.DriverTransaction.Dispose(); + } + + public static void DoRollback(ITransactionExecutionEnvironmentBolt transactionExecutionEnvironment) + { + transactionExecutionEnvironment.DriverTransaction.RollbackAsync().Wait(); + // transactionExecutionEnvironment.DriverTransaction.Dispose(); + } + + // public static BoltNeo4jTransaction FromIdAndClient(Guid transactionId, IDriver driver) + // { + // return new BoltNeo4jTransaction(driver, null, Database){Id = transactionId}; + // } + } +} \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltNeo4jTransactionProxy.cs b/Neo4jClient/Transactions/Bolt/BoltNeo4jTransactionProxy.cs similarity index 52% rename from Neo4jClient.Full.Shared/Transactions/Bolt/BoltNeo4jTransactionProxy.cs rename to Neo4jClient/Transactions/Bolt/BoltNeo4jTransactionProxy.cs index 17e984711..69faed746 100644 --- a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltNeo4jTransactionProxy.cs +++ b/Neo4jClient/Transactions/Bolt/BoltNeo4jTransactionProxy.cs @@ -1,7 +1,9 @@ -using Neo4j.Driver.V1; +using System.Threading.Tasks; -namespace Neo4jClient.Transactions +namespace Neo4jClient.Transactions.Bolt { + using Neo4j.Driver; + /// /// Implements the TransactionScopeProxy interfaces for INeo4jTransaction /// @@ -13,33 +15,34 @@ public BoltNeo4jTransactionProxy(ITransactionalGraphClient client, BoltTransacti : base(client, transactionContext) { doCommitInScope = newScope; - } public override bool Committable => true; - public override bool IsOpen => (TransactionContext != null) && TransactionContext.IsOpen; + public override bool IsOpen => TransactionContext != null && TransactionContext.IsOpen; + public override Bookmark LastBookmark => TransactionContext?.BoltTransaction?.LastBookmark; - protected override void DoCommit() + protected override async Task DoCommitAsync() { if (doCommitInScope) - TransactionContext.Commit(); + await TransactionContext.CommitAsync().ConfigureAwait(false); } + public override string Database { get; set; } + protected override bool ShouldDisposeTransaction() { return doCommitInScope; } - public override void Rollback() + public override Task RollbackAsync() { - TransactionContext.Rollback(); - + return TransactionContext.RollbackAsync(); } - public override void KeepAlive() + public override Task KeepAliveAsync() { - TransactionContext.KeepAlive(); + return TransactionContext.KeepAliveAsync(); } } } \ No newline at end of file diff --git a/Neo4jClient/Transactions/Bolt/BoltResponse.cs b/Neo4jClient/Transactions/Bolt/BoltResponse.cs new file mode 100644 index 000000000..d787bbfcc --- /dev/null +++ b/Neo4jClient/Transactions/Bolt/BoltResponse.cs @@ -0,0 +1,9 @@ +using Neo4j.Driver; + +namespace Neo4jClient.Transactions.Bolt +{ + public class BoltResponse + { + public IResultCursor StatementResult { get; set; } + } +} \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltSuppressTransactionProxy.cs b/Neo4jClient/Transactions/Bolt/BoltSuppressTransactionProxy.cs similarity index 60% rename from Neo4jClient.Full.Shared/Transactions/Bolt/BoltSuppressTransactionProxy.cs rename to Neo4jClient/Transactions/Bolt/BoltSuppressTransactionProxy.cs index 41741931d..3a140d8e6 100644 --- a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltSuppressTransactionProxy.cs +++ b/Neo4jClient/Transactions/Bolt/BoltSuppressTransactionProxy.cs @@ -1,7 +1,10 @@ using System; +using System.Threading.Tasks; -namespace Neo4jClient.Transactions +namespace Neo4jClient.Transactions.Bolt { + using Neo4j.Driver; + internal class BoltSuppressTransactionProxy : BoltTransactionScopeProxy { public BoltSuppressTransactionProxy(ITransactionalGraphClient client) @@ -9,28 +12,30 @@ public BoltSuppressTransactionProxy(ITransactionalGraphClient client) { } + public override string Database { get; set; } + protected override bool ShouldDisposeTransaction() { return false; } - protected override void DoCommit() + protected override Task DoCommitAsync() { throw new InvalidOperationException("Committing during a suppressed transaction scope"); } public override bool Committable => false; - public override void Rollback() + public override Task RollbackAsync() { throw new InvalidOperationException("Rolling back during a suppressed transaction scope"); } - public override void KeepAlive() - { - // no-op - } + #pragma warning disable 1998 + public override async Task KeepAliveAsync() { } + #pragma warning restore 1998 public override bool IsOpen => true; + public override Bookmark LastBookmark => throw new NotImplementedException(); } } \ No newline at end of file diff --git a/Neo4jClient/Transactions/Bolt/BoltTransactionContext.cs b/Neo4jClient/Transactions/Bolt/BoltTransactionContext.cs new file mode 100644 index 000000000..d0bf4be25 --- /dev/null +++ b/Neo4jClient/Transactions/Bolt/BoltTransactionContext.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Neo4jClient.Cypher; +using Neo4jClient.Execution; + +namespace Neo4jClient.Transactions.Bolt +{ + /// + /// 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 BoltTransactionContext : TransactionContextBase + { + internal BoltNeo4jTransaction BoltTransaction => Transaction as BoltNeo4jTransaction; + + public BoltTransactionContext(ITransaction transaction) : base(transaction) + { + } + + protected override async Task RunQuery(BoltGraphClient graphClient, CypherQuery query, + IExecutionPolicy policy, string commandDescription) + { + var result = await BoltTransaction.DriverTransaction.RunAsync(query, graphClient).ConfigureAwait(false); + var resp = new BoltResponse {StatementResult = result}; + return resp; + } + } +} diff --git a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionManager.cs b/Neo4jClient/Transactions/Bolt/BoltTransactionManager.cs similarity index 63% rename from Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionManager.cs rename to Neo4jClient/Transactions/Bolt/BoltTransactionManager.cs index d3c8cfc42..7e4dd8aad 100644 --- a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionManager.cs +++ b/Neo4jClient/Transactions/Bolt/BoltTransactionManager.cs @@ -1,14 +1,16 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Transactions; +using Neo4j.Driver; using Neo4jClient.Cypher; using Neo4jClient.Execution; +using Neo4jClient.Extensions; -namespace Neo4jClient.Transactions +namespace Neo4jClient.Transactions.Bolt { - /// /// Handles all the queries related to transactions that could be needed in a ITransactionalGraphClient /// @@ -28,22 +30,11 @@ private static readonly AsyncLocal ScopedTransactions { - get - { - if (scopedTransactions.Value == null) - { - scopedTransactions.Value = ThreadContextHelper.CreateBoltScopedTransactions(); - } - - return scopedTransactions.Value; - } + get => scopedTransactions.Value ?? (scopedTransactions.Value = ThreadContextHelper.CreateBoltScopedTransactions()); set => scopedTransactions.Value = value; } #endif - // holds the transaction contexts for transactions from the System.Transactions framework - private readonly IDictionary dtcContexts; - private readonly BoltTransactionPromotableSinglePhasesNotification promotable; private readonly ITransactionalGraphClient client; public BoltTransactionManager(ITransactionalGraphClient client) @@ -52,32 +43,6 @@ public BoltTransactionManager(ITransactionalGraphClient client) // specifies that we are about to use variables that depend on OS threads Thread.BeginThreadAffinity(); ScopedTransactions = ThreadContextHelper.CreateBoltScopedTransactions(); - - // 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 BoltTransactionPromotableSinglePhasesNotification(client); - dtcContexts = new Dictionary(); - } - - private BoltTransactionContext GetOrCreateDtcTransactionContext() - { - // we need to lock as we could get other async requests to the same transaction - var txId = Transaction.Current.TransactionInformation.LocalIdentifier; - lock (dtcContexts) - { - BoltTransactionContext txContext; - if (dtcContexts.TryGetValue(txId, out txContext)) - { - return txContext; - } - - // associate it with the ambient transaction - txContext = new BoltTransactionContext(promotable.AmbientTransaction); - dtcContexts[txId] = txContext; - - return txContext; - } } private BoltTransactionContext GetContext() @@ -88,8 +53,7 @@ private BoltTransactionContext GetContext() return nonDtcTransaction.TransactionContext; } - // if we are not in a native transaction get the context of our ambient transaction - return GetOrCreateDtcTransactionContext(); + throw new InvalidOperationException("There is no active transaction"); } public bool InTransaction @@ -107,33 +71,20 @@ public bool InTransaction } } - public BoltTransactionScopeProxy CurrentInternalTransaction - { - get - { - return ScopedTransactions.TryPeek(); - } - } + public BoltTransactionScopeProxy CurrentInternalTransaction => ScopedTransactions.TryPeek(); - public ITransaction CurrentNonDtcTransaction => CurrentInternalTransaction; + public ITransaction CurrentTransaction => CurrentInternalTransaction; - public ITransaction CurrentDtcTransaction => Transaction.Current == null ? null : promotable.AmbientTransaction; + public Bookmark LastBookmark => CurrentTransaction.LastBookmark; - /// - /// Implements the internal part for ITransactionalGraphClient.BeginTransaction - /// - /// How should the transaction scope be created. - /// - /// for more information. - /// Bookmarks for use with this transaction. - /// - public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmarks) + /// + public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmarks, string database) { if (scopeOption == TransactionScopeOption.Suppress) { // TransactionScopeOption.Suppress doesn't fail with older versions of Neo4j //TODO: Check this - return BeginSupressTransaction(); + return BeginSuppressTransaction(); } if (client.ServerVersion < new Version(3, 0)) @@ -152,14 +103,16 @@ public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumer } // then scopeOption == TransactionScopeOption.RequiresNew or we dont have a current transaction - return BeginNewTransaction(bookmarks); + return BeginNewTransaction(bookmarks, database); } - private BoltTransactionContext GenerateTransaction(IEnumerable bookmarks) + private BoltTransactionContext GenerateTransaction(IEnumerable bookmarks, string database) { - var session = ((BoltGraphClient)client).Driver.Session(bookmarks); - var transaction = session.BeginTransaction(); - return new BoltTransactionContext(new BoltNeo4jTransaction(session, transaction)); + var session = ((BoltGraphClient) client).Driver.AsyncSession(client.ServerVersion, database, true, bookmarks); + var transactionTask = session.BeginTransactionAsync(); + transactionTask.Wait(); + var transaction = transactionTask.Result; + return new BoltTransactionContext(new BoltNeo4jTransaction(session, transaction, database)); } private BoltTransactionContext GenerateTransaction(BoltTransactionContext reference) @@ -172,9 +125,9 @@ private void PushScopeTransaction(BoltTransactionScopeProxy transaction) ScopedTransactions.Push(transaction); } - private ITransaction BeginNewTransaction(IEnumerable bookmarks) + private ITransaction BeginNewTransaction(IEnumerable bookmarks, string database) { - var transaction = new BoltNeo4jTransactionProxy(client, GenerateTransaction(bookmarks), true); + var transaction = new BoltNeo4jTransactionProxy(client, GenerateTransaction(bookmarks, database), true); PushScopeTransaction(transaction); return transaction; } @@ -202,7 +155,7 @@ private ITransaction BeginJoinTransaction() return joinedTransaction; } - private ITransaction BeginSupressTransaction() + private ITransaction BeginSuppressTransaction() { var suppressTransaction = new BoltSuppressTransactionProxy(client); PushScopeTransaction(suppressTransaction); @@ -215,15 +168,6 @@ public void EndTransaction() currentTransaction?.Dispose(); } - /// - /// Registers to ambient System.Transactions.TransactionContext if needed - /// - public void RegisterToTransactionIfNeeded() - { - //If promotable is null - we don't support tx. - promotable?.EnlistIfNecessary(); - } - public void Dispose() { ScopedTransactions = null; diff --git a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionScopeProxy.cs b/Neo4jClient/Transactions/Bolt/BoltTransactionScopeProxy.cs similarity index 72% rename from Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionScopeProxy.cs rename to Neo4jClient/Transactions/Bolt/BoltTransactionScopeProxy.cs index 99f039017..88f1be35b 100644 --- a/Neo4jClient.Full.Shared/Transactions/Bolt/BoltTransactionScopeProxy.cs +++ b/Neo4jClient/Transactions/Bolt/BoltTransactionScopeProxy.cs @@ -1,9 +1,10 @@ -using System; -using System.Collections.Specialized; -using Neo4j.Driver.V1; +using System.Collections.Specialized; +using System.Threading.Tasks; -namespace Neo4jClient.Transactions +namespace Neo4jClient.Transactions.Bolt { + using Neo4j.Driver; + /// /// 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 @@ -16,7 +17,7 @@ internal abstract class BoltTransactionScopeProxy : ITransaction private bool disposing = false; private BoltTransactionContext transactionContext; - internal Neo4j.Driver.V1.ITransaction DriverTransaction => TransactionContext.BoltTransaction.DriverTransaction; + internal Neo4j.Driver.IAsyncTransaction DriverTransaction => TransactionContext.BoltTransaction.DriverTransaction; public BoltTransactionContext TransactionContext => transactionContext; protected BoltTransactionScopeProxy(ITransactionalGraphClient client, BoltTransactionContext transactionContext) @@ -39,7 +40,7 @@ public virtual void Dispose() client.EndTransaction(); if (!markCommitted && Committable && TransactionContext.IsOpen) { - Rollback(); + RollbackAsync().Wait(); // annoying, but can't dispose asynchronously } if (transactionContext != null && ShouldDisposeTransaction()) @@ -49,17 +50,20 @@ public virtual void Dispose() } } - public void Commit() + public abstract string Database { get; set; } + + public Task CommitAsync() { markCommitted = true; - DoCommit(); + return DoCommitAsync(); } protected abstract bool ShouldDisposeTransaction(); - protected abstract void DoCommit(); + protected abstract Task DoCommitAsync(); public abstract bool Committable { get; } - public abstract void Rollback(); - public abstract void KeepAlive(); + public abstract Task RollbackAsync(); + public abstract Task KeepAliveAsync(); public abstract bool IsOpen { get; } + public abstract Bookmark LastBookmark { get; } } } diff --git a/Neo4jClient/Transactions/ClosedTransactionException.cs b/Neo4jClient/Transactions/ClosedTransactionException.cs new file mode 100644 index 000000000..b3a357e8e --- /dev/null +++ b/Neo4jClient/Transactions/ClosedTransactionException.cs @@ -0,0 +1,15 @@ +using System; + +namespace Neo4jClient.Transactions +{ + public class ClosedTransactionException : Exception + { + 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; } + } +} diff --git a/Neo4jClient.Shared/Transactions/IInternalTransactionalGraphClient.cs b/Neo4jClient/Transactions/IInternalTransactionalGraphClient.cs similarity index 100% rename from Neo4jClient.Shared/Transactions/IInternalTransactionalGraphClient.cs rename to Neo4jClient/Transactions/IInternalTransactionalGraphClient.cs diff --git a/Neo4jClient.Shared/Transactions/INeo4jTransaction.cs b/Neo4jClient/Transactions/INeo4jTransaction.cs similarity index 100% rename from Neo4jClient.Shared/Transactions/INeo4jTransaction.cs rename to Neo4jClient/Transactions/INeo4jTransaction.cs diff --git a/Neo4jClient.Shared/Transactions/ITransaction.cs b/Neo4jClient/Transactions/ITransaction.cs similarity index 63% rename from Neo4jClient.Shared/Transactions/ITransaction.cs rename to Neo4jClient/Transactions/ITransaction.cs index 0d4836ffb..4afd293b7 100644 --- a/Neo4jClient.Shared/Transactions/ITransaction.cs +++ b/Neo4jClient/Transactions/ITransaction.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Specialized; +using System.Threading.Tasks; namespace Neo4jClient.Transactions { + using Neo4j.Driver; + /// /// Represents a Neo4j transaction shared between multiple HTTP requests /// @@ -12,20 +15,25 @@ namespace Neo4jClient.Transactions /// public interface ITransaction : IDisposable { + /// + /// In 4 + databases, which database this transaction is against. + /// + string Database { get; } + /// /// Commits our open transaction. /// - void Commit(); + Task CommitAsync(); /// /// Rollbacks any changes made by our open transaction /// - void Rollback(); + Task RollbackAsync(); /// /// Prevents the transaction from being claimed as an orphaned transaction. /// - void KeepAlive(); + Task KeepAliveAsync(); /// /// Returns true if the transaction is still open, that is, if the programmer has not called @@ -34,9 +42,14 @@ public interface ITransaction : IDisposable bool IsOpen { get; } /// - /// Customheader collection This will be the same for the entire transaction. - /// So the commit will use the same customheader(s) as the cypher customheader + /// Custom header collection This will be the same for the entire transaction. + /// So the commit will use the same custom header(s) as the cypher custom header /// NameValueCollection CustomHeaders { get; set; } + + /// + /// The last bookmark provided by the database to allow for causal consistency. + /// + Bookmark LastBookmark { get; } } } diff --git a/Neo4jClient.Shared/Transactions/ITransactionExecutionEnvironment.cs b/Neo4jClient/Transactions/ITransactionExecutionEnvironment.cs similarity index 92% rename from Neo4jClient.Shared/Transactions/ITransactionExecutionEnvironment.cs rename to Neo4jClient/Transactions/ITransactionExecutionEnvironment.cs index c5d3d0057..969e4af65 100644 --- a/Neo4jClient.Shared/Transactions/ITransactionExecutionEnvironment.cs +++ b/Neo4jClient/Transactions/ITransactionExecutionEnvironment.cs @@ -11,7 +11,7 @@ internal interface ITransactionExecutionEnvironmentBolt string Username { get; set; } string Password { get; set; } Guid ResourceManagerId { get; set; } - Neo4j.Driver.V1.ITransaction DriverTransaction { get; set; } + Neo4j.Driver.IAsyncTransaction DriverTransaction { get; set; } } internal interface ITransactionExecutionEnvironment diff --git a/Neo4jClient/Transactions/ITransactionManager.cs b/Neo4jClient/Transactions/ITransactionManager.cs new file mode 100644 index 000000000..88aa08400 --- /dev/null +++ b/Neo4jClient/Transactions/ITransactionManager.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Neo4jClient.Cypher; + +namespace Neo4jClient.Transactions +{ + using Neo4j.Driver; + + /// + /// Interface that handles all the queries related to transactions that could be needed in a ITransactionalGraphClient + /// for implementation. + /// + public interface ITransactionManager : IDisposable + { + /// + /// Is this in a transaction at the moment? + /// + bool InTransaction { get; } + + /// Begins a transaction. + /// How should the transaction scope be created. + /// + /// for more information. + /// Bookmarks for use with this transaction. + /// The database to execute the transaction against. + /// An object representing the transaction. + ITransaction BeginTransaction(TransactionScopeOption option, IEnumerable bookmarks, string database); + void EndTransaction(); + ITransaction CurrentTransaction { get; } + Bookmark LastBookmark { get; } + Task EnqueueCypherRequest(string commandDescription, IGraphClient graphClient, CypherQuery query); + } +} diff --git a/Neo4jClient.Shared/Transactions/ITransactionResourceManager.cs b/Neo4jClient/Transactions/ITransactionResourceManager.cs similarity index 100% rename from Neo4jClient.Shared/Transactions/ITransactionResourceManager.cs rename to Neo4jClient/Transactions/ITransactionResourceManager.cs diff --git a/Neo4jClient.Shared/Transactions/ITransactionalGraphClient.cs b/Neo4jClient/Transactions/ITransactionalGraphClient.cs similarity index 97% rename from Neo4jClient.Shared/Transactions/ITransactionalGraphClient.cs rename to Neo4jClient/Transactions/ITransactionalGraphClient.cs index 0ff155e89..dbe1b54b0 100644 --- a/Neo4jClient.Shared/Transactions/ITransactionalGraphClient.cs +++ b/Neo4jClient/Transactions/ITransactionalGraphClient.cs @@ -183,7 +183,9 @@ public interface ITransactionalGraphClient : IGraphClient /// /// /// The bookmarks to use for this transaction. - ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmark); + /// The database to use + ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmarks, string database = null); + /// /// The current transaction object. @@ -203,10 +205,10 @@ public interface ITransactionalGraphClient : IGraphClient /// 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; } + // + // /// + // /// The Neo4j transaction initial transaction endpoint + // /// + // Uri TransactionEndpoint { get; } } } \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/Transactions/Neo4jRestTransaction.cs b/Neo4jClient/Transactions/Neo4jRestTransaction.cs similarity index 81% rename from Neo4jClient.Full.Shared/Transactions/Neo4jRestTransaction.cs rename to Neo4jClient/Transactions/Neo4jRestTransaction.cs index f96c74b3b..06c27fb46 100644 --- a/Neo4jClient.Full.Shared/Transactions/Neo4jRestTransaction.cs +++ b/Neo4jClient/Transactions/Neo4jRestTransaction.cs @@ -1,12 +1,15 @@ using System; using System.Collections.Specialized; using System.Net; +using System.Threading.Tasks; using Neo4jClient.ApiModels.Cypher; using Neo4jClient.Execution; using Neo4jClient.Serialization; namespace Neo4jClient.Transactions { + using Neo4j.Driver; + /// /// Implements the Neo4j HTTP transaction for multiple HTTP requests /// @@ -18,6 +21,7 @@ internal class Neo4jRestTransaction: INeo4jTransaction public Uri Endpoint { get; set; } public NameValueCollection CustomHeaders { get; set; } + public Bookmark LastBookmark => throw new InvalidOperationException("This is not possible with the GraphClient. You would need the BoltGraphClient."); internal int Id { @@ -38,21 +42,16 @@ internal int Id } } - internal static Neo4jRestTransaction FromIdAndClient(int transactionId, ITransactionalGraphClient client) - { - return new Neo4jRestTransaction(client) - { - Endpoint = client.TransactionEndpoint.AddPath(transactionId.ToString()) - }; - } - - public Neo4jRestTransaction(ITransactionalGraphClient graphClient) + public Neo4jRestTransaction(ITransactionalGraphClient graphClient, string database) { Endpoint = null; IsOpen = true; client = graphClient; + Database = database; } + public string Database { get; set; } + protected void CleanupAfterClosedTransaction() { IsOpen = false; @@ -85,7 +84,7 @@ internal void Cancel() /// /// Commits our current transaction and closes the transaction. /// - public void Commit() + public async Task CommitAsync() { CheckForOpenTransaction(); // we have to check for an empty endpoint because we dont have one until our first request @@ -95,32 +94,33 @@ public void Commit() return; } - DoCommit(Endpoint, client.ExecutionConfiguration, client.Serializer, CustomHeaders); + await DoCommit(Endpoint, client.ExecutionConfiguration, client.Serializer, CustomHeaders).ConfigureAwait(false); CleanupAfterClosedTransaction(); } /// /// Rolls back our current transaction and closes the transaction. /// - public void Rollback() + public async Task RollbackAsync() { CheckForOpenTransaction(); // we have to check for an empty endpoint because we dont have one until our first request + if (Endpoint == null) { CleanupAfterClosedTransaction(); return; } - + //This change is due to: https://github.com/Readify/Neo4jClient/issues/127 and https://github.com/neo4j/neo4j/issues/5806 - HttpStatusCode[] expectedStatusCodes = {HttpStatusCode.OK}; if (client.CypherCapabilities.AutoRollsBackOnError && client.ExecutionConfiguration.HasErrors) expectedStatusCodes = new [] {HttpStatusCode.OK, HttpStatusCode.NotFound}; - Request.With(client.ExecutionConfiguration) + await Request.With(client.ExecutionConfiguration) .Delete(Endpoint) .WithExpectedStatusCodes(expectedStatusCodes) - .Execute(); + .ExecuteAsync().ConfigureAwait(false); CleanupAfterClosedTransaction(); } @@ -128,28 +128,32 @@ public void Rollback() /// /// Emits an empty request to keep alive our current transaction. /// - public void KeepAlive() + public Task KeepAliveAsync() { CheckForOpenTransaction(); // no need to issue a request as we haven't sent a single request if (Endpoint == null) { - return; +#if NET45 + return Task.FromResult(0); +#else + return Task.CompletedTask; +#endif } - DoKeepAlive(Endpoint, client.ExecutionConfiguration, client.Serializer); + return DoKeepAlive(Endpoint, client.ExecutionConfiguration, client.Serializer); } /// /// Forces a keep alive, setting the endpoint if necessary /// - internal void ForceKeepAlive() + internal async Task ForceKeepAlive() { var keepAliveUri = Endpoint ?? client.TransactionEndpoint; - var transactionEndpoint = DoKeepAlive( + var transactionEndpoint = await DoKeepAlive( keepAliveUri, client.ExecutionConfiguration, - client.Serializer,newTransaction: Endpoint == null); + client.Serializer,newTransaction: Endpoint == null).ConfigureAwait(false); if (Endpoint != null) { @@ -158,26 +162,26 @@ internal void ForceKeepAlive() Endpoint = transactionEndpoint; } - private static void DoCommit(Uri commitUri, ExecutionConfiguration executionConfiguration, ISerializer serializer, NameValueCollection customHeaders = null) + private static Task DoCommit(Uri commitUri, ExecutionConfiguration executionConfiguration, ISerializer serializer, NameValueCollection customHeaders = null) { - Request.With(executionConfiguration, customHeaders) + return Request.With(executionConfiguration, customHeaders) .Post(commitUri.AddPath("commit")) .WithJsonContent(serializer.Serialize(new CypherStatementList())) .WithExpectedStatusCodes(HttpStatusCode.OK) - .Execute(); + .ExecuteAsync(); } - private static void DoRollback(Uri rollbackUri, ExecutionConfiguration executionConfiguration, NameValueCollection customHeaders) + private static Task DoRollback(Uri rollbackUri, ExecutionConfiguration executionConfiguration, NameValueCollection customHeaders) { // 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, customHeaders) + return Request.With(executionConfiguration, customHeaders) .Delete(rollbackUri) .WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.NotFound) - .Execute(); + .ExecuteAsync(); } - private static Uri DoKeepAlive( + private static async Task DoKeepAlive( Uri keepAliveUri, ExecutionConfiguration executionConfiguration, ISerializer serializer, @@ -189,8 +193,8 @@ private static Uri DoKeepAlive( .WithJsonContent(serializer.Serialize(new CypherStatementList())); var response = newTransaction ? - partialRequest.WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created).Execute() : - partialRequest.WithExpectedStatusCodes(HttpStatusCode.OK).Execute(); + await partialRequest.WithExpectedStatusCodes(HttpStatusCode.OK, HttpStatusCode.Created).ExecuteAsync().ConfigureAwait(false) : + await partialRequest.WithExpectedStatusCodes(HttpStatusCode.OK).ExecuteAsync().ConfigureAwait(false); return response.Headers.Location; } @@ -200,12 +204,12 @@ private static Uri DoKeepAlive( /// /// The transaction execution environment /// Custom headers to sent to the neo4j server - internal static void DoCommit(ITransactionExecutionEnvironment transactionExecutionEnvironment, NameValueCollection customHeaders = null) + internal static Task DoCommit(ITransactionExecutionEnvironment transactionExecutionEnvironment, NameValueCollection customHeaders = null) { var commitUri = transactionExecutionEnvironment.TransactionBaseEndpoint.AddPath( transactionExecutionEnvironment.TransactionId.ToString()); - DoCommit( + return DoCommit( commitUri, new ExecutionConfiguration { @@ -223,13 +227,13 @@ internal static void DoCommit(ITransactionExecutionEnvironment transactionExecut /// Rolls back a transaction given the ID /// /// The transaction execution environment - internal static void DoRollback(ITransactionExecutionEnvironment transactionExecutionEnvironment, NameValueCollection customHeaders = null) + internal static Task DoRollback(ITransactionExecutionEnvironment transactionExecutionEnvironment, NameValueCollection customHeaders = null) { try { var rollbackUri = transactionExecutionEnvironment.TransactionBaseEndpoint.AddPath( transactionExecutionEnvironment.TransactionId.ToString()); - DoRollback( + return DoRollback( rollbackUri, new ExecutionConfiguration { @@ -250,11 +254,11 @@ internal static void DoRollback(ITransactionExecutionEnvironment transactionExec /// Keeps alive a transaction given the ID /// /// The transaction execution environment - internal static void DoKeepAlive(ITransactionExecutionEnvironment transactionExecutionEnvironment) + internal static Task DoKeepAlive(ITransactionExecutionEnvironment transactionExecutionEnvironment) { var keepAliveUri = transactionExecutionEnvironment.TransactionBaseEndpoint.AddPath( transactionExecutionEnvironment.TransactionId.ToString()); - DoKeepAlive( + return DoKeepAlive( keepAliveUri, new ExecutionConfiguration { @@ -271,7 +275,7 @@ public void Dispose() { if (IsOpen) { - Rollback(); + RollbackAsync().Wait(); // annoying but can't dispose asynchronously } } } diff --git a/Neo4jClient.Full.Shared/Transactions/Neo4jTransactionProxy.cs b/Neo4jClient/Transactions/Neo4jTransactionProxy.cs similarity index 63% rename from Neo4jClient.Full.Shared/Transactions/Neo4jTransactionProxy.cs rename to Neo4jClient/Transactions/Neo4jTransactionProxy.cs index d2e0a76ce..3ae89053e 100644 --- a/Neo4jClient.Full.Shared/Transactions/Neo4jTransactionProxy.cs +++ b/Neo4jClient/Transactions/Neo4jTransactionProxy.cs @@ -1,4 +1,6 @@ -namespace Neo4jClient.Transactions +using System.Threading.Tasks; + +namespace Neo4jClient.Transactions { /// /// Implements the TransactionScopeProxy interfaces for INeo4jTransaction @@ -17,10 +19,15 @@ public Neo4jTransactionProxy(ITransactionalGraphClient client, TransactionContex public override bool IsOpen => (TransactionContext != null) && TransactionContext.IsOpen; - protected override void DoCommit() + protected override Task DoCommitAsync() { if (doCommitInScope) - TransactionContext.Commit(); + return TransactionContext.CommitAsync(); +#if NET45 + return Task.FromResult(0); +#else + return Task.CompletedTask; +#endif } protected override bool ShouldDisposeTransaction() @@ -28,14 +35,14 @@ protected override bool ShouldDisposeTransaction() return doCommitInScope; } - public override void Rollback() + public override Task RollbackAsync() { - TransactionContext.Rollback(); + return TransactionContext.RollbackAsync(); } - public override void KeepAlive() + public override Task KeepAliveAsync() { - TransactionContext.KeepAlive(); + return TransactionContext.KeepAliveAsync(); } } } \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/Transactions/Neo4jTransactionResourceManager.cs b/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs similarity index 99% rename from Neo4jClient.Full.Shared/Transactions/Neo4jTransactionResourceManager.cs rename to Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs index f71daa684..921e3cd58 100644 --- a/Neo4jClient.Full.Shared/Transactions/Neo4jTransactionResourceManager.cs +++ b/Neo4jClient/Transactions/Neo4jTransactionResourceManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Transactions; +using Neo4jClient.Transactions.Bolt; namespace Neo4jClient.Transactions { diff --git a/Neo4jClient.Full.Shared/Transactions/SuppressTransactionProxy.cs b/Neo4jClient/Transactions/SuppressTransactionProxy.cs similarity index 78% rename from Neo4jClient.Full.Shared/Transactions/SuppressTransactionProxy.cs rename to Neo4jClient/Transactions/SuppressTransactionProxy.cs index f68b04553..d09589cb1 100644 --- a/Neo4jClient.Full.Shared/Transactions/SuppressTransactionProxy.cs +++ b/Neo4jClient/Transactions/SuppressTransactionProxy.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading.Tasks; namespace Neo4jClient.Transactions { @@ -17,7 +18,7 @@ protected override bool ShouldDisposeTransaction() return false; } - protected override void DoCommit() + protected override Task DoCommitAsync() { throw new InvalidOperationException("Committing during a suppressed transaction scope"); } @@ -27,14 +28,19 @@ public override bool Committable get { return false; } } - public override void Rollback() + public override Task RollbackAsync() { throw new InvalidOperationException("Rolling back during a suppressed transaction scope"); } - public override void KeepAlive() + public override Task KeepAliveAsync() { // no-op +#if NET45 + return Task.FromResult(0); +#else + return Task.CompletedTask; +#endif } public override bool IsOpen diff --git a/Neo4jClient.Full.Shared/Transactions/ThreadContextHelpers.cs b/Neo4jClient/Transactions/ThreadContextHelpers.cs similarity index 92% rename from Neo4jClient.Full.Shared/Transactions/ThreadContextHelpers.cs rename to Neo4jClient/Transactions/ThreadContextHelpers.cs index d9014b5d6..2237a459c 100644 --- a/Neo4jClient.Full.Shared/Transactions/ThreadContextHelpers.cs +++ b/Neo4jClient/Transactions/ThreadContextHelpers.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Neo4jClient.Transactions.Bolt; namespace Neo4jClient.Transactions { @@ -26,9 +27,7 @@ internal interface IScopedTransactions where T : class T TryPeek(); } - internal class ThreadContextWrapper - : IScopedTransactions - where T : class + internal class ThreadContextWrapper : IScopedTransactions where T : class { private readonly Stack _stack; diff --git a/Neo4jClient.Full.Shared/Transactions/TransactionConnectionContext.cs b/Neo4jClient/Transactions/TransactionConnectionContext.cs similarity index 100% rename from Neo4jClient.Full.Shared/Transactions/TransactionConnectionContext.cs rename to Neo4jClient/Transactions/TransactionConnectionContext.cs diff --git a/Neo4jClient/Transactions/TransactionContext.cs b/Neo4jClient/Transactions/TransactionContext.cs new file mode 100644 index 000000000..c478baa3c --- /dev/null +++ b/Neo4jClient/Transactions/TransactionContext.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Specialized; +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 : TransactionContextBase, INeo4jTransaction + { + public INeo4jTransaction NeoTransaction => Transaction as INeo4jTransaction; + + public TransactionContext(INeo4jTransaction transaction): base(transaction) + { + } + + protected override Task RunQuery(IGraphClient client, CypherQuery query, IExecutionPolicy policy, string commandDescription) + { + + var txBaseEndpoint = policy.BaseEndpoint((policy.InTransaction) ? policy.Database : query.Database, !policy.InTransaction); + var serializedQuery = policy.SerializeRequest(query); + CustomHeaders = query.CustomHeaders; + return Request.With(client.ExecutionConfiguration, query.CustomHeaders, query.MaxExecutionTime) + .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, + response => + { + // 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. + policy.AfterExecution(TransactionHttpUtils.GetMetadataFromResponse(response), this); + + return response; + }); + } + + public Uri Endpoint + { + get => NeoTransaction.Endpoint; + set => NeoTransaction.Endpoint = value; + } + } +} diff --git a/Neo4jClient/Transactions/TransactionContextBase.cs b/Neo4jClient/Transactions/TransactionContextBase.cs new file mode 100644 index 000000000..19eb54822 --- /dev/null +++ b/Neo4jClient/Transactions/TransactionContextBase.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Specialized; +using System.Threading; +using System.Threading.Tasks; +using Neo4jClient.Cypher; +using Neo4jClient.Execution; +using Neo4jClient.Transactions.Bolt; + +namespace Neo4jClient.Transactions +{ + using Neo4j.Driver; + + internal abstract class TransactionContextBase : ITransaction + { + /// + /// This replaces the synchronous queue. It is picked up and replaced atomically. + /// + private Task previousTask; + + protected TransactionContextBase(ITransaction transaction) + { + Transaction = transaction; + } + + /// + /// The Neo4j transaction object. + /// + public ITransaction Transaction { get; } + + public NameValueCollection CustomHeaders { get; set; } + public Bookmark LastBookmark => Transaction.LastBookmark; + public bool IsOpen => Transaction.IsOpen; + + protected abstract Task RunQuery(TClient client, CypherQuery query, IExecutionPolicy policy, string commandDescription); + + public async Task EnqueueTask(string commandDescription, TClient graphClient, IExecutionPolicy policy, CypherQuery query) + { + var taskCompletion = new TaskCompletionSource(); + try + { + var localPreviousTask = Interlocked.Exchange(ref previousTask, taskCompletion.Task); + if (localPreviousTask != null) await localPreviousTask.ConfigureAwait(false); + var resp = await RunQuery(graphClient, query, policy, commandDescription).ConfigureAwait(false); + taskCompletion.SetResult(resp); + } + catch (OperationCanceledException) + { + taskCompletion.SetCanceled(); + } + catch (Exception e) + { + taskCompletion.SetException(e); + } + + return await taskCompletion.Task.ConfigureAwait(false); + } + + public void Dispose() + { + Transaction.Dispose(); + } + + public string Database + { + get => Transaction.Database; + // set => Transaction.Database = value; + } + + public async Task CommitAsync() + { + // await PreventAddingAndWait().ConfigureAwait(false); + + if (CustomHeaders != null) + { + Transaction.CustomHeaders = CustomHeaders; + } + + await Transaction.CommitAsync().ConfigureAwait(false); + } + + // private async Task PreventAddingAndWait() + // { + // var cancelled = new TaskCompletionSource(); + // cancelled.SetCanceled(); + // var task = Interlocked.Exchange(ref previousTask, cancelled.Task); // cancel any newly created tasks + // if (task != null) await task.ConfigureAwait(false); + // } + + public async Task RollbackAsync() + { + //await PreventAddingAndWait().ConfigureAwait(false); + await Transaction.RollbackAsync().ConfigureAwait(false); + } + + public Task KeepAliveAsync() + { + return Transaction.KeepAliveAsync(); + } + } +} \ No newline at end of file diff --git a/Neo4jClient.Full.Shared/Transactions/TransactionExecutionEnvironment.cs b/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs similarity index 86% rename from Neo4jClient.Full.Shared/Transactions/TransactionExecutionEnvironment.cs rename to Neo4jClient/Transactions/TransactionExecutionEnvironment.cs index 02afaaa51..d34183a05 100644 --- a/Neo4jClient.Full.Shared/Transactions/TransactionExecutionEnvironment.cs +++ b/Neo4jClient/Transactions/TransactionExecutionEnvironment.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using Neo4j.Driver.V1; +using Neo4j.Driver; using Neo4jClient.Execution; using Newtonsoft.Json; @@ -8,15 +8,6 @@ namespace Neo4jClient.Transactions { internal class BoltTransactionExecutionEnvironment : MarshalByRefObject, ITransactionExecutionEnvironmentBolt { - public Guid TransactionId { get; set; } - public IEnumerable JsonConverters { get; set; } - public string Username { get; set; } - public string Password { get; set; } - public Guid ResourceManagerId { get; set; } - public ISession Session { get; set; } - public Neo4j.Driver.V1.ITransaction DriverTransaction { get; set; } - public IList Bookmarks { get; set; } - public BoltTransactionExecutionEnvironment(ExecutionConfiguration executionConfiguration) { Username = executionConfiguration.Username; @@ -24,23 +15,23 @@ public BoltTransactionExecutionEnvironment(ExecutionConfiguration executionConfi JsonConverters = executionConfiguration.JsonConverters; ResourceManagerId = executionConfiguration.ResourceManagerId; } - } - /// - /// 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 IAsyncSession Session { get; set; } + public IList Bookmarks { get; set; } + public Guid TransactionId { get; set; } public IEnumerable JsonConverters { get; set; } public string Username { get; set; } public string Password { get; set; } public Guid ResourceManagerId { get; set; } + public IAsyncTransaction DriverTransaction { get; set; } + } + /// + /// 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 TransactionExecutionEnvironment(ExecutionConfiguration executionConfiguration) { UserAgent = executionConfiguration.UserAgent; @@ -51,5 +42,13 @@ public TransactionExecutionEnvironment(ExecutionConfiguration executionConfigura ResourceManagerId = executionConfiguration.ResourceManagerId; } + public Uri TransactionBaseEndpoint { get; set; } + public int TransactionId { get; set; } + public bool UseJsonStreaming { get; set; } + public string UserAgent { get; set; } + public IEnumerable JsonConverters { get; set; } + public string Username { get; set; } + public string Password { get; set; } + public Guid ResourceManagerId { get; set; } } -} +} \ No newline at end of file diff --git a/Neo4jClient.Shared/Transactions/TransactionHttpUtils.cs b/Neo4jClient/Transactions/TransactionHttpUtils.cs similarity index 100% rename from Neo4jClient.Shared/Transactions/TransactionHttpUtils.cs rename to Neo4jClient/Transactions/TransactionHttpUtils.cs diff --git a/Neo4jClient.Full.Shared/Transactions/TransactionManager.cs b/Neo4jClient/Transactions/TransactionManager.cs similarity index 65% rename from Neo4jClient.Full.Shared/Transactions/TransactionManager.cs rename to Neo4jClient/Transactions/TransactionManager.cs index 890e52aa5..48f21988a 100644 --- a/Neo4jClient.Full.Shared/Transactions/TransactionManager.cs +++ b/Neo4jClient/Transactions/TransactionManager.cs @@ -10,6 +10,8 @@ namespace Neo4jClient.Transactions { + using Neo4j.Driver; + /// /// Handles all the queries related to transactions that could be needed in a ITransactionalGraphClient /// @@ -28,23 +30,12 @@ private static readonly AsyncLocal> s = new AsyncLocal>(); internal static IScopedTransactions ScopedTransactions { - get - { - if (scopedTransactions.Value == null) - { - scopedTransactions.Value = ThreadContextHelper.CreateScopedTransactions(); - } - - return scopedTransactions.Value; - } + get => scopedTransactions.Value ?? (scopedTransactions.Value = ThreadContextHelper.CreateScopedTransactions()); set => scopedTransactions.Value = value; } #endif - - // holds the transaction contexts for transactions from the System.Transactions framework - private readonly IDictionary dtcContexts; - private readonly TransactionPromotableSinglePhaseNotification promotable; + private readonly ITransactionalGraphClient client; public TransactionManager(ITransactionalGraphClient client) @@ -53,36 +44,6 @@ public TransactionManager(ITransactionalGraphClient client) // specifies that we are about to use variables that depend on OS threads Thread.BeginThreadAffinity(); ScopedTransactions = ThreadContextHelper.CreateScopedTransactions(); - - // this object enables the interacion with System.Transactions and MSDTC, at first by - // letting us manage the transaction objects ourselves, and if we require to be promoted to MSDTC, - // then it notifies the library how to do it. - promotable = new TransactionPromotableSinglePhaseNotification(client); - dtcContexts = new Dictionary(); - } - - private TransactionContext GetOrCreateDtcTransactionContext(NameValueCollection customHeaders = null) - { - // 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) - { - Transaction = {CustomHeaders = customHeaders}, - CustomHeaders = customHeaders - }; - dtcContexts[txId] = txContext; - - return txContext; - } } private TransactionContext GetContext(NameValueCollection customHeaders = null) @@ -93,8 +54,7 @@ private TransactionContext GetContext(NameValueCollection customHeaders = null) return nonDtcTransaction.TransactionContext; } - // if we are not in a native transaction get the context of our ambient transaction - return GetOrCreateDtcTransactionContext(customHeaders); + throw new InvalidOperationException("There is no active transaction"); } public bool InTransaction @@ -108,34 +68,38 @@ public bool InTransaction } // if we are in an ambient System.Transactions transaction then we are in a transaction! + //BUG: ??? Using System.Transactions here...?!? return Transaction.Current != null; } } public TransactionScopeProxy CurrentInternalTransaction => ScopedTransactions.TryPeek(); - public ITransaction CurrentNonDtcTransaction => CurrentInternalTransaction; + public ITransaction CurrentTransaction => CurrentInternalTransaction; + public Bookmark LastBookmark => CurrentTransaction.LastBookmark; - public ITransaction CurrentDtcTransaction => Transaction.Current == null ? null : promotable.AmbientTransaction; - - public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmarks) + public ITransaction BeginTransaction(TransactionScopeOption option, IEnumerable bookmarks, string database) { - return BeginTransaction(scopeOption); + return BeginTransaction(option, database); } + // public ITransaction BeginTransaction(TransactionScopeOption scopeOption, IEnumerable bookmarks) + // { + // return BeginTransaction(scopeOption, client.DefaultDatabase); + // } + /// /// Implements the internal part for ITransactionalGraphClient.BeginTransaction /// /// How should the transaction scope be created. - /// - /// for more information. + /// for more information. /// - public ITransaction BeginTransaction(TransactionScopeOption scopeOption) + public ITransaction BeginTransaction(TransactionScopeOption scopeOption, string database) { if (scopeOption == TransactionScopeOption.Suppress) { // TransactionScopeOption.Suppress doesn't fail with older versions of Neo4j - return BeginSupressTransaction(); + return BeginSuppressTransaction(); } if (client.ServerVersion < new Version(2, 0)) @@ -153,17 +117,17 @@ public ITransaction BeginTransaction(TransactionScopeOption scopeOption) } // then scopeOption == TransactionScopeOption.RequiresNew or we dont have a current transaction - return BeginNewTransaction(); + return BeginNewTransaction(database); } - private TransactionContext GenerateTransaction() + private TransactionContext GenerateTransaction(string database) { - return new TransactionContext(new Neo4jRestTransaction(client)); + return new TransactionContext(new Neo4jRestTransaction(client, database)); } - private TransactionContext GenerateTransaction(TransactionContext reference) + private static TransactionContext GenerateTransaction(TransactionContext reference) { - return new TransactionContext(reference.Transaction); + return new TransactionContext(reference.NeoTransaction); } private static void PushScopeTransaction(TransactionScopeProxy transaction) @@ -171,9 +135,9 @@ private static void PushScopeTransaction(TransactionScopeProxy transaction) ScopedTransactions.Push(transaction); } - private ITransaction BeginNewTransaction() + private ITransaction BeginNewTransaction(string database) { - var transaction = new Neo4jTransactionProxy(client, GenerateTransaction(), true); + var transaction = new Neo4jTransactionProxy(client, GenerateTransaction(database), true); PushScopeTransaction(transaction); return transaction; } @@ -201,14 +165,15 @@ private ITransaction BeginJoinTransaction() return joinedTransaction; } - private ITransaction BeginSupressTransaction() + private ITransaction BeginSuppressTransaction() { var suppressTransaction = new SuppressTransactionProxy(client); PushScopeTransaction(suppressTransaction); return suppressTransaction; } - + + public void EndTransaction() { @@ -216,14 +181,6 @@ public void EndTransaction() currentTransaction?.Dispose(); } - /// - /// Registers to ambient System.Transactions.TransactionContext if needed - /// - public void RegisterToTransactionIfNeeded() - { - promotable?.EnlistIfNecessary(); - } - public Task EnqueueCypherRequest(string commandDescription, IGraphClient graphClient, CypherQuery query) { var policy = new CypherTransactionExecutionPolicy(graphClient); @@ -232,8 +189,10 @@ public Task EnqueueCypherRequest(string commandDescription, // we don't care about updating the object. var txContext = GetContext(query.CustomHeaders); txContext.CustomHeaders = query.CustomHeaders; + policy.Database = txContext.NeoTransaction.Database; // the main difference with a normal Request.With() call is that the request is associated with the // TX context. + return txContext.EnqueueTask(commandDescription, graphClient, policy, query); } diff --git a/Neo4jClient.Full.Shared/Transactions/TransactionScopeProxy.cs b/Neo4jClient/Transactions/TransactionScopeProxy.cs similarity index 75% rename from Neo4jClient.Full.Shared/Transactions/TransactionScopeProxy.cs rename to Neo4jClient/Transactions/TransactionScopeProxy.cs index 43de57083..ae2123f0e 100644 --- a/Neo4jClient.Full.Shared/Transactions/TransactionScopeProxy.cs +++ b/Neo4jClient/Transactions/TransactionScopeProxy.cs @@ -1,8 +1,11 @@ using System; using System.Collections.Specialized; +using System.Threading.Tasks; namespace Neo4jClient.Transactions { + using Neo4j.Driver; + /// /// 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 @@ -27,11 +30,12 @@ protected TransactionScopeProxy(ITransactionalGraphClient client, TransactionCon public Uri Endpoint { - get { return transactionContext.Endpoint; } - set { transactionContext.Endpoint = value; } + get => transactionContext.Endpoint; + set => transactionContext.Endpoint = value; } public NameValueCollection CustomHeaders { get; set; } + public Bookmark LastBookmark => throw new InvalidOperationException("This is not possible with the GraphClient. You would need the BoltGraphClient."); public virtual void Dispose() { @@ -44,7 +48,7 @@ public virtual void Dispose() client.EndTransaction(); if (!markCommitted && Committable && TransactionContext.IsOpen) { - Rollback(); + RollbackAsync().Wait(); // annoying, but can't dispose asynchronously } if (transactionContext != null && ShouldDisposeTransaction()) @@ -54,21 +58,23 @@ public virtual void Dispose() } } - public void Commit() + public string Database { get; set; } + + public Task CommitAsync() { markCommitted = true; if (CustomHeaders != null) { transactionContext.CustomHeaders = CustomHeaders; } - DoCommit(); + return DoCommitAsync(); } protected abstract bool ShouldDisposeTransaction(); - protected abstract void DoCommit(); + protected abstract Task DoCommitAsync(); public abstract bool Committable { get; } - public abstract void Rollback(); - public abstract void KeepAlive(); + public abstract Task RollbackAsync(); + public abstract Task KeepAliveAsync(); public abstract bool IsOpen { get; } } } diff --git a/Neo4jClient.Shared/UriCreator.cs b/Neo4jClient/UriCreator.cs similarity index 100% rename from Neo4jClient.Shared/UriCreator.cs rename to Neo4jClient/UriCreator.cs diff --git a/Neo4jClient.Shared/Utilities.cs b/Neo4jClient/Utilities.cs similarity index 100% rename from Neo4jClient.Shared/Utilities.cs rename to Neo4jClient/Utilities.cs diff --git a/Neo4jClient/project.json b/Neo4jClient/project.json deleted file mode 100644 index 49dfb6933..000000000 --- a/Neo4jClient/project.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "supports": {}, - "dependencies": { - "Microsoft.DependencyValidation.Analyzers": "0.9.0", - "Microsoft.NETCore.Portable.Compatibility": "1.0.1", - "Microsoft.VisualBasic": "10.2.0", - "Neo4j.Driver.Signed": "1.7.0", - "NETStandard.Library": "2.0.0", - "Newtonsoft.Json": "9.0.1", - "System.Collections": "4.3.0", - "System.Collections.NonGeneric": "4.3.0", - "System.ComponentModel": "4.3.0", - "System.ComponentModel.Annotations": "4.4.0", - "System.ComponentModel.TypeConverter": "4.3.0", - "System.Reflection.TypeExtensions": "4.4.0", - "System.Runtime.Serialization.Primitives": "4.3.0" - }, - "frameworks": { - "netstandard1.3": {} - } -} \ No newline at end of file diff --git a/README.md b/README.md index 1a187ff18..4f05aaad7 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,65 @@ -## What is Neo4jClient? +## Neo4jClient +--- -A .NET client for neo4j. Supports basic CRUD operations, Cypher and Gremlin queries via fluent interfaces, and some indexing operations. +A .NET client for [Neo4j](https://neo4j.com). Supports Cypher queries via fluent interfaces, and some indexing operations. Grab the latest drop straight from the `Neo4jClient` package on [NuGet](http://nuget.org/List/Packages/Neo4jClient). -Read [our wiki doco](https://github.com/Readify/Neo4jClient/wiki). +Read [our wiki docs](https://github.com/DotNet4Neo4j/Neo4jClient/wiki) - **Currently OUT OF DATE** +--- ## Current Builds -The official neo4jclient build and nuget package is automated via [AppVeyor](http://www.appveyor.com). +The official Neo4jClient build and nuget package is automated via [AppVeyor](http://www.appveyor.com). -### Stable (3.x) +--- +## Stable 4.x [![Build status](https://ci.appveyor.com/api/projects/status/q96upd53uq0hyepe?svg=true)](https://ci.appveyor.com/project/ChrisSkardon/neo4jclient) -[![Build status](https://ci.appveyor.com/api/projects/status/q96upd53uq0hyepe?svg=true)](https://ci.appveyor.com/project/ChrisSkardon/neo4jclient) +Version 4.0.0 of Neo4jClient is _now_ the stable version. There have been a lot of changes, additions, removals, so it's likely there will be breaking changes. -#### Changes in 3.x +--- + +## Changing from 3.x to 4.x + +This isn't an exhaustive list of things you need to do, but I'll try to add things if I've forgotten them. + +### Uris + +You will need to use the correct URI for the server version you are connecting to: + +#### GraphClient + * 3.x server: `http://localhost:7474/db/data` + * 4.x server: `http://localhost:7474/` + +#### BoltGraphClient + * 3.x or 4.x server: `neo4j://localhost:7687` + * Worth reviewing the [Neo4j Documentation](https://neo4j.com/docs/driver-manual/current/client-applications/#driver-configuration-examples) to see what you need to use. + + ### Async + + As this release is 100% `async` you will need to update any calls to `Results` or `ExecuteWithoutResults` to their `Async` equivalents. + +--- + +## Breaking Changes + +* Async endpoints only + * To get this release out, `Neo4jClient` is `Async` only now. +* Transactions will no longer use the `TransactionScope` class which means that [MSDTC](https://en.wikipedia.org/wiki/Microsoft_Distributed_Transaction_Coordinator) will no longer work. + * This has been an issue since the dawn of Core/NetStandard - `TransactionScope` may be in NetStandard now - but the other classes the Transaction code was relying on wasn't. +* The `GraphClient` and `BoltGraphClient` will no longer support Neo4j 3.4 or lower. + * Largely this is because the `Neo4j.Driver` that does the `Bolt` side of things only targets 3.5+, and keeping all the backwards compatibility means a lot of work, for little gain. + +### Dependency Changes + +* [Json.NET](https://www.nuget.org/packages/Newtonsoft.Json/) - `10.0.3` -> `12.0.3` +* [Neo4j.Driver.Signed](https://www.nuget.org/packages/Neo4j.Driver.Signed/4.1.1) - `1.7.2` -> `4.1.1` + +--- +## Historical Notes + +If you're changing from `2.x` to `3.x`, you'll want the below information, but you should really be on `4.x` unless you have to target an older DB instance. + +### Changes in 3.x * Bolt! * Transactions now use `AsyncLocal<>` instead of `ThreadStatic` @@ -22,14 +68,22 @@ The official neo4jclient build and nuget package is automated via [AppVeyor](htt * JSON.NET updated to 10.0.3 * `PathResults` doesn't work with Bolt, you need to use `PathResultsBolt` instead. -#### Dependency Changes in 2.0 +### Dependency Changes in 2.0 * JSON.NET updated to 9.0.1 -#### Breaking Changes in 2.0 +### Breaking Changes in 2.0 * If using the *DotNet Core* version of `Neo4jClient` - transactions will **not** work. This will be returned when DotNet Core gets the `TransactionScope` (See [this comment](https://github.com/Readify/Neo4jClient/issues/135#issuecomment-231981065) for more details). +--- + ## License Information Licensed under MS-PL. See `LICENSE` in the root of this repository for full license text. + +--- + +## Updates to the 3.x releases + +I will not be updating the 3.x version of the client, the focus is on 4.x and the features that gives us. Neo4j no longer actively support Neo4j 3.4 so you should consider updating if you can. Largely - anyone using the `3.x` version of the client is coping with it's deficiencies, and as 4.x addresses most of them. ¯\_(ツ)_/¯ \ No newline at end of file