diff --git a/community/server/pom.xml b/community/server/pom.xml index 986ae75d2e23e..396a060c58985 100644 --- a/community/server/pom.xml +++ b/community/server/pom.xml @@ -507,52 +507,6 @@ - - maven-antrun-plugin - 1.7 - - - - generate-source-based-documentation - process-classes - - - - - - - - - run - - - - - - ant-contrib - ant-contrib - 1.0b3 - - - ant - ant - - - - - org.apache.ant - ant - 1.8.2 - - - org.apache.ant - ant-apache-regexp - 1.8.2 - - - - diff --git a/community/server/src/docs/dev/images/jconsole_coordinator.png b/community/server/src/docs/dev/images/jconsole_coordinator.png deleted file mode 100644 index bbd0044242556..0000000000000 Binary files a/community/server/src/docs/dev/images/jconsole_coordinator.png and /dev/null differ diff --git a/community/server/src/docs/ops/powershell.asciidoc b/community/server/src/docs/ops/powershell.asciidoc deleted file mode 100755 index e751010a8fa1b..0000000000000 --- a/community/server/src/docs/ops/powershell.asciidoc +++ /dev/null @@ -1,126 +0,0 @@ -[[powershell]] -= Windows PowerShell module - -The Neo4j PowerShell module allows administrators to: - -* install, start and stop Neo4j Windows® Services -* and start tools, such as `Neo4j Shell` and `Neo4j Import`. - -The PowerShell module is installed as part of the http://neo4j.com/download/[ZIP file] distributions of Neo4j. - -[[powershell-requirements]] -== System Requirements - -* Requires PowerShell v2.0 or above. -* Supported on either 32 or 64 bit operating systems. - -[[powershell-windows]] -== Managing Neo4j on Windows - -On Windows it is sometimes necessary to _Unblock_ a downloaded zip file before you can import its contents as a module. If you right-click on the zip file and choose "Properties" you will get a dialog. Bottom-right on that dialog you will find an "Unblock" button. Click that. Then you should be able to import the module. - -Running scripts has to be enabled on the system. -This can for example be achieved by executing the following from an elevated PowerShell prompt: -[source,powershell] ----- -Set-ExecutionPolicy -ExecutionPolicy RemoteSigned ----- -For more information see https://technet.microsoft.com/en-us/library/hh847748.aspx[About execution policies]. - -The powershell module will display a warning if it detects that you do not have administrative rights. - -[[powershell-module-import]] -== How do I import the module? - -The module file is located in the _bin_ directory of your Neo4j installation, i.e. where you unzipped the downloaded file. -For example, if Neo4j was installed in _C:\Neo4j_ then the module would be imported like this: - -[source,powershell] ----- -Import-Module C:\Neo4j\bin\Neo4j-Management.psd1 ----- - -This will add the module to the current session. - -Once the module has been imported you can start an interactive console version of a Neo4j Server like this: - -[source,powershell] ----- -Invoke-Neo4j console ----- - -To stop the server, issue `Ctrl-C` in the console window that was created by the command. - -[[powershell-help]] -== How do I get help about the module? - -Once the module is imported you can query the available commands like this: - -[source,powershell] ----- -Get-Command -Module Neo4j-Management ----- - -The output should be similar to the following: - -[source] ----- -CommandType Name Version Source ------------ ---- ------- ------ -Function Invoke-Neo4j 3.0.0 Neo4j-Management -Function Invoke-Neo4jAdmin 3.0.0 Neo4j-Management -Function Invoke-Neo4jBackup 3.0.0 Neo4j-Management -Function Invoke-Neo4jImport 3.0.0 Neo4j-Management -Function Invoke-Neo4jShell 3.0.0 Neo4j-Management ----- - -The module also supports the standard PowerShell help commands. - -[source,powershell] ----- -Get-Help Invoke-Neo4j ----- - -To see examples for a command, do like this: - -[source,powershell] ----- -Get-Help Invoke-Neo4j -examples ----- - -[[powershell-examples]] -== Example usage - -* List of available commands: -+ -[source,powershell] ----- -Invoke-Neo4j ----- - -* Current status of the Neo4j service: -+ -[source,powershell] ----- -Invoke-Neo4j status ----- - -* Install the service with verbose output: -+ -[source,powershell] ----- -Invoke-Neo4j install-service -Verbose ----- - -* Available commands for administrative tasks: -+ -[source,powershell] ----- -Invoke-Neo4jAdmin ----- - -[[powershell-common-parameters]] -== Common PowerShell parameters - -The module commands support the common PowerShell parameter of `Verbose`. - diff --git a/community/server/src/docs/ops/security.asciidoc b/community/server/src/docs/ops/security.asciidoc deleted file mode 100644 index 9a2118866a321..0000000000000 --- a/community/server/src/docs/ops/security.asciidoc +++ /dev/null @@ -1,163 +0,0 @@ -[[security-server]] -= Securing Neo4j Server - -== Secure the port and remote client connection accepts == - -By default, the Neo4j Server is bundled with a Web server that binds to host +localhost+ on port +7474+, answering only requests from the local machine. - -This is configured in <>: - -[source,properties] ----- -# Let the webserver only listen on the specified IP. Default is localhost (only -# accept local connections). Uncomment to allow any connection. -dbms.connector.http.type=HTTP -dbms.connector.http.enabled=true -#dbms.connector.http.address=0.0.0.0:7474 ----- - -If you want the server to listen to external hosts, configure the Web server in _neo4j.conf_ by setting the property +dbms.connector.http.address=0.0.0.0:7474+ which will cause the server to bind to all available network interfaces. -Note that firewalls et cetera have to be configured accordingly as well. - -[[security-server-auth]] -== Server authentication and authorization == - -Neo4j requires clients to supply authentication credentials when accessing the REST API. -Without valid credentials, access to the database will be forbidden. - -The authentication and authorization data is stored under _data/dbms/auth_. -If necessary, this file can be copied over to other neo4j instances to ensure they share the same username/password (see <>). - -Please refer to <> for additional details. -When accessing Neo4j over unsecured networks, make sure HTTPS is configured and used for access (see <>). - -If necessary, authentication may be disabled. -This will allow any client to access the database without supplying authentication credentials. - -[source,properties] ----- -# Disable authorization -dbms.security.auth_enabled=false ----- - -[WARNING] -Disabling authentication is not recommended, and should only be done if the operator has a good understanding of their network security, including protection against http://en.wikipedia.org/wiki/Cross-site_scripting[cross-site scripting (XSS)] attacks via web browsers. -Developers should not disable authentication if they have a local installation using the default listening ports. - -[[security-server-https]] -== HTTPS support == - -The Neo4j server includes built in support for SSL encrypted communication over HTTPS. -The first time the server starts, it automatically generates a self-signed SSL certificate and a private key. -Because the certificate is self signed, it is not safe to rely on for production use, instead, you should provide your own key and certificate for the server to use. - -[CAUTION] -Using auto-generation of self-signed SSL certificates will not work if the Neo4j server has been configured with multiple connectors that bind to different IP addresses. -If you need to use multiple IP addresses, please configure certificates manually and use multi-host or wildcard certificates instead. - -To provide your own key and certificate put them in the <> directory. -The files must be named _neo4j.key_ and _neo4j.cert_. -The location of the directory can be configured by setting _dbms.directories.certificates_ in <>. - -[source,properties] ----- -# Certificates location (auto generated if the file does not exist) -dbms.directories.certificates=certificates - ----- - -Note that the key should be unencrypted. -Make sure you set correct permissions on the private key, so that only the Neo4j server user can read/write it. - -Neo4j also supports chained SSL certificates. -This requires to have all certificates in PEM format combined in one file and the private key needs to be in DER format. - -You can set what port the HTTPS connector should bind to in the same configuration file, as well as turn HTTPS on or off: - -[source,properties] ----- -dbms.connector.https.type=HTTP -dbms.connector.https.enabled=true -dbms.connector.https.encryption=TLS -dbms.connector.https.address=localhost:7473 ----- - -== Arbitrary code execution == - -[IMPORTANT] -The Neo4j server exposes remote scripting functionality by default that allow full access to the underlying system. -Exposing your server without implementing a security layer presents a substantial security vulnerability. - -By default, the Neo4j Server comes with some places where arbitrary code code execution can happen. These are the <> REST endpoints. -To secure these, either disable them completely by removing offending plugins from the server classpath, or secure access to these URLs through proxies or Authorization Rules. -Also, the Java Security Manager, see http://docs.oracle.com/javase/7/docs/technotes/guides/security/index.html, can be used to secure parts of the codebase. - -== Server authorization rules == - -Administrators may require more fine-grained security policies in addition to the basic authorization and/or IP-level restrictions on the Web server. -Neo4j server supports administrators in allowing or disallowing access the specific aspects of the database based on credentials that users or applications provide. - -To facilitate domain-specific authorization policies in Neo4j Server, security rules can be implemented and registered with the server. -This makes scenarios like user and role based security and authentication against external lookup services possible. -See +org.neo4j.server.rest.security.SecurityRule+ in the javadocs downloadable from http://search.maven.org/#search%7Cgav%7C1%7Cg%3A%22org.neo4j.app%22%20AND%20a%3A%22neo4j-server%22[Maven Central (org.neo4j.app:neo4j-server)]. - -[CAUTION] -The use of Server Authorization Rules may interact unexpectedly with the built-in authentication and authorization (see <>), if enabled. - -include::enforcing-server-authorization-rules.asciidoc[] - -include::using-wildcards-to-target-security-rules.asciidoc[] - -include::using-complex-wildcards-to-target-security-rules.asciidoc[] - - -== Using a proxy == - -Although the Neo4j server has a number of security features built-in (see the above chapters), for sensitive deployments it is often sensible to front against the outside world it with a proxy like Apache `mod_proxy` footnote:[http://httpd.apache.org/docs/2.2/mod/mod_proxy.html]. - -This provides a number of advantages: - -* Control access to the Neo4j server to specific IP addresses, URL patterns and IP ranges. This can be used to make for instance only the '/db/data' namespace accessible to non-local clients, while the '/db/admin' URLs only respond to a specific IP address. -+ -[source] ---------------- - - Order Deny,Allow - Deny from all - Allow from 192.168.0 - ---------------- -+ -While it is possible to develop plugins using Neo4j's `SecurityRule` (see above), operations professionals would often prefer to configure proxy servers such as Apache. -However, it should be noted that in cases where both approaches are being used, they will work harmoniously provided that the behavior is consistent across proxy server and `SecurityRule` plugins. - -* Run Neo4j Server as a non-root user on a Linux/Unix system on a port < 1000 (e.g. port 80) using -+ -[source] ---------------- -ProxyPass /neo4jdb/data http://localhost:7474/db/data -ProxyPassReverse /neo4jdb/data http://localhost:7474/db/data ---------------- - -* Simple load balancing in a clustered environment to load-balance read load using the Apache `mod_proxy_balancer` footnote:[http://httpd.apache.org/docs/2.2/mod/mod_proxy_balancer.html] plugin -+ -[source] --------------- - -BalancerMember http://192.168.1.50:80 -BalancerMember http://192.168.1.51:80 - -ProxyPass /test balancer://mycluster --------------- - -== LOAD CSV - -The Cypher `LOAD CSV` clause can load files from the filesystem, and its default configuration allows any file on the system to be read using a `file:///` URL. -This presents a security vulnerability in production environments where database users should not otherwise have access to files on the system. -For production deployments, configure the <> setting, which will make all files identified in a `file:///` URL relative to the specified directory, similarly to how a unix chroot works. -Alternatively, set the <> setting to false, which disables the use of `file:///` URLs entirely. -Further information can be found in <>. - -== Neo4j Web Interface Security - -For configuration settings to consider in order to get the level of security you want to achieve, see <>. diff --git a/community/server/src/docs/ops/server-debugging.asciidoc b/community/server/src/docs/ops/server-debugging.asciidoc deleted file mode 100644 index 896d3eb11e3cd..0000000000000 --- a/community/server/src/docs/ops/server-debugging.asciidoc +++ /dev/null @@ -1,16 +0,0 @@ -[[server-debugging]] -= Setup for remote debugging - -In order to configure the Neo4j server for remote debugging sessions, the Java debugging parameters need to be passed to the Java process through the configuration. -They live in the _conf/neo4j-wrapper.properties_ file. - -In order to specify the parameters, add a line for the additional Java arguments like this: - -[source,properties] ----- -dbms.jvm.additional=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 ----- - -This configuration will start a Neo4j server ready for remote debugging attachement at localhost and port `5005`. -Use these parameters to attach to the process from Eclipse, IntelliJ or your remote debugger of choice after starting the server. - diff --git a/community/server/src/docs/ops/server-installation.asciidoc b/community/server/src/docs/ops/server-installation.asciidoc deleted file mode 100644 index 08201749ce59e..0000000000000 --- a/community/server/src/docs/ops/server-installation.asciidoc +++ /dev/null @@ -1,152 +0,0 @@ -[[server-installation]] -= Server Installation - -== Deployment Scenarios == - -As a developer, you may wish to download Neo4j and run it locally on your desktop computer. -We recommend this as an easy way to discover Neo4j. - -* For Windows, see <>. -* For Unix/Linux, see <>. -* For OSX, see <>. - -As a systems administrator, you may wish to install Neo4j using a packaging system so you can ensure that a cluster of machines have identical installs. -See <> for more information on this. - -For information on High Availability, please refer to <>. - -== Prerequisites == - -With the exception of our Windows and Mac Installers, you'll need a Java Virtual Machine installed on your computer. -We recommend that you install http://openjdk.java.net/[OpenJDK 8], -http://www.oracle.com/technetwork/java/javase/downloads/index.html[Oracle Java 8] or -http://www.ibm.com/developerworks/java/jdk/[IBM Java 8] (POWER8 only). - -[[server-permissions]] -== Setting Proper File Permissions == - -When installing Neo4j Server, keep in mind that the _bin/neo4j_ executable will need to be run by some OS system user, and that user will need write permissions to some files/directories. -This goes specifically for the _data_ directory. -That user will also need execute permissions on other files, such as those in the _bin_ directory. - -It is recommended to either choose or create a user who will own and manage the Neo4j Server. -This user should own the entire Neo4j directory, so make sure to untar/unzip it as this user and not with `sudo` (UNIX/Linux/OSx) etc. - -If the _data_ directory is not writable by the user Neo4j won't be able to write anything either to the store. -As a result any logs would be appended to _neo4j.log_. -The following error message would indicate a possible permissions issue: `Write transactions to database disabled`. - -[[windows-install]] -== Windows == - -[[windows-installer]] -=== Windows Installer === - -1. Download the version that you want from http://neo4j.com/download/. - * Select the appropriate version and architecture for your platform. -2. Double-click the downloaded installer file. -3. Follow the prompts. - -[NOTE] -The installer will prompt to be granted Administrator privileges. -Newer versions of Windows come with a SmartScreen feature that may prevent the installer from running -- you can make it run anyway by clicking "More info" on the "Windows protected your PC" screen. - -[TIP] -If you install Neo4j using the windows installer and you already have an existing instance of Neo4j the installer will select a new install directory by default. -If you specify the same directory it will ask if you want to upgrade. -This should proceed without issue although some users have reported a `JRE is damaged` error. -If you see this error simply install Neo4j into a different location. - -[[windows-console]] -=== Windows Console Application === -1. Download the latest release from http://neo4j.com/download/. - * Select the appropriate Zip distribution. -2. Right-click the downloaded file, click Extract All. -3. Change directory to top-level extracted directory. - * Run `bin\neo4j console` -4. Stop the server by typing Ctrl-C in the console. - -[NOTE] -Some users have reported problems on Windows when using the ZoneAlarm firewall. -If you are having problems getting large responses from the server, or if the web interface does not work, try disabling ZoneAlarm. -Contact ZoneAlarm support to get information on how to resolve this. - -=== Windows service === - -Neo4j can also be run as a Windows service. -Install the service with `bin\neo4j install-service` and start it with `bin\neo4j start`. -Other commands available are `stop`, `restart`, `status` and `uninstall-service`. - -[[linux-install]] -== Linux == - -[[linux-packages]] -=== Linux Packages === - -* For Debian packages, see the instructions at http://debian.neo4j.org/. - -After installation you may have to do some platform specific configuration and performance tuning. -For that, refer to <>. - -[[unix-console]] -=== Unix Console Application === - -1. Download the latest release from http://neo4j.com/download/. - * Select the appropriate tar.gz distribution for your platform. -2. Extract the contents of the archive, using: `tar -xf ` - * Refer to the top-level extracted directory as: +NEO4J_HOME+ -3. Change directory to: `$NEO4J_HOME` - * Run: `./bin/neo4j console` -4. Stop the server by typing Ctrl-C in the console. - -=== Linux Service === - -The `neo4j` command can also be used with `start`, `stop`, `restart` or `status` instead of `console`. -By using these actions, you can create a Neo4j service. -See the <> for further details. - -[CAUTION] -This approach to running Neo4j as a service is deprecated. -We strongly advise you to run Neo4j from a package where feasible. - -You can build your own `init.d` script. -See for instance the Linux Standard Base specification on http://refspecs.linuxfoundation.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/tocsysinit.html[system initialization], or one of the many https://gist.github.com/chrisvest/7673244[samples] and http://www.linux.com/learn/tutorials/442412-managing-linux-daemons-with-init-scripts[tutorials]. - -[[osx-install]] -== Mac OSX == - -=== Mac OSX Installer === - -1. Download the _.dmg_ installer that you want from http://neo4j.com/download/. -2. Click the downloaded installer file. -3. Drag the Neo4j icon into the Applications folder. - -[TIP] -If you install Neo4j using the Mac installer and already have an existing instance of Neo4j the installer will ensure that both the old and new versions can co-exist on your system. - -=== Running Neo4j from the Terminal === - -The server can be started in the background from the terminal with the command `neo4j start`, and then stopped again with `neo4j stop`. -The server can also be started in the foreground with `neo4j console` -- then its log output will be printed to the terminal. - -The `neo4j-shell` command can be used to interact with Neo4j from the command line using Cypher. It will automatically connect to any -server that is running on localhost with the default port, otherwise it will show a help message. You can alternatively start the -shell with an embedded Neo4j instance, by using the `-path path/to/data` argument -- note that only a single instance of Neo4j -can access the database files at a time. - -=== OSX Service === - -Use the standard OSX system tools to create a service based on the `neo4j` command. - -=== A note on Java on OS X Mavericks === - -Unlike previous versions, OS X Mavericks does not come with Java pre-installed. You might encounter that the first time you run Neo4j, where OS X will trigger a popup offering you to install Java SE 6. - -Java SE 6 or 7 is incompatible with Neo4j {neo4j-version}, so we strongly advise you to skip installing Java SE 6 or 7 if you have no other uses for it. Instead, for Neo4j {neo4j-version} we recommend you install Java SE 8 from Oracle (http://www.oracle.com/technetwork/java/javase/downloads/index.html) as that is what we support for production use. - -== Multiple Server instances on one machine == - -Neo4j can be set up to run as several instances on one machine, providing for instance several databases for development. - -For how to set this up, see <>. -Just use the Neo4j edition of your choice, follow the guide and remember to not set the servers to run in HA mode. diff --git a/community/server/src/docs/ops/server-performance.asciidoc b/community/server/src/docs/ops/server-performance.asciidoc deleted file mode 100644 index 6c60c1ca2c873..0000000000000 --- a/community/server/src/docs/ops/server-performance.asciidoc +++ /dev/null @@ -1,31 +0,0 @@ -[[server-performance]] -Server Performance Tuning -========================= - -At the heart of the Neo4j server is a regular Neo4j storage engine instance. -That engine can be tuned in the same way as the other embedded configurations, using the same file format. - -Specifying Neo4j tuning properties ----------------------------------- - -In the server distribution <> is the main configuration file for Neo4j. -On restarting the server the tuning enhancements specified in this file will be loaded and configured into the underlying database engine. - -Specifying JVM tuning properties --------------------------------- - -Tuning the standalone server is achieved by editing the _neo4j-wrapper.conf_ file in the +conf+ directory of +NEO4J_HOME+. - -Edit the following properties: - -.neo4j-wrapper.conf JVM tuning properties -[options="header", cols=">. - diff --git a/community/server/src/test/java/org/neo4j/server/BatchOperationHeaderIT.java b/community/server/src/test/java/org/neo4j/server/BatchOperationHeaderIT.java new file mode 100644 index 0000000000000..e7d24bf72b050 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/BatchOperationHeaderIT.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.neo4j.server.rest.JaxRsResponse; +import org.neo4j.server.rest.PrettyJSON; +import org.neo4j.server.rest.RestRequest; +import org.neo4j.test.server.ExclusiveServerTestBase; + +import static org.dummy.web.service.DummyThirdPartyWebService.DUMMY_WEB_SERVICE_MOUNT_POINT; +import static org.junit.Assert.assertEquals; + +import static org.neo4j.server.helpers.CommunityServerBuilder.server; +import static org.neo4j.server.rest.domain.JsonHelper.jsonToList; + +public class BatchOperationHeaderIT extends ExclusiveServerTestBase +{ + private NeoServer server; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @Before + public void cleanTheDatabase() throws IOException + { + server = server().withThirdPartyJaxRsPackage( "org.dummy.web.service", + DUMMY_WEB_SERVICE_MOUNT_POINT ).usingDataDir( folder.getRoot().getAbsolutePath() ).build(); + server.start(); + } + + @After + public void stopServer() + { + if ( server != null ) + { + server.stop(); + } + } + + @Test + public void shouldPassHeaders() throws Exception + { + String jsonData = new PrettyJSON() + .array() + .object() + .key( "method" ).value( "GET" ) + .key( "to" ).value( "../.." + DUMMY_WEB_SERVICE_MOUNT_POINT + "/needs-auth-header" ) + .key( "body" ).object().endObject() + .endObject() + .endArray() + .toString(); + + JaxRsResponse response = new RestRequest( null, "user", "pass" ) + .post( "http://localhost:7474/db/data/batch", jsonData ); + + assertEquals( 200, response.getStatus() ); + + final List> responseData = jsonToList( response.getEntity() ); + + Map res = (Map) responseData.get( 0 ).get( "body" ); + + /* + * { + * Accept=[application/json], + * Content-Type=[application/json], + * Authorization=[Basic dXNlcjpwYXNz], + * User-Agent=[Java/1.6.0_27] <-- ignore that, it changes often + * Host=[localhost:7474], + * Connection=[keep-alive], + * Content-Length=[86] + * } + */ + assertEquals( "Basic dXNlcjpwYXNz", res.get( "Authorization" ) ); + assertEquals( "application/json", res.get( "Accept" ) ); + assertEquals( "application/json", res.get( "Content-Type" ) ); + assertEquals( "localhost:7474", res.get( "Host" ) ); + assertEquals( "keep-alive", res.get( "Connection" ) ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerDefaultPortAndHostnameIT.java b/community/server/src/test/java/org/neo4j/server/NeoServerDefaultPortAndHostnameIT.java new file mode 100644 index 0000000000000..efde711dbd40e --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/NeoServerDefaultPortAndHostnameIT.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server; + +import org.junit.Test; + +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.AbstractRestFunctionalTestBase; +import org.neo4j.server.rest.JaxRsResponse; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class NeoServerDefaultPortAndHostnameIT extends AbstractRestFunctionalTestBase +{ + @Test + public void shouldDefaultToSensiblePortIfNoneSpecifiedInConfig() throws Exception + { + FunctionalTestHelper functionalTestHelper = new FunctionalTestHelper( server() ); + + JaxRsResponse response = functionalTestHelper.get( functionalTestHelper.managementUri() ); + + assertThat( response.getStatus(), is( 200 ) ); + } + + @Test + public void shouldDefaultToLocalhostOfNoneSpecifiedInConfig() throws Exception + { + assertThat( server().baseUri().getHost(), is( "localhost" ) ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerIT.java b/community/server/src/test/java/org/neo4j/server/NeoServerIT.java new file mode 100644 index 0000000000000..2f798449c5943 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/NeoServerIT.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; + +import org.junit.Test; + +import org.neo4j.server.rest.AbstractRestFunctionalTestBase; +import org.neo4j.test.server.HTTP; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +public class NeoServerIT extends AbstractRestFunctionalTestBase +{ + @Test + public void whenServerIsStartedItshouldStartASingleDatabase() throws Exception + { + assertNotNull( server().getDatabase() ); + } + + @Test + public void shouldRedirectRootToBrowser() throws Exception + { + assertFalse( server().baseUri() + .toString() + .contains( "browser" ) ); + + HTTP.Response res = HTTP.withHeaders( HttpHeaders.ACCEPT, MediaType.TEXT_HTML ).GET( server().baseUri().toString() ); + assertThat( res.header( "Location" ), containsString( "browser") ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerJAXRSIT.java b/community/server/src/test/java/org/neo4j/server/NeoServerJAXRSIT.java new file mode 100644 index 0000000000000..9c0ae697d4932 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/NeoServerJAXRSIT.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server; + +import java.net.URI; + +import org.dummy.web.service.DummyThirdPartyWebService; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.server.helpers.CommunityServerBuilder; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.helpers.ServerHelper; +import org.neo4j.server.helpers.Transactor; +import org.neo4j.server.helpers.UnitOfWork; +import org.neo4j.server.rest.JaxRsResponse; +import org.neo4j.server.rest.RestRequest; +import org.neo4j.test.server.ExclusiveServerTestBase; + +import static org.junit.Assert.assertEquals; + +import static org.neo4j.server.helpers.FunctionalTestHelper.CLIENT; + +public class NeoServerJAXRSIT extends ExclusiveServerTestBase +{ + private NeoServer server; + + @Before + public void cleanTheDatabase() + { + ServerHelper.cleanTheDatabase( server ); + } + + @After + public void stopServer() + { + if ( server != null ) + { + server.stop(); + } + } + + @Test + public void shouldMakeJAXRSClassesAvailableViaHTTP() throws Exception + { + CommunityServerBuilder builder = CommunityServerBuilder.server(); + server = ServerHelper.createNonPersistentServer( builder ); + FunctionalTestHelper functionalTestHelper = new FunctionalTestHelper( server ); + + JaxRsResponse response = new RestRequest().get( functionalTestHelper.managementUri() ); + assertEquals( 200, response.getStatus() ); + response.close(); + } + + @Test + public void shouldLoadThirdPartyJaxRsClasses() throws Exception + { + server = CommunityServerBuilder.server() + .withThirdPartyJaxRsPackage( "org.dummy.web.service", + DummyThirdPartyWebService.DUMMY_WEB_SERVICE_MOUNT_POINT ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + + URI thirdPartyServiceUri = new URI( server.baseUri() + .toString() + DummyThirdPartyWebService.DUMMY_WEB_SERVICE_MOUNT_POINT ).normalize(); + String response = CLIENT.resource( thirdPartyServiceUri.toString() ) + .get( String.class ); + assertEquals( "hello", response ); + + // Assert that extensions gets initialized + int nodesCreated = createSimpleDatabase( server.getDatabase().getGraph() ); + thirdPartyServiceUri = new URI( server.baseUri() + .toString() + DummyThirdPartyWebService.DUMMY_WEB_SERVICE_MOUNT_POINT + "/inject-test" ).normalize(); + response = CLIENT.resource( thirdPartyServiceUri.toString() ) + .get( String.class ); + assertEquals( String.valueOf( nodesCreated ), response ); + } + + private int createSimpleDatabase( final GraphDatabaseAPI graph ) + { + final int numberOfNodes = 10; + new Transactor( graph, new UnitOfWork() + { + + @Override + public void doWork() + { + for ( int i = 0; i < numberOfNodes; i++ ) + { + graph.createNode(); + } + + for ( Node n1 : graph.getAllNodes() ) + { + for ( Node n2 : graph.getAllNodes() ) + { + if ( n1.equals( n2 ) ) + { + continue; + } + + n1.createRelationshipTo( n2, RelationshipType.withName( "REL" ) ); + } + } + } + } ).execute(); + + return numberOfNodes; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerPortConflictIT.java b/community/server/src/test/java/org/neo4j/server/NeoServerPortConflictIT.java new file mode 100644 index 0000000000000..f019de42baccb --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/NeoServerPortConflictIT.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; + +import org.junit.Test; + +import org.neo4j.helpers.HostnamePort; +import org.neo4j.logging.AssertableLogProvider; +import org.neo4j.server.helpers.CommunityServerBuilder; +import org.neo4j.test.server.ExclusiveServerTestBase; + +import static java.lang.String.format; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +public class NeoServerPortConflictIT extends ExclusiveServerTestBase +{ + @Test + public void shouldComplainIfServerPortIsAlreadyTaken() throws IOException, InterruptedException + { + HostnamePort contestedAddress = new HostnamePort( "localhost", 9999 ); + try ( ServerSocket ignored = new ServerSocket( + contestedAddress.getPort(), 0, InetAddress.getByName( contestedAddress.getHost() ) ) ) + { + AssertableLogProvider logProvider = new AssertableLogProvider(); + CommunityNeoServer server = CommunityServerBuilder.server( logProvider ) + .onAddress( contestedAddress ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + try + { + server.start(); + + fail( "Should have reported failure to start" ); + } + catch ( ServerStartupException e ) + { + assertThat( e.getMessage(), containsString( "Starting Neo4j failed" ) ); + } + + logProvider.assertAtLeastOnce( + AssertableLogProvider.inLog( containsString( "CommunityNeoServer" ) ).error( + "Failed to start Neo4j on %s: %s", + contestedAddress, + format( "Address %s is already in use, cannot bind to it.", contestedAddress ) + ) + ); + server.stop(); + } + } + + @Test + public void shouldComplainIfServerHTTPSPortIsAlreadyTaken() throws IOException, InterruptedException + { + HostnamePort unContestedAddress = new HostnamePort( "localhost", 8888 ); + HostnamePort contestedAddress = new HostnamePort( "localhost", 9999 ); + try ( ServerSocket ignored = new ServerSocket( + contestedAddress.getPort(), 0, InetAddress.getByName( contestedAddress.getHost() ) ) ) + { + AssertableLogProvider logProvider = new AssertableLogProvider(); + CommunityNeoServer server = CommunityServerBuilder.server( logProvider ) + .onAddress( unContestedAddress ) + .onHttpsAddress( contestedAddress ) + .withHttpsEnabled() + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + try + { + server.start(); + + fail( "Should have reported failure to start" ); + } + catch ( ServerStartupException e ) + { + assertThat( e.getMessage(), containsString( "Starting Neo4j failed" ) ); + } + + logProvider.assertAtLeastOnce( + AssertableLogProvider.inLog( containsString( "CommunityNeoServer" ) ).error( + "Failed to start Neo4j on %s: %s", + unContestedAddress, + format( "At least one of the addresses %s or %s is already in use, cannot bind to it.", + unContestedAddress, contestedAddress ) + ) + ); + server.stop(); + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerShutdownLoggingIT.java b/community/server/src/test/java/org/neo4j/server/NeoServerShutdownLoggingIT.java new file mode 100644 index 0000000000000..442dad4979330 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/NeoServerShutdownLoggingIT.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server; + +import java.io.IOException; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import org.neo4j.logging.AssertableLogProvider; +import org.neo4j.server.helpers.ServerHelper; +import org.neo4j.test.server.ExclusiveServerTestBase; + +public class NeoServerShutdownLoggingIT extends ExclusiveServerTestBase +{ + private AssertableLogProvider logProvider; + private NeoServer server; + + @Before + public void setupServer() throws IOException + { + logProvider = new AssertableLogProvider(); + server = ServerHelper.createNonPersistentServer( logProvider ); + ServerHelper.cleanTheDatabase( server ); + } + + @After + public void shutdownTheServer() + { + if ( server != null ) + { + server.stop(); + } + } + + @Test + public void shouldLogShutdown() throws Exception + { + server.stop(); + logProvider.assertContainsMessageContaining( "Stopped." ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerStartupLoggingIT.java b/community/server/src/test/java/org/neo4j/server/NeoServerStartupLoggingIT.java new file mode 100644 index 0000000000000..2c4548ff4d7fc --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/NeoServerStartupLoggingIT.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +import com.sun.jersey.api.client.Client; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.logging.FormattedLogProvider; +import org.neo4j.server.helpers.ServerHelper; +import org.neo4j.server.rest.JaxRsResponse; +import org.neo4j.server.rest.RestRequest; +import org.neo4j.test.server.ExclusiveServerTestBase; + +import static org.hamcrest.Matchers.greaterThan; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; + +public class NeoServerStartupLoggingIT extends ExclusiveServerTestBase +{ + private static ByteArrayOutputStream out; + private static NeoServer server; + + @BeforeClass + public static void setupServer() throws IOException + { + out = new ByteArrayOutputStream(); + server = ServerHelper.createNonPersistentServer( FormattedLogProvider.toOutputStream( out ) ); + } + + @Before + public void cleanTheDatabase() + { + ServerHelper.cleanTheDatabase( server ); + } + + @AfterClass + public static void stopServer() + { + server.stop(); + } + + @Test + public void shouldLogStartup() throws Exception + { + // Check the logs + assertThat( out.toString().length(), is( greaterThan( 0 ) ) ); + + // Check the server is alive + Client nonRedirectingClient = Client.create(); + nonRedirectingClient.setFollowRedirects( false ); + final JaxRsResponse response = new RestRequest(server.baseUri(), nonRedirectingClient).get(); + assertThat( response.getStatus(), is( greaterThan( 199 ) ) ); + + } +} diff --git a/community/server/src/test/java/org/neo4j/server/ServerConfigIT.java b/community/server/src/test/java/org/neo4j/server/ServerConfigIT.java new file mode 100644 index 0000000000000..610598372b148 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/ServerConfigIT.java @@ -0,0 +1,214 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server; + +import java.io.IOException; +import javax.ws.rs.core.MediaType; + +import org.junit.After; +import org.junit.Test; + +import org.neo4j.helpers.HostnamePort; +import org.neo4j.server.configuration.ServerSettings; +import org.neo4j.server.rest.JaxRsResponse; +import org.neo4j.server.rest.RestRequest; +import org.neo4j.server.scripting.javascript.GlobalJavascriptInitializer; +import org.neo4j.test.server.ExclusiveServerTestBase; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import static org.neo4j.server.helpers.CommunityServerBuilder.server; +import static org.neo4j.test.server.HTTP.POST; + +public class ServerConfigIT extends ExclusiveServerTestBase +{ + private CommunityNeoServer server; + + @After + public void stopTheServer() + { + server.stop(); + } + + @Test + public void shouldPickUpAddressFromConfig() throws Exception + { + HostnamePort nonDefaultAddress = new HostnamePort( "0.0.0.0", 4321 ); + server = server().onAddress( nonDefaultAddress ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + + assertEquals( nonDefaultAddress, server.getAddress() ); + + JaxRsResponse response = new RestRequest( server.baseUri() ).get(); + + assertThat( response.getStatus(), is( 200 ) ); + response.close(); + } + + @Test + public void shouldPickupRelativeUrisForMangementApiAndRestApi() throws IOException + { + String dataUri = "/a/different/data/uri/"; + String managementUri = "/a/different/management/uri/"; + + server = server().withRelativeRestApiUriPath( dataUri ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .withRelativeManagementApiUriPath( managementUri ) + .build(); + server.start(); + + JaxRsResponse response = new RestRequest().get( "http://localhost:7474" + dataUri, + MediaType.TEXT_HTML_TYPE ); + assertEquals( 200, response.getStatus() ); + + response = new RestRequest().get( "http://localhost:7474" + managementUri ); + assertEquals( 200, response.getStatus() ); + response.close(); + } + + @Test + public void shouldGenerateWADLWhenExplicitlyEnabledInConfig() throws IOException + { + server = server().withProperty( ServerSettings.wadl_enabled.name(), "true" ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + JaxRsResponse response = new RestRequest().get( "http://localhost:7474/application.wadl", + MediaType.WILDCARD_TYPE ); + + assertEquals( 200, response.getStatus() ); + assertEquals( "application/vnd.sun.wadl+xml", response.getHeaders().get( "Content-Type" ).iterator().next() ); + assertThat( response.getEntity(), containsString( "" ) ); + } + + @Test + public void shouldNotGenerateWADLWhenNotExplicitlyEnabledInConfig() throws IOException + { + server = server() + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + JaxRsResponse response = new RestRequest().get( "http://localhost:7474/application.wadl", + MediaType.WILDCARD_TYPE ); + + assertEquals( 404, response.getStatus() ); + } + + @Test + public void shouldNotGenerateWADLWhenExplicitlyDisabledInConfig() throws IOException + { + server = server().withProperty( ServerSettings.wadl_enabled.name(), "false" ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + JaxRsResponse response = new RestRequest().get( "http://localhost:7474/application.wadl", + MediaType.WILDCARD_TYPE ); + + assertEquals( 404, response.getStatus() ); + } + + @Test + public void shouldEnablConsoleServiceByDefault() throws IOException + { + // Given + server = server().usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ).build(); + server.start(); + + // When & then + assertEquals( 200, new RestRequest().get( "http://localhost:7474/db/manage/server/console" ).getStatus() ); + } + + @Test + public void shouldDisableConsoleServiceWhenAskedTo() throws IOException + { + // Given + server = server().withProperty( ServerSettings.console_module_enabled.name(), "false" ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + + // When & then + assertEquals( 404, new RestRequest().get( "http://localhost:7474/db/manage/server/console" ).getStatus() ); + } + + @Test + public void shouldHaveSandboxingEnabledByDefault() throws Exception + { + // Given + server = server() + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + String node = POST( server.baseUri().toASCIIString() + "db/data/node" ).location(); + + // When + JaxRsResponse response = new RestRequest().post( node + "/traverse/node", "{\n" + + " \"order\" : \"breadth_first\",\n" + + " \"return_filter\" : {\n" + + " \"body\" : \"position.getClass().getClassLoader()\",\n" + + " \"language\" : \"javascript\"\n" + + " },\n" + + " \"prune_evaluator\" : {\n" + + " \"body\" : \"position.getClass().getClassLoader()\",\n" + + " \"language\" : \"javascript\"\n" + + " },\n" + + " \"uniqueness\" : \"node_global\",\n" + + " \"relationships\" : [ {\n" + + " \"direction\" : \"all\",\n" + + " \"type\" : \"knows\"\n" + + " }, {\n" + + " \"direction\" : \"all\",\n" + + " \"type\" : \"loves\"\n" + + " } ],\n" + + " \"max_depth\" : 3\n" + + "}", MediaType.APPLICATION_JSON_TYPE ); + + // Then + assertEquals( 400, response.getStatus() ); + } + + /* + * We can't actually test that disabling sandboxing works, because of the set-once global nature of Rhino + * security. Instead, we test here that changing it triggers the expected exception, letting us know that + * the code that *would* have set it to disabled realizes it has already been set to sandboxed. + * + * This at least lets us know that the configuration attribute gets picked up and used. + */ + @Test(expected = RuntimeException.class) + public void shouldBeAbleToDisableSandboxing() throws Exception + { + // NOTE: This has to be initialized to sandboxed, because it can only be initialized once per JVM session, + // and all other tests depend on it being sandboxed. + GlobalJavascriptInitializer.initialize( GlobalJavascriptInitializer.Mode.SANDBOXED ); + + server = server().withProperty( ServerSettings.script_sandboxing_enabled.name(), "false" ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + + // When + server.start(); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/TransactionTimeoutIT.java b/community/server/src/test/java/org/neo4j/server/TransactionTimeoutIT.java new file mode 100644 index 0000000000000..ca4e255edd9d4 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/TransactionTimeoutIT.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server; + +import java.util.List; +import java.util.Map; + +import org.junit.After; +import org.junit.Test; + +import org.neo4j.server.configuration.ServerSettings; +import org.neo4j.test.server.ExclusiveServerTestBase; +import org.neo4j.test.server.HTTP; + +import static java.util.Arrays.asList; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.neo4j.helpers.collection.MapUtil.map; +import static org.neo4j.kernel.api.exceptions.Status.Transaction.TransactionNotFound; +import static org.neo4j.server.helpers.CommunityServerBuilder.server; + +public class TransactionTimeoutIT extends ExclusiveServerTestBase +{ + private CommunityNeoServer server; + + @After + public void stopTheServer() + { + server.stop(); + } + + @Test + public void shouldHonorReallyLowSessionTimeout() throws Exception + { + // Given + server = server().withProperty( ServerSettings.transaction_timeout.name(), "1" ).build(); + server.start(); + + String tx = HTTP.POST( txURI(), asList( map( "statement", "CREATE (n)" ) ) ).location(); + + // When + Thread.sleep( 1000 * 5 ); + Map response = HTTP.POST( tx + "/commit" ).content(); + + // Then + @SuppressWarnings("unchecked") + List> errors = (List>) response.get( "errors" ); + assertThat( (String) errors.get( 0 ).get( "code" ), equalTo( TransactionNotFound.code().serialize() ) ); + } + + private String txURI() + { + return server.baseUri().toString() + "db/data/transaction"; + } + +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/AutoIndexIT.java b/community/server/src/test/java/org/neo4j/server/rest/AutoIndexIT.java new file mode 100644 index 0000000000000..d92ec11b05a1c --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/AutoIndexIT.java @@ -0,0 +1,425 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.util.List; + +import org.junit.Test; + +import org.neo4j.graphdb.Transaction; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.web.RestfulGraphDatabase; +import org.neo4j.test.GraphDescription.Graph; +import org.neo4j.test.GraphDescription.NODE; +import org.neo4j.test.GraphDescription.PROP; +import org.neo4j.test.GraphDescription.REL; + +import static org.hamcrest.Matchers.hasItem; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class AutoIndexIT extends AbstractRestFunctionalTestBase +{ + /** + * Find node by query from an automatic index. + *

+ * See Find node by query for the actual query syntax. + */ + @Test + @Graph( nodes = {@NODE( name = "I", setNameProperty = true )}, autoIndexNodes = true ) + public void shouldRetrieveFromAutoIndexByQuery() + { + data.get(); + assertSize( 1, gen.get() + .expectedStatus( 200 ) + .get( nodeAutoIndexUri() + "?query=name:I" ) + .entity() ); + } + + private String nodeAutoIndexUri() + { + return getDataUri() + "index/auto/node/"; + } + + /** + * Automatic index nodes can be found via exact lookups with normal Index + * REST syntax. + */ + @Test + @Graph( nodes = {@NODE( name = "I", setNameProperty = true )}, autoIndexNodes = true ) + public void find_node_by_exact_match_from_an_automatic_index() + { + data.get(); + assertSize( 1, gen.get() + .expectedStatus( 200 ) + .get( nodeAutoIndexUri() + "name/I" ) + .entity() ); + } + + /** + * The automatic relationship index can not be removed. + */ + @Test + @Graph( nodes = {@NODE( name = "I", setNameProperty = true )}, autoIndexNodes = true ) + public void Relationship_AutoIndex_is_not_removable() + { + data.get(); + gen.get() + .expectedStatus( 405 ) + .delete( relationshipAutoIndexUri() ) + .entity(); + } + + /** + * The automatic node index can not be removed. + */ + @Test + @Graph( nodes = {@NODE( name = "I", setNameProperty = true )}, autoIndexNodes = true ) + public void AutoIndex_is_not_removable() + { + gen.get() + .expectedStatus( 405 ) + .delete( nodeAutoIndexUri() ) + .entity(); + } + + /** + * It is not allowed to add items manually to automatic indexes. + */ + @Test + @Graph( nodes = {@NODE( name = "I", setNameProperty = true )}, autoIndexNodes = true ) + public void items_can_not_be_added_manually_to_an_AutoIndex() throws Exception + { + data.get(); + String indexName; + try ( Transaction tx = graphdb().beginTx() ) + { + indexName = graphdb().index().getNodeAutoIndexer().getAutoIndex().getName(); + tx.success(); + } + + gen.get() + .expectedStatus( 405 ) + .payload( createJsonStringFor( getNodeUri( data.get() + .get( "I" ) ), "name", "I" ) ) + .post( postNodeIndexUri( indexName ) ) + .entity(); + + } + + private String createJsonStringFor( final String targetUri, final String key, final String value ) + { + return "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"uri\": \"" + targetUri + "\"}"; + } + + /** + * It is not allowed to add items manually to automatic indexes. + */ + @Test + @Graph( nodes = {@NODE( name = "I" ), @NODE( name = "you" )}, relationships = {@REL( start = "I", end = "you", + type = "know", properties = {@PROP( key = "since", value = "today" )} )}, autoIndexRelationships = true ) + public void items_can_not_be_added_manually_to_a_Relationship_AutoIndex() throws Exception + { + data.get(); + String indexName; + try ( Transaction tx = graphdb().beginTx() ) + { + indexName = graphdb().index().getRelationshipAutoIndexer().getAutoIndex().getName(); + tx.success(); + } + try ( Transaction tx = graphdb().beginTx() ) + { + gen.get() + .expectedStatus( 405 ) + .payload( createJsonStringFor( getRelationshipUri( data.get() + .get( "I" ) + .getRelationships() + .iterator() + .next() ), "name", "I" ) ) + .post( postRelationshipIndexUri( indexName ) ) + .entity(); + } + } + + /** + * It is not allowed to remove entries manually from automatic indexes. + */ + @Test + @Graph( nodes = {@NODE( name = "I", setNameProperty = true )}, autoIndexNodes = true ) + public void autoindexed_items_cannot_be_removed_manually() + { + long id = data.get() + .get( "I" ) + .getId(); + String indexName; + try ( Transaction tx = graphdb().beginTx() ) + { + indexName = graphdb().index().getNodeAutoIndexer().getAutoIndex().getName(); + tx.success(); + } + gen.get() + .expectedStatus( 405 ) + .delete( getDataUri() + "index/node/" + indexName + "/name/I/" + id ) + .entity(); + gen.get() + .expectedStatus( 405 ) + .delete( getDataUri() + "index/node/" + indexName + "/name/" + id ) + .entity(); + gen.get() + .expectedStatus( 405 ) + .delete( getDataUri() + "index/node/" + indexName + "/" + id ) + .entity(); + } + + /** + * It is not allowed to remove entries manually from automatic indexes. + */ + @Test + @Graph( nodes = {@NODE( name = "I" ), @NODE( name = "you" )}, relationships = {@REL( start = "I", end = "you", + type = "know", properties = {@PROP( key = "since", value = "today" )} )}, autoIndexRelationships = true ) + public void autoindexed_relationships_cannot_be_removed_manually() + { + try ( Transaction tx = graphdb().beginTx() ) + { + data.get(); + tx.success(); + } + + try ( Transaction tx = graphdb().beginTx() ) + { + long id = data.get() + .get( "I" ) + .getRelationships() + .iterator() + .next() + .getId(); + String indexName = graphdb().index() + .getRelationshipAutoIndexer() + .getAutoIndex() + .getName(); + gen.get() + .expectedStatus( 405 ) + .delete( getDataUri() + "index/relationship/" + indexName + "/since/today/" + id ) + .entity(); + gen.get() + .expectedStatus( 405 ) + .delete( getDataUri() + "index/relationship/" + indexName + "/since/" + id ) + .entity(); + gen.get() + .expectedStatus( 405 ) + .delete( getDataUri() + "index/relationship/" + indexName + "/" + id ) + .entity(); + } + } + + /** + * See the example request. + */ + @Test + @Graph( nodes = {@NODE( name = "I" ), @NODE( name = "you" )}, relationships = {@REL( start = "I", end = "you", + type = "know", properties = {@PROP( key = "since", value = "today" )} )}, autoIndexRelationships = true ) + public void Find_relationship_by_query_from_an_automatic_index() + { + data.get(); + assertSize( 1, gen.get() + .expectedStatus( 200 ) + .get( relationshipAutoIndexUri() + "?query=since:today" ) + .entity() ); + } + + /** + * See the example request. + */ + @Test + @Graph( nodes = {@NODE( name = "I" ), @NODE( name = "you" )}, relationships = {@REL( start = "I", end = "you", + type = "know", properties = {@PROP( key = "since", value = "today" )} )}, autoIndexRelationships = true ) + public void Find_relationship_by_exact_match_from_an_automatic_index() + { + data.get(); + assertSize( 1, gen.get() + .expectedStatus( 200 ) + .get( relationshipAutoIndexUri() + "since/today/" ) + .entity() ); + } + + /** + * Get current status for autoindexing on nodes. + */ + @Test + public void getCurrentStatusForNodes() + { + setEnabledAutoIndexingForType( "node", false ); + checkAndAssertAutoIndexerIsEnabled( "node", false ); + } + + /** + * Enable node autoindexing. + */ + @Test + public void enableNodeAutoIndexing() + { + setEnabledAutoIndexingForType( "node", true ); + checkAndAssertAutoIndexerIsEnabled( "node", true ); + } + + /** + * Add a property for autoindexing on nodes. + */ + @Test + public void addAutoIndexingPropertyForNodes() + { + gen.get() + .expectedStatus( 204 ) + .payload( "myProperty1" ) + .post( autoIndexURI( "node" ) + "/properties" ); + } + + /** + * Lookup list of properties being autoindexed. + */ + @Test + public void listAutoIndexingPropertiesForNodes() throws JsonParseException + { + int initialPropertiesSize = getAutoIndexedPropertiesForType( "node" ).size(); + + String propName = "some-property" + System.currentTimeMillis(); + server().getDatabase().getGraph().index().getNodeAutoIndexer().startAutoIndexingProperty( propName ); + + List properties = getAutoIndexedPropertiesForType( "node" ); + + assertEquals( initialPropertiesSize + 1, properties.size() ); + assertThat( properties, hasItem( propName ) ); + } + + /** + * Remove a property for autoindexing on nodes. + */ + @Test + public void removeAutoIndexingPropertyForNodes() + { + gen.get() + .expectedStatus( 204 ) + .delete( autoIndexURI( "node" ) + "/properties/myProperty1" ); + } + + @Test + public void switchOnOffAutoIndexingForNodes() + { + switchOnOffAutoIndexingForType( "node" ); + } + + @Test + public void switchOnOffAutoIndexingForRelationships() + { + switchOnOffAutoIndexingForType( "relationship" ); + } + + @Test + public void addRemoveAutoIndexedPropertyForNodes() throws JsonParseException + { + addRemoveAutoIndexedPropertyForType( "node" ); + } + + @Test + public void addRemoveAutoIndexedPropertyForRelationships() throws JsonParseException + { + addRemoveAutoIndexedPropertyForType( "relationship" ); + } + + private String relationshipAutoIndexUri() + { + return getDataUri() + "index/auto/relationship/"; + } + + private void addRemoveAutoIndexedPropertyForType( String uriPartForType ) throws JsonParseException + { + int intialPropertiesSize = getAutoIndexedPropertiesForType( uriPartForType ).size(); + + long millis = System.currentTimeMillis(); + String myProperty1 = uriPartForType + "-myProperty1-" + millis; + String myProperty2 = uriPartForType + "-myProperty2-" + millis; + + gen.get() + .expectedStatus( 204 ) + .payload( myProperty1 ) + .post( autoIndexURI( uriPartForType ) + "/properties" ); + gen.get() + .expectedStatus( 204 ) + .payload( myProperty2 ) + .post( autoIndexURI( uriPartForType ) + "/properties" ); + + List properties = getAutoIndexedPropertiesForType( uriPartForType ); + assertEquals( intialPropertiesSize + 2, properties.size() ); + assertTrue( properties.contains( myProperty1 ) ); + assertTrue( properties.contains( myProperty2 ) ); + + gen.get() + .expectedStatus( 204 ) + .payload( null ) + .delete( autoIndexURI( uriPartForType ) + + "/properties/" + myProperty2 ); + + properties = getAutoIndexedPropertiesForType( uriPartForType ); + assertEquals( intialPropertiesSize + 1, properties.size() ); + assertTrue( properties.contains( myProperty1 ) ); + } + + @SuppressWarnings( "unchecked" ) + private List getAutoIndexedPropertiesForType( String uriPartForType ) + throws JsonParseException + { + String result = gen.get() + .expectedStatus( 200 ) + .get( autoIndexURI( uriPartForType ) + "/properties" ).entity(); + return (List) JsonHelper.readJson( result ); + } + + private void switchOnOffAutoIndexingForType( String uriPartForType ) + { + setEnabledAutoIndexingForType( uriPartForType, true ); + checkAndAssertAutoIndexerIsEnabled( uriPartForType, true ); + setEnabledAutoIndexingForType( uriPartForType, false ); + checkAndAssertAutoIndexerIsEnabled( uriPartForType, false ); + } + + private void setEnabledAutoIndexingForType( String uriPartForType, boolean enabled ) + { + gen.get() + .expectedStatus( 204 ) + .payload( Boolean.toString( enabled ) ) + .put( autoIndexURI( uriPartForType ) + "/status" ); + } + + private void checkAndAssertAutoIndexerIsEnabled( String uriPartForType, boolean enabled ) + { + String result = gen.get() + .expectedStatus( 200 ) + .get( autoIndexURI( uriPartForType ) + "/status" ).entity(); + assertEquals( enabled, Boolean.parseBoolean( result ) ); + } + + private String autoIndexURI( String type ) + { + return getDataUri() + + RestfulGraphDatabase.PATH_AUTO_INDEX.replace( "{type}", type ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/AutoIndexWithNonDefaultConfigurationThroughRESTAPIIT.java b/community/server/src/test/java/org/neo4j/server/rest/AutoIndexWithNonDefaultConfigurationThroughRESTAPIIT.java new file mode 100644 index 0000000000000..b73381c516417 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/AutoIndexWithNonDefaultConfigurationThroughRESTAPIIT.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.neo4j.server.CommunityNeoServer; +import org.neo4j.server.helpers.CommunityServerBuilder; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.helpers.ServerHelper; +import org.neo4j.test.TestData; +import org.neo4j.test.server.ExclusiveServerTestBase; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; + +public class AutoIndexWithNonDefaultConfigurationThroughRESTAPIIT extends ExclusiveServerTestBase +{ + private static CommunityNeoServer server; + private static FunctionalTestHelper functionalTestHelper; + + @ClassRule + public static TemporaryFolder staticFolder = new TemporaryFolder(); + + public + @Rule + TestData gen = TestData.producedThrough( RESTDocsGenerator.PRODUCER ); + + @Before + public void setUp() + { + gen.get().setSection( "dev/rest-api" ); + } + + @BeforeClass + public static void allocateServer() throws IOException + { + server = CommunityServerBuilder.server() + .usingDataDir( staticFolder.getRoot().getAbsolutePath() ) + .withAutoIndexingEnabledForNodes( "foo", "bar" ) + .build(); + server.start(); + functionalTestHelper = new FunctionalTestHelper( server ); + } + + @Before + public void cleanTheDatabase() + { + ServerHelper.cleanTheDatabase( server ); + } + + @AfterClass + public static void stopServer() + { + server.stop(); + } + + /** + * Create an auto index for nodes with specific configuration. + */ + @Test + public void shouldCreateANodeAutoIndexWithGivenFullTextConfiguration() throws Exception + { + String responseBody = gen.get() + .expectedStatus( 201 ) + .payload( "{\"name\":\"node_auto_index\", \"config\":{\"type\":\"fulltext\",\"provider\":\"lucene\"}}" ) + .post( functionalTestHelper.nodeIndexUri() ) + .entity(); + + assertThat( responseBody, containsString( "\"type\" : \"fulltext\"" ) ); + } + + /** + * Create an auto index for relationships with specific configuration. + */ + @Test + public void shouldCreateARelationshipAutoIndexWithGivenFullTextConfiguration() throws Exception + { + String responseBody = gen.get() + .expectedStatus( 201 ) + .payload( + "{\"name\":\"relationship_auto_index\", \"config\":{\"type\":\"fulltext\"," + + "\"provider\":\"lucene\"}}" ) + .post( functionalTestHelper.relationshipIndexUri() ) + .entity(); + + assertThat( responseBody, containsString( "\"type\" : \"fulltext\"" ) ); + } + +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/BatchOperationIT.java b/community/server/src/test/java/org/neo4j/server/rest/BatchOperationIT.java new file mode 100644 index 0000000000000..3c1af928f9221 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/BatchOperationIT.java @@ -0,0 +1,773 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import com.sun.jersey.api.client.ClientHandlerException; +import com.sun.jersey.api.client.UniformInterfaceException; +import org.codehaus.jackson.JsonNode; +import org.json.JSONException; +import org.junit.Test; + +import java.util.List; +import java.util.Map; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.ServerTestUtils; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription.Graph; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.neo4j.graphdb.Neo4jMatchers.hasProperty; +import static org.neo4j.graphdb.Neo4jMatchers.inTx; + +public class BatchOperationIT extends AbstractRestFunctionalDocTestBase +{ + + @Documented( "Execute multiple operations in batch.\n" + + "\n" + + "The batch service expects an array of job descriptions as input, each job\n" + + "description describing an action to be performed via the normal server\n" + + "API.\n" + + "\n" + + "Each job description should contain a +to+ attribute, with a value\n" + + "relative to the data API root (so http://localhost:7474/db/data/node becomes\n" + + "just /node), and a +method+ attribute containing HTTP verb to use.\n" + + "\n" + + "Optionally you may provide a +body+ attribute, and an +id+ attribute to\n" + + "help you keep track of responses, although responses are guaranteed to be\n" + + "returned in the same order the job descriptions are received.\n" + + "\n" + + "The following figure outlines the different parts of the job\n" + + "descriptions:\n" + + "\n" + + "image::batch-request-api.png[]" ) + @SuppressWarnings( "unchecked" ) + @Test + @Graph("Joe knows John") + public void shouldPerformMultipleOperations() throws Exception + { + long idJoe = data.get().get( "Joe" ).getId(); + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("PUT") + .key("to") .value("/node/" + idJoe + "/properties") + .key("body") + .object() + .key("age").value(1) + .endObject() + .key("id") .value(0) + .endObject() + .object() + .key("method") .value("GET") + .key("to") .value("/node/" + idJoe) + .key("id") .value(1) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age").value(1) + .endObject() + .key("id") .value(2) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age").value(1) + .endObject() + .key("id") .value(3) + .endObject() + .endArray().toString(); + + + String entity = gen.get() + .description( startGraph( "execute multiple operations in batch" ) ) + .payload(jsonString) + .expectedStatus(200) + .post(batchUri()).entity(); + + List> results = JsonHelper.jsonToList(entity); + + assertEquals(4, results.size()); + + Map putResult = results.get(0); + Map getResult = results.get(1); + Map firstPostResult = results.get(2); + Map secondPostResult = results.get(3); + + // Ids should be ok + assertEquals(0, putResult.get("id")); + assertEquals(2, firstPostResult.get("id")); + assertEquals(3, secondPostResult.get("id")); + + // Should contain "from" + assertEquals("/node/"+idJoe+"/properties", putResult.get("from")); + assertEquals("/node/"+idJoe, getResult.get("from")); + assertEquals("/node", firstPostResult.get("from")); + assertEquals("/node", secondPostResult.get("from")); + + // Post should contain location + assertTrue(((String) firstPostResult.get("location")).length() > 0); + assertTrue(((String) secondPostResult.get("location")).length() > 0); + + // Should have created by the first PUT request + Map body = (Map) getResult.get("body"); + assertEquals(1, ((Map) body.get("data")).get("age")); + + + } + + @Documented( "Refer to items created earlier in the same batch job.\n" + + "\n" + + "The batch operation API allows you to refer to the URI returned from a\n" + + "created resource in subsequent job descriptions, within the same batch\n" + + "call.\n" + + "\n" + + "Use the +{[JOB ID]}+ special syntax to inject URIs from created resources\n" + + "into JSON strings in subsequent job descriptions." ) + @Test + public void shouldBeAbleToReferToCreatedResource() throws Exception + { + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("id") .value(0) + .key("body") + .object() + .key("name").value("bob") + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("id") .value(1) + .key("body") + .object() + .key("age").value(12) + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("{0}/relationships") + .key("id") .value(3) + .key("body") + .object() + .key("to").value("{1}") + .key("data") + .object() + .key("since").value("2010") + .endObject() + .key("type").value("KNOWS") + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/index/relationship/my_rels") + .key("id") .value(4) + .key("body") + .object() + .key("key").value("since") + .key("value").value("2010") + .key("uri").value("{3}") + .endObject() + .endObject() + .endArray().toString(); + + String entity = gen.get() + .expectedStatus( 200 ) + .payload( jsonString ) + .post( batchUri() ) + .entity(); + + List> results = JsonHelper.jsonToList(entity); + + assertEquals(4, results.size()); + +// String rels = gen.get() +// .expectedStatus( 200 ).get( getRelationshipIndexUri( "my_rels", "since", "2010")).entity(); +// assertEquals(1, JsonHelper.jsonToList( rels ).size()); + } + + private String batchUri() + { + return getDataUri()+"batch"; + } + + @Test + public void shouldGetLocationHeadersWhenCreatingThings() throws Exception + { + int originalNodeCount = countNodes(); + + final String jsonString = new PrettyJSON() + .array() + .object() + .key("method").value("POST") + .key("to").value("/node") + .key("body") + .object() + .key("age").value(1) + .endObject() + .endObject() + .endArray().toString(); + + JaxRsResponse response = RestRequest.req().post(batchUri(), jsonString); + + assertEquals(200, response.getStatus()); + assertEquals(originalNodeCount + 1, countNodes()); + + List> results = JsonHelper.jsonToList(response.getEntity()); + + assertEquals(1, results.size()); + + Map result = results.get(0); + assertTrue(((String) result.get("location")).length() > 0); + } + + @Test + public void shouldForwardUnderlyingErrors() throws Exception + { + JaxRsResponse response = RestRequest.req().post(batchUri(), new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age") + .array() + .value(true) + .value("hello") + .endArray() + .endObject() + .endObject() + .endArray() + .toString()); + assertEquals(500, response.getStatus()); + Map res = JsonHelper.jsonToMap(response.getEntity()); + + assertTrue(((String)res.get("message")).startsWith("Invalid JSON array in POST body")); + } + + @Test + public void shouldRollbackAllWhenGivenIncorrectRequest() throws ClientHandlerException, + UniformInterfaceException, JSONException + { + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age").value("1") + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .array() + .value("a_list") + .value("this_makes_no_sense") + .endArray() + .endObject() + .endArray() + .toString(); + + int originalNodeCount = countNodes(); + + JaxRsResponse response = RestRequest.req().post(batchUri(), jsonString); + + assertEquals(500, response.getStatus()); + assertEquals(originalNodeCount, countNodes()); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldHandleUnicodeGetCorrectly() throws Exception + { + String asianText = "\u4f8b\u5b50"; + String germanText = "öäüÖÄÜß"; + + String complicatedString = asianText + germanText; + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") .object() + .key(complicatedString).value(complicatedString) + .endObject() + .endObject() + .endArray() + .toString(); + + String entity = gen.get() + .expectedStatus( 200 ) + .payload( jsonString ) + .post( batchUri() ) + .entity(); + + // Pull out the property value from the depths of the response + Map response = (Map) JsonHelper.jsonToList(entity).get(0).get("body"); + String returnedValue = (String)((Map)response.get("data")).get(complicatedString); + + // Ensure nothing was borked. + assertThat("Expected twisted unicode case to work, but response was: " + entity, + returnedValue, is(complicatedString)); + } + + @Test + public void shouldHandleFailingCypherStatementCorrectly() throws Exception + { + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/cypher") + .key("body") .object() + .key("query").value("create (n) set n.foo = {maps:'not welcome'} return n") + .key("params").object().key("id").value("0").endObject() + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .endObject() + .endArray() + .toString(); + + String entity = gen.get() + .expectedStatus( 500 ) + .payload( jsonString ) + .post( batchUri() ) + .entity(); + + // Pull out the property value from the depths of the response + Map result = JsonHelper.jsonToMap(entity); + String exception = (String) result.get("exception"); + assertThat(exception, is("BatchOperationFailedException")); + String innerException = (String) ((Map) JsonHelper.jsonToMap((String) result.get("message"))).get("exception"); + assertThat(innerException, is("CypherTypeException")); + } + + @Test + @Graph("Peter likes Jazz") + public void shouldHandleEscapedStrings() throws ClientHandlerException, + UniformInterfaceException, JSONException, JsonParseException + { + String string = "Jazz"; + Node gnode = getNode( string ); + assertThat( gnode, inTx(graphdb(), hasProperty( "name" ).withValue(string)) ); + + String name = "string\\ and \"test\""; + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("PUT") + .key("to") .value("/node/"+gnode.getId()+"/properties") + .key("body") + .object() + .key("name").value(name) + .endObject() + .endObject() + .endArray() + .toString(); + gen.get() + .expectedStatus( 200 ) + .payload( jsonString ) + .post( batchUri() ) + .entity(); + + jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("GET") + .key("to") .value("/node/"+gnode.getId()+"/properties/name") + .endObject() + .endArray() + .toString(); + String entity = gen.get() + .expectedStatus( 200 ) + .payload( jsonString ) + .post( batchUri() ) + .entity(); + + List> results = JsonHelper.jsonToList(entity); + assertEquals(results.get(0).get("body"), name); + } + + @Test + public void shouldRollbackAllWhenInsertingIllegalData() throws ClientHandlerException, + UniformInterfaceException, JSONException + { + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age").value(1) + .endObject() + .endObject() + + .object() + .key("method").value("POST") + .key("to").value("/node") + .key("body") + .object() + .key("age") + .object() + .key("age").value(1) + .endObject() + .endObject() + .endObject() + + .endArray().toString(); + + int originalNodeCount = countNodes(); + + JaxRsResponse response = RestRequest.req().post(batchUri(), jsonString); + + assertEquals(500, response.getStatus()); + assertEquals(originalNodeCount, countNodes()); + + } + + @Test + public void shouldRollbackAllOnSingle404() throws ClientHandlerException, + UniformInterfaceException, JSONException + { + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age").value(1) + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("www.google.com") + .endObject() + + .endArray().toString(); + + int originalNodeCount = countNodes(); + + JaxRsResponse response = RestRequest.req().post(batchUri(), jsonString); + + assertEquals(500, response.getStatus()); + assertEquals(originalNodeCount, countNodes()); + } + + @Test + public void shouldBeAbleToReferToUniquelyCreatedEntities() throws Exception + { + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/index/node/Cultures?unique") + .key("body") + .object() + .key("key").value("ID") + .key("value").value("fra") + .key("properties") + .object() + .key("ID").value("fra") + .endObject() + .endObject() + .key("id") .value(0) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("id") .value(1) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("{1}/relationships") + .key("body") + .object() + .key("to").value("{0}") + .key("type").value("has") + .endObject() + .key("id") .value(2) + .endObject() + .endArray().toString(); + + JaxRsResponse response = RestRequest.req().post(batchUri(), jsonString); + + assertEquals(200, response.getStatus()); + + } + + @Test + public void shouldNotFailWhenRemovingAndAddingLabelsInOneBatch() throws Exception + { + // given + + /* + curl -X POST http://localhost:7474/db/data/batch -H 'Content-Type: application/json' + -d '[ + {"body":{"name":"Alice"},"to":"node","id":0,"method":"POST"}, + {"body":["expert","coder"],"to":"{0}/labels","id":1,"method":"POST"}, + {"body":["novice","chef"],"to":"{0}/labels","id":2,"method":"PUT"} + ]' + */ + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("node") + .key("id") .value(0) + .key("body") + .object() + .key("key").value("name") + .key("value").value("Alice") + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("{0}/labels") + .key("id") .value(1) + .key("body") + .array() + .value( "expert" ) + .value( "coder" ) + .endArray() + .endObject() + .object() + .key("method") .value("PUT") + .key("to") .value("{0}/labels") + .key("id") .value(2) + .key("body") + .array() + .value( "novice" ) + .value( "chef" ) + .endArray() + .endObject() + .endArray().toString(); + + // when + JaxRsResponse response = RestRequest.req().post(batchUri(), jsonString); + + // then + assertEquals(200, response.getStatus()); + } + + // It has to be possible to create relationships among created and not-created nodes + // in batch operation. Tests the fix for issue #690. + @Test + public void shouldBeAbleToReferToNotCreatedUniqueEntities() throws Exception + { + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/index/node/Cultures?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("tobias") + .key("properties") + .object() + .key("name").value("Tobias Tester") + .endObject() + .endObject() + .key("id") .value(0) + .endObject() + .object() // Creates Andres, hence 201 Create + .key("method") .value("POST") + .key("to") .value("/index/node/Cultures?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("andres") + .key("properties") + .object() + .key("name").value("Andres Tester") + .endObject() + .endObject() + .key("id") .value(1) + .endObject() + .object() // Duplicated to ID.1, hence 200 OK + .key("method") .value("POST") + .key("to") .value("/index/node/Cultures?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("andres") + .key("properties") + .object() + .key("name").value("Andres Tester") + .endObject() + .endObject() + .key("id") .value(2) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/index/relationship/my_rels/?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("tobias-andres") + .key("start").value("{0}") + .key("end").value("{1}") + .key("type").value("FRIENDS") + .endObject() + .key("id") .value(3) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/index/relationship/my_rels/?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("andres-tobias") + .key("start").value("{2}") // Not-created entity here + .key("end").value("{0}") + .key("type").value("FRIENDS") + .endObject() + .key("id") .value(4) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/index/relationship/my_rels/?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("andres-tobias") + .key("start").value("{1}") // Relationship should not be created + .key("end").value("{0}") + .key("type").value("FRIENDS") + .endObject() + .key("id") .value(5) + .endObject() + .endArray().toString(); + + JaxRsResponse response = RestRequest.req().post(batchUri(), jsonString); + + assertEquals(200, response.getStatus()); + + final String entity = response.getEntity(); + List> results = JsonHelper.jsonToList(entity); + assertEquals(6, results.size()); + Map andresResult1 = results.get(1); + Map andresResult2 = results.get(2); + Map secondRelationship = results.get(4); + Map thirdRelationship = results.get(5); + + // Same people + Map body1 = (Map) andresResult1.get("body"); + Map body2 = (Map) andresResult2.get("body"); + assertEquals(body1.get("id"), body2.get("id")); + // Same relationship + body1 = (Map) secondRelationship.get("body"); + body2 = (Map) thirdRelationship.get("body"); + assertEquals(body1.get("self"), body2.get("self")); + // Created for {2} {0} + assertTrue(((String) secondRelationship.get("location")).length() > 0); + // {2} = {1} = Andres + body1 = (Map) secondRelationship.get("body"); + body2 = (Map) andresResult1.get("body"); + assertEquals(body1.get("start"), body2.get("self")); + } + + @Test + public void shouldFailWhenUsingPeriodicCommitViaNewTxEndpoint() throws Exception + { + ServerTestUtils.withCSVFile( 1, new ServerTestUtils.BlockWithCSVFileURL() + { + @Override + public void execute( String url ) throws Exception + { + // Given + String jsonString = new PrettyJSON() + .array() + .object() + .key( "method" ).value("POST") + .key( "to" ).value("/transaction/commit") + .key( "body" ).object() + .key("statements").array() + .object().key( "statement" ).value( "USING PERIODIC COMMIT LOAD CSV FROM '" + url + "' AS line CREATE ()" ).endObject() + .endArray() + .endObject() + .endObject() + .endArray() + .toString(); + + // When + JsonNode result = JsonHelper.jsonNode(gen.get() + .expectedStatus(200) + .payload(jsonString) + .post(batchUri()) + .entity()); + + // Then + JsonNode results = result.get(0).get("body").get("results"); + JsonNode errors = result.get(0).get("body").get("errors"); + + assertTrue( "Results not an array", results.isArray() ); + assertEquals( 0, results.size() ); + assertTrue( "Errors not an array", errors.isArray() ); + assertEquals( 1, errors.size() ); + + String errorCode = errors.get(0).get("code").getTextValue(); + assertEquals( "Neo.ClientError.Statement.SemanticError", errorCode ); + } + } ); + } + + private int countNodes() + { + try ( Transaction tx = graphdb().beginTx() ) + { + int count = 0; + for(Node node : graphdb().getAllNodes()) + { + count++; + } + return count; + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/CompactJsonIT.java b/community/server/src/test/java/org/neo4j/server/rest/CompactJsonIT.java new file mode 100644 index 0000000000000..b596ecfdf6c9a --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/CompactJsonIT.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.util.Collections; +import javax.ws.rs.core.Response.Status; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.repr.formats.CompactJsonFormat; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class CompactJsonIT extends AbstractRestFunctionalTestBase +{ + private long thomasAnderson; + private long trinity; + private long thomasAndersonLovesTrinity; + + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + } + + @Before + public void setupTheDatabase() + { + createTheMatrix(); + } + + private void createTheMatrix() + { + // Create the matrix example + thomasAnderson = createAndIndexNode( "Thomas Anderson" ); + trinity = createAndIndexNode( "Trinity" ); + long tank = createAndIndexNode( "Tank" ); + + long knowsRelationshipId = helper.createRelationship( "KNOWS", thomasAnderson, trinity ); + thomasAndersonLovesTrinity = helper.createRelationship( "LOVES", thomasAnderson, trinity ); + helper.setRelationshipProperties( thomasAndersonLovesTrinity, + Collections.singletonMap( "strength", (Object) 100 ) ); + helper.createRelationship( "KNOWS", thomasAnderson, tank ); + helper.createRelationship( "KNOWS", trinity, tank ); + + // index a relationship + helper.createRelationshipIndex( "relationships" ); + helper.addRelationshipToIndex( "relationships", "key", "value", knowsRelationshipId ); + + // index a relationship + helper.createRelationshipIndex( "relationships2" ); + helper.addRelationshipToIndex( "relationships2", "key2", "value2", knowsRelationshipId ); + } + + private long createAndIndexNode( String name ) + { + long id = helper.createNode(); + helper.setNodeProperties( id, Collections.singletonMap( "name", (Object) name ) ); + helper.addNodeToIndex( "node", "name", name, id ); + return id; + } + + @Test + public void shouldGetThomasAndersonDirectly() { + JaxRsResponse response = RestRequest.req().get(functionalTestHelper.nodeUri(thomasAnderson), CompactJsonFormat.MEDIA_TYPE); + assertEquals(Status.OK.getStatusCode(), response.getStatus()); + String entity = response.getEntity(); + assertTrue(entity.contains("Thomas Anderson")); + assertValidJson(entity); + response.close(); + } + + private void assertValidJson( String entity ) + { + try + { + assertTrue( JsonHelper.jsonToMap( entity ) + .containsKey( "self" ) ); + assertFalse( JsonHelper.jsonToMap( entity ) + .containsKey( "properties" ) ); + } + catch ( JsonParseException e ) + { + e.printStackTrace(); + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/ConfigureBaseUriIT.java b/community/server/src/test/java/org/neo4j/server/rest/ConfigureBaseUriIT.java new file mode 100644 index 0000000000000..0f3bcd9f745d8 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/ConfigureBaseUriIT.java @@ -0,0 +1,235 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.net.URI; + +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.server.helpers.FunctionalTestHelper; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class ConfigureBaseUriIT extends AbstractRestFunctionalTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + } + + @Test + public void shouldForwardHttpAndHost() throws Exception + { + URI rootUri = functionalTestHelper.baseUri(); + + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( rootUri ); + + httpget.setHeader( "Accept", "application/json" ); + httpget.setHeader( "X-Forwarded-Host", "foobar.com" ); + httpget.setHeader( "X-Forwarded-Proto", "http" ); + + HttpResponse response = httpclient.execute( httpget ); + + String length = response.getHeaders( "CONTENT-LENGTH" )[0].getValue(); + byte[] data = new byte[Integer.valueOf( length )]; + response.getEntity().getContent().read( data ); + + String responseEntityBody = new String( data ); + + assertTrue( responseEntityBody.contains( "http://foobar.com" ) ); + assertFalse( responseEntityBody.contains( "localhost" ) ); + } + finally + { + httpclient.getConnectionManager().shutdown(); + } + + } + + @Test + public void shouldForwardHttpsAndHost() throws Exception + { + URI rootUri = functionalTestHelper.baseUri(); + + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( rootUri ); + + httpget.setHeader( "Accept", "application/json" ); + httpget.setHeader( "X-Forwarded-Host", "foobar.com" ); + httpget.setHeader( "X-Forwarded-Proto", "https" ); + + HttpResponse response = httpclient.execute( httpget ); + + String length = response.getHeaders( "CONTENT-LENGTH" )[0].getValue(); + byte[] data = new byte[Integer.valueOf( length )]; + response.getEntity().getContent().read( data ); + + String responseEntityBody = new String( data ); + + assertTrue( responseEntityBody.contains( "https://foobar.com" ) ); + assertFalse( responseEntityBody.contains( "localhost" ) ); + } + finally + { + httpclient.getConnectionManager().shutdown(); + } + } + + @Test + public void shouldForwardHttpAndHostOnDifferentPort() throws Exception + { + + URI rootUri = functionalTestHelper.baseUri(); + + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( rootUri ); + + httpget.setHeader( "Accept", "application/json" ); + httpget.setHeader( "X-Forwarded-Host", "foobar.com:9999" ); + httpget.setHeader( "X-Forwarded-Proto", "http" ); + + HttpResponse response = httpclient.execute( httpget ); + + String length = response.getHeaders( "CONTENT-LENGTH" )[0].getValue(); + byte[] data = new byte[Integer.valueOf( length )]; + response.getEntity().getContent().read( data ); + + String responseEntityBody = new String( data ); + + assertTrue( responseEntityBody.contains( "http://foobar.com:9999" ) ); + assertFalse( responseEntityBody.contains( "localhost" ) ); + } + finally + { + httpclient.getConnectionManager().shutdown(); + } + } + + @Test + public void shouldForwardHttpAndFirstHost() throws Exception + { + URI rootUri = functionalTestHelper.baseUri(); + + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( rootUri ); + + httpget.setHeader( "Accept", "application/json" ); + httpget.setHeader( "X-Forwarded-Host", "foobar.com, bazbar.com" ); + httpget.setHeader( "X-Forwarded-Proto", "http" ); + + HttpResponse response = httpclient.execute( httpget ); + + String length = response.getHeaders( "CONTENT-LENGTH" )[0].getValue(); + byte[] data = new byte[Integer.valueOf( length )]; + response.getEntity().getContent().read( data ); + + String responseEntityBody = new String( data ); + + assertTrue( responseEntityBody.contains( "http://foobar.com" ) ); + assertFalse( responseEntityBody.contains( "localhost" ) ); + } + finally + { + httpclient.getConnectionManager().shutdown(); + } + + } + + @Test + public void shouldForwardHttpsAndHostOnDifferentPort() throws Exception + { + URI rootUri = functionalTestHelper.baseUri(); + + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( rootUri ); + + httpget.setHeader( "Accept", "application/json" ); + httpget.setHeader( "X-Forwarded-Host", "foobar.com:9999" ); + httpget.setHeader( "X-Forwarded-Proto", "https" ); + + HttpResponse response = httpclient.execute( httpget ); + + String length = response.getHeaders( "CONTENT-LENGTH" )[0].getValue(); + byte[] data = new byte[Integer.valueOf( length )]; + response.getEntity().getContent().read( data ); + + String responseEntityBody = new String( data ); + + assertTrue( responseEntityBody.contains( "https://foobar.com:9999" ) ); + assertFalse( responseEntityBody.contains( "localhost" ) ); + } + finally + { + httpclient.getConnectionManager().shutdown(); + } + } + + + @Test + public void shouldUseRequestUriWhenNoXForwardHeadersPresent() throws Exception + { + URI rootUri = functionalTestHelper.baseUri(); + + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( rootUri ); + + httpget.setHeader( "Accept", "application/json" ); + + HttpResponse response = httpclient.execute( httpget ); + + String length = response.getHeaders( "CONTENT-LENGTH" )[0].getValue(); + byte[] data = new byte[Integer.valueOf( length )]; + response.getEntity().getContent().read( data ); + + String responseEntityBody = new String( data ); + + assertFalse( responseEntityBody.contains( "https://foobar.com" ) ); + assertFalse( responseEntityBody.contains( ":0" ) ); + assertTrue( responseEntityBody.contains( "localhost" ) ); + } + finally + { + httpclient.getConnectionManager().shutdown(); + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/CreateRelationshipTest.java b/community/server/src/test/java/org/neo4j/server/rest/CreateRelationshipTest.java new file mode 100644 index 0000000000000..431c88207beb9 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/CreateRelationshipTest.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.util.Map; +import javax.ws.rs.core.MediaType; + +import com.sun.jersey.api.client.ClientResponse.Status; +import org.junit.Test; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.graphdb.Transaction; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.repr.RelationshipRepresentationTest; +import org.neo4j.test.GraphDescription.Graph; +import org.neo4j.test.TestData.Title; + +import static org.junit.Assert.assertTrue; + +public class CreateRelationshipTest extends AbstractRestFunctionalDocTestBase +{ + @Test + @Graph( "Joe knows Sara" ) + @Documented( "Upon successful creation of a relationship, the new relationship is returned." ) + @Title( "Create a relationship with properties" ) + public void create_a_relationship_with_properties() throws Exception + { + String jsonString = "{\"to\" : \"" + + getDataUri() + + "node/" + + getNode( "Sara" ).getId() + + "\", \"type\" : \"LOVES\", \"data\" : {\"foo\" : \"bar\"}}"; + Node i = getNode( "Joe" ); + gen.get().description( startGraph( "Add relationship with properties before" ) ); + gen.get().expectedStatus( + Status.CREATED.getStatusCode() ).payload( jsonString ).post( + getNodeUri( i ) + "/relationships" ); + try ( Transaction tx = graphdb().beginTx() ) + { + assertTrue( i.hasRelationship( RelationshipType.withName( "LOVES" ) ) ); + } + } + + @Test + @Documented( "Upon successful creation of a relationship, the new relationship is returned." ) + @Title( "Create relationship" ) + @Graph( "Joe knows Sara" ) + public void create_relationship() throws Exception + { + String jsonString = "{\"to\" : \"" + + getDataUri() + + "node/" + + getNode( "Sara" ).getId() + + "\", \"type\" : \"LOVES\"}"; + Node i = getNode( "Joe" ); + String entity = gen.get().expectedStatus( + Status.CREATED.getStatusCode() ).payload( jsonString ) + .description( startGraph( "create relationship" ) ) + .post( getNodeUri( i ) + "/relationships" ).entity(); + try ( Transaction tx = graphdb().beginTx() ) + { + assertTrue( i.hasRelationship( RelationshipType.withName( "LOVES" ) ) ); + } + assertProperRelationshipRepresentation( JsonHelper.jsonToMap( entity ) ); + } + + @Test + @Graph( "Joe knows Sara" ) + public void shouldRespondWith404WhenStartNodeDoesNotExist() + { + String jsonString = "{\"to\" : \"" + + getDataUri() + + "node/" + + getNode( "Joe" ) + + "\", \"type\" : \"LOVES\", \"data\" : {\"foo\" : \"bar\"}}"; + gen.get().expectedStatus( + Status.NOT_FOUND.getStatusCode() ).expectedType( MediaType.TEXT_HTML_TYPE ).payload( jsonString ).post( + getDataUri() + "/node/12345/relationships" ).entity(); + } + + @Test + @Graph( "Joe knows Sara" ) + public void creating_a_relationship_to_a_nonexisting_end_node() + { + String jsonString = "{\"to\" : \"" + + getDataUri() + + "node/" + + "999999\", \"type\" : \"LOVES\", \"data\" : {\"foo\" : \"bar\"}}"; + gen.get().expectedStatus( + Status.BAD_REQUEST.getStatusCode() ).payload( jsonString ).post( + getNodeUri( getNode( "Joe" ) ) + "/relationships" ).entity(); + } + + @Test + @Graph( "Joe knows Sara" ) + public void creating_a_loop_relationship() + throws Exception + { + + Node joe = getNode( "Joe" ); + String jsonString = "{\"to\" : \"" + getNodeUri( joe ) + + "\", \"type\" : \"LOVES\"}"; + String entity = gen.get().expectedStatus( + Status.CREATED.getStatusCode() ).payload( jsonString ).post( + getNodeUri( getNode( "Joe" ) ) + "/relationships" ).entity(); + assertProperRelationshipRepresentation( JsonHelper.jsonToMap( entity ) ); + } + + @Test + @Graph( "Joe knows Sara" ) + public void providing_bad_JSON() + { + String jsonString = "{\"to\" : \"" + + getNodeUri( data.get().get( "Joe" ) ) + + "\", \"type\" : \"LOVES\", \"data\" : {\"foo\" : **BAD JSON HERE*** \"bar\"}}"; + gen.get().expectedStatus( + Status.BAD_REQUEST.getStatusCode() ).payload( jsonString ).post( + getNodeUri( getNode( "Joe" ) ) + "/relationships" ).entity(); + } + + private void assertProperRelationshipRepresentation( + Map relrep ) + { + RelationshipRepresentationTest.verifySerialisation( relrep ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/CypherIT.java b/community/server/src/test/java/org/neo4j/server/rest/CypherIT.java new file mode 100644 index 0000000000000..260ab780df23f --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/CypherIT.java @@ -0,0 +1,374 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.UnsupportedEncodingException; +import java.util.Collection; +import java.util.Map; +import javax.ws.rs.core.Response.Status; + +import org.junit.Test; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.helpers.collection.Pair; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription; +import org.neo4j.test.GraphDescription.Graph; +import org.neo4j.test.GraphDescription.LABEL; +import org.neo4j.test.GraphDescription.NODE; +import org.neo4j.test.GraphDescription.PROP; +import org.neo4j.test.GraphDescription.REL; +import org.neo4j.test.TestData.Title; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.isA; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.core.IsInstanceOf.instanceOf; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import static org.neo4j.server.rest.domain.JsonHelper.jsonToMap; + +public class CypherIT extends AbstractRestFunctionalTestBase { + + @Test + @Title( "Send a query" ) + @Documented( "A simple query returning all nodes connected to some node, returning the node and the name " + + "property, if it exists, otherwise `NULL`:" ) + @Graph( nodes = { + @NODE( name = "I", setNameProperty = true ), + @NODE( name = "you", setNameProperty = true ), + @NODE( name = "him", setNameProperty = true, properties = { + @PROP( key = "age", value = "25", type = GraphDescription.PropType.INTEGER ) } ) }, + relationships = { + @REL( start = "I", end = "him", type = "know", properties = { } ), + @REL( start = "I", end = "you", type = "know", properties = { } ) } ) + public void testPropertyColumn() throws UnsupportedEncodingException { + String script = createScript( "MATCH (x {name: 'I'})-[r]->(n) RETURN type(r), n.name, n.age" ); + + String response = cypherRestCall( script, Status.OK ); + + assertThat( response, containsString( "you" ) ); + assertThat( response, containsString( "him" ) ); + assertThat( response, containsString( "25" ) ); + assertThat( response, not( containsString( "\"x\"" ) ) ); + } + + @Test + @Title( "Retrieve query metadata" ) + @Documented("By passing in an additional GET parameter when you execute Cypher queries, metadata about the " + + "query will be returned, such as how many labels were added or removed by the query.") + @Graph( nodes = { @NODE( name = "I", setNameProperty = true, labels = { @LABEL( "Director" ) } ) } ) + public void testQueryStatistics() throws JsonParseException + { + // Given + String script = createScript( "MATCH (n {name: 'I'}) SET n:Actor REMOVE n:Director RETURN labels(n)" ); + + // When + Map output = jsonToMap(doCypherRestCall( cypherUri() + "?includeStats=true", script, Status.OK )); + + // Then + @SuppressWarnings("unchecked") + Map stats = (Map) output.get( "stats" ); + + assertThat( stats, isA( Map.class ) ); + assertThat( (Boolean) stats.get( "contains_updates" ), is( true ) ); + assertThat( (Integer) stats.get( "labels_added" ), is( 1 ) ); + assertThat( (Integer) stats.get( "labels_removed" ), is( 1 ) ); + assertThat( (Integer) stats.get( "nodes_created" ), is( 0 ) ); + assertThat( (Integer) stats.get( "nodes_deleted" ), is( 0 ) ); + assertThat( (Integer) stats.get( "properties_set" ), is( 0 ) ); + assertThat( (Integer) stats.get( "relationships_created" ), is( 0 ) ); + assertThat( (Integer) stats.get( "relationship_deleted" ), is( 0 ) ); + } + + /** + * Ensure that order of data and column is ok. + */ + @Test + @Graph( nodes = { + @NODE( name = "I", setNameProperty = true ), + @NODE( name = "you", setNameProperty = true ), + @NODE( name = "him", setNameProperty = true, properties = { + @PROP( key = "age", value = "25", type = GraphDescription.PropType.INTEGER ) } ) }, + relationships = { + @REL( start = "I", end = "him", type = "know", properties = { } ), + @REL( start = "I", end = "you", type = "know", properties = { } ) } ) + public void testDataColumnOrder() throws UnsupportedEncodingException { + String script = createScript( "MATCH (x)-[r]->(n) WHERE id(x) = %I% RETURN type(r), n.name, n.age" ); + + String response = cypherRestCall( script, Status.OK ); + + assertThat( response.indexOf( "columns" ) < response.indexOf( "data" ), is( true )); + } + + @Test + @Title( "Errors" ) + @Documented( "Errors on the server will be reported as a JSON-formatted message, exception name and stacktrace." ) + @Graph( "I know you" ) + public void error_gets_returned_as_json() throws Exception { + String response = cypherRestCall( "MATCH (x {name: 'I'}) RETURN x.dummy/0", Status.BAD_REQUEST ); + Map output = jsonToMap( response ); + assertTrue( output.toString(), output.containsKey( "message" ) ); + assertTrue( output.containsKey( "exception" ) ); + assertTrue( output.containsKey( "stackTrace" ) ); + } + + @Test + @Title( "Return paths" ) + @Documented( "Paths can be returned just like other return types." ) + @Graph( "I know you" ) + public void return_paths() throws Exception { + String script = "MATCH path = (x {name: 'I'})--(friend) RETURN path, friend.name"; + String response = cypherRestCall( script, Status.OK ); + + assertEquals( 2, ( jsonToMap( response ) ).size() ); + assertThat( response, containsString( "data" ) ); + assertThat( response, containsString( "you" ) ); + } + + @Test + @Title("Use parameters") + @Documented( "Cypher supports queries with parameters which are submitted as JSON." ) + @Graph( value = { "I know you" }, autoIndexNodes = true ) + public void send_queries_with_parameters() throws Exception { + data.get(); + String script = "MATCH (x {name: {startName}})-[r]-(friend) WHERE friend" + + ".name = {name} RETURN TYPE(r)"; + String response = cypherRestCall( script, Status.OK, Pair.of( "startName", "I" ), Pair.of( "name", "you" ) ); + + + assertEquals( 2, ( jsonToMap( response ) ).size() ); + assertTrue( response.contains( "know" ) ); + assertTrue( response.contains( "data" ) ); + } + + @Test + @Documented( "Create a node with a label and a property using Cypher. See the request for the parameter " + + "sent with the query." ) + @Title( "Create a node" ) + @Graph + public void send_query_to_create_a_node() throws Exception { + data.get(); + String script = "CREATE (n:Person { name : {name} }) RETURN n"; + String response = cypherRestCall( script, Status.OK, Pair.of( "name", "Andres" ) ); + + assertTrue( response.contains( "name" ) ); + assertTrue( response.contains( "Andres" ) ); + } + + @Test + @Title( "Create a node with multiple properties" ) + @Documented( "Create a node with a label and multiple properties using Cypher. See the request for the parameter " + + "sent with the query." ) + @Graph + public void send_query_to_create_a_node_from_a_map() throws Exception + { + data.get(); + String script = "CREATE (n:Person { props } ) RETURN n"; + String params = "\"props\" : { \"position\" : \"Developer\", \"name\" : \"Michael\", \"awesome\" : true, \"children\" : 3 }"; + String response = cypherRestCall( script, Status.OK, params ); + + assertTrue( response.contains( "name" ) ); + assertTrue( response.contains( "Michael" ) ); + } + + @Test + @Documented( "Create multiple nodes with properties using Cypher. See the request for the parameter sent " + + "with the query." ) + @Title( "Create multiple nodes with properties" ) + @Graph + public void send_query_to_create_multipe_nodes_from_a_map() throws Exception + { + data.get(); + String script = "UNWIND {props} AS properties CREATE (n:Person) SET n = properties RETURN n"; + String params = "\"props\" : [ { \"name\" : \"Andres\", \"position\" : \"Developer\" }, { \"name\" : \"Michael\", \"position\" : \"Developer\" } ]"; + String response = cypherRestCall( script, Status.OK, params ); + + assertTrue( response.contains( "name" ) ); + assertTrue( response.contains( "Michael" ) ); + assertTrue( response.contains( "Andres" ) ); + } + + @Test + @Title( "Set all properties on a node using Cypher" ) + @Documented( "Set all properties on a node." ) + @Graph + public void setAllPropertiesUsingMap() throws Exception + { + data.get(); + String script = "CREATE (n:Person { name: 'this property is to be deleted' } ) SET n = { props } RETURN n"; + String params = "\"props\" : { \"position\" : \"Developer\", \"firstName\" : \"Michael\", \"awesome\" : true, \"children\" : 3 }"; + String response = cypherRestCall( script, Status.OK, params ); + + assertTrue( response.contains( "firstName" ) ); + assertTrue( response.contains( "Michael" ) ); + assertTrue( !response.contains( "name" ) ); + assertTrue( !response.contains( "deleted" ) ); + } + + @Test + @Graph( nodes = { + @NODE( name = "I", properties = { + @PROP( key = "prop", value = "Hello", type = GraphDescription.PropType.STRING ) } ), + @NODE( name = "you" ) }, + relationships = { + @REL( start = "I", end = "him", type = "know", properties = { + @PROP( key = "prop", value = "World", type = GraphDescription.PropType.STRING ) } ) } ) + public void nodes_are_represented_as_nodes() throws Exception { + data.get(); + String script = "MATCH (n)-[r]->() WHERE id(n) = %I% RETURN n, r"; + + String response = cypherRestCall( script, Status.OK ); + + assertThat( response, containsString( "Hello" ) ); + assertThat( response, containsString( "World" ) ); + } + + @Test + @Title( "Syntax errors" ) + @Documented( "Sending a query with syntax errors will give a bad request (HTTP 400) response together with " + + "an error message." ) + @Graph( value = { "I know you" }, autoIndexNodes = true ) + public void send_queries_with_syntax_errors() throws Exception { + data.get(); + String script = "START x = node:node_auto_index(name={startName}) MATC path = (x-[r]-friend) WHERE friend" + + ".name = {name} RETURN TYPE(r)"; + String response = cypherRestCall( script, Status.BAD_REQUEST, Pair.of( "startName", "I" ), Pair.of( "name", "you" ) ); + + + Map output = jsonToMap( response ); + assertTrue( output.containsKey( "message" ) ); + assertTrue( output.containsKey( "stackTrace" ) ); + } + + @Test + @Documented( "When sending queries that\n" + + "return nested results like list and maps,\n" + + "these will get serialized into nested JSON representations\n" + + "according to their types." ) + @Graph( value = { "I know you" }, autoIndexNodes = true ) + public void nested_results() throws Exception { + data.get(); + String script = "MATCH (n) WHERE n.name in ['I', 'you'] RETURN collect(n.name)"; + String response = cypherRestCall(script, Status.OK);System.out.println(); + + Map resultMap = jsonToMap( response ); + assertEquals( 2, resultMap.size() ); + assertThat( response, anyOf( containsString( "\"I\",\"you\"" ), containsString( + "\"you\",\"I\"" ), containsString( "\"I\", \"you\"" ), containsString( + "\"you\", \"I\"" )) ); + } + + @Test + @Title( "Profile a query" ) + @Documented( "By passing in an extra parameter, you can ask the cypher executor to return a profile of the " + + "query as it is executed. This can help in locating bottlenecks." ) + @Graph( nodes = { + @NODE( name = "I", setNameProperty = true ), + @NODE( name = "you", setNameProperty = true ), + @NODE( name = "him", setNameProperty = true, properties = { + @PROP( key = "age", value = "25", type = GraphDescription.PropType.INTEGER ) } ) }, + relationships = { + @REL( start = "I", end = "him", type = "know", properties = { } ), + @REL( start = "I", end = "you", type = "know", properties = { } ) } ) + public void testProfiling() throws Exception { + String script = createScript( "MATCH (x)-[r]->(n) WHERE id(x) = %I% RETURN type(r), n.name, n.age" ); + + // WHEN + String response = doCypherRestCall( cypherUri() + "?profile=true", script, Status.OK ); + + // THEN + Map des = jsonToMap( response ); + assertThat( des.get( "plan" ), instanceOf( Map.class )); + + @SuppressWarnings("unchecked") + Map plan = (Map)des.get( "plan" ); + assertThat( plan.get( "name" ), instanceOf( String.class ) ); + assertThat( plan.get( "children" ), instanceOf( Collection.class )); + assertThat( plan.get( "rows" ), instanceOf( Number.class )); + assertThat( plan.get( "dbHits" ), instanceOf( Number.class )); + } + + @Test + @Graph( value = { "I know you" }, autoIndexNodes = false ) + public void array_property() throws Exception { + setProperty("I", "array1", new int[] { 1, 2, 3 } ); + setProperty("I", "array2", new String[] { "a", "b", "c" } ); + + String script = "MATCH (n) WHERE id(n) = %I% RETURN n.array1, n.array2"; + String response = cypherRestCall( script, Status.OK ); + + assertThat( response, anyOf( containsString( "[ 1, 2, 3 ]" ), containsString( "[1,2,3]" )) ); + assertThat( response, anyOf( containsString( "[ \"a\", \"b\", \"c\" ]" ), + containsString( "[\"a\",\"b\",\"c\"]" )) ); + } + + void setProperty(String nodeName, String propertyName, Object propertyValue) { + Node i = this.getNode(nodeName); + GraphDatabaseService db = i.getGraphDatabase(); + + try ( Transaction tx = db.beginTx() ) + { + i.setProperty(propertyName, propertyValue); + tx.success(); + } + } + + @Test + @Title( "Send queries with errors" ) + @Documented( "This example shows what happens if you misspell an identifier." ) + @Graph( value = { "I know you" }, autoIndexNodes = true ) + public void send_queries_with_errors() throws Exception { + data.get(); + String script = "START x = node:node_auto_index(name={startName}) MATCH path = (x)-[r]-(friend) WHERE frien" + + ".name = {name} RETURN type(r)"; + String response = cypherRestCall( script, Status.BAD_REQUEST, Pair.of( "startName", "I" ), Pair.of( "name", "you" ) ); + + Map responseMap = jsonToMap( response ); + assertThat( responseMap.keySet(), containsInAnyOrder( + "message", "exception", "fullname", "stackTrace", "cause", "errors" ) ); + assertThat( response, containsString( "message" ) ); + assertThat( ((String) responseMap.get( "message" )), containsString( "Variable `frien` not defined" ) ); + } + + @SafeVarargs + private final String cypherRestCall( String script, Status status, Pair... params ) + { + return doCypherRestCall( cypherUri(), script, status, params ); + } + + private String cypherRestCall( String script, Status status, String paramString ) + { + return doCypherRestCall( cypherUri(), script, status, paramString ); + } + + private String cypherUri() + { + return getDataUri() + "cypher"; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/CypherSessionTest.java b/community/server/src/test/java/org/neo4j/server/rest/CypherSessionTest.java new file mode 100644 index 0000000000000..a91622d105cbc --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/CypherSessionTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import javax.servlet.http.HttpServletRequest; + +import org.junit.Test; + +import org.neo4j.helpers.collection.Pair; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; +import org.neo4j.logging.NullLogProvider; +import org.neo4j.server.database.CypherExecutor; +import org.neo4j.server.database.Database; +import org.neo4j.server.database.WrappedDatabase; +import org.neo4j.server.rest.management.console.CypherSession; +import org.neo4j.test.TestGraphDatabaseFactory; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +public class CypherSessionTest +{ + @Test + public void shouldReturnASingleNode() throws Throwable + { + GraphDatabaseFacade graphdb = (GraphDatabaseFacade) new TestGraphDatabaseFactory().newImpermanentDatabase(); + Database database = new WrappedDatabase( graphdb ); + CypherExecutor executor = new CypherExecutor( database, Config.defaults(), NullLogProvider.getInstance() ); + executor.start(); + try + { + CypherSession session = new CypherSession( executor, NullLogProvider.getInstance(), mock( HttpServletRequest.class ) ); + Pair result = session.evaluate( "create (a) return a" ); + assertThat( result.first(), containsString( "Node[0]" ) ); + } + finally + { + graphdb.shutdown(); + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/DatabaseMetadataServiceIT.java b/community/server/src/test/java/org/neo4j/server/rest/DatabaseMetadataServiceIT.java new file mode 100644 index 0000000000000..4be9786b7b5bc --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/DatabaseMetadataServiceIT.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.GraphDbHelper; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.Matchers.allOf; +import static org.junit.Assert.assertThat; + +public class DatabaseMetadataServiceIT extends AbstractRestFunctionalTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + } + + @Documented( "Get relationship types." ) + @Test + public void shouldReturn200OnGet() + { + helper.createRelationship( "KNOWS" ); + helper.createRelationship( "LOVES" ); + + String result = gen.get() + .expectedStatus( 200 ) + .get( functionalTestHelper.dataUri() + "relationship/types" ) + .entity(); + assertThat( result, allOf( containsString( "KNOWS" ), containsString( "LOVES" ) ) ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/DegreeIT.java b/community/server/src/test/java/org/neo4j/server/rest/DegreeIT.java new file mode 100644 index 0000000000000..99d3d7cdd9461 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/DegreeIT.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.util.Map; + +import org.junit.Test; + +import org.neo4j.graphdb.Node; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription; + +import static junit.framework.TestCase.assertEquals; + +public class DegreeIT extends AbstractRestFunctionalTestBase +{ + @Documented( "Get the degree of a node\n" + + "\n" + + "Return the total number of relationships associated with a node." ) + @Test + @GraphDescription.Graph( {"Root knows Mattias", "Root knows Johan"} ) + public void get_degree() throws JsonParseException + { + Map nodes = data.get(); + String nodeUri = getNodeUri( nodes.get( "Root" ) ); + + // Document + RESTDocsGenerator.ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .get( nodeUri + "/degree/all" ); + + // Then + assertEquals( 2, JsonHelper.jsonNode( response.response().getEntity() ).asInt() ); + } + + @Documented( "Get the degree of a node by direction\n" + + "\n" + + "Return the number of relationships of a particular direction for a node.\n" + + "Specify `all`, `in` or `out`." ) + @Test + @GraphDescription.Graph( {"Root knows Mattias", "Root knows Johan"} ) + public void get_degree_by_direction() throws JsonParseException + { + Map nodes = data.get(); + String nodeUri = getNodeUri( nodes.get( "Root" ) ); + + // Document + RESTDocsGenerator.ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .get( nodeUri + "/degree/out" ); + + // Then + assertEquals( 2, JsonHelper.jsonNode( response.response().getEntity() ).asInt() ); + } + + @Documented( "Get the degree of a node by direction and types\n" + + "\n" + + "If you are only interested in the degree of a particular relationship type, or a set of relationship types, you specify relationship types after the direction.\n" + + "You can combine multiple relationship types by using the `&` character." ) + @Test + @GraphDescription.Graph( {"Root KNOWS Mattias", "Root KNOWS Johan", "Root LIKES Cookie"} ) + public void get_degree_by_direction_and_type() throws JsonParseException + { + Map nodes = data.get(); + String nodeUri = getNodeUri( nodes.get( "Root" ) ); + + // Document + RESTDocsGenerator.ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .get( nodeUri + "/degree/out/KNOWS&LIKES" ); + + // Then + assertEquals( 3, JsonHelper.jsonNode( response.response().getEntity() ).asInt() ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/DisableWADLIT.java b/community/server/src/test/java/org/neo4j/server/rest/DisableWADLIT.java new file mode 100644 index 0000000000000..34a74a106c346 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/DisableWADLIT.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.net.URI; + +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.GraphDbHelper; + +import static org.junit.Assert.assertEquals; + +public class DisableWADLIT extends AbstractRestFunctionalTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + } + + @Test + public void should404OnAnyUriEndinginWADL() throws Exception + { + URI nodeUri = new URI( "http://localhost:7474/db/data/application.wadl" ); + + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( nodeUri ); + + httpget.setHeader( "Accept", "*/*" ); + HttpResponse response = httpclient.execute( httpget ); + + assertEquals( 404, response.getStatusLine().getStatusCode() ); + + } + finally + { + httpclient.getConnectionManager().shutdown(); + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/DiscoveryServiceIT.java b/community/server/src/test/java/org/neo4j/server/rest/DiscoveryServiceIT.java new file mode 100644 index 0000000000000..f5328ee6b6293 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/DiscoveryServiceIT.java @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.util.Map; +import javax.ws.rs.core.MediaType; + +import com.sun.jersey.api.client.Client; +import org.junit.Test; + +import org.neo4j.server.rest.domain.JsonHelper; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class DiscoveryServiceIT extends AbstractRestFunctionalTestBase +{ + @Test + public void shouldRespondWith200WhenRetrievingDiscoveryDocument() throws Exception + { + JaxRsResponse response = getDiscoveryDocument(); + assertEquals( 200, response.getStatus() ); + response.close(); + } + + @Test + public void shouldGetContentLengthHeaderWhenRetrievingDiscoveryDocument() throws Exception + { + JaxRsResponse response = getDiscoveryDocument(); + assertNotNull( response.getHeaders() + .get( "Content-Length" ) ); + response.close(); + } + + @Test + public void shouldHaveJsonMediaTypeWhenRetrievingDiscoveryDocument() throws Exception + { + JaxRsResponse response = getDiscoveryDocument(); + assertThat( response.getType().toString(), containsString(MediaType.APPLICATION_JSON) ); + response.close(); + } + + @Test + public void shouldHaveJsonDataInResponse() throws Exception + { + JaxRsResponse response = getDiscoveryDocument(); + + Map map = JsonHelper.jsonToMap( response.getEntity() ); + + String managementKey = "management"; + assertTrue( map.containsKey( managementKey ) ); + assertNotNull( map.get( managementKey ) ); + + String dataKey = "data"; + assertTrue( map.containsKey( dataKey ) ); + assertNotNull( map.get( dataKey ) ); + response.close(); + } + + @Test + public void shouldRedirectOnHtmlRequest() throws Exception + { + Client nonRedirectingClient = Client.create(); + nonRedirectingClient.setFollowRedirects( false ); + + JaxRsResponse clientResponse = new RestRequest(null,nonRedirectingClient).get(server().baseUri().toString(),MediaType.TEXT_HTML_TYPE); + + assertEquals( 303, clientResponse.getStatus() ); + } + + private JaxRsResponse getDiscoveryDocument() throws Exception + { + return new RestRequest(server().baseUri()).get(); + } + +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/DocumentationData.java b/community/server/src/test/java/org/neo4j/server/rest/DocumentationData.java index 83b6ba6a1b86d..c03ae21da99f6 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/DocumentationData.java +++ b/community/server/src/test/java/org/neo4j/server/rest/DocumentationData.java @@ -35,7 +35,6 @@ class DocumentationData public String entity; public Map requestHeaders; public Map responseHeaders; - public boolean ignore; public void setPayload( final String payload ) { @@ -56,11 +55,6 @@ public String getPayload() } } - public String getPrettifiedEntity() - { - return JSONPrettifier.parse( entity ); - } - public void setPayloadType( final MediaType payloadType ) { this.payloadType = payloadType; @@ -108,10 +102,6 @@ public void setRequestHeaders( final Map request ) requestHeaders = request; } - public void setIgnore() { - this.ignore = true; - } - @Override public String toString() { @@ -119,4 +109,4 @@ public String toString() + ", uri=" + uri + ", method=" + method + ", status=" + status + ", entity=" + entity + ", requestHeaders=" + requestHeaders + ", responseHeaders=" + responseHeaders + "]"; } -} \ No newline at end of file +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/GetIndexRootIT.java b/community/server/src/test/java/org/neo4j/server/rest/GetIndexRootIT.java new file mode 100644 index 0000000000000..0c55966ef8de1 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/GetIndexRootIT.java @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.JsonParseException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import static org.neo4j.server.rest.domain.JsonHelper.jsonToMap; + +public class GetIndexRootIT extends AbstractRestFunctionalTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + } + + /** + * /db/data/index is not itself a resource + */ + @Test + public void shouldRespondWith404ForNonResourceIndexPath() throws Exception + { + JaxRsResponse response = RestRequest.req().get( functionalTestHelper.indexUri() ); + assertEquals( 404, response.getStatus() ); + response.close(); + } + + /** + * /db/data/index/node should be a resource with no content + * + * @throws Exception + */ + @Test + public void shouldRespondWithNodeIndexes() throws Exception + { + JaxRsResponse response = RestRequest.req().get( functionalTestHelper.nodeIndexUri() ); + assertResponseContainsNoIndexesOtherThanAutoIndexes( response ); + response.close(); + } + + private void assertResponseContainsNoIndexesOtherThanAutoIndexes( JaxRsResponse response ) throws JsonParseException + { + switch ( response.getStatus() ) + { + case 204: + return; // OK no auto indices + case 200: + assertEquals( 0, functionalTestHelper.removeAnyAutoIndex( jsonToMap( response.getEntity() ) ).size() ); + break; + default: + fail( "Invalid response code " + response.getStatus() ); + } + } + + /** + * /db/data/index/relationship should be a resource with no content + * + * @throws Exception + */ + @Test + public void shouldRespondWithRelationshipIndexes() throws Exception + { + JaxRsResponse response = RestRequest.req().get( functionalTestHelper.relationshipIndexUri() ); + assertResponseContainsNoIndexesOtherThanAutoIndexes( response ); + response.close(); + } + + // TODO More tests... +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/GetNodePropertiesIT.java b/community/server/src/test/java/org/neo4j/server/rest/GetNodePropertiesIT.java new file mode 100644 index 0000000000000..e2c513b843c74 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/GetNodePropertiesIT.java @@ -0,0 +1,185 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.util.Collections; +import javax.ws.rs.core.MediaType; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; +import org.neo4j.test.server.HTTP; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import static org.neo4j.helpers.collection.MapUtil.stringMap; + +public class GetNodePropertiesIT extends AbstractRestFunctionalDocTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + private RestRequest req = RestRequest.req(); + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + } + + @Documented( "Get properties for node." ) + @Test + public void shouldGet200ForProperties() throws JsonParseException { + String entity = JsonHelper.createJsonFrom(Collections.singletonMap("foo", "bar")); + JaxRsResponse createResponse = req.post(functionalTestHelper.dataUri() + "node/", entity); + gen.get() + .expectedStatus(200) + .get(createResponse.getLocation() + .toString() + "/properties"); + } + + @Test + public void shouldGetContentLengthHeaderForRetrievingProperties() throws JsonParseException + { + String entity = JsonHelper.createJsonFrom(Collections.singletonMap("foo", "bar")); + final RestRequest request = req; + JaxRsResponse createResponse = request.post(functionalTestHelper.dataUri() + "node/", entity); + JaxRsResponse response = request.get(createResponse.getLocation().toString() + "/properties"); + assertNotNull( response.getHeaders().get("Content-Length") ); + } + + @Test + public void shouldGetCorrectContentEncodingRetrievingProperties() throws JsonParseException + { + String asianText = "\u4f8b\u5b50"; + String germanText = "öäüÖÄÜß"; + + String complicatedString = asianText + germanText; + + + String entity = JsonHelper.createJsonFrom( Collections.singletonMap( "foo", complicatedString )); + final RestRequest request = req; + JaxRsResponse createResponse = request.post(functionalTestHelper.dataUri() + "node/", entity); + String response = (String) JsonHelper.readJson( request.get( getPropertyUri( createResponse.getLocation() + .toString(), "foo" ) ).getEntity() ); + assertEquals( complicatedString, response ); + } + @Test + public void shouldGetCorrectContentEncodingRetrievingPropertiesWithStreaming() throws JsonParseException + { + String asianText = "\u4f8b\u5b50"; + String germanText = "öäüÖÄÜß"; + + String complicatedString = asianText + germanText; + + String entity = JsonHelper.createJsonFrom( Collections.singletonMap( "foo", complicatedString ) ); + final RestRequest request = req.header( StreamingJsonFormat.STREAM_HEADER,"true"); + JaxRsResponse createResponse = request.post(functionalTestHelper.dataUri() + "node/", entity); + String response = (String) JsonHelper.readJson( request.get( getPropertyUri( createResponse.getLocation() + .toString(), "foo" ), new MediaType( "application", "json", stringMap( "stream", "true" ) ) ).getEntity() ); + assertEquals( complicatedString, response ); + } + + @Test + public void shouldGet404ForPropertiesOnNonExistentNode() { + JaxRsResponse response = RestRequest.req().get(functionalTestHelper.dataUri() + "node/999999/properties"); + assertEquals(404, response.getStatus()); + } + + @Test + public void shouldBeJSONContentTypeOnPropertiesResponse() throws JsonParseException + { + String entity = JsonHelper.createJsonFrom(Collections.singletonMap("foo", "bar")); + JaxRsResponse createResource = req.post(functionalTestHelper.dataUri() + "node/", entity); + JaxRsResponse response = req.get(createResource.getLocation().toString() + "/properties"); + assertThat( response.getType().toString(), containsString( MediaType.APPLICATION_JSON ) ); + } + + @Test + public void shouldGet404ForNoProperty() + { + final JaxRsResponse createResponse = req.post(functionalTestHelper.dataUri() + "node/", ""); + JaxRsResponse response = req.get(getPropertyUri(createResponse.getLocation().toString(), "foo")); + assertEquals(404, response.getStatus()); + } + + @Documented( "Get property for node.\n" + + "\n" + + "Get a single node property from a node." ) + @Test + public void shouldGet200ForProperty() throws JsonParseException + { + String entity = JsonHelper.createJsonFrom(Collections.singletonMap("foo", "bar")); + JaxRsResponse createResponse = req.post(functionalTestHelper.dataUri() + "node/", entity); + JaxRsResponse response = req.get(getPropertyUri(createResponse.getLocation().toString(), "foo")); + assertEquals(200, response.getStatus()); + + gen.get() + .expectedStatus( 200 ) + .get(getPropertyUri(createResponse.getLocation() + .toString(), "foo")); + } + + @Test + public void shouldGet404ForPropertyOnNonExistentNode() { + JaxRsResponse response = RestRequest.req().get(getPropertyUri(functionalTestHelper.dataUri() + "node/" + "999999", "foo")); + assertEquals(404, response.getStatus()); + } + + @Test + public void shouldBeJSONContentTypeOnPropertyResponse() throws JsonParseException { + String entity = JsonHelper.createJsonFrom( Collections.singletonMap( "foo", "bar" ) ); + + JaxRsResponse createResponse = req.post(functionalTestHelper.dataUri() + "node/", entity); + + JaxRsResponse response = req.get(getPropertyUri(createResponse.getLocation().toString(), "foo")); + + assertThat( response.getType().toString(), containsString(MediaType.APPLICATION_JSON) ); + + createResponse.close(); + response.close(); + } + + @Test + public void shouldReturnEmptyMapForEmptyProperties() throws Exception + { + // Given + String location = HTTP.POST( server().baseUri().resolve( "db/data/node" ).toString() ).location(); + + // When + HTTP.Response res = HTTP.GET( location + "/properties" ); + + // Then + assertThat(res.rawContent(), equalTo("{ }")); + } + + private String getPropertyUri( final String baseUri, final String key ) + { + return baseUri + "/properties/" + key; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/GetOnRootIT.java b/community/server/src/test/java/org/neo4j/server/rest/GetOnRootIT.java new file mode 100644 index 0000000000000..f50ef612aeb81 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/GetOnRootIT.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.util.Map; + +import org.junit.Test; + +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.kernel.internal.Version; +import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.repr.StreamingFormat; +import org.neo4j.test.GraphDescription.Graph; +import org.neo4j.test.TestData.Title; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class GetOnRootIT extends AbstractRestFunctionalTestBase +{ + @Title("Get service root") + @Documented( "The service root is your starting point to discover the REST API. It contains the basic starting " + + "points for the database, and some version and extension information." ) + @Test + @Graph("I know you") + public void assert200OkFromGet() throws Exception + { + String body = gen.get().expectedStatus( 200 ).get( getDataUri() ).entity(); + Map map = JsonHelper.jsonToMap( body ); + assertEquals( getDataUri() + "node", map.get( "node" ) ); + assertNotNull( map.get( "node_index" ) ); + assertNotNull( map.get( "relationship_index" ) ); + assertNotNull( map.get( "extensions_info" ) ); + assertNotNull( map.get( "batch" ) ); + assertNotNull( map.get( "cypher" ) ); + assertNotNull( map.get( "indexes" ) ); + assertNotNull( map.get( "constraints" ) ); + assertNotNull( map.get( "node_labels" ) ); + assertEquals( Version.getKernel().getReleaseVersion(), map.get( "neo4j_version" ) ); + + // Make sure advertised urls work + JaxRsResponse response; + if ( map.get( "reference_node" ) != null ) + { + response = RestRequest.req().get( + (String) map.get( "reference_node" ) ); + assertEquals( 200, response.getStatus() ); + response.close(); + } + response = RestRequest.req().get( (String) map.get( "node_index" ) ); + assertTrue( response.getStatus() == 200 || response.getStatus() == 204 ); + response.close(); + + response = RestRequest.req().get( + (String) map.get( "relationship_index" ) ); + assertTrue( response.getStatus() == 200 || response.getStatus() == 204 ); + response.close(); + + response = RestRequest.req().get( (String) map.get( "extensions_info" ) ); + assertEquals( 200, response.getStatus() ); + response.close(); + + response = RestRequest.req().post( (String) map.get( "batch" ), "[]" ); + assertEquals( 200, response.getStatus() ); + response.close(); + + response = RestRequest.req().post( (String) map.get( "cypher" ), "{\"query\":\"CREATE (n) RETURN n\"}" ); + assertEquals( 200, response.getStatus() ); + response.close(); + + response = RestRequest.req().get( (String) map.get( "indexes" ) ); + assertEquals( 200, response.getStatus() ); + response.close(); + + response = RestRequest.req().get( (String) map.get( "constraints" ) ); + assertEquals( 200, response.getStatus() ); + response.close(); + + response = RestRequest.req().get( (String) map.get( "node_labels" ) ); + assertEquals( 200, response.getStatus() ); + response.close(); + } + + @Documented( "All responses from the REST API can be transmitted as JSON streams, resulting in\n" + + "better performance and lower memory overhead on the server side. To use\n" + + "streaming, supply the header `X-Stream: true` with each request." ) + @Test + public void streaming() throws Exception + { + data.get(); + ResponseEntity responseEntity = gen() + .withHeader( StreamingFormat.STREAM_HEADER, "true" ) + .expectedType( APPLICATION_JSON_TYPE ) + .expectedStatus( 200 ) + .get( getDataUri() ); + JaxRsResponse response = responseEntity.response(); + // this gets the full media type, including things like + // ; stream=true at the end + String foundMediaType = response.getType() + .toString(); + String expectedMediaType = StreamingFormat.MEDIA_TYPE.toString(); + assertEquals( expectedMediaType, foundMediaType ); + + String body = responseEntity.entity(); + Map map = JsonHelper.jsonToMap( body ); + assertEquals( getDataUri() + "node", map.get( "node" ) ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/GetRelationshipPropertiesIT.java b/community/server/src/test/java/org/neo4j/server/rest/GetRelationshipPropertiesIT.java new file mode 100644 index 0000000000000..4f2c2e8ac72f6 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/GetRelationshipPropertiesIT.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.ws.rs.core.MediaType; + +import org.hamcrest.MatcherAssert; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.server.HTTP; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +import static org.neo4j.test.server.HTTP.RawPayload.quotedJson; + +public class GetRelationshipPropertiesIT extends AbstractRestFunctionalTestBase +{ + private static String baseRelationshipUri; + + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + setupTheDatabase(); + } + + private static void setupTheDatabase() + { + long relationship = helper.createRelationship( "LIKES" ); + Map map = new HashMap(); + map.put( "foo", "bar" ); + helper.setRelationshipProperties( relationship, map ); + baseRelationshipUri = functionalTestHelper.dataUri() + "relationship/" + relationship + "/properties/"; + } + + @Test + public void shouldGet200AndContentLengthForProperties() + { + long relId = helper.createRelationship( "LIKES" ); + helper.setRelationshipProperties( relId, Collections.singletonMap( "foo", "bar" ) ); + JaxRsResponse response = RestRequest.req().get( functionalTestHelper.dataUri() + "relationship/" + relId + + "/properties" ); + assertEquals( 200, response.getStatus() ); + assertNotNull( response.getHeaders() + .get( "Content-Length" ) ); + response.close(); + } + + @Test + public void shouldGet404ForPropertiesOnNonExistentRelationship() + { + JaxRsResponse response = RestRequest.req().get( functionalTestHelper.dataUri() + + "relationship/999999/properties" ); + assertEquals( 404, response.getStatus() ); + response.close(); + } + + @Test + public void shouldBeJSONContentTypeOnPropertiesResponse() + { + long relId = helper.createRelationship( "LIKES" ); + helper.setRelationshipProperties( relId, Collections.singletonMap( "foo", "bar" ) ); + JaxRsResponse response = RestRequest.req().get( functionalTestHelper.dataUri() + "relationship/" + relId + + "/properties" ); + assertThat( response.getType().toString(), containsString( MediaType.APPLICATION_JSON ) ); + response.close(); + } + + private String getPropertyUri( String key ) + { + return baseRelationshipUri + key; + } + + @Test + public void shouldGet404ForNoProperty() + { + JaxRsResponse response = RestRequest.req().get( getPropertyUri( "baz" ) ); + assertEquals( 404, response.getStatus() ); + response.close(); + } + + @Test + public void shouldGet404ForNonExistingRelationship() + { + String uri = functionalTestHelper.dataUri() + "relationship/999999/properties/foo"; + JaxRsResponse response = RestRequest.req().get( uri ); + assertEquals( 404, response.getStatus() ); + response.close(); + } + + @Test + public void shouldBeValidJSONOnResponse() throws JsonParseException + { + JaxRsResponse response = RestRequest.req().get( getPropertyUri( "foo" ) ); + assertThat( response.getType().toString(), containsString( MediaType.APPLICATION_JSON ) ); + assertNotNull( JsonHelper.createJsonFrom( response.getEntity() ) ); + response.close(); + } + + @Test + public void shouldReturnEmptyMapForEmptyProperties() throws Exception + { + // Given + String node = HTTP.POST( server().baseUri().resolve( "db/data/node" ).toString() ).location(); + String rel = HTTP.POST( node + "/relationships", quotedJson( "{'to':'" + node + "', " + + "'type':'LOVES'}" ) ).location(); + + // When + HTTP.Response res = HTTP.GET( rel + "/properties" ); + + // Then + MatcherAssert.assertThat( res.rawContent(), equalTo( "{ }" ) ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/HtmlIT.java b/community/server/src/test/java/org/neo4j/server/rest/HtmlIT.java new file mode 100644 index 0000000000000..149f2dadee780 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/HtmlIT.java @@ -0,0 +1,186 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.util.Collections; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response.Status; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.server.rest.domain.RelationshipDirection; +import org.neo4j.test.server.HTTP; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class HtmlIT extends AbstractRestFunctionalTestBase +{ + private long thomasAnderson; + private long trinity; + private long thomasAndersonLovesTrinity; + + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + } + + @Before + public void setupTheDatabase() + { + // Create the matrix example + thomasAnderson = createAndIndexNode( "Thomas Anderson" ); + trinity = createAndIndexNode( "Trinity" ); + long tank = createAndIndexNode( "Tank" ); + + long knowsRelationshipId = helper.createRelationship( "KNOWS", thomasAnderson, trinity ); + thomasAndersonLovesTrinity = helper.createRelationship( "LOVES", thomasAnderson, trinity ); + helper.setRelationshipProperties( thomasAndersonLovesTrinity, + Collections.singletonMap( "strength", (Object) 100 ) ); + helper.createRelationship( "KNOWS", thomasAnderson, tank ); + helper.createRelationship( "KNOWS", trinity, tank ); + + // index a relationship + helper.createRelationshipIndex( "relationships" ); + helper.addRelationshipToIndex( "relationships", "key", "value", knowsRelationshipId ); + + // index a relationship + helper.createRelationshipIndex( "relationships2" ); + helper.addRelationshipToIndex( "relationships2", "key2", "value2", knowsRelationshipId ); + } + + private long createAndIndexNode( String name ) + { + long id = helper.createNode(); + helper.setNodeProperties( id, Collections.singletonMap( "name", (Object) name ) ); + helper.addNodeToIndex( "node", "name", name, id ); + return id; + } + + @Test + public void shouldGetRoot() { + JaxRsResponse response = RestRequest.req().get(functionalTestHelper.dataUri(), MediaType.TEXT_HTML_TYPE); + assertEquals(Status.OK.getStatusCode(), response.getStatus()); + assertValidHtml( response.getEntity() ); + response.close(); + } + + @Test + public void shouldGetRootWithHTTP() { + HTTP.Response response = HTTP.withHeaders("Accept", MediaType.TEXT_HTML).GET(functionalTestHelper.dataUri()); + assertEquals(Status.OK.getStatusCode(), response.status()); + assertValidHtml( response.rawContent() ); + } + + @Test + public void shouldGetNodeIndexRoot() { + JaxRsResponse response = RestRequest.req().get(functionalTestHelper.nodeIndexUri(), MediaType.TEXT_HTML_TYPE); + assertEquals(Status.OK.getStatusCode(), response.getStatus()); + assertValidHtml( response.getEntity() ); + response.close(); + } + + @Test + public void shouldGetRelationshipIndexRoot() { + JaxRsResponse response = RestRequest.req().get(functionalTestHelper.relationshipIndexUri(), MediaType.TEXT_HTML_TYPE); + assertEquals(Status.OK.getStatusCode(), response.getStatus()); + assertValidHtml( response.getEntity() ); + response.close(); + } + + @Test + public void shouldGetTrinityWhenSearchingForHer() { + JaxRsResponse response = RestRequest.req().get(functionalTestHelper.indexNodeUri("node", "name", "Trinity"), MediaType.TEXT_HTML_TYPE); + assertEquals(Status.OK.getStatusCode(), response.getStatus()); + String entity = response.getEntity(); + assertTrue(entity.contains("Trinity")); + assertValidHtml(entity); + response.close(); + } + + @Test + public void shouldGetThomasAndersonDirectly() { + JaxRsResponse response = RestRequest.req().get(functionalTestHelper.nodeUri(thomasAnderson), MediaType.TEXT_HTML_TYPE); + assertEquals(Status.OK.getStatusCode(), response.getStatus()); + String entity = response.getEntity(); + assertTrue(entity.contains("Thomas Anderson")); + assertValidHtml(entity); + response.close(); + } + + @Test + public void shouldGetSomeRelationships() { + final RestRequest request = RestRequest.req(); + JaxRsResponse response = request.get(functionalTestHelper.relationshipsUri(thomasAnderson, RelationshipDirection.all.name(), "KNOWS"), MediaType.TEXT_HTML_TYPE); + assertEquals(Status.OK.getStatusCode(), response.getStatus()); + String entity = response.getEntity(); + assertTrue(entity.contains("KNOWS")); + assertFalse(entity.contains("LOVES")); + assertValidHtml(entity); + response.close(); + + response = request.get(functionalTestHelper.relationshipsUri(thomasAnderson, RelationshipDirection.all.name(), "LOVES"), + MediaType.TEXT_HTML_TYPE); + + entity = response.getEntity(); + assertFalse(entity.contains("KNOWS")); + assertTrue(entity.contains("LOVES")); + assertValidHtml(entity); + response.close(); + + response = request.get( + functionalTestHelper.relationshipsUri(thomasAnderson, RelationshipDirection.all.name(), "LOVES", + "KNOWS"),MediaType.TEXT_HTML_TYPE); + entity = response.getEntity(); + assertTrue(entity.contains("KNOWS")); + assertTrue(entity.contains("LOVES")); + assertValidHtml(entity); + response.close(); + } + + @Test + public void shouldGetThomasAndersonLovesTrinityRelationship() { + JaxRsResponse response = RestRequest.req().get(functionalTestHelper.relationshipUri(thomasAndersonLovesTrinity), MediaType.TEXT_HTML_TYPE); + assertEquals(Status.OK.getStatusCode(), response.getStatus()); + String entity = response.getEntity(); + assertTrue(entity.contains("strength")); + assertTrue(entity.contains("100")); + assertTrue(entity.contains("LOVES")); + assertValidHtml(entity); + response.close(); + } + + private void assertValidHtml( String entity ) + { + assertTrue( entity.contains( "" ) ); + assertTrue( entity.contains( "" ) ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/IndexNodeIT.java b/community/server/src/test/java/org/neo4j/server/rest/IndexNodeIT.java new file mode 100644 index 0000000000000..e9b1e90cc22b5 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/IndexNodeIT.java @@ -0,0 +1,957 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response.Status; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.function.Factory; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; +import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.domain.URIHelper; + +import static java.util.Collections.singletonList; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import static org.neo4j.graphdb.Neo4jMatchers.hasProperty; +import static org.neo4j.graphdb.Neo4jMatchers.inTx; +import static org.neo4j.server.helpers.FunctionalTestHelper.CLIENT; + +public class IndexNodeIT extends AbstractRestFunctionalTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + + @BeforeClass + public static void setupServer() + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + } + + @Before + public void setup() + { + gen().setGraph( server().getDatabase().getGraph() ); + } + + @Documented( "List node indexes." ) + @Test + public void shouldGetListOfNodeIndexesWhenOneExist() throws JsonParseException + { + String indexName = indexes.newInstance(); + helper.createNodeIndex( indexName ); + String entity = gen() + .expectedStatus( 200 ) + .get( functionalTestHelper.nodeIndexUri() ) + .entity(); + + Map map = JsonHelper.jsonToMap( entity ); + assertNotNull( map.get( indexName ) ); + + HashMap theIndex = new HashMap<>(); + theIndex.put( indexName, map.get( indexName ) ); + + assertEquals( "Was: " + theIndex + ", no-auto-index:" + functionalTestHelper.removeAnyAutoIndex( theIndex ), + 1, functionalTestHelper.removeAnyAutoIndex( theIndex ).size() ); + } + + @Documented( "Create node index\n" + + "\n" + + "NOTE: Instead of creating the index this way, you can simply start to use\n" + + "it, and it will be created automatically with default configuration." ) + @Test + public void shouldCreateANamedNodeIndex() + { + String indexName = indexes.newInstance(); + int expectedIndexes = helper.getNodeIndexes().length + 1; + Map indexSpecification = new HashMap<>(); + indexSpecification.put( "name", indexName ); + + gen() + .payload( JsonHelper.createJsonFrom( indexSpecification ) ) + .expectedStatus( 201 ) + .expectedHeader( "Location" ) + .post( functionalTestHelper.nodeIndexUri() ); + + assertEquals( expectedIndexes, helper.getNodeIndexes().length ); + assertThat( helper.getNodeIndexes(), FunctionalTestHelper.arrayContains( indexName ) ); + } + + @Test + public void shouldCreateANamedNodeIndexWithSpaces() + { + String indexName = indexes.newInstance() + " with spaces"; + int expectedIndexes = helper.getNodeIndexes().length + 1; + Map indexSpecification = new HashMap<>(); + indexSpecification.put( "name", indexName ); + + gen() + .payload( JsonHelper.createJsonFrom( indexSpecification ) ) + .expectedStatus( 201 ) + .expectedHeader( "Location" ) + .post( functionalTestHelper.nodeIndexUri() ); + + assertEquals( expectedIndexes, helper.getNodeIndexes().length ); + assertThat( helper.getNodeIndexes(), FunctionalTestHelper.arrayContains( indexName ) ); + } + + @Documented( "Create node index with configuration.\n\n" + + "This request is only necessary if you want to customize the index settings. \n" + + "If you are happy with the defaults, you can just start indexing nodes/relationships, as\n" + + "non-existent indexes will automatically be created as you do. See\n" + + "<> for more information on index configuration." ) + @Test + public void shouldCreateANamedNodeIndexWithConfiguration() throws Exception + { + int expectedIndexes = helper.getNodeIndexes().length + 1; + + gen() + .payload( "{\"name\":\"fulltext\", \"config\":{\"type\":\"fulltext\",\"provider\":\"lucene\"}}" ) + .expectedStatus( 201 ) + .expectedHeader( "Location" ) + .post( functionalTestHelper.nodeIndexUri() ); + + assertEquals( expectedIndexes, helper.getNodeIndexes().length ); + assertThat( helper.getNodeIndexes(), FunctionalTestHelper.arrayContains( "fulltext" ) ); + } + + @Documented( "Add node to index.\n" + + "\n" + + "Associates a node with the given key/value pair in the given index.\n" + + "\n" + + "NOTE: Spaces in the URI have to be encoded as +%20+.\n" + + "\n" + + "CAUTION: This does *not* overwrite previous entries. If you index the\n" + + "same key/value/item combination twice, two index entries are created. To\n" + + "do update-type operations, you need to delete the old entry before adding\n" + + "a new one." ) + @Test + public void shouldAddToIndex() throws Exception + { + final String indexName = indexes.newInstance(); + final String key = "some-key"; + final String value = "some value"; + long nodeId = createNode(); + // implicitly create the index + gen() + .expectedStatus( 201 ) + .payload( + JsonHelper.createJsonFrom( generateNodeIndexCreationPayload( key, value, + functionalTestHelper.nodeUri( nodeId ) ) ) ) + .post( functionalTestHelper.indexNodeUri( indexName ) ); + // look if we get one entry back + JaxRsResponse response = RestRequest.req().get( + functionalTestHelper.indexNodeUri( indexName, key, + URIHelper.encode( value ) ) ); + String entity = response.getEntity(); + Collection hits = (Collection) JsonHelper.readJson( entity ); + assertEquals( 1, hits.size() ); + } + + @Documented( "Find node by exact match.\n" + + "\n" + + "NOTE: Spaces in the URI have to be encoded as +%20+." ) + @Test + public void shouldAddToIndexAndRetrieveItByExactMatch() throws Exception + { + String indexName = indexes.newInstance(); + String key = "key"; + String value = "the value"; + long nodeId = createNode(); + value = URIHelper.encode( value ); + // implicitly create the index + JaxRsResponse response = RestRequest.req() + .post( functionalTestHelper.indexNodeUri( indexName ), createJsonStringFor( nodeId, key, value ) ); + assertEquals( 201, response.getStatus() ); + + // search it exact + String entity = gen() + .expectedStatus( 200 ) + .get( functionalTestHelper.indexNodeUri( indexName, key, URIHelper.encode( value ) ) ) + .entity(); + Collection hits = (Collection) JsonHelper.readJson( entity ); + assertEquals( 1, hits.size() ); + } + + @Documented( "Find node by query.\n" + + "\n" + + "The query language used here depends on what type of index you are\n" + + "querying. The default index type is Lucene, in which case you should use\n" + + "the Lucene query language here. Below an example of a fuzzy search over\n" + + "multiple keys.\n" + + "\n" + + "See: {lucene-base-uri}/queryparser/org/apache/lucene/queryparser/classic/package-summary.html\n" + + "\n" + + "Getting the results with a predefined ordering requires adding the\n" + + "parameter\n" + + "\n" + + "`order=ordering`\n" + + "\n" + + "where ordering is one of index, relevance or score. In this case an\n" + + "additional field will be added to each result, named score, that holds\n" + + "the float value that is the score reported by the query result." ) + @Test + public void shouldAddToIndexAndRetrieveItByQuery() throws JsonParseException + { + String indexName = indexes.newInstance(); + String key = "Name"; + String value = "Builder"; + long node = helper.createNode( MapUtil.map( key, value ) ); + helper.addNodeToIndex( indexName, key, value, node ); + helper.addNodeToIndex( indexName, "Gender", "Male", node ); + + String entity = gen() + .expectedStatus( 200 ) + .get( functionalTestHelper.indexNodeUri( indexName ) + "?query=" + key + + ":Build~0.1%20AND%20Gender:Male" ) + .entity(); + + Collection hits = (Collection) JsonHelper.readJson( entity ); + assertEquals( 1, hits.size() ); + LinkedHashMap nodeMap = (LinkedHashMap) hits.iterator().next(); + assertNull( "score should not be present when not explicitly ordering", nodeMap.get( "score" ) ); + } + + @Test + public void orderedResultsAreSupersetOfUnordered() throws Exception + { + // Given + String indexName = indexes.newInstance(); + String key = "Name"; + String value = "Builder"; + long node = helper.createNode( MapUtil.map( key, value ) ); + helper.addNodeToIndex( indexName, key, value, node ); + helper.addNodeToIndex( indexName, "Gender", "Male", node ); + + String entity = gen().expectedStatus( 200 ).get( + functionalTestHelper.indexNodeUri( indexName ) + + "?query=" + key + ":Build~0.1%20AND%20Gender:Male" ).entity(); + + @SuppressWarnings( "unchecked" ) + Collection> hits = + (Collection>) JsonHelper.readJson( entity ); + LinkedHashMap nodeMapUnordered = hits.iterator().next(); + + // When + entity = gen().expectedStatus( 200 ).get( + functionalTestHelper.indexNodeUri( indexName ) + + "?query="+key+":Build~0.1%20AND%20Gender:Male&order=score" ).entity(); + + //noinspection unchecked + hits = (Collection>) JsonHelper.readJson( entity ); + LinkedHashMap nodeMapOrdered = hits.iterator().next(); + + // Then + for ( Map.Entry unorderedEntry : nodeMapUnordered.entrySet() ) + { + assertEquals( "wrong entry for key: " + unorderedEntry.getKey(), + unorderedEntry.getValue(), + nodeMapOrdered.get( unorderedEntry.getKey() ) ); + } + assertTrue( "There should be only one extra value for the ordered map", + nodeMapOrdered.size() == nodeMapUnordered.size() + 1 ); + } + + //TODO:add compatibility tests for old syntax + @Test + public void shouldAddToIndexAndRetrieveItByQuerySorted() + throws JsonParseException + { + String indexName = indexes.newInstance(); + String key = "Name"; + long node1 = helper.createNode(); + long node2 = helper.createNode(); + + helper.addNodeToIndex( indexName, key, "Builder2", node1 ); + helper.addNodeToIndex( indexName, "Gender", "Male", node1 ); + helper.addNodeToIndex( indexName, key, "Builder", node2 ); + helper.addNodeToIndex( indexName, "Gender", "Male", node2 ); + + String entity = gen().expectedStatus( 200 ).get( + functionalTestHelper.indexNodeUri( indexName ) + + "?query=" + key + ":Builder~%20AND%20Gender:Male&order=relevance" ).entity(); + + Collection hits = (Collection) JsonHelper.readJson( entity ); + assertEquals( 2, hits.size() ); + @SuppressWarnings( "unchecked" ) + Iterator> it = (Iterator>) hits.iterator(); + + LinkedHashMap node2Map = it.next(); + LinkedHashMap node1Map = it.next(); + float score2 = ( (Double) node2Map.get( "score" ) ).floatValue(); + float score1 = ( (Double) node1Map.get( "score" ) ).floatValue(); + assertTrue( + "results returned in wrong order for relevance ordering", + ( (String) node2Map.get( "self" ) ).endsWith( Long.toString( node2 ) ) ); + assertTrue( + "results returned in wrong order for relevance ordering", + ( (String) node1Map.get( "self" ) ).endsWith( Long.toString( node1 ) ) ); + /* + * scores are always the same, just the ordering changes. So all subsequent tests will + * check the same condition. + */ + assertTrue( "scores are reversed", score2 > score1 ); + + entity = gen().expectedStatus( 200 ).get( + functionalTestHelper.indexNodeUri( indexName ) + + "?query="+key+":Builder~%20AND%20Gender:Male&order=index" ).entity(); + + hits = (Collection) JsonHelper.readJson( entity ); + assertEquals( 2, hits.size() ); + //noinspection unchecked + it = (Iterator>) hits.iterator(); + + /* + * index order, so as they were added + */ + node1Map = it.next(); + node2Map = it.next(); + score1 = ( (Double) node1Map.get( "score" ) ).floatValue(); + score2 = ( (Double) node2Map.get( "score" ) ).floatValue(); + assertTrue( + "results returned in wrong order for index ordering", + ( (String) node1Map.get( "self" ) ).endsWith( Long.toString( node1 ) ) ); + assertTrue( + "results returned in wrong order for index ordering", + ( (String) node2Map.get( "self" ) ).endsWith( Long.toString( node2 ) ) ); + assertTrue( "scores are reversed", score2 > score1 ); + + entity = gen().expectedStatus( 200 ).get( + functionalTestHelper.indexNodeUri( indexName ) + + "?query="+key+":Builder~%20AND%20Gender:Male&order=score" ).entity(); + + hits = (Collection) JsonHelper.readJson( entity ); + assertEquals( 2, hits.size() ); + //noinspection unchecked + it = (Iterator>) hits.iterator(); + + node2Map = it.next(); + node1Map = it.next(); + score2 = ( (Double) node2Map.get( "score" ) ).floatValue(); + score1 = ( (Double) node1Map.get( "score" ) ).floatValue(); + assertTrue( + "results returned in wrong order for score ordering", + ( (String) node2Map.get( "self" ) ).endsWith( Long.toString( node2 ) ) ); + assertTrue( + "results returned in wrong order for score ordering", + ( (String) node1Map.get( "self" ) ).endsWith( Long.toString( node1 ) ) ); + assertTrue( "scores are reversed", score2 > score1 ); + } + + /** + * POST ${org.neo4j.server.rest.web}/index/node/{indexName}/{key}/{value} + * "http://uri.for.node.to.index" + */ + @Test + public void shouldRespondWith201CreatedWhenIndexingJsonNodeUri() + { + final long nodeId = helper.createNode(); + final String key = "key"; + final String value = "value"; + final String indexName = indexes.newInstance(); + helper.createNodeIndex( indexName ); + + JaxRsResponse response = RestRequest.req() + .post( functionalTestHelper.indexNodeUri( indexName ), createJsonStringFor( nodeId, key, value ) ); + assertEquals( 201, response.getStatus() ); + assertNotNull( response.getHeaders() + .getFirst( "Location" ) ); + assertEquals( singletonList( nodeId ), helper.getIndexedNodes( indexName, key, value ) ); + } + + @Test + public void shouldGetNodeRepresentationFromIndexUri() throws JsonParseException + { + long nodeId = helper.createNode(); + String key = "key2"; + String value = "value"; + + String indexName = indexes.newInstance(); + helper.createNodeIndex( indexName ); + JaxRsResponse response = RestRequest.req() + .post( functionalTestHelper.indexNodeUri( indexName ), + createJsonStringFor( nodeId, key, value )); + + assertEquals( Status.CREATED.getStatusCode(), response.getStatus() ); + String indexUri = response.getHeaders() + .getFirst( "Location" ); + + response = RestRequest.req() + .get( indexUri ); + assertEquals( 200, response.getStatus() ); + + String entity = response.getEntity(); + + Map map = JsonHelper.jsonToMap( entity ); + assertNotNull( map.get( "self" ) ); + } + + @Test + public void shouldGet404WhenRequestingIndexUriWhichDoesntExist() + { + String key = "key3"; + String value = "value"; + String indexName = indexes.newInstance(); + String indexUri = functionalTestHelper.nodeIndexUri() + indexName + "/" + key + "/" + value; + JaxRsResponse response = RestRequest.req() + .get( indexUri ); + assertEquals( Status.NOT_FOUND.getStatusCode(), response.getStatus() ); + } + + @Test + public void shouldGet404WhenDeletingNonExtistentIndex() + { + final String indexName = indexes.newInstance(); + String indexUri = functionalTestHelper.nodeIndexUri() + indexName; + JaxRsResponse response = RestRequest.req().delete( indexUri ); + assertEquals( Status.NOT_FOUND.getStatusCode(), response.getStatus() ); + } + + @Test + public void shouldGet200AndArrayOfNodeRepsWhenGettingFromIndex() throws JsonParseException + { + String key = "myKey"; + String value = "myValue"; + + String name1 = "Thomas Anderson"; + String name2 = "Agent Smith"; + + String indexName = indexes.newInstance(); + final RestRequest request = RestRequest.req(); + JaxRsResponse responseToPost = request.post( functionalTestHelper.nodeUri(), "{\"name\":\"" + name1 + "\"}" ); + assertEquals( 201, responseToPost.getStatus() ); + String location1 = responseToPost.getHeaders() + .getFirst( HttpHeaders.LOCATION ); + responseToPost.close(); + responseToPost = request.post( functionalTestHelper.nodeUri(), "{\"name\":\"" + name2 + "\"}" ); + assertEquals( 201, responseToPost.getStatus() ); + String location2 = responseToPost.getHeaders() + .getFirst( HttpHeaders.LOCATION ); + responseToPost.close(); + responseToPost = request.post( functionalTestHelper.indexNodeUri( indexName ), + createJsonStringFor( functionalTestHelper.getNodeIdFromUri( location1 ), key, value ) ); + assertEquals( 201, responseToPost.getStatus() ); + String indexLocation1 = responseToPost.getHeaders() + .getFirst( HttpHeaders.LOCATION ); + responseToPost.close(); + responseToPost = request.post( functionalTestHelper.indexNodeUri( indexName ), + createJsonStringFor( functionalTestHelper.getNodeIdFromUri( location2 ), key, value ) ); + assertEquals( 201, responseToPost.getStatus() ); + String indexLocation2 = responseToPost.getHeaders() + .getFirst( HttpHeaders.LOCATION ); + Map uriToName = new HashMap<>(); + uriToName.put( indexLocation1, name1 ); + uriToName.put( indexLocation2, name2 ); + responseToPost.close(); + + JaxRsResponse response = RestRequest.req() + .get( functionalTestHelper.indexNodeUri( indexName, key, value ) ); + assertEquals( 200, response.getStatus() ); + Collection items = (Collection) JsonHelper.readJson( response.getEntity() ); + int counter = 0; + for ( Object item : items ) + { + Map map = (Map) item; + Map properties = (Map) map.get( "data" ); + assertNotNull( map.get( "self" ) ); + String indexedUri = (String) map.get( "indexed" ); + assertEquals( uriToName.get( indexedUri ), properties.get( "name" ) ); + counter++; + } + assertEquals( 2, counter ); + response.close(); + } + + @Test + public void shouldGet200WhenGettingNodesFromIndexWithNoHits() + { + final String indexName = indexes.newInstance(); + helper.createNodeIndex( indexName ); + JaxRsResponse response = RestRequest.req() + .get( functionalTestHelper.indexNodeUri( indexName, "non-existent-key", "non-existent-value" ) ); + assertEquals( 200, response.getStatus() ); + response.close(); + } + + @Documented( "Delete node index." ) + @Test + public void shouldReturn204WhenRemovingNodeIndexes() + { + final String indexName = indexes.newInstance(); + helper.createNodeIndex( indexName ); + + gen() + .expectedStatus( 204 ) + .delete( functionalTestHelper.indexNodeUri( indexName ) ); + } + + // + // REMOVING ENTRIES + // + + @Documented( "Remove all entries with a given node from an index." ) + @Test + public void shouldBeAbleToRemoveIndexingById() + { + String key1 = "kvkey1"; + String key2 = "kvkey2"; + String value1 = "value1"; + String value2 = "value2"; + String indexName = indexes.newInstance(); + long node = helper.createNode( MapUtil.map( key1, value1, key1, value2, key2, value1, key2, value2 ) ); + helper.addNodeToIndex( indexName, key1, value1, node ); + helper.addNodeToIndex( indexName, key1, value2, node ); + helper.addNodeToIndex( indexName, key2, value1, node ); + helper.addNodeToIndex( indexName, key2, value2, node ); + + gen() + .expectedStatus( 204 ) + .delete( functionalTestHelper.indexNodeUri( indexName ) + "/" + node ); + + assertEquals( 0, helper.getIndexedNodes( indexName, key1, value1 ) + .size() ); + assertEquals( 0, helper.getIndexedNodes( indexName, key1, value2 ) + .size() ); + assertEquals( 0, helper.getIndexedNodes( indexName, key2, value1 ) + .size() ); + assertEquals( 0, helper.getIndexedNodes( indexName, key2, value2 ) + .size() ); + } + + @Documented( "Remove all entries with a given node and key from an index." ) + @Test + public void shouldBeAbleToRemoveIndexingByIdAndKey() + { + String key1 = "kvkey1"; + String key2 = "kvkey2"; + String value1 = "value1"; + String value2 = "value2"; + String indexName = indexes.newInstance(); + long node = helper.createNode( MapUtil.map( key1, value1, key1, value2, key2, value1, key2, value2 ) ); + helper.addNodeToIndex( indexName, key1, value1, node ); + helper.addNodeToIndex( indexName, key1, value2, node ); + helper.addNodeToIndex( indexName, key2, value1, node ); + helper.addNodeToIndex( indexName, key2, value2, node ); + + gen() + .expectedStatus( 204 ) + .delete( functionalTestHelper.nodeIndexUri() + indexName + "/" + key2 + "/" + node ); + + assertEquals( 1, helper.getIndexedNodes( indexName, key1, value1 ) + .size() ); + assertEquals( 1, helper.getIndexedNodes( indexName, key1, value2 ) + .size() ); + assertEquals( 0, helper.getIndexedNodes( indexName, key2, value1 ) + .size() ); + assertEquals( 0, helper.getIndexedNodes( indexName, key2, value2 ) + .size() ); + } + + @Documented( "Remove all entries with a given node, key and value from an index." ) + @Test + public void shouldBeAbleToRemoveIndexingByIdAndKeyAndValue() + { + String key1 = "kvkey1"; + String key2 = "kvkey2"; + String value1 = "value1"; + String value2 = "value2"; + String indexName = indexes.newInstance(); + long node = helper.createNode( MapUtil.map( key1, value1, key1, value2, key2, value1, key2, value2 ) ); + helper.addNodeToIndex( indexName, key1, value1, node ); + helper.addNodeToIndex( indexName, key1, value2, node ); + helper.addNodeToIndex( indexName, key2, value1, node ); + helper.addNodeToIndex( indexName, key2, value2, node ); + + gen() + .expectedStatus( 204 ) + .delete( functionalTestHelper.nodeIndexUri() + indexName + "/" + key1 + "/" + value1 + "/" + node ); + + assertEquals( 0, helper.getIndexedNodes( indexName, key1, value1 ) + .size() ); + assertEquals( 1, helper.getIndexedNodes( indexName, key1, value2 ) + .size() ); + assertEquals( 1, helper.getIndexedNodes( indexName, key2, value1 ) + .size() ); + assertEquals( 1, helper.getIndexedNodes( indexName, key2, value2 ) + .size() ); + + } + + @Test + public void shouldBeAbleToIndexValuesContainingSpaces() throws Exception + { + final long nodeId = helper.createNode(); + final String key = "key"; + final String value = "value with spaces in it"; + + String indexName = indexes.newInstance(); + helper.createNodeIndex( indexName ); + final RestRequest request = RestRequest.req(); + JaxRsResponse response = request.post( functionalTestHelper.indexNodeUri( indexName ), + createJsonStringFor( nodeId, key, value ) ); + + assertEquals( Status.CREATED.getStatusCode(), response.getStatus() ); + URI location = response.getLocation(); + response.close(); + response = request.get( functionalTestHelper.indexNodeUri( indexName, key, URIHelper.encode( value ) ) ); + assertEquals( Status.OK.getStatusCode(), response.getStatus() ); + Collection hits = (Collection) JsonHelper.readJson( response.getEntity() ); + assertEquals( 1, hits.size() ); + response.close(); + + CLIENT.resource( location ) + .delete(); + response = request.get( functionalTestHelper.indexNodeUri( indexName, key, URIHelper.encode( value ) ) ); + hits = (Collection) JsonHelper.readJson( response.getEntity() ); + assertEquals( 0, hits.size() ); + } + + @Test + public void shouldRespondWith400WhenSendingCorruptJson() throws Exception + { + final String indexName = indexes.newInstance(); + helper.createNodeIndex( indexName ); + final String corruptJson = "{\"key\" \"myKey\"}"; + JaxRsResponse response = RestRequest.req() + .post( functionalTestHelper.indexNodeUri( indexName ), + corruptJson ); + assertEquals( 400, response.getStatus() ); + response.close(); + } + + @Documented( "Get or create unique node (create).\n" + + "\n" + + "The node is created if it doesn't exist in the unique index already." ) + @Test + public void get_or_create_a_node_in_an_unique_index() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Tobias"; + helper.createNodeIndex( index ); + ResponseEntity response = gen() + .expectedStatus( 201 /* created */ ) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( "{\"key\": \"" + key + "\", \"value\": \"" + value + + "\", \"properties\": {\"" + key + "\": \"" + value + + "\", \"sequence\": 1}}" ) + .post( functionalTestHelper.nodeIndexUri() + index + "?uniqueness=get_or_create" ); + + MultivaluedMap headers = response.response().getHeaders(); + Map result = JsonHelper.jsonToMap( response.entity() ); + assertEquals( result.get( "indexed" ), headers.getFirst( "Location" ) ); + Map data = assertCast( Map.class, result.get( "data" ) ); + assertEquals( value, data.get( key ) ); + assertEquals( 1, data.get( "sequence" ) ); + } + + @Test + public void get_or_create_node_with_array_properties() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Tobias"; + helper.createNodeIndex( index ); + ResponseEntity response = gen() + .expectedStatus( 201 /* created */ ) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( "{\"key\": \"" + key + "\", \"value\": \"" + value + + "\", \"properties\": {\"" + key + "\": \"" + value + + "\", \"array\": [1,2,3]}}" ) + .post( functionalTestHelper.nodeIndexUri() + index + "?unique" ); + + MultivaluedMap headers = response.response().getHeaders(); + Map result = JsonHelper.jsonToMap( response.entity() ); + String location = headers.getFirst("Location"); + assertEquals( result.get( "indexed" ), location ); + Map data = assertCast( Map.class, result.get( "data" ) ); + assertEquals( value, data.get( key ) ); + assertEquals(Arrays.asList( 1, 2, 3), data.get( "array" ) ); + Node node; + try ( Transaction tx = graphdb().beginTx() ) + { + node = graphdb().index().forNodes(index).get(key, value).getSingle(); + } + assertThat( node, inTx( graphdb(), hasProperty( key ).withValue( value ) ) ); + assertThat( node, inTx( graphdb(), hasProperty( "array" ).withValue( new int[]{1, 2, 3} ) ) ); + } + + @Documented( "Get or create unique node (existing).\n" + + "\n" + + "Here,\n" + + "a node is not created but the existing unique node returned, since another node\n" + + "is indexed with the same data already. The node data returned is then that of the\n" + + "already existing node." ) + @Test + public void get_or_create_unique_node_if_already_existing() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Peter"; + + GraphDatabaseService graphdb = graphdb(); + try ( Transaction tx = graphdb().beginTx() ) + { + Node peter = graphdb.createNode(); + peter.setProperty( key, value ); + peter.setProperty( "sequence", 1 ); + graphdb.index().forNodes( index ).add( peter, key, value ); + + tx.success(); + } + + helper.createNodeIndex( index ); + ResponseEntity response = gen() + .expectedStatus( 200 /* ok */ ) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( "{\"key\": \"" + key + "\", \"value\": \"" + value + + "\", \"properties\": {\"" + key + "\": \"" + value + + "\", \"sequence\": 2}}" ) + .post( functionalTestHelper.nodeIndexUri() + index + "?uniqueness=get_or_create" ); + + Map result = JsonHelper.jsonToMap( response.entity() ); + Map data = assertCast( Map.class, result.get( "data" ) ); + assertEquals( value, data.get( key ) ); + assertEquals( 1, data.get( "sequence" ) ); + } + + @Documented( "Create a unique node or return fail (create).\n" + + "\n" + + "Here, in case\n" + + "of an already existing node, an error should be returned. In this\n" + + "example, no existing indexed node is found and a new node is created." ) + @Test + public void create_a_unique_node_or_fail_create() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Tobias"; + helper.createNodeIndex( index ); + ResponseEntity response = gen.get() + .expectedStatus( 201 /* created */ ) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( "{\"key\": \"" + key + "\", \"value\": \"" + value + + "\", \"properties\": {\"" + key + "\": \"" + value + + "\", \"sequence\": 1}}" ) + .post( functionalTestHelper.nodeIndexUri() + index + "?uniqueness=create_or_fail" + + "" ); + + MultivaluedMap headers = response.response().getHeaders(); + Map result = JsonHelper.jsonToMap( response.entity() ); + assertEquals( result.get( "indexed" ), headers.getFirst( "Location" ) ); + Map data = assertCast( Map.class, result.get( "data" ) ); + assertEquals( value, data.get( key ) ); + assertEquals( 1, data.get( "sequence" ) ); + } + + + @Documented( "Create a unique node or return fail (fail).\n" + + "\n" + + "Here, in case\n" + + "of an already existing node, an error should be returned. In this\n" + + "example, an existing node indexed with the same data\n" + + "is found and an error is returned." ) + @Test + public void create_a_unique_node_or_return_fail___fail() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Peter"; + + GraphDatabaseService graphdb = graphdb(); + helper.createNodeIndex( index ); + + try ( Transaction tx = graphdb.beginTx() ) + { + Node peter = graphdb.createNode(); + peter.setProperty( key, value ); + peter.setProperty( "sequence", 1 ); + graphdb.index().forNodes( index ).add( peter, key, value ); + + tx.success(); + } + + RestRequest.req(); + + ResponseEntity response = gen.get() + .expectedStatus( 409 /* conflict */ ) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( "{\"key\": \"" + key + "\", \"value\": \"" + value + + "\", \"properties\": {\"" + key + "\": \"" + value + + "\", \"sequence\": 2}}" ) + .post( functionalTestHelper.nodeIndexUri() + index + "?uniqueness=create_or_fail" ); + + + + Map result = JsonHelper.jsonToMap( response.entity() ); + Map data = assertCast( Map.class, result.get( "data" ) ); + assertEquals( value, data.get( key ) ); + assertEquals( 1, data.get( "sequence" ) ); + } + + @Documented( "Add an existing node to unique index (not indexed).\n" + + "\n" + + "Associates a node with the given key/value pair in the given unique\n" + + "index.\n" + + "\n" + + "In this example, we are using `create_or_fail` uniqueness." ) + @Test + public void addExistingNodeToUniqueIndexAdded() throws Exception + { + final String indexName = indexes.newInstance(); + final String key = "some-key"; + final String value = "some value"; + long nodeId = createNode(); + // implicitly create the index + gen() + .expectedStatus( 201 /* created */ ) + .payload( + JsonHelper.createJsonFrom( generateNodeIndexCreationPayload( key, value, + functionalTestHelper.nodeUri( nodeId ) ) ) ) + .post( functionalTestHelper.indexNodeUri( indexName ) + "?uniqueness=create_or_fail" ); + // look if we get one entry back + JaxRsResponse response = RestRequest.req() + .get( functionalTestHelper.indexNodeUri( indexName, key, URIHelper.encode( value ) ) ); + String entity = response.getEntity(); + Collection hits = (Collection) JsonHelper.readJson( entity ); + assertEquals( 1, hits.size() ); + } + + @Documented( "Add an existing node to unique index (already indexed).\n" + + "\n" + + "In this case, the node already exists in the index, and thus we get a `HTTP 409` status response,\n" + + "as we have set the uniqueness to `create_or_fail`." ) + @Test + public void addExistingNodeToUniqueIndexExisting() throws Exception + { + final String indexName = indexes.newInstance(); + final String key = "some-key"; + final String value = "some value"; + + try ( Transaction tx = graphdb().beginTx() ) + { + Node peter = graphdb().createNode(); + peter.setProperty( key, value ); + graphdb().index().forNodes( indexName ).add( peter, key, value ); + + tx.success(); + } + + gen() + .expectedStatus( 409 /* conflict */ ) + .payload( + JsonHelper.createJsonFrom( generateNodeIndexCreationPayload( key, value, + functionalTestHelper.nodeUri( createNode() ) ) ) ) + .post( functionalTestHelper.indexNodeUri( indexName ) + "?uniqueness=create_or_fail" ); + } + + @Documented( "Backward Compatibility Test (using old syntax ?unique)\n" + + "Put node if absent - Create.\n" + + "\n" + + "Add a node to an index unless a node already exists for the given index data. In\n" + + "this case, a new node is created since nothing existing is found in the index." ) + @Test + public void put_node_if_absent___create() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Mattias"; + helper.createNodeIndex( index ); + String uri = functionalTestHelper.nodeIndexUri() + index + "?unique"; + gen().expectedStatus( 201 /* created */ ) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"uri\":\"" + functionalTestHelper.nodeUri( helper.createNode() ) + "\"}" ) + .post( uri ); + } + + @Test + public void already_indexed_node_should_not_fail_on_create_or_fail() throws Exception + { + // Given + final String index = indexes.newInstance(), key = "name", value = "Peter"; + GraphDatabaseService graphdb = graphdb(); + helper.createNodeIndex( index ); + Node node; + try ( Transaction tx = graphdb.beginTx() ) + { + node = graphdb.createNode(); + graphdb.index().forNodes( index ).add( node, key, value ); + tx.success(); + } + + // When & Then + gen.get() + .expectedStatus( 201 ) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( + "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"uri\":\"" + + functionalTestHelper.nodeUri( node.getId() ) + "\"}" ) + .post( functionalTestHelper.nodeIndexUri() + index + "?uniqueness=create_or_fail" ); + } + + private static T assertCast( Class type, Object object ) + { + assertTrue( type.isInstance( object ) ); + return type.cast( object ); + } + + private long createNode() + { + GraphDatabaseService graphdb = server().getDatabase().getGraph(); + try ( Transaction tx = graphdb.beginTx() ) + { + Node node = graphdb.createNode(); + tx.success(); + return node.getId(); + } + } + + private String createJsonStringFor( final long nodeId, final String key, final String value ) + { + return "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"uri\": \"" + + functionalTestHelper.nodeUri( nodeId ) + "\"}"; + } + + private Object generateNodeIndexCreationPayload( String key, String value, String nodeUri ) + { + Map results = new HashMap<>(); + results.put( "key", key ); + results.put( "value", value ); + results.put( "uri", nodeUri ); + return results; + } + + private final Factory indexes = UniqueStrings.withPrefix( "index" ); +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/IndexRelationshipIT.java b/community/server/src/test/java/org/neo4j/server/rest/IndexRelationshipIT.java new file mode 100644 index 0000000000000..090869ed0fe5a --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/IndexRelationshipIT.java @@ -0,0 +1,561 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.net.URI; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response.Status; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.function.Factory; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.graphdb.Transaction; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; +import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.domain.URIHelper; + +import static java.util.Arrays.asList; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; + +import static org.neo4j.server.helpers.FunctionalTestHelper.CLIENT; + +public class IndexRelationshipIT extends AbstractRestFunctionalTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + private static RestRequest request; + + private enum MyRelationshipTypes implements RelationshipType + { + KNOWS + } + + @BeforeClass + public static void setupServer() + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + request = RestRequest.req(); + } + + /** + * POST ${org.neo4j.server.rest.web}/index/relationship { + * "name":"index-name" "config":{ // optional map of index configuration + * params "key1":"value1", "key2":"value2" } } + * + * POST ${org.neo4j.server.rest.web}/index/relationship/{indexName}/{key}/{ + * value} "http://uri.for.node.to.index" + */ + @Test + public void shouldCreateANamedRelationshipIndexAndAddToIt() throws JsonParseException + { + String indexName = indexes.newInstance(); + int expectedIndexes = helper.getRelationshipIndexes().length + 1; + Map indexSpecification = new HashMap<>(); + indexSpecification.put( "name", indexName ); + JaxRsResponse response = httpPostIndexRelationshipRoot( JsonHelper.createJsonFrom( indexSpecification ) ); + assertEquals( 201, response.getStatus() ); + assertNotNull( response.getHeaders().get( "Location" ).get( 0 ) ); + assertEquals( expectedIndexes, helper.getRelationshipIndexes().length ); + assertNotNull( helper.createRelationshipIndex( indexName ) ); + // Add a relationship to the index + String key = "key"; + String value = "value"; + String relationshipType = "related-to"; + long relationshipId = helper.createRelationship( relationshipType ); + response = httpPostIndexRelationshipNameKeyValue( indexName, relationshipId, key, value ); + assertEquals( Status.CREATED.getStatusCode(), response.getStatus() ); + String indexUri = response.getHeaders().get( "Location" ).get( 0 ); + assertNotNull( indexUri ); + assertEquals( Arrays.asList( (Long) relationshipId ), helper.getIndexedRelationships( indexName, key, value ) ); + // Get the relationship from the indexed URI (Location in header) + response = httpGet( indexUri ); + assertEquals( 200, response.getStatus() ); + String discovredEntity = response.getEntity(); + Map map = JsonHelper.jsonToMap( discovredEntity ); + assertNotNull( map.get( "self" ) ); + } + + @Test + public void shouldGet404WhenRequestingIndexUriWhichDoesntExist() + { + String key = "key3"; + String value = "value"; + String indexName = indexes.newInstance(); + String indexUri = functionalTestHelper.relationshipIndexUri() + indexName + "/" + key + "/" + value; + JaxRsResponse response = httpGet( indexUri ); + assertEquals( Status.NOT_FOUND.getStatusCode(), response.getStatus() ); + } + + @Test + public void shouldGet404WhenDeletingNonExtistentIndex() + { + String indexName = indexes.newInstance(); + String indexUri = functionalTestHelper.relationshipIndexUri() + indexName; + JaxRsResponse response = request.delete( indexUri ); + assertEquals( Status.NOT_FOUND.getStatusCode(), response.getStatus() ); + } + + @Test + public void shouldGet200AndArrayOfRelationshipRepsWhenGettingFromIndex() throws JsonParseException + { + final long startNode = helper.createNode(); + final long endNode = helper.createNode(); + final String key = "key_get"; + final String value = "value"; + final String relationshipName1 = "related-to"; + final String relationshipName2 = "dislikes"; + String jsonString = jsonRelationshipCreationSpecification( relationshipName1, endNode, key, value ); + JaxRsResponse createRelationshipResponse = httpPostCreateRelationship( startNode, jsonString ); + assertEquals( 201, createRelationshipResponse.getStatus() ); + String relationshipLocation1 = createRelationshipResponse.getLocation().toString(); + jsonString = jsonRelationshipCreationSpecification( relationshipName2, endNode, key, value ); + createRelationshipResponse = httpPostCreateRelationship( startNode, jsonString ); + assertEquals( 201, createRelationshipResponse.getStatus() ); + String relationshipLocation2 = createRelationshipResponse.getHeaders().get( HttpHeaders.LOCATION ).get( 0 ); + String indexName = indexes.newInstance(); + JaxRsResponse indexCreationResponse = httpPostIndexRelationshipRoot( "{\"name\":\"" + indexName + "\"}" ); + assertEquals( 201, indexCreationResponse.getStatus() ); + JaxRsResponse indexedRelationshipResponse = httpPostIndexRelationshipNameKeyValue( indexName, + functionalTestHelper.getRelationshipIdFromUri( relationshipLocation1 ), key, value ); + String indexLocation1 = indexedRelationshipResponse.getHeaders().get( HttpHeaders.LOCATION ).get( 0 ); + indexedRelationshipResponse = httpPostIndexRelationshipNameKeyValue( indexName, + functionalTestHelper.getRelationshipIdFromUri( relationshipLocation2 ), key, value ); + String indexLocation2 = indexedRelationshipResponse.getHeaders().get( HttpHeaders.LOCATION ).get( 0 ); + Map uriToName = new HashMap<>(); + uriToName.put( indexLocation1.toString(), relationshipName1 ); + uriToName.put( indexLocation2.toString(), relationshipName2 ); + JaxRsResponse response = RestRequest.req().get( + functionalTestHelper.indexRelationshipUri( indexName, key, value ) ); + assertEquals( 200, response.getStatus() ); + Collection items = (Collection) JsonHelper.readJson( response.getEntity() ); + int counter = 0; + for ( Object item : items ) + { + Map map = (Map) item; + assertNotNull( map.get( "self" ) ); + String indexedUri = (String) map.get( "indexed" ); + assertEquals( uriToName.get( indexedUri ), map.get( "type" ) ); + counter++; + } + assertEquals( 2, counter ); + response.close(); + } + + @Test + public void shouldGet200WhenGettingRelationshipFromIndexWithNoHits() + { + String indexName = indexes.newInstance(); + helper.createRelationshipIndex( indexName ); + JaxRsResponse response = RestRequest.req().get( + functionalTestHelper.indexRelationshipUri( indexName, "non-existent-key", "non-existent-value" ) ); + assertEquals( 200, response.getStatus() ); + } + + @Test + public void shouldGet200WhenQueryingIndex() + { + String indexName = indexes.newInstance(); + String key = "bobsKey"; + String value = "bobsValue"; + long relationship = helper.createRelationship( "TYPE" ); + helper.addRelationshipToIndex( indexName, key, value, relationship ); + JaxRsResponse response = RestRequest.req().get( + functionalTestHelper.indexRelationshipUri( indexName ) + "?query=" + key + ":" + value ); + assertEquals( 200, response.getStatus() ); + } + + @Test + public void shouldBeAbleToRemoveIndexing() + { + String key1 = "kvkey1"; + String key2 = "kvkey2"; + String value1 = "value1"; + String value2 = "value2"; + String indexName = indexes.newInstance(); + long relationship = helper.createRelationship( "some type" ); + helper.setRelationshipProperties( relationship, + MapUtil.map( key1, value1, key1, value2, key2, value1, key2, value2 ) ); + helper.addRelationshipToIndex( indexName, key1, value1, relationship ); + helper.addRelationshipToIndex( indexName, key1, value2, relationship ); + helper.addRelationshipToIndex( indexName, key2, value1, relationship ); + helper.addRelationshipToIndex( indexName, key2, value2, relationship ); + assertEquals( 1, helper.getIndexedRelationships( indexName, key1, value1 ).size() ); + assertEquals( 1, helper.getIndexedRelationships( indexName, key1, value2 ).size() ); + assertEquals( 1, helper.getIndexedRelationships( indexName, key2, value1 ).size() ); + assertEquals( 1, helper.getIndexedRelationships( indexName, key2, value2 ).size() ); + JaxRsResponse response = RestRequest.req().delete( + functionalTestHelper.relationshipIndexUri() + indexName + "/" + key1 + "/" + value1 + "/" + + relationship ); + assertEquals( 204, response.getStatus() ); + assertEquals( 0, helper.getIndexedRelationships( indexName, key1, value1 ).size() ); + assertEquals( 1, helper.getIndexedRelationships( indexName, key1, value2 ).size() ); + assertEquals( 1, helper.getIndexedRelationships( indexName, key2, value1 ).size() ); + assertEquals( 1, helper.getIndexedRelationships( indexName, key2, value2 ).size() ); + response = RestRequest.req().delete( + functionalTestHelper.relationshipIndexUri() + indexName + "/" + key2 + "/" + relationship ); + assertEquals( 204, response.getStatus() ); + assertEquals( 0, helper.getIndexedRelationships( indexName, key1, value1 ).size() ); + assertEquals( 1, helper.getIndexedRelationships( indexName, key1, value2 ).size() ); + assertEquals( 0, helper.getIndexedRelationships( indexName, key2, value1 ).size() ); + assertEquals( 0, helper.getIndexedRelationships( indexName, key2, value2 ).size() ); + response = RestRequest.req().delete( + functionalTestHelper.relationshipIndexUri() + indexName + "/" + relationship ); + assertEquals( 204, response.getStatus() ); + assertEquals( 0, helper.getIndexedRelationships( indexName, key1, value1 ).size() ); + assertEquals( 0, helper.getIndexedRelationships( indexName, key1, value2 ).size() ); + assertEquals( 0, helper.getIndexedRelationships( indexName, key2, value1 ).size() ); + assertEquals( 0, helper.getIndexedRelationships( indexName, key2, value2 ).size() ); + // Delete the index + response = RestRequest.req().delete( functionalTestHelper.indexRelationshipUri( indexName ) ); + assertEquals( 204, response.getStatus() ); + assertFalse( asList( helper.getRelationshipIndexes() ).contains( indexName ) ); + } + + @Test + public void shouldBeAbleToIndexValuesContainingSpaces() throws Exception + { + final long startNodeId = helper.createNode(); + final long endNodeId = helper.createNode(); + final String relationshiptype = "tested-together"; + final long relationshipId = helper.createRelationship( relationshiptype, startNodeId, endNodeId ); + final String key = "key"; + final String value = "value with spaces in it"; + final String indexName = indexes.newInstance(); + helper.createRelationshipIndex( indexName ); + JaxRsResponse response = httpPostIndexRelationshipNameKeyValue( indexName, relationshipId, key, value ); + assertEquals( Status.CREATED.getStatusCode(), response.getStatus() ); + URI location = response.getLocation(); + response.close(); + response = httpGetIndexRelationshipNameKeyValue( indexName, key, URIHelper.encode( value ) ); + assertEquals( Status.OK.getStatusCode(), response.getStatus() ); + String responseEntity = response.getEntity(); + Collection hits = (Collection) JsonHelper.readJson( responseEntity ); + assertEquals( 1, hits.size() ); + response.close(); + CLIENT.resource( location ).delete(); + response = httpGetIndexRelationshipNameKeyValue( indexName, key, URIHelper.encode( value ) ); + assertEquals( 200, response.getStatus() ); + responseEntity = response.getEntity(); + hits = (Collection) JsonHelper.readJson( responseEntity ); + assertEquals( 0, hits.size() ); + response.close(); + } + + @Test + public void shouldRespondWith400WhenSendingCorruptJson() throws Exception + { + final String indexName = indexes.newInstance(); + helper.createRelationshipIndex( indexName ); + final String corruptJson = "{[}"; + JaxRsResponse response = RestRequest.req().post( functionalTestHelper.indexRelationshipUri( indexName ), + corruptJson ); + assertEquals( 400, response.getStatus() ); + } + + @Documented( "Get or create unique relationship (create).\n" + + "\n" + + "Create a unique relationship in an index.\n" + + "If a relationship matching the given key and value already exists in the index, it will be returned.\n" + + "If not, a new relationship will be created.\n" + + "\n" + + "NOTE: The type and direction of the relationship is not regarded when determining uniqueness." ) + @Test + public void get_or_create_relationship() throws Exception + { + final String index = indexes.newInstance(), type="knowledge", key = "name", value = "Tobias"; + helper.createRelationshipIndex( index ); + long start = helper.createNode(); + long end = helper.createNode(); + gen.get() + .expectedStatus( 201 /* created */) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( "{\"key\": \"" + key + "\", \"value\":\"" + value + + "\", \"start\": \"" + functionalTestHelper.nodeUri( start ) + + "\", \"end\": \"" + functionalTestHelper.nodeUri( end ) + + "\", \"type\": \"" + type + "\"}" ) + .post( functionalTestHelper.relationshipIndexUri() + index + "/?uniqueness=get_or_create" ); + } + + @Documented( "Get or create unique relationship (existing).\n" + + "\n" + + "Here, in case\n" + + "of an already existing relationship, the sent data is ignored and the\n" + + "existing relationship returned." ) + @Test + public void get_or_create_unique_relationship_existing() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Peter"; + GraphDatabaseService graphdb = graphdb(); + helper.createRelationshipIndex( index ); + try ( Transaction tx = graphdb.beginTx() ) + { + Node node1 = graphdb.createNode(); + Node node2 = graphdb.createNode(); + Relationship rel = node1.createRelationshipTo( node2, MyRelationshipTypes.KNOWS ); + graphdb.index().forRelationships( index ).add( rel, key, value ); + tx.success(); + } + gen.get() + .expectedStatus( 200 /* existing */) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( + "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"start\": \"" + + functionalTestHelper.nodeUri( helper.createNode() ) + "\", \"end\": \"" + + functionalTestHelper.nodeUri( helper.createNode() ) + "\", \"type\":\"" + + MyRelationshipTypes.KNOWS + "\"}" ) + .post( functionalTestHelper.relationshipIndexUri() + index + "?uniqueness=get_or_create" ); + } + + @Documented( "Create a unique relationship or return fail (create).\n" + + "\n" + + "Here, in case\n" + + "of an already existing relationship, an error should be returned. In this\n" + + "example, no existing relationship is found and a new relationship is created." ) + @Test + public void create_a_unique_relationship_or_return_fail___create() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Tobias"; + helper.createRelationshipIndex( index ); + ResponseEntity response = gen + .get() + .expectedStatus( 201 /* created */) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( + "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"start\": \"" + + functionalTestHelper.nodeUri( helper.createNode() ) + "\", \"end\": \"" + + functionalTestHelper.nodeUri( helper.createNode() ) + "\", \"type\":\"" + + MyRelationshipTypes.KNOWS + "\"}" ) + .post( functionalTestHelper.relationshipIndexUri() + index + "?uniqueness=create_or_fail" ); + MultivaluedMap headers = response.response().getHeaders(); + Map result = JsonHelper.jsonToMap( response.entity() ); + assertEquals( result.get( "indexed" ), headers.getFirst( "Location" ) ); + } + + @Documented( "Create a unique relationship or return fail (fail).\n" + + "\n" + + "Here, in case\n" + + "of an already existing relationship, an error should be returned. In this\n" + + "example, an existing relationship is found and an error is returned." ) + @Test + public void create_a_unique_relationship_or_return_fail___fail() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Peter"; + GraphDatabaseService graphdb = graphdb(); + helper.createRelationshipIndex( index ); + try ( Transaction tx = graphdb.beginTx() ) + { + Node node1 = graphdb.createNode(); + Node node2 = graphdb.createNode(); + Relationship rel = node1.createRelationshipTo( node2, MyRelationshipTypes.KNOWS ); + graphdb.index().forRelationships( index ).add( rel, key, value ); + tx.success(); + } + gen.get() + .expectedStatus( 409 /* conflict */) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( + "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"start\": \"" + + functionalTestHelper.nodeUri( helper.createNode() ) + "\", \"end\": \"" + + functionalTestHelper.nodeUri( helper.createNode() ) + "\", \"type\":\"" + + MyRelationshipTypes.KNOWS + "\"}" ) + .post( functionalTestHelper.relationshipIndexUri() + index + "?uniqueness=create_or_fail" ); + } + + @Documented( "Add an existing relationship to a unique index (not indexed).\n" + + "\n" + + "If a relationship matching the given key and value already exists in the index, it will be returned.\n" + + "If not, an `HTTP 409` (conflict) status will be returned in this case, as we are using `create_or_fail`.\n" + + "\n" + + "It's possible to use `get_or_create` uniqueness as well.\n" + + "\n" + + "NOTE: The type and direction of the relationship is not regarded when determining uniqueness." ) + @Test + public void put_relationship_or_fail_if_absent() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Peter"; + helper.createRelationshipIndex( index ); + gen.get() + .expectedStatus( 201 /* created */) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( + "{\"key\": \"" + + key + + "\", \"value\": \"" + + value + + "\", \"uri\":\"" + + functionalTestHelper.relationshipUri( helper.createRelationship( "KNOWS", + helper.createNode(), helper.createNode() ) ) + "\"}" ) + .post( functionalTestHelper.relationshipIndexUri() + index + "?uniqueness=create_or_fail" ); + } + + @Documented( "Add an existing relationship to a unique index (already indexed)." ) + @Test + public void put_relationship_if_absent_only_fail() throws Exception + { + // Given + final String index = indexes.newInstance(), key = "name", value = "Peter"; + GraphDatabaseService graphdb = graphdb(); + helper.createRelationshipIndex( index ); + try ( Transaction tx = graphdb.beginTx() ) + { + Node node1 = graphdb.createNode(); + Node node2 = graphdb.createNode(); + Relationship rel = node1.createRelationshipTo( node2, MyRelationshipTypes.KNOWS ); + graphdb.index().forRelationships( index ).add( rel, key, value ); + tx.success(); + } + + Relationship rel; + try ( Transaction tx = graphdb.beginTx() ) + { + Node node1 = graphdb.createNode(); + Node node2 = graphdb.createNode(); + rel = node1.createRelationshipTo( node2, MyRelationshipTypes.KNOWS ); + tx.success(); + } + + // When & Then + gen.get() + .expectedStatus( 409 /* conflict */) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( + "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"uri\":\"" + + functionalTestHelper.relationshipUri( rel.getId() ) + "\"}" ) + .post( functionalTestHelper.relationshipIndexUri() + index + "?uniqueness=create_or_fail" ); + } + + @Test + public void already_indexed_relationship_should_not_fail_on_create_or_fail() throws Exception + { + // Given + final String index = indexes.newInstance(), key = "name", value = "Peter"; + GraphDatabaseService graphdb = graphdb(); + helper.createRelationshipIndex( index ); + Relationship rel; + try ( Transaction tx = graphdb.beginTx() ) + { + Node node1 = graphdb.createNode(); + Node node2 = graphdb.createNode(); + rel = node1.createRelationshipTo( node2, MyRelationshipTypes.KNOWS ); + graphdb.index().forRelationships( index ).add( rel, key, value ); + tx.success(); + } + + // When & Then + gen.get() + .expectedStatus( 201 ) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( + "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"uri\":\"" + + functionalTestHelper.relationshipUri( rel.getId() ) + "\"}" ) + .post( functionalTestHelper.relationshipIndexUri() + index + "?uniqueness=create_or_fail" ); + } + + /** + * This can be safely removed in version 1.11 an onwards. + */ + @Test + public void createUniqueShouldBeBackwardsCompatibleWith1_8() throws Exception + { + final String index = indexes.newInstance(), key = "name", value = "Peter"; + GraphDatabaseService graphdb = graphdb(); + helper.createRelationshipIndex( index ); + try ( Transaction tx = graphdb.beginTx() ) + { + Node node1 = graphdb.createNode(); + Node node2 = graphdb.createNode(); + Relationship rel = node1.createRelationshipTo( node2, MyRelationshipTypes.KNOWS ); + graphdb.index().forRelationships( index ).add( rel, key, value ); + tx.success(); + } + gen.get() + .expectedStatus( 200 /* conflict */) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .payload( + "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"start\": \"" + + functionalTestHelper.nodeUri( helper.createNode() ) + "\", \"end\": \"" + + functionalTestHelper.nodeUri( helper.createNode() ) + "\", \"type\":\"" + + MyRelationshipTypes.KNOWS + "\"}" ) + .post( functionalTestHelper.relationshipIndexUri() + index + "?unique" ); + } + + private JaxRsResponse httpPostIndexRelationshipRoot( String jsonIndexSpecification ) + { + return RestRequest.req().post( functionalTestHelper.relationshipIndexUri(), jsonIndexSpecification ); + } + + private JaxRsResponse httpGetIndexRelationshipNameKeyValue( String indexName, String key, String value ) + { + return RestRequest.req().get( functionalTestHelper.indexRelationshipUri( indexName, key, value ) ); + } + + private JaxRsResponse httpPostIndexRelationshipNameKeyValue( String indexName, long relationshipId, String key, + String value ) + { + return RestRequest.req().post( functionalTestHelper.indexRelationshipUri( indexName ), + createJsonStringFor( relationshipId, key, value ) ); + } + + private String createJsonStringFor( final long relationshipId, final String key, final String value ) + { + return "{\"key\": \"" + key + "\", \"value\": \"" + value + "\", \"uri\": \"" + + functionalTestHelper.relationshipUri( relationshipId ) + "\"}"; + } + + private JaxRsResponse httpGet( String indexUri ) + { + return request.get( indexUri ); + } + + private JaxRsResponse httpPostCreateRelationship( long startNode, String jsonString ) + { + return RestRequest.req().post( functionalTestHelper.dataUri() + "node/" + startNode + "/relationships", + jsonString ); + } + + private String jsonRelationshipCreationSpecification( String relationshipName, long endNode, String key, + String value ) + { + return "{\"to\" : \"" + functionalTestHelper.dataUri() + "node/" + endNode + "\"," + "\"type\" : \"" + + relationshipName + "\", " + "\"data\" : {\"" + key + "\" : \"" + value + "\"}}"; + } + + private final Factory indexes = UniqueStrings.withPrefix( "index" ); +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/JmxServiceIT.java b/community/server/src/test/java/org/neo4j/server/rest/JmxServiceIT.java new file mode 100644 index 0000000000000..4cf60cebc7dbb --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/JmxServiceIT.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.server.helpers.FunctionalTestHelper; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; + +public class JmxServiceIT extends AbstractRestFunctionalTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + } + + @Test + public void shouldRespondWithJMXResources() throws Exception { + String url = functionalTestHelper.managementUri() + "/server/jmx"; + JaxRsResponse resp = RestRequest.req().get(url); + String json = resp.getEntity(); + + assertEquals(json, 200, resp.getStatus()); + assertThat(json, containsString("resources")); + assertThat(json, containsString("jmx/domain/{domain}/{objectName}")); + resp.close(); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/LabelsIT.java b/community/server/src/test/java/org/neo4j/server/rest/LabelsIT.java new file mode 100644 index 0000000000000..baef2c9b49cb6 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/LabelsIT.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.UnsupportedEncodingException; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; + +import org.junit.Test; + +import org.neo4j.graphdb.Node; +import org.neo4j.helpers.collection.Iterables; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.web.PropertyValueException; +import org.neo4j.test.GraphDescription; +import org.neo4j.test.GraphDescription.LABEL; +import org.neo4j.test.GraphDescription.NODE; +import org.neo4j.test.GraphDescription.PROP; + +import static java.util.Arrays.asList; + +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import static org.neo4j.graphdb.Label.label; +import static org.neo4j.graphdb.Neo4jMatchers.hasLabel; +import static org.neo4j.graphdb.Neo4jMatchers.hasLabels; +import static org.neo4j.graphdb.Neo4jMatchers.inTx; +import static org.neo4j.helpers.collection.Iterables.map; +import static org.neo4j.helpers.collection.Iterators.asSet; +import static org.neo4j.server.rest.domain.JsonHelper.createJsonFrom; +import static org.neo4j.server.rest.domain.JsonHelper.readJson; +import static org.neo4j.test.GraphDescription.PropType.ARRAY; +import static org.neo4j.test.GraphDescription.PropType.STRING; + +public class LabelsIT extends AbstractRestFunctionalTestBase +{ + + @Documented( "Adding a label to a node." ) + @Test + @GraphDescription.Graph( nodes = { @NODE( name = "Clint Eastwood", setNameProperty = true ) } ) + public void adding_a_label_to_a_node() throws PropertyValueException + { + Map nodes = data.get(); + String nodeUri = getNodeUri( nodes.get( "Clint Eastwood" ) ); + + gen.get() + .description( startGraph( "adding a label to a node" ) ) + .expectedStatus( 204 ) + .payload( createJsonFrom( "Person" ) ) + .post( nodeUri + "/labels" ); + } + + @Documented( "Adding multiple labels to a node." ) + @Test + @GraphDescription.Graph( nodes = { @NODE( name = "Clint Eastwood", setNameProperty = true ) } ) + public void adding_multiple_labels_to_a_node() throws PropertyValueException + { + Map nodes = data.get(); + String nodeUri = getNodeUri( nodes.get( "Clint Eastwood" ) ); + + gen.get() + .description( startGraph( "adding multiple labels to a node" ) ) + .expectedStatus( 204 ) + .payload( createJsonFrom( new String[]{"Person", "Actor"} ) ) + .post( nodeUri + "/labels" ); + + // Then + assertThat( nodes.get( "Clint Eastwood" ), inTx( graphdb(), hasLabels( "Person", "Actor" ) ) ); + } + + @Documented( "Adding a label with an invalid name.\n" + + "\n" + + "Labels with empty names are not allowed, however, all other valid strings are accepted as label names.\n" + + "Adding an invalid label to a node will lead to a HTTP 400 response." ) + @Test + @GraphDescription.Graph( nodes = { @NODE( name = "Clint Eastwood", setNameProperty = true ) } ) + public void adding_an_invalid_label_to_a_node() throws PropertyValueException + { + Map nodes = data.get(); + String nodeUri = getNodeUri( nodes.get( "Clint Eastwood" ) ); + + gen.get() + .expectedStatus( 400 ) + .payload( createJsonFrom( "" ) ) + .post( nodeUri + "/labels" ); + } + + @Documented( "Replacing labels on a node.\n" + + "\n" + + "This removes any labels currently on a node, and replaces them with the labels passed in as the\n" + + "request body." ) + @Test + @GraphDescription.Graph( nodes = { @NODE( name = "Clint Eastwood", setNameProperty = true, + labels = { @LABEL( "Person" ) }) } ) + public void replacing_labels_on_a_node() throws PropertyValueException + { + Map nodes = data.get(); + String nodeUri = getNodeUri( nodes.get( "Clint Eastwood" ) ); + + // When + gen.get() + .description( startGraph( "replacing labels on a node" ) ) + .expectedStatus( 204 ) + .payload( createJsonFrom( new String[]{"Actor", "Director"}) ) + .put( nodeUri + "/labels" ); + + // Then + assertThat( nodes.get( "Clint Eastwood" ), inTx(graphdb(), hasLabels("Actor", "Director")) ); + } + + @Documented( "Listing labels for a node." ) + @Test + @GraphDescription.Graph( nodes = { @NODE( name = "Clint Eastwood", labels = { @LABEL( "Actor" ), @LABEL( "Director" ) }, setNameProperty = true ) } ) + public void listing_node_labels() throws JsonParseException + { + Map nodes = data.get(); + String nodeUri = getNodeUri( nodes.get( "Clint Eastwood" ) ); + + String body = gen.get() + .expectedStatus( 200 ) + .get( nodeUri + "/labels" ) + .entity(); + @SuppressWarnings("unchecked") + List labels = (List) readJson( body ); + assertEquals( asSet( "Actor", "Director" ), Iterables.asSet( labels ) ); + } + + @Documented( "Removing a label from a node." ) + @Test + @GraphDescription.Graph( nodes = { @NODE( name = "Clint Eastwood", setNameProperty = true, labels = { @LABEL( "Person" ) } ) } ) + public void removing_a_label_from_a_node() throws PropertyValueException + { + Map nodes = data.get(); + Node node = nodes.get( "Clint Eastwood" ); + String nodeUri = getNodeUri( node ); + + String labelName = "Person"; + gen.get() + .description( startGraph( "removing a label from a node" ) ) + .expectedStatus( 204 ) + .delete( nodeUri + "/labels/" + labelName ); + + assertThat( node, inTx( graphdb(), not( hasLabel( label( labelName ) ) ) ) ); + } + + @Documented( "Removing a non-existent label from a node." ) + @Test + @GraphDescription.Graph( nodes = { @NODE( name = "Clint Eastwood", setNameProperty = true ) } ) + public void removing_a_non_existent_label_from_a_node() throws PropertyValueException + { + Map nodes = data.get(); + Node node = nodes.get( "Clint Eastwood" ); + String nodeUri = getNodeUri( node ); + + String labelName = "Person"; + gen.get() + .description( startGraph( "removing a non-existent label from a node" ) ) + .expectedStatus( 204 ) + .delete( nodeUri + "/labels/" + labelName ); + + assertThat( node, inTx( graphdb(), not( hasLabel( label( labelName ) ) ) ) ); + } + + @Documented( "Get all nodes with a label." ) + @Test + @GraphDescription.Graph( nodes = { + @NODE( name = "Clint Eastwood", setNameProperty = true, labels = { @LABEL( "Actor" ), @LABEL( "Director" ) } ), + @NODE( name = "Donald Sutherland", setNameProperty = true, labels = { @LABEL( "Actor" ) } ), + @NODE( name = "Steven Spielberg", setNameProperty = true, labels = { @LABEL( "Director" ) } ) + } ) + public void get_all_nodes_with_label() throws JsonParseException + { + data.get(); + String uri = getNodesWithLabelUri( "Actor" ); + String body = gen.get() + .expectedStatus( 200 ) + .get( uri ) + .entity(); + + List parsed = (List) readJson( body ); + assertEquals( asSet( "Clint Eastwood", "Donald Sutherland" ), Iterables + .asSet( map( getProperty( "name", String.class ), parsed ) ) ); + } + + @Test + @Documented( "Get nodes by label and property.\n" + + "\n" + + "You can retrieve all nodes with a given label and property by passing one property as a query parameter.\n" + + "Notice that the property value is JSON-encoded and then URL-encoded.\n" + + "\n" + + "If there is an index available on the label/property combination you send, that index will be used. If no\n" + + "index is available, all nodes with the given label will be filtered through to find matching nodes.\n" + + "\n" + + "Currently, it is not possible to search using multiple properties." ) + @GraphDescription.Graph( nodes = { + @NODE( name = "Donald Sutherland", labels={ @LABEL( "Person" )} ), + @NODE( name = "Clint Eastwood", labels={ @LABEL( "Person" )}, properties = { @PROP( key = "name", value = "Clint Eastwood" )}), + @NODE( name = "Steven Spielberg", labels={ @LABEL( "Person" )}, properties = { @PROP( key = "name", value = "Steven Spielberg" )})}) + public void get_nodes_with_label_and_property() throws JsonParseException, UnsupportedEncodingException + { + data.get(); + + String labelName = "Person"; + + String result = gen.get() + .expectedStatus( 200 ) + .get( getNodesWithLabelAndPropertyUri( labelName, "name", "Clint Eastwood" ) ) + .entity(); + + List parsed = (List) readJson( result ); + assertEquals( asSet( "Clint Eastwood" ), Iterables.asSet( map( getProperty( "name", String.class ), parsed ) ) ); + } + + @Test + @Documented( "Get nodes by label and array property." ) + @GraphDescription.Graph( nodes = { + @NODE(name = "Donald Sutherland", labels = {@LABEL("Person")}), + @NODE(name = "Clint Eastwood", labels = {@LABEL("Person")}, properties = + {@PROP(key = "names", value = "Clint,Eastwood", type = ARRAY, componentType = STRING)}), + @NODE(name = "Steven Spielberg", labels = {@LABEL("Person")}, properties = + {@PROP(key = "names", value = "Steven,Spielberg", type = ARRAY, componentType = STRING)})}) + public void get_nodes_with_label_and_array_property() throws JsonParseException, UnsupportedEncodingException + { + data.get(); + + String labelName = "Person"; + + String uri = getNodesWithLabelAndPropertyUri( labelName, "names", new String[] { "Clint", "Eastwood" } ); + + String result = gen.get() + .expectedStatus( 200 ) + .get( uri ) + .entity(); + + List parsed = (List) readJson( result ); + assertEquals( 1, parsed.size() ); + + //noinspection AssertEqualsBetweenInconvertibleTypes + assertEquals( Iterables.asSet( asList( asList( "Clint", "Eastwood" ) ) ), + Iterables.asSet( map( getProperty( "names", List.class ), parsed ) ) ); + } + + @Test + @Documented( "List all labels.\n" + + " \n" + + "By default, the server will return labels in use only. If you also want to return labels not in use,\n" + + "append the \"in_use=0\" query parameter." ) + @GraphDescription.Graph( nodes = { + @NODE( name = "Clint Eastwood", setNameProperty = true, labels = { @LABEL( "Person" ), @LABEL( "Actor" ), @LABEL( "Director" ) } ), + @NODE( name = "Donald Sutherland", setNameProperty = true, labels = { @LABEL( "Person" ), @LABEL( "Actor" ) } ), + @NODE( name = "Steven Spielberg", setNameProperty = true, labels = { @LABEL( "Person" ), @LABEL( "Director" ) } ) + } ) + public void list_all_labels() throws JsonParseException + { + data.get(); + String uri = getLabelsUri(); + String body = gen.get() + .expectedStatus( 200 ) + .get( uri ) + .entity(); + + Set parsed = Iterables.asSet((List) readJson( body )); + assertTrue( parsed.contains( "Person" ) ); + assertTrue( parsed.contains( "Actor" ) ); + assertTrue( parsed.contains( "Director" ) ); + } + + private Function getProperty( final String propertyKey, final Class propertyType ) + { + return from -> { + Map node = (Map) from; + Map data1 = (Map) node.get( "data" ); + return propertyType.cast( data1.get( propertyKey ) ); + }; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/ListPropertyKeysIT.java b/community/server/src/test/java/org/neo4j/server/rest/ListPropertyKeysIT.java new file mode 100644 index 0000000000000..9985e39d1aebb --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/ListPropertyKeysIT.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.util.List; +import java.util.Set; + +import org.junit.Test; + +import org.neo4j.helpers.collection.Iterables; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription; + +import static org.junit.Assert.assertTrue; + +import static org.neo4j.server.rest.domain.JsonHelper.readJson; + +public class ListPropertyKeysIT extends AbstractRestFunctionalTestBase +{ + @Test + @Documented( "List all property keys." ) + @GraphDescription.Graph( nodes = { + @GraphDescription.NODE( name = "a", setNameProperty = true ), + @GraphDescription.NODE( name = "b", setNameProperty = true ), + @GraphDescription.NODE( name = "c", setNameProperty = true ) + } ) + public void list_all_property_keys_ever_used() throws JsonParseException + { + data.get(); + String uri = getPropertyKeysUri(); + String body = gen.get() + .expectedStatus( 200 ) + .get( uri ) + .entity(); + + Set parsed = Iterables.asSet((List) readJson( body )); + assertTrue( parsed.contains( "name" ) ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/ManageNodeIT.java b/community/server/src/test/java/org/neo4j/server/rest/ManageNodeIT.java new file mode 100644 index 0000000000000..302526dc75f65 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/ManageNodeIT.java @@ -0,0 +1,651 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.hamcrest.MatcherAssert; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.neo4j.helpers.FakeClock; +import org.neo4j.kernel.GraphDatabaseDependencies; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.configuration.Settings; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; +import org.neo4j.kernel.internal.KernelData; +import org.neo4j.kernel.monitoring.Monitors; +import org.neo4j.logging.LogProvider; +import org.neo4j.logging.NullLogProvider; +import org.neo4j.server.CommunityNeoServer; +import org.neo4j.server.NeoServer; +import org.neo4j.server.configuration.ServerSettings; +import org.neo4j.server.database.Database; +import org.neo4j.server.database.WrappedDatabase; +import org.neo4j.server.helpers.CommunityServerBuilder; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.management.JmxService; +import org.neo4j.server.rest.management.RootService; +import org.neo4j.server.rest.management.VersionAndEditionService; +import org.neo4j.server.rest.management.console.ConsoleService; +import org.neo4j.server.rest.management.console.ConsoleSessionFactory; +import org.neo4j.server.rest.management.console.ScriptSession; +import org.neo4j.server.rest.management.console.ShellSession; +import org.neo4j.server.rest.repr.OutputFormat; +import org.neo4j.server.rest.repr.formats.JsonFormat; +import org.neo4j.shell.ShellSettings; +import org.neo4j.string.UTF8; +import org.neo4j.test.TestData; +import org.neo4j.test.TestGraphDatabaseFactory; +import org.neo4j.test.server.EntityOutputFormat; +import org.neo4j.test.server.ExclusiveServerTestBase; +import org.neo4j.test.server.HTTP; + +import static java.lang.System.lineSeparator; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasKey; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import static org.neo4j.helpers.collection.MapUtil.stringMap; +import static org.neo4j.server.configuration.ServerSettings.httpConnector; +import static org.neo4j.test.SuppressOutput.suppressAll; + +public class ManageNodeIT extends AbstractRestFunctionalDocTestBase +{ + private static final long NON_EXISTENT_NODE_ID = 999999; + private static String NODE_URI_PATTERN = "^.*/node/[0-9]+$"; + + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + } + + @Test + public void create_node() throws Exception + { + JaxRsResponse response = gen.get() + .expectedStatus( 201 ) + .expectedHeader( "Location" ) + .post( functionalTestHelper.nodeUri() ) + .response(); + assertTrue( response.getLocation() + .toString() + .matches( NODE_URI_PATTERN ) ); + } + + @Test + public void create_node_with_properties() throws Exception + { + JaxRsResponse response = gen.get() + .payload( "{\"foo\" : \"bar\"}" ) + .expectedStatus( 201 ) + .expectedHeader( "Location" ) + .expectedHeader( "Content-Length" ) + .post( functionalTestHelper.nodeUri() ) + .response(); + assertTrue( response.getLocation() + .toString() + .matches( NODE_URI_PATTERN ) ); + } + + @Test + public void create_node_with_array_properties() throws Exception + { + String response = gen.get() + .payload( "{\"foo\" : [1,2,3]}" ) + .expectedStatus( 201 ) + .expectedHeader( "Location" ) + .expectedHeader( "Content-Length" ) + .post( functionalTestHelper.nodeUri() ) + .response().getEntity(); + assertThat( response, containsString( "[ 1, 2, 3 ]" ) ); + } + + @Documented( "Property values can not be null.\n" + + "\n" + + "This example shows the response you get when trying to set a property to +null+." ) + @Test + public void shouldGet400WhenSupplyingNullValueForAProperty() throws Exception + { + gen.get() + .payload( "{\"foo\":null}" ) + .expectedStatus( 400 ) + .post( functionalTestHelper.nodeUri() ); + } + + @Test + public void shouldGet400WhenCreatingNodeMalformedProperties() throws Exception + { + JaxRsResponse response = sendCreateRequestToServer("this:::isNot::JSON}"); + assertEquals( 400, response.getStatus() ); + } + + @Test + public void shouldGet400WhenCreatingNodeUnsupportedNestedPropertyValues() throws Exception + { + JaxRsResponse response = sendCreateRequestToServer("{\"foo\" : {\"bar\" : \"baz\"}}"); + assertEquals( 400, response.getStatus() ); + } + + private JaxRsResponse sendCreateRequestToServer(final String json) + { + return RestRequest.req().post( functionalTestHelper.dataUri() + "node/" , json ); + } + + private JaxRsResponse sendCreateRequestToServer() + { + return RestRequest.req().post( functionalTestHelper.dataUri() + "node/" , null, MediaType.APPLICATION_JSON_TYPE ); + } + + @Test + public void shouldGetValidLocationHeaderWhenCreatingNode() throws Exception + { + JaxRsResponse response = sendCreateRequestToServer(); + assertNotNull( response.getLocation() ); + assertTrue( response.getLocation() + .toString() + .startsWith( functionalTestHelper.dataUri() + "node/" ) ); + } + + @Test + public void shouldGetASingleContentLengthHeaderWhenCreatingANode() + { + JaxRsResponse response = sendCreateRequestToServer(); + List contentLentgthHeaders = response.getHeaders() + .get( "Content-Length" ); + assertNotNull( contentLentgthHeaders ); + assertEquals( 1, contentLentgthHeaders.size() ); + } + + @Test + public void shouldBeJSONContentTypeOnResponse() + { + JaxRsResponse response = sendCreateRequestToServer(); + assertThat( response.getType().toString(), containsString( MediaType.APPLICATION_JSON ) ); + } + + @Test + public void shouldGetValidNodeRepresentationWhenCreatingNode() throws Exception + { + JaxRsResponse response = sendCreateRequestToServer(); + String entity = response.getEntity(); + + Map map = JsonHelper.jsonToMap( entity ); + + assertNotNull( map ); + assertTrue( map.containsKey( "self" ) ); + + } + + @Documented( "Delete node." ) + @Test + public void shouldRespondWith204WhenNodeDeleted() throws Exception + { + long node = helper.createNode(); + gen.get().description( startGraph( "delete node" ) ) + .expectedStatus( 204 ) + .delete( functionalTestHelper.dataUri() + "node/" + node ); + } + + @Test + public void shouldRespondWith404AndSensibleEntityBodyWhenNodeToBeDeletedCannotBeFound() throws Exception + { + JaxRsResponse response = sendDeleteRequestToServer(NON_EXISTENT_NODE_ID); + assertEquals( 404, response.getStatus() ); + + Map jsonMap = JsonHelper.jsonToMap( response.getEntity() ); + assertThat( jsonMap, hasKey( "message" ) ); + assertNotNull( jsonMap.get( "message" ) ); + } + + @Documented( "Nodes with relationships cannot be deleted.\n" + + "\n" + + "The relationships on a node has to be deleted before the node can be\n" + + "deleted.\n" + + " \n" + + "TIP: You can use `DETACH DELETE` in Cypher to delete nodes and their relationships in one go." ) + @Test + public void shouldRespondWith409AndSensibleEntityBodyWhenNodeCannotBeDeleted() throws Exception + { + long id = helper.createNode(); + helper.createRelationship( "LOVES", id, helper.createNode() ); + JaxRsResponse response = sendDeleteRequestToServer(id); + assertEquals( 409, response.getStatus() ); + Map jsonMap = JsonHelper.jsonToMap( response.getEntity() ); + assertThat( jsonMap, hasKey( "message" ) ); + assertNotNull( jsonMap.get( "message" ) ); + + gen.get().description( startGraph( "nodes with rels can not be deleted" ) ) + .expectedStatus( 409 ) + .delete( functionalTestHelper.dataUri() + "node/" + id ); + } + + @Test + public void shouldRespondWith400IfInvalidJsonSentAsNodePropertiesDuringNodeCreation() throws URISyntaxException + { + String mangledJsonArray = "{\"myprop\":[1,2,\"three\"]}"; + JaxRsResponse response = sendCreateRequestToServer(mangledJsonArray); + assertEquals( 400, response.getStatus() ); + assertEquals( "text/plain", response.getType() + .toString() ); + assertThat( response.getEntity(), containsString( mangledJsonArray ) ); + } + + @Test + public void shouldRespondWith400IfInvalidJsonSentAsNodeProperty() throws URISyntaxException { + URI nodeLocation = sendCreateRequestToServer().getLocation(); + + String mangledJsonArray = "[1,2,\"three\"]"; + JaxRsResponse response = RestRequest.req().put(nodeLocation.toString() + "/properties/myprop", mangledJsonArray); + assertEquals(400, response.getStatus()); + assertEquals("text/plain", response.getType() + .toString()); + assertThat( response.getEntity(), containsString(mangledJsonArray)); + response.close(); + } + + @Test + public void shouldRespondWith400IfInvalidJsonSentAsNodeProperties() throws URISyntaxException { + URI nodeLocation = sendCreateRequestToServer().getLocation(); + + String mangledJsonProperties = "{\"a\":\"b\", \"c\":[1,2,\"three\"]}"; + JaxRsResponse response = RestRequest.req().put(nodeLocation.toString() + "/properties", mangledJsonProperties); + assertEquals(400, response.getStatus()); + assertEquals("text/plain", response.getType() + .toString()); + assertThat( response.getEntity(), containsString(mangledJsonProperties)); + response.close(); + } + + private JaxRsResponse sendDeleteRequestToServer(final long id) throws Exception + { + return RestRequest.req().delete(functionalTestHelper.dataUri() + "node/" + id); + } + + /* + Note that when running this test from within an IDE, the version field will be an empty string. This is because the + code that generates the version identifier is written by Maven as part of the build process(!). The tests will pass + both in the IDE (where the empty string will be correctly compared). + */ + public static class CommunityVersionAndEditionServiceDocIT extends ExclusiveServerTestBase + { + private static NeoServer server; + private static FunctionalTestHelper functionalTestHelper; + + @ClassRule + public static TemporaryFolder staticFolder = new TemporaryFolder(); + + public + @Rule + TestData gen = TestData.producedThrough( RESTDocsGenerator.PRODUCER ); + private static FakeClock clock; + + @Before + public void setUp() + { + gen.get().setSection( "dev/rest-api/database-version" ); + } + + @BeforeClass + public static void setupServer() throws Exception + { + clock = new FakeClock(); + server = CommunityServerBuilder.server() + .usingDataDir( staticFolder.getRoot().getAbsolutePath() ) + .withClock( clock ) + .build(); + + suppressAll().call( new Callable() + { + @Override + public Void call() throws Exception + { + server.start(); + return null; + } + } ); + functionalTestHelper = new FunctionalTestHelper( server ); + } + + @Before + public void setupTheDatabase() throws Exception + { + // do nothing, we don't care about the database contents here + } + + @AfterClass + public static void stopServer() throws Exception + { + suppressAll().call( new Callable() + { + @Override + public Void call() throws Exception + { + server.stop(); + return null; + } + } ); + } + + @Test + public void shouldReportCommunityEdition() throws Exception + { + // Given + String releaseVersion = server.getDatabase().getGraph().getDependencyResolver().resolveDependency( KernelData + .class ).version().getReleaseVersion(); + + // When + HTTP.Response res = + HTTP.GET( functionalTestHelper.managementUri() + "/" + VersionAndEditionService.SERVER_PATH ); + + // Then + assertEquals( 200, res.status() ); + assertThat( res.get( "edition" ).asText(), equalTo( "community" ) ); + assertThat( res.get( "version" ).asText(), equalTo( releaseVersion ) ); + } + } + + public static class ConfigureEnabledManagementConsolesDocIT extends ExclusiveServerTestBase + { + private NeoServer server; + + @After + public void stopTheServer() + { + server.stop(); + } + + @Test + public void shouldBeAbleToExplicitlySetConsolesToEnabled() throws Exception + { + server = CommunityServerBuilder.server().withProperty( ServerSettings.console_module_engines.name(), "" ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + + assertThat( exec( "ls", "shell" ).getStatus(), is( 400 ) ); + } + + @Test + public void shellConsoleShouldBeEnabledByDefault() throws Exception + { + server = CommunityServerBuilder.server().usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ).build(); + server.start(); + + assertThat( exec( "ls", "shell" ).getStatus(), is( 200 ) ); + } + + private JaxRsResponse exec( String command, String engine ) + { + return RestRequest.req().post( server.baseUri() + "db/manage/server/console", "{" + + "\"engine\":\"" + engine + "\"," + + "\"command\":\"" + command + "\\n\"}" ); + } + } + + public static class ConsoleServiceDocTest + { + private final URI uri = URI.create( "http://peteriscool.com:6666/" ); + + @Test + public void correctRepresentation() throws URISyntaxException + { + ConsoleService consoleService = new ConsoleService( new ShellOnlyConsoleSessionFactory(), mock( Database.class ), + NullLogProvider.getInstance(), new OutputFormat( new JsonFormat(), uri, null ) ); + + Response consoleResponse = consoleService.getServiceDefinition(); + + assertEquals( 200, consoleResponse.getStatus() ); + String response = decode( consoleResponse ); + MatcherAssert.assertThat( response, containsString( "resources" ) ); + MatcherAssert.assertThat( response, containsString( uri.toString() ) ); + } + + @Test + public void advertisesAvailableConsoleEngines() throws URISyntaxException + { + ConsoleService consoleServiceWithJustShellEngine = new ConsoleService( new ShellOnlyConsoleSessionFactory(), + mock( Database.class ), NullLogProvider.getInstance(), new OutputFormat( new JsonFormat(), uri, null ) ); + + String response = decode( consoleServiceWithJustShellEngine.getServiceDefinition()); + + MatcherAssert.assertThat( response, containsString( "\"engines\" : [ \"shell\" ]" ) ); + + } + + private String decode( final Response response ) + { + return UTF8.decode( (byte[]) response.getEntity() ); + } + + private static class ShellOnlyConsoleSessionFactory implements ConsoleSessionFactory + { + @Override + public ScriptSession createSession( String engineName, Database database, LogProvider logProvider ) + { + return null; + } + + @Override + public Iterable supportedEngines() + { + return Collections.singletonList( "shell" ); + } + } + } + + public static class JmxServiceDocTest + { + public JmxService jmxService; + private final URI uri = URI.create( "http://peteriscool.com:6666/" ); + + @Test + public void correctRepresentation() throws URISyntaxException + { + Response resp = jmxService.getServiceDefinition(); + + assertEquals( 200, resp.getStatus() ); + + String json = UTF8.decode( (byte[]) resp.getEntity() ); + MatcherAssert.assertThat( json, containsString( "resources" ) ); + MatcherAssert.assertThat( json, containsString( uri.toString() ) ); + MatcherAssert.assertThat( json, containsString( "jmx/domain/{domain}/{objectName}" ) ); + } + + @Test + public void shouldListDomainsCorrectly() throws Exception + { + Response resp = jmxService.listDomains(); + + assertEquals( 200, resp.getStatus() ); + } + + @Test + public void testwork() throws Exception + { + jmxService.queryBeans( "[\"*:*\"]" ); + } + + @Before + public void setUp() throws Exception + { + this.jmxService = new JmxService( new OutputFormat( new JsonFormat(), uri, null ), null ); + } + + } + + public static class Neo4jShellConsoleSessionDocTest implements ConsoleSessionFactory + { + private ConsoleService consoleService; + private Database database; + private final URI uri = URI.create( "http://peteriscool.com:6666/" ); + + @Before + public void setUp() throws Exception + { + this.database = new WrappedDatabase( (GraphDatabaseFacade) new TestGraphDatabaseFactory(). + newImpermanentDatabaseBuilder(). + setConfig( ShellSettings.remote_shell_enabled, Settings.TRUE ). + newGraphDatabase() ); + this.consoleService = new ConsoleService( + this, + database, + NullLogProvider.getInstance(), + new OutputFormat( new JsonFormat(), uri, null ) ); + } + + @After + public void shutdownDatabase() + { + this.database.getGraph().shutdown(); + } + + @Override + public ScriptSession createSession( String engineName, Database database, LogProvider logProvider ) + { + return new ShellSession( database.getGraph() ); + } + + @Test + public void doesntMangleNewlines() throws Exception + { + Response response = consoleService.exec( new JsonFormat(), + "{ \"command\" : \"create (n) return n;\", \"engine\":\"shell\" }" ); + + + assertEquals( 200, response.getStatus() ); + String result = decode( response ).get( 0 ); + + String expected = "+-----------+" + lineSeparator() + + "| n |" + lineSeparator() + + "+-----------+" + lineSeparator() + + "| Node[0]{} |" + lineSeparator() + + "+-----------+" + lineSeparator() + + "1 row"; + + MatcherAssert.assertThat( result, containsString( expected ) ); + } + + private List decode( final Response response ) throws JsonParseException + { + return (List) JsonHelper.readJson( UTF8.decode( (byte[]) response.getEntity() ) ); + } + + @Override + public Iterable supportedEngines() + { + return new ArrayList() + {{ + add( "shell" ); + }}; + } + } + + public static class RootServiceDocTest + { + @Test + public void shouldAdvertiseServicesWhenAsked() throws Exception + { + UriInfo uriInfo = mock( UriInfo.class ); + URI uri = new URI( "http://example.org:7474/" ); + when( uriInfo.getBaseUri() ).thenReturn( uri ); + + RootService svc = new RootService( new CommunityNeoServer( new Config( stringMap( + httpConnector( "1" ).type.name(), "HTTP", + httpConnector( "1" ).enabled.name(), "true" + ) ), + GraphDatabaseDependencies.newDependencies().userLogProvider( NullLogProvider.getInstance() ) + .monitors( new Monitors() ), + NullLogProvider.getInstance() ) ); + + EntityOutputFormat output = new EntityOutputFormat( new JsonFormat(), null, null ); + Response serviceDefinition = svc.getServiceDefinition( uriInfo, output ); + + assertEquals( 200, serviceDefinition.getStatus() ); + Map result = (Map) output.getResultAsMap().get( "services" ); + + assertThat( result.get( "console" ) + .toString(), containsString( String.format( "%sserver/console", uri.toString() ) ) ); + assertThat( result.get( "jmx" ) + .toString(), containsString( String.format( "%sserver/jmx", uri.toString() ) ) ); + } + } + + public static class VersionAndEditionServiceTest + { + @Test + public void shouldReturnReadableStringForServiceName() throws Exception + { + // given + VersionAndEditionService service = new VersionAndEditionService( mock( CommunityNeoServer.class ) ); + + // when + String serviceName = service.getName(); + // then + assertEquals( "version", serviceName ); + } + + @Test + public void shouldReturnSensiblePathWhereServiceIsHosted() throws Exception + { + // given + VersionAndEditionService service = new VersionAndEditionService( mock( CommunityNeoServer.class ) ); + + // when + String serverPath = service.getServerPath(); + + // then + assertEquals( "server/version", serverPath ); + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/PathsIT.java b/community/server/src/test/java/org/neo4j/server/rest/PathsIT.java new file mode 100644 index 0000000000000..6abe738831c24 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/PathsIT.java @@ -0,0 +1,330 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.Response.Status; + +import org.junit.Test; + +import org.neo4j.graphdb.Node; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription; +import org.neo4j.test.GraphDescription.Graph; +import org.neo4j.test.GraphDescription.NODE; +import org.neo4j.test.GraphDescription.PROP; +import org.neo4j.test.GraphDescription.REL; +import org.neo4j.test.TestData.Title; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class PathsIT extends AbstractRestFunctionalTestBase +{ +// Layout +// +// (e)---------------- +// | | +// (d)------------- | +// | \/ +// (a)-(c)-(b)-(f)-(g) +// |\ / / +// | ---- / +// -------- +// + @Test + @Graph( value = { "a to c", "a to d", "c to b", "d to e", "b to f", "c to f", "f to g", "d to g", "e to g", + "c to g" } ) + @Title( "Find all shortest paths" ) + @Documented( "The +shortestPath+ algorithm can find multiple paths between the same nodes, like in this example." ) + public void shouldBeAbleToFindAllShortestPaths() throws JsonParseException + { + + // Get all shortest paths + long a = nodeId( data.get(), "a" ); + long g = nodeId( data.get(), "g" ); + String response = gen() + .expectedStatus( Status.OK.getStatusCode() ) + .payload( getAllShortestPathPayLoad( g ) ) + .post( "http://localhost:7474/db/data/node/" + a + "/paths" ) + .entity(); + Collection result = (Collection) JsonHelper.readJson( response ); + assertEquals( 2, result.size() ); + for ( Object representation : result ) + { + Map path = (Map) representation; + + assertThatPathStartsWith( path, a ); + assertThatPathEndsWith( path, g ); + assertThatPathHasLength( path, 2 ); + } + } + +// Layout +// +// (e)---------------- +// | | +// (d)------------- | +// | \/ +// (a)-(c)-(b)-(f)-(g) +// |\ / / +// | ---- / +// -------- + @Title( "Find one of the shortest paths" ) + @Test + @Graph( value = { "a to c", "a to d", "c to b", "d to e", "b to f", "c to f", "f to g", "d to g", "e to g", + "c to g" } ) + @Documented( "If no path algorithm is specified, a +shortestPath+ algorithm with a max\n" + + "depth of 1 will be chosen. In this example, the +max_depth+ is set to +3+\n" + + "in order to find the shortest path between a maximum of 3 linked nodes." ) + public void shouldBeAbleToFetchSingleShortestPath() throws JsonParseException + { + long a = nodeId( data.get(), "a" ); + long g = nodeId( data.get(), "g" ); + String response = gen() + .expectedStatus( Status.OK.getStatusCode() ) + .payload( getAllShortestPathPayLoad( g ) ) + .post( "http://localhost:7474/db/data/node/" + a + "/path" ) + .entity(); + // Get single shortest path + + Map path = JsonHelper.jsonToMap( response ); + + assertThatPathStartsWith( path, a ); + assertThatPathEndsWith( path, g ); + assertThatPathHasLength( path, 2 ); + } + + private void assertThatPathStartsWith( final Map path, final long start ) + { + assertTrue( "Path should start with " + start + "\nBut it was " + path, path.get( "start" ) + .toString() + .endsWith( "/node/" + start ) ); + } + + private void assertThatPathEndsWith( final Map path, final long start ) + { + assertTrue( "Path should end with " + start + "\nBut it was " + path, path.get( "end" ) + .toString() + .endsWith( "/node/" + start ) ); + } + + private void assertThatPathHasLength( final Map path, final int length ) + { + Object actual = path.get( "length" ); + + assertEquals( "Expected path to have a length of " + length + "\nBut it was " + actual, length, actual ); + } + +// Layout +// +// 1.5------(b)--------0.5 +// / \ +// (a)-0.5-(c)-0.5-(d)-0.5-(e) +// \ / +// 0.5-------(f)------1.2 +// + @Test + @Graph( nodes = { @NODE( name = "a", setNameProperty = true ), @NODE( name = "b", setNameProperty = true ), + @NODE( name = "c", setNameProperty = true ), @NODE( name = "d", setNameProperty = true ), + @NODE( name = "e", setNameProperty = true ), @NODE( name = "f", setNameProperty = true ) }, relationships = { + @REL( start = "a", end = "b", type = "to", properties = { @PROP( key = "cost", value = "1.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "a", end = "c", type = "to", properties = { @PROP( key = "cost", value = "0.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "a", end = "f", type = "to", properties = { @PROP( key = "cost", value = "0.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "c", end = "d", type = "to", properties = { @PROP( key = "cost", value = "0.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "d", end = "e", type = "to", properties = { @PROP( key = "cost", value = "0.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "b", end = "e", type = "to", properties = { @PROP( key = "cost", value = "0.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "f", end = "e", type = "to", properties = { @PROP( key = "cost", value = "1.2", type = GraphDescription.PropType.DOUBLE ) } ) } ) + @Title( "Execute a Dijkstra algorithm and get a single path" ) + @Documented( "This example is running a Dijkstra algorithm over a graph with different\n" + + "cost properties on different relationships. Note that the request URI\n" + + "ends with +/path+ which means a single path is what we want here." ) + public void shouldGetCorrectDijkstraPathWithWeights() throws Exception + { + // Get cheapest paths using Dijkstra + long a = nodeId( data.get(), "a" ); + long e = nodeId( data.get(), "e" ); + String response = gen().expectedStatus( Status.OK.getStatusCode() ) + .payload( getAllPathsUsingDijkstraPayLoad( e, false ) ) + .post( "http://localhost:7474/db/data/node/" + a + "/path" ) + .entity(); + // + Map path = JsonHelper.jsonToMap( response ); + assertThatPathStartsWith( path, a ); + assertThatPathEndsWith( path, e ); + assertThatPathHasLength( path, 3 ); + assertEquals( 1.5, path.get( "weight" ) ); + } + +// Layout +// +// 1.5------(b)--------0.5 +// / \ +// (a)-0.5-(c)-0.5-(d)-0.5-(e) +// \ / +// 0.5-------(f)------1.0 +// + @Test + @Graph( nodes = { @NODE( name = "a", setNameProperty = true ), @NODE( name = "b", setNameProperty = true ), + @NODE( name = "c", setNameProperty = true ), @NODE( name = "d", setNameProperty = true ), + @NODE( name = "e", setNameProperty = true ), @NODE( name = "f", setNameProperty = true ) }, relationships = { + @REL( start = "a", end = "b", type = "to", properties = { @PROP( key = "cost", value = "1.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "a", end = "c", type = "to", properties = { @PROP( key = "cost", value = "0.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "a", end = "f", type = "to", properties = { @PROP( key = "cost", value = "0.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "c", end = "d", type = "to", properties = { @PROP( key = "cost", value = "0.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "d", end = "e", type = "to", properties = { @PROP( key = "cost", value = "0.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "b", end = "e", type = "to", properties = { @PROP( key = "cost", value = "0.5", type = GraphDescription.PropType.DOUBLE ) } ), + @REL( start = "f", end = "e", type = "to", properties = { @PROP( key = "cost", value = "1.0", type = GraphDescription.PropType.DOUBLE ) } ) } ) + @Title( "Execute a Dijkstra algorithm and get multiple paths" ) + @Documented( "This example is running a Dijkstra algorithm over a graph with different\n" + + "cost properties on different relationships. Note that the request URI\n" + + "ends with +/paths+ which means we want multiple paths returned, in case\n" + + "they exist." ) + public void shouldGetCorrectDijkstraPathsWithWeights() throws Exception + { + // Get cheapest paths using Dijkstra + long a = nodeId( data.get(), "a" ); + long e = nodeId( data.get(), "e" ); + String response = gen().expectedStatus( Status.OK.getStatusCode() ) + .payload( getAllPathsUsingDijkstraPayLoad( e, false ) ) + .post( "http://localhost:7474/db/data/node/" + a + "/paths" ) + .entity(); + // + List> list = JsonHelper.jsonToList( response ); + assertEquals( 2, list.size() ); + Map firstPath = list.get( 0 ); + Map secondPath = list.get( 1 ); + System.out.println( firstPath ); + System.out.println( secondPath ); + assertThatPathStartsWith( firstPath, a ); + assertThatPathStartsWith( secondPath, a ); + assertThatPathEndsWith( firstPath, e ); + assertThatPathEndsWith( secondPath, e ); + assertEquals( 1.5, firstPath.get( "weight" ) ); + assertEquals( 1.5, secondPath.get( "weight" ) ); + // 5 = 3 + 2 + assertEquals( 5, (Integer) firstPath.get( "length" ) + (Integer) secondPath.get( "length" ) ); + assertEquals( 1, Math.abs( (Integer) firstPath.get( "length" ) - (Integer) secondPath.get( "length" ) ) ); + } + +// Layout +// +// 1------(b)-----1 +// / \ +// (a)-1-(c)-1-(d)-1-(e) +// \ / +// 1------(f)-----1 +// + @Test + @Graph( nodes = { @NODE( name = "a", setNameProperty = true ), + @NODE( name = "b", setNameProperty = true ), @NODE( name = "c", setNameProperty = true ), + @NODE( name = "d", setNameProperty = true ), @NODE( name = "e", setNameProperty = true ), + @NODE( name = "f", setNameProperty = true ) }, relationships = { + @REL( start = "a", end = "b", type = "to", properties = { @PROP( key = "cost", value = "1", type = GraphDescription.PropType.INTEGER ) } ), + @REL( start = "a", end = "c", type = "to", properties = { @PROP( key = "cost", value = "1", type = GraphDescription.PropType.INTEGER ) } ), + @REL( start = "a", end = "f", type = "to", properties = { @PROP( key = "cost", value = "1", type = GraphDescription.PropType.INTEGER ) } ), + @REL( start = "c", end = "d", type = "to", properties = { @PROP( key = "cost", value = "1", type = GraphDescription.PropType.INTEGER ) } ), + @REL( start = "d", end = "e", type = "to", properties = { @PROP( key = "cost", value = "1", type = GraphDescription.PropType.INTEGER ) } ), + @REL( start = "b", end = "e", type = "to", properties = { @PROP( key = "cost", value = "1", type = GraphDescription.PropType.INTEGER ) } ), + @REL( start = "f", end = "e", type = "to", properties = { @PROP( key = "cost", value = "1", type = GraphDescription.PropType.INTEGER ) } ) } ) + @Title( "Execute a Dijkstra algorithm with equal weights on relationships" ) + @Documented( "The following is executing a Dijkstra search on a graph with equal\n" + + "weights on all relationships. This example is included to show the\n" + + "difference when the same graph structure is used, but the path weight is\n" + + "equal to the number of hops." ) + public void shouldGetCorrectDijkstraPathsWithEqualWeightsWithDefaultCost() throws Exception + { + // Get cheapest path using Dijkstra + long a = nodeId( data.get(), "a" ); + long e = nodeId( data.get(), "e" ); + String response = gen() + .expectedStatus( Status.OK.getStatusCode() ) + .payload( getAllPathsUsingDijkstraPayLoad( e, false ) ) + .post( "http://localhost:7474/db/data/node/" + a + "/path" ) + .entity(); + + Map path = JsonHelper.jsonToMap( response ); + assertThatPathStartsWith( path, a ); + assertThatPathEndsWith( path, e ); + assertThatPathHasLength( path, 2 ); + assertEquals( 2.0, path.get( "weight" ) ); + } + +// Layout +// +// (e)---------------- +// | | +// (d)------------- | +// | \/ +// (a)-(c)-(b)-(f)-(g) +// |\ / / +// | ---- / +// -------- + @Test + @Graph( value = { "a to c", "a to d", "c to b", "d to e", "b to f", "c to f", "f to g", "d to g", "e to g", + "c to g" } ) + public void shouldReturn404WhenFailingToFindASinglePath() throws JsonParseException + { + long a = nodeId( data.get(), "a" ); + long g = nodeId( data.get(), "g" ); + String noHitsJson = "{\"to\":\"" + + nodeUri( g ) + + "\", \"max_depth\":1, \"relationships\":{\"type\":\"dummy\", \"direction\":\"in\"}, \"algorithm\":\"shortestPath\"}"; + String entity = gen() + .expectedStatus( Status.NOT_FOUND.getStatusCode() ) + .payload( noHitsJson ) + .post( "http://localhost:7474/db/data/node/" + a + "/path" ) + .entity(); + System.out.println( entity ); + } + + private long nodeId( final Map map, final String string ) + { + return map.get( string ) + .getId(); + } + + private String nodeUri( final long l ) + { + return NODES + l; + } + + private String getAllShortestPathPayLoad( final long to ) + { + String json = "{\"to\":\"" + + nodeUri( to ) + + "\", \"max_depth\":3, \"relationships\":{\"type\":\"to\", \"direction\":\"out\"}, \"algorithm\":\"shortestPath\"}"; + return json; + } + + // + private String getAllPathsUsingDijkstraPayLoad( final long to, final boolean includeDefaultCost ) + { + String json = "{\"to\":\"" + nodeUri( to ) + "\"" + ", \"cost_property\":\"cost\"" + + ( includeDefaultCost ? ", \"default_cost\":1" : "" ) + + ", \"relationships\":{\"type\":\"to\", \"direction\":\"out\"}, \"algorithm\":\"dijkstra\"}"; + return json; + } + +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/RESTDocsGenerator.java b/community/server/src/test/java/org/neo4j/server/rest/RESTDocsGenerator.java index dd2886abfafa3..399e05148a3c5 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/RESTDocsGenerator.java +++ b/community/server/src/test/java/org/neo4j/server/rest/RESTDocsGenerator.java @@ -19,14 +19,6 @@ */ package org.neo4j.server.rest; -import com.sun.jersey.api.client.Client; -import com.sun.jersey.api.client.ClientRequest; -import com.sun.jersey.api.client.ClientRequest.Builder; -import com.sun.jersey.api.client.ClientResponse; - -import java.io.File; -import java.io.IOException; -import java.io.Writer; import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; @@ -39,19 +31,20 @@ import java.util.function.Predicate; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientRequest; +import com.sun.jersey.api.client.ClientRequest.Builder; +import com.sun.jersey.api.client.ClientResponse; import org.neo4j.doc.tools.AsciiDocGenerator; import org.neo4j.function.Predicates; -import org.neo4j.graphdb.Transaction; import org.neo4j.helpers.collection.Pair; import org.neo4j.test.GraphDefinition; import org.neo4j.test.TestData.Producer; -import org.neo4j.visualization.asciidoc.AsciidocHelper; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; /** * Generate asciidoc-formatted documentation from HTTP requests and responses. @@ -65,8 +58,6 @@ */ public class RESTDocsGenerator extends AsciiDocGenerator { - private static final String EQUAL_SIGNS = "======"; - private static final Builder REQUEST_BUILDER = ClientRequest.create(); private static final List RESPONSE_HEADERS = Arrays.asList( "Content-Type", "Location" ); @@ -96,9 +87,6 @@ public void destroy( RESTDocsGenerator product, boolean successful ) private final List>> expectedHeaderFields = new ArrayList<>(); private String payload; private final Map addedRequestHeaders = new TreeMap<>( ); - private boolean noDoc = false; - private boolean noGraph = false; - private int headingLevel = 3; /** * Creates a documented test case. Finish building it by using one of these: @@ -196,33 +184,6 @@ public RESTDocsGenerator payload( final String payload ) return this; } - public RESTDocsGenerator noDoc() { - this.noDoc = true; - return this; - } - - public RESTDocsGenerator noGraph() - { - this.noGraph = true; - return this; - } - - /** - * Set a custom heading level. Defaults to 3. - * - * @param headingLevel a value between 1 and 6 (inclusive) - */ - public RESTDocsGenerator docHeadingLevel( final int headingLevel ) - { - if ( headingLevel < 1 || headingLevel > EQUAL_SIGNS.length() ) - { - throw new IllegalArgumentException( "Heading level out of bounds: " - + headingLevel ); - } - this.headingLevel = headingLevel; - return this; - } - /** * Add an expected response header. If the heading is missing in the * response the test will fail. The header and its value are also included @@ -232,7 +193,7 @@ public RESTDocsGenerator docHeadingLevel( final int headingLevel ) */ public RESTDocsGenerator expectedHeader( final String expectedHeaderField ) { - this.expectedHeaderFields.add( Pair.of(expectedHeaderField, Predicates.notNull()) ); + this.expectedHeaderFields.add( Pair.of(expectedHeaderField, Predicates.notNull()) ); return this; } @@ -413,10 +374,6 @@ private ResponseEntity retrieveResponse( final String title, final String descri assertTrue( "wrong headers: " + response.getHeaders(), headerField.other().test( response.getHeaders() .getFirst( headerField.first() ) ) ); } - if ( noDoc ) - { - data.setIgnore(); - } data.setTitle( title ); data.setDescription( description ); data.setMethod( request.getMethod() ); @@ -424,17 +381,6 @@ private ResponseEntity retrieveResponse( final String title, final String descri data.setStatus( responseCode ); assertEquals( "Wrong response status. response: " + data.entity, responseCode, response.getStatus() ); getResponseHeaders( data, response.getHeaders(), headerNames(headerFields) ); - if ( graph == null ) - { - document( data ); - } - else - { - try ( Transaction transaction = graph.beginTx() ) - { - document( data ); - } - } return new ResponseEntity( response, data.entity ); } @@ -515,137 +461,4 @@ public JaxRsResponse response() return response; } } - - protected void document( final DocumentationData data ) - { - if (data.ignore) - { - return; - } - String name = data.title.replace( " ", "-" ).toLowerCase(); - String filename = name + ".asciidoc"; - File dir = new File( new File( new File( "target" ), "docs" ), section ); - data.description = replaceSnippets( data.description, dir, name ); - try ( Writer fw = AsciiDocGenerator.getFW( dir, filename ) ) - { - String longSection = section.replaceAll( "\\(|\\)", "" )+"-" + name.replaceAll( "\\(|\\)", "" ); - if(longSection.indexOf( "/" )>0) - { - longSection = longSection.substring( longSection.indexOf( "/" )+1 ); - } - line( fw, "[[" + longSection + "]]" ); - //make first Character uppercase - String firstChar = data.title.substring( 0, 1 ).toUpperCase(); - String heading = firstChar + data.title.substring( 1 ); - line( fw, getAsciidocHeading( heading ) ); - line( fw, "" ); - if ( data.description != null && !data.description.isEmpty() ) - { - line( fw, data.description ); - line( fw, "" ); - } - if ( !noGraph && graph != null ) - { - fw.append( AsciiDocGenerator.dumpToSeparateFile( dir, - name + ".graph", - AsciidocHelper.createGraphVizWithNodeId( "Final Graph", - graph, title ) ) ); - line(fw, "" ); - } - line( fw, "_Example request_" ); - line( fw, "" ); - StringBuilder sb = new StringBuilder( 512 ); - sb.append( "* *+" ) - .append( data.method ) - .append( "+* +" ) - .append( data.uri ) - .append( "+\n" ); - if ( data.requestHeaders != null ) - { - for ( Entry header : data.requestHeaders.entrySet() ) - { - sb.append( "* *+" ) - .append( header.getKey() ) - .append( ":+* +" ) - .append( header.getValue() ) - .append( "+\n" ); - } - } - String prettifiedPayload = data.getPayload(); - if ( prettifiedPayload != null ) - { - writeEntity( sb, prettifiedPayload ); - } - fw.append( AsciiDocGenerator.dumpToSeparateFile( dir, name - + ".request", - sb.toString() ) ); - sb = new StringBuilder( 2048 ); - line( fw, "" ); - line( fw, "_Example response_" ); - line( fw, "" ); - int statusCode = data.status; - sb.append( "* *+" ) - .append( statusCode ) - .append( ":+* +" ) - .append( statusNameFromStatusCode( statusCode ) ) - .append( "+\n" ); - if ( data.responseHeaders != null ) - { - for ( Entry header : data.responseHeaders.entrySet() ) - { - sb.append( "* *+" ) - .append( header.getKey() ) - .append( ":+* +" ) - .append( header.getValue() ) - .append( "+\n" ); - } - } - writeEntity( sb, data.getPrettifiedEntity() ); - fw.append( AsciiDocGenerator.dumpToSeparateFile( dir, - name + ".response", sb.toString() ) ); - line( fw, "" ); - } - catch ( IOException e ) - { - e.printStackTrace(); - fail(); - } - } - - private String statusNameFromStatusCode( int statusCode ) - { - Object name = Response.Status.fromStatusCode( statusCode ); - if ( name == null ) - { - switch ( statusCode ) - { - case 405: - name = "Method Not Allowed"; - break; - case 422: - name = "Unprocessable Entity"; - break; - default: - throw new RuntimeException( "Missing name for status code: [" + statusCode + "]." ); - } - } - return String.valueOf( name ); - } - - private String getAsciidocHeading( final String heading ) - { - String equalSigns = EQUAL_SIGNS.substring( 0, headingLevel ); - return equalSigns + ' ' + heading + ' ' + equalSigns; - } - - public void writeEntity( final StringBuilder sb, final String entity ) - { - if ( entity != null ) - { - sb.append( "\n[source,javascript]\n" ) - .append( "----\n" ) - .append( entity ) - .append( "\n----\n\n" ); - } - } } diff --git a/community/server/src/test/java/org/neo4j/server/rest/RedirectorIT.java b/community/server/src/test/java/org/neo4j/server/rest/RedirectorIT.java new file mode 100644 index 0000000000000..6100d60df59cf --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/RedirectorIT.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import org.junit.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +public class RedirectorIT extends AbstractRestFunctionalTestBase +{ + @Test + public void shouldRedirectRootToBrowser() throws Exception { + JaxRsResponse response = new RestRequest(server().baseUri()).get(); + + assertThat(response.getStatus(), is(not(404))); + } + + @Test + public void shouldNotRedirectTheRestOfTheWorld() throws Exception { + JaxRsResponse response = new RestRequest(server().baseUri()).get("a/different/relative/data/uri/"); + + assertThat(response.getStatus(), is(404)); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/RelationshipIT.java b/community/server/src/test/java/org/neo4j/server/rest/RelationshipIT.java new file mode 100644 index 0000000000000..62dbd225f70f0 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/RelationshipIT.java @@ -0,0 +1,262 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import javax.ws.rs.core.Response.Status; + +import com.sun.jersey.api.client.ClientResponse; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.graphdb.Direction; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.graphdb.Transaction; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.repr.StreamingFormat; +import org.neo4j.test.GraphDescription; +import org.neo4j.test.GraphDescription.Graph; +import org.neo4j.test.GraphDescription.NODE; +import org.neo4j.test.GraphDescription.PROP; +import org.neo4j.test.GraphDescription.REL; +import org.neo4j.test.TestData.Title; + +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import static org.neo4j.graphdb.Neo4jMatchers.hasProperty; +import static org.neo4j.graphdb.Neo4jMatchers.inTx; + +public class RelationshipIT extends AbstractRestFunctionalDocTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + } + + @Test + @Title("Remove properties from a relationship") + @Graph(nodes = {@NODE(name = "Romeo", setNameProperty = true), + @NODE(name = "Juliet", setNameProperty = true)}, relationships = {@REL(start = "Romeo", end = "Juliet", + type = "LOVES", properties = {@PROP(key = "cost", value = "high", type = GraphDescription.PropType + .STRING)})}) + public void shouldReturn204WhenPropertiesAreRemovedFromRelationship() + { + Relationship loves = getFirstRelationshipFromRomeoNode(); + gen().description( startGraph( "remove properties from a relationship" ) ) + .expectedStatus( Status.NO_CONTENT.getStatusCode() ) + .delete( functionalTestHelper.relationshipPropertiesUri( loves.getId() ) ).entity(); + } + + @Test + @Graph("I know you") + public void get_Relationship_by_ID() throws JsonParseException + { + Node node = data.get().get( "I" ); + Relationship relationship; + try ( Transaction transaction = node.getGraphDatabase().beginTx() ) + { + relationship = node.getSingleRelationship( + RelationshipType.withName( "know" ), + Direction.OUTGOING ); + } + String response = gen().expectedStatus( + com.sun.jersey.api.client.ClientResponse.Status.OK.getStatusCode() ).get( + getRelationshipUri( relationship ) ).entity(); + assertTrue( JsonHelper.jsonToMap( response ).containsKey( "start" ) ); + } + + @Test + @Title("Remove property from a relationship") + @Documented( "See the example request below." ) + @Graph(nodes = {@NODE(name = "Romeo", setNameProperty = true), + @NODE(name = "Juliet", setNameProperty = true)}, relationships = {@REL(start = "Romeo", end = "Juliet", + type = "LOVES", properties = {@PROP(key = "cost", value = "high", type = GraphDescription.PropType + .STRING)})}) + public void shouldReturn204WhenPropertyIsRemovedFromRelationship() + { + data.get(); + Relationship loves = getFirstRelationshipFromRomeoNode(); + gen().description( + startGraph( "Remove property from a relationship1" ) ); + gen().expectedStatus( Status.NO_CONTENT.getStatusCode() ).delete( + getPropertiesUri( loves ) + "/cost" ).entity(); + + } + + @Test + @Title("Remove non-existent property from a relationship") + @Documented( "Attempting to remove a property that doesn't exist results in an error." ) + @Graph(nodes = {@NODE(name = "Romeo", setNameProperty = true), + @NODE(name = "Juliet", setNameProperty = true)}, relationships = {@REL(start = "Romeo", end = "Juliet", + type = "LOVES", properties = {@PROP(key = "cost", value = "high", type = GraphDescription.PropType + .STRING)})}) + public void shouldReturn404WhenPropertyWhichDoesNotExistRemovedFromRelationship() + { + data.get(); + Relationship loves = getFirstRelationshipFromRomeoNode(); + gen().description( startGraph( "remove non-existent property from relationship" ) ) + .expectedStatus( Status.NOT_FOUND.getStatusCode() ) + .delete( getPropertiesUri( loves ) + "/non-existent" ).entity(); + } + + @Test + @Graph(nodes = {@NODE(name = "Romeo", setNameProperty = true), + @NODE(name = "Juliet", setNameProperty = true)}, relationships = {@REL(start = "Romeo", end = "Juliet", + type = "LOVES", properties = {@PROP(key = "cost", value = "high", type = GraphDescription.PropType + .STRING)})}) + public void shouldReturn404WhenPropertyWhichDoesNotExistRemovedFromRelationshipStreaming() + { + data.get(); + Relationship loves = getFirstRelationshipFromRomeoNode(); + gen().withHeader( StreamingFormat.STREAM_HEADER, "true" ).expectedStatus( Status.NOT_FOUND.getStatusCode + () ).delete( + getPropertiesUri( loves ) + "/non-existent" ).entity(); + } + + @Test + @Graph( "I know you" ) + @Title( "Remove properties from a non-existing relationship" ) + @Documented( "Attempting to remove all properties from a relationship which doesn't exist results in an error." ) + public void shouldReturn404WhenPropertiesRemovedFromARelationshipWhichDoesNotExist() + { + data.get(); + gen().expectedStatus( Status.NOT_FOUND.getStatusCode() ) + .delete( functionalTestHelper.relationshipPropertiesUri( 1234L ) ) + .entity(); + } + + @Test + @Graph( "I know you" ) + @Title( "Remove property from a non-existing relationship" ) + @Documented( "Attempting to remove a property from a relationship which doesn't exist results in an error." ) + public void shouldReturn404WhenPropertyRemovedFromARelationshipWhichDoesNotExist() + { + data.get(); + gen().expectedStatus( Status.NOT_FOUND.getStatusCode() ) + .delete( + functionalTestHelper.relationshipPropertiesUri( 1234L ) + + "/cost" ) + .entity(); + + } + + @Test + @Graph(nodes = {@NODE(name = "Romeo", setNameProperty = true), + @NODE(name = "Juliet", setNameProperty = true)}, relationships = {@REL(start = "Romeo", end = "Juliet", + type = "LOVES", properties = {@PROP(key = "cost", value = "high", type = GraphDescription.PropType + .STRING)})}) + @Title("Delete relationship") + public void removeRelationship() + { + data.get(); + Relationship loves = getFirstRelationshipFromRomeoNode(); + gen().description( startGraph( "Delete relationship1" ) ); + gen().expectedStatus( Status.NO_CONTENT.getStatusCode() ).delete( + getRelationshipUri( loves ) ).entity(); + + } + + @Test + @Graph(nodes = {@NODE(name = "Romeo", setNameProperty = true), + @NODE(name = "Juliet", setNameProperty = true)}, relationships = {@REL(start = "Romeo", end = "Juliet", + type = "LOVES", properties = {@PROP(key = "cost", value = "high", type = GraphDescription.PropType + .STRING)})}) + public void get_single_property_on_a_relationship() throws Exception + { + Relationship loves = getFirstRelationshipFromRomeoNode(); + String response = gen().expectedStatus( ClientResponse.Status.OK ).get( getRelPropURI( loves, + "cost" ) ).entity(); + assertTrue( response.contains( "high" ) ); + } + + private String getRelPropURI( Relationship loves, String propertyKey ) + { + return getRelationshipUri( loves ) + "/properties/" + propertyKey; + } + + @Test + @Graph(nodes = {@NODE(name = "Romeo", setNameProperty = true), + @NODE(name = "Juliet", setNameProperty = true)}, relationships = {@REL(start = "Romeo", end = "Juliet", + type = "LOVES", properties = {@PROP(key = "cost", value = "high", type = GraphDescription.PropType + .STRING)})}) + public void set_single_property_on_a_relationship() throws Exception + { + Relationship loves = getFirstRelationshipFromRomeoNode(); + assertThat( loves, inTx( graphdb(), hasProperty( "cost" ).withValue( "high" ) ) ); + gen().description( startGraph( "Set relationship property1" ) ); + gen().expectedStatus( ClientResponse.Status.NO_CONTENT ).payload( "\"deadly\"" ).put( getRelPropURI( loves, + "cost" ) ).entity(); + assertThat( loves, inTx( graphdb(), hasProperty( "cost" ).withValue( "deadly" ) ) ); + } + + @Test + @Graph(nodes = {@NODE(name = "Romeo", setNameProperty = true), + @NODE(name = "Juliet", setNameProperty = true)}, relationships = {@REL(start = "Romeo", end = "Juliet", + type = "LOVES", properties = {@PROP(key = "cost", value = "high", type = GraphDescription.PropType + .STRING), @PROP(key = "since", value = "1day", type = GraphDescription.PropType.STRING)})}) + public void set_all_properties_on_a_relationship() throws Exception + { + Relationship loves = getFirstRelationshipFromRomeoNode(); + assertThat( loves, inTx( graphdb(), hasProperty( "cost" ).withValue( "high" ) ) ); + gen().description( startGraph( "Set relationship property1" ) ); + gen().expectedStatus( ClientResponse.Status.NO_CONTENT ).payload( JsonHelper.createJsonFrom( MapUtil.map( + "happy", false ) ) ).put( getRelPropsURI( loves ) ).entity(); + assertThat( loves, inTx( graphdb(), hasProperty( "happy" ).withValue( false ) ) ); + assertThat( loves, inTx( graphdb(), not( hasProperty( "cost" ) ) ) ); + } + + @Test + @Graph(nodes = {@NODE(name = "Romeo", setNameProperty = true), + @NODE(name = "Juliet", setNameProperty = true)}, relationships = {@REL(start = "Romeo", end = "Juliet", + type = "LOVES", properties = {@PROP(key = "cost", value = "high", type = GraphDescription.PropType + .STRING), @PROP(key = "since", value = "1day", type = GraphDescription.PropType.STRING)})}) + public void get_all_properties_on_a_relationship() throws Exception + { + Relationship loves = getFirstRelationshipFromRomeoNode(); + String response = gen().expectedStatus( ClientResponse.Status.OK ).get( getRelPropsURI( loves ) ).entity(); + assertTrue( response.contains( "high" ) ); + } + + private Relationship getFirstRelationshipFromRomeoNode() + { + Node romeo = getNode( "Romeo" ); + + try ( Transaction transaction = romeo.getGraphDatabase().beginTx() ) + { + return romeo.getRelationships().iterator().next(); + } + } + + private String getRelPropsURI( Relationship rel ) + { + return getRelationshipUri( rel ) + "/properties"; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/RemoveNodePropertiesIT.java b/community/server/src/test/java/org/neo4j/server/rest/RemoveNodePropertiesIT.java new file mode 100644 index 0000000000000..d52e9d335851b --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/RemoveNodePropertiesIT.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.GraphDbHelper; + +import static org.junit.Assert.assertEquals; + +public class RemoveNodePropertiesIT extends AbstractRestFunctionalDocTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + } + + private String getPropertiesUri( final long nodeId ) + { + return functionalTestHelper.nodePropertiesUri( nodeId ); + } + + @Test + public void shouldReturn204WhenPropertiesAreRemoved() + { + long nodeId = helper.createNode(); + Map map = new HashMap(); + map.put( "jim", "tobias" ); + helper.setNodeProperties( nodeId, map ); + JaxRsResponse response = removeNodePropertiesOnServer(nodeId); + assertEquals( 204, response.getStatus() ); + response.close(); + } + + @Documented( "Delete all properties from node." ) + @Test + public void shouldReturn204WhenAllPropertiesAreRemoved() + { + long nodeId = helper.createNode(); + Map map = new HashMap(); + map.put( "jim", "tobias" ); + helper.setNodeProperties( nodeId, map ); + gen.get().description( startGraph( "delete all prps from node" ) ) + .expectedStatus( 204 ) + .delete( functionalTestHelper.nodePropertiesUri( nodeId ) ); + } + + @Test + public void shouldReturn404WhenPropertiesSentToANodeWhichDoesNotExist() { + JaxRsResponse response = RestRequest.req().delete(getPropertiesUri(999999)); + assertEquals(404, response.getStatus()); + response.close(); + } + + private JaxRsResponse removeNodePropertiesOnServer(final long nodeId) + { + return RestRequest.req().delete(getPropertiesUri(nodeId)); + } + + @Documented( "To delete a single property\n" + + "from a node, see the example below" ) + @Test + public void delete_a_named_property_from_a_node() + { + long nodeId = helper.createNode(); + Map map = new HashMap(); + map.put( "name", "tobias" ); + helper.setNodeProperties( nodeId, map ); + gen.get() + .expectedStatus( 204 ) + .description( startGraph( "delete named property start" )) + .delete( functionalTestHelper.nodePropertyUri( nodeId, "name") ); + } + + @Test + public void shouldReturn404WhenRemovingNonExistingNodeProperty() + { + long nodeId = helper.createNode(); + Map map = new HashMap(); + map.put( "jim", "tobias" ); + helper.setNodeProperties( nodeId, map ); + JaxRsResponse response = removeNodePropertyOnServer(nodeId, "foo"); + assertEquals(404, response.getStatus()); + } + + @Test + public void shouldReturn404WhenPropertySentToANodeWhichDoesNotExist() { + JaxRsResponse response = RestRequest.req().delete(getPropertyUri(999999, "foo")); + assertEquals(404, response.getStatus()); + } + + private String getPropertyUri( final long nodeId, final String key ) + { + return functionalTestHelper.nodePropertyUri( nodeId, key ); + } + + private JaxRsResponse removeNodePropertyOnServer(final long nodeId, final String key) + { + return RestRequest.req().delete(getPropertyUri(nodeId, key)); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/RemoveRelationshipIT.java b/community/server/src/test/java/org/neo4j/server/rest/RemoveRelationshipIT.java new file mode 100644 index 0000000000000..7a5832138b046 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/RemoveRelationshipIT.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.net.URI; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.GraphDbHelper; + +import static org.junit.Assert.assertEquals; + +public class RemoveRelationshipIT extends AbstractRestFunctionalTestBase +{ + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + } + + @Test + public void shouldGet204WhenRemovingAValidRelationship() throws Exception + { + long relationshipId = helper.createRelationship( "KNOWS" ); + + JaxRsResponse response = sendDeleteRequest(new URI(functionalTestHelper.relationshipUri(relationshipId))); + + assertEquals( 204, response.getStatus() ); + response.close(); + } + + @Test + public void shouldGet404WhenRemovingAnInvalidRelationship() throws Exception + { + long relationshipId = helper.createRelationship( "KNOWS" ); + + JaxRsResponse response = sendDeleteRequest(new URI( + functionalTestHelper.relationshipUri((relationshipId + 1) * 9999))); + + assertEquals( 404, response.getStatus() ); + response.close(); + } + + private JaxRsResponse sendDeleteRequest(URI requestUri) + { + return RestRequest.req().delete(requestUri); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/RetrieveNodeIT.java b/community/server/src/test/java/org/neo4j/server/rest/RetrieveNodeIT.java new file mode 100644 index 0000000000000..8cee5873897fd --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/RetrieveNodeIT.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import javax.ws.rs.core.MediaType; + +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; +import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.repr.formats.CompactJsonFormat; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class RetrieveNodeIT extends AbstractRestFunctionalDocTestBase +{ + private URI nodeUri; + private static FunctionalTestHelper functionalTestHelper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + } + + @Before + public void cleanTheDatabaseAndInitialiseTheNodeUri() throws Exception + { + nodeUri = new URI( functionalTestHelper.nodeUri() + "/" + + new GraphDbHelper( server().getDatabase() ).createNode() ); + } + + @Test + public void shouldParameteriseUrisInNodeRepresentationWithHostHeaderValue() throws Exception + { + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( nodeUri ); + + httpget.setHeader( "Accept", "application/json" ); + httpget.setHeader( "Host", "dummy.neo4j.org" ); + HttpResponse response = httpclient.execute( httpget ); + HttpEntity entity = response.getEntity(); + + String entityBody = IOUtils.toString( entity.getContent(), StandardCharsets.UTF_8 ); + + assertThat( entityBody, containsString( "http://dummy.neo4j.org/db/data/node/" ) ); + + } finally + { + httpclient.getConnectionManager().shutdown(); + } + } + + @Test + public void shouldParameteriseUrisInNodeRepresentationWithoutHostHeaderUsingRequestUri() throws Exception + { + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( nodeUri ); + + httpget.setHeader( "Accept", "application/json" ); + HttpResponse response = httpclient.execute( httpget ); + HttpEntity entity = response.getEntity(); + + String entityBody = IOUtils.toString( entity.getContent(), StandardCharsets.UTF_8 ); + + assertThat( entityBody, containsString( nodeUri.toString() ) ); + } finally + { + httpclient.getConnectionManager().shutdown(); + } + } + + @Documented( "Get node.\n" + + "\n" + + "Note that the response contains URI/templates for the available\n" + + "operations for getting properties and relationships." ) + @Test + public void shouldGet200WhenRetrievingNode() throws Exception + { + String uri = nodeUri.toString(); + gen.get() + .expectedStatus( 200 ) + .get( uri ); + } + + @Documented( "Get node -- compact.\n" + + "\n" + + "Specifying the subformat in the requests media type yields a more compact\n" + + "JSON response without metadata and templates." ) + @Test + public void shouldGet200WhenRetrievingNodeCompact() + { + String uri = nodeUri.toString(); + ResponseEntity entity = gen.get() + .expectedType( CompactJsonFormat.MEDIA_TYPE ) + .expectedStatus( 200 ) + .get( uri ); + assertTrue( entity.entity() + .contains( "self" ) ); + } + + @Test + public void shouldGetContentLengthHeaderWhenRetrievingNode() throws Exception + { + JaxRsResponse response = retrieveNodeFromService( nodeUri.toString() ); + assertNotNull( response.getHeaders() + .get( "Content-Length" ) ); + response.close(); + } + + @Test + public void shouldHaveJsonMediaTypeOnResponse() + { + JaxRsResponse response = retrieveNodeFromService( nodeUri.toString() ); + assertThat( response.getType().toString(), containsString( MediaType.APPLICATION_JSON ) ); + response.close(); + } + + @Test + public void shouldHaveJsonDataInResponse() throws Exception + { + JaxRsResponse response = retrieveNodeFromService( nodeUri.toString() ); + + Map map = JsonHelper.jsonToMap( response.getEntity() ); + assertTrue( map.containsKey( "self" ) ); + response.close(); + } + + @Documented( "Get non-existent node." ) + @Test + public void shouldGet404WhenRetrievingNonExistentNode() throws Exception + { + gen.get() + .expectedStatus( 404 ) + .get( nodeUri + "00000" ); + } + + private JaxRsResponse retrieveNodeFromService( final String uri ) + { + return RestRequest.req().get( uri ); + } + +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/RetrieveRelationshipsFromNodeIT.java b/community/server/src/test/java/org/neo4j/server/rest/RetrieveRelationshipsFromNodeIT.java new file mode 100644 index 0000000000000..7bffaeb148e94 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/RetrieveRelationshipsFromNodeIT.java @@ -0,0 +1,322 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.MediaType; + +import org.apache.commons.io.IOUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.DefaultHttpClient; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.repr.RelationshipRepresentationTest; +import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +public class RetrieveRelationshipsFromNodeIT extends AbstractRestFunctionalDocTestBase +{ + private long nodeWithRelationships; + private long nodeWithoutRelationships; + private long nonExistingNode; + + private static FunctionalTestHelper functionalTestHelper; + private static GraphDbHelper helper; + private long likes; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + helper = functionalTestHelper.getGraphDbHelper(); + } + + @Before + public void setupTheDatabase() + { + nodeWithRelationships = helper.createNode(); + likes = helper.createRelationship( "LIKES", nodeWithRelationships, helper.createNode() ); + helper.createRelationship( "LIKES", helper.createNode(), nodeWithRelationships ); + helper.createRelationship( "HATES", nodeWithRelationships, helper.createNode() ); + nodeWithoutRelationships = helper.createNode(); + nonExistingNode = nodeWithoutRelationships * 100; + } + + private JaxRsResponse sendRetrieveRequestToServer( long nodeId, String path ) + { + return RestRequest.req().get( functionalTestHelper.nodeUri() + "/" + nodeId + "/relationships" + path ); + } + + private void verifyRelReps( int expectedSize, String json ) throws JsonParseException + { + List> relreps = JsonHelper.jsonToList( json ); + assertEquals( expectedSize, relreps.size() ); + for ( Map relrep : relreps ) + { + RelationshipRepresentationTest.verifySerialisation( relrep ); + } + } + + @Test + public void shouldParameteriseUrisInRelationshipRepresentationWithHostHeaderValue() throws Exception + { + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( "http://localhost:7474/db/data/relationship/" + likes ); + httpget.setHeader( "Accept", "application/json" ); + httpget.setHeader( "Host", "dummy.neo4j.org" ); + HttpResponse response = httpclient.execute( httpget ); + HttpEntity entity = response.getEntity(); + + String entityBody = IOUtils.toString( entity.getContent(), StandardCharsets.UTF_8 ); + + System.out.println( entityBody ); + + assertThat( entityBody, containsString( "http://dummy.neo4j.org/db/data/relationship/" + likes ) ); + assertThat( entityBody, not( containsString( "localhost:7474" ) ) ); + } + finally + { + httpclient.getConnectionManager().shutdown(); + } + } + + @Test + public void shouldParameteriseUrisInRelationshipRepresentationWithoutHostHeaderUsingRequestUri() throws Exception + { + HttpClient httpclient = new DefaultHttpClient(); + try + { + HttpGet httpget = new HttpGet( "http://localhost:7474/db/data/relationship/" + likes ); + + httpget.setHeader( "Accept", "application/json" ); + HttpResponse response = httpclient.execute( httpget ); + HttpEntity entity = response.getEntity(); + + String entityBody = IOUtils.toString( entity.getContent(), StandardCharsets.UTF_8 ); + + assertThat( entityBody, containsString( "http://localhost:7474/db/data/relationship/" + likes ) ); + } + finally + { + httpclient.getConnectionManager().shutdown(); + } + } + + @Documented( "Get all relationships." ) + @Test + public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingAllRelationshipsForANode() + throws JsonParseException + { + String entity = gen.get() + .expectedStatus( 200 ) + .get( functionalTestHelper.nodeUri() + "/" + nodeWithRelationships + "/relationships" + "/all" ) + .entity(); + verifyRelReps( 3, entity ); + } + + @Test + public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingAllRelationshipsForANodeStreaming() + throws JsonParseException + { + String entity = gen.get() + .withHeader(StreamingJsonFormat.STREAM_HEADER,"true") + .expectedStatus(200) + .get( functionalTestHelper.nodeUri() + "/" + nodeWithRelationships + "/relationships" + "/all" ) + .entity(); + verifyRelReps( 3, entity ); + } + + @Documented( "Get incoming relationships." ) + @Test + public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingIncomingRelationshipsForANode() + throws JsonParseException + { + String entity = gen.get() + .expectedStatus( 200 ) + .get( functionalTestHelper.nodeUri() + "/" + nodeWithRelationships + "/relationships" + "/in" ) + .entity(); + verifyRelReps( 1, entity ); + } + + @Documented( "Get outgoing relationships." ) + @Test + public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingOutgoingRelationshipsForANode() + throws JsonParseException + { + String entity = gen.get() + .expectedStatus( 200 ) + .get( functionalTestHelper.nodeUri() + "/" + nodeWithRelationships + "/relationships" + "/out" ) + .entity(); + verifyRelReps( 2, entity ); + } + + @Documented( "Get typed relationships.\n" + + "\n" + + "Note that the \"+&+\" needs to be encoded like \"+%26+\" for example when\n" + + "using http://curl.haxx.se/[cURL] from the terminal." ) + @Test + public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingAllTypedRelationshipsForANode() + throws JsonParseException + { + String entity = gen.get() + .expectedStatus( 200 ) + .get( functionalTestHelper.nodeUri() + "/" + nodeWithRelationships + "/relationships" + + "/all/LIKES&HATES" ) + .entity(); + verifyRelReps( 3, entity ); + } + + @Test + public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingIncomingTypedRelationshipsForANode() + throws JsonParseException + { + JaxRsResponse response = sendRetrieveRequestToServer( nodeWithRelationships, "/in/LIKES" ); + assertEquals( 200, response.getStatus() ); + assertThat( response.getType().toString(), containsString( MediaType.APPLICATION_JSON ) ); + verifyRelReps( 1, response.getEntity() ); + response.close(); + } + + @Test + public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingOutgoingTypedRelationshipsForANode() + throws JsonParseException + { + JaxRsResponse response = sendRetrieveRequestToServer( nodeWithRelationships, "/out/HATES" ); + assertEquals( 200, response.getStatus() ); + assertThat( response.getType().toString(), containsString( MediaType.APPLICATION_JSON ) ); + verifyRelReps( 1, response.getEntity() ); + response.close(); + } + + @Documented( "Get relationships on a node without relationships." ) + @Test + public void shouldRespondWith200AndEmptyListOfRelationshipRepresentationsWhenGettingAllRelationshipsForANodeWithoutRelationships() + throws JsonParseException + { + String entity = gen.get() + .expectedStatus( 200 ) + .get( functionalTestHelper.nodeUri() + "/" + nodeWithoutRelationships + "/relationships" + "/all" ) + .entity(); + verifyRelReps( 0, entity ); + } + + @Test + public void shouldRespondWith200AndEmptyListOfRelationshipRepresentationsWhenGettingIncomingRelationshipsForANodeWithoutRelationships() + throws JsonParseException + { + JaxRsResponse response = sendRetrieveRequestToServer( nodeWithoutRelationships, "/in" ); + assertEquals( 200, response.getStatus() ); + assertThat( response.getType().toString(), containsString( MediaType.APPLICATION_JSON ) ); + verifyRelReps( 0, response.getEntity() ); + response.close(); + } + + @Test + public void shouldRespondWith200AndEmptyListOfRelationshipRepresentationsWhenGettingOutgoingRelationshipsForANodeWithoutRelationships() + throws JsonParseException + { + JaxRsResponse response = sendRetrieveRequestToServer( nodeWithoutRelationships, "/out" ); + assertEquals( 200, response.getStatus() ); + assertThat( response.getType().toString(), containsString( MediaType.APPLICATION_JSON ) ); + verifyRelReps( 0, response.getEntity() ); + response.close(); + } + + @Test + public void shouldRespondWith404WhenGettingAllRelationshipsForNonExistingNode() + { + JaxRsResponse response = sendRetrieveRequestToServer( nonExistingNode, "/all" ); + assertEquals( 404, response.getStatus() ); + response.close(); + } + + @Test + public void shouldRespondWith404WhenGettingIncomingRelationshipsForNonExistingNode() + { + JaxRsResponse response = sendRetrieveRequestToServer( nonExistingNode, "/in" ); + assertEquals( 404, response.getStatus() ); + response.close(); + } + + @Test + public void shouldRespondWith404WhenGettingIncomingRelationshipsForNonExistingNodeStreaming() + { + JaxRsResponse response = RestRequest.req().header(StreamingJsonFormat.STREAM_HEADER,"true").get(functionalTestHelper.nodeUri() + "/" + nonExistingNode + "/relationships" + "/in"); + assertEquals( 404, response.getStatus() ); + response.close(); + } + + @Test + public void shouldRespondWith404WhenGettingOutgoingRelationshipsForNonExistingNode() + { + JaxRsResponse response = sendRetrieveRequestToServer( nonExistingNode, "/out" ); + assertEquals( 404, response.getStatus() ); + response.close(); + } + + @Test + public void shouldGet200WhenRetrievingValidRelationship() + { + long relationshipId = helper.createRelationship( "LIKES" ); + + JaxRsResponse response = RestRequest.req().get( functionalTestHelper.relationshipUri( relationshipId ) ); + + assertEquals( 200, response.getStatus() ); + response.close(); + } + + @Test + public void shouldGetARelationshipRepresentationInJsonWhenRetrievingValidRelationship() throws Exception + { + long relationshipId = helper.createRelationship( "LIKES" ); + + JaxRsResponse response = RestRequest.req().get( functionalTestHelper.relationshipUri( relationshipId ) ); + + String entity = response.getEntity(); + assertNotNull( entity ); + isLegalJson( entity ); + response.close(); + } + + private void isLegalJson( String entity ) throws IOException, JsonParseException + { + JsonHelper.jsonToMap( entity ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/SchemaConstraintsIT.java b/community/server/src/test/java/org/neo4j/server/rest/SchemaConstraintsIT.java new file mode 100644 index 0000000000000..d202472b8607f --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/SchemaConstraintsIT.java @@ -0,0 +1,245 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import org.neo4j.function.Factory; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.schema.ConstraintDefinition; +import org.neo4j.graphdb.schema.ConstraintType; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.junit.Assert.assertThat; + +import static org.neo4j.graphdb.Label.label; +import static org.neo4j.graphdb.Neo4jMatchers.containsOnly; +import static org.neo4j.graphdb.Neo4jMatchers.getConstraints; +import static org.neo4j.graphdb.Neo4jMatchers.isEmpty; +import static org.neo4j.helpers.collection.MapUtil.map; +import static org.neo4j.server.rest.domain.JsonHelper.createJsonFrom; +import static org.neo4j.server.rest.domain.JsonHelper.jsonToList; +import static org.neo4j.server.rest.domain.JsonHelper.jsonToMap; + +public class SchemaConstraintsIT extends AbstractRestFunctionalTestBase +{ + @Documented( "Create uniqueness constraint.\n" + + "Create a uniqueness constraint on a property." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void createPropertyUniquenessConstraint() throws JsonParseException + { + data.get(); + + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + Map definition = map( "property_keys", singletonList( propertyKey ) ); + + String result = gen.get().expectedStatus( 200 ).payload( createJsonFrom( definition ) ).post( + getSchemaConstraintLabelUniquenessUri( labelName ) ).entity(); + + Map serialized = jsonToMap( result ); + + Map constraint = new HashMap<>( ); + constraint.put( "type", ConstraintType.UNIQUENESS.name() ); + constraint.put( "label", labelName ); + constraint.put( "property_keys", singletonList( propertyKey ) ); + + assertThat( serialized, equalTo( constraint ) ); + } + + @Documented( "Get a specific uniqueness constraint.\n" + + "Get a specific uniqueness constraint for a label and a property." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void getLabelUniquenessPropertyConstraint() throws JsonParseException + { + data.get(); + + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + createLabelUniquenessPropertyConstraint( labelName, propertyKey ); + + String result = gen.get().expectedStatus( 200 ).get( + getSchemaConstraintLabelUniquenessPropertyUri( labelName, propertyKey ) ).entity(); + + List> serializedList = jsonToList( result ); + + Map constraint = new HashMap<>( ); + constraint.put( "type", ConstraintType.UNIQUENESS.name() ); + constraint.put( "label", labelName ); + constraint.put( "property_keys", singletonList( propertyKey ) ); + + assertThat( serializedList, hasItem( constraint ) ); + } + + @SuppressWarnings( "unchecked" ) + @Documented( "Get all uniqueness constraints for a label." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void getLabelUniquenessPropertyConstraints() throws JsonParseException + { + data.get(); + + String labelName = labels.newInstance(), propertyKey1 = properties.newInstance(), propertyKey2 = properties.newInstance(); + createLabelUniquenessPropertyConstraint( labelName, propertyKey1 ); + createLabelUniquenessPropertyConstraint( labelName, propertyKey2 ); + + String result = gen.get().expectedStatus( 200 ).get( getSchemaConstraintLabelUniquenessUri( labelName ) ).entity(); + + List> serializedList = jsonToList( result ); + + Map constraint1 = new HashMap<>( ); + constraint1.put( "type", ConstraintType.UNIQUENESS.name() ); + constraint1.put( "label", labelName ); + constraint1.put( "property_keys", singletonList( propertyKey1 ) ); + + Map constraint2 = new HashMap<>( ); + constraint2.put( "type", ConstraintType.UNIQUENESS.name() ); + constraint2.put( "label", labelName ); + constraint2.put( "property_keys", singletonList( propertyKey2 ) ); + + assertThat( serializedList, hasItems( constraint1, constraint2 ) ); + } + + @SuppressWarnings( "unchecked" ) + @Documented( "Get all constraints for a label." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void getLabelPropertyConstraints() throws JsonParseException + { + data.get(); + + String labelName = labels.newInstance(), propertyKey1 = properties.newInstance(); + createLabelUniquenessPropertyConstraint( labelName, propertyKey1 ); + + String result = gen.get().expectedStatus( 200 ).get( getSchemaConstraintLabelUri( labelName ) ).entity(); + + List> serializedList = jsonToList( result ); + + Map constraint1 = new HashMap<>( ); + constraint1.put( "type", ConstraintType.UNIQUENESS.name() ); + constraint1.put( "label", labelName ); + constraint1.put( "property_keys", singletonList( propertyKey1 ) ); + + assertThat( serializedList, hasItems( constraint1 ) ); + } + + @SuppressWarnings( "unchecked" ) + @Documented( "Get all constraints." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void get_constraints() throws JsonParseException + { + data.get(); + + String labelName1 = labels.newInstance(), propertyKey1 = properties.newInstance(); + createLabelUniquenessPropertyConstraint( labelName1, propertyKey1 ); + + String result = gen.get().expectedStatus( 200 ).get( getSchemaConstraintUri() ).entity(); + + List> serializedList = jsonToList( result ); + + Map constraint1 = new HashMap<>(); + constraint1.put( "type", ConstraintType.UNIQUENESS.name() ); + constraint1.put( "label", labelName1 ); + constraint1.put( "property_keys", singletonList( propertyKey1 ) ); + + assertThat( serializedList, hasItems( constraint1 ) ); + } + + @Documented( "Drop uniqueness constraint.\n" + + "Drop uniqueness constraint for a label and a property." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void drop_constraint() throws Exception + { + data.get(); + + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + ConstraintDefinition constraintDefinition = createLabelUniquenessPropertyConstraint( labelName, propertyKey ); + assertThat( getConstraints( graphdb(), label( labelName ) ), containsOnly( constraintDefinition ) ); + + gen.get().expectedStatus( 204 ).delete( getSchemaConstraintLabelUniquenessPropertyUri( labelName, propertyKey ) ).entity(); + + assertThat( getConstraints( graphdb(), label( labelName ) ), isEmpty() ); + } + + /** + * Create an index for a label and property key which already exists. + */ + @Test + public void create_existing_constraint() + { + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + createLabelUniquenessPropertyConstraint( labelName, propertyKey ); + + Map definition = map( "property_keys", singletonList( propertyKey ) ); + gen.get().expectedStatus( 409 ).payload( createJsonFrom( definition ) ) + .post( getSchemaConstraintLabelUniquenessUri( labelName ) ).entity(); + } + + @Test + public void drop_non_existent_constraint() throws Exception + { + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + + gen.get().expectedStatus( 404 ) + .delete( getSchemaConstraintLabelUniquenessPropertyUri( labelName, propertyKey ) ); + } + + /** + * Creating a compound index should not yet be supported. + */ + @Test + public void create_compound_schema_index() + { + Map definition = map( "property_keys", + asList( properties.newInstance(), properties.newInstance() ) ); + + gen.get().expectedStatus( 400 ) + .payload( createJsonFrom( definition ) ).post( getSchemaIndexLabelUri( labels.newInstance() ) ); + } + + private ConstraintDefinition createLabelUniquenessPropertyConstraint( String labelName, String propertyKey ) + { + try ( Transaction tx = graphdb().beginTx() ) + { + ConstraintDefinition constraintDefinition = graphdb().schema().constraintFor( label( labelName ) ) + .assertPropertyIsUnique( propertyKey ).create(); + tx.success(); + return constraintDefinition; + } + } + + private final Factory labels = UniqueStrings.withPrefix( "label" ); + private final Factory properties = UniqueStrings.withPrefix( "property" ); + private final Factory relationshipTypes = UniqueStrings.withPrefix( "relationshipType" ); +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/SchemaIndexIT.java b/community/server/src/test/java/org/neo4j/server/rest/SchemaIndexIT.java new file mode 100644 index 0000000000000..c090a8a318af6 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/SchemaIndexIT.java @@ -0,0 +1,256 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; + +import org.junit.Test; + +import org.neo4j.function.Factory; +import org.neo4j.graphdb.Neo4jMatchers; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.schema.IndexDefinition; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription; + +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; + +import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import static org.neo4j.graphdb.Label.label; +import static org.neo4j.graphdb.Neo4jMatchers.containsOnly; +import static org.neo4j.helpers.collection.MapUtil.map; +import static org.neo4j.server.rest.domain.JsonHelper.createJsonFrom; +import static org.neo4j.server.rest.domain.JsonHelper.jsonToList; +import static org.neo4j.server.rest.domain.JsonHelper.jsonToMap; + +public class SchemaIndexIT extends AbstractRestFunctionalTestBase +{ + @Documented( "Create index.\n" + + "\n" + + "This will start a background job in the database that will create and populate the index.\n" + + "You can check the status of your index by listing all the indexes for the relevant label." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void create_index() throws JsonParseException + { + data.get(); + + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + Map definition = map( "property_keys", singletonList( propertyKey ) ); + + String result = gen.get() + .expectedStatus( 200 ) + .payload( createJsonFrom( definition ) ) + .post( getSchemaIndexLabelUri( labelName ) ) + .entity(); + + Map serialized = jsonToMap( result ); + + + Map index = new HashMap<>(); + index.put( "label", labelName ); + index.put( "property_keys", singletonList( propertyKey ) ); + + assertThat( serialized, equalTo( index ) ); + } + + @Documented( "List indexes for a label." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void get_indexes_for_label() throws Exception + { + data.get(); + + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + createIndex( labelName, propertyKey ); + Map definition = map( "property_keys", singletonList( propertyKey ) ); + + List> serializedList = retryOnStillPopulating( new Callable() + { + @Override + public String call() + { + return gen.get() + .expectedStatus( 200 ) + .payload( createJsonFrom( definition ) ) + .get( getSchemaIndexLabelUri( labelName ) ) + .entity(); + } + } ); + + Map index = new HashMap<>(); + index.put( "label", labelName ); + index.put( "property_keys", singletonList( propertyKey ) ); + + assertThat( serializedList, hasItem( index ) ); + } + + private List> retryOnStillPopulating( Callable callable ) throws Exception + { + long endTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis( 1 ); + List> serializedList; + do + { + String result = callable.call(); + serializedList = jsonToList( result ); + if ( System.currentTimeMillis() > endTime ) + { + fail( "Indexes didn't populate correctly, last result '" + result + "'" ); + } + } + while ( stillPopulating( serializedList ) ); + return serializedList; + } + + private boolean stillPopulating( List> serializedList ) + { + // We've created an index. That HTTP call for creating the index will return + // immediately and indexing continue in the background. Querying the index endpoint + // while index is populating gives back additional information like population progress. + // This test below will look at the response of a "get index" result and if still populating + // then return true so that caller may retry the call later. + for ( Map map : serializedList ) + { + if ( map.containsKey( "population_progress" ) ) + { + return true; + } + } + return false; + } + + @SuppressWarnings( "unchecked" ) + @Documented( "Get all indexes." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void get_indexes() throws Exception + { + data.get(); + + String labelName1 = labels.newInstance(), propertyKey1 = properties.newInstance(); + String labelName2 = labels.newInstance(), propertyKey2 = properties.newInstance(); + createIndex( labelName1, propertyKey1 ); + createIndex( labelName2, propertyKey2 ); + + List> serializedList = retryOnStillPopulating( new Callable() + { + @Override + public String call() throws Exception + { + return gen.get().expectedStatus( 200 ).get( getSchemaIndexUri() ).entity(); + } + } ); + + Map index1 = new HashMap<>(); + index1.put( "label", labelName1 ); + index1.put( "property_keys", singletonList( propertyKey1 ) ); + + Map index2 = new HashMap<>(); + index2.put( "label", labelName2 ); + index2.put( "property_keys", singletonList( propertyKey2 ) ); + + assertThat( serializedList, hasItems( index1, index2 ) ); + } + + @Documented( "Drop index" ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void drop_index() throws Exception + { + data.get(); + + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + IndexDefinition schemaIndex = createIndex( labelName, propertyKey ); + assertThat( Neo4jMatchers.getIndexes( graphdb(), label( labelName ) ), containsOnly( schemaIndex ) ); + + gen.get() + .expectedStatus( 204 ) + .delete( getSchemaIndexLabelPropertyUri( labelName, propertyKey ) ) + .entity(); + + assertThat( Neo4jMatchers.getIndexes( graphdb(), label( labelName ) ), not( containsOnly( schemaIndex ) ) ); + } + + /** + * Create an index for a label and property key which already exists. + */ + @Test + public void create_existing_index() + { + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + createIndex( labelName, propertyKey ); + Map definition = map( "property_keys", singletonList( propertyKey ) ); + + gen.get() + .expectedStatus( 409 ) + .payload( createJsonFrom( definition ) ) + .post( getSchemaIndexLabelUri( labelName ) ); + } + + @Test + public void drop_non_existent_index() throws Exception + { + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + + gen.get() + .expectedStatus( 404 ) + .delete( getSchemaIndexLabelPropertyUri( labelName, propertyKey ) ); + } + + /** + * Creating a compound index should not yet be supported + */ + @Test + public void create_compound_index() + { + Map definition = map( "property_keys", asList( properties.newInstance(), properties.newInstance()) ); + + gen.get() + .expectedStatus( 400 ) + .payload( createJsonFrom( definition ) ) + .post( getSchemaIndexLabelUri( labels.newInstance() ) ); + } + + private IndexDefinition createIndex( String labelName, String propertyKey ) + { + try ( Transaction tx = graphdb().beginTx() ) + { + IndexDefinition indexDefinition = graphdb().schema().indexFor( label( labelName ) ).on( propertyKey ) + .create(); + tx.success(); + return indexDefinition; + } + } + + private final Factory labels = UniqueStrings.withPrefix( "label" ); + private final Factory properties = UniqueStrings.withPrefix( "property" ); +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/SetNodePropertiesIT.java b/community/server/src/test/java/org/neo4j/server/rest/SetNodePropertiesIT.java new file mode 100644 index 0000000000000..ee8db5ab8d37d --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/SetNodePropertiesIT.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +import org.neo4j.graphdb.Node; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription.Graph; +import org.neo4j.test.GraphDescription.NODE; +import org.neo4j.test.GraphDescription.PROP; + +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +import static org.neo4j.graphdb.Neo4jMatchers.hasProperty; +import static org.neo4j.graphdb.Neo4jMatchers.inTx; + +public class SetNodePropertiesIT extends + AbstractRestFunctionalTestBase +{ + + @Graph( "jim knows joe" ) + @Documented( "Update node properties.\n" + + "\n" + + "This will replace all existing properties on the node with the new set\n" + + "of attributes." ) + @Test + public void shouldReturn204WhenPropertiesAreUpdated() + throws JsonParseException + { + Node jim = data.get().get( "jim" ); + assertThat( jim, inTx(graphdb(), not( hasProperty( "age" ) ) ) ); + gen.get().payload( + JsonHelper.createJsonFrom( MapUtil.map( "age", "18" ) ) ).expectedStatus( + 204 ).put( getPropertiesUri( jim ) ); + assertThat( jim, inTx(graphdb(), hasProperty( "age" ).withValue( "18" ) ) ); + } + + @Graph( "jim knows joe" ) + @Test + public void set_node_properties_in_Unicode() + throws JsonParseException + { + Node jim = data.get().get( "jim" ); + gen.get().payload( + JsonHelper.createJsonFrom( MapUtil.map( "name", "\u4f8b\u5b50" ) ) ).expectedStatus( + 204 ).put( getPropertiesUri( jim ) ); + assertThat( jim, inTx( graphdb(), hasProperty( "name" ).withValue( "\u4f8b\u5b50" ) ) ); + } + + @Test + @Graph( "jim knows joe" ) + public void shouldReturn400WhenSendinIncompatibleJsonProperties() + throws JsonParseException + { + Map map = new HashMap(); + map.put( "jim", new HashMap() ); + gen.get().payload( JsonHelper.createJsonFrom( map ) ).expectedStatus( + 400 ).put( getPropertiesUri( data.get().get( "jim" ) ) ); + } + + @Test + @Graph( "jim knows joe" ) + public void shouldReturn400WhenSendingCorruptJsonProperties() + { + JaxRsResponse response = RestRequest.req().put( + getPropertiesUri( data.get().get( "jim" ) ), + "this:::Is::notJSON}" ); + assertEquals( 400, response.getStatus() ); + response.close(); + } + + @Test + @Graph( "jim knows joe" ) + public void shouldReturn404WhenPropertiesSentToANodeWhichDoesNotExist() + throws JsonParseException + { + gen.get().payload( + JsonHelper.createJsonFrom( MapUtil.map( "key", "val" ) ) ).expectedStatus( + 404 ).put( getDataUri() + "node/12345/properties" ); + } + + private URI getPropertyUri( Node node, String key ) throws Exception + { + return new URI( getPropertiesUri( node ) + "/" + key ); + } + + @Documented( "Set property on node.\n" + + "\n" + + "Setting different properties will retain the existing ones for this node.\n" + + "Note that a single value are submitted not as a map but just as a value\n" + + "(which is valid JSON) like in the example\n" + + "below." ) + @Graph( nodes = {@NODE(name="jim", properties={@PROP(key="foo2", value="bar2")})} ) + @Test + public void shouldReturn204WhenPropertyIsSet() throws Exception + { + Node jim = data.get().get( "jim" ); + gen.get().payload( JsonHelper.createJsonFrom( "bar" ) ).expectedStatus( + 204 ).put( getPropertyUri( jim, "foo" ).toString() ); + assertThat( jim, inTx(graphdb(), hasProperty( "foo" ) ) ); + assertThat( jim, inTx(graphdb(), hasProperty( "foo2" ) ) ); + } + + @Documented( "Property values can not be nested.\n" + + "\n" + + "Nesting properties is not supported. You could for example store the\n" + + "nested JSON as a string instead." ) + @Test + public void shouldReturn400WhenSendinIncompatibleJsonProperty() + throws Exception + { + gen.get() + .payload( "{\"foo\" : {\"bar\" : \"baz\"}}" ) + .expectedStatus( + 400 ).post( getDataUri() + "node/" ); + } + + @Test + @Graph( "jim knows joe" ) + public void shouldReturn400WhenSendingCorruptJsonProperty() + throws Exception + { + JaxRsResponse response = RestRequest.req().put( + getPropertyUri( data.get().get( "jim" ), "foo" ), + "this:::Is::notJSON}" ); + assertEquals( 400, response.getStatus() ); + response.close(); + } + + @Test + @Graph( "jim knows joe" ) + public void shouldReturn404WhenPropertySentToANodeWhichDoesNotExist() + throws Exception + { + JaxRsResponse response = RestRequest.req().put( + getDataUri() + "node/1234/foo", + JsonHelper.createJsonFrom( "bar" ) ); + assertEquals( 404, response.getStatus() ); + response.close(); + } + +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/SetRelationshipPropertiesIT.java b/community/server/src/test/java/org/neo4j/server/rest/SetRelationshipPropertiesIT.java new file mode 100644 index 0000000000000..c357394745737 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/SetRelationshipPropertiesIT.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription.Graph; + +import static org.junit.Assert.assertEquals; + +public class SetRelationshipPropertiesIT extends AbstractRestFunctionalDocTestBase +{ + private URI propertiesUri; + private URI badUri; + + private static FunctionalTestHelper functionalTestHelper; + + @BeforeClass + public static void setupServer() throws IOException + { + functionalTestHelper = new FunctionalTestHelper( server() ); + } + + @Before + public void setupTheDatabase() throws Exception + { + long relationshipId = new GraphDbHelper( server().getDatabase() ).createRelationship( "KNOWS" ); + propertiesUri = new URI( functionalTestHelper.relationshipPropertiesUri( relationshipId ) ); + badUri = new URI( functionalTestHelper.relationshipPropertiesUri( relationshipId + 1 * 99999 ) ); + } + + @Documented( "Update relationship properties." ) + @Test + @Graph + public void shouldReturn204WhenPropertiesAreUpdated() throws JsonParseException + { + data.get(); + Map map = new HashMap(); + map.put( "jim", "tobias" ); + gen.get().description( startGraph( "update relationship properties" ) ) + .payload( JsonHelper.createJsonFrom( map ) ) + .expectedStatus( 204 ) + .put( propertiesUri.toString() ); + JaxRsResponse response = updatePropertiesOnServer(map); + assertEquals( 204, response.getStatus() ); + response.close(); + } + + @Test + public void shouldReturn400WhenSendinIncompatibleJsonProperties() throws JsonParseException + { + Map map = new HashMap(); + map.put( "jim", new HashMap() ); + JaxRsResponse response = updatePropertiesOnServer(map); + assertEquals( 400, response.getStatus() ); + response.close(); + } + + @Test + public void shouldReturn400WhenSendingCorruptJsonProperties() { + JaxRsResponse response = RestRequest.req().put(propertiesUri.toString(), "this:::Is::notJSON}"); + assertEquals(400, response.getStatus()); + response.close(); + } + + @Test + public void shouldReturn404WhenPropertiesSentToANodeWhichDoesNotExist() throws JsonParseException { + Map map = new HashMap(); + map.put("jim", "tobias"); + + JaxRsResponse response = RestRequest.req().put(badUri.toString(), JsonHelper.createJsonFrom(map)); + assertEquals(404, response.getStatus()); + response.close(); + } + + private JaxRsResponse updatePropertiesOnServer(final Map map) throws JsonParseException + { + return RestRequest.req().put(propertiesUri.toString(), JsonHelper.createJsonFrom(map)); + } + + private String getPropertyUri(final String key) throws Exception + { + return propertiesUri.toString() + "/" + key ; + } + + @Test + public void shouldReturn204WhenPropertyIsSet() throws Exception + { + JaxRsResponse response = setPropertyOnServer("foo", "bar"); + assertEquals( 204, response.getStatus() ); + response.close(); + } + + @Test + public void shouldReturn400WhenSendinIncompatibleJsonProperty() throws Exception + { + JaxRsResponse response = setPropertyOnServer("jim", new HashMap()); + assertEquals( 400, response.getStatus() ); + response.close(); + } + + @Test + public void shouldReturn400WhenSendingCorruptJsonProperty() throws Exception { + JaxRsResponse response = RestRequest.req().put(getPropertyUri("foo"), "this:::Is::notJSON}"); + assertEquals(400, response.getStatus()); + response.close(); + } + + @Test + public void shouldReturn404WhenPropertySentToANodeWhichDoesNotExist() throws Exception { + JaxRsResponse response = RestRequest.req().put(badUri.toString() + "/foo", JsonHelper.createJsonFrom("bar")); + assertEquals(404, response.getStatus()); + response.close(); + } + + private JaxRsResponse setPropertyOnServer(final String key, final Object value) throws Exception { + return RestRequest.req().put(getPropertyUri(key), JsonHelper.createJsonFrom(value)); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/TraverserIT.java b/community/server/src/test/java/org/neo4j/server/rest/TraverserIT.java new file mode 100644 index 0000000000000..61c33c1c60345 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/TraverserIT.java @@ -0,0 +1,242 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import javax.ws.rs.core.Response.Status; + +import org.junit.Test; + +import org.neo4j.graphdb.Node; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription.Graph; +import org.neo4j.test.GraphDescription.NODE; + +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +import static org.neo4j.helpers.collection.MapUtil.map; +import static org.neo4j.server.rest.domain.JsonHelper.createJsonFrom; +import static org.neo4j.server.rest.domain.JsonHelper.readJson; + +public class TraverserIT extends AbstractRestFunctionalTestBase +{ + + @Test + public void shouldGet404WhenTraversingFromNonExistentNode() + { + gen().expectedStatus( Status.NOT_FOUND.getStatusCode() ).payload( + "{}" ).post( getDataUri() + "node/10000/traverse/node" ).entity(); + } + + @Test + @Graph( nodes = {@NODE(name="I")} ) + public void shouldGet200WhenNoHitsFromTraversing() + { + assertSize( 0,gen().expectedStatus( 200 ).payload( "" ).post( + getTraverseUriNodes( getNode( "I" ) ) ).entity()); + } + + /** + * In order to return relationships, + * simply specify the return type as part of the URL. + */ + @Test + @Graph( {"I know you", "I own car"} ) + public void return_relationships_from_a_traversal() + { + assertSize( 2, gen().expectedStatus( 200 ).payload( "{\"order\":\"breadth_first\",\"uniqueness\":\"none\",\"return_filter\":{\"language\":\"builtin\",\"name\":\"all\"}}" ).post( + getTraverseUriRelationships( getNode( "I" ) ) ).entity()); + } + + + /** + * In order to return paths from a traversal, + * specify the +Path+ return type as part of the URL. + */ + @Test + @Graph( {"I know you", "I own car"} ) + public void return_paths_from_a_traversal() + { + assertSize( 3, gen().expectedStatus( 200 ).payload( "{\"order\":\"breadth_first\",\"uniqueness\":\"none\",\"return_filter\":{\"language\":\"builtin\",\"name\":\"all\"}}" ).post( + getTraverseUriPaths( getNode( "I" ) ) ).entity()); + } + + + private String getTraverseUriRelationships( Node node ) + { + return getNodeUri( node) + "/traverse/relationship"; + } + private String getTraverseUriPaths( Node node ) + { + return getNodeUri( node) + "/traverse/path"; + } + + private String getTraverseUriNodes( Node node ) + { + // TODO Auto-generated method stub + return getNodeUri( node) + "/traverse/node"; + } + + @Test + @Graph( "I know you" ) + public void shouldGetSomeHitsWhenTraversingWithDefaultDescription() + throws JsonParseException + { + String entity = gen().expectedStatus( Status.OK.getStatusCode() ).payload( "{}" ).post( + getTraverseUriNodes( getNode( "I" ) ) ).entity(); + + expectNodes( entity, getNode( "you" )); + } + + private void expectNodes( String entity, Node... nodes ) + throws JsonParseException + { + Set expected = new HashSet<>(); + for ( Node node : nodes ) + { + expected.add( getNodeUri( node ) ); + } + Collection items = (Collection) readJson( entity ); + for ( Object item : items ) + { + Map map = (Map) item; + String uri = (String) map.get( "self" ); + assertTrue( uri + " not found", expected.remove( uri ) ); + } + assertTrue( "Expected not empty:" + expected, expected.isEmpty() ); + } + + @Documented( "Traversal using a return filter.\n" + + "\n" + + "In this example, the +none+ prune evaluator is used and a return filter\n" + + "is supplied in order to return all names containing \"t\".\n" + + "The result is to be returned as nodes and the max depth is\n" + + "set to 3." ) + @Graph( {"Root knows Mattias", "Root knows Johan", "Johan knows Emil", "Emil knows Peter", "Emil knows Tobias", "Tobias loves Sara"} ) + @Test + public void shouldGetExpectedHitsWhenTraversingWithDescription() + throws JsonParseException + { + Node start = getNode( "Root" ); + List> rels = new ArrayList<>(); + rels.add( map( "type", "knows", "direction", "all" ) ); + rels.add( map( "type", "loves", "direction", "all" ) ); + String description = createJsonFrom( map( + "order", + "breadth_first", + "uniqueness", + "node_global", + "prune_evaluator", + map( "language", "javascript", "body", "position.length() > 10" ), + "return_filter", + map( "language", "javascript", "body", + "position.endNode().getProperty('name').toLowerCase().contains('t')" ), + "relationships", rels, "max_depth", 3 ) ); + String entity = gen().expectedStatus( 200 ).payload( description ).post( + getTraverseUriNodes( start ) ).entity(); + expectNodes( entity, getNodes( "Root", "Mattias", "Peter", "Tobias" ) ); + } + + @Documented( "Traversal returning nodes below a certain depth.\n" + + "\n" + + "Here, all nodes at a traversal depth below 3 are returned." ) + @Graph( {"Root knows Mattias", "Root knows Johan", "Johan knows Emil", "Emil knows Peter", "Emil knows Tobias", "Tobias loves Sara"} ) + @Test + public void shouldGetExpectedHitsWhenTraversingAtDepth() + throws JsonParseException + { + Node start = getNode( "Root" ); + String description = createJsonFrom( map( + "prune_evaluator", + map( "language", "builtin", "name", "none" ), + "return_filter", + map( "language", "javascript", "body", + "position.length()<3;" ) ) ); + String entity = gen().expectedStatus( 200 ).payload( description ).post( + getTraverseUriNodes( start ) ).entity(); + expectNodes( entity, getNodes( "Root", "Mattias", "Johan", "Emil" ) ); + } + + @Test + @Graph( "I know you" ) + public void shouldGet400WhenSupplyingInvalidTraverserDescriptionFormat() + { + gen().expectedStatus( Status.BAD_REQUEST.getStatusCode() ).payload( + "::not JSON{[ at all" ).post( + getTraverseUriNodes( getNode( "I" ) ) ).entity(); + } + + @Test + @Graph( {"Root knows Mattias", + "Root knows Johan", "Johan knows Emil", "Emil knows Peter", + "Root eats Cork", "Cork hates Root", + "Root likes Banana", "Banana is_a Fruit"} ) + public void shouldAllowTypeOrderedTraversals() + throws JsonParseException + { + Node start = getNode( "Root" ); + String description = createJsonFrom( map( + "expander", "order_by_type", + "relationships", + new Map[]{ + map( "type", "eats"), + map( "type", "knows" ), + map( "type", "likes" ) + }, + "prune_evaluator", + map( "language", "builtin", + "name", "none" ), + "return_filter", + map( "language", "javascript", + "body", "position.length()<2;" ) + ) ); + @SuppressWarnings( "unchecked" ) + List> nodes = (List>) readJson( gen().expectedStatus( 200 ).payload( + description ).post( + getTraverseUriNodes( start ) ).entity() ); + + assertThat( nodes.size(), is( 5 ) ); + assertThat( getName( nodes.get( 0 ) ), is( "Root" ) ); + assertThat( getName( nodes.get( 1 ) ), is( "Cork" ) ); + + // We don't really care about the ordering between Johan and Mattias, we just assert that they + // both are there, in between Root/Cork and Banana + Set knowsNodes = new HashSet<>( Arrays.asList( "Johan", "Mattias" ) ); + assertTrue( knowsNodes.remove( getName( nodes.get( 2 ) ) ) ); + assertTrue( knowsNodes.remove( getName( nodes.get( 3 ) ) ) ); + + assertThat( getName( nodes.get( 4 ) ), is( "Banana" ) ); + } + + @SuppressWarnings( "unchecked" ) + private String getName( Map propContainer ) + { + return (String) ((Map)propContainer.get( "data" )).get( "name" ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/paging/PagedTraverserIT.java b/community/server/src/test/java/org/neo4j/server/rest/paging/PagedTraverserIT.java new file mode 100644 index 0000000000000..195c6535cfb72 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/paging/PagedTraverserIT.java @@ -0,0 +1,492 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.paging; + +import java.net.URI; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import javax.ws.rs.core.MediaType; + +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.graphdb.Transaction; +import org.neo4j.helpers.FakeClock; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.CommunityNeoServer; +import org.neo4j.server.database.Database; +import org.neo4j.server.helpers.CommunityServerBuilder; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.helpers.ServerHelper; +import org.neo4j.server.rest.JaxRsResponse; +import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; +import org.neo4j.server.rest.RestRequest; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.scripting.javascript.GlobalJavascriptInitializer; +import org.neo4j.test.TestData; +import org.neo4j.test.server.ExclusiveServerTestBase; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; + +import static org.neo4j.test.SuppressOutput.suppressAll; + +public class PagedTraverserIT extends ExclusiveServerTestBase +{ + private static CommunityNeoServer server; + private static FunctionalTestHelper functionalTestHelper; + + private Node theStartNode; + private static final String PAGED_TRAVERSE_LINK_REL = "paged_traverse"; + private static final int SHORT_LIST_LENGTH = 33; + private static final int LONG_LIST_LENGTH = 444; + private static final int VERY_LONG_LIST_LENGTH = LONG_LIST_LENGTH*2; + + @ClassRule + public static TemporaryFolder staticFolder = new TemporaryFolder(); + + public + @Rule + TestData gen = TestData.producedThrough( RESTDocsGenerator.PRODUCER ); + private static FakeClock clock; + + @Before + public void setUp() + { + gen.get().setSection( "dev/rest-api" ); + } + + @BeforeClass + public static void setupServer() throws Exception + { + clock = new FakeClock(); + server = CommunityServerBuilder.server() + .usingDataDir( staticFolder.getRoot().getAbsolutePath() ) + .withClock( clock ) + .build(); + + suppressAll().call( (Callable) () -> { + server.start(); + return null; + } ); + functionalTestHelper = new FunctionalTestHelper( server ); + } + + @Before + public void setupTheDatabase() throws Exception + { + ServerHelper.cleanTheDatabase( server ); + } + + @AfterClass + public static void stopServer() throws Exception + { + suppressAll().call( (Callable) () -> { + server.stop(); + return null; + } ); + } + + @Test + public void nodeRepresentationShouldHaveLinkToPagedTraverser() throws Exception + { + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + + JaxRsResponse response = RestRequest.req().get( functionalTestHelper.nodeUri( theStartNode.getId() ) ); + + Map jsonMap = JsonHelper.jsonToMap( response.getEntity() ); + + assertNotNull( jsonMap.containsKey( PAGED_TRAVERSE_LINK_REL ) ); + assertThat( String.valueOf( jsonMap.get( PAGED_TRAVERSE_LINK_REL ) ), + containsString( "/db/data/node/" + String.valueOf( theStartNode.getId() ) + + "/paged/traverse/{returnType}{?pageSize,leaseTime}" ) ); + } + + @Documented( "Creating a paged traverser.\n\n" + + "Paged traversers are created by ++POST++-ing a\n" + + "traversal description to the link identified by the +paged_traverser+ key\n" + + "in a node representation. When creating a paged traverser, the same\n" + + "options apply as for a regular traverser, meaning that +node+, +path+,\n" + + "or +fullpath+, can be targeted." ) + @Test + public void shouldPostATraverserWithDefaultOptionsAndReceiveTheFirstPageOfResults() throws Exception + { + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + + ResponseEntity entity = gen.get() + .expectedType( MediaType.valueOf( "application/json; charset=UTF-8" ) ) + .expectedHeader( "Location" ) + .expectedStatus( 201 ) + .payload( traverserDescription() ) + .payloadType( MediaType.APPLICATION_JSON_TYPE ) + .post( functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node" ); + assertEquals( 201, entity.response() + .getStatus() ); + assertThat( entity.response() + .getLocation() + .toString(), containsString( "/db/data/node/" + theStartNode.getId() + "/paged/traverse/node/" ) ); + assertEquals( "application/json; charset=UTF-8", entity.response() + .getType() + .toString() ); + } + + @Documented( "Paging through the results of a paged traverser.\n\n" + + "Paged traversers holdstate on the server, and allow clients to page through\n" + + "the results of a traversal. To progress to the next page of traversal results,\n" + + "the client issues a HTTP GET request on the paged traversal URI which causes the\n" + + "traversal to fill the next page (or partially fill it if insufficient\n" + + "results are available).\n" + + " \n" + + "Note that if a traverser expires through inactivity it will cause a 404\n" + + "response on the next +GET+ request. Traversers' leases are renewed on\n" + + "every successful access for the same amount of time as originally\n" + + "specified.\n" + + " \n" + + "When the paged traverser reaches the end of its results, the client can\n" + + "expect a 404 response as the traverser is disposed by the server." ) + @Test + public void shouldBeAbleToTraverseAllThePagesWithDefaultPageSize() + { + theStartNode = createLinkedList( LONG_LIST_LENGTH, server.getDatabase() ); + + URI traverserLocation = createPagedTraverser().getLocation(); + + int enoughPagesToExpireTheTraverser = 3; + for ( int i = 0; i < enoughPagesToExpireTheTraverser; i++ ) + { + + gen.get() + .expectedType( MediaType.APPLICATION_JSON_TYPE ) + .expectedStatus( 200 ) + .payload( traverserDescription() ) + .get( traverserLocation.toString() ); + } + + JaxRsResponse response = new RestRequest( traverserLocation ).get(); + assertEquals( 404, response.getStatus() ); + } + + @Test + public void shouldExpireTheTraverserAfterDefaultTimeoutAndGetA404Response() + { + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + + JaxRsResponse postResponse = createPagedTraverser(); + assertEquals( 201, postResponse.getStatus() ); + + final int TEN_MINUTES = 10; + clock.forward( TEN_MINUTES, TimeUnit.MINUTES ); + + JaxRsResponse getResponse = new RestRequest( postResponse.getLocation() ).get(); + + assertEquals( 404, getResponse.getStatus() ); + } + + @Documented( "Paged traverser page size.\n\n" + + "The default page size is 50 items, but\n" + + "depending on the application larger or smaller pages sizes might be\n" + + "appropriate. This can be set by adding a +pageSize+ query parameter." ) + @Test + public void shouldBeAbleToTraverseAllThePagesWithNonDefaultPageSize() + { + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + + URI traverserLocation = createPagedTraverserWithPageSize( 1 ).getLocation(); + + int enoughPagesToExpireTheTraverser = 12; + for ( int i = 0; i < enoughPagesToExpireTheTraverser; i++ ) + { + + JaxRsResponse response = new RestRequest( traverserLocation ).get(); + assertEquals( 200, response.getStatus() ); + } + + JaxRsResponse response = new RestRequest( traverserLocation ).get(); + assertEquals( 404, response.getStatus() ); + } + + @Documented( "Paged traverser timeout.\n\n" + + "The default timeout for a paged traverser is 60\n" + + "seconds, but depending on the application larger or smaller timeouts\n" + + "might be appropriate. This can be set by adding a +leaseTime+ query\n" + + "parameter with the number of seconds the paged traverser should last." ) + @Test + public void shouldExpireTraverserWithNonDefaultTimeout() + { + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + + URI traverserLocation = createPagedTraverserWithTimeoutInMinutes( 10 ).getLocation(); + + clock.forward( 11, TimeUnit.MINUTES ); + + JaxRsResponse response = new RestRequest( traverserLocation ).get(); + assertEquals( 404, response.getStatus() ); + } + + @Test + public void shouldTraverseAllPagesWithANonDefaultTimeoutAndNonDefaultPageSize() + { + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + + URI traverserLocation = createPagedTraverserWithTimeoutInMinutesAndPageSize( 10, 2 ).getLocation(); + + int enoughPagesToExpireTheTraverser = 6; + for ( int i = 0; i < enoughPagesToExpireTheTraverser; i++ ) + { + + JaxRsResponse response = new RestRequest( traverserLocation ).get(); + assertEquals( 200, response.getStatus() ); + } + + JaxRsResponse response = new RestRequest( traverserLocation ).get(); + assertEquals( 404, response.getStatus() ); + } + + @Test + public void shouldRespondWith400OnNegativeLeaseTime() + { + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + + int negativeLeaseTime = -9; + JaxRsResponse response = RestRequest.req().post( + functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node?leaseTime=" + + String.valueOf( negativeLeaseTime ), traverserDescription() ); + + assertEquals( 400, response.getStatus() ); + } + + @Test + public void shouldRespondWith400OnNegativePageSize() + { + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + + int negativePageSize = -99; + JaxRsResponse response = RestRequest.req().post( + functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node?pageSize=" + + String.valueOf( negativePageSize ), traverserDescription() ); + + assertEquals( 400, response.getStatus() ); + } + + + @Test + public void shouldRespondWith400OnScriptErrors() + { + GlobalJavascriptInitializer.initialize( GlobalJavascriptInitializer.Mode.SANDBOXED ); + + theStartNode = createLinkedList( 1, server.getDatabase() ); + + JaxRsResponse response = RestRequest.req().post( + functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node?pageSize=50", + "{" + + "\"prune_evaluator\":{\"language\":\"builtin\",\"name\":\"none\"}," + + "\"return_filter\":{\"language\":\"javascript\",\"body\":\"position.getClass()" + + ".getClassLoader();\"}," + + "\"order\":\"depth_first\"," + + "\"relationships\":{\"type\":\"NEXT\",\"direction\":\"out\"}" + + "}" ); + + assertEquals( 400, response.getStatus() ); + } + + @Test + public void shouldRespondWith200OnFirstDeletionOfTraversalAnd404Afterwards() + { + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + + JaxRsResponse response = createPagedTraverser(); + + final RestRequest request = RestRequest.req(); + JaxRsResponse deleteResponse = request.delete( response.getLocation() ); + assertEquals( 200, deleteResponse.getStatus() ); + + deleteResponse = request.delete( response.getLocation() ); + assertEquals( 404, deleteResponse.getStatus() ); + } + + @Test + public void shouldAcceptJsonAndStreamingFlagAndProduceStreamedJson() + { + // given + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + + // when + JaxRsResponse pagedTraverserResponse = createStreamingPagedTraverserWithTimeoutInMinutesAndPageSize( 60, 1 ); + + + System.out.println( pagedTraverserResponse.getHeaders().getFirst( "Content-Type" ) ); + + // then + assertNotNull( pagedTraverserResponse.getHeaders().getFirst( "Content-Type" ) ); + assertThat( pagedTraverserResponse.getHeaders().getFirst( "Content-Type" ), + containsString( "application/json; charset=UTF-8; stream=true" ) ); + } + + private JaxRsResponse createStreamingPagedTraverserWithTimeoutInMinutesAndPageSize( int leaseTimeInSeconds, + int pageSize ) + { + String description = traverserDescription(); + + return RestRequest.req().header( "X-Stream", "true" ).post( + functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node?leaseTime=" + + leaseTimeInSeconds + "&pageSize=" + pageSize, description ); + } + + @Test + public void should201WithAcceptJsonHeader() + { + // given + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + String uri = functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node"; + + // when + JaxRsResponse response = RestRequest.req().accept( MediaType.APPLICATION_JSON_TYPE ).post( uri, + traverserDescription() ); + + // then + assertEquals( 201, response.getStatus() ); + assertNotNull( response.getHeaders().getFirst( "Content-Type" ) ); + assertThat( response.getType().toString(), containsString( MediaType.APPLICATION_JSON ) ); + } + + @Test + public void should201WithAcceptHtmlHeader() + { + // given + theStartNode = createLinkedList( SHORT_LIST_LENGTH, server.getDatabase() ); + String uri = functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node"; + + // when + JaxRsResponse response = RestRequest.req().accept( MediaType.TEXT_HTML_TYPE ).post( uri, + traverserDescription() ); + + // then + assertEquals( 201, response.getStatus() ); + assertNotNull( response.getHeaders().getFirst( "Content-Type" ) ); + assertThat( response.getType().toString(), containsString( MediaType.TEXT_HTML ) ); + } + + @Test + public void shouldHaveTransportEncodingChunkedOnResponseHeader() + { + // given + theStartNode = createLinkedList( VERY_LONG_LIST_LENGTH, server.getDatabase() ); + + // when + JaxRsResponse response = createStreamingPagedTraverserWithTimeoutInMinutesAndPageSize( 60, 1000 ); + + // then + assertEquals( 201, response.getStatus() ); + assertEquals( "application/json; charset=UTF-8; stream=true", response.getHeaders().getFirst( "Content-Type" + ) ); + assertThat( response.getHeaders().getFirst( "Transfer-Encoding" ), containsString( "chunked" ) ); + } + + private JaxRsResponse createPagedTraverserWithTimeoutInMinutesAndPageSize( final int leaseTimeInSeconds, + final int pageSize ) + { + String description = traverserDescription(); + + return RestRequest.req().post( + functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node?leaseTime=" + + leaseTimeInSeconds + "&pageSize=" + pageSize, description ); + } + + private JaxRsResponse createPagedTraverserWithTimeoutInMinutes( final int leaseTime ) + { + ResponseEntity responseEntity = gen.get() + .expectedType( MediaType.APPLICATION_JSON_TYPE ) + .expectedStatus( 201 ) + .payload( traverserDescription() ) + .post( functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node?leaseTime=" + + String.valueOf( leaseTime ) ); + + return responseEntity.response(); + } + + private JaxRsResponse createPagedTraverserWithPageSize( final int pageSize ) + { + ResponseEntity responseEntity = gen.get() + .expectedType( MediaType.APPLICATION_JSON_TYPE ) + .expectedStatus( 201 ) + .payload( traverserDescription() ) + .post( functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node?pageSize=" + + String.valueOf( pageSize ) ); + + return responseEntity.response(); + } + + private JaxRsResponse createPagedTraverser() + { + final String uri = functionalTestHelper.nodeUri( theStartNode.getId() ) + "/paged/traverse/node"; + return RestRequest.req().post( uri, traverserDescription() ); + } + + private String traverserDescription() + { + String description = "{" + + "\"prune_evaluator\":{\"language\":\"builtin\",\"name\":\"none\"}," + + "\"return_filter\":{\"language\":\"javascript\",\"body\":\"position.endNode().getProperty('name')" + + ".contains('1');\"}," + + "\"order\":\"depth_first\"," + + "\"relationships\":{\"type\":\"NEXT\",\"direction\":\"out\"}" + + "}"; + + return description; + } + + private Node createLinkedList( final int listLength, final Database db ) + { + Node startNode = null; + try ( Transaction tx = db.getGraph().beginTx() ) + { + Node previous = null; + for ( int i = 0; i < listLength; i++ ) + { + Node current = db.getGraph().createNode(); + current.setProperty( "name", String.valueOf( i ) ); + + if ( previous != null ) + { + previous.createRelationshipTo( current, RelationshipType.withName( "NEXT" ) ); + } + else + { + startNode = current; + } + + previous = current; + } + tx.success(); + return startNode; + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/security/AuthenticationIT.java b/community/server/src/test/java/org/neo4j/server/rest/security/AuthenticationIT.java new file mode 100644 index 0000000000000..78b30c7f4611e --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/security/AuthenticationIT.java @@ -0,0 +1,352 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.security; + +import java.io.IOException; +import javax.ws.rs.core.HttpHeaders; + +import com.sun.jersey.core.util.Base64; +import org.codehaus.jackson.JsonNode; +import org.junit.After; +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Rule; +import org.junit.Test; + +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.CommunityNeoServer; +import org.neo4j.server.helpers.CommunityServerBuilder; +import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.string.UTF8; +import org.neo4j.test.TestData; +import org.neo4j.test.server.ExclusiveServerTestBase; +import org.neo4j.test.server.HTTP; +import org.neo4j.test.server.HTTP.RawPayload; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class AuthenticationIT extends ExclusiveServerTestBase +{ + public @Rule TestData gen = TestData.producedThrough( RESTDocsGenerator.PRODUCER ); + private CommunityNeoServer server; + + @Before + public void setUp() + { + gen.get().setSection( "dev/rest-api" ); + } + + @Test + @Documented( "Missing authorization\n" + + "\n" + + "If an +Authorization+ header is not supplied, the server will reply with an error." ) + public void missing_authorization() throws JsonParseException, IOException + { + // Given + startServerWithConfiguredUser(); + + // Document + RESTDocsGenerator.ResponseEntity response = gen.get() + .expectedStatus( 401 ) + .expectedHeader( "WWW-Authenticate", "Basic realm=\"Neo4j\"" ) + .get( dataURL() ); + + // Then + JsonNode data = JsonHelper.jsonNode( response.entity() ); + JsonNode firstError = data.get( "errors" ).get( 0 ); + assertThat( firstError.get( "code" ).asText(), equalTo( "Neo.ClientError.Security.Unauthorized" ) ); + assertThat( firstError.get( "message" ).asText(), equalTo( "No authentication header supplied." ) ); + } + + @Test + @Documented( "Authenticate to access the server\n" + + "\n" + + "Authenticate by sending a username and a password to Neo4j using HTTP Basic Auth.\n" + + "Requests should include an +Authorization+ header, with a value of +Basic +,\n" + + "where \"payload\" is a base64 encoded string of \"username:password\"." ) + public void successful_authentication() throws JsonParseException, IOException + { + // Given + startServerWithConfiguredUser(); + + // Document + RESTDocsGenerator.ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .withHeader( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ) + .get( userURL( "neo4j" ) ); + + // Then + JsonNode data = JsonHelper.jsonNode( response.entity() ); + assertThat( data.get( "username" ).asText(), equalTo( "neo4j" ) ); + assertThat( data.get( "password_change_required" ).asBoolean(), equalTo( false ) ); + assertThat( data.get( "password_change" ).asText(), equalTo( passwordURL( "neo4j" ) ) ); + } + + @Test + @Documented( "Incorrect authentication\n" + + "\n" + + "If an incorrect username or password is provided, the server replies with an error." ) + public void incorrect_authentication() throws JsonParseException, IOException + { + // Given + startServerWithConfiguredUser(); + + // Document + RESTDocsGenerator.ResponseEntity response = gen.get() + .expectedStatus( 401 ) + .withHeader( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "incorrect" ) ) + .expectedHeader( "WWW-Authenticate", "Basic realm=\"Neo4j\"" ) + .post( dataURL() ); + + // Then + JsonNode data = JsonHelper.jsonNode( response.entity() ); + JsonNode firstError = data.get( "errors" ).get( 0 ); + assertThat( firstError.get( "code" ).asText(), equalTo( "Neo.ClientError.Security.Unauthorized" ) ); + assertThat( firstError.get( "message" ).asText(), equalTo( "Invalid username or password." ) ); + } + + @Test + @Documented( "Required password changes\n" + + "\n" + + "In some cases, like the very first time Neo4j is accessed, the user will be required to choose\n" + + "a new password. The database will signal that a new password is required and deny access.\n" + + "\n" + + "See <> for how to set a new password." ) + public void password_change_required() throws JsonParseException, IOException + { + // Given + startServer( true ); + + // Document + RESTDocsGenerator.ResponseEntity response = gen.get() + .expectedStatus( 403 ) + .withHeader( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "neo4j" ) ) + .get( dataURL() ); + + // Then + JsonNode data = JsonHelper.jsonNode( response.entity() ); + JsonNode firstError = data.get( "errors" ).get( 0 ); + assertThat( firstError.get( "code" ).asText(), equalTo( "Neo.ClientError.Security.Forbidden" ) ); + assertThat( firstError.get( "message" ).asText(), equalTo( "User is required to change their password." ) ); + assertThat( data.get( "password_change" ).asText(), equalTo( passwordURL( "neo4j" ) ) ); + } + + @Test + @Documented( "When auth is disabled\n" + + "\n" + + "When auth has been disabled in the configuration, requests can be sent without an +Authorization+ header." ) + public void auth_disabled() throws IOException + { + // Given + startServer( false ); + + // Document + gen.get() + .expectedStatus( 200 ) + .get( dataURL() ); + } + + @Test + public void shouldSayMalformedHeaderIfMalformedAuthorization() throws Exception + { + // Given + startServerWithConfiguredUser(); + + // When + HTTP.Response response = HTTP.withHeaders( HttpHeaders.AUTHORIZATION, "This makes no sense" ).GET( dataURL() ); + + // Then + assertThat( response.status(), equalTo( 400 ) ); + assertThat( response.get( "errors" ).get( 0 ).get( "code" ).asText(), equalTo( "Neo.ClientError.Request.InvalidFormat" ) ); + assertThat( response.get( "errors" ).get( 0 ).get( "message" ).asText(), equalTo( "Invalid authentication header." ) ); + } + + @Test + public void shouldNotAllowDataAccess() throws Exception + { + // Given + startServerWithConfiguredUser(); + + // When & then + assertAuthorizationRequired( "POST", "db/data/node", RawPayload.quotedJson( "{'name':'jake'}" ), 201 ); + assertAuthorizationRequired( "GET", "db/data/node/1234", 404 ); + assertAuthorizationRequired( "POST", "db/data/transaction/commit", RawPayload.quotedJson( + "{'statements':[{'statement':'MATCH (n) RETURN n'}]}" ), 200 ); + + assertEquals(200, HTTP.GET( server.baseUri().resolve( "" ).toString() ).status() ); + } + + @Test + public void shouldAllowAllAccessIfAuthenticationIsDisabled() throws Exception + { + // Given + startServer( false ); + + // When & then + assertEquals( 201, HTTP.POST( server.baseUri().resolve( "db/data/node" ).toString(), + RawPayload.quotedJson( "{'name':'jake'}" ) ).status() ); + assertEquals( 404, HTTP.GET( server.baseUri().resolve( "db/data/node/1234" ).toString() ).status() ); + assertEquals( 200, HTTP.POST( server.baseUri().resolve( "db/data/transaction/commit" ).toString(), + RawPayload.quotedJson( "{'statements':[{'statement':'MATCH (n) RETURN n'}]}" ) ).status() ); + } + + @Test + public void shouldReplyNicelyToTooManyFailedAuthAttempts() throws Exception + { + // Given + startServerWithConfiguredUser(); + long timeout = System.currentTimeMillis() + 30_000; + + // When + HTTP.Response response = null; + while ( System.currentTimeMillis() < timeout ) + { + // Done in a loop because we're racing with the clock to get enough failed requests into 5 seconds + response = HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "incorrect" ) ).POST( + server.baseUri().resolve( "authentication" ).toString(), + HTTP.RawPayload.quotedJson( "{'username':'neo4j', 'password':'something that is wrong'}" ) + ); + + if ( response.status() == 429 ) + { + break; + } + } + + // Then + assertThat( response.status(), equalTo( 429 ) ); + JsonNode firstError = response.get( "errors" ).get( 0 ); + assertThat( firstError.get( "code" ).asText(), equalTo( "Neo.ClientError.Security.AuthenticationRateLimit" ) ); + assertThat( firstError.get( "message" ).asText(), equalTo( "Too many failed authentication requests. Please wait 5 seconds and try again." ) ); + } + + // TODO: Enable this test when we have authorization in place + @Test + @Ignore + public void shouldNotAllowDataAccessForUnauthorizedUser() throws Exception + { + // Given + startServerWithConfiguredUser(); // TODO: The user for this test should not have read access + + // When + HTTP.Response response = + HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ).POST( + server.baseUri().resolve( "authentication" ).toString(), + HTTP.RawPayload.quotedJson( "{'username':'neo4j', 'password':'secret'}" ) + ); + + // When & then + assertEquals( 403, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ) + .POST( server.baseUri().resolve( "db/data/node" ).toString(), + RawPayload.quotedJson( "{'name':'jake'}" ) ).status() ); + assertEquals( 403, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ) + .GET( server.baseUri().resolve( "db/data/node/1234" ).toString() ).status() ); + assertEquals( 403, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ) + .POST( server.baseUri().resolve( "db/data/transaction/commit" ).toString(), + RawPayload.quotedJson( "{'statements':[{'statement':'MATCH (n) RETURN n'}]}" ) ).status() ); + } + + private void assertAuthorizationRequired( String method, String path, int expectedAuthorizedStatus ) throws JsonParseException + { + assertAuthorizationRequired( method, path, null, expectedAuthorizedStatus ); + } + + private void assertAuthorizationRequired( String method, String path, Object payload, int expectedAuthorizedStatus ) throws JsonParseException + { + // When no header + HTTP.Response response = HTTP.request( method, server.baseUri().resolve( path ).toString(), payload ); + assertThat(response.status(), equalTo(401)); + assertThat(response.get("errors").get(0).get("code").asText(), equalTo("Neo.ClientError.Security.Unauthorized")); + assertThat(response.get("errors").get(0).get("message").asText(), equalTo("No authentication header supplied.")); + assertThat(response.header( HttpHeaders.WWW_AUTHENTICATE ), equalTo("Basic realm=\"Neo4j\"")); + + // When malformed header + response = HTTP.withHeaders( HttpHeaders.AUTHORIZATION, "This makes no sense" ).request( method, server.baseUri().resolve( path ).toString(), payload ); + assertThat(response.status(), equalTo(400)); + assertThat(response.get("errors").get(0).get("code").asText(), equalTo("Neo.ClientError.Request.InvalidFormat")); + assertThat(response.get("errors").get(0).get( "message" ).asText(), equalTo("Invalid authentication header.")); + + // When invalid credential + response = HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "incorrect" ) ).request( method, server.baseUri().resolve( path ).toString(), payload ); + assertThat(response.status(), equalTo(401)); + assertThat(response.get("errors").get(0).get("code").asText(), equalTo("Neo.ClientError.Security.Unauthorized")); + assertThat(response.get("errors").get(0).get("message").asText(), equalTo("Invalid username or password.")); + assertThat(response.header(HttpHeaders.WWW_AUTHENTICATE ), equalTo("Basic realm=\"Neo4j\"")); + + // When authorized + response = HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ).request( method, server.baseUri().resolve( path ).toString(), payload ); + assertThat(response.status(), equalTo(expectedAuthorizedStatus)); + } + + @After + public void cleanup() + { + if(server != null) {server.stop();} + } + + public void startServerWithConfiguredUser() throws IOException + { + startServer( true ); + // Set the password + HTTP.Response post = HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "neo4j" ) ).POST( + server.baseUri().resolve( "/user/neo4j/password" ).toString(), + RawPayload.quotedJson( "{'password':'secret'}" ) + ); + assertEquals( 200, post.status() ); + } + + public void startServer( boolean authEnabled ) throws IOException + { + server = CommunityServerBuilder.server() + .withProperty( GraphDatabaseSettings.auth_enabled.name(), Boolean.toString( authEnabled ) ) + .build(); + server.start(); + } + + private String challengeResponse( String username, String password ) + { + return "Basic " + base64( username + ":" + password ); + } + + private String dataURL() + { + return server.baseUri().resolve( "db/data/" ).toString(); + } + + private String userURL( String username ) + { + return server.baseUri().resolve( "user/" + username ).toString(); + } + + private String passwordURL( String username ) + { + return server.baseUri().resolve( "user/" + username + "/password" ).toString(); + } + + private String base64(String value) + { + return UTF8.decode( Base64.encode( value ) ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/security/SecurityRulesIT.java b/community/server/src/test/java/org/neo4j/server/rest/security/SecurityRulesIT.java new file mode 100644 index 0000000000000..cfcbadca00c76 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/security/SecurityRulesIT.java @@ -0,0 +1,295 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.security; + +import java.net.URI; +import javax.ws.rs.core.MediaType; + +import org.dummy.web.service.DummyThirdPartyWebService; +import org.junit.After; +import org.junit.Rule; +import org.junit.Test; + +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.CommunityNeoServer; +import org.neo4j.server.helpers.CommunityServerBuilder; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.JaxRsResponse; +import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.test.TestData; +import org.neo4j.test.TestData.Title; +import org.neo4j.test.server.ExclusiveServerTestBase; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; + +public class SecurityRulesIT extends ExclusiveServerTestBase +{ + private CommunityNeoServer server; + + private FunctionalTestHelper functionalTestHelper; + + @Rule + public TestData gen = TestData.producedThrough( RESTDocsGenerator.PRODUCER ); + + @After + public void stopServer() + { + if ( server != null ) + { + server.stop(); + } + } + + @Test + @Title( "Enforcing Server Authorization Rules" ) + @Documented( "In this example, a (dummy) failing security rule is registered to deny\n" + + "access to all URIs to the server by listing the rules class in\n" + + "'neo4j.conf':\n" + + "\n" + + "@@config\n" + + "\n" + + "with the rule source code of:\n" + + "\n" + + "@@failingRule\n" + + "\n" + + "With this rule registered, any access to the server will be\n" + + "denied. In a production-quality implementation the rule\n" + + "will likely lookup credentials/claims in a 3rd-party\n" + + "directory service (e.g. LDAP) or in a local database of\n" + + "authorized users." ) + public void should401WithBasicChallengeWhenASecurityRuleFails() + throws Exception + { + server = CommunityServerBuilder.server().withDefaultDatabaseTuning().withSecurityRules( + PermanentlyFailingSecurityRule.class.getCanonicalName() ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + gen.get().addSnippet( + "config", + "\n[source,properties]\n----\ndbms.security.http_authorization_classes=my.rules" + + ".PermanentlyFailingSecurityRule\n----\n" ); + gen.get().addTestSourceSnippets( PermanentlyFailingSecurityRule.class, + "failingRule" ); + functionalTestHelper = new FunctionalTestHelper( server ); + gen.get().setSection( "ops" ); + JaxRsResponse response = gen.get().expectedStatus( 401 ).expectedHeader( + "WWW-Authenticate" ).post( functionalTestHelper.nodeUri() ).response(); + + assertThat( response.getHeaders().getFirst( "WWW-Authenticate" ), + containsString( "Basic realm=\"" + + PermanentlyFailingSecurityRule.REALM + "\"" ) ); + } + + @Test + public void should401WithBasicChallengeIfAnyOneOfTheRulesFails() + throws Exception + { + server = CommunityServerBuilder.server().withDefaultDatabaseTuning().withSecurityRules( + PermanentlyFailingSecurityRule.class.getCanonicalName(), + PermanentlyPassingSecurityRule.class.getCanonicalName() ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + functionalTestHelper = new FunctionalTestHelper( server ); + + JaxRsResponse response = gen.get().expectedStatus( 401 ).expectedHeader( + "WWW-Authenticate" ).post( functionalTestHelper.nodeUri() ).response(); + + assertThat( response.getHeaders().getFirst( "WWW-Authenticate" ), + containsString( "Basic realm=\"" + + PermanentlyFailingSecurityRule.REALM + "\"" ) ); + } + + @Test + public void shouldInvokeAllSecurityRules() throws Exception + { + // given + server = CommunityServerBuilder.server().withDefaultDatabaseTuning().withSecurityRules( + NoAccessToDatabaseSecurityRule.class.getCanonicalName()) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + functionalTestHelper = new FunctionalTestHelper( server ); + + // when + gen.get().expectedStatus( 401 ).get( functionalTestHelper.dataUri() ).response(); + + // then + assertTrue( NoAccessToDatabaseSecurityRule.wasInvoked() ); + } + + @Test + public void shouldRespondWith201IfAllTheRulesPassWhenCreatingANode() + throws Exception + { + server = CommunityServerBuilder.server().withDefaultDatabaseTuning().withSecurityRules( + PermanentlyPassingSecurityRule.class.getCanonicalName() ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + functionalTestHelper = new FunctionalTestHelper( server ); + + gen.get().expectedStatus( 201 ).expectedHeader( "Location" ).post( + functionalTestHelper.nodeUri() ).response(); + } + + @Test + @Title( "Using Wildcards to Target Security Rules" ) + @Documented( "In this example, a security rule is registered to deny\n" + + "access to all URIs to the server by listing the rule(s) class(es) in\n" + + "'neo4j.conf'.\n" + + "In this case, the rule is registered\n" + + "using a wildcard URI path (where `*` characters can be used to signify\n" + + "any part of the path). For example `/users*` means the rule\n" + + "will be bound to any resources under the `/users` root path. Similarly\n" + + "`/users*type*` will bind the rule to resources matching\n" + + "URIs like `/users/fred/type/premium`.\n" + + "\n" + + "@@config\n" + + "\n" + + "with the rule source code of:\n" + + "\n" + + "@@failingRuleWithWildcardPath\n" + + "\n" + + "With this rule registered, any access to URIs under /protected/ will be\n" + + "denied by the server. Using wildcards allows flexible targeting of security rules to\n" + + "arbitrary parts of the server's API, including any unmanaged extensions or managed\n" + + "plugins that have been registered." ) + public void aSimpleWildcardUriPathShould401OnAccessToProtectedSubPath() + throws Exception + { + String mountPoint = "/protected/tree/starts/here" + DummyThirdPartyWebService.DUMMY_WEB_SERVICE_MOUNT_POINT; + server = CommunityServerBuilder.server().withDefaultDatabaseTuning() + .withThirdPartyJaxRsPackage( "org.dummy.web.service", + mountPoint ) + .withSecurityRules( + PermanentlyFailingSecurityRuleWithWildcardPath.class.getCanonicalName() ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + + gen.get() + .addSnippet( + "config", + "\n[source,properties]\n----\ndbms.security.http_authorization_classes=my.rules" + + ".PermanentlyFailingSecurityRuleWithWildcardPath\n----\n" ); + + gen.get().addTestSourceSnippets( PermanentlyFailingSecurityRuleWithWildcardPath.class, + "failingRuleWithWildcardPath" ); + + gen.get().setSection("ops"); + + functionalTestHelper = new FunctionalTestHelper( server ); + + JaxRsResponse clientResponse = gen.get() + .expectedStatus( 401 ) + .expectedType( MediaType.APPLICATION_JSON_TYPE ) + .expectedHeader( "WWW-Authenticate" ) + .get( trimTrailingSlash( functionalTestHelper.baseUri() ) + + mountPoint + "/more/stuff" ) + .response(); + + assertEquals(401, clientResponse.getStatus()); + } + + @Test + @Title( "Using Complex Wildcards to Target Security Rules" ) + @Documented( "In this example, a security rule is registered to deny\n" + + "access to all URIs matching a complex pattern.\n" + + "The config looks like this:\n" + + "\n" + + "@@config\n" + + "\n" + + "with the rule source code of:\n" + + "\n" + + "@@failingRuleWithComplexWildcardPath" ) + public void aComplexWildcardUriPathShould401OnAccessToProtectedSubPath() + throws Exception + { + String mountPoint = "/protected/wildcard_replacement/x/y/z/something/else/more_wildcard_replacement/a/b/c" + + "/final/bit"; + server = CommunityServerBuilder.server().withDefaultDatabaseTuning() + .withThirdPartyJaxRsPackage( "org.dummy.web.service", + mountPoint ) + .withSecurityRules( + PermanentlyFailingSecurityRuleWithComplexWildcardPath.class.getCanonicalName() ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + gen.get().addSnippet( + "config", + "\n[source,properties]\n----\ndbms.security.http_authorization_classes=my.rules" + + ".PermanentlyFailingSecurityRuleWithComplexWildcardPath\n----\n"); + gen.get().addTestSourceSnippets( PermanentlyFailingSecurityRuleWithComplexWildcardPath.class, + "failingRuleWithComplexWildcardPath" ); + gen.get().setSection( "ops" ); + + functionalTestHelper = new FunctionalTestHelper( server ); + + JaxRsResponse clientResponse = gen.get() + .expectedStatus( 401 ) + .expectedType( MediaType.APPLICATION_JSON_TYPE ) + .expectedHeader( "WWW-Authenticate" ) + .get( trimTrailingSlash( functionalTestHelper.baseUri() ) + + mountPoint + "/more/stuff" ) + .response(); + + assertEquals( 401, clientResponse.getStatus() ); + } + + + @Test + public void should403WhenAuthenticatedButForbidden() + throws Exception + { + server = CommunityServerBuilder.server().withDefaultDatabaseTuning().withSecurityRules( + PermanentlyForbiddenSecurityRule.class.getCanonicalName(), + PermanentlyPassingSecurityRule.class.getCanonicalName() ) + .usingDataDir( folder.directory( name.getMethodName() ).getAbsolutePath() ) + .build(); + server.start(); + functionalTestHelper = new FunctionalTestHelper( server ); + + JaxRsResponse clientResponse = gen.get() + .expectedStatus(403) + .expectedType(MediaType.APPLICATION_JSON_TYPE) + .get(trimTrailingSlash(functionalTestHelper.baseUri())) + .response(); + + assertEquals(403, clientResponse.getStatus()); + } + + private String trimTrailingSlash( URI uri ) + { + String result = uri.toString(); + if ( result.endsWith( "/" ) ) + { + return result.substring( 0, result.length() - 1 ); + } + else + { + return result; + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/security/UsersIT.java b/community/server/src/test/java/org/neo4j/server/rest/security/UsersIT.java new file mode 100644 index 0000000000000..e9c1df25a4ee2 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/security/UsersIT.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.security; + +import java.io.IOException; +import javax.ws.rs.core.HttpHeaders; + +import com.sun.jersey.core.util.Base64; +import org.codehaus.jackson.JsonNode; +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.CommunityNeoServer; +import org.neo4j.server.helpers.CommunityServerBuilder; +import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.string.UTF8; +import org.neo4j.test.TestData; +import org.neo4j.test.server.ExclusiveServerTestBase; +import org.neo4j.test.server.HTTP; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +public class UsersIT extends ExclusiveServerTestBase +{ + public @Rule TestData gen = TestData.producedThrough( RESTDocsGenerator.PRODUCER ); + private CommunityNeoServer server; + + @Before + public void setUp() + { + gen.get().setSection( "dev/rest-api" ); + } + + @Test + @Documented( "User status\n" + + "\n" + + "Given that you know the current password, you can ask the server for the user status." ) + public void user_status() throws JsonParseException, IOException + { + // Given + startServerWithConfiguredUser(); + + // Document + RESTDocsGenerator.ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .withHeader( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ) + .get( userURL( "neo4j" ) ); + + // Then + JsonNode data = JsonHelper.jsonNode( response.entity() ); + assertThat( data.get( "username" ).asText(), equalTo( "neo4j" ) ); + assertThat( data.get( "password_change_required" ).asBoolean(), equalTo( false ) ); + assertThat( data.get( "password_change" ).asText(), equalTo( passwordURL( "neo4j" ) ) ); + } + + @Test + @Documented( "User status on first access\n" + + "\n" + + "On first access, and using the default password, the user status will indicate that the users password requires changing." ) + public void user_status_first_access() throws JsonParseException, IOException + { + // Given + startServer( true ); + + // Document + RESTDocsGenerator.ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .withHeader( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "neo4j" ) ) + .get( userURL( "neo4j" ) ); + + // Then + JsonNode data = JsonHelper.jsonNode( response.entity() ); + assertThat( data.get( "username" ).asText(), equalTo( "neo4j" ) ); + assertThat( data.get( "password_change_required" ).asBoolean(), equalTo( true ) ); + assertThat( data.get( "password_change" ).asText(), equalTo( passwordURL( "neo4j" ) ) ); + } + + @Test + @Documented( "Changing the user password\n" + + "\n" + + "Given that you know the current password, you can ask the server to change a users password. You can choose any\n" + + "password you like, as long as it is different from the current password." ) + public void change_password() throws JsonParseException, IOException + { + // Given + startServer( true ); + + // Document + RESTDocsGenerator.ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .withHeader( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "neo4j" ) ) + .payload( quotedJson( "{'password':'secret'}" ) ) + .post( server.baseUri().resolve( "/user/neo4j/password" ).toString() ); + + // Then the new password should work + assertEquals( 200, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ).GET( dataURL() ).status() ); + + // Then the old password should not be invalid + assertEquals( 401, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "neo4j" ) ).POST( dataURL() ).status() ); + } + + @Test + public void cantChangeToCurrentPassword() throws Exception + { + // Given + startServer( true ); + + // When + HTTP.Response res = HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "neo4j" ) ).POST( + server.baseUri().resolve( "/user/neo4j/password" ).toString(), + HTTP.RawPayload.quotedJson( "{'password':'neo4j'}" ) ); + + // Then + assertThat( res.status(), equalTo( 422 ) ); + } + + @After + public void cleanup() + { + if(server != null) {server.stop();} + } + + public void startServer(boolean authEnabled) throws IOException + { + server = CommunityServerBuilder.server() + .withProperty( GraphDatabaseSettings.auth_enabled.name(), Boolean.toString( authEnabled ) ) + .build(); + server.start(); + } + + public void startServerWithConfiguredUser() throws IOException + { + startServer( true ); + // Set the password + HTTP.Response post = HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "neo4j" ) ).POST( + server.baseUri().resolve( "/user/neo4j/password" ).toString(), + HTTP.RawPayload.quotedJson( "{'password':'secret'}" ) + ); + assertEquals( 200, post.status() ); + } + + private String challengeResponse( String username, String password ) + { + return "Basic " + base64( username + ":" + password ); + } + + private String dataURL() + { + return server.baseUri().resolve( "db/data/" ).toString(); + } + + private String userURL( String username ) + { + return server.baseUri().resolve( "user/" + username ).toString(); + } + + private String passwordURL( String username ) + { + return server.baseUri().resolve( "user/" + username + "/password" ).toString(); + } + + private String base64(String value) + { + return UTF8.decode( Base64.encode( value ) ); + } + + private String quotedJson( String singleQuoted ) + { + return singleQuoted.replaceAll( "'", "\"" ); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingBatchOperationIT.java b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingBatchOperationIT.java new file mode 100644 index 0000000000000..6ee0334346ad7 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingBatchOperationIT.java @@ -0,0 +1,658 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.streaming; + +import com.sun.jersey.api.client.ClientHandlerException; +import com.sun.jersey.api.client.UniformInterfaceException; +import org.json.JSONException; +import org.junit.Test; + +import java.util.List; +import java.util.Map; + +import org.neo4j.graphdb.Neo4jMatchers; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.helpers.collection.Iterables; +import org.neo4j.server.rest.AbstractRestFunctionalTestBase; +import org.neo4j.server.rest.JaxRsResponse; +import org.neo4j.server.rest.PrettyJSON; +import org.neo4j.server.rest.RestRequest; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.repr.BadInputException; +import org.neo4j.server.rest.repr.StreamingFormat; +import org.neo4j.test.GraphDescription.Graph; + +import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; +import static org.neo4j.graphdb.Neo4jMatchers.inTx; + +public class StreamingBatchOperationIT extends AbstractRestFunctionalTestBase +{ + + /** + * By specifying an extended header attribute in the HTTP request, + * the server will stream the results back as soon as they are processed on the server side + * instead of constructing a full response when all entities are processed. + */ + @SuppressWarnings( "unchecked" ) + @Test + @Graph("Joe knows John") + public void execute_multiple_operations_in_batch_streaming() throws Exception { + long idJoe = data.get().get( "Joe" ).getId(); + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("PUT") + .key("to") .value("/node/" + idJoe + "/properties") + .key("body") + .object() + .key("age").value(1) + .endObject() + .key("id") .value(0) + .endObject() + .object() + .key("method") .value("GET") + .key("to") .value("/node/" + idJoe) + .key("id") .value(1) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age").value(1) + .endObject() + .key("id") .value(2) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age").value(1) + .endObject() + .key("id") .value(3) + .endObject() + .endArray().toString(); + + + String entity = gen.get() + .expectedType( APPLICATION_JSON_TYPE ) + .withHeader(StreamingFormat.STREAM_HEADER,"true") + .payload(jsonString) + .expectedStatus(200) + .post( batchUri() ).entity(); + List> results = JsonHelper.jsonToList(entity); + + assertEquals(4, results.size()); + + Map putResult = results.get(0); + Map getResult = results.get(1); + Map firstPostResult = results.get(2); + Map secondPostResult = results.get(3); + + // Ids should be ok + assertEquals(0, putResult.get("id")); + assertEquals(2, firstPostResult.get("id")); + assertEquals(3, secondPostResult.get("id")); + + // Should contain "from" + assertEquals("/node/"+idJoe+"/properties", putResult.get("from")); + assertEquals("/node/"+idJoe, getResult.get("from")); + assertEquals("/node", firstPostResult.get("from")); + assertEquals("/node", secondPostResult.get("from")); + + // Post should contain location + assertTrue(((String) firstPostResult.get("location")).length() > 0); + assertTrue(((String) secondPostResult.get("location")).length() > 0); + + // Should have created by the first PUT request + Map body = (Map) getResult.get("body"); + assertEquals(1, ((Map) body.get("data")).get("age")); + + + } + + /** + * The batch operation API allows you to refer to the URI returned from a + * created resource in subsequent job descriptions, within the same batch + * call. + * + * Use the +{[JOB ID]}+ special syntax to inject URIs from created resources + * into JSON strings in subsequent job descriptions. + */ + @Test + public void refer_to_items_created_earlier_in_the_same_batch_job_streaming() throws Exception { + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("id") .value(0) + .key("body") + .object() + .key("name").value("bob") + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("id") .value(1) + .key("body") + .object() + .key("age").value(12) + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("{0}/relationships") + .key("id") .value(3) + .key("body") + .object() + .key("to").value("{1}") + .key("data") + .object() + .key("since").value("2010") + .endObject() + .key("type").value("KNOWS") + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/index/relationship/my_rels") + .key("id") .value(4) + .key("body") + .object() + .key("key").value("since") + .key("value").value("2010") + .key("uri").value("{3}") + .endObject() + .endObject() + .endArray().toString(); + + String entity = gen.get() + .expectedType(APPLICATION_JSON_TYPE) + .withHeader(StreamingFormat.STREAM_HEADER, "true") + .expectedStatus(200) + .payload( jsonString ) + .post( batchUri() ) + .entity(); + + List> results = JsonHelper.jsonToList(entity); + + assertEquals(4, results.size()); + +// String rels = gen.get() +// .expectedStatus( 200 ).get( getRelationshipIndexUri( "my_rels", "since", "2010")).entity(); +// assertEquals(1, JsonHelper.jsonToList( rels ).size()); + } + + @Test + public void shouldGetLocationHeadersWhenCreatingThings() throws Exception { + + long originalNodeCount = countNodes(); + + final String jsonString = new PrettyJSON() + .array() + .object() + .key("method").value("POST") + .key("to").value("/node") + .key("body") + .object() + .key("age").value(1) + .endObject() + .endObject() + .endArray().toString(); + + JaxRsResponse response = RestRequest.req() + .accept(APPLICATION_JSON_TYPE) + .header(StreamingFormat.STREAM_HEADER, "true") + .post(batchUri(), jsonString); + + assertEquals(200, response.getStatus()); + + final String entity = response.getEntity(); + List> results = JsonHelper.jsonToList(entity); + + assertEquals(originalNodeCount + 1, countNodes()); + assertEquals(1, results.size()); + + Map result = results.get(0); + assertTrue(((String) result.get("location")).length() > 0); + } + + private String batchUri() + { + return getDataUri()+"batch"; + + } + + @Test + public void shouldForwardUnderlyingErrors() throws Exception { + + JaxRsResponse response = RestRequest.req().accept(APPLICATION_JSON_TYPE).header(StreamingFormat.STREAM_HEADER,"true") + + .post(batchUri(), new PrettyJSON() + .array() + .object() + .key("method").value("POST") + .key("to").value("/node") + .key("body") + .object() + .key("age") + .array() + .value(true) + .value("hello") + .endArray() + .endObject() + .endObject() + .endArray() + .toString()); + Map res = singleResult( response, 0 ); + + assertTrue(((String)res.get("message")).startsWith("Invalid JSON array in POST body")); + assertEquals( 400, res.get( "status" ) ); + } + + private Map singleResult( JaxRsResponse response, int i ) throws JsonParseException + { + return JsonHelper.jsonToList( response.getEntity() ).get( i ); + } + + @Test + public void shouldRollbackAllWhenGivenIncorrectRequest() throws JsonParseException, ClientHandlerException, + UniformInterfaceException, JSONException { + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age").value("1") + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .array() + .value("a_list") + .value("this_makes_no_sense") + .endArray() + .endObject() + .endArray() + .toString(); + + long originalNodeCount = countNodes(); + + JaxRsResponse response = RestRequest.req() + .accept(APPLICATION_JSON_TYPE) + .header(StreamingFormat.STREAM_HEADER, "true") + .post(batchUri(), jsonString); + assertEquals(200, response.getStatus()); + + // Message of the ClassCastException differs in Oracle JDK [typeX cannot be cast to typeY] + // and IBM JDK [typeX incompatible with typeY]. That is why we check parts of the message and exception class. + Map body = (Map) singleResult( response, 1 ).get( "body" ); + assertEquals( BadInputException.class.getSimpleName(), body.get( "exception" ) ); + assertThat( body.get( "message" ), containsString( "java.util.ArrayList" ) ); + assertThat( body.get( "message" ), containsString( "java.util.Map" ) ); + assertEquals(400, singleResult( response, 1 ).get( "status" )); + + assertEquals(originalNodeCount, countNodes()); + } + + @Test + @SuppressWarnings("unchecked") + public void shouldHandleUnicodeGetCorrectly() throws Exception { + String asianText = "\u4f8b\u5b50"; + String germanText = "öäüÖÄÜß"; + + String complicatedString = asianText + germanText; + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") .object() + .key(complicatedString).value(complicatedString) + .endObject() + .endObject() + .endArray() + .toString(); + + String entity = gen.get() + .expectedType( APPLICATION_JSON_TYPE ) + .withHeader( StreamingFormat.STREAM_HEADER,"true" ) + .expectedStatus(200) + .payload( jsonString ) + .post( batchUri() ) + .entity(); + + // Pull out the property value from the depths of the response + Map response = (Map) JsonHelper.jsonToList(entity).get(0).get("body"); + String returnedValue = (String)((Map)response.get("data")).get(complicatedString); + + // Ensure nothing was borked. + assertThat(returnedValue, is(complicatedString)); + } + + @Test + @Graph("Peter likes Jazz") + public void shouldHandleEscapedStrings() throws ClientHandlerException, + UniformInterfaceException, JSONException, JsonParseException { + String string = "Jazz"; + Node gnode = getNode( string ); + assertThat( gnode, inTx(graphdb(), Neo4jMatchers.hasProperty( "name" ).withValue(string)) ); + + String name = "string\\ and \"test\""; + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("PUT") + .key("to") .value("/node/"+gnode.getId()+"/properties") + .key("body") + .object() + .key("name").value(name) + .endObject() + .endObject() + .endArray() + .toString(); + gen.get() + .expectedType(APPLICATION_JSON_TYPE) + .withHeader(StreamingFormat.STREAM_HEADER, "true") + .expectedStatus( 200 ) + .payload( jsonString ) + .post( batchUri() ) + .entity(); + + jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("GET") + .key("to") .value("/node/"+gnode.getId()+"/properties/name") + .endObject() + .endArray() + .toString(); + String entity = gen.get() + .expectedStatus( 200 ) + .payload( jsonString ) + .post( batchUri() ) + .entity(); + + List> results = JsonHelper.jsonToList(entity); + assertEquals(results.get(0).get("body"), name); + } + + @Test + public void shouldRollbackAllWhenInsertingIllegalData() throws JsonParseException, ClientHandlerException, + UniformInterfaceException, JSONException { + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age").value(1) + .endObject() + .endObject() + + .object() + .key("method").value("POST") + .key("to").value("/node") + .key("body") + .object() + .key("age") + .object() + .key("age").value(1) + .endObject() + .endObject() + .endObject() + + .endArray().toString(); + + long originalNodeCount = countNodes(); + + JaxRsResponse response = RestRequest.req() + .accept(APPLICATION_JSON_TYPE) + .header(StreamingFormat.STREAM_HEADER, "true") + .post(batchUri(), jsonString); + assertEquals(200, response.getStatus()); + assertEquals(400, singleResult( response,1 ).get("status")); + assertEquals(originalNodeCount, countNodes()); + + } + + @Test + public void shouldRollbackAllOnSingle404() throws JsonParseException, ClientHandlerException, + UniformInterfaceException, JSONException { + + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("body") + .object() + .key("age").value(1) + .endObject() + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("www.google.com") + .endObject() + + .endArray().toString(); + + long originalNodeCount = countNodes(); + + JaxRsResponse response = RestRequest.req() + .accept( APPLICATION_JSON_TYPE ) + .header(StreamingFormat.STREAM_HEADER, "true") + .post(batchUri(), jsonString); + assertEquals(200, response.getStatus()); + assertEquals(404, singleResult( response ,1 ).get("status")); + assertEquals(originalNodeCount, countNodes()); + + } + + @Test + public void shouldBeAbleToReferToUniquelyCreatedEntities() throws Exception { + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/index/node/Cultures?unique") + .key("body") + .object() + .key("key").value("ID") + .key("value").value("fra") + .key("properties") + .object() + .key("ID").value("fra") + .endObject() + .endObject() + .key("id") .value(0) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/node") + .key("id") .value(1) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("{1}/relationships") + .key("body") + .object() + .key("to").value("{0}") + .key("type").value("has") + .endObject() + .key("id") .value(2) + .endObject() + .endArray().toString(); + + JaxRsResponse response = RestRequest.req() + .accept( APPLICATION_JSON_TYPE ) + .header(StreamingFormat.STREAM_HEADER, "true") + .post(batchUri(), jsonString); + + assertEquals(200, response.getStatus()); + + } + + // It has to be possible to create relationships among created and not-created nodes + // in batch operation. Tests the fix for issue #690. + @Test + public void shouldBeAbleToReferToNotCreatedUniqueEntities() throws Exception { + String jsonString = new PrettyJSON() + .array() + .object() + .key("method") .value("POST") + .key("to") .value("/index/node/Cultures?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("tobias") + .key("properties") + .object() + .key("name").value("Tobias Tester") + .endObject() + .endObject() + .key("id") .value(0) + .endObject() + .object() // Creates Andres, hence 201 Create + .key("method") .value("POST") + .key("to") .value("/index/node/Cultures?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("andres") + .key("properties") + .object() + .key("name").value("Andres Tester") + .endObject() + .endObject() + .key("id") .value(1) + .endObject() + .object() // Duplicated to ID.1, hence 200 OK + .key("method") .value("POST") + .key("to") .value("/index/node/Cultures?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("andres") + .key("properties") + .object() + .key("name").value("Andres Tester") + .endObject() + .endObject() + .key("id") .value(2) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/index/relationship/my_rels/?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("tobias-andres") + .key("start").value("{0}") + .key("end").value("{1}") + .key("type").value("FRIENDS") + .endObject() + .key("id") .value(3) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/index/relationship/my_rels/?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("andres-tobias") + .key("start").value("{2}") // Not-created entity here + .key("end").value("{0}") + .key("type").value("FRIENDS") + .endObject() + .key("id") .value(4) + .endObject() + .object() + .key("method") .value("POST") + .key("to") .value("/index/relationship/my_rels/?unique") + .key("body") + .object() + .key("key").value("name") + .key("value").value("andres-tobias") + .key("start").value("{1}") // Relationship should not be created + .key("end").value("{0}") + .key("type").value("FRIENDS") + .endObject() + .key("id") .value(5) + .endObject() + .endArray().toString(); + + JaxRsResponse response = RestRequest.req() + .accept( APPLICATION_JSON_TYPE ) + .header(StreamingFormat.STREAM_HEADER, "true") + .post(batchUri(), jsonString); + + assertEquals(200, response.getStatus()); + final String entity = response.getEntity(); + List> results = JsonHelper.jsonToList(entity); + assertEquals(6, results.size()); + Map andresResult1 = results.get(1); + Map andresResult2 = results.get(2); + Map secondRelationship = results.get(4); + Map thirdRelationship = results.get(5); + + // Same people + Map body1 = (Map) andresResult1.get("body"); + Map body2 = (Map) andresResult2.get("body"); + assertEquals(body1.get("id"), body2.get("id")); + // Same relationship + body1 = (Map) secondRelationship.get("body"); + body2 = (Map) thirdRelationship.get("body"); + assertEquals(body1.get("self"), body2.get("self")); + // Created for {2} {0} + assertTrue(((String) secondRelationship.get("location")).length() > 0); + // {2} = {1} = Andres + body1 = (Map) secondRelationship.get("body"); + body2 = (Map) andresResult1.get("body"); + assertEquals(body1.get("start"), body2.get("self")); + + } + private long countNodes() + { + try ( Transaction transaction = graphdb().beginTx() ) + { + return Iterables.count( (Iterable) graphdb().getAllNodes() ); + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingCypherIT.java b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingCypherIT.java new file mode 100644 index 0000000000000..c275a5f87d657 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingCypherIT.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.streaming; + +import org.neo4j.server.rest.CypherIT; +import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; + +public class StreamingCypherIT extends CypherIT +{ + @Override + public RESTDocsGenerator gen() { + return super.gen().withHeader(StreamingJsonFormat.STREAM_HEADER, "true"); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingIndexNodeIT.java b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingIndexNodeIT.java new file mode 100644 index 0000000000000..fc46be41d43cc --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingIndexNodeIT.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.streaming; + +import org.neo4j.server.rest.IndexNodeIT; +import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; + +public class StreamingIndexNodeIT extends IndexNodeIT +{ + @Override + public RESTDocsGenerator gen() { + return super.gen().withHeader(StreamingJsonFormat.STREAM_HEADER, "true"); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingPathsIT.java b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingPathsIT.java new file mode 100644 index 0000000000000..110b439f9bf12 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingPathsIT.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.streaming; + +import org.neo4j.server.rest.PathsIT; +import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; + +public class StreamingPathsIT extends PathsIT +{ + @Override + public RESTDocsGenerator gen() { + return super.gen().withHeader(StreamingJsonFormat.STREAM_HEADER, "true"); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingRelationshipIT.java b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingRelationshipIT.java new file mode 100644 index 0000000000000..c93596ba41e64 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingRelationshipIT.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.streaming; + +import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.server.rest.RelationshipIT; +import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; + +public class StreamingRelationshipIT extends RelationshipIT +{ + @Override + public RESTDocsGenerator gen() { + return super.gen().withHeader(StreamingJsonFormat.STREAM_HEADER, "true"); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingTraverserIT.java b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingTraverserIT.java new file mode 100644 index 0000000000000..cfc505edc5034 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingTraverserIT.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.streaming; + +import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.server.rest.TraverserIT; +import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; + +public class StreamingTraverserIT extends TraverserIT +{ + @Override + public RESTDocsGenerator gen() { + return super.gen().withHeader(StreamingJsonFormat.STREAM_HEADER, "true"); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/transactional/CypherQueriesIT.java b/community/server/src/test/java/org/neo4j/server/rest/transactional/CypherQueriesIT.java index 53edee902d53f..70a37ea4c8ebd 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/transactional/CypherQueriesIT.java +++ b/community/server/src/test/java/org/neo4j/server/rest/transactional/CypherQueriesIT.java @@ -19,15 +19,17 @@ */ package org.neo4j.server.rest.transactional; -import org.junit.Test; - import java.util.Iterator; import java.util.List; import java.util.Map; +import org.junit.Test; + import org.neo4j.server.rest.AbstractRestFunctionalTestBase; import org.neo4j.server.rest.domain.JsonParseException; + import static org.junit.Assert.assertFalse; + import static org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; import static org.neo4j.server.rest.domain.JsonHelper.jsonToMap; @@ -39,7 +41,6 @@ public void runningWithGeometryTypes() throws JsonParseException { // Document ResponseEntity response = gen.get() - .noGraph() .expectedStatus( 200 ) .payload( quotedJson( "{ 'statements': [ { 'statement': 'RETURN point({latitude:1.2,longitude:2.3}) as point' } ] }" ) ) diff --git a/community/server/src/test/java/org/neo4j/server/rest/transactional/TransactionTest.java b/community/server/src/test/java/org/neo4j/server/rest/transactional/TransactionTest.java new file mode 100644 index 0000000000000..adaa83e5987bb --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/rest/transactional/TransactionTest.java @@ -0,0 +1,418 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest.transactional; + +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.junit.Test; + +import org.neo4j.kernel.api.exceptions.Status; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.server.rest.AbstractRestFunctionalTestBase; +import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.server.rest.repr.util.RFC1123; +import org.neo4j.test.server.HTTP; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsMapContaining.hasKey; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import static org.neo4j.helpers.collection.Iterators.iterator; +import static org.neo4j.server.rest.domain.JsonHelper.jsonToMap; +import static org.neo4j.test.server.HTTP.GET; +import static org.neo4j.test.server.HTTP.POST; + +public class TransactionTest extends AbstractRestFunctionalTestBase +{ + @Test + @Documented( "Begin a transaction\n" + + "\n" + + "You begin a new transaction by posting zero or more Cypher statements\n" + + "to the transaction endpoint. The server will respond with the result of\n" + + "your statements, as well as the location of your open transaction." ) + public void begin_a_transaction() throws JsonParseException + { + // Document + ResponseEntity response = gen.get() + .expectedStatus( 201 ) + .payload( quotedJson( "{ 'statements': [ { 'statement': 'CREATE (n {props}) RETURN n', " + + "'parameters': { 'props': { 'name': 'My Node' } } } ] }" ) ) + .post( getDataUri() + "transaction" ); + + // Then + Map result = jsonToMap( response.entity() ); + assertNoErrors( result ); + Map node = resultCell( result, 0, 0 ); + assertThat( (String) node.get( "name" ), equalTo( "My Node" ) ); + } + + @Test + @Documented( "Execute statements in an open transaction\n" + + "\n" + + "Given that you have an open transaction, you can make a number of requests, each of which executes additional\n" + + "statements, and keeps the transaction open by resetting the transaction timeout." ) + public void execute_statements_in_an_open_transaction() throws JsonParseException + { + // Given + String location = POST( getDataUri() + "transaction" ).location(); + + // Document + ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .payload( quotedJson( "{ 'statements': [ { 'statement': 'CREATE (n) RETURN n' } ] }" ) ) + .post( location ); + + // Then + Map result = jsonToMap( response.entity() ); + assertThat(result, hasKey( "transaction" )); + assertNoErrors( result ); + } + + @Test + @Documented( "Execute statements in an open transaction in REST format for the return.\n" + + "\n" + + "Given that you have an open transaction, you can make a number of requests, each of which executes additional\n" + + "statements, and keeps the transaction open by resetting the transaction timeout. Specifying the `REST` format will\n" + + "give back full Neo4j Rest API representations of the Neo4j Nodes, Relationships and Paths, if returned." ) + public void execute_statements_in_an_open_transaction_using_REST() throws JsonParseException + { + // Given + String location = POST( getDataUri() + "transaction" ).location(); + + // Document + ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .payload( quotedJson( "{ 'statements': [ { 'statement': 'CREATE (n) RETURN n','resultDataContents':['REST'] } ] }" ) ) + .post( location ); + + // Then + Map result = jsonToMap( response.entity() ); + ArrayList rest = (ArrayList) ((Map)((ArrayList)((Map)((ArrayList)result.get("results")).get(0)) .get("data")).get(0)).get("rest"); + String selfUri = ((String)((Map)rest.get(0)).get("self")); + assertTrue(selfUri.startsWith(getDatabaseUri())); + assertNoErrors( result ); + } + + @Test + @Documented( "Reset transaction timeout of an open transaction\n" + + "\n" + + "Every orphaned transaction is automatically expired after a period of inactivity. This may be prevented\n" + + "by resetting the transaction timeout.\n" + + "\n" + + "The timeout may be reset by sending a keep-alive request to the server that executes an empty list of statements.\n" + + "This request will reset the transaction timeout and return the new time at which the transaction will\n" + + "expire as an RFC1123 formatted timestamp value in the ``transaction'' section of the response." ) + public void reset_transaction_timeout_of_an_open_transaction() + throws JsonParseException, ParseException, InterruptedException + { + // Given + HTTP.Response initialResponse = POST( getDataUri() + "transaction" ); + String location = initialResponse.location(); + long initialExpirationTime = expirationTime( jsonToMap( initialResponse.rawContent() ) ); + + // This generous wait time is necessary to compensate for limited resolution of RFC 1123 timestamps + // and the fact that the system clock is allowed to run "backwards" between threads + // (cf. http://stackoverflow.com/questions/2978598) + // + Thread.sleep( 3000 ); + + // Document + ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .payload( quotedJson( "{ 'statements': [ ] }" ) ) + .post( location ); + + + // Then + Map result = jsonToMap( response.entity() ); + assertNoErrors( result ); + long newExpirationTime = expirationTime( result ); + + assertTrue( "Expiration time was not increased", newExpirationTime > initialExpirationTime ); + } + + @Test + @Documented( "Commit an open transaction\n" + + "\n" + + "Given you have an open transaction, you can send a commit request. Optionally, you submit additional statements\n" + + "along with the request that will be executed before committing the transaction." ) + public void commit_an_open_transaction() throws JsonParseException + { + // Given + String location = POST( getDataUri() + "transaction" ).location(); + + // Document + ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .payload( quotedJson( "{ 'statements': [ { 'statement': 'CREATE (n) RETURN id(n)' } ] }" ) ) + .post( location + "/commit" ); + + // Then + Map result = jsonToMap( response.entity() ); + assertNoErrors( result ); + + Integer id = resultCell( result, 0, 0 ); + assertThat( GET( getNodeUri( id ) ).status(), is( 200 ) ); + } + + @Test + @Documented( "Begin and commit a transaction in one request\n" + + "\n" + + "If there is no need to keep a transaction open across multiple HTTP requests, you can begin a transaction,\n" + + "execute statements, and commit with just a single HTTP request." ) + public void begin_and_commit_a_transaction_in_one_request() throws JsonParseException + { + // Document + ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .payload( quotedJson( "{ 'statements': [ { 'statement': 'CREATE (n) RETURN id(n)' } ] }" ) ) + .post( getDataUri() + "transaction/commit" ); + + // Then + Map result = jsonToMap( response.entity() ); + assertNoErrors( result ); + + Integer id = resultCell( result, 0, 0 ); + assertThat( GET( getNodeUri( id ) ).status(), is( 200 ) ); + } + + @Test + @Documented( "Execute multiple statements\n" + + "\n" + + "You can send multiple Cypher statements in the same request.\n" + + "The response will contain the result of each statement." ) + public void execute_multiple_statements() throws JsonParseException + { + // Document + ResponseEntity response = gen.get().expectedStatus( 200 ) + .payload( quotedJson( "{ 'statements': [ { 'statement': 'CREATE (n) RETURN id(n)' }, " + + "{ 'statement': 'CREATE (n {props}) RETURN n', " + + "'parameters': { 'props': { 'name': 'My Node' } } } ] }" ) ) + .post( getDataUri() + "transaction/commit" ); + + // Then + Map result = jsonToMap( response.entity() ); + assertNoErrors( result ); + Integer id = resultCell( result, 0, 0 ); + assertThat( GET( getNodeUri( id ) ).status(), is( 200 ) ); + assertThat( response.entity(), containsString( "My Node" ) ); + } + + @Test + @Documented( "Return results in graph format\n" + + "\n" + + "If you want to understand the graph structure of nodes and relationships returned by your query,\n" + + "you can specify the \"graph\" results data format. For example, this is useful when you want to visualise the\n" + + "graph structure. The format collates all the nodes and relationships from all columns of the result,\n" + + "and also flattens collections of nodes and relationships, including paths." ) + public void return_results_in_graph_format() throws JsonParseException + { + // Document + ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .payload( quotedJson( "{'statements':[{'statement':" + + "'CREATE ( bike:Bike { weight: 10 } ) " + + "CREATE ( frontWheel:Wheel { spokes: 3 } ) " + + "CREATE ( backWheel:Wheel { spokes: 32 } ) " + + "CREATE p1 = (bike)-[:HAS { position: 1 } ]->(frontWheel) " + + "CREATE p2 = (bike)-[:HAS { position: 2 } ]->(backWheel) " + + "RETURN bike, p1, p2', " + + "'resultDataContents': ['row','graph']}] }" ) ) + .post( getDataUri() + "transaction/commit" ); + + // Then + Map result = jsonToMap( response.entity() ); + assertNoErrors( result ); + + Map> row = graphRow( result, 0 ); + assertEquals( 3, row.get( "nodes" ).size() ); + assertEquals( 2, row.get( "relationships" ).size() ); + } + + @Test + @Documented( "Rollback an open transaction\n" + + "\n" + + "Given that you have an open transaction, you can send a rollback request. The server will rollback the\n" + + "transaction. Any further statements trying to run in this transaction will fail immediately." ) + public void rollback_an_open_transaction() throws JsonParseException + { + // Given + HTTP.Response firstReq = POST( getDataUri() + "transaction", + HTTP.RawPayload.quotedJson( "{ 'statements': [ { 'statement': 'CREATE (n) RETURN id(n)' } ] }" ) ); + String location = firstReq.location(); + + // Document + ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .delete( location ); + + // Then + Map result = jsonToMap( response.entity() ); + assertNoErrors( result ); + + Integer id = resultCell( firstReq, 0, 0 ); + assertThat( GET( getNodeUri( id ) ).status(), is( 404 ) ); + } + + @Test + @Documented( "Handling errors\n" + + "\n" + + "The result of any request against the transaction endpoint is streamed back to the client.\n" + + "Therefore the server does not know whether the request will be successful or not when it sends the HTTP status\n" + + "code.\n" + + "\n" + + "Because of this, all requests against the transactional endpoint will return 200 or 201 status code, regardless\n" + + "of whether statements were successfully executed. At the end of the response payload, the server includes a list\n" + + "of errors that occurred while executing statements. If this list is empty, the request completed successfully.\n" + + "\n" + + "If any errors occur while executing statements, the server will roll back the transaction.\n" + + "\n" + + "In this example, we send the server an invalid statement to demonstrate error handling.\n" + + " \n" + + "For more information on the status codes, see <>." ) + public void handling_errors() throws JsonParseException + { + // Given + String location = POST( getDataUri() + "transaction" ).location(); + + // Document + ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .payload( quotedJson( "{ 'statements': [ { 'statement': 'This is not a valid Cypher Statement.' } ] }" ) ) + .post( location + "/commit" ); + + // Then + Map result = jsonToMap( response.entity() ); + assertErrors( result, Status.Statement.SyntaxError ); + } + + @Test + @Documented("Handling errors in an open transaction\n" + + "\n" + + "Whenever there is an error in a request the server will rollback the transaction.\n" + + "By inspecting the response for the presence/absence of the `transaction` key you can tell if the " + + "transaction is still open") + public void errors_in_open_transaction() throws JsonParseException + { + // Given + String location = POST( getDataUri() + "transaction" ).location(); + + // Document + ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .payload( quotedJson( "{ 'statements': [ { 'statement': 'This is not a valid Cypher Statement.' } ] }" ) ) + .post( location ); + + // Then + Map result = jsonToMap( response.entity() ); + assertThat(result, not(hasKey( "transaction" ))); + } + + @Test + @Documented( "Include query statistics\n" + + "\n" + + "By setting `includeStats` to `true` for a statement, query statistics will be returned for it." ) + public void include_query_statistics() throws JsonParseException + { + // Document + ResponseEntity response = gen.get() + .expectedStatus( 200 ) + .payload( quotedJson( + "{ 'statements': [ { 'statement': 'CREATE (n) RETURN id(n)', 'includeStats': true } ] }" ) ) + .post( getDataUri() + "transaction/commit" ); + + // Then + Map entity = jsonToMap( response.entity() ); + assertNoErrors( entity ); + Map firstResult = ((List>) entity.get( "results" )).get( 0 ); + + assertThat( firstResult, hasKey( "stats" ) ); + Map stats = (Map) firstResult.get( "stats" ); + assertThat( (Integer) stats.get( "nodes_created" ), equalTo( 1 ) ); + } + + + + private void assertNoErrors( Map response ) + { + assertErrors( response ); + } + + private void assertErrors( Map response, Status... expectedErrors ) + { + @SuppressWarnings("unchecked") + Iterator> errors = ((List>) response.get( "errors" )).iterator(); + Iterator expected = iterator( expectedErrors ); + + while ( expected.hasNext() ) + { + assertTrue( errors.hasNext() ); + assertThat( (String)errors.next().get( "code" ), equalTo( expected.next().code().serialize() ) ); + } + if ( errors.hasNext() ) + { + Map error = errors.next(); + fail( "Expected no more errors, but got " + error.get( "code" ) + " - '" + error.get( "message" ) + "'." ); + } + } + + private T resultCell( HTTP.Response response, int row, int column ) + { + return resultCell( response.>content(), row, column ); + } + + @SuppressWarnings("unchecked") + private T resultCell( Map response, int row, int column ) + { + Map result = ((List>) response.get( "results" )).get( 0 ); + List> data = (List>) result.get( "data" ); + return (T) data.get( row ).get( "row" ).get( column ); + } + + @SuppressWarnings("unchecked") + private Map> graphRow( Map response, int row ) + { + Map result = ((List>) response.get( "results" )).get( 0 ); + List> data = (List>) result.get( "data" ); + return (Map>) data.get( row ).get( "graph" ); + } + + private String quotedJson( String singleQuoted ) + { + return singleQuoted.replaceAll( "'", "\"" ); + } + + private long expirationTime( Map entity ) throws ParseException + { + String timestampString = (String) ( (Map) entity.get( "transaction" ) ).get( "expires" ); + return RFC1123.parseTimestamp( timestampString ).getTime(); + } +} diff --git a/community/server/src/test/java/org/neo4j/server/web/logging/HTTPLoggingIT.java b/community/server/src/test/java/org/neo4j/server/web/logging/HTTPLoggingIT.java new file mode 100644 index 0000000000000..d8c37a4c285c0 --- /dev/null +++ b/community/server/src/test/java/org/neo4j/server/web/logging/HTTPLoggingIT.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.web.logging; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import org.neo4j.function.ThrowingSupplier; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.server.NeoServer; +import org.neo4j.server.configuration.ServerSettings; +import org.neo4j.server.helpers.CommunityServerBuilder; +import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.server.rest.JaxRsResponse; +import org.neo4j.server.rest.RestRequest; +import org.neo4j.test.TargetDirectory; +import org.neo4j.test.server.ExclusiveServerTestBase; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertThat; + +import static org.neo4j.io.fs.FileUtils.readTextFile; +import static org.neo4j.test.Assert.assertEventually; + +public class HTTPLoggingIT extends ExclusiveServerTestBase +{ + @Rule public ExpectedException exception = ExpectedException.none(); + + @Test + public void givenExplicitlyDisabledServerLoggingConfigurationShouldNotLogAccesses() throws Exception + { + // given + File logDirectory = testDirectory.directory( + "givenExplicitlyDisabledServerLoggingConfigurationShouldNotLogAccesses-logdir" ); + FileUtils.forceMkdir( logDirectory ); + final File confDir = testDirectory.directory( + "givenExplicitlyDisabledServerLoggingConfigurationShouldNotLogAccesses-confdir" ); + FileUtils.forceMkdir( confDir ); + + NeoServer server = CommunityServerBuilder.server().withDefaultDatabaseTuning() + .withProperty( ServerSettings.http_logging_enabled.name(), "false" ) + .withProperty( GraphDatabaseSettings.logs_directory.name(), logDirectory.toString() ) + .usingDataDir( testDirectory.directory( + "givenExplicitlyDisabledServerLoggingConfigurationShouldNotLogAccesses-dbdir" + ).getAbsolutePath() ) + .build(); + try + { + server.start(); + FunctionalTestHelper functionalTestHelper = new FunctionalTestHelper( server ); + + // when + String query = "?implicitlyDisabled" + randomString(); + JaxRsResponse response = new RestRequest().get( functionalTestHelper.managementUri() + query ); + assertThat( response.getStatus(), is( 200 ) ); + response.close(); + + // then + File httpLog = new File( logDirectory, "http.log" ); + assertThat( httpLog.exists(), is( false ) ); + } + finally + { + server.stop(); + } + } + + @Test + public void givenExplicitlyEnabledServerLoggingConfigurationShouldLogAccess() throws Exception + { + // given + final File logDirectory = + testDirectory.directory( "givenExplicitlyEnabledServerLoggingConfigurationShouldLogAccess-logdir" ); + FileUtils.forceMkdir( logDirectory ); + System.out.println(logDirectory); + final File confDir = + testDirectory.directory( "givenExplicitlyEnabledServerLoggingConfigurationShouldLogAccess-confdir" ); + FileUtils.forceMkdir( confDir ); + + final String query = "?explicitlyEnabled=" + randomString(); + + NeoServer server = CommunityServerBuilder.server().withDefaultDatabaseTuning() + .withProperty( ServerSettings.http_logging_enabled.name(), "true" ) + .withProperty( GraphDatabaseSettings.logs_directory.name(), logDirectory.getAbsolutePath() ) + .usingDataDir( testDirectory.directory( + "givenExplicitlyEnabledServerLoggingConfigurationShouldLogAccess-dbdir" + ).getAbsolutePath() ) + .build(); + try + { + server.start(); + + FunctionalTestHelper functionalTestHelper = new FunctionalTestHelper( server ); + + // when + JaxRsResponse response = new RestRequest().get( functionalTestHelper.managementUri() + query ); + assertThat( response.getStatus(), is( 200 ) ); + response.close(); + + // then + File httpLog = new File( logDirectory, "http.log" ); + assertEventually( "request appears in log", fileContentSupplier( httpLog ), containsString( query ), 5, TimeUnit.SECONDS ); + } + finally + { + server.stop(); + } + } + + @Rule + public final TargetDirectory.TestDirectory testDirectory = TargetDirectory.testDirForTest( getClass() ); + + private ThrowingSupplier fileContentSupplier( final File file ) + { + return () -> readTextFile( file, StandardCharsets.UTF_8 ); + } + + private String randomString() + { + return UUID.randomUUID().toString(); + } +} diff --git a/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/PropertyExistenceConstraintsIT.java b/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/PropertyExistenceConstraintsIT.java new file mode 100644 index 0000000000000..827ad58d8789b --- /dev/null +++ b/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/PropertyExistenceConstraintsIT.java @@ -0,0 +1,345 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.neo4j.server.rest; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +import org.neo4j.function.Factory; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.schema.ConstraintDefinition; +import org.neo4j.graphdb.schema.ConstraintType; +import org.neo4j.kernel.impl.annotations.Documented; +import org.neo4j.logging.NullLogProvider; +import org.neo4j.server.NeoServer; +import org.neo4j.server.enterprise.EnterpriseServerSettings; +import org.neo4j.server.enterprise.helpers.EnterpriseServerBuilder; +import org.neo4j.server.helpers.CommunityServerBuilder; +import org.neo4j.server.helpers.ServerHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription; +import org.neo4j.test.GraphHolder; +import org.neo4j.test.TestData; + +import static java.util.Collections.singletonList; + +import static org.hamcrest.Matchers.hasItem; +import static org.hamcrest.Matchers.hasItems; +import static org.junit.Assert.assertThat; + +import static org.neo4j.graphdb.Label.label; +import static org.neo4j.server.rest.domain.JsonHelper.jsonToList; +import static org.neo4j.server.rest.web.Surface.PATH_SCHEMA_CONSTRAINT; +import static org.neo4j.server.rest.web.Surface.PATH_SCHEMA_RELATIONSHIP_CONSTRAINT; +import static org.neo4j.test.SuppressOutput.suppressAll; + +public class PropertyExistenceConstraintsIT implements GraphHolder +{ + private final Factory labels = UniqueStrings.withPrefix( "label" ); + private final Factory properties = UniqueStrings.withPrefix( "property" ); + private final Factory relationshipTypes = UniqueStrings.withPrefix( "relationshipType" ); + + @Rule + public TestData> data = TestData.producedThrough( GraphDescription.createGraphFor( this, true ) ); + @Rule + public TestData gen = TestData.producedThrough( RESTDocsGenerator.PRODUCER ); + + private static NeoServer server; + + @Override + public GraphDatabaseService graphdb() + { + return server.getDatabase().getGraph(); + } + + @BeforeClass + public static void initServer() throws Exception + { + suppressAll().call( new Callable() + { + @Override + public Void call() throws IOException + { + CommunityServerBuilder serverBuilder = EnterpriseServerBuilder.server( NullLogProvider.getInstance() ) + .withProperty( EnterpriseServerSettings.mode.name(), "enterprise" ); + + PropertyExistenceConstraintsIT.server = ServerHelper.createNonPersistentServer( serverBuilder ); + return null; + } + } ); + } + + @AfterClass + public static void stopServer() throws Exception + { + if ( server != null ) + { + suppressAll().call( new Callable() + { + @Override + public Void call() + { + server.stop(); + return null; + } + } ); + } + } + + @Documented( "Get a specific node property existence constraint.\n" + + "Get a specific node property existence constraint for a label and a property." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void getLabelPropertyExistenceConstraint() throws JsonParseException + { + data.get(); + + String labelName = labels.newInstance(), propertyKey = properties.newInstance(); + createLabelPropertyExistenceConstraint( labelName, propertyKey ); + + String result = gen.get().expectedStatus( 200 ).get( + getSchemaConstraintLabelExistencePropertyUri( labelName, propertyKey ) ).entity(); + + List> serializedList = jsonToList( result ); + + Map constraint = new HashMap<>(); + constraint.put( "type", ConstraintType.NODE_PROPERTY_EXISTENCE.name() ); + constraint.put( "label", labelName ); + constraint.put( "property_keys", singletonList( propertyKey ) ); + + assertThat( serializedList, hasItem( constraint ) ); + } + + @Documented( "Get a specific relationship property existence constraint.\n" + + "Get a specific relationship property existence constraint for a label and a property." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void getRelationshipTypePropertyExistenceConstraint() throws JsonParseException + { + data.get(); + + String typeName = relationshipTypes.newInstance(), propertyKey = properties.newInstance(); + createRelationshipTypePropertyExistenceConstraint( typeName, propertyKey ); + + String result = gen.get().expectedStatus( 200 ).get( + getSchemaRelationshipConstraintTypeExistencePropertyUri( typeName, propertyKey ) ).entity(); + + List> serializedList = jsonToList( result ); + + Map constraint = new HashMap<>(); + constraint.put( "type", ConstraintType.RELATIONSHIP_PROPERTY_EXISTENCE.name() ); + constraint.put( "relationshipType", typeName ); + constraint.put( "property_keys", singletonList( propertyKey ) ); + + assertThat( serializedList, hasItem( constraint ) ); + } + + @SuppressWarnings( "unchecked" ) + @Documented( "Get all node property existence constraints for a label." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void getLabelPropertyExistenceConstraints() throws JsonParseException + { + data.get(); + + String labelName = labels.newInstance(), propertyKey1 = properties.newInstance(), propertyKey2 = + properties.newInstance(); + createLabelPropertyExistenceConstraint( labelName, propertyKey1 ); + createLabelPropertyExistenceConstraint( labelName, propertyKey2 ); + + String result = + gen.get().expectedStatus( 200 ).get( getSchemaConstraintLabelExistenceUri( labelName ) ) + .entity(); + + List> serializedList = jsonToList( result ); + + Map constraint1 = new HashMap<>(); + constraint1.put( "type", ConstraintType.NODE_PROPERTY_EXISTENCE.name() ); + constraint1.put( "label", labelName ); + constraint1.put( "property_keys", singletonList( propertyKey1 ) ); + + Map constraint2 = new HashMap<>(); + constraint2.put( "type", ConstraintType.NODE_PROPERTY_EXISTENCE.name() ); + constraint2.put( "label", labelName ); + constraint2.put( "property_keys", singletonList( propertyKey2 ) ); + + assertThat( serializedList, hasItems( constraint1, constraint2 ) ); + } + + @SuppressWarnings( "unchecked" ) + @Documented( "Get all relationship property existence constraints for a type." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void getRelationshipTypePropertyExistenceConstraints() throws JsonParseException + { + data.get(); + + String typeName = relationshipTypes.newInstance(), propertyKey1 = properties.newInstance(), + propertyKey2 = properties.newInstance(); + createRelationshipTypePropertyExistenceConstraint( typeName, propertyKey1 ); + createRelationshipTypePropertyExistenceConstraint( typeName, propertyKey2 ); + + String result = gen.get().expectedStatus( 200 ) + .get( getSchemaRelationshipConstraintTypeExistenceUri( typeName ) ).entity(); + + List> serializedList = jsonToList( result ); + + Map constraint1 = new HashMap<>(); + constraint1.put( "type", ConstraintType.RELATIONSHIP_PROPERTY_EXISTENCE.name() ); + constraint1.put( "relationshipType", typeName ); + constraint1.put( "property_keys", singletonList( propertyKey1 ) ); + + Map constraint2 = new HashMap<>(); + constraint2.put( "type", ConstraintType.RELATIONSHIP_PROPERTY_EXISTENCE.name() ); + constraint2.put( "relationshipType", typeName ); + constraint2.put( "property_keys", singletonList( propertyKey2 ) ); + + assertThat( serializedList, hasItems( constraint1, constraint2 ) ); + } + + @SuppressWarnings( "unchecked" ) + @Documented( "Get all constraints for a label." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void getLabelPropertyConstraints() throws JsonParseException + { + data.get(); + + String labelName = labels.newInstance(), propertyKey1 = properties.newInstance(), propertyKey2 = + properties.newInstance(); + createLabelUniquenessPropertyConstraint( labelName, propertyKey1 ); + createLabelPropertyExistenceConstraint( labelName, propertyKey2 ); + + String result = + gen.get().expectedStatus( 200 ).get( getSchemaConstraintLabelUri( labelName ) ).entity(); + + List> serializedList = jsonToList( result ); + + Map constraint1 = new HashMap<>(); + constraint1.put( "type", ConstraintType.UNIQUENESS.name() ); + constraint1.put( "label", labelName ); + constraint1.put( "property_keys", singletonList( propertyKey1 ) ); + + Map constraint2 = new HashMap<>(); + constraint2.put( "type", ConstraintType.NODE_PROPERTY_EXISTENCE.name() ); + constraint2.put( "label", labelName ); + constraint2.put( "property_keys", singletonList( propertyKey2 ) ); + + assertThat( serializedList, hasItems( constraint1, constraint2 ) ); + } + + @SuppressWarnings( "unchecked" ) + @Documented( "Get all constraints." ) + @Test + @GraphDescription.Graph( nodes = {} ) + public void get_constraints() throws JsonParseException + { + data.get(); + + String labelName1 = labels.newInstance(), propertyKey1 = properties.newInstance(); + String labelName2 = labels.newInstance(), propertyKey2 = properties.newInstance(); + createLabelUniquenessPropertyConstraint( labelName1, propertyKey1 ); + createLabelPropertyExistenceConstraint( labelName2, propertyKey2 ); + + String result = gen.get().expectedStatus( 200 ).get( getSchemaConstraintUri() ).entity(); + + List> serializedList = jsonToList( result ); + + Map constraint1 = new HashMap<>(); + constraint1.put( "type", ConstraintType.UNIQUENESS.name() ); + constraint1.put( "label", labelName1 ); + constraint1.put( "property_keys", singletonList( propertyKey1 ) ); + + Map constraint2 = new HashMap<>(); + constraint2.put( "type", ConstraintType.NODE_PROPERTY_EXISTENCE.name() ); + constraint2.put( "label", labelName2 ); + constraint2.put( "property_keys", singletonList( propertyKey2 ) ); + + assertThat( serializedList, hasItems( constraint1, constraint2 ) ); + } + + private ConstraintDefinition createLabelUniquenessPropertyConstraint( String labelName, String propertyKey ) + { + try ( Transaction tx = graphdb().beginTx() ) + { + ConstraintDefinition constraintDefinition = graphdb().schema().constraintFor( label( labelName ) ) + .assertPropertyIsUnique( propertyKey ).create(); + tx.success(); + return constraintDefinition; + } + } + + private String getDataUri() + { + return "http://localhost:7474/db/data/"; + } + + public String getSchemaConstraintUri() + { + return getDataUri() + PATH_SCHEMA_CONSTRAINT; + } + + public String getSchemaConstraintLabelUri( String label ) + { + return getDataUri() + PATH_SCHEMA_CONSTRAINT + "/" + label; + } + + private void createLabelPropertyExistenceConstraint( String labelName, String propertyKey ) + { + String query = String.format( "CREATE CONSTRAINT ON (n:%s) ASSERT exists(n.%s)", labelName, propertyKey ); + graphdb().execute( query ); + } + + private void createRelationshipTypePropertyExistenceConstraint( String typeName, String propertyKey ) + { + String query = String.format( "CREATE CONSTRAINT ON ()-[r:%s]-() ASSERT exists(r.%s)", typeName, propertyKey ); + graphdb().execute( query ); + } + + private String getSchemaConstraintLabelExistenceUri( String label ) + { + return getDataUri() + PATH_SCHEMA_CONSTRAINT + "/" + label + "/existence/"; + } + + private String getSchemaRelationshipConstraintTypeExistenceUri( String type ) + { + return getDataUri() + PATH_SCHEMA_RELATIONSHIP_CONSTRAINT + "/" + type + "/existence/"; + } + + private String getSchemaConstraintLabelExistencePropertyUri( String label, String property ) + { + return getDataUri() + PATH_SCHEMA_CONSTRAINT + "/" + label + "/existence/" + property; + } + + private String getSchemaRelationshipConstraintTypeExistencePropertyUri( String type, String property ) + { + return getDataUri() + PATH_SCHEMA_RELATIONSHIP_CONSTRAINT + "/" + type + "/existence/" + property; + } +} diff --git a/manual/contents/pom.xml b/manual/contents/pom.xml index f13a850652f80..d55f2be572f20 100644 --- a/manual/contents/pom.xml +++ b/manual/contents/pom.xml @@ -168,8 +168,29 @@ provided - org.neo4j.app - neo4j-server + org.neo4j.doc + neo4j-server-docs + ${neo4j.version} + docs + provided + + + org.neo4j.doc + neo4j-server-docs + ${neo4j.version} + test-sources + provided + + + org.neo4j.doc + neo4j-server-enterprise-docs + ${neo4j.version} + docs + provided + + + org.neo4j.doc + neo4j-server-enterprise-docs ${neo4j.version} test-sources provided @@ -342,7 +363,7 @@ META-INF,META-INF/**,org/neo4j/kernel/impl/storemigration/legacystore/** jar ${docs.test-sources} - neo4j-cypher-docs,neo4j-examples,neo4j-harness-test,neo4j-server,neo4j-lucene-index-docs,neo4j-backup-docs,neo4j-kernel-docs + neo4j-cypher-docs,neo4j-examples,neo4j-harness-test,neo4j-server,neo4j-server-docs,neo4j-server-enterprise-docs,neo4j-lucene-index-docs,neo4j-backup-docs,neo4j-kernel-docs diff --git a/manual/contents/src/docinfo.xml b/manual/contents/src/docinfo.xml index e91251121696f..614210a2ca941 100644 --- a/manual/contents/src/docinfo.xml +++ b/manual/contents/src/docinfo.xml @@ -59,11 +59,6 @@ Upgrading - - - Security - - Resources diff --git a/manual/contents/src/operations/configuration.asciidoc b/manual/contents/src/operations/configuration.asciidoc index aa974ec3c6008..5a20d4e2cac32 100644 --- a/manual/contents/src/operations/configuration.asciidoc +++ b/manual/contents/src/operations/configuration.asciidoc @@ -13,10 +13,6 @@ include::{importdir}/neo4j-kernel-docs-docs-jar/ops/introduction.asciidoc[] :leveloffset: 2 -include::{importdir}/neo4j-server-docs-jar/ops/server-performance.asciidoc[] - -:leveloffset: 2 - include::{importdir}/neo4j-kernel-docs-docs-jar/ops/performance-guide.asciidoc[] include::{importdir}/neo4j-kernel-docs-docs-jar/ops/logical-logs.asciidoc[] diff --git a/manual/contents/src/operations/index.asciidoc b/manual/contents/src/operations/index.asciidoc index bee3019059c6b..740dc766d8e62 100644 --- a/manual/contents/src/operations/index.asciidoc +++ b/manual/contents/src/operations/index.asciidoc @@ -25,10 +25,6 @@ include::{importdir}/neo4j-backup-docs-docs-jar/ops/backup.asciidoc[] :leveloffset: 1 -include::security.asciidoc[] - -:leveloffset: 1 - include::{importdir}/neo4j-management-docs-docs-jar/ops/monitoring.asciidoc[] diff --git a/manual/contents/src/operations/installation-deployment/index.asciidoc b/manual/contents/src/operations/installation-deployment/index.asciidoc index 680a009eb12e7..6191885f033a6 100644 --- a/manual/contents/src/operations/installation-deployment/index.asciidoc +++ b/manual/contents/src/operations/installation-deployment/index.asciidoc @@ -4,7 +4,6 @@ Neo4j is accessed as a standalone server, either directly through a REST interface or through a language-specific driver. Neo4j can be installed as a server, running either as a headless application or system service. -For information on installing The Neo4j Server, see <>. For running Neo4j in high availability mode, see <>. @@ -14,25 +13,13 @@ include::requirements.asciidoc[] :leveloffset: 2 -include::{importdir}/neo4j-server-docs-jar/ops/server-installation.asciidoc[] - -:leveloffset: 2 - include::file-locations.asciidoc[] :leveloffset: 2 -include::{importdir}/neo4j-server-docs-jar/ops/powershell.asciidoc[] - -:leveloffset: 2 - include::{importdir}/neo4j-docs-jar/ops/upgrades.asciidoc[] :leveloffset: 2 -include::{importdir}/neo4j-server-docs-jar/ops/server-debugging.asciidoc[] - -:leveloffset: 2 - include::udc.asciidoc[] diff --git a/manual/contents/src/operations/security.asciidoc b/manual/contents/src/operations/security.asciidoc deleted file mode 100644 index 3f449392b2cf0..0000000000000 --- a/manual/contents/src/operations/security.asciidoc +++ /dev/null @@ -1,12 +0,0 @@ -[[operations-security]] -Security -======== - -Neo4j in itself does not enforce security on the data level. -However, there are different aspects that should be considered when using Neo4j in different scenarios. -See <> for details. - -:leveloffset: 2 - -include::{importdir}/neo4j-server-docs-jar/ops/security.asciidoc[] - diff --git a/manual/contents/src/reference/index.asciidoc b/manual/contents/src/reference/index.asciidoc index 23c5e68a4b066..671fa802b6188 100644 --- a/manual/contents/src/reference/index.asciidoc +++ b/manual/contents/src/reference/index.asciidoc @@ -21,7 +21,7 @@ include::import/import.asciidoc[] :leveloffset: 1 -include::{importdir}/neo4j-server-docs-jar/dev/rest-api/index.asciidoc[] +include::{importdir}/neo4j-server-docs-docs-jar/dev/rest-api/index.asciidoc[] :leveloffset: 1 diff --git a/manual/contents/src/resources.asciidoc b/manual/contents/src/resources.asciidoc index eff475d0fbb69..33ca066a3814e 100644 --- a/manual/contents/src/resources.asciidoc +++ b/manual/contents/src/resources.asciidoc @@ -17,5 +17,3 @@ Below are some starting points within this manual: * <> * <> * <> -* <> - diff --git a/manual/cypher/cypher-docs/src/docs/dev/tutorials/cypher.asciidoc b/manual/cypher/cypher-docs/src/docs/dev/tutorials/cypher.asciidoc index 0e935cdb7b3f1..5f683cf675bab 100644 --- a/manual/cypher/cypher-docs/src/docs/dev/tutorials/cypher.asciidoc +++ b/manual/cypher/cypher-docs/src/docs/dev/tutorials/cypher.asciidoc @@ -5,11 +5,6 @@ This chapter will guide you through your first steps with <>. In the online edition of this manual, all queries in this section can be executed interactively without installing Neo4j on your computer. -Otherwise, first get the Neo4j server running to try things out locally. -Instructions are found in <>. -With the server running, you can choose to issue Cypher queries from either the Neo4j Browser or the Neo4j shell. -See <>. - :leveloffset: 2 include::../../parsed-graphgists/cypher/create-nodes-and-rels.asciidoc[] diff --git a/manual/enterprise-server-docs/LICENSES.txt b/manual/enterprise-server-docs/LICENSES.txt new file mode 100644 index 0000000000000..8c351519aba76 --- /dev/null +++ b/manual/enterprise-server-docs/LICENSES.txt @@ -0,0 +1,216 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + +------------------------------------------------------------------------------ +Apache Software License, Version 2.0 + Apache Commons Configuration + Commons BeanUtils + Commons Digester + Commons Lang + Commons Logging +------------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + diff --git a/manual/enterprise-server-docs/NOTICE.txt b/manual/enterprise-server-docs/NOTICE.txt new file mode 100644 index 0000000000000..e38637c77c364 --- /dev/null +++ b/manual/enterprise-server-docs/NOTICE.txt @@ -0,0 +1,34 @@ +Neo4j +Copyright © 2002-2016 Network Engine for Objects in Lund AB (referred to +in this notice as "Neo Technology") + [http://neotechnology.com] + +This product includes software ("Software") developed by Neo Technology. + +The copyright in the bundled Neo4j graph database (including the +Software) is owned by Neo Technology. The Software developed and owned +by Neo Technology is licensed under the GNU GENERAL PUBLIC LICENSE +Version 3 (http://www.fsf.org/licensing/licenses/gpl-3.0.html) ("GPL") +to all third parties and that license, as required by the GPL, is +included in the LICENSE.txt file. + +However, if you have executed an End User Software License and Services +Agreement or an OEM Software License and Support Services Agreement, or +another commercial license agreement with Neo Technology or one of its +affiliates (each, a "Commercial Agreement"), the terms of the license in +such Commercial Agreement will supersede the GPL and you may use the +software solely pursuant to the terms of the relevant Commercial +Agreement. + +Full license texts are found in LICENSES.txt. + +Third-party licenses +-------------------- + +Apache Software License, Version 2.0 + Apache Commons Configuration + Commons BeanUtils + Commons Digester + Commons Lang + Commons Logging + diff --git a/manual/enterprise-server-docs/pom.xml b/manual/enterprise-server-docs/pom.xml new file mode 100644 index 0000000000000..f6bf2a2fd0ed0 --- /dev/null +++ b/manual/enterprise-server-docs/pom.xml @@ -0,0 +1,308 @@ + + + + org.neo4j + parent + 3.0.7-SNAPSHOT + ../.. + + + 4.0.0 + org.neo4j.doc + neo4j-server-enterprise-docs + 3.0.7-SNAPSHOT + Neo4j - Enterprise Server Docs + Documentation for the Neo4j Enterprise server + + + org.neo4j.server + server + server.impl + GPL-3-header.txt + notice-gpl-prefix.txt + true + + ${project.version} + 1.1.8 + + org.neo4j.server.CommunityEntryPoint + target/generated-resources/appassembler/jsw + target/test-classes/etc/neo-server + + 3.0.2 + ${project.build.directory} + + + false + http://localhost:7474 + + 2.42.2 + Firefox + + + webdriver-binaries/chromedriver + http://chromedriver.googlecode.com/files/chromedriver_mac_23.0.1240.0.zip + + **Needs to provided externally by the CI job for security reasons** + + + + + + + + + + org.apache.httpcomponents + httpclient + test + 4.3.4 + + + + org.apache.httpcomponents + httpcore + test + 4.3.2 + + + + + + + org.neo4j.app + neo4j-server + ${project.version} + test + + + + org.neo4j.doc + neo4j-server-docs + ${project.version} + test + test-jar + + + + org.neo4j.app + neo4j-server-enterprise + ${project.version} + test + + + + + com.sun.jersey + jersey-client + test + + + + com.google.code.gson + gson + test + + + + org.apache.httpcomponents + httpclient + test + + + + org.apache.httpcomponents + httpcore + test + + + + org.neo4j + neo4j-graphviz + ${project.version} + test + + + + org.neo4j + neo4j-kernel + ${project.version} + test-jar + test + + + + org.neo4j + neo4j-logging + ${project.version} + test-jar + test + + + + org.neo4j + neo4j-io + ${project.version} + test-jar + test + + + + junit + junit + test + + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + + + org.mockito + mockito-core + test + + + + org.json + json + 20090211 + test + + + + org.neo4j.doc + neo4j-doc-tools + ${project.version} + test + test-jar + + + + + + + + maven-antrun-plugin + 1.7 + + + + generate-source-based-documentation + process-test-classes + + + + + + + + + run + + + + + + ant-contrib + ant-contrib + 1.0b3 + + + ant + ant + + + + + org.apache.ant + ant + 1.8.2 + + + org.apache.ant + ant-apache-regexp + 1.8.2 + + + + + + + + + surefire-windows + + + windows + + + + + + maven-surefire-plugin + + always + + + + + + + all-tests + + + tests + all + + + + + + + + java.net + http://download.java.net/maven/2/ + + + selenium-repository + http://selenium.googlecode.com/svn/repository + + + neo4j-release-repository + Neo4j Maven 2 release repository + http://m2.neo4j.org/content/repositories/releases + + true + + + false + + + + neo4j-snapshot-repository + Neo4j Maven 2 snapshot repository + http://m2.neo4j.org/content/repositories/snapshots + + true + + + false + + + + + + + jsdoctk2 + http://jsdoctk-plugin.googlecode.com/svn/repo + + + + diff --git a/manual/enterprise-server-docs/src/test/java/org/neo4j/doc/server/rest/EnterpriseServerBuilder.java b/manual/enterprise-server-docs/src/test/java/org/neo4j/doc/server/rest/EnterpriseServerBuilder.java new file mode 100644 index 0000000000000..a092964f1e217 --- /dev/null +++ b/manual/enterprise-server-docs/src/test/java/org/neo4j/doc/server/rest/EnterpriseServerBuilder.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest; + +import java.io.File; +import java.io.IOException; +import java.util.Optional; + +import org.neo4j.doc.server.helpers.CommunityServerBuilder; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.factory.GraphDatabaseFacadeFactory; +import org.neo4j.logging.LogProvider; +import org.neo4j.logging.NullLogProvider; +import org.neo4j.server.CommunityNeoServer; +import org.neo4j.server.enterprise.EnterpriseNeoServer; +import org.neo4j.server.rest.web.DatabaseActions; + +public class EnterpriseServerBuilder extends CommunityServerBuilder +{ + protected EnterpriseServerBuilder( LogProvider logProvider ) + { + super( logProvider ); + } + + public static EnterpriseServerBuilder server() + { + return server( NullLogProvider.getInstance() ); + } + + public static EnterpriseServerBuilder server( LogProvider logProvider ) + { + return new EnterpriseServerBuilder( logProvider ); + } + + @Override + public EnterpriseNeoServer build() throws IOException + { + return (EnterpriseNeoServer) super.build(); + } + + @Override + public EnterpriseServerBuilder usingDataDir( String dataDir ) + { + super.usingDataDir( dataDir ); + return this; + } + + @Override + protected CommunityNeoServer build(Optional configFile, Config config, GraphDatabaseFacadeFactory.Dependencies dependencies) + { + return new TestEnterpriseNeoServer( config, configFile, dependencies, logProvider ); + } + + private class TestEnterpriseNeoServer extends EnterpriseNeoServer + { + private final Optional configFile; + + public TestEnterpriseNeoServer( Config config, Optional configFile, GraphDatabaseFacadeFactory.Dependencies dependencies, LogProvider logProvider ) + { + super( config, dependencies, logProvider ); + this.configFile = configFile; + } + + @Override + protected DatabaseActions createDatabaseActions() + { + return createDatabaseActionsObject( database, getConfig() ); + } + + @Override + public void stop() + { + super.stop(); + configFile.ifPresent( File::delete ); + } + } +} diff --git a/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/PropertyExistenceConstraintsDocIT.java b/manual/enterprise-server-docs/src/test/java/org/neo4j/doc/server/rest/PropertyExistenceConstraintsDocIT.java similarity index 95% rename from enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/PropertyExistenceConstraintsDocIT.java rename to manual/enterprise-server-docs/src/test/java/org/neo4j/doc/server/rest/PropertyExistenceConstraintsDocIT.java index 52c137894afbf..3881f4a90001c 100644 --- a/enterprise/server-enterprise/src/test/java/org/neo4j/server/rest/PropertyExistenceConstraintsDocIT.java +++ b/manual/enterprise-server-docs/src/test/java/org/neo4j/doc/server/rest/PropertyExistenceConstraintsDocIT.java @@ -5,24 +5,19 @@ * This file is part of Neo4j. * * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as - * published by the Free Software Foundation, either version 3 of the - * License, or (at your option) any later version. + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. + * GNU General Public License for more details. * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ -package org.neo4j.server.rest; - -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.Test; +package org.neo4j.doc.server.rest; import java.io.IOException; import java.util.HashMap; @@ -30,6 +25,13 @@ import java.util.Map; import java.util.concurrent.Callable; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; + +import org.neo4j.doc.server.helpers.CommunityServerBuilder; +import org.neo4j.doc.server.helpers.ServerHelper; import org.neo4j.function.Factory; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; @@ -40,18 +42,17 @@ import org.neo4j.logging.NullLogProvider; import org.neo4j.server.NeoServer; import org.neo4j.server.enterprise.EnterpriseServerSettings; -import org.neo4j.server.enterprise.helpers.EnterpriseServerBuilder; -import org.neo4j.server.helpers.CommunityServerBuilder; -import org.neo4j.server.helpers.ServerHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.test.GraphDescription; import org.neo4j.test.GraphHolder; import org.neo4j.test.TestData; import static java.util.Collections.singletonList; + import static org.hamcrest.Matchers.hasItem; import static org.hamcrest.Matchers.hasItems; import static org.junit.Assert.assertThat; + import static org.neo4j.graphdb.Label.label; import static org.neo4j.server.rest.domain.JsonHelper.jsonToList; import static org.neo4j.server.rest.web.Surface.PATH_SCHEMA_CONSTRAINT; diff --git a/manual/ha-docs/src/docs/dev/arbiter.asciidoc b/manual/ha-docs/src/docs/dev/arbiter.asciidoc index 62317de5b1c2a..e080f9192f03e 100644 --- a/manual/ha-docs/src/docs/dev/arbiter.asciidoc +++ b/manual/ha-docs/src/docs/dev/arbiter.asciidoc @@ -21,5 +21,4 @@ neo4j_home$ ./bin/neo4j start ---- You can also stop, install and remove it as a service and ask for its status in exactly the same way as for other Neo4j instances. -See also <>. diff --git a/manual/import-tool/src/docs/ops/import-tool.asciidoc b/manual/import-tool/src/docs/ops/import-tool.asciidoc index dd332c1ad99de..ecec99f9fc33e 100644 --- a/manual/import-tool/src/docs/ops/import-tool.asciidoc +++ b/manual/import-tool/src/docs/ops/import-tool.asciidoc @@ -93,8 +93,6 @@ Under Unix/Linux/OSX, the command is named `neo4j-import`. Depending on the inst Under Windows, used by executing `bin\neo4j-import` from inside the installation directory. -For help with running the import tool under Windows, see the reference in <>. - [[import-tool-options]] === Options @@ -180,7 +178,7 @@ With all data in place, we execute the following command: include::example-command.adoc[] ---- -We're now ready to start up a database from the target directory. (see <>) +We're now ready to start up a database from the target directory. Once we've got the database up and running we can add appropriate indexes. (see <>.) diff --git a/manual/pom.xml b/manual/pom.xml index 66a170f9c4ca2..94d2860b98acb 100644 --- a/manual/pom.xml +++ b/manual/pom.xml @@ -28,6 +28,8 @@ server-examples bolt import-tool + server-docs + enterprise-server-docs shell kernel management-docs diff --git a/manual/server-docs/LICENSES.txt b/manual/server-docs/LICENSES.txt new file mode 100644 index 0000000000000..8c351519aba76 --- /dev/null +++ b/manual/server-docs/LICENSES.txt @@ -0,0 +1,216 @@ +This file contains the full license text of the included third party +libraries. For an overview of the licenses see the NOTICE.txt file. + +------------------------------------------------------------------------------ +Apache Software License, Version 2.0 + Apache Commons Configuration + Commons BeanUtils + Commons Digester + Commons Lang + Commons Logging +------------------------------------------------------------------------------ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + + diff --git a/manual/server-docs/NOTICE.txt b/manual/server-docs/NOTICE.txt new file mode 100644 index 0000000000000..e38637c77c364 --- /dev/null +++ b/manual/server-docs/NOTICE.txt @@ -0,0 +1,34 @@ +Neo4j +Copyright © 2002-2016 Network Engine for Objects in Lund AB (referred to +in this notice as "Neo Technology") + [http://neotechnology.com] + +This product includes software ("Software") developed by Neo Technology. + +The copyright in the bundled Neo4j graph database (including the +Software) is owned by Neo Technology. The Software developed and owned +by Neo Technology is licensed under the GNU GENERAL PUBLIC LICENSE +Version 3 (http://www.fsf.org/licensing/licenses/gpl-3.0.html) ("GPL") +to all third parties and that license, as required by the GPL, is +included in the LICENSE.txt file. + +However, if you have executed an End User Software License and Services +Agreement or an OEM Software License and Support Services Agreement, or +another commercial license agreement with Neo Technology or one of its +affiliates (each, a "Commercial Agreement"), the terms of the license in +such Commercial Agreement will supersede the GPL and you may use the +software solely pursuant to the terms of the relevant Commercial +Agreement. + +Full license texts are found in LICENSES.txt. + +Third-party licenses +-------------------- + +Apache Software License, Version 2.0 + Apache Commons Configuration + Commons BeanUtils + Commons Digester + Commons Lang + Commons Logging + diff --git a/manual/server-docs/pom.xml b/manual/server-docs/pom.xml new file mode 100644 index 0000000000000..ac5dd518ce15c --- /dev/null +++ b/manual/server-docs/pom.xml @@ -0,0 +1,293 @@ + + + + org.neo4j + parent + 3.0.7-SNAPSHOT + ../.. + + + 4.0.0 + org.neo4j.doc + neo4j-server-docs + 3.0.7-SNAPSHOT + Neo4j - Server Docs + Documentation for the Neo4j server + + + org.neo4j.server + server + server.impl + GPL-3-header.txt + notice-gpl-prefix.txt + true + + ${project.version} + 1.1.8 + + org.neo4j.server.CommunityEntryPoint + target/generated-resources/appassembler/jsw + target/test-classes/etc/neo-server + + 3.0.2 + ${project.build.directory} + + + false + http://localhost:7474 + + 2.42.2 + Firefox + + + webdriver-binaries/chromedriver + http://chromedriver.googlecode.com/files/chromedriver_mac_23.0.1240.0.zip + + **Needs to provided externally by the CI job for security reasons** + + + + + + + + + + org.apache.httpcomponents + httpclient + test + 4.3.4 + + + + org.apache.httpcomponents + httpcore + test + 4.3.2 + + + + + + + org.neo4j.app + neo4j-server + ${project.version} + test + + + + + com.sun.jersey + jersey-client + test + + + + com.google.code.gson + gson + test + + + + org.apache.httpcomponents + httpclient + test + + + + org.apache.httpcomponents + httpcore + test + + + + org.neo4j + neo4j-graphviz + ${project.version} + test + + + + org.neo4j + neo4j-kernel + ${project.version} + test-jar + test + + + + org.neo4j + neo4j-logging + ${project.version} + test-jar + test + + + + org.neo4j + neo4j-io + ${project.version} + test-jar + test + + + + junit + junit + test + + + org.hamcrest + hamcrest-core + test + + + org.hamcrest + hamcrest-library + test + + + org.mockito + mockito-core + test + + + + org.json + json + 20090211 + test + + + + org.neo4j.doc + neo4j-doc-tools + ${project.version} + test + test-jar + + + + + + + + maven-antrun-plugin + 1.7 + + + + generate-source-based-documentation + process-test-classes + + + + + + + + + run + + + + + + ant-contrib + ant-contrib + 1.0b3 + + + ant + ant + + + + + org.apache.ant + ant + 1.8.2 + + + org.apache.ant + ant-apache-regexp + 1.8.2 + + + + + + + + + surefire-windows + + + windows + + + + + + maven-surefire-plugin + + always + + + + + + + all-tests + + + tests + all + + + + + + + + java.net + http://download.java.net/maven/2/ + + + selenium-repository + http://selenium.googlecode.com/svn/repository + + + neo4j-release-repository + Neo4j Maven 2 release repository + http://m2.neo4j.org/content/repositories/releases + + true + + + false + + + + neo4j-snapshot-repository + Neo4j Maven 2 snapshot repository + http://m2.neo4j.org/content/repositories/snapshots + + true + + + false + + + + + + + jsdoctk2 + http://jsdoctk-plugin.googlecode.com/svn/repo + + + + diff --git a/community/server/src/docs/dev/images/batch-request-api.png b/manual/server-docs/src/docs/dev/images/batch-request-api.png similarity index 100% rename from community/server/src/docs/dev/images/batch-request-api.png rename to manual/server-docs/src/docs/dev/images/batch-request-api.png diff --git a/community/server/src/docs/dev/rest-api/batch-operations.asciidoc b/manual/server-docs/src/docs/dev/rest-api/batch-operations.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/batch-operations.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/batch-operations.asciidoc diff --git a/community/server/src/docs/dev/rest-api/constraints.asciidoc b/manual/server-docs/src/docs/dev/rest-api/constraints.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/constraints.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/constraints.asciidoc diff --git a/community/server/src/docs/dev/rest-api/cypher.asciidoc b/manual/server-docs/src/docs/dev/rest-api/cypher.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/cypher.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/cypher.asciidoc diff --git a/community/server/src/docs/dev/rest-api/graph-algos.asciidoc b/manual/server-docs/src/docs/dev/rest-api/graph-algos.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/graph-algos.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/graph-algos.asciidoc diff --git a/community/server/src/docs/dev/rest-api/index.asciidoc b/manual/server-docs/src/docs/dev/rest-api/index.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/index.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/index.asciidoc diff --git a/community/server/src/docs/dev/rest-api/indexes-unique.asciidoc b/manual/server-docs/src/docs/dev/rest-api/indexes-unique.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/indexes-unique.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/indexes-unique.asciidoc diff --git a/community/server/src/docs/dev/rest-api/indexes.asciidoc b/manual/server-docs/src/docs/dev/rest-api/indexes.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/indexes.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/indexes.asciidoc diff --git a/community/server/src/docs/dev/rest-api/introduction.asciidoc b/manual/server-docs/src/docs/dev/rest-api/introduction.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/introduction.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/introduction.asciidoc diff --git a/community/server/src/docs/dev/rest-api/legacy-indexes.asciidoc b/manual/server-docs/src/docs/dev/rest-api/legacy-indexes.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/legacy-indexes.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/legacy-indexes.asciidoc diff --git a/community/server/src/docs/dev/rest-api/node-degree.asciidoc b/manual/server-docs/src/docs/dev/rest-api/node-degree.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/node-degree.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/node-degree.asciidoc diff --git a/community/server/src/docs/dev/rest-api/node-labels.asciidoc b/manual/server-docs/src/docs/dev/rest-api/node-labels.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/node-labels.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/node-labels.asciidoc diff --git a/community/server/src/docs/dev/rest-api/node-properties.asciidoc b/manual/server-docs/src/docs/dev/rest-api/node-properties.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/node-properties.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/node-properties.asciidoc diff --git a/community/server/src/docs/dev/rest-api/nodes.asciidoc b/manual/server-docs/src/docs/dev/rest-api/nodes.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/nodes.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/nodes.asciidoc diff --git a/community/server/src/docs/dev/rest-api/properties.asciidoc b/manual/server-docs/src/docs/dev/rest-api/properties.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/properties.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/properties.asciidoc diff --git a/community/server/src/docs/dev/rest-api/relationship-properties.asciidoc b/manual/server-docs/src/docs/dev/rest-api/relationship-properties.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/relationship-properties.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/relationship-properties.asciidoc diff --git a/community/server/src/docs/dev/rest-api/relationship-types.asciidoc b/manual/server-docs/src/docs/dev/rest-api/relationship-types.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/relationship-types.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/relationship-types.asciidoc diff --git a/community/server/src/docs/dev/rest-api/relationships.asciidoc b/manual/server-docs/src/docs/dev/rest-api/relationships.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/relationships.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/relationships.asciidoc diff --git a/community/server/src/docs/dev/rest-api/security.asciidoc b/manual/server-docs/src/docs/dev/rest-api/security.asciidoc similarity index 96% rename from community/server/src/docs/dev/rest-api/security.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/security.asciidoc index cb528d89d6173..2a5e89744c58b 100644 --- a/community/server/src/docs/dev/rest-api/security.asciidoc +++ b/manual/server-docs/src/docs/dev/rest-api/security.asciidoc @@ -3,7 +3,6 @@ In order to prevent unauthorized access to Neo4j, the REST API supports authorization and authentication. When enabled, requests to the REST API must be authorized using the username and password of a valid user. -Authorization is enabled by default, see <> for how to disable it. When Neo4j is first installed you can authenticate with the default user `neo4j` and the default password `neo4j`. However, the default password must be changed (see <>) before access to resources will be permitted. diff --git a/community/server/src/docs/dev/rest-api/service-root.asciidoc b/manual/server-docs/src/docs/dev/rest-api/service-root.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/service-root.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/service-root.asciidoc diff --git a/community/server/src/docs/dev/rest-api/transactional-status-codes.asciidoc b/manual/server-docs/src/docs/dev/rest-api/transactional-status-codes.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/transactional-status-codes.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/transactional-status-codes.asciidoc diff --git a/community/server/src/docs/dev/rest-api/transactional.asciidoc b/manual/server-docs/src/docs/dev/rest-api/transactional.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/transactional.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/transactional.asciidoc diff --git a/community/server/src/docs/dev/rest-api/traversals.asciidoc b/manual/server-docs/src/docs/dev/rest-api/traversals.asciidoc similarity index 98% rename from community/server/src/docs/dev/rest-api/traversals.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/traversals.asciidoc index a6482e1ef5071..b3bc1b0e83d89 100644 --- a/community/server/src/docs/dev/rest-api/traversals.asciidoc +++ b/manual/server-docs/src/docs/dev/rest-api/traversals.asciidoc @@ -4,7 +4,7 @@ [WARNING] The Traversal REST Endpoint executes arbitrary Javascript code under the hood as part of the evaluators definitions. In hosted and open environments, this can constitute a security risk. In these case, consider using declarative approaches like <> or write your own server side plugin executing the -interesting traversals with the Java API ( see <> ) or secure your server, see <>. +interesting traversals with the Java API ( see <> ) or secure your server. Traversals are performed from a start node. The traversal is controlled by the URI and the body sent with the request. diff --git a/community/server/src/docs/dev/rest-api/wadl.asciidoc b/manual/server-docs/src/docs/dev/rest-api/wadl.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/wadl.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/wadl.asciidoc diff --git a/community/server/src/docs/dev/rest-api/weblogic.asciidoc b/manual/server-docs/src/docs/dev/rest-api/weblogic.asciidoc similarity index 100% rename from community/server/src/docs/dev/rest-api/weblogic.asciidoc rename to manual/server-docs/src/docs/dev/rest-api/weblogic.asciidoc diff --git a/community/server/src/docs/man/neo4j-admin.1.asciidoc b/manual/server-docs/src/docs/man/neo4j-admin.1.asciidoc similarity index 100% rename from community/server/src/docs/man/neo4j-admin.1.asciidoc rename to manual/server-docs/src/docs/man/neo4j-admin.1.asciidoc diff --git a/community/server/src/docs/man/neo4j.1.asciidoc b/manual/server-docs/src/docs/man/neo4j.1.asciidoc similarity index 100% rename from community/server/src/docs/man/neo4j.1.asciidoc rename to manual/server-docs/src/docs/man/neo4j.1.asciidoc diff --git a/manual/server-docs/src/test/java/org/dummy/web/service/DummyPluginInitializer.java b/manual/server-docs/src/test/java/org/dummy/web/service/DummyPluginInitializer.java new file mode 100644 index 0000000000000..9eeeedba8c5c4 --- /dev/null +++ b/manual/server-docs/src/test/java/org/dummy/web/service/DummyPluginInitializer.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dummy.web.service; + +import java.util.Collection; +import java.util.Collections; + +import org.apache.commons.configuration.Configuration; +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.server.plugins.Injectable; +import org.neo4j.server.plugins.PluginLifecycle; + +public class DummyPluginInitializer implements PluginLifecycle +{ + public DummyPluginInitializer() + { + } + + @Override + public Collection> start( GraphDatabaseService graphDatabaseService, Configuration config ) + { + return Collections.>singleton( new Injectable() + { + @Override + public Long getValue() + { + return 42L; + + } + + @Override + public Class getType() + { + return Long.class; + } + } ); + } + + @Override + public void stop() + { + } +} diff --git a/manual/server-docs/src/test/java/org/dummy/web/service/DummyThirdPartyWebService.java b/manual/server-docs/src/test/java/org/dummy/web/service/DummyThirdPartyWebService.java new file mode 100644 index 0000000000000..c23893e720d0b --- /dev/null +++ b/manual/server-docs/src/test/java/org/dummy/web/service/DummyThirdPartyWebService.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.dummy.web.service; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Transaction; + +@Path( "/" ) +public class DummyThirdPartyWebService +{ + + public static final String DUMMY_WEB_SERVICE_MOUNT_POINT = "/dummy"; + + @GET + @Produces( MediaType.TEXT_PLAIN ) + public Response sayHello() + { + return Response.ok() + .entity( "hello" ) + .build(); + } + + + @GET + @Path("/{something}/{somethingElse}") + @Produces( MediaType.TEXT_PLAIN ) + public Response forSecurityTesting() { + return Response.ok().entity("you've reached a dummy service").build(); + } + + @GET + @Path( "inject-test" ) + @Produces( MediaType.TEXT_PLAIN ) + public Response countNodes( @Context GraphDatabaseService db ) + { + try (Transaction transaction = db.beginTx()) + { + return Response.ok() + .entity( String.valueOf( countNodesIn( db ) ) ) + .build(); + } + } + + @GET + @Path( "needs-auth-header" ) + @Produces( MediaType.APPLICATION_JSON ) + public Response authHeader( @Context HttpHeaders headers ) + { + StringBuilder theEntity = new StringBuilder( "{" ); + Iterator>> headerIt = headers.getRequestHeaders().entrySet().iterator(); + while ( headerIt.hasNext() ) + { + Map.Entry> header = headerIt.next(); + if (header.getValue().size() != 1) + { + throw new IllegalArgumentException( "Mutlivalued header: " + + header.getKey() ); + } + theEntity.append( "\"" ).append( header.getKey() ).append( "\":\"" ) + .append( header.getValue().get( 0 ) ).append( "\"" ); + if ( headerIt.hasNext() ) + { + theEntity.append( ", " ); + } + } + theEntity.append( "}" ); + return Response.ok().entity( theEntity.toString() ).build(); + } + + private int countNodesIn( GraphDatabaseService db ) + { + int count = 0; + for ( @SuppressWarnings("unused") Node node : db.getAllNodes() ) + { + count++; + } + return count; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/BatchOperationHeaderDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/BatchOperationHeaderDocIT.java similarity index 88% rename from community/server/src/test/java/org/neo4j/server/BatchOperationHeaderDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/BatchOperationHeaderDocIT.java index de9346d832d35..a431ef2b53377 100644 --- a/community/server/src/test/java/org/neo4j/server/BatchOperationHeaderDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/BatchOperationHeaderDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server; +package org.neo4j.doc.server; import java.io.IOException; import java.util.List; @@ -29,15 +29,16 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.neo4j.server.rest.JaxRsResponse; -import org.neo4j.server.rest.PrettyJSON; -import org.neo4j.server.rest.RestRequest; -import org.neo4j.test.server.ExclusiveServerTestBase; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; +import org.neo4j.doc.server.rest.PrettyJSON; +import org.neo4j.doc.server.rest.RestRequest; +import org.neo4j.server.NeoServer; +import org.neo4j.doc.server.rest.JaxRsResponse; import static org.dummy.web.service.DummyThirdPartyWebService.DUMMY_WEB_SERVICE_MOUNT_POINT; import static org.junit.Assert.assertEquals; -import static org.neo4j.server.helpers.CommunityServerBuilder.server; +import static org.neo4j.doc.server.helpers.CommunityServerBuilder.server; import static org.neo4j.server.rest.domain.JsonHelper.jsonToList; public class BatchOperationHeaderDocIT extends ExclusiveServerTestBase @@ -50,7 +51,7 @@ public class BatchOperationHeaderDocIT extends ExclusiveServerTestBase @Before public void cleanTheDatabase() throws IOException { - server = server().withThirdPartyJaxRsPackage( "org.dummy.web.service", + server = CommunityServerBuilder.server().withThirdPartyJaxRsPackage( "org.dummy.web.service", DUMMY_WEB_SERVICE_MOUNT_POINT ).usingDataDir( folder.getRoot().getAbsolutePath() ).build(); server.start(); } diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/ExclusiveServerTestBase.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/ExclusiveServerTestBase.java new file mode 100644 index 0000000000000..9855c02e492b4 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/ExclusiveServerTestBase.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server; + +import java.util.concurrent.Callable; + +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.TestName; + +import org.neo4j.test.SuppressOutput; +import org.neo4j.test.TargetDirectory; + +import static org.neo4j.test.SuppressOutput.suppressAll; + +public class ExclusiveServerTestBase +{ + @Rule + public TargetDirectory.TestDirectory folder = TargetDirectory.testDirForTest( getClass() ); + @Rule + public SuppressOutput suppressOutput = suppressAll(); + @Rule + public TestName name = new TestName(); + + @BeforeClass + public static void ensureServerNotRunning() throws Exception + { + System.setProperty( "org.neo4j.useInsecureCertificateGeneration", "true" ); + suppressAll().call( new Callable() + { + @Override + public Void call() throws Exception + { + ServerHolder.ensureNotRunning(); + return null; + } + } ); + } +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/HTTP.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/HTTP.java new file mode 100644 index 0000000000000..8aa0ecc064038 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/HTTP.java @@ -0,0 +1,369 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server; + +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.MediaType; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientRequest; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.config.ClientConfig; +import com.sun.jersey.api.client.config.DefaultClientConfig; +import org.codehaus.jackson.JsonNode; + +import org.neo4j.helpers.collection.Iterables; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; + +import static java.util.Collections.unmodifiableMap; + +import static org.hamcrest.Matchers.allOf; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.not; +import static org.junit.Assert.assertThat; + +import static org.neo4j.helpers.collection.MapUtil.stringMap; +import static org.neo4j.server.rest.domain.JsonHelper.createJsonFrom; + +/** + * A tool for performing REST HTTP requests + */ +public class HTTP +{ + + private static final Builder BUILDER = new Builder().withHeaders( "Accept", "application/json" ); + private static final Client CLIENT; + static { + DefaultClientConfig defaultClientConfig = new DefaultClientConfig(); + defaultClientConfig.getProperties().put( ClientConfig.PROPERTY_FOLLOW_REDIRECTS, Boolean.FALSE ); + CLIENT = Client.create( defaultClientConfig ); + } + + public static Builder withHeaders( Map headers ) + { + return BUILDER.withHeaders( headers ); + } + + public static Builder withHeaders( String... kvPairs ) + { + return BUILDER.withHeaders( kvPairs ); + } + + public static Builder withBaseUri( String baseUri ) + { + return BUILDER.withBaseUri( baseUri ); + } + + public static Response POST( String uri ) + { + return BUILDER.POST( uri ); + } + + public static Response POST( String uri, Object payload ) + { + return BUILDER.POST( uri, payload ); + } + + public static Response POST( String uri, RawPayload payload ) + { + return BUILDER.POST( uri, payload ); + } + + public static Response PUT( String uri ) + { + return BUILDER.PUT( uri ); + } + + public static Response PUT( String uri, Object payload ) + { + return BUILDER.PUT( uri, payload ); + } + + public static Response PUT( String uri, RawPayload payload ) + { + return BUILDER.PUT( uri, payload ); + } + + public static Response DELETE( String uri ) + { + return BUILDER.DELETE( uri ); + } + + public static Response GET( String uri ) + { + return BUILDER.GET( uri ); + } + + public static Response request( String method, String uri ) + { + return BUILDER.request( method, uri ); + } + + public static Response request( String method, String uri, Object payload ) + { + return BUILDER.request( method, uri, payload ); + } + + public static class Builder + { + private final Map headers; + private final String baseUri; + + private Builder() + { + this( Collections.emptyMap(), "" ); + } + + private Builder( Map headers, String baseUri ) + { + this.baseUri = baseUri; + this.headers = unmodifiableMap( headers ); + } + + public Builder withHeaders( String... kvPairs ) + { + return withHeaders( stringMap( kvPairs ) ); + } + + public Builder withHeaders( Map newHeaders ) + { + HashMap combined = new HashMap<>(); + combined.putAll( headers ); + combined.putAll( newHeaders ); + return new Builder( combined, baseUri ); + } + + public Builder withBaseUri( String baseUri ) + { + return new Builder( headers, baseUri ); + } + + public Response POST( String uri ) + { + return request( "POST", uri ); + } + + public Response POST( String uri, Object payload ) + { + return request( "POST", uri, payload ); + } + + public Response POST( String uri, RawPayload payload ) + { + return request( "POST", uri, payload ); + } + + public Response PUT( String uri ) + { + return request( "PUT", uri ); + } + + public Response PUT( String uri, Object payload ) + { + return request( "PUT", uri, payload ); + } + + public Response PUT( String uri, RawPayload payload ) + { + return request( "PUT", uri, payload ); + } + + public Response DELETE( String uri ) + { + return request( "DELETE", uri ); + } + + public Response GET( String uri ) + { + return request( "GET", uri ); + } + + public Response request( String method, String uri ) + { + return new Response( CLIENT.handle( build().build( buildUri( uri ), method ) ) ); + } + + public Response request( String method, String uri, Object payload ) + { + if(payload == null) + { + return request(method, uri); + } + String jsonPayload = payload instanceof RawPayload ? ((RawPayload) payload).get() : createJsonFrom( + payload ); + ClientRequest.Builder lastBuilder = build().entity( jsonPayload, MediaType.APPLICATION_JSON_TYPE ); + + return new Response( CLIENT.handle( lastBuilder.build( buildUri( uri ), method ) ) ); + } + + private URI buildUri( String uri ) + { + URI unprefixedUri = URI.create( uri ); + if ( unprefixedUri.isAbsolute() ) + { + return unprefixedUri; + } + else + { + return URI.create( baseUri + uri ); + } + } + + private ClientRequest.Builder build() + { + ClientRequest.Builder builder = ClientRequest.create(); + for ( Map.Entry header : headers.entrySet() ) + { + builder = builder.header( header.getKey(), header.getValue() ); + } + + return builder; + } + } + + /** + * Check some general validations that all REST responses should always pass. + */ + public static ClientResponse sanityCheck( ClientResponse response ) + { + List contentEncodings = response.getHeaders().get( "Content-Encoding" ); + String contentEncoding; + if ( contentEncodings != null && (contentEncoding = Iterables.singleOrNull( contentEncodings )) != null ) + { + // Specifically, this is never used for character encoding. + contentEncoding = contentEncoding.toLowerCase(); + assertThat( contentEncoding, anyOf( + containsString( "gzip" ), + containsString( "deflate" ) ) ); + assertThat( contentEncoding, allOf( + not( containsString( "utf-8" ) ) ) ); + } + return response; + } + + public static class Response + { + private final ClientResponse response; + private final String entity; + + public Response( ClientResponse response ) + { + this.response = sanityCheck( response ); + this.entity = response.getEntity( String.class ); + } + + public int status() + { + return response.getStatus(); + } + + public String location() + { + if ( response.getLocation() != null ) + { + return response.getLocation().toString(); + } + throw new RuntimeException( "The request did not contain a location header, " + + "unable to provide location. Status code was: " + status() ); + } + + @SuppressWarnings("unchecked") + public T content() + { + try + { + return (T) JsonHelper.readJson( entity ); + } + catch ( JsonParseException e ) + { + throw new RuntimeException( "Unable to deserialize: " + entity, e ); + } + } + + public String rawContent() + { + return entity; + } + + public String stringFromContent( String key ) throws JsonParseException + { + return get(key).asText(); + } + + public JsonNode get(String fieldName) throws JsonParseException + { + return JsonHelper.jsonNode( entity ).get( fieldName ); + } + + public String header( String name ) + { + return response.getHeaders().getFirst( name ); + } + + @Override + public String toString() + { + StringBuilder sb = new StringBuilder(); + sb.append( "HTTP " ).append( response.getStatus() ).append( "\n" ); + for ( Map.Entry> header : response.getHeaders().entrySet() ) + { + for ( String headerEntry : header.getValue() ) + { + sb.append( header.getKey() + ": " ).append( headerEntry ).append( "\n" ); + } + } + sb.append( "\n" ); + sb.append( entity ).append( "\n" ); + + return sb.toString(); + } + } + + public static class RawPayload + { + private final String payload; + + public static RawPayload rawPayload( String payload ) + { + return new RawPayload( payload ); + } + + public static RawPayload quotedJson( String json ) + { + return new RawPayload( json.replaceAll( "'", "\"" ) ); + } + + private RawPayload( String payload ) + { + this.payload = payload; + } + + public String get() + { + return payload; + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerDefaultPortAndHostnameDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerDefaultPortAndHostnameDocIT.java similarity index 88% rename from community/server/src/test/java/org/neo4j/server/NeoServerDefaultPortAndHostnameDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerDefaultPortAndHostnameDocIT.java index 6453f3af4b265..b093975be305a 100644 --- a/community/server/src/test/java/org/neo4j/server/NeoServerDefaultPortAndHostnameDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerDefaultPortAndHostnameDocIT.java @@ -17,13 +17,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server; +package org.neo4j.doc.server; import org.junit.Test; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.AbstractRestFunctionalTestBase; -import org.neo4j.server.rest.JaxRsResponse; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.AbstractRestFunctionalTestBase; +import org.neo4j.doc.server.rest.JaxRsResponse; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertThat; diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerDocIT.java similarity index 93% rename from community/server/src/test/java/org/neo4j/server/NeoServerDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerDocIT.java index 8770a6b2107d8..0ad1f6b2b52a7 100644 --- a/community/server/src/test/java/org/neo4j/server/NeoServerDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerDocIT.java @@ -17,15 +17,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server; +package org.neo4j.doc.server; import org.junit.Test; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; -import org.neo4j.server.rest.AbstractRestFunctionalTestBase; -import org.neo4j.test.server.HTTP; +import org.neo4j.doc.server.rest.AbstractRestFunctionalTestBase; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertFalse; diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerJAXRSDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerJAXRSDocIT.java similarity index 86% rename from community/server/src/test/java/org/neo4j/server/NeoServerJAXRSDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerJAXRSDocIT.java index 56820b04574de..24e193044ce2f 100644 --- a/community/server/src/test/java/org/neo4j/server/NeoServerJAXRSDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerJAXRSDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server; +package org.neo4j.doc.server; import java.net.URI; @@ -26,22 +26,20 @@ import org.junit.Before; import org.junit.Test; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.helpers.ServerHelper; +import org.neo4j.doc.server.helpers.UnitOfWork; +import org.neo4j.doc.server.rest.JaxRsResponse; +import org.neo4j.doc.server.rest.RestRequest; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.RelationshipType; import org.neo4j.kernel.internal.GraphDatabaseAPI; -import org.neo4j.server.helpers.CommunityServerBuilder; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.helpers.ServerHelper; -import org.neo4j.server.helpers.Transactor; -import org.neo4j.server.helpers.UnitOfWork; -import org.neo4j.server.rest.JaxRsResponse; -import org.neo4j.server.rest.RestRequest; -import org.neo4j.test.server.ExclusiveServerTestBase; +import org.neo4j.server.NeoServer; +import org.neo4j.doc.server.helpers.Transactor; import static org.junit.Assert.assertEquals; -import static org.neo4j.server.helpers.FunctionalTestHelper.CLIENT; - public class NeoServerJAXRSDocIT extends ExclusiveServerTestBase { private NeoServer server; @@ -85,7 +83,7 @@ public void shouldLoadThirdPartyJaxRsClasses() throws Exception URI thirdPartyServiceUri = new URI( server.baseUri() .toString() + DummyThirdPartyWebService.DUMMY_WEB_SERVICE_MOUNT_POINT ).normalize(); - String response = CLIENT.resource( thirdPartyServiceUri.toString() ) + String response = FunctionalTestHelper.CLIENT.resource( thirdPartyServiceUri.toString() ) .get( String.class ); assertEquals( "hello", response ); @@ -93,7 +91,7 @@ public void shouldLoadThirdPartyJaxRsClasses() throws Exception int nodesCreated = createSimpleDatabase( server.getDatabase().getGraph() ); thirdPartyServiceUri = new URI( server.baseUri() .toString() + DummyThirdPartyWebService.DUMMY_WEB_SERVICE_MOUNT_POINT + "/inject-test" ).normalize(); - response = CLIENT.resource( thirdPartyServiceUri.toString() ) + response = FunctionalTestHelper.CLIENT.resource( thirdPartyServiceUri.toString() ) .get( String.class ); assertEquals( String.valueOf( nodesCreated ), response ); } diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerPortConflictDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerPortConflictDocIT.java similarity index 96% rename from community/server/src/test/java/org/neo4j/server/NeoServerPortConflictDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerPortConflictDocIT.java index a2e9e7e05acfe..ba1ccb153fced 100644 --- a/community/server/src/test/java/org/neo4j/server/NeoServerPortConflictDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerPortConflictDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server; +package org.neo4j.doc.server; import java.io.IOException; import java.net.InetAddress; @@ -25,10 +25,11 @@ import org.junit.Test; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; import org.neo4j.helpers.HostnamePort; import org.neo4j.logging.AssertableLogProvider; -import org.neo4j.server.helpers.CommunityServerBuilder; -import org.neo4j.test.server.ExclusiveServerTestBase; +import org.neo4j.server.CommunityNeoServer; +import org.neo4j.server.ServerStartupException; import static java.lang.String.format; diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerShutdownLoggingDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerShutdownLoggingDocIT.java similarity index 92% rename from community/server/src/test/java/org/neo4j/server/NeoServerShutdownLoggingDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerShutdownLoggingDocIT.java index a09a2b1c151eb..3524ab56318af 100644 --- a/community/server/src/test/java/org/neo4j/server/NeoServerShutdownLoggingDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerShutdownLoggingDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server; +package org.neo4j.doc.server; import org.junit.After; import org.junit.Before; @@ -26,8 +26,8 @@ import java.io.IOException; import org.neo4j.logging.AssertableLogProvider; -import org.neo4j.server.helpers.ServerHelper; -import org.neo4j.test.server.ExclusiveServerTestBase; +import org.neo4j.server.NeoServer; +import org.neo4j.doc.server.helpers.ServerHelper; public class NeoServerShutdownLoggingDocIT extends ExclusiveServerTestBase { diff --git a/community/server/src/test/java/org/neo4j/server/NeoServerStartupLoggingDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerStartupLoggingDocIT.java similarity index 91% rename from community/server/src/test/java/org/neo4j/server/NeoServerStartupLoggingDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerStartupLoggingDocIT.java index 54ebcf7b1bb1e..55cf0bb0a8640 100644 --- a/community/server/src/test/java/org/neo4j/server/NeoServerStartupLoggingDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/NeoServerStartupLoggingDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server; +package org.neo4j.doc.server; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -28,11 +28,11 @@ import org.junit.BeforeClass; import org.junit.Test; +import org.neo4j.doc.server.rest.RestRequest; import org.neo4j.logging.FormattedLogProvider; -import org.neo4j.server.helpers.ServerHelper; -import org.neo4j.server.rest.JaxRsResponse; -import org.neo4j.server.rest.RestRequest; -import org.neo4j.test.server.ExclusiveServerTestBase; +import org.neo4j.server.NeoServer; +import org.neo4j.doc.server.helpers.ServerHelper; +import org.neo4j.doc.server.rest.JaxRsResponse; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.is; diff --git a/community/server/src/test/java/org/neo4j/server/ServerConfigDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/ServerConfigDocIT.java similarity index 96% rename from community/server/src/test/java/org/neo4j/server/ServerConfigDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/ServerConfigDocIT.java index a56d7d555f480..d53281bfb9b2c 100644 --- a/community/server/src/test/java/org/neo4j/server/ServerConfigDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/ServerConfigDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server; +package org.neo4j.doc.server; import java.io.IOException; import javax.ws.rs.core.MediaType; @@ -26,19 +26,19 @@ import org.junit.Test; import org.neo4j.helpers.HostnamePort; +import org.neo4j.server.CommunityNeoServer; import org.neo4j.server.configuration.ServerSettings; -import org.neo4j.server.rest.JaxRsResponse; -import org.neo4j.server.rest.RestRequest; +import org.neo4j.doc.server.rest.JaxRsResponse; +import org.neo4j.doc.server.rest.RestRequest; import org.neo4j.server.scripting.javascript.GlobalJavascriptInitializer; -import org.neo4j.test.server.ExclusiveServerTestBase; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertThat; -import static org.neo4j.server.helpers.CommunityServerBuilder.server; -import static org.neo4j.test.server.HTTP.POST; +import static org.neo4j.doc.server.helpers.CommunityServerBuilder.server; +import static org.neo4j.doc.server.HTTP.POST; public class ServerConfigDocIT extends ExclusiveServerTestBase { diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/ServerHolder.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/ServerHolder.java new file mode 100644 index 0000000000000..358959a6df916 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/ServerHolder.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server; + +import java.io.IOException; + +import org.neo4j.server.NeoServer; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; +import org.neo4j.doc.server.helpers.ServerHelper; + +final class ServerHolder extends Thread +{ + private static AssertionError allocation; + private static NeoServer server; + private static CommunityServerBuilder builder; + + static synchronized NeoServer allocate() throws IOException + { + if ( allocation != null ) throw allocation; + if ( server == null ) server = startServer(); + allocation = new AssertionError( "The server was allocated from here but not released properly" ); + return server; + } + + static synchronized void release( NeoServer server ) + { + if ( server == null ) return; + if ( server != ServerHolder.server ) + throw new AssertionError( "trying to suspend a server not allocated from here" ); + if ( allocation == null ) throw new AssertionError( "releasing the server although it is not allocated" ); + allocation = null; + } + + static synchronized void ensureNotRunning() + { + if ( allocation != null ) throw allocation; + shutdown(); + } + + static synchronized void setServerBuilderProperty( String key, String value ) + { + initBuilder(); + builder = builder.withProperty( key, value ); + } + + private static NeoServer startServer() throws IOException + { + initBuilder(); + return ServerHelper.createNonPersistentServer( builder ); + } + + private static synchronized void shutdown() + { + allocation = null; + try + { + if ( server != null ) server.stop(); + } + finally + { + builder = null; + server = null; + } + } + + private static void initBuilder() + { + if ( builder == null ) + { + builder = CommunityServerBuilder.server(); + } + } + + @Override + public void run() + { + shutdown(); + } + + static + { + Runtime.getRuntime().addShutdownHook( new ServerHolder() ); + } + + private ServerHolder() + { + super( ServerHolder.class.getName() ); + } +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/ServerTestUtils.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/ServerTestUtils.java new file mode 100644 index 0000000000000..aa125c5c11192 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/ServerTestUtils.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server; + +import java.io.Closeable; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.Map; +import java.util.Properties; + +import org.neo4j.dbms.DatabaseManagementSystemSettings; +import org.neo4j.graphdb.config.Setting; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.server.configuration.ServerSettings; + +public class ServerTestUtils +{ + public static File createTempDir() throws IOException + { + return Files.createTempDirectory( "neo4j-test" ).toFile(); + } + + public static File getSharedTestTemporaryFolder() + { + try + { + return createTempConfigFile().getParentFile(); + } + catch ( Exception e ) + { + throw new RuntimeException( e ); + } + } + + public static File createTempConfigFile() throws IOException + { + File file = File.createTempFile( "neo4j", "conf" ); + file.delete(); + return file; + } + + public static String getRelativePath( File folder, Setting setting ) + { + return folder.toPath().resolve( setting.getDefaultValue() ).toString(); + } + + public static void addDefaultRelativeProperties( Map properties, File temporaryFolder ) + { + addRelativeProperty( temporaryFolder, properties, DatabaseManagementSystemSettings.data_directory ); + addRelativeProperty( temporaryFolder, properties, GraphDatabaseSettings.logs_directory ); + addRelativeProperty( temporaryFolder, properties, ServerSettings.certificates_directory ); + } + + private static void addRelativeProperty( File temporaryFolder, Map properties, + Setting setting ) + { + properties.put( setting.name(), getRelativePath( temporaryFolder, setting ) ); + } + + public static void writeConfigToFile( Map properties, File file ) + { + Properties props = loadProperties( file ); + for ( Map.Entry entry : properties.entrySet() ) + { + props.setProperty( entry.getKey(), entry.getValue() ); + } + storeProperties( file, props ); + } + + public static String asOneLine( Map properties ) + { + StringBuilder builder = new StringBuilder(); + for ( Map.Entry property : properties.entrySet() ) + { + builder.append( ( builder.length() > 0 ? "," : "" ) ); + builder.append( property.getKey() ).append( "=" ).append( property.getValue() ); + } + return builder.toString(); + } + + private static void storeProperties( File file, Properties properties ) + { + OutputStream out = null; + try + { + out = new FileOutputStream( file ); + properties.store( out, "" ); + } + catch ( IOException e ) + { + throw new RuntimeException( e ); + } + finally + { + safeClose( out ); + } + } + + private static Properties loadProperties( File file ) + { + Properties properties = new Properties(); + if ( file.exists() ) + { + InputStream in = null; + try + { + in = new FileInputStream( file ); + properties.load( in ); + } + catch ( IOException e ) + { + throw new RuntimeException( e ); + } + finally + { + safeClose( in ); + } + } + return properties; + } + + private static void safeClose( Closeable closeable ) + { + if ( closeable != null ) + { + try + { + closeable.close(); + } + catch ( IOException e ) + { + e.printStackTrace(); + } + } + } + + public interface BlockWithCSVFileURL { + void execute(String url) throws Exception; + } + + public static void withCSVFile( int rowCount, BlockWithCSVFileURL block ) throws Exception + { + File file = File.createTempFile( "file", ".csv", null ); + try { + try ( PrintWriter writer = new PrintWriter( file ) ) { + for (int i = 0; i < rowCount; ++i) { + writer.println("1,2,3"); + } + } + + String url = file.toURI().toURL().toString().replace( "\\", "\\\\" ); + block.execute( url ); + } + finally + { + file.delete(); + } + } +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/SharedServerTestBase.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/SharedServerTestBase.java new file mode 100644 index 0000000000000..1f4d4be848090 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/SharedServerTestBase.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server; + +import java.util.concurrent.Callable; + +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Rule; + +import org.neo4j.doc.server.helpers.ServerHelper; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.server.NeoServer; +import org.neo4j.test.SuppressOutput; + +import static org.neo4j.test.SuppressOutput.suppressAll; + +public class SharedServerTestBase +{ + private static boolean useExternal = Boolean.valueOf( System.getProperty( "neo-server.external", "false" ) ); + + protected static NeoServer server() + { + return server; + } + + private static NeoServer server; + + @Rule + public SuppressOutput suppressOutput = suppressAll(); + + @BeforeClass + public static void allocateServer() throws Throwable + { + System.setProperty( "org.neo4j.useInsecureCertificateGeneration", "true" ); + if ( !useExternal ) + { + suppressAll().call( new Callable() + { + @Override + public Void call() throws Exception + { + ServerHolder.setServerBuilderProperty( GraphDatabaseSettings.cypher_hints_error.name(), "true" ); + server = ServerHolder.allocate(); + ServerHelper.cleanTheDatabase( server ); + return null; + } + } ); + } + } + + @AfterClass + public static void releaseServer() throws Exception + { + if ( !useExternal ) + { + try + { + suppressAll().call( new Callable() + { + @Override + public Void call() throws Exception + { + ServerHolder.release( server ); + return null; + } + } ); + } + finally + { + server = null; + } + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/TransactionTimeoutDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/TransactionTimeoutDocIT.java similarity index 92% rename from community/server/src/test/java/org/neo4j/server/TransactionTimeoutDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/TransactionTimeoutDocIT.java index c62dffb418c23..e72fd702820b3 100644 --- a/community/server/src/test/java/org/neo4j/server/TransactionTimeoutDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/TransactionTimeoutDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server; +package org.neo4j.doc.server; import java.util.List; import java.util.Map; @@ -25,16 +25,15 @@ import org.junit.After; import org.junit.Test; +import org.neo4j.server.CommunityNeoServer; import org.neo4j.server.configuration.ServerSettings; -import org.neo4j.test.server.ExclusiveServerTestBase; -import org.neo4j.test.server.HTTP; import static java.util.Arrays.asList; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.neo4j.helpers.collection.MapUtil.map; import static org.neo4j.kernel.api.exceptions.Status.Transaction.TransactionNotFound; -import static org.neo4j.server.helpers.CommunityServerBuilder.server; +import static org.neo4j.doc.server.helpers.CommunityServerBuilder.server; public class TransactionTimeoutDocIT extends ExclusiveServerTestBase { diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/CommunityServerBuilder.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/CommunityServerBuilder.java new file mode 100644 index 0000000000000..01f592784ee84 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/CommunityServerBuilder.java @@ -0,0 +1,373 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.helpers; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Properties; + +import org.neo4j.dbms.DatabaseManagementSystemSettings; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.helpers.Clock; +import org.neo4j.helpers.HostnamePort; +import org.neo4j.kernel.GraphDatabaseDependencies; +import org.neo4j.kernel.configuration.Config; +import org.neo4j.kernel.impl.factory.CommunityFacadeFactory; +import org.neo4j.kernel.impl.factory.GraphDatabaseFacadeFactory; +import org.neo4j.kernel.monitoring.Monitors; +import org.neo4j.logging.Log; +import org.neo4j.logging.LogProvider; +import org.neo4j.logging.NullLogProvider; +import org.neo4j.server.CommunityBootstrapper; +import org.neo4j.server.CommunityNeoServer; +import org.neo4j.doc.server.ServerTestUtils; +import org.neo4j.server.configuration.ConfigLoader; +import org.neo4j.server.configuration.ServerSettings; +import org.neo4j.server.database.Database; +import org.neo4j.server.database.LifecycleManagingDatabase; +import org.neo4j.server.preflight.PreFlightTasks; +import org.neo4j.server.preflight.PreflightTask; +import org.neo4j.server.rest.paging.LeaseManager; +import org.neo4j.server.rest.web.DatabaseActions; +import org.neo4j.test.ImpermanentGraphDatabase; + +import static org.neo4j.helpers.Clock.SYSTEM_CLOCK; +import static org.neo4j.helpers.collection.MapUtil.stringMap; +import static org.neo4j.doc.server.ServerTestUtils.asOneLine; +import static org.neo4j.server.configuration.ServerSettings.httpConnector; +import static org.neo4j.server.database.LifecycleManagingDatabase.lifecycleManagingDatabase; + +public class CommunityServerBuilder +{ + protected final LogProvider logProvider; + private HostnamePort address = new HostnamePort( "localhost", 7474 ); + private HostnamePort httpsAddress = new HostnamePort( "localhost", 7473 ); + private String maxThreads = null; + protected String dataDir = null; + private String managementUri = "/db/manage/"; + private String restUri = "/db/data/"; + protected PreFlightTasks preflightTasks; + private final HashMap thirdPartyPackages = new HashMap<>(); + private final Properties arbitraryProperties = new Properties(); + + public static LifecycleManagingDatabase.GraphFactory IN_MEMORY_DB = ( config, dependencies ) -> { + File storeDir = config.get( DatabaseManagementSystemSettings.database_path ); + Map params = config.getParams(); + params.put( CommunityFacadeFactory.Configuration.ephemeral.name(), "true" ); + return new ImpermanentGraphDatabase( storeDir, params, GraphDatabaseDependencies.newDependencies(dependencies) ); + }; + + protected Clock clock = null; + private String[] autoIndexedNodeKeys = null; + private final String[] autoIndexedRelationshipKeys = null; + private String[] securityRuleClassNames; + public boolean persistent; + private boolean httpsEnabled = false; + + public static CommunityServerBuilder server( LogProvider logProvider ) + { + return new CommunityServerBuilder( logProvider ); + } + + public static CommunityServerBuilder server() + { + return new CommunityServerBuilder( NullLogProvider.getInstance() ); + } + + public CommunityNeoServer build() throws IOException + { + if ( dataDir == null && persistent ) + { + throw new IllegalStateException( "Must specify path" ); + } + final Optional configFile = buildBefore(); + + Log log = logProvider.getLog( getClass() ); + Config config = new ConfigLoader( CommunityBootstrapper.settingsClasses ).loadConfig( configFile ); + config.setLogger( log ); + return build( configFile, config, GraphDatabaseDependencies.newDependencies().userLogProvider( logProvider ) + .monitors( new Monitors() ) ); + } + + protected CommunityNeoServer build( Optional configFile, Config config, + GraphDatabaseFacadeFactory.Dependencies dependencies ) + { + return new TestCommunityNeoServer( config, configFile, dependencies, logProvider ); + } + + public Optional createConfigFiles() throws IOException + { + File temporaryConfigFile = ServerTestUtils.createTempConfigFile(); + File temporaryFolder = ServerTestUtils.createTempDir(); + + ServerTestUtils.writeConfigToFile( createConfiguration( temporaryFolder ), temporaryConfigFile ); + + return Optional.of( temporaryConfigFile ); + } + + public CommunityServerBuilder withClock( Clock clock ) + { + this.clock = clock; + return this; + } + + private Map createConfiguration( File temporaryFolder ) + { + Map properties = stringMap( + ServerSettings.management_api_path.name(), managementUri, + ServerSettings.rest_api_path.name(), restUri ); + + ServerTestUtils.addDefaultRelativeProperties( properties, temporaryFolder ); + + if ( dataDir != null ) + { + properties.put( DatabaseManagementSystemSettings.data_directory.name(), dataDir ); + } + + if ( maxThreads != null ) + { + properties.put( ServerSettings.webserver_max_threads.name(), maxThreads ); + } + + if ( thirdPartyPackages.keySet().size() > 0 ) + { + properties.put( ServerSettings.third_party_packages.name(), asOneLine( thirdPartyPackages ) ); + } + + if ( autoIndexedNodeKeys != null && autoIndexedNodeKeys.length > 0 ) + { + properties.put( "dbms.auto_index.nodes.enabled", "true" ); + String propertyKeys = org.apache.commons.lang.StringUtils.join( autoIndexedNodeKeys, "," ); + properties.put( "dbms.auto_index.nodes.keys", propertyKeys ); + } + + if ( autoIndexedRelationshipKeys != null && autoIndexedRelationshipKeys.length > 0 ) + { + properties.put( "dbms.auto_index.relationships.enabled", "true" ); + String propertyKeys = org.apache.commons.lang.StringUtils.join( autoIndexedRelationshipKeys, "," ); + properties.put( "dbms.auto_index.relationships.keys", propertyKeys ); + } + + if ( securityRuleClassNames != null && securityRuleClassNames.length > 0 ) + { + String propertyKeys = org.apache.commons.lang.StringUtils.join( securityRuleClassNames, "," ); + properties.put( ServerSettings.security_rules.name(), propertyKeys ); + } + + properties.put( httpConnector("http").type.name(), "HTTP" ); + properties.put( httpConnector("http").enabled.name(), "true" ); + properties.put( httpConnector("http").address.name(), address.toString() ); + + if ( httpsEnabled ) + { + properties.put( httpConnector("https").type.name(), "HTTP" ); + properties.put( httpConnector("https").enabled.name(), "true" ); + properties.put( httpConnector("https").address.name(), httpsAddress.toString() ); + properties.put( httpConnector("https").encryption.name(), "TLS" ); + } + + properties.put( GraphDatabaseSettings.auth_enabled.name(), "false" ); + properties.put( ServerSettings.certificates_directory.name(), new File(temporaryFolder, "certificates").getAbsolutePath() ); + properties.put( GraphDatabaseSettings.logs_directory.name(), new File(temporaryFolder, "logs").getAbsolutePath() ); + properties.put( GraphDatabaseSettings.pagecache_memory.name(), "8m" ); + + for ( Object key : arbitraryProperties.keySet() ) + { + properties.put( String.valueOf( key ), String.valueOf( arbitraryProperties.get( key ) ) ); + } + return properties; + } + + protected CommunityServerBuilder( LogProvider logProvider ) + { + this.logProvider = logProvider; + } + + public CommunityServerBuilder persistent() + { + this.persistent = true; + return this; + } + + public CommunityServerBuilder withMaxJettyThreads( int maxThreads ) + { + this.maxThreads = String.valueOf( maxThreads ); + return this; + } + + public CommunityServerBuilder usingDataDir( String dataDir ) + { + this.dataDir = dataDir; + return this; + } + + public CommunityServerBuilder withRelativeManagementApiUriPath( String uri ) + { + try + { + URI theUri = new URI( uri ); + if ( theUri.isAbsolute() ) + { + this.managementUri = theUri.getPath(); + } + else + { + this.managementUri = theUri.toString(); + } + } + catch ( URISyntaxException e ) + { + throw new RuntimeException( e ); + } + return this; + } + + public CommunityServerBuilder withRelativeRestApiUriPath( String uri ) + { + try + { + URI theUri = new URI( uri ); + if ( theUri.isAbsolute() ) + { + this.restUri = theUri.getPath(); + } + else + { + this.restUri = theUri.toString(); + } + } + catch ( URISyntaxException e ) + { + throw new RuntimeException( e ); + } + return this; + } + + public CommunityServerBuilder withDefaultDatabaseTuning() + { + return this; + } + + public CommunityServerBuilder withThirdPartyJaxRsPackage( String packageName, String mountPoint ) + { + thirdPartyPackages.put( packageName, mountPoint ); + return this; + } + + public CommunityServerBuilder withAutoIndexingEnabledForNodes( String... keys ) + { + autoIndexedNodeKeys = keys; + return this; + } + + public CommunityServerBuilder onAddress( HostnamePort address ) + { + this.address = address; + return this; + } + + public CommunityServerBuilder onHttpsAddress( HostnamePort address ) + { + this.httpsAddress = address; + return this; + } + + public CommunityServerBuilder withSecurityRules( String... securityRuleClassNames ) + { + this.securityRuleClassNames = securityRuleClassNames; + return this; + } + + public CommunityServerBuilder withHttpsEnabled() + { + httpsEnabled = true; + return this; + } + + public CommunityServerBuilder withProperty( String key, String value ) + { + arbitraryProperties.put( key, value ); + return this; + } + + public CommunityServerBuilder withPreflightTasks( PreflightTask... tasks ) + { + this.preflightTasks = new PreFlightTasks( NullLogProvider.getInstance(), tasks ); + return this; + } + + protected DatabaseActions createDatabaseActionsObject( Database database, Config config ) + { + Clock clockToUse = (clock != null) ? clock : SYSTEM_CLOCK; + + return new DatabaseActions( + new LeaseManager( clockToUse ), + config.get( ServerSettings.script_sandboxing_enabled ), database.getGraph() ); + } + + protected Optional buildBefore() throws IOException + { + Optional configFile = createConfigFiles(); + + if ( preflightTasks == null ) + { + preflightTasks = new PreFlightTasks( NullLogProvider.getInstance() ) + { + @Override + public boolean run() + { + return true; + } + }; + } + return configFile; + } + + private class TestCommunityNeoServer extends CommunityNeoServer + { + private final Optional configFile; + + private TestCommunityNeoServer( Config config, Optional configFile, GraphDatabaseFacadeFactory + .Dependencies dependencies, LogProvider logProvider ) + { + super( config, lifecycleManagingDatabase( persistent ? COMMUNITY_FACTORY : IN_MEMORY_DB ), dependencies, + logProvider ); + this.configFile = configFile; + } + + @Override + protected DatabaseActions createDatabaseActions() + { + return createDatabaseActionsObject( database, getConfig() ); + } + + @Override + public void stop() + { + super.stop(); + configFile.ifPresent( File::delete ); + } + } +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/FunctionalTestHelper.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/FunctionalTestHelper.java new file mode 100644 index 0000000000000..f66975ecc7b66 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/FunctionalTestHelper.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.helpers; + +import java.net.URI; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +import com.sun.jersey.api.client.Client; +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.TypeSafeMatcher; + +import org.neo4j.doc.server.rest.domain.GraphDbHelper; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.server.NeoServer; +import org.neo4j.doc.server.rest.JaxRsResponse; +import org.neo4j.doc.server.rest.RestRequest; +import org.neo4j.server.rest.web.RestfulGraphDatabase; + +import static org.neo4j.server.rest.web.RestfulGraphDatabase.PATH_AUTO_INDEX; + +public final class FunctionalTestHelper +{ + private final NeoServer server; + private final GraphDbHelper helper; + + public static final Client CLIENT = Client.create(); + private RestRequest request; + + public FunctionalTestHelper( NeoServer server ) + { + if ( server.getDatabase() == null ) + { + throw new RuntimeException( "Server must be started before using " + getClass().getName() ); + } + this.helper = new GraphDbHelper( server.getDatabase() ); + this.server = server; + this.request = new RestRequest(server.baseUri().resolve("db/data/")); + } + + public static Matcher arrayContains( final String element ) + { + return new TypeSafeMatcher() + { + private String[] array; + + @Override + public void describeTo( Description descr ) + { + descr.appendText( "The array " ) + .appendText( Arrays.toString( array ) ) + .appendText( " does not contain <" ) + .appendText( element ) + .appendText( ">" ); + } + + @Override + public boolean matchesSafely( String[] array ) + { + this.array = array; + for ( String string : array ) + { + if ( element == null ) + { + if ( string == null ) return true; + } + else if ( element.equals( string ) ) + { + return true; + } + } + return false; + } + }; + } + + public GraphDbHelper getGraphDbHelper() + { + return helper; + } + + public String dataUri() + { + return server.baseUri().toString() + "db/data/"; + } + + public String nodeUri() + { + return dataUri() + "node"; + } + + public String nodeUri( long id ) + { + return nodeUri() + "/" + id; + } + + public String nodePropertiesUri( long id ) + { + return nodeUri( id ) + "/properties"; + } + + public String nodePropertyUri( long id, String key ) + { + return nodePropertiesUri( id ) + "/" + key; + } + + String relationshipUri() + { + return dataUri() + "relationship"; + } + + public String relationshipUri( long id ) + { + return relationshipUri() + "/" + id; + } + + public String relationshipPropertiesUri( long id ) + { + return relationshipUri( id ) + "/properties"; + } + + String relationshipPropertyUri( long id, String key ) + { + return relationshipPropertiesUri( id ) + "/" + key; + } + + public String relationshipsUri( long nodeId, String dir, String... types ) + { + StringBuilder typesString = new StringBuilder(); + for ( String type : types ) + { + typesString.append( typesString.length() > 0 ? "&" : "" ); + typesString.append( type ); + } + return nodeUri( nodeId ) + "/relationships/" + dir + "/" + typesString; + } + + public String indexUri() + { + return dataUri() + "index/"; + } + + String nodeAutoIndexUri() + { + return indexUri() + "auto/node/"; + } + + String relationshipAutoIndexUri() + { + return indexUri() + "auto/relationship/"; + } + + public String nodeIndexUri() + { + return indexUri() + "node/"; + } + + public String relationshipIndexUri() + { + return indexUri() + "relationship/"; + } + + public String managementUri() + { + return server.baseUri() + .toString() + "db/manage"; + } + + public String indexNodeUri( String indexName ) + { + return nodeIndexUri() + indexName; + } + + public String indexNodeUri( String indexName, String key, Object value ) + { + return indexNodeUri( indexName ) + "/" + key + "/" + value; + } + + + public String indexRelationshipUri( String indexName ) + { + return relationshipIndexUri() + indexName; + } + + public String indexRelationshipUri( String indexName, String key, Object value ) + { + return indexRelationshipUri( indexName ) + "/" + key + "/" + value; + } + + public String extensionUri() + { + return dataUri() + "ext"; + } + + String extensionUri( String name ) + { + return extensionUri() + "/" + name; + } + + String graphdbExtensionUri( String name, String method ) + { + return extensionUri( name ) + "/graphdb/" + method; + } + + String nodeExtensionUri( String name, String method, long id ) + { + return extensionUri( name ) + "/node/" + id + "/" + method; + } + + String relationshipExtensionUri( String name, String method, long id ) + { + return extensionUri( name ) + "/relationship/" + id + "/" + method; + } + + public GraphDatabaseAPI getDatabase() + { + return server.getDatabase().getGraph(); + } + + public JaxRsResponse get(String path) { + return request.get(path); + } + + public JaxRsResponse get(String path, String data) { + return request.get(path, data); + } + + public JaxRsResponse delete(String path) { + return request.delete(path); + } + + public JaxRsResponse post(String path, String data) { + return request.post(path, data); + } + + public void put(String path, String data) { + request.put(path, data); + } + + public long getNodeIdFromUri( String nodeUri ) + { + return Long.valueOf( nodeUri.substring( nodeUri.lastIndexOf( "/" ) +1 , nodeUri.length() ) ); + } + + public long getRelationshipIdFromUri( String relationshipUri ) + { + return getNodeIdFromUri( relationshipUri ); + } + + public Map removeAnyAutoIndex( Map map ) + { + Map result = new HashMap(); + for ( Map.Entry entry : map.entrySet() ) + { + Map innerMap = (Map) entry.getValue(); + String template = innerMap.get( "template" ).toString(); + if ( !template.contains( PATH_AUTO_INDEX.replace("{type}", RestfulGraphDatabase.NODE_AUTO_INDEX_TYPE) ) && + !template.contains( PATH_AUTO_INDEX.replace("{type}", RestfulGraphDatabase.RELATIONSHIP_AUTO_INDEX_TYPE) ) && + !template.contains( "_auto_" ) ) + result.put( entry.getKey(), entry.getValue() ); + } + return result; + } + + public URI baseUri() + { + return server.baseUri(); + } + + public String browserUri() + { + return server.baseUri().toString() + "browser/"; + } +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/ServerHelper.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/ServerHelper.java new file mode 100644 index 0000000000000..a8fb076fa982e --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/ServerHelper.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.helpers; + +import java.io.File; +import java.io.IOException; +import java.net.InetAddress; +import java.net.ServerSocket; + +import org.apache.commons.io.FileUtils; + +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.factory.GraphDatabaseSettings; +import org.neo4j.graphdb.index.IndexManager; +import org.neo4j.graphdb.schema.ConstraintDefinition; +import org.neo4j.graphdb.schema.IndexDefinition; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.logging.LogProvider; +import org.neo4j.server.NeoServer; + +public class ServerHelper +{ + public static void cleanTheDatabase( final NeoServer server ) + { + if ( server == null ) + { + return; + } + + rollbackAllOpenTransactions( server ); + + cleanTheDatabase( server.getDatabase().getGraph() ); + + removeLogs( server ); + } + + public static void cleanTheDatabase( GraphDatabaseAPI db ) + { + new Transactor( db, new DeleteAllData( db ), 10 ).execute(); + new Transactor( db, new DeleteAllSchema( db ), 10 ).execute(); + } + + private static void removeLogs( NeoServer server ) + { + File logDir = new File( server.getDatabase().getLocation() + File.separator + ".." + File.separator + "log" ); + try + { + FileUtils.deleteDirectory( logDir ); + } + catch ( IOException e ) + { + throw new RuntimeException( e ); + } + } + + public static NeoServer createNonPersistentServer() throws IOException + { + return createServer( CommunityServerBuilder.server(), false, null ); + } + + public static NeoServer createNonPersistentReadOnlyServer() throws IOException + { + CommunityServerBuilder builder = CommunityServerBuilder.server(); + builder.withProperty( GraphDatabaseSettings.read_only.name(), "true" ); + return createServer( builder, false, null ); + } + + public static NeoServer createNonPersistentServer( LogProvider logProvider ) throws IOException + { + return createServer( CommunityServerBuilder.server( logProvider ), false, null ); + } + + public static NeoServer createNonPersistentServer( CommunityServerBuilder builder ) throws IOException + { + return createServer( builder, false, null ); + } + + private static NeoServer createServer( CommunityServerBuilder builder, boolean persistent, File path ) + throws IOException + { + if ( persistent ) + { + builder = builder.persistent(); + } + NeoServer server = builder + .usingDataDir( path != null ? path.getAbsolutePath() : null ) + .build(); + + checkServerCanStart( server.baseUri().getHost(), server.baseUri().getPort() ); + + server.start(); + return server; + } + + private static void checkServerCanStart( String host, int port ) throws IOException + { + ServerSocket serverSocket = null; + try + { + serverSocket = new ServerSocket( port, 1, InetAddress.getByName( host ) ); + } + catch ( IOException ex ) + { + throw new RuntimeException( "Unable to start server on " + host + ":" + port, ex ); + } + finally + { + if ( serverSocket != null ) + { + serverSocket.close(); + } + } + } + + private static void rollbackAllOpenTransactions( NeoServer server ) + { + server.getTransactionRegistry().rollbackAllSuspendedTransactions(); + } + + private static class DeleteAllData implements UnitOfWork + { + private final GraphDatabaseAPI db; + + public DeleteAllData( GraphDatabaseAPI db ) + { + this.db = db; + } + + @Override + public void doWork() + { + deleteAllNodesAndRelationships(); + deleteAllIndexes(); + } + + private void deleteAllNodesAndRelationships() + { + Iterable allNodes = db.getAllNodes(); + for ( Node n : allNodes ) + { + Iterable relationships = n.getRelationships(); + for ( Relationship rel : relationships ) + { + rel.delete(); + } + n.delete(); + } + } + + private void deleteAllIndexes() + { + IndexManager indexManager = db.index(); + + for ( String indexName : indexManager.nodeIndexNames() ) + { + try + { + db.index() + .forNodes( indexName ) + .delete(); + } + catch ( UnsupportedOperationException e ) + { + // Encountered a read-only index. + } + } + + for ( String indexName : indexManager.relationshipIndexNames() ) + { + try + { + db.index() + .forRelationships( indexName ) + .delete(); + } + catch ( UnsupportedOperationException e ) + { + // Encountered a read-only index. + } + } + + for ( String k : indexManager.getNodeAutoIndexer().getAutoIndexedProperties() ) + { + indexManager.getNodeAutoIndexer().stopAutoIndexingProperty( k ); + } + indexManager.getNodeAutoIndexer().setEnabled( false ); + + for ( String k : indexManager.getRelationshipAutoIndexer().getAutoIndexedProperties() ) + { + indexManager.getRelationshipAutoIndexer().stopAutoIndexingProperty( k ); + } + indexManager.getRelationshipAutoIndexer().setEnabled( false ); + } + } + + private static class DeleteAllSchema implements UnitOfWork + { + private final GraphDatabaseAPI db; + + public DeleteAllSchema( GraphDatabaseAPI db ) + { + this.db = db; + } + + @Override + public void doWork() + { + deleteAllIndexRules(); + deleteAllConstraints(); + } + + private void deleteAllIndexRules() + { + for ( IndexDefinition index : db.schema().getIndexes() ) + { + if ( !index.isConstraintIndex() ) + { + index.drop(); + } + } + } + + private void deleteAllConstraints() + { + for ( ConstraintDefinition constraint : db.schema().getConstraints() ) + { + constraint.drop(); + } + } + } +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/Transactor.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/Transactor.java new file mode 100644 index 0000000000000..d516aad10bcda --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/Transactor.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.helpers; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Transaction; + +public class Transactor +{ + + private final UnitOfWork unitOfWork; + private final GraphDatabaseService graphDb; + private final int attempts; // how many times to try, if the transaction fails for some reason + + public Transactor( GraphDatabaseService graphDb, UnitOfWork unitOfWork ) + { + this( graphDb, unitOfWork, 1 ); + } + + public Transactor( GraphDatabaseService graphDb, UnitOfWork unitOfWork, int attempts ) + { + assert attempts > 0 : "The Transactor should make at least one attempt at running the transaction."; + this.unitOfWork = unitOfWork; + this.graphDb = graphDb; + this.attempts = attempts; + } + + public void execute() + { + for ( int attemptsLeft = attempts - 1; attemptsLeft >= 0; attemptsLeft-- ) + { + try ( Transaction tx = graphDb.beginTx() ) + { + unitOfWork.doWork(); + tx.success(); + } + catch ( RuntimeException e ) + { + if ( attemptsLeft == 0 ) + { + throw e; + } + } + } + } + +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/UnitOfWork.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/UnitOfWork.java new file mode 100644 index 0000000000000..c8a2d743e0c7d --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/helpers/UnitOfWork.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.helpers; + +public interface UnitOfWork +{ + void doWork(); +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AbstractRestFunctionalDocTestBase.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AbstractRestFunctionalDocTestBase.java new file mode 100644 index 0000000000000..1d0a99db0bf61 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AbstractRestFunctionalDocTestBase.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest; + +import org.neo4j.graphdb.Transaction; + +import org.junit.Before; + +/** + * Remove nodes and relationships between tests. + */ +public class AbstractRestFunctionalDocTestBase extends AbstractRestFunctionalTestBase +{ + @Before + public void removeNodesAndRelationships() + { + try ( Transaction tx = graphdb().beginTx() ) + { + graphdb().execute( "MATCH (n) DETACH DELETE n" ); + tx.success(); + } + } +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AbstractRestFunctionalTestBase.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AbstractRestFunctionalTestBase.java new file mode 100644 index 0000000000000..5dfa8145234a1 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AbstractRestFunctionalTestBase.java @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest; + +import java.io.UnsupportedEncodingException; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Map; +import javax.ws.rs.core.Response.Status; + +import org.junit.Before; +import org.junit.Rule; + +import org.neo4j.graphdb.GraphDatabaseService; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.Relationship; +import org.neo4j.helpers.collection.Pair; +import org.neo4j.kernel.internal.GraphDatabaseAPI; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; +import org.neo4j.test.GraphDescription; +import org.neo4j.test.GraphHolder; +import org.neo4j.test.TestData; +import org.neo4j.doc.server.HTTP; +import org.neo4j.doc.server.SharedServerTestBase; +import org.neo4j.visualization.asciidoc.AsciidocHelper; + +import static java.lang.String.format; +import static java.net.URLEncoder.encode; + +import static org.junit.Assert.assertEquals; + +import static org.neo4j.server.rest.domain.JsonHelper.createJsonFrom; +import static org.neo4j.server.rest.web.Surface.PATH_NODES; +import static org.neo4j.server.rest.web.Surface.PATH_NODE_INDEX; +import static org.neo4j.server.rest.web.Surface.PATH_RELATIONSHIPS; +import static org.neo4j.server.rest.web.Surface.PATH_RELATIONSHIP_INDEX; +import static org.neo4j.server.rest.web.Surface.PATH_SCHEMA_CONSTRAINT; +import static org.neo4j.server.rest.web.Surface.PATH_SCHEMA_INDEX; +import static org.neo4j.server.rest.web.Surface.PATH_SCHEMA_RELATIONSHIP_CONSTRAINT; + +public class AbstractRestFunctionalTestBase extends SharedServerTestBase implements GraphHolder +{ + protected static final String NODES = "http://localhost:7474/db/data/node/"; + + public @Rule + TestData> data = TestData.producedThrough( GraphDescription.createGraphFor( this, true ) ); + + public @Rule + TestData gen = TestData.producedThrough( RESTDocsGenerator.PRODUCER ); + + @Before + public void setUp() + { + gen().setSection( getDocumentationSectionName() ); + gen().setGraph( graphdb() ); + } + + @SafeVarargs + public final String doCypherRestCall( String endpoint, String scriptTemplate, Status status, + Pair... params ) + { + String parameterString = createParameterString( params ); + + return doCypherRestCall( endpoint, scriptTemplate, status, parameterString ); + } + + public String doCypherRestCall( String endpoint, String scriptTemplate, Status status, String parameterString ) + { + data.get(); + + String script = createScript( scriptTemplate ); + String queryString = "{\"query\": \"" + script + "\",\"params\":{" + parameterString + "}}"; + + String snippet = org.neo4j.cypher.internal.compiler.v3_0.prettifier.Prettifier$.MODULE$.apply( script ); + gen().expectedStatus( status.getStatusCode() ) + .payload( queryString ) + .description( AsciidocHelper.createAsciiDocSnippet( "cypher", snippet ) ); + return gen().post( endpoint ).entity(); + } + + private Long idFor( String name ) + { + return data.get().get( name ).getId(); + } + + private String createParameterString( Pair[] params ) + { + String paramString = ""; + for ( Pair param : params ) + { + String delimiter = paramString.isEmpty() || paramString.endsWith( "{" ) ? "" : ","; + + paramString += delimiter + "\"" + param.first() + "\":\"" + param.other() + "\""; + } + + return paramString; + } + + protected String createScript( String template ) + { + for ( String key : data.get().keySet() ) + { + template = template.replace( "%" + key + "%", idFor( key ).toString() ); + } + return template; + } + + protected String startGraph( String name ) + { + return AsciidocHelper.createGraphVizWithNodeId( "Starting Graph", graphdb(), name ); + } + + @Override + public GraphDatabaseService graphdb() + { + return server().getDatabase().getGraph(); + } + + public T resolveDependency( Class cls ) + { + return ((GraphDatabaseAPI)graphdb()).getDependencyResolver().resolveDependency( cls ); + } + + protected static String getDataUri() + { + return "http://localhost:7474/db/data/"; + } + + protected String getDatabaseUri() + { + return "http://localhost:7474/db/"; + } + + protected String getNodeUri( Node node ) + { + return getNodeUri(node.getId()); + } + + protected String getNodeUri( long node ) + { + return getDataUri() + PATH_NODES + "/" + node; + } + + protected String getRelationshipUri( Relationship relationship ) + { + return getDataUri() + PATH_RELATIONSHIPS + "/" + relationship.getId(); + } + + protected String postNodeIndexUri( String indexName ) + { + return getDataUri() + PATH_NODE_INDEX + "/" + indexName; + } + + protected String postRelationshipIndexUri( String indexName ) + { + return getDataUri() + PATH_RELATIONSHIP_INDEX + "/" + indexName; + } + + protected String txUri() + { + return getDataUri() + "transaction"; + } + + protected static String txCommitUri() + { + return getDataUri() + "transaction/commit"; + } + + protected String txUri( long txId ) + { + return getDataUri() + "transaction/" + txId; + } + + public static long extractTxId( HTTP.Response response ) + { + int lastSlash = response.location().lastIndexOf( "/" ); + String txIdString = response.location().substring( lastSlash + 1 ); + return Long.parseLong( txIdString ); + } + + protected Node getNode( String name ) + { + return data.get().get( name ); + } + + protected Node[] getNodes( String... names ) + { + Node[] nodes = {}; + ArrayList result = new ArrayList<>(); + for (String name : names) + { + result.add( getNode( name ) ); + } + return result.toArray( nodes ); + } + + public void assertSize( int expectedSize, String entity ) + { + Collection hits; + try + { + hits = (Collection) JsonHelper.readJson( entity ); + assertEquals( expectedSize, hits.size() ); + } + catch ( JsonParseException e ) + { + throw new RuntimeException( e ); + } + } + + public String getPropertiesUri( Relationship rel ) + { + return getRelationshipUri(rel)+ "/properties"; + } + public String getPropertiesUri( Node node ) + { + return getNodeUri( node )+ "/properties"; + } + + public RESTDocsGenerator gen() { + return gen.get(); + } + + public void description( String description ) + { + gen().description( description ); + + } + + protected String getDocumentationSectionName() { + return "dev/rest-api"; + } + + public String getLabelsUri() + { + return format( "%slabels", getDataUri() ); + } + + public String getPropertyKeysUri() + { + return format( "%spropertykeys", getDataUri() ); + } + + public String getNodesWithLabelUri( String label ) + { + return format( "%slabel/%s/nodes", getDataUri(), label ); + } + + public String getNodesWithLabelAndPropertyUri( String label, String property, Object value ) throws UnsupportedEncodingException + { + return format( "%slabel/%s/nodes?%s=%s", getDataUri(), label, property, + encode( createJsonFrom( value ), StandardCharsets.UTF_8.name() ) ); + } + + public String getSchemaIndexUri() + { + return getDataUri() + PATH_SCHEMA_INDEX; + } + + public String getSchemaIndexLabelUri( String label ) + { + return getDataUri() + PATH_SCHEMA_INDEX + "/" + label; + } + + public String getSchemaIndexLabelPropertyUri( String label, String property ) + { + return getDataUri() + PATH_SCHEMA_INDEX + "/" + label + "/" + property; + } + + public String getSchemaConstraintUri() + { + return getDataUri() + PATH_SCHEMA_CONSTRAINT; + } + + public String getSchemaConstraintLabelUri( String label ) + { + return getDataUri() + PATH_SCHEMA_CONSTRAINT + "/" + label; + } + + public String getSchemaConstraintLabelUniquenessUri( String label ) + { + return getDataUri() + PATH_SCHEMA_CONSTRAINT + "/" + label + "/uniqueness/"; + } + + public String getSchemaConstraintLabelExistenceUri( String label ) + { + return getDataUri() + PATH_SCHEMA_CONSTRAINT + "/" + label + "/existence/"; + } + + public String getSchemaRelationshipConstraintTypeExistenceUri( String type ) + { + return getDataUri() + PATH_SCHEMA_RELATIONSHIP_CONSTRAINT + "/" + type + "/existence/"; + } + + public String getSchemaConstraintLabelUniquenessPropertyUri( String label, String property ) + { + return getDataUri() + PATH_SCHEMA_CONSTRAINT + "/" + label + "/uniqueness/" + property; + } + + public String getSchemaConstraintLabelExistencePropertyUri( String label, String property ) + { + return getDataUri() + PATH_SCHEMA_CONSTRAINT + "/" + label + "/existence/" + property; + } + + public String getSchemaRelationshipConstraintTypeExistencePropertyUri( String type, String property ) + { + return getDataUri() + PATH_SCHEMA_RELATIONSHIP_CONSTRAINT + "/" + type + "/existence/" + property; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/AutoIndexDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AutoIndexDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/AutoIndexDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AutoIndexDocIT.java index bcdc103174b5e..8514ecc5a7f1d 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/AutoIndexDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AutoIndexDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/community/server/src/test/java/org/neo4j/server/rest/AutoIndexWithNonDefaultConfigurationThroughRESTAPIDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AutoIndexWithNonDefaultConfigurationThroughRESTAPIDocIT.java similarity index 93% rename from community/server/src/test/java/org/neo4j/server/rest/AutoIndexWithNonDefaultConfigurationThroughRESTAPIDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AutoIndexWithNonDefaultConfigurationThroughRESTAPIDocIT.java index 89238ed35d04d..2adcd5182cd59 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/AutoIndexWithNonDefaultConfigurationThroughRESTAPIDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/AutoIndexWithNonDefaultConfigurationThroughRESTAPIDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.AfterClass; import org.junit.Before; @@ -29,12 +29,12 @@ import java.io.IOException; +import org.neo4j.doc.server.ExclusiveServerTestBase; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; import org.neo4j.server.CommunityNeoServer; -import org.neo4j.server.helpers.CommunityServerBuilder; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.helpers.ServerHelper; +import org.neo4j.doc.server.helpers.ServerHelper; import org.neo4j.test.TestData; -import org.neo4j.test.server.ExclusiveServerTestBase; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertThat; diff --git a/community/server/src/test/java/org/neo4j/server/rest/BatchOperationDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/BatchOperationDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/BatchOperationDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/BatchOperationDocIT.java index fcdd0d46d6381..97f69f61ebe36 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/BatchOperationDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/BatchOperationDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.UniformInterfaceException; @@ -28,10 +28,10 @@ import java.util.List; import java.util.Map; +import org.neo4j.doc.server.ServerTestUtils; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Transaction; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.ServerTestUtils; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.test.GraphDescription.Graph; diff --git a/community/server/src/test/java/org/neo4j/server/rest/CompactJsonDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CompactJsonDocIT.java similarity index 96% rename from community/server/src/test/java/org/neo4j/server/rest/CompactJsonDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CompactJsonDocIT.java index 47c6e55f4c764..9fdd71ebbc413 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/CompactJsonDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CompactJsonDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Before; import org.junit.BeforeClass; @@ -27,8 +27,8 @@ import java.util.Collections; import javax.ws.rs.core.Response.Status; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.server.rest.repr.formats.CompactJsonFormat; diff --git a/community/server/src/test/java/org/neo4j/server/rest/ConfigureBaseUriDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/ConfigureBaseUriDocIT.java similarity index 98% rename from community/server/src/test/java/org/neo4j/server/rest/ConfigureBaseUriDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/ConfigureBaseUriDocIT.java index 8456d8d021853..60912aa80434f 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/ConfigureBaseUriDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/ConfigureBaseUriDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; @@ -29,7 +29,7 @@ import java.io.IOException; import java.net.URI; -import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; diff --git a/community/server/src/test/java/org/neo4j/server/rest/CreateRelationshipDocTest.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CreateRelationshipDocTest.java similarity index 97% rename from community/server/src/test/java/org/neo4j/server/rest/CreateRelationshipDocTest.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CreateRelationshipDocTest.java index b431599245da7..947ee8b0411b5 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/CreateRelationshipDocTest.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CreateRelationshipDocTest.java @@ -17,20 +17,19 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; - -import com.sun.jersey.api.client.ClientResponse.Status; -import org.junit.Test; +package org.neo4j.doc.server.rest; import java.util.Map; import javax.ws.rs.core.MediaType; +import com.sun.jersey.api.client.ClientResponse.Status; +import org.junit.Test; + import org.neo4j.graphdb.Node; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.Transaction; import org.neo4j.kernel.impl.annotations.Documented; import org.neo4j.server.rest.domain.JsonHelper; -import org.neo4j.server.rest.repr.RelationshipRepresentationTest; import org.neo4j.test.GraphDescription.Graph; import org.neo4j.test.TestData.Title; @@ -140,6 +139,6 @@ public void providing_bad_JSON() private void assertProperRelationshipRepresentation( Map relrep ) { - RelationshipRepresentationTest.verifySerialisation( relrep ); + RetrieveRelationshipsFromNodeDocIT.verifySerialisation( relrep ); } } diff --git a/community/server/src/test/java/org/neo4j/server/rest/CypherDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CypherDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/CypherDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CypherDocIT.java index c8fe07ce25ec6..48035e358f646 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/CypherDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CypherDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; @@ -52,7 +52,8 @@ import static org.junit.Assert.assertTrue; import static org.neo4j.server.rest.domain.JsonHelper.jsonToMap; -public class CypherDocIT extends AbstractRestFunctionalTestBase { +public class CypherDocIT extends AbstractRestFunctionalTestBase +{ @Test @Title( "Send a query" ) diff --git a/community/server/src/test/java/org/neo4j/server/rest/CypherSessionDocTest.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CypherSessionDocTest.java similarity index 98% rename from community/server/src/test/java/org/neo4j/server/rest/CypherSessionDocTest.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CypherSessionDocTest.java index ea2f8dc80680f..6730513a182a9 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/CypherSessionDocTest.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/CypherSessionDocTest.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/community/server/src/test/java/org/neo4j/server/rest/DatabaseMetadataServiceDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DatabaseMetadataServiceDocIT.java similarity index 93% rename from community/server/src/test/java/org/neo4j/server/rest/DatabaseMetadataServiceDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DatabaseMetadataServiceDocIT.java index 00feac8b06f76..28ca6a42c5069 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/DatabaseMetadataServiceDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DatabaseMetadataServiceDocIT.java @@ -17,16 +17,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.BeforeClass; import org.junit.Test; import java.io.IOException; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.domain.GraphDbHelper; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.Matchers.allOf; diff --git a/community/server/src/test/java/org/neo4j/server/rest/DegreeDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DegreeDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/DegreeDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DegreeDocIT.java index 9d10e92dd04a5..6f0b069e6a112 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/DegreeDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DegreeDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/community/server/src/test/java/org/neo4j/server/rest/DisableWADLDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DisableWADLDocIT.java similarity index 93% rename from community/server/src/test/java/org/neo4j/server/rest/DisableWADLDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DisableWADLDocIT.java index 8cf7d01671e4b..dd262f82ea76c 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/DisableWADLDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DisableWADLDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; @@ -29,8 +29,8 @@ import java.io.IOException; import java.net.URI; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import static org.junit.Assert.assertEquals; diff --git a/community/server/src/test/java/org/neo4j/server/rest/DiscoveryServiceDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DiscoveryServiceDocIT.java similarity index 98% rename from community/server/src/test/java/org/neo4j/server/rest/DiscoveryServiceDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DiscoveryServiceDocIT.java index 145351139255b..e49bc35eaae52 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/DiscoveryServiceDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DiscoveryServiceDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import com.sun.jersey.api.client.Client; import org.junit.Test; diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DocumentationData.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DocumentationData.java new file mode 100644 index 0000000000000..8c6523dd6fe0f --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/DocumentationData.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest; + +import java.util.Map; + +import javax.ws.rs.core.MediaType; + +public class DocumentationData +{ + private String payload; + private MediaType payloadType = MediaType.APPLICATION_JSON_TYPE; + public String title; + public String description; + public String uri; + public String method; + public int status; + public String entity; + public Map requestHeaders; + public Map responseHeaders; + public boolean ignore; + + public void setPayload( final String payload ) + { + this.payload = payload; + } + + public String getPayload() + { + if ( this.payload != null && !this.payload.trim() + .isEmpty() + && MediaType.APPLICATION_JSON_TYPE.equals( payloadType ) ) + { + return JSONPrettifier.parse( this.payload ); + } + else + { + return this.payload; + } + } + + public String getPrettifiedEntity() + { + return JSONPrettifier.parse( entity ); + } + + public void setPayloadType( final MediaType payloadType ) + { + this.payloadType = payloadType; + } + + public void setDescription( final String description ) + { + this.description = description; + } + + public void setTitle( final String title ) + { + this.title = title; + } + + + public void setUri( final String uri ) + { + this.uri = uri; + } + + public void setMethod( final String method ) + { + this.method = method; + } + + public void setStatus( final int responseCode ) + { + this.status = responseCode; + + } + + public void setEntity( final String entity ) + { + this.entity = entity; + } + + public void setResponseHeaders( final Map response ) + { + responseHeaders = response; + } + + public void setRequestHeaders( final Map request ) + { + requestHeaders = request; + } + + public void setIgnore() { + this.ignore = true; + } + + @Override + public String toString() + { + return "DocumentationData [payload=" + payload + ", title=" + title + ", description=" + description + + ", uri=" + uri + ", method=" + method + ", status=" + status + ", entity=" + entity + + ", requestHeaders=" + requestHeaders + ", responseHeaders=" + responseHeaders + "]"; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/GetIndexRootDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetIndexRootDocIT.java similarity index 97% rename from community/server/src/test/java/org/neo4j/server/rest/GetIndexRootDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetIndexRootDocIT.java index d13c8fcc443f1..62a9389390ec1 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/GetIndexRootDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetIndexRootDocIT.java @@ -17,14 +17,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.BeforeClass; import org.junit.Test; import java.io.IOException; -import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; import org.neo4j.server.rest.domain.JsonParseException; import static org.junit.Assert.assertEquals; diff --git a/community/server/src/test/java/org/neo4j/server/rest/GetNodePropertiesDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetNodePropertiesDocIT.java similarity index 98% rename from community/server/src/test/java/org/neo4j/server/rest/GetNodePropertiesDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetNodePropertiesDocIT.java index f46b2702bb195..36c38a46f9682 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/GetNodePropertiesDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetNodePropertiesDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.BeforeClass; import org.junit.Test; @@ -26,12 +26,12 @@ import java.util.Collections; import javax.ws.rs.core.MediaType; +import org.neo4j.doc.server.HTTP; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.helpers.FunctionalTestHelper; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; -import org.neo4j.test.server.HTTP; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/community/server/src/test/java/org/neo4j/server/rest/GetOnRootDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetOnRootDocIT.java similarity index 98% rename from community/server/src/test/java/org/neo4j/server/rest/GetOnRootDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetOnRootDocIT.java index 0d8ce958f6903..a1cb938e7cd13 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/GetOnRootDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetOnRootDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; @@ -25,7 +25,7 @@ import org.neo4j.kernel.internal.Version; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; +import org.neo4j.doc.server.rest.RESTDocsGenerator.ResponseEntity; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.repr.StreamingFormat; import org.neo4j.test.GraphDescription.Graph; diff --git a/community/server/src/test/java/org/neo4j/server/rest/GetRelationshipPropertiesDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetRelationshipPropertiesDocIT.java similarity index 93% rename from community/server/src/test/java/org/neo4j/server/rest/GetRelationshipPropertiesDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetRelationshipPropertiesDocIT.java index eb1bb3f8e617d..5faa1784646ef 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/GetRelationshipPropertiesDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/GetRelationshipPropertiesDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.hamcrest.MatcherAssert; import org.junit.BeforeClass; @@ -29,18 +29,17 @@ import java.util.Map; import javax.ws.rs.core.MediaType; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.doc.server.HTTP; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; -import org.neo4j.test.server.HTTP; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; -import static org.neo4j.test.server.HTTP.RawPayload.quotedJson; public class GetRelationshipPropertiesDocIT extends AbstractRestFunctionalTestBase { @@ -135,7 +134,7 @@ public void shouldReturnEmptyMapForEmptyProperties() throws Exception { // Given String node = HTTP.POST( server().baseUri().resolve( "db/data/node" ).toString() ).location(); - String rel = HTTP.POST( node + "/relationships", quotedJson( "{'to':'" + node + "', " + + String rel = HTTP.POST( node + "/relationships", HTTP.RawPayload.quotedJson( "{'to':'" + node + "', " + "'type':'LOVES'}" ) ).location(); // When diff --git a/community/server/src/test/java/org/neo4j/server/rest/HtmlDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/HtmlDocIT.java similarity index 95% rename from community/server/src/test/java/org/neo4j/server/rest/HtmlDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/HtmlDocIT.java index 1de6577ace8ed..692cd7b6c8a11 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/HtmlDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/HtmlDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Before; import org.junit.BeforeClass; @@ -28,10 +28,11 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response.Status; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.doc.server.HTTP; +import org.neo4j.doc.server.SharedServerTestBase; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.server.rest.domain.RelationshipDirection; -import org.neo4j.test.server.HTTP; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -49,7 +50,7 @@ public class HtmlDocIT extends AbstractRestFunctionalTestBase @BeforeClass public static void setupServer() throws IOException { - functionalTestHelper = new FunctionalTestHelper( server() ); + functionalTestHelper = new FunctionalTestHelper( SharedServerTestBase.server() ); helper = functionalTestHelper.getGraphDbHelper(); } diff --git a/community/server/src/test/java/org/neo4j/server/rest/IndexNodeDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/IndexNodeDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/IndexNodeDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/IndexNodeDocIT.java index a36cf6eeec5a5..d290fcbf29a10 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/IndexNodeDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/IndexNodeDocIT.java @@ -17,11 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; - -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.Test; +package org.neo4j.doc.server.rest; import java.net.URI; import java.util.Arrays; @@ -35,28 +31,33 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response.Status; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.RESTDocsGenerator.ResponseEntity; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.function.Factory; import org.neo4j.graphdb.GraphDatabaseService; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Transaction; import org.neo4j.helpers.collection.MapUtil; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; -import org.neo4j.server.rest.domain.GraphDbHelper; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.server.rest.domain.URIHelper; import static java.util.Collections.singletonList; + import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; + import static org.neo4j.graphdb.Neo4jMatchers.hasProperty; import static org.neo4j.graphdb.Neo4jMatchers.inTx; -import static org.neo4j.server.helpers.FunctionalTestHelper.CLIENT; public class IndexNodeDocIT extends AbstractRestFunctionalTestBase { @@ -641,7 +642,7 @@ public void shouldBeAbleToIndexValuesContainingSpaces() throws Exception assertEquals( 1, hits.size() ); response.close(); - CLIENT.resource( location ) + FunctionalTestHelper.CLIENT.resource( location ) .delete(); response = request.get( functionalTestHelper.indexNodeUri( indexName, key, URIHelper.encode( value ) ) ); hits = (Collection) JsonHelper.readJson( response.getEntity() ); diff --git a/community/server/src/test/java/org/neo4j/server/rest/IndexRelationshipDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/IndexRelationshipDocIT.java similarity index 98% rename from community/server/src/test/java/org/neo4j/server/rest/IndexRelationshipDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/IndexRelationshipDocIT.java index 60c3cfa108f94..d1e4f5327867e 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/IndexRelationshipDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/IndexRelationshipDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.BeforeClass; import org.junit.Test; @@ -40,9 +40,9 @@ import org.neo4j.graphdb.Transaction; import org.neo4j.helpers.collection.MapUtil; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; -import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.RESTDocsGenerator.ResponseEntity; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.server.rest.domain.URIHelper; @@ -51,7 +51,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; -import static org.neo4j.server.helpers.FunctionalTestHelper.CLIENT; +import static org.neo4j.doc.server.helpers.FunctionalTestHelper.CLIENT; public class IndexRelationshipDocIT extends AbstractRestFunctionalTestBase { diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/JSONPrettifier.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/JSONPrettifier.java new file mode 100644 index 0000000000000..0449338bd3c2e --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/JSONPrettifier.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest; + +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.ObjectWriter; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; + +/* + * Naive implementation of a JSON prettifier. + */ +public class JSONPrettifier +{ + private static final Gson GSON = new GsonBuilder().setPrettyPrinting() + .create(); + private static final JsonParser JSON_PARSER = new JsonParser(); + private static final ObjectMapper MAPPER = new ObjectMapper(); + private static final ObjectWriter WRITER = MAPPER.writerWithDefaultPrettyPrinter(); + + public static String parse( final String json ) + { + if ( json == null ) + { + return ""; + } + + String result = json; + + try + { + if ( json.contains( "\"exception\"" ) ) + { + // the gson renderer is much better for stacktraces + result = gsonPrettyPrint( json ); + } + else + { + result = jacksonPrettyPrint( json ); + } + } + catch ( Exception e ) + { + /* + * Enable the output to see where exceptions happen. + * We need to be able to tell the rest docs tools to expect + * a json parsing error from here, then we can simply throw an exception instead. + * (we have tests sending in broken json to test the response) + */ + // System.out.println( "***************************************" ); + // System.out.println( json ); + // System.out.println( "***************************************" ); + } + return result; + } + + private static String gsonPrettyPrint( final String json ) throws Exception + { + JsonElement element = JSON_PARSER.parse( json ); + return GSON.toJson( element ); + } + + private static String jacksonPrettyPrint( final String json ) + throws Exception + { + Object myObject = MAPPER.readValue( json, Object.class ); + return WRITER.writeValueAsString( myObject ); + } +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/JaxRsResponse.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/JaxRsResponse.java new file mode 100644 index 0000000000000..67cbe8c5338b4 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/JaxRsResponse.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest; + +import java.net.URI; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; + +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.core.util.StringKeyObjectValueIgnoreCaseMultivaluedMap; +import org.junit.Test; + +import org.neo4j.logging.NullLogProvider; +import org.neo4j.server.database.Database; +import org.neo4j.server.rest.management.AdvertisableService; +import org.neo4j.server.rest.management.console.ConsoleService; +import org.neo4j.server.rest.management.repr.ServerRootRepresentation; + +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.mockito.Mockito.mock; + +public class JaxRsResponse extends Response +{ + + private final int status; + private final MultivaluedMap metaData; + private final MultivaluedMap headers; + private final URI location; + private String data; + private MediaType type; + + public JaxRsResponse( ClientResponse response ) + { + this(response, extractContent(response)); + } + + private static String extractContent(ClientResponse response) { + if (response.getStatus() == Status.NO_CONTENT.getStatusCode()) return null; + return response.getEntity(String.class); + } + + public JaxRsResponse(ClientResponse response, String entity) { + status = response.getStatus(); + metaData = extractMetaData(response); + headers = extractHeaders(response); + location = response.getLocation(); + type = response.getType(); + data = entity; + response.close(); + } + + @Override + public String getEntity() + { + return data; + } + + @Override + public int getStatus() + { + return status; + } + + @Override + public MultivaluedMap getMetadata() + { + return metaData; + } + + private MultivaluedMap extractMetaData(ClientResponse jettyResponse) { + MultivaluedMap metadata = new StringKeyObjectValueIgnoreCaseMultivaluedMap(); + for ( Map.Entry> header : jettyResponse.getHeaders() + .entrySet() ) + { + for ( Object value : header.getValue() ) + { + metadata.putSingle( header.getKey(), value ); + } + } + return metadata; + } + + public MultivaluedMap getHeaders() + { + return headers; + } + + private MultivaluedMap extractHeaders(ClientResponse jettyResponse) { + return jettyResponse.getHeaders(); + } + + // new URI( getHeaders().get( HttpHeaders.LOCATION ).get(0)); + public URI getLocation() + { + return location; + } + + public void close() + { + + } + + public static JaxRsResponse extractFrom(ClientResponse clientResponse) { + return new JaxRsResponse(clientResponse); + } + + public MediaType getType() { + return type; + } + + + public static class ServerRootRepresentationTest + { + @Test + public void shouldProvideAListOfServiceUris() throws Exception + { + ConsoleService consoleService = new ConsoleService( null, mock( Database.class ), NullLogProvider.getInstance(), null ); + ServerRootRepresentation srr = new ServerRootRepresentation( new URI( "http://example.org:9999" ), + Collections.singletonList( consoleService ) ); + Map> map = srr.serialize(); + + assertNotNull( map.get( "services" ) ); + + assertThat( map.get( "services" ) + .get( consoleService.getName() ), containsString( consoleService.getServerPath() ) ); + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/JmxServiceDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/JmxServiceDocIT.java similarity index 95% rename from community/server/src/test/java/org/neo4j/server/rest/JmxServiceDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/JmxServiceDocIT.java index edc7a1189a4c8..6754adf886ac6 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/JmxServiceDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/JmxServiceDocIT.java @@ -17,14 +17,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import java.io.IOException; import org.junit.BeforeClass; import org.junit.Test; -import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; diff --git a/community/server/src/test/java/org/neo4j/server/rest/LabelsDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/LabelsDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/LabelsDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/LabelsDocIT.java index 6a1697db68f28..e6fefaa751646 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/LabelsDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/LabelsDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/community/server/src/test/java/org/neo4j/server/rest/ListPropertyKeysDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/ListPropertyKeysDocIT.java similarity index 98% rename from community/server/src/test/java/org/neo4j/server/rest/ListPropertyKeysDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/ListPropertyKeysDocIT.java index 0307840279183..6ef8fb2089659 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/ListPropertyKeysDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/ListPropertyKeysDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/community/server/src/test/java/org/neo4j/server/rest/ManageNodeDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/ManageNodeDocIT.java similarity index 91% rename from community/server/src/test/java/org/neo4j/server/rest/ManageNodeDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/ManageNodeDocIT.java index 2f42d3a2178ba..6f165122df21c 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/ManageNodeDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/ManageNodeDocIT.java @@ -17,17 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; - -import org.hamcrest.MatcherAssert; -import org.junit.After; -import org.junit.AfterClass; -import org.junit.Before; -import org.junit.BeforeClass; -import org.junit.ClassRule; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; +package org.neo4j.doc.server.rest; import java.io.File; import java.io.IOException; @@ -40,17 +30,29 @@ import java.util.concurrent.Callable; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; + +import org.hamcrest.MatcherAssert; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; import org.neo4j.doc.metatest.TestJavaTestDocsGenerator; +import org.neo4j.doc.server.ExclusiveServerTestBase; +import org.neo4j.doc.server.HTTP; +import org.neo4j.doc.server.SharedServerTestBase; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.helpers.FakeClock; -import org.neo4j.kernel.GraphDatabaseDependencies; -import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.configuration.Settings; import org.neo4j.kernel.impl.annotations.Documented; import org.neo4j.kernel.impl.factory.GraphDatabaseFacade; import org.neo4j.kernel.internal.KernelData; -import org.neo4j.kernel.monitoring.Monitors; import org.neo4j.logging.LogProvider; import org.neo4j.logging.NullLogProvider; import org.neo4j.server.CommunityNeoServer; @@ -58,13 +60,9 @@ import org.neo4j.server.configuration.ServerSettings; import org.neo4j.server.database.Database; import org.neo4j.server.database.WrappedDatabase; -import org.neo4j.server.helpers.CommunityServerBuilder; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.domain.GraphDbHelper; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.server.rest.management.JmxService; -import org.neo4j.server.rest.management.RootService; import org.neo4j.server.rest.management.VersionAndEditionService; import org.neo4j.server.rest.management.console.ConsoleService; import org.neo4j.server.rest.management.console.ConsoleSessionFactory; @@ -76,11 +74,9 @@ import org.neo4j.string.UTF8; import org.neo4j.test.TestData; import org.neo4j.test.TestGraphDatabaseFactory; -import org.neo4j.test.server.EntityOutputFormat; -import org.neo4j.test.server.ExclusiveServerTestBase; -import org.neo4j.test.server.HTTP; import static java.lang.System.lineSeparator; + import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.hasKey; @@ -90,9 +86,7 @@ import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; -import static org.neo4j.helpers.collection.MapUtil.stringMap; -import static org.neo4j.server.configuration.ServerSettings.httpConnector; + import static org.neo4j.test.SuppressOutput.suppressAll; public class ManageNodeDocIT extends AbstractRestFunctionalDocTestBase @@ -106,7 +100,7 @@ public class ManageNodeDocIT extends AbstractRestFunctionalDocTestBase @BeforeClass public static void setupServer() throws IOException { - functionalTestHelper = new FunctionalTestHelper( server() ); + functionalTestHelper = new FunctionalTestHelper( SharedServerTestBase.server() ); helper = functionalTestHelper.getGraphDbHelper(); } @@ -626,36 +620,6 @@ public Iterable supportedEngines() } } - public static class RootServiceDocTest - { - @Test - public void shouldAdvertiseServicesWhenAsked() throws Exception - { - UriInfo uriInfo = mock( UriInfo.class ); - URI uri = new URI( "http://example.org:7474/" ); - when( uriInfo.getBaseUri() ).thenReturn( uri ); - - RootService svc = new RootService( new CommunityNeoServer( new Config( stringMap( - httpConnector( "1" ).type.name(), "HTTP", - httpConnector( "1" ).enabled.name(), "true" - ) ), - GraphDatabaseDependencies.newDependencies().userLogProvider( NullLogProvider.getInstance() ) - .monitors( new Monitors() ), - NullLogProvider.getInstance() ) ); - - EntityOutputFormat output = new EntityOutputFormat( new JsonFormat(), null, null ); - Response serviceDefinition = svc.getServiceDefinition( uriInfo, output ); - - assertEquals( 200, serviceDefinition.getStatus() ); - Map result = (Map) output.getResultAsMap().get( "services" ); - - assertThat( result.get( "console" ) - .toString(), containsString( String.format( "%sserver/console", uri.toString() ) ) ); - assertThat( result.get( "jmx" ) - .toString(), containsString( String.format( "%sserver/jmx", uri.toString() ) ) ); - } - } - public static class VersionAndEditionServiceTest { @Test diff --git a/community/server/src/test/java/org/neo4j/server/rest/PathsDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/PathsDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/PathsDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/PathsDocIT.java index 1089188e31f3e..d9e5f8c8ac667 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/PathsDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/PathsDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/PrettyJSON.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/PrettyJSON.java new file mode 100644 index 0000000000000..9fd94d010d136 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/PrettyJSON.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest; + +import org.json.JSONStringer; + +/* + * Convenient access to a naive implementation of a JSON prettifier, + */ +public class PrettyJSON extends JSONStringer +{ + + @Override + public String toString() + { + String json = super.toString(); + + return JSONPrettifier.parse( json ); + } +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RESTDocsGenerator.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RESTDocsGenerator.java new file mode 100644 index 0000000000000..2835b513b670e --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RESTDocsGenerator.java @@ -0,0 +1,651 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest; + +import java.io.File; +import java.io.IOException; +import java.io.Writer; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; +import java.util.function.Predicate; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientRequest; +import com.sun.jersey.api.client.ClientRequest.Builder; +import com.sun.jersey.api.client.ClientResponse; + +import org.neo4j.doc.tools.AsciiDocGenerator; +import org.neo4j.function.Predicates; +import org.neo4j.graphdb.Transaction; +import org.neo4j.helpers.collection.Pair; +import org.neo4j.test.GraphDefinition; +import org.neo4j.test.TestData.Producer; +import org.neo4j.visualization.asciidoc.AsciidocHelper; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Generate asciidoc-formatted documentation from HTTP requests and responses. + * The status and media type of all responses is checked as well as the + * existence of any expected headers. + * + * The filename of the resulting ASCIIDOC test file is derived from the title. + * + * The title is determined by either a JavaDoc period terminated first title line, + * the @Title annotation or the method name, where "_" is replaced by " ". + */ +public class RESTDocsGenerator extends AsciiDocGenerator +{ + private static final String EQUAL_SIGNS = "======"; + + private static final Builder REQUEST_BUILDER = ClientRequest.create(); + + private static final List RESPONSE_HEADERS = Arrays.asList( "Content-Type", "Location" ); + + private static final List REQUEST_HEADERS = Arrays.asList( "Content-Type", "Accept" ); + + public static final Producer PRODUCER = new Producer() + { + @Override + public RESTDocsGenerator create( GraphDefinition graph, String title, String documentation ) + { + RESTDocsGenerator gen = RESTDocsGenerator.create( title ); + gen.description(documentation); + return gen; + } + + @Override + public void destroy( RESTDocsGenerator product, boolean successful ) + { + // TODO: invoke some complete method here? + } + }; + + private int expectedResponseStatus = -1; + private MediaType expectedMediaType = MediaType.valueOf( "application/json; charset=UTF-8" ); + private MediaType payloadMediaType = MediaType.APPLICATION_JSON_TYPE; + private final List>> expectedHeaderFields = new ArrayList<>(); + private String payload; + private final Map addedRequestHeaders = new TreeMap<>( ); + private boolean noDoc = false; + private boolean noGraph = false; + private int headingLevel = 3; + + /** + * Creates a documented test case. Finish building it by using one of these: + * {@link #get(String)}, {@link #post(String)}, {@link #put(String)}, + * {@link #delete(String)}, {@link #request(ClientRequest)}. To access the + * response, use {@link ResponseEntity#entity} to get the entity or + * {@link ResponseEntity#response} to get the rest of the response + * (excluding the entity). + * + * @param title title of the test + */ + public static RESTDocsGenerator create( final String title ) + { + if ( title == null ) + { + throw new IllegalArgumentException( "The title can not be null" ); + } + return new RESTDocsGenerator( title ); + } + + private RESTDocsGenerator( String title ) + { + super( title, "rest-api" ); + } + + + + /** + * Set the expected status of the response. The test will fail if the + * response has a different status. Defaults to HTTP 200 OK. + * + * @param expectedResponseStatus the expected response status + */ + public RESTDocsGenerator expectedStatus( final int expectedResponseStatus ) + { + this.expectedResponseStatus = expectedResponseStatus; + return this; + } + + /** + * Set the expected status of the response. The test will fail if the + * response has a different status. Defaults to HTTP 200 OK. + * + * @param expectedStatus the expected response status + */ + public RESTDocsGenerator expectedStatus( final ClientResponse.Status expectedStatus) + { + this.expectedResponseStatus = expectedStatus.getStatusCode(); + return this; + } + + /** + * Set the expected media type of the response. The test will fail if the + * response has a different media type. Defaults to application/json. + * + * @param expectedMediaType the expected media tyupe + */ + public RESTDocsGenerator expectedType( final MediaType expectedMediaType ) + { + this.expectedMediaType = expectedMediaType; + return this; + } + + /** + * The media type of the request payload. Defaults to application/json. + * + * @param payloadMediaType the media type to use + */ + public RESTDocsGenerator payloadType( final MediaType payloadMediaType ) + { + this.payloadMediaType = payloadMediaType; + return this; + } + + /** + * The additional headers for the request + * + * @param key header key + * @param value header value + */ + public RESTDocsGenerator withHeader( final String key, final String value ) + { + this.addedRequestHeaders.put(key,value); + return this; + } + + /** + * Set the payload of the request. + * + * @param payload the payload + */ + public RESTDocsGenerator payload( final String payload ) + { + this.payload = payload; + return this; + } + + public RESTDocsGenerator noDoc() { + this.noDoc = true; + return this; + } + + public RESTDocsGenerator noGraph() + { + this.noGraph = true; + return this; + } + + /** + * Set a custom heading level. Defaults to 3. + * + * @param headingLevel a value between 1 and 6 (inclusive) + */ + public RESTDocsGenerator docHeadingLevel( final int headingLevel ) + { + if ( headingLevel < 1 || headingLevel > EQUAL_SIGNS.length() ) + { + throw new IllegalArgumentException( "Heading level out of bounds: " + + headingLevel ); + } + this.headingLevel = headingLevel; + return this; + } + + /** + * Add an expected response header. If the heading is missing in the + * response the test will fail. The header and its value are also included + * in the documentation. + * + * @param expectedHeaderField the expected header + */ + public RESTDocsGenerator expectedHeader( final String expectedHeaderField ) + { + this.expectedHeaderFields.add( Pair.of(expectedHeaderField, Predicates.notNull()) ); + return this; + } + + /** + * Add an expected response header. If the heading is missing in the + * response the test will fail. The header and its value are also included + * in the documentation. + * + * @param expectedHeaderField the expected header + * @param expectedValue the expected header value + */ + public RESTDocsGenerator expectedHeader( final String expectedHeaderField, String expectedValue ) + { + this.expectedHeaderFields.add( Pair.of(expectedHeaderField, Predicate.isEqual( expectedValue )) ); + return this; + } + + /** + * Send a request using your own request object. + * + * @param request the request to perform + */ + public ResponseEntity request( final ClientRequest request ) + { + return retrieveResponse( title, description, request.getURI() + .toString(), expectedResponseStatus, expectedMediaType, expectedHeaderFields, request ); + } + + @Override + public RESTDocsGenerator description( String description ) + { + return (RESTDocsGenerator) super.description( description ); + } + + /** + * Send a GET request. + * + * @param uri the URI to use. + */ + public ResponseEntity get( final String uri ) + { + return retrieveResponseFromRequest( title, description, "GET", uri, expectedResponseStatus, expectedMediaType, + expectedHeaderFields ); + } + + /** + * Send a POST request. + * + * @param uri the URI to use. + */ + public ResponseEntity post( final String uri ) + { + return retrieveResponseFromRequest( title, description, "POST", uri, payload, payloadMediaType, + expectedResponseStatus, expectedMediaType, expectedHeaderFields ); + } + + /** + * Send a PUT request. + * + * @param uri the URI to use. + */ + public ResponseEntity put( final String uri ) + { + return retrieveResponseFromRequest( title, description, "PUT", uri, payload, payloadMediaType, + expectedResponseStatus, expectedMediaType, expectedHeaderFields ); + } + + /** + * Send a DELETE request. + * + * @param uri the URI to use. + */ + public ResponseEntity delete( final String uri ) + { + return retrieveResponseFromRequest( title, description, "DELETE", uri, payload, payloadMediaType, + expectedResponseStatus, expectedMediaType, expectedHeaderFields ); + } + + /** + * Send a request with no payload. + */ + private ResponseEntity retrieveResponseFromRequest( final String title, final String description, + final String method, final String uri, final int responseCode, final MediaType accept, + final List>> headerFields ) + { + ClientRequest request; + try + { + request = withHeaders(REQUEST_BUILDER) + .accept(accept) + .build( new URI( uri ), method ); + } + catch ( URISyntaxException e ) + { + throw new RuntimeException( e ); + } + return retrieveResponse( title, description, uri, responseCode, accept, headerFields, request ); + } + + /** + * Send a request with payload. + */ + private ResponseEntity retrieveResponseFromRequest( final String title, final String description, + final String method, final String uri, final String payload, final MediaType payloadType, + final int responseCode, final MediaType accept, final List>> headerFields ) + { + ClientRequest request; + try + { + if ( payload != null ) + { + request = withHeaders(REQUEST_BUILDER) + .type(payloadType) + .accept(accept) + .entity(payload) + .build( new URI( uri ), method ); + } + else + { + request = withHeaders(REQUEST_BUILDER).accept( accept ) + .build(new URI(uri), method); + } + } + catch ( URISyntaxException e ) + { + throw new RuntimeException( e ); + } + return retrieveResponse( title, description, uri, responseCode, accept, headerFields, request ); + } + + private T withHeaders(T builder) { + for (Entry entry : addedRequestHeaders.entrySet()) { + builder.header(entry.getKey(),entry.getValue()); + } + return builder; + } + + /** + * Send the request and create the documentation. + */ + private ResponseEntity retrieveResponse( final String title, final String description, final String uri, + final int responseCode, final MediaType type, final List>> headerFields, final ClientRequest request ) + { + DocumentationData data = new DocumentationData(); + getRequestHeaders( data, request.getHeaders() ); + if ( request.getEntity() != null ) + { + data.setPayload( String.valueOf( request.getEntity() ) ); + List contentTypes = request.getHeaders() + .get( "Content-Type" ); + if ( contentTypes != null ) + { + if ( contentTypes.size() != 1 ) + { + throw new IllegalArgumentException( + "Request contains multiple content-types." ); + } + Object contentType = contentTypes.get( 0 ); + if ( contentType instanceof MediaType ) + { + data.setPayloadType( (MediaType) contentType ); + } + } + // data.setPayloadType( contentType ); + } + Client client = new Client(); + ClientResponse response = client.handle( request ); + if ( response.hasEntity() && response.getStatus() != 204 ) + { + data.setEntity( response.getEntity( String.class ) ); + } + if ( response.getType() != null ) + { + assertTrue( "wrong response type: "+ data.entity, response.getType().isCompatible( type ) ); + } + for ( Pair> headerField : headerFields ) + { + assertTrue( "wrong headers: " + response.getHeaders(), headerField.other().test( response.getHeaders() + .getFirst( headerField.first() ) ) ); + } + if ( noDoc ) + { + data.setIgnore(); + } + data.setTitle( title ); + data.setDescription( description ); + data.setMethod( request.getMethod() ); + data.setUri( uri ); + data.setStatus( responseCode ); + assertEquals( "Wrong response status. response: " + data.entity, responseCode, response.getStatus() ); + getResponseHeaders( data, response.getHeaders(), headerNames(headerFields) ); + if ( graph == null ) + { + document( data ); + } + else + { + try ( Transaction transaction = graph.beginTx() ) + { + document( data ); + } + } + return new ResponseEntity( response, data.entity ); + } + + private List headerNames( List>> headerPredicates ) + { + List names = new ArrayList<>(); + for ( Pair> headerPredicate : headerPredicates ) + { + names.add( headerPredicate.first() ); + } + return names; + } + + private void getResponseHeaders( final DocumentationData data, final MultivaluedMap headers, + final List additionalFilter ) + { + data.setResponseHeaders( getHeaders( headers, RESPONSE_HEADERS, additionalFilter ) ); + } + + private void getRequestHeaders( final DocumentationData data, final MultivaluedMap headers ) + { + data.setRequestHeaders( getHeaders( headers, REQUEST_HEADERS, + addedRequestHeaders.keySet() ) ); + } + + private Map getHeaders( final MultivaluedMap headers, final List filter, + final Collection additionalFilter ) + { + Map filteredHeaders = new TreeMap<>(); + for ( Entry> header : headers.entrySet() ) + { + String key = header.getKey(); + if ( filter.contains( key ) || additionalFilter.contains( key ) ) + { + String values = ""; + for ( T value : header.getValue() ) + { + if ( !values.isEmpty() ) + { + values += ", "; + } + values += String.valueOf( value ); + } + filteredHeaders.put( key, values ); + } + } + return filteredHeaders; + } + + /** + * Wraps a response, to give access to the response entity as well. + */ + public static class ResponseEntity + { + private final String entity; + private final JaxRsResponse response; + + public ResponseEntity( ClientResponse response, String entity ) + { + this.response = new JaxRsResponse(response,entity); + this.entity = entity; + } + + /** + * The response entity as a String. + */ + public String entity() + { + return entity; + } + + /** + * Note that the response object returned does not give access to the + * response entity. + */ + public JaxRsResponse response() + { + return response; + } + } + + protected void document( final DocumentationData data ) + { + if (data.ignore) + { + return; + } + String name = data.title.replace( " ", "-" ).toLowerCase(); + String filename = name + ".asciidoc"; + File dir = new File( new File( new File( "target" ), "docs" ), section ); + data.description = replaceSnippets( data.description, dir, name ); + try ( Writer fw = AsciiDocGenerator.getFW( dir, filename ) ) + { + String longSection = section.replaceAll( "\\(|\\)", "" )+"-" + name.replaceAll( "\\(|\\)", "" ); + if(longSection.indexOf( "/" )>0) + { + longSection = longSection.substring( longSection.indexOf( "/" )+1 ); + } + line( fw, "[[" + longSection + "]]" ); + //make first Character uppercase + String firstChar = data.title.substring( 0, 1 ).toUpperCase(); + String heading = firstChar + data.title.substring( 1 ); + line( fw, getAsciidocHeading( heading ) ); + line( fw, "" ); + if ( data.description != null && !data.description.isEmpty() ) + { + line( fw, data.description ); + line( fw, "" ); + } + if ( !noGraph && graph != null ) + { + fw.append( AsciiDocGenerator.dumpToSeparateFile( dir, + name + ".graph", + AsciidocHelper.createGraphVizWithNodeId( "Final Graph", + graph, title ) ) ); + line(fw, "" ); + } + line( fw, "_Example request_" ); + line( fw, "" ); + StringBuilder sb = new StringBuilder( 512 ); + sb.append( "* *+" ) + .append( data.method ) + .append( "+* +" ) + .append( data.uri ) + .append( "+\n" ); + if ( data.requestHeaders != null ) + { + for ( Entry header : data.requestHeaders.entrySet() ) + { + sb.append( "* *+" ) + .append( header.getKey() ) + .append( ":+* +" ) + .append( header.getValue() ) + .append( "+\n" ); + } + } + String prettifiedPayload = data.getPayload(); + if ( prettifiedPayload != null ) + { + writeEntity( sb, prettifiedPayload ); + } + fw.append( AsciiDocGenerator.dumpToSeparateFile( dir, name + + ".request", + sb.toString() ) ); + sb = new StringBuilder( 2048 ); + line( fw, "" ); + line( fw, "_Example response_" ); + line( fw, "" ); + int statusCode = data.status; + sb.append( "* *+" ) + .append( statusCode ) + .append( ":+* +" ) + .append( statusNameFromStatusCode( statusCode ) ) + .append( "+\n" ); + if ( data.responseHeaders != null ) + { + for ( Entry header : data.responseHeaders.entrySet() ) + { + sb.append( "* *+" ) + .append( header.getKey() ) + .append( ":+* +" ) + .append( header.getValue() ) + .append( "+\n" ); + } + } + writeEntity( sb, data.getPrettifiedEntity() ); + fw.append( AsciiDocGenerator.dumpToSeparateFile( dir, + name + ".response", sb.toString() ) ); + line( fw, "" ); + } + catch ( IOException e ) + { + e.printStackTrace(); + fail(); + } + } + + private String statusNameFromStatusCode( int statusCode ) + { + Object name = Response.Status.fromStatusCode( statusCode ); + if ( name == null ) + { + switch ( statusCode ) + { + case 405: + name = "Method Not Allowed"; + break; + case 422: + name = "Unprocessable Entity"; + break; + default: + throw new RuntimeException( "Missing name for status code: [" + statusCode + "]." ); + } + } + return String.valueOf( name ); + } + + private String getAsciidocHeading( final String heading ) + { + String equalSigns = EQUAL_SIGNS.substring( 0, headingLevel ); + return equalSigns + ' ' + heading + ' ' + equalSigns; + } + + public void writeEntity( final StringBuilder sb, final String entity ) + { + if ( entity != null ) + { + sb.append( "\n[source,javascript]\n" ) + .append( "----\n" ) + .append( entity ) + .append( "\n----\n\n" ); + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/RedirectorDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RedirectorDocIT.java similarity index 97% rename from community/server/src/test/java/org/neo4j/server/rest/RedirectorDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RedirectorDocIT.java index 3f1014d79a806..21ade82e8118a 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/RedirectorDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RedirectorDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/community/server/src/test/java/org/neo4j/server/rest/RelationshipDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RelationshipDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/RelationshipDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RelationshipDocIT.java index 0297cdbbf4c8c..3352c2dafee40 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/RelationshipDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RelationshipDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import com.sun.jersey.api.client.ClientResponse; import org.junit.BeforeClass; @@ -33,7 +33,7 @@ import org.neo4j.graphdb.Transaction; import org.neo4j.helpers.collection.MapUtil; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.server.rest.repr.StreamingFormat; diff --git a/community/server/src/test/java/org/neo4j/server/rest/RemoveNodePropertiesDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RemoveNodePropertiesDocIT.java similarity index 96% rename from community/server/src/test/java/org/neo4j/server/rest/RemoveNodePropertiesDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RemoveNodePropertiesDocIT.java index 91cd4c0814686..b5adcacb1283c 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/RemoveNodePropertiesDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RemoveNodePropertiesDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.BeforeClass; import org.junit.Test; @@ -26,9 +26,9 @@ import java.util.HashMap; import java.util.Map; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.domain.GraphDbHelper; import static org.junit.Assert.assertEquals; diff --git a/community/server/src/test/java/org/neo4j/server/rest/RemoveRelationshipDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RemoveRelationshipDocIT.java similarity index 93% rename from community/server/src/test/java/org/neo4j/server/rest/RemoveRelationshipDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RemoveRelationshipDocIT.java index df0e0078ab538..ea633a110ebfa 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/RemoveRelationshipDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RemoveRelationshipDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.BeforeClass; import org.junit.Test; @@ -25,8 +25,8 @@ import java.io.IOException; import java.net.URI; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import static org.junit.Assert.assertEquals; diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RestRequest.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RestRequest.java new file mode 100644 index 0000000000000..d01d2be1bc140 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RestRequest.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import com.sun.jersey.api.client.Client; +import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.WebResource; +import com.sun.jersey.api.client.WebResource.Builder; +import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter; + +import org.neo4j.doc.server.HTTP; +import org.neo4j.server.rest.domain.JsonHelper; +import org.neo4j.server.rest.domain.JsonParseException; + +public class RestRequest { + + private final URI baseUri; + private final static Client DEFAULT_CLIENT = Client.create(); + private final Client client; + private MediaType accept = MediaType.APPLICATION_JSON_TYPE; + private Map headers=new HashMap(); + + public RestRequest( URI baseUri ) { + this( baseUri, null, null ); + } + + public RestRequest( URI baseUri, String username, String password ) { + this.baseUri = uriWithoutSlash( baseUri ); + if ( username != null ) + { + client = Client.create(); + client.addFilter( new HTTPBasicAuthFilter( username, password ) ); + } + else + { + client = DEFAULT_CLIENT; + } + } + + public RestRequest(URI uri, Client client) { + this.baseUri = uriWithoutSlash( uri ); + this.client = client; + } + + public RestRequest() { + this( null ); + } + + private URI uriWithoutSlash( URI uri ) { + if (uri == null) return null; + String uriString = uri.toString(); + return uriString.endsWith( "/" ) ? uri( uriString.substring( 0, uriString.length() - 1 ) ) : uri; + } + + public static String encode( Object value ) { + if ( value == null ) return ""; + try { + return URLEncoder.encode( value.toString(), StandardCharsets.UTF_8.name() ).replaceAll( "\\+", "%20" ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + + private Builder builder( String path ) { + return builder( path, accept ); + } + + private Builder builder( String path, final MediaType accept ) { + WebResource resource = client.resource( uri( pathOrAbsolute( path ) ) ); + Builder builder = resource.accept( accept ); + if ( !headers.isEmpty() ) { + for ( Map.Entry header : headers.entrySet() ) { + builder = builder.header( header.getKey(),header.getValue() ); + } + } + return builder; + } + + private String pathOrAbsolute( String path ) { + if ( path.startsWith( "http://" ) ) return path; + return baseUri + "/" + path; + } + + public JaxRsResponse get( String path ) { + return JaxRsResponse.extractFrom( HTTP.sanityCheck( builder( path ).get( ClientResponse.class ) ) ); + } + + public JaxRsResponse get(String path, String data) { + return get( path, data, MediaType.APPLICATION_JSON_TYPE ); + } + + public JaxRsResponse get( String path, String data, final MediaType mediaType ) { + Builder builder = builder( path ); + if ( data != null ) { + builder = builder.entity( data, mediaType); + } else { + builder = builder.type( mediaType ); + } + return JaxRsResponse.extractFrom( HTTP.sanityCheck( builder.get( ClientResponse.class ) ) ); + } + + public JaxRsResponse delete(String path) { + return JaxRsResponse.extractFrom( HTTP.sanityCheck( builder( path ).delete( ClientResponse.class ) ) ); + } + + public JaxRsResponse post(String path, String data) { + return post(path, data, MediaType.APPLICATION_JSON_TYPE); + } + + public JaxRsResponse post(String path, String data, final MediaType mediaType) { + Builder builder = builder( path ); + if ( data != null ) { + builder = builder.entity( data, mediaType); + } else { + builder = builder.type(mediaType); + } + return JaxRsResponse.extractFrom( HTTP.sanityCheck( builder.post( ClientResponse.class ) ) ); + } + + public JaxRsResponse put(String path, String data) { + Builder builder = builder( path ); + if ( data != null ) { + builder = builder.entity( data, MediaType.APPLICATION_JSON_TYPE ); + } + return new JaxRsResponse( HTTP.sanityCheck( builder.put( ClientResponse.class ) ) ); + } + + + public Object toEntity( JaxRsResponse JaxRsResponse ) throws JsonParseException { + return JsonHelper.readJson( entityString( JaxRsResponse ) ); + } + + public Map toMap( JaxRsResponse JaxRsResponse) throws JsonParseException { + final String json = entityString( JaxRsResponse ); + return JsonHelper.jsonToMap(json); + } + + private String entityString( JaxRsResponse JaxRsResponse) { + return JaxRsResponse.getEntity(); + } + + public boolean statusIs( JaxRsResponse JaxRsResponse, Response.StatusType status ) { + return JaxRsResponse.getStatus() == status.getStatusCode(); + } + + public boolean statusOtherThan( JaxRsResponse JaxRsResponse, Response.StatusType status ) { + return !statusIs(JaxRsResponse, status ); + } + + public RestRequest with( String uri ) { + return new RestRequest( uri( uri ), client ); + } + + private URI uri( String uri ) { + try { + return new URI( uri ); + } catch ( URISyntaxException e ) { + throw new RuntimeException( e ); + } + } + + public URI getUri() { + return baseUri; + } + + public JaxRsResponse get() { + return get( "" ); + } + + public JaxRsResponse get(String path, final MediaType acceptType) { + Builder builder = builder(path, acceptType); + return JaxRsResponse.extractFrom( HTTP.sanityCheck( builder.get( ClientResponse.class ) ) ); + } + + public static RestRequest req() { + return new RestRequest(); + } + + public JaxRsResponse delete(URI location) { + return delete(location.toString()); + } + + public JaxRsResponse put(URI uri, String data) { + return put(uri.toString(),data); + } + + public RestRequest accept( MediaType accept ) + { + this.accept = accept; + return this; + } + + public RestRequest header(String header, String value) { + this.headers.put(header,value); + return this; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/RetrieveNodeDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RetrieveNodeDocIT.java similarity index 96% rename from community/server/src/test/java/org/neo4j/server/rest/RetrieveNodeDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RetrieveNodeDocIT.java index 0a6a84555fb3b..01eaf20318828 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/RetrieveNodeDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RetrieveNodeDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; @@ -35,10 +35,10 @@ import java.util.Map; import javax.ws.rs.core.MediaType; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; -import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.doc.server.rest.RESTDocsGenerator.ResponseEntity; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.repr.formats.CompactJsonFormat; diff --git a/community/server/src/test/java/org/neo4j/server/rest/RetrieveRelationshipsFromNodeDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RetrieveRelationshipsFromNodeDocIT.java similarity index 88% rename from community/server/src/test/java/org/neo4j/server/rest/RetrieveRelationshipsFromNodeDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RetrieveRelationshipsFromNodeDocIT.java index 02f9297a7f490..23a82ecc1ddd1 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/RetrieveRelationshipsFromNodeDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/RetrieveRelationshipsFromNodeDocIT.java @@ -17,7 +17,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Map; +import javax.ws.rs.core.MediaType; import org.apache.commons.io.IOUtils; import org.apache.http.HttpEntity; @@ -29,18 +35,11 @@ import org.junit.BeforeClass; import org.junit.Test; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Map; -import javax.ws.rs.core.MediaType; - +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.domain.GraphDbHelper; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; -import org.neo4j.server.rest.repr.RelationshipRepresentationTest; import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; import static org.hamcrest.Matchers.containsString; @@ -48,6 +47,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; public class RetrieveRelationshipsFromNodeDocIT extends AbstractRestFunctionalDocTestBase { @@ -88,7 +88,7 @@ private void verifyRelReps( int expectedSize, String json ) throws JsonParseExce assertEquals( expectedSize, relreps.size() ); for ( Map relrep : relreps ) { - RelationshipRepresentationTest.verifySerialisation( relrep ); + verifySerialisation( relrep ); } } @@ -319,4 +319,31 @@ private void isLegalJson( String entity ) throws IOException, JsonParseException { JsonHelper.jsonToMap( entity ); } + + public static void verifySerialisation( Map relrep ) + { + assertUriMatches( RELATIONSHIP_URI_PATTERN, relrep.get( "self" ) + .toString() ); + assertUriMatches( NODE_URI_PATTERN, relrep.get( "start" ) + .toString() ); + assertUriMatches( NODE_URI_PATTERN, relrep.get( "end" ) + .toString() ); + assertNotNull( relrep.get( "type" ) ); + assertUriMatches( RELATIONSHIP_URI_PATTERN + "/properties", relrep.get( "properties" ) + .toString() ); + assertUriMatches( RELATIONSHIP_URI_PATTERN + "/properties/\\{key\\}", (String) relrep.get( "property" ) ); + assertNotNull( relrep.get( "data" ) ); + assertNotNull( relrep.get( "metadata" ) ); + Map metadata = (Map) relrep.get( "metadata" ); + assertNotNull( metadata.get("type") ); + assertTrue( ( (Number) metadata.get("id") ).longValue() >= 0 ); + } + + private static final String NODE_URI_PATTERN = "http://.*/node/[0-9]+"; + private static final String RELATIONSHIP_URI_PATTERN = "http://.*/relationship/[0-9]+"; + + private static void assertUriMatches( String expectedRegex, String actualUri ) + { + assertTrue( "expected <" + expectedRegex + "> got <" + actualUri + ">", actualUri.matches( expectedRegex ) ); + } } diff --git a/community/server/src/test/java/org/neo4j/server/rest/SchemaConstraintsDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SchemaConstraintsDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/SchemaConstraintsDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SchemaConstraintsDocIT.java index 873fd71dc2496..eb7c8d5441068 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/SchemaConstraintsDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SchemaConstraintsDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/community/server/src/test/java/org/neo4j/server/rest/SchemaIndexDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SchemaIndexDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/SchemaIndexDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SchemaIndexDocIT.java index 8f2d3ca37cac6..041ea0d57c67b 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/SchemaIndexDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SchemaIndexDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/community/server/src/test/java/org/neo4j/server/rest/SetNodePropertiesDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SetNodePropertiesDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/SetNodePropertiesDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SetNodePropertiesDocIT.java index e2cb98f7d6cbe..f9a754e925f62 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/SetNodePropertiesDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SetNodePropertiesDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/community/server/src/test/java/org/neo4j/server/rest/SetRelationshipPropertiesDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SetRelationshipPropertiesDocIT.java similarity index 97% rename from community/server/src/test/java/org/neo4j/server/rest/SetRelationshipPropertiesDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SetRelationshipPropertiesDocIT.java index 9e636cbb37cbe..2ece4f5b0459f 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/SetRelationshipPropertiesDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/SetRelationshipPropertiesDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Before; import org.junit.BeforeClass; @@ -28,9 +28,9 @@ import java.util.HashMap; import java.util.Map; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.domain.GraphDbHelper; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.test.GraphDescription.Graph; diff --git a/community/server/src/test/java/org/neo4j/server/rest/TraverserDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/TraverserDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/TraverserDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/TraverserDocIT.java index f1c1cd393179c..4744d5088c26f 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/TraverserDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/TraverserDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest; +package org.neo4j.doc.server.rest; import org.junit.Test; diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/UniqueStrings.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/UniqueStrings.java new file mode 100644 index 0000000000000..5a8824d1686f5 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/UniqueStrings.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest; + +import org.neo4j.function.Factory; + +public class UniqueStrings +{ + public static Factory withPrefix( final String prefix ) + { + return new Factory() + { + private int next = 0; + + @Override + public String newInstance() + { + return prefix + "_" + System.currentTimeMillis() + "_" + ++next; + } + }; + } + +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/domain/GraphDbHelper.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/domain/GraphDbHelper.java new file mode 100644 index 0000000000000..e4fa44af07fdc --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/domain/GraphDbHelper.java @@ -0,0 +1,493 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest.domain; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.neo4j.graphdb.Label; +import org.neo4j.graphdb.Node; +import org.neo4j.graphdb.NotFoundException; +import org.neo4j.graphdb.Relationship; +import org.neo4j.graphdb.RelationshipType; +import org.neo4j.graphdb.Transaction; +import org.neo4j.graphdb.index.Index; +import org.neo4j.graphdb.index.IndexManager; +import org.neo4j.graphdb.index.RelationshipIndex; +import org.neo4j.graphdb.schema.ConstraintCreator; +import org.neo4j.graphdb.schema.ConstraintDefinition; +import org.neo4j.graphdb.schema.ConstraintType; +import org.neo4j.graphdb.schema.IndexDefinition; +import org.neo4j.helpers.collection.IterableWrapper; +import org.neo4j.helpers.collection.Iterables; +import org.neo4j.helpers.collection.MapUtil; +import org.neo4j.kernel.api.security.AccessMode; +import org.neo4j.kernel.api.KernelAPI; +import org.neo4j.kernel.api.KernelTransaction; +import org.neo4j.kernel.api.Statement; +import org.neo4j.kernel.api.exceptions.TransactionFailureException; +import org.neo4j.kernel.impl.coreapi.schema.InternalSchemaActions; +import org.neo4j.kernel.impl.coreapi.schema.NodePropertyExistenceConstraintDefinition; +import org.neo4j.kernel.impl.coreapi.schema.RelationshipPropertyExistenceConstraintDefinition; +import org.neo4j.server.database.Database; + +import static org.mockito.Mockito.mock; +import static org.neo4j.graphdb.Label.label; +import static org.neo4j.helpers.collection.Iterables.count; +import static org.neo4j.helpers.collection.Iterables.single; + +public class GraphDbHelper +{ + private final Database database; + + public GraphDbHelper( Database database ) + { + this.database = database; + } + + public int getNumberOfNodes() + { + KernelAPI kernelAPI = database.getGraph().getDependencyResolver().resolveDependency( KernelAPI.class ); + try ( KernelTransaction tx = kernelAPI.newTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ); + Statement statement = tx.acquireStatement() ) + { + return Math.toIntExact( statement.readOperations().nodesGetCount() ); + } + catch ( TransactionFailureException e ) + { + throw new RuntimeException( e ); + } + } + + public int getNumberOfRelationships() + { + KernelAPI kernelAPI = database.getGraph().getDependencyResolver().resolveDependency( KernelAPI.class ); + try ( KernelTransaction tx = kernelAPI.newTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ); + Statement statement = tx.acquireStatement() ) + { + return Math.toIntExact( statement.readOperations().relationshipsGetCount() ); + } + catch ( TransactionFailureException e ) + { + throw new RuntimeException( e ); + } + } + + + public Map getNodeProperties( long nodeId ) + { + try (Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ)) + { + Node node = database.getGraph().getNodeById( nodeId ); + Map allProperties = node.getAllProperties(); + tx.success(); + return allProperties; + } + } + + public void setNodeProperties( long nodeId, Map properties ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE) ) + { + Node node = database.getGraph().getNodeById( nodeId ); + for ( Map.Entry propertyEntry : properties.entrySet() ) + { + node.setProperty( propertyEntry.getKey(), propertyEntry.getValue() ); + } + tx.success(); + } + } + + public long createNode( Label... labels ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE) ) + { + Node node = database.getGraph().createNode( labels ); + tx.success(); + return node.getId(); + } + } + + public long createNode( Map properties, Label... labels ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE) ) + { + Node node = database.getGraph().createNode( labels ); + for ( Map.Entry entry : properties.entrySet() ) + { + node.setProperty( entry.getKey(), entry.getValue() ); + } + tx.success(); + return node.getId(); + } + } + + public void deleteNode( long id ) + { + try (Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE)) + { + Node node = database.getGraph().getNodeById( id ); + node.delete(); + tx.success(); + } + } + + public long createRelationship( String type, long startNodeId, long endNodeId ) + { + try (Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE)) + { + Node startNode = database.getGraph().getNodeById( startNodeId ); + Node endNode = database.getGraph().getNodeById( endNodeId ); + Relationship relationship = startNode.createRelationshipTo( endNode, + RelationshipType.withName( type ) ); + tx.success(); + return relationship.getId(); + } + } + + public long createRelationship( String type ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE) ) + { + Node startNode = database.getGraph().createNode(); + Node endNode = database.getGraph().createNode(); + Relationship relationship = startNode.createRelationshipTo( endNode, + RelationshipType.withName( type ) ); + tx.success(); + return relationship.getId(); + } + } + + public void setRelationshipProperties( long relationshipId, Map properties ) + + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE) ) + { + Relationship relationship = database.getGraph().getRelationshipById( relationshipId ); + for ( Map.Entry propertyEntry : properties.entrySet() ) + { + relationship.setProperty( propertyEntry.getKey(), propertyEntry.getValue() ); + } + tx.success(); + } + } + + public Map getRelationshipProperties( long relationshipId ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ) ) + { + Relationship relationship = database.getGraph().getRelationshipById( relationshipId ); + Map allProperties = relationship.getAllProperties(); + tx.success(); + return allProperties; + } + } + + public Relationship getRelationship( long relationshipId ) + { + try (Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ)) + { + Relationship relationship = database.getGraph().getRelationshipById( relationshipId ); + tx.success(); + return relationship; + } + } + + public void addNodeToIndex( String indexName, String key, Object value, long id ) + { + try (Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.FULL)) + { + database.getGraph().index().forNodes( indexName ).add( database.getGraph().getNodeById( id ), key, value ); + tx.success(); + } + } + + public Collection queryIndexedNodes( String indexName, String key, Object value ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE) ) + { + Collection result = new ArrayList<>(); + for ( Node node : database.getGraph().index().forNodes( indexName ).query( key, value ) ) + { + result.add( node.getId() ); + } + tx.success(); + return result; + } + } + + public Collection getIndexedNodes( String indexName, String key, Object value ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE) ) + { + Collection result = new ArrayList<>(); + for ( Node node : database.getGraph().index().forNodes( indexName ).get( key, value ) ) + { + result.add( node.getId() ); + } + tx.success(); + return result; + } + } + + public Collection getIndexedRelationships( String indexName, String key, Object value ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE) ) + { + Collection result = new ArrayList<>(); + for ( Relationship relationship : database.getGraph().index().forRelationships( indexName ).get( key, value ) ) + { + result.add( relationship.getId() ); + } + tx.success(); + return result; + } + } + + public void addRelationshipToIndex( String indexName, String key, String value, long relationshipId ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.FULL) ) + { + Index index = database.getGraph().index().forRelationships( indexName ); + index.add( database.getGraph().getRelationshipById( relationshipId ), key, value ); + tx.success(); + } + + } + + public String[] getNodeIndexes() + { + try (Transaction transaction = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ)) + { + return database.getGraph().index().nodeIndexNames(); + } + } + + public Index createNodeFullTextIndex( String named ) + { + try ( Transaction transaction = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.FULL) ) + { + Index index = database.getGraph().index().forNodes( named, MapUtil.stringMap( IndexManager.PROVIDER, "lucene", "type", "fulltext" ) ); + transaction.success(); + return index; + } + } + + public Index createNodeIndex( String named ) + { + try ( Transaction transaction = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.FULL) ) + { + Index nodeIndex = database.getGraph().index() + .forNodes( named ); + transaction.success(); + return nodeIndex; + } + } + + public String[] getRelationshipIndexes() + { + try (Transaction transaction = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ)) + { + return database.getGraph().index() + .relationshipIndexNames(); + } + } + + public long getFirstNode() + { + try (Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE)) + { + try + { + Node referenceNode = database.getGraph().getNodeById(0l); + + tx.success(); + return referenceNode.getId(); + } + catch(NotFoundException e) + { + Node newNode = database.getGraph().createNode(); + tx.success(); + return newNode.getId(); + } + } + } + + public Index createRelationshipIndex( String named ) + { + try (Transaction transaction = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.FULL)) + { + RelationshipIndex relationshipIndex = database.getGraph().index() + .forRelationships( named ); + transaction.success(); + return relationshipIndex; + } + } + + public Iterable getNodeLabels( long node ) + { + return new IterableWrapper( database.getGraph().getNodeById( node ).getLabels() ) + { + @Override + protected String underlyingObjectToObject( Label object ) + { + return object.name(); + } + }; + } + + public void addLabelToNode( long node, String labelName ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.WRITE) ) + { + database.getGraph().getNodeById( node ).addLabel( label( labelName ) ); + tx.success(); + } + } + + public Iterable getSchemaIndexes( String labelName ) + { + return database.getGraph().schema().getIndexes( label( labelName ) ); + } + + public IndexDefinition createSchemaIndex( String labelName, String propertyKey ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.FULL) ) + { + IndexDefinition index = database.getGraph().schema().indexFor( label( labelName ) ).on( propertyKey ).create(); + tx.success(); + return index; + } + } + + public Iterable getPropertyUniquenessConstraints( String labelName, final String propertyKey ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ) ) + { + Iterable definitions = Iterables.filter( item -> { + if ( item.isConstraintType( ConstraintType.UNIQUENESS ) ) + { + Iterable keys = item.getPropertyKeys(); + return single( keys ).equals( propertyKey ); + } + else + { + return false; + } + + }, database.getGraph().schema().getConstraints( label( labelName ) ) ); + tx.success(); + return definitions; + } + } + + public Iterable getNodePropertyExistenceConstraints( String labelName, + final String propertyKey ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ) ) + { + Iterable definitions = filterByConstraintTypeAndPropertyKey( + database.getGraph().schema().getConstraints( label( labelName ) ), + ConstraintType.NODE_PROPERTY_EXISTENCE, propertyKey ); + tx.success(); + return definitions; + } + } + + public Iterable getRelationshipPropertyExistenceConstraints( String typeName, + final String propertyKey ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ) ) + { + RelationshipType type = RelationshipType.withName( typeName ); + Iterable definitions = filterByConstraintTypeAndPropertyKey( + database.getGraph().schema().getConstraints( type ), + ConstraintType.RELATIONSHIP_PROPERTY_EXISTENCE, propertyKey ); + tx.success(); + return definitions; + } + } + + public ConstraintDefinition createPropertyUniquenessConstraint( String labelName, List propertyKeys ) + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.FULL) ) + { + ConstraintCreator creator = database.getGraph().schema().constraintFor( label( labelName ) ); + for ( String propertyKey : propertyKeys ) + { + creator = creator.assertPropertyIsUnique( propertyKey ); + } + ConstraintDefinition result = creator.create(); + tx.success(); + return result; + } + } + + public ConstraintDefinition createNodePropertyExistenceConstraint( String labelName, String propertyKey ) + { + String query = String.format( "CREATE CONSTRAINT ON (n:%s) ASSERT exists(n.%s)", labelName, propertyKey ); + database.getGraph().execute( query ); + awaitIndexes(); + return new NodePropertyExistenceConstraintDefinition( mock( InternalSchemaActions.class ), + label( labelName ), propertyKey ); + } + + public ConstraintDefinition createRelationshipPropertyExistenceConstraint( String typeName, String propertyKey ) + { + String query = String.format( "CREATE CONSTRAINT ON ()-[r:%s]-() ASSERT exists(r.%s)", typeName, propertyKey ); + database.getGraph().execute( query ); + awaitIndexes(); + return new RelationshipPropertyExistenceConstraintDefinition( mock( InternalSchemaActions.class ), + RelationshipType.withName( typeName ), propertyKey ); + } + + public long getLabelCount( long nodeId ) + { + try (Transaction transaction = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ)) + { + return count( database.getGraph().getNodeById( nodeId ).getLabels()); + } + } + + private static Iterable filterByConstraintTypeAndPropertyKey( + Iterable definitions, final ConstraintType type, final String propertyKey ) + { + return Iterables.filter( definition -> { + if ( definition.isConstraintType( type ) ) + { + Iterable keys = definition.getPropertyKeys(); + return single( keys ).equals( propertyKey ); + } + return false; + }, definitions ); + } + + private void awaitIndexes() + { + try ( Transaction tx = database.getGraph().beginTransaction( KernelTransaction.Type.implicit, AccessMode.Static.READ) ) + { + database.getGraph().schema().awaitIndexesOnline( 10, TimeUnit.SECONDS ); + tx.success(); + } + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/paging/PagedTraverserDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/paging/PagedTraverserDocIT.java similarity index 97% rename from community/server/src/test/java/org/neo4j/server/rest/paging/PagedTraverserDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/paging/PagedTraverserDocIT.java index 58ca9bbffc32d..10d32763a19af 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/paging/PagedTraverserDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/paging/PagedTraverserDocIT.java @@ -17,7 +17,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.paging; +package org.neo4j.doc.server.rest.paging; + +import java.net.URI; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.TimeUnit; +import javax.ws.rs.core.MediaType; import org.junit.AfterClass; import org.junit.Before; @@ -27,12 +33,14 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; -import java.net.URI; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; -import javax.ws.rs.core.MediaType; - +import org.neo4j.doc.server.ExclusiveServerTestBase; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; +import org.neo4j.doc.server.helpers.ServerHelper; +import org.neo4j.doc.server.rest.JaxRsResponse; +import org.neo4j.doc.server.rest.RESTDocsGenerator; +import org.neo4j.doc.server.rest.RESTDocsGenerator.ResponseEntity; +import org.neo4j.doc.server.rest.RestRequest; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.RelationshipType; import org.neo4j.graphdb.Transaction; @@ -40,22 +48,15 @@ import org.neo4j.kernel.impl.annotations.Documented; import org.neo4j.server.CommunityNeoServer; import org.neo4j.server.database.Database; -import org.neo4j.server.helpers.CommunityServerBuilder; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.helpers.ServerHelper; -import org.neo4j.server.rest.JaxRsResponse; -import org.neo4j.server.rest.RESTDocsGenerator; -import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; -import org.neo4j.server.rest.RestRequest; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.scripting.javascript.GlobalJavascriptInitializer; import org.neo4j.test.TestData; -import org.neo4j.test.server.ExclusiveServerTestBase; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThat; + import static org.neo4j.test.SuppressOutput.suppressAll; public class PagedTraverserDocIT extends ExclusiveServerTestBase diff --git a/community/server/src/test/java/org/neo4j/server/rest/security/AuthenticationDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/AuthenticationDocIT.java similarity index 94% rename from community/server/src/test/java/org/neo4j/server/rest/security/AuthenticationDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/AuthenticationDocIT.java index 06cc1a9a8daa9..60d6890c4e633 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/security/AuthenticationDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/AuthenticationDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.security; +package org.neo4j.doc.server.rest.security; import java.io.IOException; import javax.ws.rs.core.HttpHeaders; @@ -30,18 +30,17 @@ import org.junit.Rule; import org.junit.Test; +import org.neo4j.doc.server.ExclusiveServerTestBase; +import org.neo4j.doc.server.HTTP; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.kernel.impl.annotations.Documented; import org.neo4j.server.CommunityNeoServer; -import org.neo4j.server.helpers.CommunityServerBuilder; -import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.doc.server.rest.RESTDocsGenerator; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.string.UTF8; import org.neo4j.test.TestData; -import org.neo4j.test.server.ExclusiveServerTestBase; -import org.neo4j.test.server.HTTP; -import org.neo4j.test.server.HTTP.RawPayload; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertEquals; @@ -195,9 +194,9 @@ public void shouldNotAllowDataAccess() throws Exception startServerWithConfiguredUser(); // When & then - assertAuthorizationRequired( "POST", "db/data/node", RawPayload.quotedJson( "{'name':'jake'}" ), 201 ); + assertAuthorizationRequired( "POST", "db/data/node", HTTP.RawPayload.quotedJson( "{'name':'jake'}" ), 201 ); assertAuthorizationRequired( "GET", "db/data/node/1234", 404 ); - assertAuthorizationRequired( "POST", "db/data/transaction/commit", RawPayload.quotedJson( + assertAuthorizationRequired( "POST", "db/data/transaction/commit", HTTP.RawPayload.quotedJson( "{'statements':[{'statement':'MATCH (n) RETURN n'}]}" ), 200 ); assertEquals(200, HTTP.GET( server.baseUri().resolve( "" ).toString() ).status() ); @@ -211,10 +210,10 @@ public void shouldAllowAllAccessIfAuthenticationIsDisabled() throws Exception // When & then assertEquals( 201, HTTP.POST( server.baseUri().resolve( "db/data/node" ).toString(), - RawPayload.quotedJson( "{'name':'jake'}" ) ).status() ); + HTTP.RawPayload.quotedJson( "{'name':'jake'}" ) ).status() ); assertEquals( 404, HTTP.GET( server.baseUri().resolve( "db/data/node/1234" ).toString() ).status() ); assertEquals( 200, HTTP.POST( server.baseUri().resolve( "db/data/transaction/commit" ).toString(), - RawPayload.quotedJson( "{'statements':[{'statement':'MATCH (n) RETURN n'}]}" ) ).status() ); + HTTP.RawPayload.quotedJson( "{'statements':[{'statement':'MATCH (n) RETURN n'}]}" ) ).status() ); } @Test @@ -265,12 +264,12 @@ public void shouldNotAllowDataAccessForUnauthorizedUser() throws Exception // When & then assertEquals( 403, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ) .POST( server.baseUri().resolve( "db/data/node" ).toString(), - RawPayload.quotedJson( "{'name':'jake'}" ) ).status() ); + HTTP.RawPayload.quotedJson( "{'name':'jake'}" ) ).status() ); assertEquals( 403, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ) .GET( server.baseUri().resolve( "db/data/node/1234" ).toString() ).status() ); assertEquals( 403, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ) .POST( server.baseUri().resolve( "db/data/transaction/commit" ).toString(), - RawPayload.quotedJson( "{'statements':[{'statement':'MATCH (n) RETURN n'}]}" ) ).status() ); + HTTP.RawPayload.quotedJson( "{'statements':[{'statement':'MATCH (n) RETURN n'}]}" ) ).status() ); } private void assertAuthorizationRequired( String method, String path, int expectedAuthorizedStatus ) throws JsonParseException @@ -317,7 +316,7 @@ public void startServerWithConfiguredUser() throws IOException // Set the password HTTP.Response post = HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "neo4j" ) ).POST( server.baseUri().resolve( "/user/neo4j/password" ).toString(), - RawPayload.quotedJson( "{'password':'secret'}" ) + HTTP.RawPayload.quotedJson( "{'password':'secret'}" ) ); assertEquals( 200, post.status() ); } diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/NoAccessToDatabaseSecurityRule.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/NoAccessToDatabaseSecurityRule.java new file mode 100644 index 0000000000000..135e2565d6e5c --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/NoAccessToDatabaseSecurityRule.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest.security; + +import javax.servlet.http.HttpServletRequest; + +import org.neo4j.server.rest.security.SecurityFilter; +import org.neo4j.server.rest.security.SecurityRule; + +public class NoAccessToDatabaseSecurityRule implements SecurityRule +{ + private static boolean wasInvoked = false; + + public static boolean wasInvoked() + { + return wasInvoked; + } + + @Override + public boolean isAuthorized( HttpServletRequest request ) + { + wasInvoked = true; + return false; + } + + @Override + public String forUriPath() + { + return "/db*"; + } + + @Override + public String wwwAuthenticateHeader() + { + return SecurityFilter.basicAuthenticationResponse("WallyWorld"); + } +} diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyFailingSecurityRule.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyFailingSecurityRule.java new file mode 100644 index 0000000000000..bbfa2fb5fcb9f --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyFailingSecurityRule.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest.security; + +import javax.servlet.http.HttpServletRequest; + +import org.neo4j.server.rest.security.SecurityFilter; +import org.neo4j.server.rest.security.SecurityRule; + +//START SNIPPET: failingRule +public class PermanentlyFailingSecurityRule implements SecurityRule +{ + + public static final String REALM = "WallyWorld"; // as per RFC2617 :-) + + @Override + public boolean isAuthorized( HttpServletRequest request ) + { + return false; // always fails - a production implementation performs + // deployment-specific authorization logic here + } + + @Override + public String forUriPath() + { + return "/*"; + } + + @Override + public String wwwAuthenticateHeader() + { + return SecurityFilter.basicAuthenticationResponse(REALM); + } +} +// END SNIPPET: failingRule diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyFailingSecurityRuleWithComplexWildcardPath.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyFailingSecurityRuleWithComplexWildcardPath.java new file mode 100644 index 0000000000000..41463192b6ccf --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyFailingSecurityRuleWithComplexWildcardPath.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest.security; + +import javax.servlet.http.HttpServletRequest; + +import org.neo4j.server.rest.security.SecurityFilter; +import org.neo4j.server.rest.security.SecurityRule; + +//START SNIPPET: failingRuleWithComplexWildcardPath +public class PermanentlyFailingSecurityRuleWithComplexWildcardPath implements SecurityRule +{ + + public static final String REALM = "WallyWorld"; // as per RFC2617 :-) + + @Override + public boolean isAuthorized( HttpServletRequest request ) + { + return false; + } + + @Override + public String forUriPath() + { + return "/protected/*/something/else/*/final/bit"; + } + + @Override + public String wwwAuthenticateHeader() + { + return SecurityFilter.basicAuthenticationResponse(REALM); + } +} +// END SNIPPET: failingRuleWithComplexWildcardPath diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyFailingSecurityRuleWithWildcardPath.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyFailingSecurityRuleWithWildcardPath.java new file mode 100644 index 0000000000000..0db353c299f8e --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyFailingSecurityRuleWithWildcardPath.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest.security; + +import javax.servlet.http.HttpServletRequest; + +import org.neo4j.server.rest.security.SecurityFilter; +import org.neo4j.server.rest.security.SecurityRule; + + +public class PermanentlyFailingSecurityRuleWithWildcardPath implements SecurityRule +{ + + public static final String REALM = "WallyWorld"; // as per RFC2617 :-) + + + public boolean isAuthorized( HttpServletRequest request ) + { + return false; + } + + //START SNIPPET: failingRuleWithWildcardPath + public String forUriPath() + { + return "/protected/*"; + } + // END SNIPPET: failingRuleWithWildcardPath + + + public String wwwAuthenticateHeader() + { + return SecurityFilter.basicAuthenticationResponse(REALM); + } +} + diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyForbiddenSecurityRule.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyForbiddenSecurityRule.java new file mode 100644 index 0000000000000..7df1671195d85 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyForbiddenSecurityRule.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest.security; + +import javax.servlet.http.HttpServletRequest; + +import org.neo4j.server.rest.security.ForbiddingSecurityRule; +import org.neo4j.server.rest.security.SecurityFilter; + +//START SNIPPET: forbiddenRule +public class PermanentlyForbiddenSecurityRule implements ForbiddingSecurityRule +{ + + public static final String REALM = "WallyWorld"; // as per RFC2617 :-) + + @Override + public boolean isAuthorized( HttpServletRequest request ) + { + return true; // always authenticated + } + + @Override + public boolean isForbidden(HttpServletRequest request) + { + return true; + } + + @Override + public String forUriPath() + { + return "/*"; + } + + @Override + public String wwwAuthenticateHeader() + { + return SecurityFilter.basicAuthenticationResponse(REALM); + } +} +// END SNIPPET: forbiddenRule diff --git a/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyPassingSecurityRule.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyPassingSecurityRule.java new file mode 100644 index 0000000000000..648dd77647439 --- /dev/null +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/PermanentlyPassingSecurityRule.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2002-2016 "Neo Technology," + * Network Engine for Objects in Lund AB [http://neotechnology.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package org.neo4j.doc.server.rest.security; + +import javax.servlet.http.HttpServletRequest; + +import org.neo4j.server.configuration.ServerSettings; +import org.neo4j.server.rest.security.SecurityRule; + +public class PermanentlyPassingSecurityRule implements SecurityRule +{ + + public static final String REALM = "WallyWorld"; // as per RFC2617 :-); + + @Override + public boolean isAuthorized( HttpServletRequest request ) + { + return true; // always passes + } + + @Override + public String forUriPath() + { + return ServerSettings.rest_api_path.getDefaultValue(); + } + + @Override + public String wwwAuthenticateHeader() + { + return REALM; + } +} diff --git a/community/server/src/test/java/org/neo4j/server/rest/security/SecurityRulesDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/SecurityRulesDocIT.java similarity index 97% rename from community/server/src/test/java/org/neo4j/server/rest/security/SecurityRulesDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/SecurityRulesDocIT.java index df27b1fde5bf4..000c6403f286f 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/security/SecurityRulesDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/SecurityRulesDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.security; +package org.neo4j.doc.server.rest.security; import java.net.URI; import javax.ws.rs.core.MediaType; @@ -27,15 +27,15 @@ import org.junit.Rule; import org.junit.Test; +import org.neo4j.doc.server.ExclusiveServerTestBase; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; import org.neo4j.kernel.impl.annotations.Documented; import org.neo4j.server.CommunityNeoServer; -import org.neo4j.server.helpers.CommunityServerBuilder; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.JaxRsResponse; -import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.doc.server.rest.JaxRsResponse; +import org.neo4j.doc.server.rest.RESTDocsGenerator; import org.neo4j.test.TestData; import org.neo4j.test.TestData.Title; -import org.neo4j.test.server.ExclusiveServerTestBase; import static org.hamcrest.Matchers.containsString; import static org.junit.Assert.assertEquals; diff --git a/community/server/src/test/java/org/neo4j/server/rest/security/UsersDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/UsersDocIT.java similarity index 94% rename from community/server/src/test/java/org/neo4j/server/rest/security/UsersDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/UsersDocIT.java index 6934cf90fa6e0..6dcc16ab2db9c 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/security/UsersDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/security/UsersDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.security; +package org.neo4j.doc.server.rest.security; import java.io.IOException; import javax.ws.rs.core.HttpHeaders; @@ -25,21 +25,22 @@ import com.sun.jersey.core.util.Base64; import org.codehaus.jackson.JsonNode; import org.junit.After; +import org.junit.Assert; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.neo4j.doc.server.ExclusiveServerTestBase; +import org.neo4j.doc.server.HTTP; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.kernel.impl.annotations.Documented; import org.neo4j.server.CommunityNeoServer; -import org.neo4j.server.helpers.CommunityServerBuilder; -import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.doc.server.rest.RESTDocsGenerator; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.string.UTF8; import org.neo4j.test.TestData; -import org.neo4j.test.server.ExclusiveServerTestBase; -import org.neo4j.test.server.HTTP; import static org.hamcrest.CoreMatchers.equalTo; import static org.junit.Assert.assertEquals; @@ -121,7 +122,7 @@ public void change_password() throws JsonParseException, IOException .post( server.baseUri().resolve( "/user/neo4j/password" ).toString() ); // Then the new password should work - assertEquals( 200, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ).GET( dataURL() ).status() ); + Assert.assertEquals( 200, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "secret" ) ).GET( dataURL() ).status() ); // Then the old password should not be invalid assertEquals( 401, HTTP.withHeaders( HttpHeaders.AUTHORIZATION, challengeResponse( "neo4j", "neo4j" ) ).POST( dataURL() ).status() ); diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingBatchOperationDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingBatchOperationDocIT.java similarity index 99% rename from community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingBatchOperationDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingBatchOperationDocIT.java index 9e38cc01eb462..2af329b4309a0 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingBatchOperationDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingBatchOperationDocIT.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.streaming; +package org.neo4j.doc.server.rest.streaming; import com.sun.jersey.api.client.ClientHandlerException; import com.sun.jersey.api.client.UniformInterfaceException; @@ -27,14 +27,14 @@ import java.util.List; import java.util.Map; +import org.neo4j.doc.server.rest.PrettyJSON; import org.neo4j.graphdb.Neo4jMatchers; import org.neo4j.graphdb.Node; import org.neo4j.graphdb.Transaction; import org.neo4j.helpers.collection.Iterables; -import org.neo4j.server.rest.AbstractRestFunctionalTestBase; -import org.neo4j.server.rest.JaxRsResponse; -import org.neo4j.server.rest.PrettyJSON; -import org.neo4j.server.rest.RestRequest; +import org.neo4j.doc.server.rest.AbstractRestFunctionalTestBase; +import org.neo4j.doc.server.rest.JaxRsResponse; +import org.neo4j.doc.server.rest.RestRequest; import org.neo4j.server.rest.domain.JsonHelper; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.server.rest.repr.BadInputException; diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingCypherDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingCypherDocIT.java similarity index 88% rename from community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingCypherDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingCypherDocIT.java index 84eb443935fc4..e2f33d8690bb3 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingCypherDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingCypherDocIT.java @@ -17,10 +17,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.streaming; +package org.neo4j.doc.server.rest.streaming; -import org.neo4j.server.rest.CypherDocIT; -import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.doc.server.rest.CypherDocIT; +import org.neo4j.doc.server.rest.RESTDocsGenerator; import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; public class StreamingCypherDocIT extends CypherDocIT diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingIndexNodeDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingIndexNodeDocIT.java similarity index 88% rename from community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingIndexNodeDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingIndexNodeDocIT.java index 5282b4ddcec71..347ff72b5ae89 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingIndexNodeDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingIndexNodeDocIT.java @@ -17,10 +17,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.streaming; +package org.neo4j.doc.server.rest.streaming; -import org.neo4j.server.rest.IndexNodeDocIT; -import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.doc.server.rest.IndexNodeDocIT; +import org.neo4j.doc.server.rest.RESTDocsGenerator; import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; public class StreamingIndexNodeDocIT extends IndexNodeDocIT diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingPathsDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingPathsDocIT.java similarity index 88% rename from community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingPathsDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingPathsDocIT.java index 2ba9e21849ff2..b5ff44d430d39 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingPathsDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingPathsDocIT.java @@ -17,10 +17,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.streaming; +package org.neo4j.doc.server.rest.streaming; -import org.neo4j.server.rest.PathsDocIT; -import org.neo4j.server.rest.RESTDocsGenerator; +import org.neo4j.doc.server.rest.PathsDocIT; +import org.neo4j.doc.server.rest.RESTDocsGenerator; import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; public class StreamingPathsDocIT extends PathsDocIT diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingRelationshipDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingRelationshipDocIT.java similarity index 87% rename from community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingRelationshipDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingRelationshipDocIT.java index c0ff51681e819..6edb8bae0ae94 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingRelationshipDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingRelationshipDocIT.java @@ -17,10 +17,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.streaming; +package org.neo4j.doc.server.rest.streaming; -import org.neo4j.server.rest.RESTDocsGenerator; -import org.neo4j.server.rest.RelationshipDocIT; +import org.neo4j.doc.server.rest.RESTDocsGenerator; +import org.neo4j.doc.server.rest.RelationshipDocIT; import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; public class StreamingRelationshipDocIT extends RelationshipDocIT diff --git a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingTraverserDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingTraverserDocIT.java similarity index 88% rename from community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingTraverserDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingTraverserDocIT.java index 123348e7935a9..6ca78e06c4d8f 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/streaming/StreamingTraverserDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/streaming/StreamingTraverserDocIT.java @@ -17,10 +17,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.streaming; +package org.neo4j.doc.server.rest.streaming; -import org.neo4j.server.rest.RESTDocsGenerator; -import org.neo4j.server.rest.TraverserDocIT; +import org.neo4j.doc.server.rest.RESTDocsGenerator; +import org.neo4j.doc.server.rest.TraverserDocIT; import org.neo4j.server.rest.repr.formats.StreamingJsonFormat; public class StreamingTraverserDocIT extends TraverserDocIT diff --git a/community/server/src/test/java/org/neo4j/server/rest/transactional/TransactionDocTest.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/transactional/TransactionDocTest.java similarity index 94% rename from community/server/src/test/java/org/neo4j/server/rest/transactional/TransactionDocTest.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/transactional/TransactionDocTest.java index 12c73d3fc3fff..47767a96d5429 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/transactional/TransactionDocTest.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/transactional/TransactionDocTest.java @@ -17,9 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.transactional; - -import org.junit.Test; +package org.neo4j.doc.server.rest.transactional; import java.text.ParseException; import java.util.ArrayList; @@ -27,13 +25,15 @@ import java.util.List; import java.util.Map; +import org.junit.Test; + +import org.neo4j.doc.server.HTTP; +import org.neo4j.doc.server.rest.AbstractRestFunctionalTestBase; +import org.neo4j.doc.server.rest.RESTDocsGenerator.ResponseEntity; import org.neo4j.kernel.api.exceptions.Status; import org.neo4j.kernel.impl.annotations.Documented; -import org.neo4j.server.rest.AbstractRestFunctionalTestBase; -import org.neo4j.server.rest.RESTDocsGenerator.ResponseEntity; import org.neo4j.server.rest.domain.JsonParseException; import org.neo4j.server.rest.repr.util.RFC1123; -import org.neo4j.test.server.HTTP; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.equalTo; @@ -44,10 +44,9 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; + import static org.neo4j.helpers.collection.Iterators.iterator; import static org.neo4j.server.rest.domain.JsonHelper.jsonToMap; -import static org.neo4j.test.server.HTTP.GET; -import static org.neo4j.test.server.HTTP.POST; public class TransactionDocTest extends AbstractRestFunctionalTestBase { @@ -82,7 +81,7 @@ public void begin_a_transaction() throws JsonParseException public void execute_statements_in_an_open_transaction() throws JsonParseException { // Given - String location = POST( getDataUri() + "transaction" ).location(); + String location = HTTP.POST( getDataUri() + "transaction" ).location(); // Document ResponseEntity response = gen.get() @@ -106,7 +105,7 @@ public void execute_statements_in_an_open_transaction() throws JsonParseExceptio public void execute_statements_in_an_open_transaction_using_REST() throws JsonParseException { // Given - String location = POST( getDataUri() + "transaction" ).location(); + String location = HTTP.POST( getDataUri() + "transaction" ).location(); // Document ResponseEntity response = gen.get() @@ -136,7 +135,7 @@ public void reset_transaction_timeout_of_an_open_transaction() throws JsonParseException, ParseException, InterruptedException { // Given - HTTP.Response initialResponse = POST( getDataUri() + "transaction" ); + HTTP.Response initialResponse = HTTP.POST( getDataUri() + "transaction" ); String location = initialResponse.location(); long initialExpirationTime = expirationTime( jsonToMap( initialResponse.rawContent() ) ); @@ -170,7 +169,7 @@ public void reset_transaction_timeout_of_an_open_transaction() public void commit_an_open_transaction() throws JsonParseException { // Given - String location = POST( getDataUri() + "transaction" ).location(); + String location = HTTP.POST( getDataUri() + "transaction" ).location(); // Document ResponseEntity response = gen.get() @@ -184,7 +183,7 @@ public void commit_an_open_transaction() throws JsonParseException assertNoErrors( result ); Integer id = resultCell( result, 0, 0 ); - assertThat( GET( getNodeUri( id ) ).status(), is( 200 ) ); + assertThat( HTTP.GET( getNodeUri( id ) ).status(), is( 200 ) ); } @Test @@ -206,7 +205,7 @@ public void begin_and_commit_a_transaction_in_one_request() throws JsonParseExce assertNoErrors( result ); Integer id = resultCell( result, 0, 0 ); - assertThat( GET( getNodeUri( id ) ).status(), is( 200 ) ); + assertThat( HTTP.GET( getNodeUri( id ) ).status(), is( 200 ) ); } @Test @@ -227,7 +226,7 @@ public void execute_multiple_statements() throws JsonParseException Map result = jsonToMap( response.entity() ); assertNoErrors( result ); Integer id = resultCell( result, 0, 0 ); - assertThat( GET( getNodeUri( id ) ).status(), is( 200 ) ); + assertThat( HTTP.GET( getNodeUri( id ) ).status(), is( 200 ) ); assertThat( response.entity(), containsString( "My Node" ) ); } @@ -271,7 +270,7 @@ public void return_results_in_graph_format() throws JsonParseException public void rollback_an_open_transaction() throws JsonParseException { // Given - HTTP.Response firstReq = POST( getDataUri() + "transaction", + HTTP.Response firstReq = HTTP.POST( getDataUri() + "transaction", HTTP.RawPayload.quotedJson( "{ 'statements': [ { 'statement': 'CREATE (n) RETURN id(n)' } ] }" ) ); String location = firstReq.location(); @@ -286,7 +285,7 @@ public void rollback_an_open_transaction() throws JsonParseException assertNoErrors( result ); Integer id = resultCell( firstReq, 0, 0 ); - assertThat( GET( getNodeUri( id ) ).status(), is( 404 ) ); + assertThat( HTTP.GET( getNodeUri( id ) ).status(), is( 404 ) ); } @Test @@ -308,7 +307,7 @@ public void rollback_an_open_transaction() throws JsonParseException public void handling_errors() throws JsonParseException { // Given - String location = POST( getDataUri() + "transaction" ).location(); + String location = HTTP.POST( getDataUri() + "transaction" ).location(); // Document ResponseEntity response = gen.get() @@ -331,7 +330,7 @@ public void handling_errors() throws JsonParseException public void errors_in_open_transaction() throws JsonParseException { // Given - String location = POST( getDataUri() + "transaction" ).location(); + String location = HTTP.POST( getDataUri() + "transaction" ).location(); // Document ResponseEntity response = gen.get() diff --git a/community/server/src/main/java/org/neo4j/server/rest/transactional/error/ErrorDocumentationGenerator.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/transactional/error/ErrorDocumentationGenerator.java similarity index 99% rename from community/server/src/main/java/org/neo4j/server/rest/transactional/error/ErrorDocumentationGenerator.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/transactional/error/ErrorDocumentationGenerator.java index f1c5c23c7ba76..b6fd863566060 100644 --- a/community/server/src/main/java/org/neo4j/server/rest/transactional/error/ErrorDocumentationGenerator.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/transactional/error/ErrorDocumentationGenerator.java @@ -17,7 +17,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.transactional.error; +package org.neo4j.doc.server.rest.transactional.error; import java.io.File; import java.io.PrintStream; diff --git a/community/server/src/test/java/org/neo4j/server/rest/transactional/error/ErrorDocumentationGeneratorTest.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/transactional/error/ErrorDocumentationGeneratorTest.java similarity index 98% rename from community/server/src/test/java/org/neo4j/server/rest/transactional/error/ErrorDocumentationGeneratorTest.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/rest/transactional/error/ErrorDocumentationGeneratorTest.java index a55f8a01ea00d..99713226af241 100644 --- a/community/server/src/test/java/org/neo4j/server/rest/transactional/error/ErrorDocumentationGeneratorTest.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/rest/transactional/error/ErrorDocumentationGeneratorTest.java @@ -17,15 +17,16 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.rest.transactional.error; - -import org.junit.Test; +package org.neo4j.doc.server.rest.transactional.error; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.charset.StandardCharsets; +import org.junit.Test; + import static java.util.Arrays.asList; + import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.stringContainsInOrder; @@ -62,7 +63,6 @@ public void tablesShouldFormatAsAsciiDoc() throws Exception assertThat( result, is(equalTo( expected )) ); } - @Test public void shouldGenerateTableOfClassifications() throws Exception { diff --git a/community/server/src/test/java/org/neo4j/server/web/logging/HTTPLoggingDocIT.java b/manual/server-docs/src/test/java/org/neo4j/doc/server/web/logging/HTTPLoggingDocIT.java similarity index 94% rename from community/server/src/test/java/org/neo4j/server/web/logging/HTTPLoggingDocIT.java rename to manual/server-docs/src/test/java/org/neo4j/doc/server/web/logging/HTTPLoggingDocIT.java index ddab490e47f3b..0bac1943c7b39 100644 --- a/community/server/src/test/java/org/neo4j/server/web/logging/HTTPLoggingDocIT.java +++ b/manual/server-docs/src/test/java/org/neo4j/doc/server/web/logging/HTTPLoggingDocIT.java @@ -17,22 +17,23 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package org.neo4j.server.web.logging; +package org.neo4j.doc.server.web.logging; import org.apache.commons.io.FileUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; + +import org.neo4j.doc.server.ExclusiveServerTestBase; +import org.neo4j.doc.server.helpers.CommunityServerBuilder; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; import org.neo4j.function.ThrowingSupplier; import org.neo4j.graphdb.factory.GraphDatabaseSettings; import org.neo4j.server.NeoServer; import org.neo4j.server.configuration.ServerSettings; -import org.neo4j.server.helpers.CommunityServerBuilder; -import org.neo4j.server.helpers.FunctionalTestHelper; -import org.neo4j.server.rest.JaxRsResponse; -import org.neo4j.server.rest.RestRequest; +import org.neo4j.doc.server.rest.JaxRsResponse; +import org.neo4j.doc.server.rest.RestRequest; import org.neo4j.test.TargetDirectory; -import org.neo4j.test.server.ExclusiveServerTestBase; import java.io.File; import java.io.IOException; diff --git a/manual/server-docs/src/test/resources/certificates/CA.sh b/manual/server-docs/src/test/resources/certificates/CA.sh new file mode 100755 index 0000000000000..d638958f038f0 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/CA.sh @@ -0,0 +1,326 @@ +#!/bin/sh + +# LICENSE ISSUES +# ============== +# +# The OpenSSL toolkit stays under a dual license, i.e. both the conditions of +# the OpenSSL License and the original SSLeay license apply to the toolkit. +# See below for the actual license texts. Actually both licenses are BSD-style +# Open Source licenses. In case of any license issues related to OpenSSL +# please contact openssl-core@openssl.org. +# +# OpenSSL License +# --------------- +# +# ==================================================================== +# Copyright (c) 1998-2011 The OpenSSL Project. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# +# 3. All advertising materials mentioning features or use of this +# software must display the following acknowledgment: +# "This product includes software developed by the OpenSSL Project +# for use in the OpenSSL Toolkit. (http://www.openssl.org/)" +# +# 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to +# endorse or promote products derived from this software without +# prior written permission. For written permission, please contact +# openssl-core@openssl.org. +# +# 5. Products derived from this software may not be called "OpenSSL" +# nor may "OpenSSL" appear in their names without prior written +# permission of the OpenSSL Project. +# +# 6. Redistributions of any form whatsoever must retain the following +# acknowledgment: +# "This product includes software developed by the OpenSSL Project +# for use in the OpenSSL Toolkit (http://www.openssl.org/)" +# +# THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY +# EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE OpenSSL PROJECT OR +# ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +# OF THE POSSIBILITY OF SUCH DAMAGE. +# ==================================================================== +# +# This product includes cryptographic software written by Eric Young +# (eay@cryptsoft.com). This product includes software written by Tim +# Hudson (tjh@cryptsoft.com). +# +# +# +# Original SSLeay License +# ----------------------- +# +# Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com) +# All rights reserved. +# +# This package is an SSL implementation written +# by Eric Young (eay@cryptsoft.com). +# The implementation was written so as to conform with Netscapes SSL. +# +# This library is free for commercial and non-commercial use as long as +# the following conditions are aheared to. The following conditions +# apply to all code found in this distribution, be it the RC4, RSA, +# lhash, DES, etc., code; not just the SSL code. The SSL documentation +# included with this distribution is covered by the same copyright terms +# except that the holder is Tim Hudson (tjh@cryptsoft.com). +# +# Copyright remains Eric Young's, and as such any Copyright notices in +# the code are not to be removed. +# If this package is used in a product, Eric Young should be given attribution +# as the author of the parts of the library used. +# This can be in the form of a textual message at program startup or +# in documentation (online or textual) provided with the package. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. All advertising materials mentioning features or use of this software +# must display the following acknowledgement: +# "This product includes cryptographic software written by +# Eric Young (eay@cryptsoft.com)" +# The word 'cryptographic' can be left out if the rouines from the library +# being used are not cryptographic related :-). +# 4. If you include any Windows specific code (or a derivative thereof) from +# the apps directory (application code) you must include an acknowledgement: +# "This product includes software written by Tim Hudson (tjh@cryptsoft.com)" +# +# THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. +# +# The licence and distribution terms for any publically available version or +# derivative of this code cannot be changed. i.e. this code cannot simply be +# copied and put under another distribution licence +# [including the GNU Public Licence.] +# + + +# +# CA - wrapper around ca to make it easier to use ... basically ca requires +# some setup stuff to be done before you can use it and this makes +# things easier between now and when Eric is convinced to fix it :-) +# +# CA -newca ... will setup the right stuff +# CA -newreq ... will generate a certificate request +# CA -sign ... will sign the generated request and output +# +# At the end of that grab newreq.pem and newcert.pem (one has the key +# and the other the certificate) and cat them together and that is what +# you want/need ... I'll make even this a little cleaner later. +# +# +# 12-Jan-96 tjh Added more things ... including CA -signcert which +# converts a certificate to a request and then signs it. +# 10-Jan-96 eay Fixed a few more bugs and added the SSLEAY_CONFIG +# environment variable so this can be driven from +# a script. +# 25-Jul-96 eay Cleaned up filenames some more. +# 11-Jun-96 eay Fixed a few filename missmatches. +# 03-May-96 eay Modified to use 'ssleay cmd' instead of 'cmd'. +# 18-Apr-96 tjh Original hacking +# +# Tim Hudson +# tjh@cryptsoft.com +# + +# default openssl.cnf file has setup as per the following +# demoCA ... where everything is stored +cp_pem() { + infile=$1 + outfile=$2 + bound=$3 + flag=0 + exec <$infile; + while read line; do + if [ $flag -eq 1 ]; then + echo $line|grep "^-----END.*$bound" 2>/dev/null 1>/dev/null + if [ $? -eq 0 ] ; then + echo $line >>$outfile + break + else + echo $line >>$outfile + fi + fi + + echo $line|grep "^-----BEGIN.*$bound" 2>/dev/null 1>/dev/null + if [ $? -eq 0 ]; then + echo $line >$outfile + flag=1 + fi + done +} + +usage() { + echo "usage: $0 -newcert|-newreq|-newreq-nodes|-newca|-sign|-verify" >&2 +} + +if [ -z "$OPENSSL" ]; then OPENSSL=openssl; fi + +if [ -z "$DAYS" ] ; then DAYS="-days 10000" ; fi # 1 year +CADAYS="-days 20000" # 3 years +REQ="$OPENSSL req -config openssl.cnf" +CA="$OPENSSL ca -config openssl.cnf" +VERIFY="$OPENSSL verify" +X509="$OPENSSL x509" +PKCS12="openssl pkcs12" + +if [ -z "$CATOP" ] ; then CATOP=./demoCA ; fi +CAKEY=./cakey.pem +CAREQ=./careq.pem +CACERT=./cacert.pem + +RET=0 + +while [ "$1" != "" ] ; do +case $1 in +-\?|-h|-help) + usage + exit 0 + ;; +-newcert) + # create a certificate + $REQ -new -x509 -keyout newkey.pem -out newcert.pem $DAYS + RET=$? + echo "Certificate is in newcert.pem, private key is in newkey.pem" + ;; +-newreq) + # create a certificate request + $REQ -new -keyout newkey.pem -out newreq.pem $DAYS + RET=$? + echo "Request is in newreq.pem, private key is in newkey.pem" + ;; +-newreq-nodes) + # create a certificate request + $REQ -new -nodes -keyout newreq.pem -out newreq.pem $DAYS + RET=$? + echo "Request (and private key) is in newreq.pem" + ;; +-newca) + # if explicitly asked for or it doesn't exist then setup the directory + # structure that Eric likes to manage things + NEW="1" + if [ "$NEW" -o ! -f ${CATOP}/serial ]; then + # create the directory hierarchy + mkdir -p ${CATOP} + mkdir -p ${CATOP}/certs + mkdir -p ${CATOP}/crl + mkdir -p ${CATOP}/newcerts + mkdir -p ${CATOP}/private + touch ${CATOP}/index.txt + fi + if [ ! -f ${CATOP}/private/$CAKEY ]; then + echo "CA certificate filename (or enter to create)" + read FILE + + # ask user for existing CA certificate + if [ "$FILE" ]; then + cp_pem $FILE ${CATOP}/private/$CAKEY PRIVATE + cp_pem $FILE ${CATOP}/$CACERT CERTIFICATE + RET=$? + if [ ! -f "${CATOP}/serial" ]; then + $X509 -in ${CATOP}/$CACERT -noout -next_serial \ + -out ${CATOP}/serial + fi + else + echo "Making CA certificate ..." + $REQ -new -keyout ${CATOP}/private/$CAKEY -nodes \ + -out ${CATOP}/$CAREQ + $CA -create_serial -out ${CATOP}/$CACERT $CADAYS -batch \ + -keyfile ${CATOP}/private/$CAKEY -selfsign \ + -extensions v3_ca \ + -infiles ${CATOP}/$CAREQ + RET=$? + fi + fi + ;; +-xsign) + $CA -policy policy_anything -infiles newreq.pem + RET=$? + ;; +-pkcs12) + if [ -z "$2" ] ; then + CNAME="My Certificate" + else + CNAME="$2" + fi + $PKCS12 -in newcert.pem -inkey newreq.pem -certfile ${CATOP}/$CACERT \ + -out newcert.p12 -export -name "$CNAME" + RET=$? + exit $RET + ;; +-sign|-signreq) + $CA -policy policy_anything -out newcert.pem -infiles newreq.pem + RET=$? + cat newcert.pem + echo "Signed certificate is in newcert.pem" + ;; +-signCA) + $CA -policy policy_anything -out newcert.pem -extensions v3_ca -infiles newreq.pem + RET=$? + echo "Signed CA certificate is in newcert.pem" + ;; +-signcert) + echo "Cert passphrase will be requested twice - bug?" + $X509 -x509toreq -in newreq.pem -signkey newreq.pem -out tmp.pem + $CA -policy policy_anything -out newcert.pem -infiles tmp.pem + RET=$? + cat newcert.pem + echo "Signed certificate is in newcert.pem" + ;; +-verify) + shift + if [ -z "$1" ]; then + $VERIFY -CAfile $CATOP/$CACERT newcert.pem + RET=$? + else + for j + do + $VERIFY -CAfile $CATOP/$CACERT $j + if [ $? != 0 ]; then + RET=$? + fi + done + fi + exit $RET + ;; +*) + echo "Unknown arg $i" >&2 + usage + exit 1 + ;; +esac +shift +done +exit $RET diff --git a/manual/server-docs/src/test/resources/certificates/README.txt b/manual/server-docs/src/test/resources/certificates/README.txt new file mode 100644 index 0000000000000..4f29e273807e5 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/README.txt @@ -0,0 +1,27 @@ +this directory contains a mini CA based on openssl's CA.sh script +for info how to create chained certificates: http://www.ibm.com/developerworks/library/j-certgen/ + +notes: +* CA certificate is not password protected, also private keys for certificates are not protected. +* TestKeyStoreFactory.java uses chained_key.der and combined.pem +* to generate chained_key.der and combined.pem the following steps have been taken: + +1) setup CA using openssl's CA.sh script, see URL above +2) create a chained certificate as described in URL above +3) convert key from chained certificate to DER format: +openssl pkcs8 -in chained_key.pem -nocrypt -out chained_key.der -outform DER +4) copy certificate from intermediate cert and chained cert to combined.pem + +certificate directly signed by CA: +direct_cert.pem +direct_req.pem + +intermediate CA certificate signed by CA: +intermediate_cert.pem +intermediate_req.pem + +chained certificate signed by intermediate CA: +chained_cert.pem +chained_req.pem + +intermediate_cert.pem and chained_cert.pem are copied to combined.pem diff --git a/manual/server-docs/src/test/resources/certificates/chained_cert.pem b/manual/server-docs/src/test/resources/certificates/chained_cert.pem new file mode 100644 index 0000000000000..2e8e8a8ae5915 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/chained_cert.pem @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 14895342359247964531 (0xceb6e2f4f2cdc973) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, ST=Malm\xC3\x83\xC2\xB6, L=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=intermediate.neo4j.org/emailAddress=intermediate@neo4j.org + Validity + Not Before: May 12 11:16:37 2013 GMT + Not After : Apr 18 11:16:37 2113 GMT + Subject: C=SE, ST=Malm\xC3\x83\xC2\xB6, L=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=chained.neo4j.org/emailAddress=chained@neo4j.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:cd:6d:d3:b2:02:e4:6e:0e:8e:9b:6c:2a:82:49: + bb:80:23:79:63:cc:c4:a0:e2:7d:5b:36:50:70:42: + b1:d6:ed:54:64:19:c9:5e:38:8d:9c:5c:00:aa:f1: + ea:fb:cb:6f:d1:9c:0a:4a:68:ac:e8:a9:63:3f:9a: + 5e:62:3f:f1:2f:bd:e7:e4:9e:a4:dc:f4:68:b6:bf: + 12:95:f4:24:ba:d6:c3:33:e9:3e:b1:f0:4c:67:b5: + 94:2c:12:08:4c:b3:17:8f:25:4b:da:fd:2b:c1:be: + 13:4e:8b:fe:0d:8a:ce:db:2c:30:ba:8b:0e:e8:43: + fe:5a:4a:50:de:42:61:4f:52:77:5d:5f:78:30:c5: + 16:6a:de:d1:26:00:bd:4e:b8:9f:34:b5:b0:30:29: + 0b:3e:4c:58:4c:de:4a:b4:27:a8:53:3c:98:8b:3c: + 78:50:c8:2c:13:64:a5:9d:71:bb:ef:3d:26:14:13: + d2:95:9e:fd:31:ef:08:f5:73:9d:92:c7:83:20:e8: + 9b:59:36:4b:ef:8c:6a:c9:d9:7e:d6:16:71:52:8a: + 64:07:36:6c:aa:b1:41:fa:d8:16:f4:7d:40:39:6f: + dd:6c:f6:5c:bb:b6:93:58:1b:45:f6:63:5f:20:d1: + a9:d0:4c:42:39:1f:2d:2a:65:fa:67:f3:34:f0:99: + 92:6b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + C2:27:B5:24:68:81:C8:45:74:D9:CE:8D:31:4B:2D:B8:3F:54:C8:1B + X509v3 Authority Key Identifier: + DirName:/C=SE/ST=Malm\xC3\x83\xC2\xB6/O=Neo Technology/CN=ca.neo4j.org/emailAddress=ca@neo4j.org + serial:CE:B6:E2:F4:F2:CD:C9:72 + + Signature Algorithm: sha1WithRSAEncryption + c5:6c:36:28:d6:c7:b2:f4:07:67:e9:94:84:44:d1:04:9d:74: + fe:59:84:3e:38:0d:59:a1:09:23:86:2e:3a:2a:0a:b4:7c:bc: + 95:61:1f:fa:4d:f7:36:4b:7c:54:c5:52:15:51:9d:60:f6:47: + 79:9c:4c:84:ac:33:ba:b8:74:1d:3c:1b:6a:c8:92:70:1d:2f: + b2:5d:20:e4:d8:bc:14:f8:a9:a4:5f:b9:bf:ef:09:fa:31:02: + 2d:09:ce:50:c5:68:a7:d3:24:06:e5:da:88:cf:9d:9d:3b:c3: + 9e:fe:af:eb:67:16:0c:f1:a7:2a:17:2f:8e:7a:5b:7b:de:b5: + 51:77:48:eb:d0:f4:7f:46:30:2f:76:25:32:c0:bb:8e:92:08: + 34:0f:f6:65:9c:d8:78:12:a9:ca:41:2a:b0:f0:8c:35:98:38: + 4b:0e:86:5f:01:2b:74:6e:44:c2:0d:38:f5:ba:cf:ee:04:96: + c4:e9:f5:b2:dd:92:7c:fa:6e:b5:63:4b:eb:3e:38:c2:5d:9b: + 66:4a:f1:c7:c6:44:fc:26:3e:85:59:2d:34:92:c6:19:8f:c9: + 8b:a5:a6:fb:b2:6d:94:fd:c0:8b:3b:1f:b6:ef:af:b5:2e:84: + ff:73:5f:3b:1e:d3:ff:15:82:e7:dd:be:48:45:7d:ba:ba:b5: + 43:94:67:f9 +-----BEGIN CERTIFICATE----- +MIIEjDCCA3SgAwIBAgIJAM624vTyzclzMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJTRTERMA8GA1UECAwITWFsbcODwrYxETAPBgNVBAcMCE1hbG3Dg8K2MRcw +FQYDVQQKDA5OZW8gVGVjaG5vbG9neTEfMB0GA1UEAwwWaW50ZXJtZWRpYXRlLm5l +bzRqLm9yZzElMCMGCSqGSIb3DQEJARYWaW50ZXJtZWRpYXRlQG5lbzRqLm9yZzAg +Fw0xMzA1MTIxMTE2MzdaGA8yMTEzMDQxODExMTYzN1owgYoxCzAJBgNVBAYTAlNF +MREwDwYDVQQIDAhNYWxtw4PCtjERMA8GA1UEBwwITWFsbcODwrYxFzAVBgNVBAoM +Dk5lbyBUZWNobm9sb2d5MRowGAYDVQQDDBFjaGFpbmVkLm5lbzRqLm9yZzEgMB4G +CSqGSIb3DQEJARYRY2hhaW5lZEBuZW80ai5vcmcwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDNbdOyAuRuDo6bbCqCSbuAI3ljzMSg4n1bNlBwQrHW7VRk +GcleOI2cXACq8er7y2/RnApKaKzoqWM/ml5iP/EvvefknqTc9Gi2vxKV9CS61sMz +6T6x8ExntZQsEghMsxePJUva/SvBvhNOi/4Nis7bLDC6iw7oQ/5aSlDeQmFPUndd +X3gwxRZq3tEmAL1OuJ80tbAwKQs+TFhM3kq0J6hTPJiLPHhQyCwTZKWdcbvvPSYU +E9KVnv0x7wj1c52Sx4Mg6JtZNkvvjGrJ2X7WFnFSimQHNmyqsUH62Bb0fUA5b91s +9ly7tpNYG0X2Y18g0anQTEI5Hy0qZfpn8zTwmZJrAgMBAAGjgeYwgeMwCQYDVR0T +BAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNh +dGUwHQYDVR0OBBYEFMIntSRogchFdNnOjTFLLbg/VMgbMIGIBgNVHSMEgYAwfqFx +pG8wbTELMAkGA1UEBhMCU0UxETAPBgNVBAgMCE1hbG3Dg8K2MRcwFQYDVQQKDA5O +ZW8gVGVjaG5vbG9neTEVMBMGA1UEAwwMY2EubmVvNGoub3JnMRswGQYJKoZIhvcN +AQkBFgxjYUBuZW80ai5vcmeCCQDOtuL08s3JcjANBgkqhkiG9w0BAQUFAAOCAQEA +xWw2KNbHsvQHZ+mUhETRBJ10/lmEPjgNWaEJI4YuOioKtHy8lWEf+k33Nkt8VMVS +FVGdYPZHeZxMhKwzurh0HTwbasiScB0vsl0g5Ni8FPippF+5v+8J+jECLQnOUMVo +p9MkBuXaiM+dnTvDnv6v62cWDPGnKhcvjnpbe961UXdI69D0f0YwL3YlMsC7jpII +NA/2ZZzYeBKpykEqsPCMNZg4Sw6GXwErdG5Ewg049brP7gSWxOn1st2SfPputWNL +6z44wl2bZkrxx8ZE/CY+hVktNJLGGY/Ji6Wm+7JtlP3Aizsftu+vtS6E/3NfOx7T +/xWC592+SEV9urq1Q5Rn+Q== +-----END CERTIFICATE----- diff --git a/manual/server-docs/src/test/resources/certificates/chained_key.der b/manual/server-docs/src/test/resources/certificates/chained_key.der new file mode 100644 index 0000000000000..2dee438d4b86d Binary files /dev/null and b/manual/server-docs/src/test/resources/certificates/chained_key.der differ diff --git a/manual/server-docs/src/test/resources/certificates/chained_key.pem b/manual/server-docs/src/test/resources/certificates/chained_key.pem new file mode 100644 index 0000000000000..4ebf2b31aa20e --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/chained_key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDNbdOyAuRuDo6b +bCqCSbuAI3ljzMSg4n1bNlBwQrHW7VRkGcleOI2cXACq8er7y2/RnApKaKzoqWM/ +ml5iP/EvvefknqTc9Gi2vxKV9CS61sMz6T6x8ExntZQsEghMsxePJUva/SvBvhNO +i/4Nis7bLDC6iw7oQ/5aSlDeQmFPUnddX3gwxRZq3tEmAL1OuJ80tbAwKQs+TFhM +3kq0J6hTPJiLPHhQyCwTZKWdcbvvPSYUE9KVnv0x7wj1c52Sx4Mg6JtZNkvvjGrJ +2X7WFnFSimQHNmyqsUH62Bb0fUA5b91s9ly7tpNYG0X2Y18g0anQTEI5Hy0qZfpn +8zTwmZJrAgMBAAECggEAMIEIBPXt3ZaOUWA+wTiLjvvRQ6ErpBkWJB007OyydN7g +mCoGg7qypQKMHdC0/PLR2hoPy/XfLaghCPl345bZab5bLH3Qy2Lh/pQq3UEiEwN2 +X6Fo7jags7QJ4kX3s7RtkZzqxjAUztAEWv1u5N+ra9CJSOJgiLLVMZhuc6wUsdeI +U6Ju5K+18TeQdJclWumTC/BnTeQwN02AxSi7wngwF3eeRFepSkOT8T6NqIGKKBl2 +LeLgaknpfMoZ0Bs2jVJtmj8AiCcFuowgL3y8hZYdUBT05w5ER4LISrYYowR54cyv +pA+0W8ZpQCnWlVIu/7T84gpozccCN6q4mT+baFRXQQKBgQD1AWxTnshkz5aN5O8w +r7rQyFbBGah6WyRC7UlBqTJvyafjHrT5gXrlQGQkInM+82x1vWmv8y4BySpxu1MX +yELYizeH6W2bjK80b9ah8OaY9a+f4eT8f5CLJt2nVCyNX1ejPtAjOKtfXw5qsvQ1 +e8DqqGOz8Eno6itJzErhRf68kQKBgQDWpcGJGv5Wd7Ahb5i2tS2NeufMJHDIBRv4 +37TIzLAGakjgPXrLtPuUXksSFuq5TwEO+vYAI0eXzbFB7GMNHHXcXuNsMSf71Vmk +rC+Y7Xs8v2slF7uYPc4H97Tld2Z9FGtA823rPB+7yeHUfoyjecluzUJ5b6XrCP2B +l0DiNaTNOwKBgBn/PqlYzda0mDsSMbcuzlndCCeRO2lQRna7iuxmsa1e7muRFI7t +CEdPH9EdvrDNT5LmuFItMZKK7/AFenmpjD5B93B6emDo+qZLv7CLSUyttSb35z5L +ouDBDP/2VIvjs/7kaV1zY+vIwFTeDD+ok/3SRFIRiyYlwtYf0Uo6DR+hAoGAOxJa +9NSM8lb2Df2cVYv/AVS1viS5NRLS4+cM5cYPG3TcoXnJ9Y3VPrvEhTHxXxYzAGBo +mxkadtiQ08euaslOVlt20yukCVZp2TvZzYhtxgYYAplJc1hu/xpYml4Fwp5/9dwI +gBiH0zJ4B/bb1ZGKsgejJ7BmRQywV528qYSB2kkCgYEAnXYqShY9oPmruoa2etGR +GubdX3pAPQ9JWE8A1pY3LUhDuqbESa+Va9chG39s80hv96nIY4hfT/fqGrCEv5ec +DgBE5jwDzT6gOkuvGlhiIGspogtUH9IPxll/mxftXHJApnMgm6Q7QcrLWvX+BcAG +ODhj++6h83zvozULRMXUJMI= +-----END PRIVATE KEY----- diff --git a/manual/server-docs/src/test/resources/certificates/chained_req.pem b/manual/server-docs/src/test/resources/certificates/chained_req.pem new file mode 100644 index 0000000000000..f827f14dc5200 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/chained_req.pem @@ -0,0 +1,46 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDNbdOyAuRuDo6b +bCqCSbuAI3ljzMSg4n1bNlBwQrHW7VRkGcleOI2cXACq8er7y2/RnApKaKzoqWM/ +ml5iP/EvvefknqTc9Gi2vxKV9CS61sMz6T6x8ExntZQsEghMsxePJUva/SvBvhNO +i/4Nis7bLDC6iw7oQ/5aSlDeQmFPUnddX3gwxRZq3tEmAL1OuJ80tbAwKQs+TFhM +3kq0J6hTPJiLPHhQyCwTZKWdcbvvPSYUE9KVnv0x7wj1c52Sx4Mg6JtZNkvvjGrJ +2X7WFnFSimQHNmyqsUH62Bb0fUA5b91s9ly7tpNYG0X2Y18g0anQTEI5Hy0qZfpn +8zTwmZJrAgMBAAECggEAMIEIBPXt3ZaOUWA+wTiLjvvRQ6ErpBkWJB007OyydN7g +mCoGg7qypQKMHdC0/PLR2hoPy/XfLaghCPl345bZab5bLH3Qy2Lh/pQq3UEiEwN2 +X6Fo7jags7QJ4kX3s7RtkZzqxjAUztAEWv1u5N+ra9CJSOJgiLLVMZhuc6wUsdeI +U6Ju5K+18TeQdJclWumTC/BnTeQwN02AxSi7wngwF3eeRFepSkOT8T6NqIGKKBl2 +LeLgaknpfMoZ0Bs2jVJtmj8AiCcFuowgL3y8hZYdUBT05w5ER4LISrYYowR54cyv +pA+0W8ZpQCnWlVIu/7T84gpozccCN6q4mT+baFRXQQKBgQD1AWxTnshkz5aN5O8w +r7rQyFbBGah6WyRC7UlBqTJvyafjHrT5gXrlQGQkInM+82x1vWmv8y4BySpxu1MX +yELYizeH6W2bjK80b9ah8OaY9a+f4eT8f5CLJt2nVCyNX1ejPtAjOKtfXw5qsvQ1 +e8DqqGOz8Eno6itJzErhRf68kQKBgQDWpcGJGv5Wd7Ahb5i2tS2NeufMJHDIBRv4 +37TIzLAGakjgPXrLtPuUXksSFuq5TwEO+vYAI0eXzbFB7GMNHHXcXuNsMSf71Vmk +rC+Y7Xs8v2slF7uYPc4H97Tld2Z9FGtA823rPB+7yeHUfoyjecluzUJ5b6XrCP2B +l0DiNaTNOwKBgBn/PqlYzda0mDsSMbcuzlndCCeRO2lQRna7iuxmsa1e7muRFI7t +CEdPH9EdvrDNT5LmuFItMZKK7/AFenmpjD5B93B6emDo+qZLv7CLSUyttSb35z5L +ouDBDP/2VIvjs/7kaV1zY+vIwFTeDD+ok/3SRFIRiyYlwtYf0Uo6DR+hAoGAOxJa +9NSM8lb2Df2cVYv/AVS1viS5NRLS4+cM5cYPG3TcoXnJ9Y3VPrvEhTHxXxYzAGBo +mxkadtiQ08euaslOVlt20yukCVZp2TvZzYhtxgYYAplJc1hu/xpYml4Fwp5/9dwI +gBiH0zJ4B/bb1ZGKsgejJ7BmRQywV528qYSB2kkCgYEAnXYqShY9oPmruoa2etGR +GubdX3pAPQ9JWE8A1pY3LUhDuqbESa+Va9chG39s80hv96nIY4hfT/fqGrCEv5ec +DgBE5jwDzT6gOkuvGlhiIGspogtUH9IPxll/mxftXHJApnMgm6Q7QcrLWvX+BcAG +ODhj++6h83zvozULRMXUJMI= +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE REQUEST----- +MIIC0DCCAbgCAQAwgYoxCzAJBgNVBAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjER +MA8GA1UEBwwITWFsbcODwrYxFzAVBgNVBAoMDk5lbyBUZWNobm9sb2d5MRowGAYD +VQQDDBFjaGFpbmVkLm5lbzRqLm9yZzEgMB4GCSqGSIb3DQEJARYRY2hhaW5lZEBu +ZW80ai5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDNbdOyAuRu +Do6bbCqCSbuAI3ljzMSg4n1bNlBwQrHW7VRkGcleOI2cXACq8er7y2/RnApKaKzo +qWM/ml5iP/EvvefknqTc9Gi2vxKV9CS61sMz6T6x8ExntZQsEghMsxePJUva/SvB +vhNOi/4Nis7bLDC6iw7oQ/5aSlDeQmFPUnddX3gwxRZq3tEmAL1OuJ80tbAwKQs+ +TFhM3kq0J6hTPJiLPHhQyCwTZKWdcbvvPSYUE9KVnv0x7wj1c52Sx4Mg6JtZNkvv +jGrJ2X7WFnFSimQHNmyqsUH62Bb0fUA5b91s9ly7tpNYG0X2Y18g0anQTEI5Hy0q +Zfpn8zTwmZJrAgMBAAGgADANBgkqhkiG9w0BAQUFAAOCAQEAQYK1Gdq6y1KzZt0R +LeZU+zlJ7lpHF8Uv7zoZUleDLFwM8gEhKWHEGqj0j3tw6ezyPOnQ6HBi2e3OueDZ +WaWRAz57/j1JUx6IXEja4zHBVf3yd+MJcNRsDpBi1awpBBax1PQbIMdAP06Ujrnh +LvXt30rFIiraiDR9PJfnkFcXoTi1EIQUwADe7Z5MsqaKi4UHyoQeuaIe5l765Zg/ +zLwN+lSqcpQ5nth04640dMh1vdDONv0EQlM7B0oXarGv5f4qfQkRMlfs3sYMRisn +mfLtUaiN/TSh35o3uhf+BVWHZu5dHw3A0pWJcRIMOVROQcgQ7vWDhFFwN7GhY4TK +5UhUYg== +-----END CERTIFICATE REQUEST----- diff --git a/manual/server-docs/src/test/resources/certificates/combined.pem b/manual/server-docs/src/test/resources/certificates/combined.pem new file mode 100644 index 0000000000000..55dd32301c372 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/combined.pem @@ -0,0 +1,71 @@ +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIJAM624vTyzclwMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjEXMBUGA1UECgwOTmVvIFRlY2hub2xv +Z3kxFTATBgNVBAMMDGNhLm5lbzRqLm9yZzEbMBkGCSqGSIb3DQEJARYMY2FAbmVv +NGoub3JnMCAXDTEzMDUxMjExMTIwMloYDzIwNjgwMjEzMTExMjAyWjBtMQswCQYD +VQQGEwJTRTERMA8GA1UECAwITWFsbcODwrYxFzAVBgNVBAoMDk5lbyBUZWNobm9s +b2d5MRUwEwYDVQQDDAxjYS5uZW80ai5vcmcxGzAZBgkqhkiG9w0BCQEWDGNhQG5l +bzRqLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALRN65IxZdmy +iwjbq+W2ZUioXHKJ4L//Nuqc3LfXhC8IKFv0dojbNFHalcfZEt/EjVRdAG8bn3N9 +FUY5hhaH1V43K0PcL1elym0/TxYFk9U3RhEOwYO8MxjI49nOCyMIH72sUCd+JQak +VF1CVkL8UouTTGCwxFgsMUUU4+jwNkKY8g2ZduVTszYmqD0nCdW3pMrs1F0zzddG +r4A/d1Pjr3GOVED43P9JxYQmhQLVXODW5raN0VKzQ+AQdUWdDUvOp+fqCqwrqBiW +prUCspdiQMDBrX8pzZBoycth7AMHoXr05pzV6u6Zp22nwx8fNGWJI4YwYJj/9zL0 +kI0lr62aqCMCAwEAAaNQME4wHQYDVR0OBBYEFNNKx8NH7ZnNlttewtIhpWFgjH8G +MB8GA1UdIwQYMBaAFNNKx8NH7ZnNlttewtIhpWFgjH8GMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAFcIFEqPhrEhVeBQnGUXubbQgXe2j45Wluf/n3zx +jjtKwfJADANMv6hpjxS3OManp7YDbYb4uo57r3hOQduWKPJH3EOFhHxW9dkJYbe2 +XGPcKxrHrGqpD2hVH6bbItAs22s1yP0ZkNgTBLZZ+eoEsCLf4Kb4NNFcFmOybW97 +z6YOnSRRLmX7cyU9zd/Ki/KaGON1ijICBqhNp9w1LsB/p0s3Ygjh7v0XYWgZAASM +8RmnsSaunMDjZZr5dyOOHiF97Ll9CT7LPURjcoRr/zxPVgbNjfjYLSXQ8Z2f1dN8 +/BrLqG3VGg7C7IPLuqeFTzI76lJMFw6pQAlnKeC1aJZykq8= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIDlzCCAn+gAwIBAgIJAM624vTyzclyMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjEXMBUGA1UECgwOTmVvIFRlY2hub2xv +Z3kxFTATBgNVBAMMDGNhLm5lbzRqLm9yZzEbMBkGCSqGSIb3DQEJARYMY2FAbmVv +NGoub3JnMCAXDTEzMDUxMjExMTQ1MloYDzIxMTMwNDE4MTExNDUyWjCBlDELMAkG +A1UEBhMCU0UxETAPBgNVBAgMCE1hbG3Dg8K2MREwDwYDVQQHDAhNYWxtw4PCtjEX +MBUGA1UECgwOTmVvIFRlY2hub2xvZ3kxHzAdBgNVBAMMFmludGVybWVkaWF0ZS5u +ZW80ai5vcmcxJTAjBgkqhkiG9w0BCQEWFmludGVybWVkaWF0ZUBuZW80ai5vcmcw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdxf+GSAsl3/VyqbrTUn7I +8+gdBUvo+AS64AqvBYiEBiUGQHb8VCUqv0tnj8pCLQPfvpU9ECH7luZTCFdxcD74 +euFzRKiGL7TNMpK4SXDEhoVzTfZTXoQA1duwa0XSHzlbQix7QXa4Pg7gSaCplEMd +OBGbWBHepkRRO+A2cUykzC5zOA7VhkV3X4f0oxSqD/ycGpdf9OpHVljgMR50/uFy +k5BYQ1MFGCIvjJPvlnefBz0Q2p3ZOaNEELruEjdElfS6pURxaQ+Ntorg0hDgIt+m +c+Oh7m2lDsVZHWV2/FoZq6h3/KyrGnE9jxIHB+HOEfyF20yxoMOdSIoprx0F3YaF +AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGG4alsp +F3VEYYFJLzRCHqoC2tVAHtTHJCcL4xylx+ceovHV9FHXeyzFdWFenk+7op35HR76 +H1soJaudVjFBB1BQs7ojstca/4RLsGAMd4V1PgDiw3o0Z6XSUxNx7AcCJJpbHuu1 +wNV3nx6Zjg0/Nxw646goVj/jsrJdJ0XmcmVngWr+btOmiQNB5+0jn7SX32h5zZKi +dNFwO9B+tADc+cQP/nbQ+Mt0emgr12saVgLJiMLghe6hAuUrJCTO5C6EEsI+ytRM +BB0nLVst5UyumD6JLgTJLuTaP/TiIwhRHQAAyTuCvCWIzllAn1hiGSRw1CIqjGBT +TGlVP6mCkce4+Tw= +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEjDCCA3SgAwIBAgIJAM624vTyzclzMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJTRTERMA8GA1UECAwITWFsbcODwrYxETAPBgNVBAcMCE1hbG3Dg8K2MRcw +FQYDVQQKDA5OZW8gVGVjaG5vbG9neTEfMB0GA1UEAwwWaW50ZXJtZWRpYXRlLm5l +bzRqLm9yZzElMCMGCSqGSIb3DQEJARYWaW50ZXJtZWRpYXRlQG5lbzRqLm9yZzAg +Fw0xMzA1MTIxMTE2MzdaGA8yMTEzMDQxODExMTYzN1owgYoxCzAJBgNVBAYTAlNF +MREwDwYDVQQIDAhNYWxtw4PCtjERMA8GA1UEBwwITWFsbcODwrYxFzAVBgNVBAoM +Dk5lbyBUZWNobm9sb2d5MRowGAYDVQQDDBFjaGFpbmVkLm5lbzRqLm9yZzEgMB4G +CSqGSIb3DQEJARYRY2hhaW5lZEBuZW80ai5vcmcwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDNbdOyAuRuDo6bbCqCSbuAI3ljzMSg4n1bNlBwQrHW7VRk +GcleOI2cXACq8er7y2/RnApKaKzoqWM/ml5iP/EvvefknqTc9Gi2vxKV9CS61sMz +6T6x8ExntZQsEghMsxePJUva/SvBvhNOi/4Nis7bLDC6iw7oQ/5aSlDeQmFPUndd +X3gwxRZq3tEmAL1OuJ80tbAwKQs+TFhM3kq0J6hTPJiLPHhQyCwTZKWdcbvvPSYU +E9KVnv0x7wj1c52Sx4Mg6JtZNkvvjGrJ2X7WFnFSimQHNmyqsUH62Bb0fUA5b91s +9ly7tpNYG0X2Y18g0anQTEI5Hy0qZfpn8zTwmZJrAgMBAAGjgeYwgeMwCQYDVR0T +BAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNh +dGUwHQYDVR0OBBYEFMIntSRogchFdNnOjTFLLbg/VMgbMIGIBgNVHSMEgYAwfqFx +pG8wbTELMAkGA1UEBhMCU0UxETAPBgNVBAgMCE1hbG3Dg8K2MRcwFQYDVQQKDA5O +ZW8gVGVjaG5vbG9neTEVMBMGA1UEAwwMY2EubmVvNGoub3JnMRswGQYJKoZIhvcN +AQkBFgxjYUBuZW80ai5vcmeCCQDOtuL08s3JcjANBgkqhkiG9w0BAQUFAAOCAQEA +xWw2KNbHsvQHZ+mUhETRBJ10/lmEPjgNWaEJI4YuOioKtHy8lWEf+k33Nkt8VMVS +FVGdYPZHeZxMhKwzurh0HTwbasiScB0vsl0g5Ni8FPippF+5v+8J+jECLQnOUMVo +p9MkBuXaiM+dnTvDnv6v62cWDPGnKhcvjnpbe961UXdI69D0f0YwL3YlMsC7jpII +NA/2ZZzYeBKpykEqsPCMNZg4Sw6GXwErdG5Ewg049brP7gSWxOn1st2SfPputWNL +6z44wl2bZkrxx8ZE/CY+hVktNJLGGY/Ji6Wm+7JtlP3Aizsftu+vtS6E/3NfOx7T +/xWC592+SEV9urq1Q5Rn+Q== +-----END CERTIFICATE----- diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/cacert.pem b/manual/server-docs/src/test/resources/certificates/demoCA/cacert.pem new file mode 100644 index 0000000000000..7702e4da2aece --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/cacert.pem @@ -0,0 +1,79 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 14895342359247964528 (0xceb6e2f4f2cdc970) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, ST=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=ca.neo4j.org/emailAddress=ca@neo4j.org + Validity + Not Before: May 12 11:12:02 2013 GMT + Not After : Feb 13 11:12:02 2068 GMT + Subject: C=SE, ST=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=ca.neo4j.org/emailAddress=ca@neo4j.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b4:4d:eb:92:31:65:d9:b2:8b:08:db:ab:e5:b6: + 65:48:a8:5c:72:89:e0:bf:ff:36:ea:9c:dc:b7:d7: + 84:2f:08:28:5b:f4:76:88:db:34:51:da:95:c7:d9: + 12:df:c4:8d:54:5d:00:6f:1b:9f:73:7d:15:46:39: + 86:16:87:d5:5e:37:2b:43:dc:2f:57:a5:ca:6d:3f: + 4f:16:05:93:d5:37:46:11:0e:c1:83:bc:33:18:c8: + e3:d9:ce:0b:23:08:1f:bd:ac:50:27:7e:25:06:a4: + 54:5d:42:56:42:fc:52:8b:93:4c:60:b0:c4:58:2c: + 31:45:14:e3:e8:f0:36:42:98:f2:0d:99:76:e5:53: + b3:36:26:a8:3d:27:09:d5:b7:a4:ca:ec:d4:5d:33: + cd:d7:46:af:80:3f:77:53:e3:af:71:8e:54:40:f8: + dc:ff:49:c5:84:26:85:02:d5:5c:e0:d6:e6:b6:8d: + d1:52:b3:43:e0:10:75:45:9d:0d:4b:ce:a7:e7:ea: + 0a:ac:2b:a8:18:96:a6:b5:02:b2:97:62:40:c0:c1: + ad:7f:29:cd:90:68:c9:cb:61:ec:03:07:a1:7a:f4: + e6:9c:d5:ea:ee:99:a7:6d:a7:c3:1f:1f:34:65:89: + 23:86:30:60:98:ff:f7:32:f4:90:8d:25:af:ad:9a: + a8:23 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + D3:4A:C7:C3:47:ED:99:CD:96:DB:5E:C2:D2:21:A5:61:60:8C:7F:06 + X509v3 Authority Key Identifier: + keyid:D3:4A:C7:C3:47:ED:99:CD:96:DB:5E:C2:D2:21:A5:61:60:8C:7F:06 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 57:08:14:4a:8f:86:b1:21:55:e0:50:9c:65:17:b9:b6:d0:81: + 77:b6:8f:8e:56:96:e7:ff:9f:7c:f1:8e:3b:4a:c1:f2:40:0c: + 03:4c:bf:a8:69:8f:14:b7:38:c6:a7:a7:b6:03:6d:86:f8:ba: + 8e:7b:af:78:4e:41:db:96:28:f2:47:dc:43:85:84:7c:56:f5: + d9:09:61:b7:b6:5c:63:dc:2b:1a:c7:ac:6a:a9:0f:68:55:1f: + a6:db:22:d0:2c:db:6b:35:c8:fd:19:90:d8:13:04:b6:59:f9: + ea:04:b0:22:df:e0:a6:f8:34:d1:5c:16:63:b2:6d:6f:7b:cf: + a6:0e:9d:24:51:2e:65:fb:73:25:3d:cd:df:ca:8b:f2:9a:18: + e3:75:8a:32:02:06:a8:4d:a7:dc:35:2e:c0:7f:a7:4b:37:62: + 08:e1:ee:fd:17:61:68:19:00:04:8c:f1:19:a7:b1:26:ae:9c: + c0:e3:65:9a:f9:77:23:8e:1e:21:7d:ec:b9:7d:09:3e:cb:3d: + 44:63:72:84:6b:ff:3c:4f:56:06:cd:8d:f8:d8:2d:25:d0:f1: + 9d:9f:d5:d3:7c:fc:1a:cb:a8:6d:d5:1a:0e:c2:ec:83:cb:ba: + a7:85:4f:32:3b:ea:52:4c:17:0e:a9:40:09:67:29:e0:b5:68: + 96:72:92:af +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIJAM624vTyzclwMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjEXMBUGA1UECgwOTmVvIFRlY2hub2xv +Z3kxFTATBgNVBAMMDGNhLm5lbzRqLm9yZzEbMBkGCSqGSIb3DQEJARYMY2FAbmVv +NGoub3JnMCAXDTEzMDUxMjExMTIwMloYDzIwNjgwMjEzMTExMjAyWjBtMQswCQYD +VQQGEwJTRTERMA8GA1UECAwITWFsbcODwrYxFzAVBgNVBAoMDk5lbyBUZWNobm9s +b2d5MRUwEwYDVQQDDAxjYS5uZW80ai5vcmcxGzAZBgkqhkiG9w0BCQEWDGNhQG5l +bzRqLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALRN65IxZdmy +iwjbq+W2ZUioXHKJ4L//Nuqc3LfXhC8IKFv0dojbNFHalcfZEt/EjVRdAG8bn3N9 +FUY5hhaH1V43K0PcL1elym0/TxYFk9U3RhEOwYO8MxjI49nOCyMIH72sUCd+JQak +VF1CVkL8UouTTGCwxFgsMUUU4+jwNkKY8g2ZduVTszYmqD0nCdW3pMrs1F0zzddG +r4A/d1Pjr3GOVED43P9JxYQmhQLVXODW5raN0VKzQ+AQdUWdDUvOp+fqCqwrqBiW +prUCspdiQMDBrX8pzZBoycth7AMHoXr05pzV6u6Zp22nwx8fNGWJI4YwYJj/9zL0 +kI0lr62aqCMCAwEAAaNQME4wHQYDVR0OBBYEFNNKx8NH7ZnNlttewtIhpWFgjH8G +MB8GA1UdIwQYMBaAFNNKx8NH7ZnNlttewtIhpWFgjH8GMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAFcIFEqPhrEhVeBQnGUXubbQgXe2j45Wluf/n3zx +jjtKwfJADANMv6hpjxS3OManp7YDbYb4uo57r3hOQduWKPJH3EOFhHxW9dkJYbe2 +XGPcKxrHrGqpD2hVH6bbItAs22s1yP0ZkNgTBLZZ+eoEsCLf4Kb4NNFcFmOybW97 +z6YOnSRRLmX7cyU9zd/Ki/KaGON1ijICBqhNp9w1LsB/p0s3Ygjh7v0XYWgZAASM +8RmnsSaunMDjZZr5dyOOHiF97Ll9CT7LPURjcoRr/zxPVgbNjfjYLSXQ8Z2f1dN8 +/BrLqG3VGg7C7IPLuqeFTzI76lJMFw6pQAlnKeC1aJZykq8= +-----END CERTIFICATE----- diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/careq.pem b/manual/server-docs/src/test/resources/certificates/demoCA/careq.pem new file mode 100644 index 0000000000000..3016af0939f74 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/careq.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICxjCCAa4CAQAwgYAxCzAJBgNVBAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjER +MA8GA1UEBwwITWFsbcODwrYxFzAVBgNVBAoMDk5lbyBUZWNobm9sb2d5MRUwEwYD +VQQDDAxjYS5uZW80ai5vcmcxGzAZBgkqhkiG9w0BCQEWDGNhQG5lbzRqLm9yZzCC +ASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALRN65IxZdmyiwjbq+W2ZUio +XHKJ4L//Nuqc3LfXhC8IKFv0dojbNFHalcfZEt/EjVRdAG8bn3N9FUY5hhaH1V43 +K0PcL1elym0/TxYFk9U3RhEOwYO8MxjI49nOCyMIH72sUCd+JQakVF1CVkL8UouT +TGCwxFgsMUUU4+jwNkKY8g2ZduVTszYmqD0nCdW3pMrs1F0zzddGr4A/d1Pjr3GO +VED43P9JxYQmhQLVXODW5raN0VKzQ+AQdUWdDUvOp+fqCqwrqBiWprUCspdiQMDB +rX8pzZBoycth7AMHoXr05pzV6u6Zp22nwx8fNGWJI4YwYJj/9zL0kI0lr62aqCMC +AwEAAaAAMA0GCSqGSIb3DQEBBQUAA4IBAQCXxRChDaRBqqjMM3kfJoxh8s8jXSKU ++2bK45IdhtUMUGg4SFqgrGaiZRAwvU7AbgZUsO9FK2+vpX3sjLLaVVud42Sorghb +hbKMTSrMGC8dz/lu9Sxyd6w+gS99sp9x03S6Sk55Z/tZm7fLgvudprvVrz1e4gLq +RlqJVpf6MabyTVurrDmRxRdVR0x86+8n9ZkQcXi81nwB13IswrwJtQBGA1DlRCxh +qWUqa+KAq/fzrUQ25nhDpCT9XX8h2SiQ3pzJR3y/NN9cigscRmjTKiUqklX9XY72 +s1Su9LUaEzqSczB0Cf46x01gkJd84pYy69GrMbv0iFQZINsZuARsvCjx +-----END CERTIFICATE REQUEST----- diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/index.txt b/manual/server-docs/src/test/resources/certificates/demoCA/index.txt new file mode 100644 index 0000000000000..a70748c93df47 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/index.txt @@ -0,0 +1,4 @@ +V 20680213111202Z CEB6E2F4F2CDC970 unknown /C=SE/ST=Malm\xC3\x83\xC2\xB6/O=Neo Technology/CN=ca.neo4j.org/emailAddress=ca@neo4j.org +V 21130418111252Z CEB6E2F4F2CDC971 unknown /C=SE/ST=Malm\xC3\x83\xC2\xB6/L=Malm\xC3\x83\xC2\xB6/O=Neo Technology/CN=direct.neo4j.org/emailAddress=direct@neo4j.org +V 21130418111452Z CEB6E2F4F2CDC972 unknown /C=SE/ST=Malm\xC3\x83\xC2\xB6/L=Malm\xC3\x83\xC2\xB6/O=Neo Technology/CN=intermediate.neo4j.org/emailAddress=intermediate@neo4j.org +V 21130418111637Z CEB6E2F4F2CDC973 unknown /C=SE/ST=Malm\xC3\x83\xC2\xB6/L=Malm\xC3\x83\xC2\xB6/O=Neo Technology/CN=chained.neo4j.org/emailAddress=chained@neo4j.org diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/index.txt.attr b/manual/server-docs/src/test/resources/certificates/demoCA/index.txt.attr new file mode 100644 index 0000000000000..8f7e63a3475ce --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/index.txt.attr @@ -0,0 +1 @@ +unique_subject = yes diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/index.txt.attr.old b/manual/server-docs/src/test/resources/certificates/demoCA/index.txt.attr.old new file mode 100644 index 0000000000000..8f7e63a3475ce --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/index.txt.attr.old @@ -0,0 +1 @@ +unique_subject = yes diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/index.txt.old b/manual/server-docs/src/test/resources/certificates/demoCA/index.txt.old new file mode 100644 index 0000000000000..b075ed6604776 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/index.txt.old @@ -0,0 +1,3 @@ +V 20680213111202Z CEB6E2F4F2CDC970 unknown /C=SE/ST=Malm\xC3\x83\xC2\xB6/O=Neo Technology/CN=ca.neo4j.org/emailAddress=ca@neo4j.org +V 21130418111252Z CEB6E2F4F2CDC971 unknown /C=SE/ST=Malm\xC3\x83\xC2\xB6/L=Malm\xC3\x83\xC2\xB6/O=Neo Technology/CN=direct.neo4j.org/emailAddress=direct@neo4j.org +V 21130418111452Z CEB6E2F4F2CDC972 unknown /C=SE/ST=Malm\xC3\x83\xC2\xB6/L=Malm\xC3\x83\xC2\xB6/O=Neo Technology/CN=intermediate.neo4j.org/emailAddress=intermediate@neo4j.org diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC970.pem b/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC970.pem new file mode 100644 index 0000000000000..7702e4da2aece --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC970.pem @@ -0,0 +1,79 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 14895342359247964528 (0xceb6e2f4f2cdc970) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, ST=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=ca.neo4j.org/emailAddress=ca@neo4j.org + Validity + Not Before: May 12 11:12:02 2013 GMT + Not After : Feb 13 11:12:02 2068 GMT + Subject: C=SE, ST=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=ca.neo4j.org/emailAddress=ca@neo4j.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:b4:4d:eb:92:31:65:d9:b2:8b:08:db:ab:e5:b6: + 65:48:a8:5c:72:89:e0:bf:ff:36:ea:9c:dc:b7:d7: + 84:2f:08:28:5b:f4:76:88:db:34:51:da:95:c7:d9: + 12:df:c4:8d:54:5d:00:6f:1b:9f:73:7d:15:46:39: + 86:16:87:d5:5e:37:2b:43:dc:2f:57:a5:ca:6d:3f: + 4f:16:05:93:d5:37:46:11:0e:c1:83:bc:33:18:c8: + e3:d9:ce:0b:23:08:1f:bd:ac:50:27:7e:25:06:a4: + 54:5d:42:56:42:fc:52:8b:93:4c:60:b0:c4:58:2c: + 31:45:14:e3:e8:f0:36:42:98:f2:0d:99:76:e5:53: + b3:36:26:a8:3d:27:09:d5:b7:a4:ca:ec:d4:5d:33: + cd:d7:46:af:80:3f:77:53:e3:af:71:8e:54:40:f8: + dc:ff:49:c5:84:26:85:02:d5:5c:e0:d6:e6:b6:8d: + d1:52:b3:43:e0:10:75:45:9d:0d:4b:ce:a7:e7:ea: + 0a:ac:2b:a8:18:96:a6:b5:02:b2:97:62:40:c0:c1: + ad:7f:29:cd:90:68:c9:cb:61:ec:03:07:a1:7a:f4: + e6:9c:d5:ea:ee:99:a7:6d:a7:c3:1f:1f:34:65:89: + 23:86:30:60:98:ff:f7:32:f4:90:8d:25:af:ad:9a: + a8:23 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + D3:4A:C7:C3:47:ED:99:CD:96:DB:5E:C2:D2:21:A5:61:60:8C:7F:06 + X509v3 Authority Key Identifier: + keyid:D3:4A:C7:C3:47:ED:99:CD:96:DB:5E:C2:D2:21:A5:61:60:8C:7F:06 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 57:08:14:4a:8f:86:b1:21:55:e0:50:9c:65:17:b9:b6:d0:81: + 77:b6:8f:8e:56:96:e7:ff:9f:7c:f1:8e:3b:4a:c1:f2:40:0c: + 03:4c:bf:a8:69:8f:14:b7:38:c6:a7:a7:b6:03:6d:86:f8:ba: + 8e:7b:af:78:4e:41:db:96:28:f2:47:dc:43:85:84:7c:56:f5: + d9:09:61:b7:b6:5c:63:dc:2b:1a:c7:ac:6a:a9:0f:68:55:1f: + a6:db:22:d0:2c:db:6b:35:c8:fd:19:90:d8:13:04:b6:59:f9: + ea:04:b0:22:df:e0:a6:f8:34:d1:5c:16:63:b2:6d:6f:7b:cf: + a6:0e:9d:24:51:2e:65:fb:73:25:3d:cd:df:ca:8b:f2:9a:18: + e3:75:8a:32:02:06:a8:4d:a7:dc:35:2e:c0:7f:a7:4b:37:62: + 08:e1:ee:fd:17:61:68:19:00:04:8c:f1:19:a7:b1:26:ae:9c: + c0:e3:65:9a:f9:77:23:8e:1e:21:7d:ec:b9:7d:09:3e:cb:3d: + 44:63:72:84:6b:ff:3c:4f:56:06:cd:8d:f8:d8:2d:25:d0:f1: + 9d:9f:d5:d3:7c:fc:1a:cb:a8:6d:d5:1a:0e:c2:ec:83:cb:ba: + a7:85:4f:32:3b:ea:52:4c:17:0e:a9:40:09:67:29:e0:b5:68: + 96:72:92:af +-----BEGIN CERTIFICATE----- +MIIDrzCCApegAwIBAgIJAM624vTyzclwMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjEXMBUGA1UECgwOTmVvIFRlY2hub2xv +Z3kxFTATBgNVBAMMDGNhLm5lbzRqLm9yZzEbMBkGCSqGSIb3DQEJARYMY2FAbmVv +NGoub3JnMCAXDTEzMDUxMjExMTIwMloYDzIwNjgwMjEzMTExMjAyWjBtMQswCQYD +VQQGEwJTRTERMA8GA1UECAwITWFsbcODwrYxFzAVBgNVBAoMDk5lbyBUZWNobm9s +b2d5MRUwEwYDVQQDDAxjYS5uZW80ai5vcmcxGzAZBgkqhkiG9w0BCQEWDGNhQG5l +bzRqLm9yZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALRN65IxZdmy +iwjbq+W2ZUioXHKJ4L//Nuqc3LfXhC8IKFv0dojbNFHalcfZEt/EjVRdAG8bn3N9 +FUY5hhaH1V43K0PcL1elym0/TxYFk9U3RhEOwYO8MxjI49nOCyMIH72sUCd+JQak +VF1CVkL8UouTTGCwxFgsMUUU4+jwNkKY8g2ZduVTszYmqD0nCdW3pMrs1F0zzddG +r4A/d1Pjr3GOVED43P9JxYQmhQLVXODW5raN0VKzQ+AQdUWdDUvOp+fqCqwrqBiW +prUCspdiQMDBrX8pzZBoycth7AMHoXr05pzV6u6Zp22nwx8fNGWJI4YwYJj/9zL0 +kI0lr62aqCMCAwEAAaNQME4wHQYDVR0OBBYEFNNKx8NH7ZnNlttewtIhpWFgjH8G +MB8GA1UdIwQYMBaAFNNKx8NH7ZnNlttewtIhpWFgjH8GMAwGA1UdEwQFMAMBAf8w +DQYJKoZIhvcNAQEFBQADggEBAFcIFEqPhrEhVeBQnGUXubbQgXe2j45Wluf/n3zx +jjtKwfJADANMv6hpjxS3OManp7YDbYb4uo57r3hOQduWKPJH3EOFhHxW9dkJYbe2 +XGPcKxrHrGqpD2hVH6bbItAs22s1yP0ZkNgTBLZZ+eoEsCLf4Kb4NNFcFmOybW97 +z6YOnSRRLmX7cyU9zd/Ki/KaGON1ijICBqhNp9w1LsB/p0s3Ygjh7v0XYWgZAASM +8RmnsSaunMDjZZr5dyOOHiF97Ll9CT7LPURjcoRr/zxPVgbNjfjYLSXQ8Z2f1dN8 +/BrLqG3VGg7C7IPLuqeFTzI76lJMFw6pQAlnKeC1aJZykq8= +-----END CERTIFICATE----- diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC971.pem b/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC971.pem new file mode 100644 index 0000000000000..1ba1946ecf185 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC971.pem @@ -0,0 +1,83 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 14895342359247964529 (0xceb6e2f4f2cdc971) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, ST=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=ca.neo4j.org/emailAddress=ca@neo4j.org + Validity + Not Before: May 12 11:12:52 2013 GMT + Not After : Apr 18 11:12:52 2113 GMT + Subject: C=SE, ST=Malm\xC3\x83\xC2\xB6, L=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=direct.neo4j.org/emailAddress=direct@neo4j.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:d9:de:2e:fe:2e:28:f5:ba:f8:a0:93:1d:e1:98: + bc:5c:5f:9c:23:22:b7:4b:e4:94:82:18:38:6c:27: + 9b:9c:5d:5a:59:b3:7a:be:d5:e8:8f:ef:b2:f6:41: + d4:73:73:a5:79:f3:bc:67:be:d0:fc:6d:b0:dc:b8: + 35:c7:3e:a3:bd:36:f1:47:15:4d:d7:0f:ab:95:be: + ff:60:03:a1:00:51:dd:7c:a9:bd:59:82:eb:f8:51: + a6:b9:8c:34:26:eb:03:66:76:61:aa:e5:ba:b9:6b: + ed:7f:c5:f3:9a:c2:39:14:3e:a2:f2:32:63:81:4d: + 42:c1:b7:89:17:d3:4c:12:0c:61:8d:f8:9e:18:e3: + 91:75:70:05:30:e6:fc:3b:0c:eb:fb:4b:e9:9f:d1: + 1e:51:e6:3f:37:36:96:ad:4a:7c:b0:f7:58:fe:3c: + 3b:e7:99:26:fb:0c:76:35:64:c9:cd:11:8d:70:91: + 4c:1c:b2:2f:21:fd:d7:22:c0:27:0c:b6:d7:7f:11: + 60:8d:e5:34:4e:d4:f7:cf:30:5a:9e:6d:16:62:4d: + 8a:4e:2c:01:05:73:1e:3f:ea:d8:5e:17:d6:61:8f: + 34:24:60:d9:7c:57:c9:90:9c:65:4e:3a:76:fa:41: + 30:26:9b:da:7b:cb:5f:bf:fc:9c:b0:17:31:3f:4f: + 60:c9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + DD:62:16:D2:AD:86:55:D0:EC:F7:5F:81:55:C1:12:50:4E:E5:3E:04 + X509v3 Authority Key Identifier: + keyid:D3:4A:C7:C3:47:ED:99:CD:96:DB:5E:C2:D2:21:A5:61:60:8C:7F:06 + + Signature Algorithm: sha1WithRSAEncryption + af:a1:2d:c7:38:73:5f:96:2f:5b:c0:66:5f:ed:b5:d6:d5:29: + 6d:ed:8a:d8:32:62:3a:53:39:7b:a1:37:15:68:52:fa:b5:d7: + 29:82:5d:fe:62:c9:07:d9:0a:8a:11:9c:93:83:9d:a2:02:59: + 1b:0c:d7:b6:b4:51:17:ef:7d:a6:90:37:85:64:39:02:67:9e: + 6f:e7:1e:97:52:33:8a:05:3a:75:4a:f3:97:76:43:8c:72:0c: + 8a:2a:93:44:b8:80:39:7d:be:26:0d:cf:f8:db:5d:02:2c:01: + 77:4b:d5:ae:8e:1c:fa:89:9d:63:cf:1e:5c:39:06:99:a7:d1: + bc:c3:ec:1a:54:29:8b:18:0f:88:05:37:6d:68:14:22:5a:d7: + 84:08:d7:0c:28:8f:e2:fc:cd:3d:37:7d:11:10:4d:8e:b0:83: + 95:41:bf:ff:48:20:f5:17:b3:ed:f7:b8:75:d8:30:93:03:1f: + 3c:cd:da:ab:dd:fb:06:42:d3:cb:cd:68:32:cf:b3:a3:70:db: + b3:16:f3:ac:8d:ef:a6:20:58:cb:1e:d0:20:e8:bc:85:c9:71: + 27:60:05:2e:e1:87:8e:09:85:78:09:84:cd:7f:b4:bd:db:9a: + 5e:17:6b:66:35:32:fd:a0:cc:7b:12:e3:3f:bf:3a:15:e4:17: + 34:de:35:85 +-----BEGIN CERTIFICATE----- +MIID9jCCAt6gAwIBAgIJAM624vTyzclxMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjEXMBUGA1UECgwOTmVvIFRlY2hub2xv +Z3kxFTATBgNVBAMMDGNhLm5lbzRqLm9yZzEbMBkGCSqGSIb3DQEJARYMY2FAbmVv +NGoub3JnMCAXDTEzMDUxMjExMTI1MloYDzIxMTMwNDE4MTExMjUyWjCBiDELMAkG +A1UEBhMCU0UxETAPBgNVBAgMCE1hbG3Dg8K2MREwDwYDVQQHDAhNYWxtw4PCtjEX +MBUGA1UECgwOTmVvIFRlY2hub2xvZ3kxGTAXBgNVBAMMEGRpcmVjdC5uZW80ai5v +cmcxHzAdBgkqhkiG9w0BCQEWEGRpcmVjdEBuZW80ai5vcmcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDZ3i7+Lij1uvigkx3hmLxcX5wjIrdL5JSCGDhs +J5ucXVpZs3q+1eiP77L2QdRzc6V587xnvtD8bbDcuDXHPqO9NvFHFU3XD6uVvv9g +A6EAUd18qb1Zguv4Uaa5jDQm6wNmdmGq5bq5a+1/xfOawjkUPqLyMmOBTULBt4kX +00wSDGGN+J4Y45F1cAUw5vw7DOv7S+mf0R5R5j83NpatSnyw91j+PDvnmSb7DHY1 +ZMnNEY1wkUwcsi8h/dciwCcMttd/EWCN5TRO1PfPMFqebRZiTYpOLAEFcx4/6the +F9ZhjzQkYNl8V8mQnGVOOnb6QTAmm9p7y1+//JywFzE/T2DJAgMBAAGjezB5MAkG +A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp +ZmljYXRlMB0GA1UdDgQWBBTdYhbSrYZV0Oz3X4FVwRJQTuU+BDAfBgNVHSMEGDAW +gBTTSsfDR+2ZzZbbXsLSIaVhYIx/BjANBgkqhkiG9w0BAQUFAAOCAQEAr6Etxzhz +X5YvW8BmX+211tUpbe2K2DJiOlM5e6E3FWhS+rXXKYJd/mLJB9kKihGck4OdogJZ +GwzXtrRRF+99ppA3hWQ5Ameeb+cel1IzigU6dUrzl3ZDjHIMiiqTRLiAOX2+Jg3P ++NtdAiwBd0vVro4c+omdY88eXDkGmafRvMPsGlQpixgPiAU3bWgUIlrXhAjXDCiP +4vzNPTd9ERBNjrCDlUG//0gg9Rez7fe4ddgwkwMfPM3aq937BkLTy81oMs+zo3Db +sxbzrI3vpiBYyx7QIOi8hclxJ2AFLuGHjgmFeAmEzX+0vduaXhdrZjUy/aDMexLj +P786FeQXNN41hQ== +-----END CERTIFICATE----- diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC972.pem b/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC972.pem new file mode 100644 index 0000000000000..4f7cacb5fc130 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC972.pem @@ -0,0 +1,74 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 14895342359247964530 (0xceb6e2f4f2cdc972) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, ST=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=ca.neo4j.org/emailAddress=ca@neo4j.org + Validity + Not Before: May 12 11:14:52 2013 GMT + Not After : Apr 18 11:14:52 2113 GMT + Subject: C=SE, ST=Malm\xC3\x83\xC2\xB6, L=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=intermediate.neo4j.org/emailAddress=intermediate@neo4j.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:dd:c5:ff:86:48:0b:25:df:f5:72:a9:ba:d3:52: + 7e:c8:f3:e8:1d:05:4b:e8:f8:04:ba:e0:0a:af:05: + 88:84:06:25:06:40:76:fc:54:25:2a:bf:4b:67:8f: + ca:42:2d:03:df:be:95:3d:10:21:fb:96:e6:53:08: + 57:71:70:3e:f8:7a:e1:73:44:a8:86:2f:b4:cd:32: + 92:b8:49:70:c4:86:85:73:4d:f6:53:5e:84:00:d5: + db:b0:6b:45:d2:1f:39:5b:42:2c:7b:41:76:b8:3e: + 0e:e0:49:a0:a9:94:43:1d:38:11:9b:58:11:de:a6: + 44:51:3b:e0:36:71:4c:a4:cc:2e:73:38:0e:d5:86: + 45:77:5f:87:f4:a3:14:aa:0f:fc:9c:1a:97:5f:f4: + ea:47:56:58:e0:31:1e:74:fe:e1:72:93:90:58:43: + 53:05:18:22:2f:8c:93:ef:96:77:9f:07:3d:10:da: + 9d:d9:39:a3:44:10:ba:ee:12:37:44:95:f4:ba:a5: + 44:71:69:0f:8d:b6:8a:e0:d2:10:e0:22:df:a6:73: + e3:a1:ee:6d:a5:0e:c5:59:1d:65:76:fc:5a:19:ab: + a8:77:fc:ac:ab:1a:71:3d:8f:12:07:07:e1:ce:11: + fc:85:db:4c:b1:a0:c3:9d:48:8a:29:af:1d:05:dd: + 86:85 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 61:b8:6a:5b:29:17:75:44:61:81:49:2f:34:42:1e:aa:02:da: + d5:40:1e:d4:c7:24:27:0b:e3:1c:a5:c7:e7:1e:a2:f1:d5:f4: + 51:d7:7b:2c:c5:75:61:5e:9e:4f:bb:a2:9d:f9:1d:1e:fa:1f: + 5b:28:25:ab:9d:56:31:41:07:50:50:b3:ba:23:b2:d7:1a:ff: + 84:4b:b0:60:0c:77:85:75:3e:00:e2:c3:7a:34:67:a5:d2:53: + 13:71:ec:07:02:24:9a:5b:1e:eb:b5:c0:d5:77:9f:1e:99:8e: + 0d:3f:37:1c:3a:e3:a8:28:56:3f:e3:b2:b2:5d:27:45:e6:72: + 65:67:81:6a:fe:6e:d3:a6:89:03:41:e7:ed:23:9f:b4:97:df: + 68:79:cd:92:a2:74:d1:70:3b:d0:7e:b4:00:dc:f9:c4:0f:fe: + 76:d0:f8:cb:74:7a:68:2b:d7:6b:1a:56:02:c9:88:c2:e0:85: + ee:a1:02:e5:2b:24:24:ce:e4:2e:84:12:c2:3e:ca:d4:4c:04: + 1d:27:2d:5b:2d:e5:4c:ae:98:3e:89:2e:04:c9:2e:e4:da:3f: + f4:e2:23:08:51:1d:00:00:c9:3b:82:bc:25:88:ce:59:40:9f: + 58:62:19:24:70:d4:22:2a:8c:60:53:4c:69:55:3f:a9:82:91: + c7:b8:f9:3c +-----BEGIN CERTIFICATE----- +MIIDlzCCAn+gAwIBAgIJAM624vTyzclyMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjEXMBUGA1UECgwOTmVvIFRlY2hub2xv +Z3kxFTATBgNVBAMMDGNhLm5lbzRqLm9yZzEbMBkGCSqGSIb3DQEJARYMY2FAbmVv +NGoub3JnMCAXDTEzMDUxMjExMTQ1MloYDzIxMTMwNDE4MTExNDUyWjCBlDELMAkG +A1UEBhMCU0UxETAPBgNVBAgMCE1hbG3Dg8K2MREwDwYDVQQHDAhNYWxtw4PCtjEX +MBUGA1UECgwOTmVvIFRlY2hub2xvZ3kxHzAdBgNVBAMMFmludGVybWVkaWF0ZS5u +ZW80ai5vcmcxJTAjBgkqhkiG9w0BCQEWFmludGVybWVkaWF0ZUBuZW80ai5vcmcw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdxf+GSAsl3/VyqbrTUn7I +8+gdBUvo+AS64AqvBYiEBiUGQHb8VCUqv0tnj8pCLQPfvpU9ECH7luZTCFdxcD74 +euFzRKiGL7TNMpK4SXDEhoVzTfZTXoQA1duwa0XSHzlbQix7QXa4Pg7gSaCplEMd +OBGbWBHepkRRO+A2cUykzC5zOA7VhkV3X4f0oxSqD/ycGpdf9OpHVljgMR50/uFy +k5BYQ1MFGCIvjJPvlnefBz0Q2p3ZOaNEELruEjdElfS6pURxaQ+Ntorg0hDgIt+m +c+Oh7m2lDsVZHWV2/FoZq6h3/KyrGnE9jxIHB+HOEfyF20yxoMOdSIoprx0F3YaF +AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGG4alsp +F3VEYYFJLzRCHqoC2tVAHtTHJCcL4xylx+ceovHV9FHXeyzFdWFenk+7op35HR76 +H1soJaudVjFBB1BQs7ojstca/4RLsGAMd4V1PgDiw3o0Z6XSUxNx7AcCJJpbHuu1 +wNV3nx6Zjg0/Nxw646goVj/jsrJdJ0XmcmVngWr+btOmiQNB5+0jn7SX32h5zZKi +dNFwO9B+tADc+cQP/nbQ+Mt0emgr12saVgLJiMLghe6hAuUrJCTO5C6EEsI+ytRM +BB0nLVst5UyumD6JLgTJLuTaP/TiIwhRHQAAyTuCvCWIzllAn1hiGSRw1CIqjGBT +TGlVP6mCkce4+Tw= +-----END CERTIFICATE----- diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC973.pem b/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC973.pem new file mode 100644 index 0000000000000..2e8e8a8ae5915 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/newcerts/CEB6E2F4F2CDC973.pem @@ -0,0 +1,87 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 14895342359247964531 (0xceb6e2f4f2cdc973) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, ST=Malm\xC3\x83\xC2\xB6, L=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=intermediate.neo4j.org/emailAddress=intermediate@neo4j.org + Validity + Not Before: May 12 11:16:37 2013 GMT + Not After : Apr 18 11:16:37 2113 GMT + Subject: C=SE, ST=Malm\xC3\x83\xC2\xB6, L=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=chained.neo4j.org/emailAddress=chained@neo4j.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:cd:6d:d3:b2:02:e4:6e:0e:8e:9b:6c:2a:82:49: + bb:80:23:79:63:cc:c4:a0:e2:7d:5b:36:50:70:42: + b1:d6:ed:54:64:19:c9:5e:38:8d:9c:5c:00:aa:f1: + ea:fb:cb:6f:d1:9c:0a:4a:68:ac:e8:a9:63:3f:9a: + 5e:62:3f:f1:2f:bd:e7:e4:9e:a4:dc:f4:68:b6:bf: + 12:95:f4:24:ba:d6:c3:33:e9:3e:b1:f0:4c:67:b5: + 94:2c:12:08:4c:b3:17:8f:25:4b:da:fd:2b:c1:be: + 13:4e:8b:fe:0d:8a:ce:db:2c:30:ba:8b:0e:e8:43: + fe:5a:4a:50:de:42:61:4f:52:77:5d:5f:78:30:c5: + 16:6a:de:d1:26:00:bd:4e:b8:9f:34:b5:b0:30:29: + 0b:3e:4c:58:4c:de:4a:b4:27:a8:53:3c:98:8b:3c: + 78:50:c8:2c:13:64:a5:9d:71:bb:ef:3d:26:14:13: + d2:95:9e:fd:31:ef:08:f5:73:9d:92:c7:83:20:e8: + 9b:59:36:4b:ef:8c:6a:c9:d9:7e:d6:16:71:52:8a: + 64:07:36:6c:aa:b1:41:fa:d8:16:f4:7d:40:39:6f: + dd:6c:f6:5c:bb:b6:93:58:1b:45:f6:63:5f:20:d1: + a9:d0:4c:42:39:1f:2d:2a:65:fa:67:f3:34:f0:99: + 92:6b + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + C2:27:B5:24:68:81:C8:45:74:D9:CE:8D:31:4B:2D:B8:3F:54:C8:1B + X509v3 Authority Key Identifier: + DirName:/C=SE/ST=Malm\xC3\x83\xC2\xB6/O=Neo Technology/CN=ca.neo4j.org/emailAddress=ca@neo4j.org + serial:CE:B6:E2:F4:F2:CD:C9:72 + + Signature Algorithm: sha1WithRSAEncryption + c5:6c:36:28:d6:c7:b2:f4:07:67:e9:94:84:44:d1:04:9d:74: + fe:59:84:3e:38:0d:59:a1:09:23:86:2e:3a:2a:0a:b4:7c:bc: + 95:61:1f:fa:4d:f7:36:4b:7c:54:c5:52:15:51:9d:60:f6:47: + 79:9c:4c:84:ac:33:ba:b8:74:1d:3c:1b:6a:c8:92:70:1d:2f: + b2:5d:20:e4:d8:bc:14:f8:a9:a4:5f:b9:bf:ef:09:fa:31:02: + 2d:09:ce:50:c5:68:a7:d3:24:06:e5:da:88:cf:9d:9d:3b:c3: + 9e:fe:af:eb:67:16:0c:f1:a7:2a:17:2f:8e:7a:5b:7b:de:b5: + 51:77:48:eb:d0:f4:7f:46:30:2f:76:25:32:c0:bb:8e:92:08: + 34:0f:f6:65:9c:d8:78:12:a9:ca:41:2a:b0:f0:8c:35:98:38: + 4b:0e:86:5f:01:2b:74:6e:44:c2:0d:38:f5:ba:cf:ee:04:96: + c4:e9:f5:b2:dd:92:7c:fa:6e:b5:63:4b:eb:3e:38:c2:5d:9b: + 66:4a:f1:c7:c6:44:fc:26:3e:85:59:2d:34:92:c6:19:8f:c9: + 8b:a5:a6:fb:b2:6d:94:fd:c0:8b:3b:1f:b6:ef:af:b5:2e:84: + ff:73:5f:3b:1e:d3:ff:15:82:e7:dd:be:48:45:7d:ba:ba:b5: + 43:94:67:f9 +-----BEGIN CERTIFICATE----- +MIIEjDCCA3SgAwIBAgIJAM624vTyzclzMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD +VQQGEwJTRTERMA8GA1UECAwITWFsbcODwrYxETAPBgNVBAcMCE1hbG3Dg8K2MRcw +FQYDVQQKDA5OZW8gVGVjaG5vbG9neTEfMB0GA1UEAwwWaW50ZXJtZWRpYXRlLm5l +bzRqLm9yZzElMCMGCSqGSIb3DQEJARYWaW50ZXJtZWRpYXRlQG5lbzRqLm9yZzAg +Fw0xMzA1MTIxMTE2MzdaGA8yMTEzMDQxODExMTYzN1owgYoxCzAJBgNVBAYTAlNF +MREwDwYDVQQIDAhNYWxtw4PCtjERMA8GA1UEBwwITWFsbcODwrYxFzAVBgNVBAoM +Dk5lbyBUZWNobm9sb2d5MRowGAYDVQQDDBFjaGFpbmVkLm5lbzRqLm9yZzEgMB4G +CSqGSIb3DQEJARYRY2hhaW5lZEBuZW80ai5vcmcwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQDNbdOyAuRuDo6bbCqCSbuAI3ljzMSg4n1bNlBwQrHW7VRk +GcleOI2cXACq8er7y2/RnApKaKzoqWM/ml5iP/EvvefknqTc9Gi2vxKV9CS61sMz +6T6x8ExntZQsEghMsxePJUva/SvBvhNOi/4Nis7bLDC6iw7oQ/5aSlDeQmFPUndd +X3gwxRZq3tEmAL1OuJ80tbAwKQs+TFhM3kq0J6hTPJiLPHhQyCwTZKWdcbvvPSYU +E9KVnv0x7wj1c52Sx4Mg6JtZNkvvjGrJ2X7WFnFSimQHNmyqsUH62Bb0fUA5b91s +9ly7tpNYG0X2Y18g0anQTEI5Hy0qZfpn8zTwmZJrAgMBAAGjgeYwgeMwCQYDVR0T +BAIwADAsBglghkgBhvhCAQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNh +dGUwHQYDVR0OBBYEFMIntSRogchFdNnOjTFLLbg/VMgbMIGIBgNVHSMEgYAwfqFx +pG8wbTELMAkGA1UEBhMCU0UxETAPBgNVBAgMCE1hbG3Dg8K2MRcwFQYDVQQKDA5O +ZW8gVGVjaG5vbG9neTEVMBMGA1UEAwwMY2EubmVvNGoub3JnMRswGQYJKoZIhvcN +AQkBFgxjYUBuZW80ai5vcmeCCQDOtuL08s3JcjANBgkqhkiG9w0BAQUFAAOCAQEA +xWw2KNbHsvQHZ+mUhETRBJ10/lmEPjgNWaEJI4YuOioKtHy8lWEf+k33Nkt8VMVS +FVGdYPZHeZxMhKwzurh0HTwbasiScB0vsl0g5Ni8FPippF+5v+8J+jECLQnOUMVo +p9MkBuXaiM+dnTvDnv6v62cWDPGnKhcvjnpbe961UXdI69D0f0YwL3YlMsC7jpII +NA/2ZZzYeBKpykEqsPCMNZg4Sw6GXwErdG5Ewg049brP7gSWxOn1st2SfPputWNL +6z44wl2bZkrxx8ZE/CY+hVktNJLGGY/Ji6Wm+7JtlP3Aizsftu+vtS6E/3NfOx7T +/xWC592+SEV9urq1Q5Rn+Q== +-----END CERTIFICATE----- diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/private/cakey.pem b/manual/server-docs/src/test/resources/certificates/demoCA/private/cakey.pem new file mode 100644 index 0000000000000..ddefa20cdd3bf --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/private/cakey.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEwAIBADANBgkqhkiG9w0BAQEFAASCBKowggSmAgEAAoIBAQC0TeuSMWXZsosI +26vltmVIqFxyieC//zbqnNy314QvCChb9HaI2zRR2pXH2RLfxI1UXQBvG59zfRVG +OYYWh9VeNytD3C9XpcptP08WBZPVN0YRDsGDvDMYyOPZzgsjCB+9rFAnfiUGpFRd +QlZC/FKLk0xgsMRYLDFFFOPo8DZCmPINmXblU7M2Jqg9JwnVt6TK7NRdM83XRq+A +P3dT469xjlRA+Nz/ScWEJoUC1Vzg1ua2jdFSs0PgEHVFnQ1Lzqfn6gqsK6gYlqa1 +ArKXYkDAwa1/Kc2QaMnLYewDB6F69Oac1erumadtp8MfHzRliSOGMGCY//cy9JCN +Ja+tmqgjAgMBAAECggEBAIWk8ASXqtfXGGlDckG9lYtqh8O6YSXPBSSXip6WF3tI +/6lRJX9Bg6FcdQMB6pSEOwGaQdUZU3Il99vqjXIr0sKX//C3xzufPtEFSRiYn8A9 +zSidmGCN4g3v8builuyre+N6UptDTCyFJ6dMoWXbY0/jqYxN2etFLmv3FQu26XEo +b/iP+7rB5PtHLNrF8EveWenJo7n9eMHj6RLwtwbJEr0FvKZ1HvP8xAw7XLvOfeCt +F/fZMWxqKPJsAnJQiv26ehIfwpy6O8gQogfoJmdMhfgDnjC73wTcQGl8LOmhnJQM +uDPQUoLNjri4/G2Q7kBkVY7bSuUBT7fB/fr+RojGVhECgYEA4avQfoCOTIK9LMvH +TiNxxmVvVAFiyCaoBW6qfEC2JAemjEJa+ORosdnAimfsq6yjywNdusc2JoY6iByN +TUTpxEFXbUnQv+hLLW26V8UVJWvvcwzfjNDGMjTecjZJ1nEfAHfHjYEKGN3gcI2V +hwDmgo8HtKtzjh/fkB6oqwQm5ysCgYEAzIlEm5s9BUjF6WgYOBnC7o8KiLP3albl +EPSSYgFu7+KreoD0iF1TG8ljk3Rv17SdDXSbHrh/IDmq+2SWBxYBhar6tCB7KsoE +n6LEwvr4VRfBO0WgacbdNsj1xOM0Xdyo4E7disfh9fpzK68EJb31WhqwnLTmfIlG +A4/iBRq1xukCgYEA2Gzys5+O2Qm5a7CzH9kWUHMPZb0+gz2vfiY4EpntFHjR3Un1 +/STw00BGS3aHn80d+KDtzycmY6PvloHrceNZXgJcWk+kh6e8EYFeaLZ2JUFyjyAV +dPL0YUoa2+X8wlASr1wwYSEUEe0YRllCwgq3BxctCLQ1SmR+/7SnhfuhY6kCgYEA +q8KeLboofRaGtKIorHvdua2/X9sZa2B7TkjD4K9pdOM5s081ioosb4ooJ7zA6ziO +Jq2EUkEG6I3k6uWmI180egaCL9+S7vTk+xWZ/E77WaHwMBEhS5KnVAlgci2GXUgf +PPadjpyaQZFHRCjd57/kmP0O0RoM8AnrFLSwTi8G9XkCgYEAymlbqtSXq1m1bmkK +AF37zblq7u7VKjCgV6RaDLJwYtqlYzR+kMZtks4Nl4u9vayMDX6SKrQL+nafkQcA +pGhKxd88L1CJ/Uen28wvWZla8AMQmqALbGz6ENUk7w9N9e6omV2E22j21XUZgP5u +WjVxF4I94Smxb7xxfTncKhqsRI0= +-----END PRIVATE KEY----- diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/serial b/manual/server-docs/src/test/resources/certificates/demoCA/serial new file mode 100644 index 0000000000000..69be238d9fa6e --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/serial @@ -0,0 +1 @@ +CEB6E2F4F2CDC974 diff --git a/manual/server-docs/src/test/resources/certificates/demoCA/serial.old b/manual/server-docs/src/test/resources/certificates/demoCA/serial.old new file mode 100644 index 0000000000000..b3a9c3a2d2d0a --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/demoCA/serial.old @@ -0,0 +1 @@ +CEB6E2F4F2CDC973 diff --git a/manual/server-docs/src/test/resources/certificates/direct_cert.pem b/manual/server-docs/src/test/resources/certificates/direct_cert.pem new file mode 100644 index 0000000000000..1ba1946ecf185 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/direct_cert.pem @@ -0,0 +1,83 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 14895342359247964529 (0xceb6e2f4f2cdc971) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, ST=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=ca.neo4j.org/emailAddress=ca@neo4j.org + Validity + Not Before: May 12 11:12:52 2013 GMT + Not After : Apr 18 11:12:52 2113 GMT + Subject: C=SE, ST=Malm\xC3\x83\xC2\xB6, L=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=direct.neo4j.org/emailAddress=direct@neo4j.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:d9:de:2e:fe:2e:28:f5:ba:f8:a0:93:1d:e1:98: + bc:5c:5f:9c:23:22:b7:4b:e4:94:82:18:38:6c:27: + 9b:9c:5d:5a:59:b3:7a:be:d5:e8:8f:ef:b2:f6:41: + d4:73:73:a5:79:f3:bc:67:be:d0:fc:6d:b0:dc:b8: + 35:c7:3e:a3:bd:36:f1:47:15:4d:d7:0f:ab:95:be: + ff:60:03:a1:00:51:dd:7c:a9:bd:59:82:eb:f8:51: + a6:b9:8c:34:26:eb:03:66:76:61:aa:e5:ba:b9:6b: + ed:7f:c5:f3:9a:c2:39:14:3e:a2:f2:32:63:81:4d: + 42:c1:b7:89:17:d3:4c:12:0c:61:8d:f8:9e:18:e3: + 91:75:70:05:30:e6:fc:3b:0c:eb:fb:4b:e9:9f:d1: + 1e:51:e6:3f:37:36:96:ad:4a:7c:b0:f7:58:fe:3c: + 3b:e7:99:26:fb:0c:76:35:64:c9:cd:11:8d:70:91: + 4c:1c:b2:2f:21:fd:d7:22:c0:27:0c:b6:d7:7f:11: + 60:8d:e5:34:4e:d4:f7:cf:30:5a:9e:6d:16:62:4d: + 8a:4e:2c:01:05:73:1e:3f:ea:d8:5e:17:d6:61:8f: + 34:24:60:d9:7c:57:c9:90:9c:65:4e:3a:76:fa:41: + 30:26:9b:da:7b:cb:5f:bf:fc:9c:b0:17:31:3f:4f: + 60:c9 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:FALSE + Netscape Comment: + OpenSSL Generated Certificate + X509v3 Subject Key Identifier: + DD:62:16:D2:AD:86:55:D0:EC:F7:5F:81:55:C1:12:50:4E:E5:3E:04 + X509v3 Authority Key Identifier: + keyid:D3:4A:C7:C3:47:ED:99:CD:96:DB:5E:C2:D2:21:A5:61:60:8C:7F:06 + + Signature Algorithm: sha1WithRSAEncryption + af:a1:2d:c7:38:73:5f:96:2f:5b:c0:66:5f:ed:b5:d6:d5:29: + 6d:ed:8a:d8:32:62:3a:53:39:7b:a1:37:15:68:52:fa:b5:d7: + 29:82:5d:fe:62:c9:07:d9:0a:8a:11:9c:93:83:9d:a2:02:59: + 1b:0c:d7:b6:b4:51:17:ef:7d:a6:90:37:85:64:39:02:67:9e: + 6f:e7:1e:97:52:33:8a:05:3a:75:4a:f3:97:76:43:8c:72:0c: + 8a:2a:93:44:b8:80:39:7d:be:26:0d:cf:f8:db:5d:02:2c:01: + 77:4b:d5:ae:8e:1c:fa:89:9d:63:cf:1e:5c:39:06:99:a7:d1: + bc:c3:ec:1a:54:29:8b:18:0f:88:05:37:6d:68:14:22:5a:d7: + 84:08:d7:0c:28:8f:e2:fc:cd:3d:37:7d:11:10:4d:8e:b0:83: + 95:41:bf:ff:48:20:f5:17:b3:ed:f7:b8:75:d8:30:93:03:1f: + 3c:cd:da:ab:dd:fb:06:42:d3:cb:cd:68:32:cf:b3:a3:70:db: + b3:16:f3:ac:8d:ef:a6:20:58:cb:1e:d0:20:e8:bc:85:c9:71: + 27:60:05:2e:e1:87:8e:09:85:78:09:84:cd:7f:b4:bd:db:9a: + 5e:17:6b:66:35:32:fd:a0:cc:7b:12:e3:3f:bf:3a:15:e4:17: + 34:de:35:85 +-----BEGIN CERTIFICATE----- +MIID9jCCAt6gAwIBAgIJAM624vTyzclxMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjEXMBUGA1UECgwOTmVvIFRlY2hub2xv +Z3kxFTATBgNVBAMMDGNhLm5lbzRqLm9yZzEbMBkGCSqGSIb3DQEJARYMY2FAbmVv +NGoub3JnMCAXDTEzMDUxMjExMTI1MloYDzIxMTMwNDE4MTExMjUyWjCBiDELMAkG +A1UEBhMCU0UxETAPBgNVBAgMCE1hbG3Dg8K2MREwDwYDVQQHDAhNYWxtw4PCtjEX +MBUGA1UECgwOTmVvIFRlY2hub2xvZ3kxGTAXBgNVBAMMEGRpcmVjdC5uZW80ai5v +cmcxHzAdBgkqhkiG9w0BCQEWEGRpcmVjdEBuZW80ai5vcmcwggEiMA0GCSqGSIb3 +DQEBAQUAA4IBDwAwggEKAoIBAQDZ3i7+Lij1uvigkx3hmLxcX5wjIrdL5JSCGDhs +J5ucXVpZs3q+1eiP77L2QdRzc6V587xnvtD8bbDcuDXHPqO9NvFHFU3XD6uVvv9g +A6EAUd18qb1Zguv4Uaa5jDQm6wNmdmGq5bq5a+1/xfOawjkUPqLyMmOBTULBt4kX +00wSDGGN+J4Y45F1cAUw5vw7DOv7S+mf0R5R5j83NpatSnyw91j+PDvnmSb7DHY1 +ZMnNEY1wkUwcsi8h/dciwCcMttd/EWCN5TRO1PfPMFqebRZiTYpOLAEFcx4/6the +F9ZhjzQkYNl8V8mQnGVOOnb6QTAmm9p7y1+//JywFzE/T2DJAgMBAAGjezB5MAkG +A1UdEwQCMAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRp +ZmljYXRlMB0GA1UdDgQWBBTdYhbSrYZV0Oz3X4FVwRJQTuU+BDAfBgNVHSMEGDAW +gBTTSsfDR+2ZzZbbXsLSIaVhYIx/BjANBgkqhkiG9w0BAQUFAAOCAQEAr6Etxzhz +X5YvW8BmX+211tUpbe2K2DJiOlM5e6E3FWhS+rXXKYJd/mLJB9kKihGck4OdogJZ +GwzXtrRRF+99ppA3hWQ5Ameeb+cel1IzigU6dUrzl3ZDjHIMiiqTRLiAOX2+Jg3P ++NtdAiwBd0vVro4c+omdY88eXDkGmafRvMPsGlQpixgPiAU3bWgUIlrXhAjXDCiP +4vzNPTd9ERBNjrCDlUG//0gg9Rez7fe4ddgwkwMfPM3aq937BkLTy81oMs+zo3Db +sxbzrI3vpiBYyx7QIOi8hclxJ2AFLuGHjgmFeAmEzX+0vduaXhdrZjUy/aDMexLj +P786FeQXNN41hQ== +-----END CERTIFICATE----- diff --git a/manual/server-docs/src/test/resources/certificates/direct_req.pem b/manual/server-docs/src/test/resources/certificates/direct_req.pem new file mode 100644 index 0000000000000..50086d17d3162 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/direct_req.pem @@ -0,0 +1,46 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDZ3i7+Lij1uvig +kx3hmLxcX5wjIrdL5JSCGDhsJ5ucXVpZs3q+1eiP77L2QdRzc6V587xnvtD8bbDc +uDXHPqO9NvFHFU3XD6uVvv9gA6EAUd18qb1Zguv4Uaa5jDQm6wNmdmGq5bq5a+1/ +xfOawjkUPqLyMmOBTULBt4kX00wSDGGN+J4Y45F1cAUw5vw7DOv7S+mf0R5R5j83 +NpatSnyw91j+PDvnmSb7DHY1ZMnNEY1wkUwcsi8h/dciwCcMttd/EWCN5TRO1PfP +MFqebRZiTYpOLAEFcx4/6theF9ZhjzQkYNl8V8mQnGVOOnb6QTAmm9p7y1+//Jyw +FzE/T2DJAgMBAAECggEBAMdrzG/1KKDk57dzciKRf7i9+1ld6ZGaRSVfriWxi4px +GMaKtov/mMRHj/RBBYo4DPu30njT/M9SSFWAeBlMiOwlYmzCX2G/gjurfG4SZEi6 +ppX5eMx6Piwp8QZeA06sR4x+R5/6cBRRai9OrlC1/zE8tEIlRVeRtM1EhkQ8sKDC +7B9WLRxnWMnYPvWhjhuQrzWyZX1+EzO0PFyPiHVJ9PYYT90Yd5tKw4Y24fcC1KNM +LPX/0dA1E0GUR4tpAVy6iF57I3sTV5K2pTDmcTJxcnss/7VepjMNgym+os3kl2gI +lhG5ziw5fYsdT/gPKP8KqKejD9gdM4IiiKVelPwyZ1ECgYEA7TGTXpp0QxwxfHSn +OngaTaeIO7LxreWEhgZaxsrlwRMLeQ4Um6F7BtrGCwhM5vPyhhX9SbLaM9JOXHaR +VYLV1kOMe8zcdcvOKn4eCHMWAeFqc4M3Pkjw2xhfbh0ZPojSCZK/Y4SLE/JoYdQ9 ++pnvJPqShLMCkf0rzKgzfEfArhMCgYEA6yRYOL19XeCWqdxA1Vpqa+i6ZZDQ0v7Y +ba+9sjIrMnVkPp6tibDy/pRF5+HPlCm9XqQUHwXVtrH+eYe8ZPuILztm14PpRdFM +7SMjNh6PeccQe68WcRmX3RQcdQo4HSCNg8RqK9dMtd8XaHUOhkAcV6uaNgmYU4rE +EQRb+P8a4TMCgYAMOcvwymFHvEJIufKMrvgcHJCHEzgl0Hi+N8GiEX86LnMe8Dzb +sL0Yo1ol422jUdukUanWjKN2nFZxqaLgF3hSLPUmxG5wm+qqggmmQdhWjHniLldA +nJ3djSoOEO3mLUM2PxwiUwigJJSAxmHTcu/Cpi+7K0bso3IIgHr24vPphQKBgDs+ +bPvewjdk3pDtbWhT33xQpqXwVqzSiLUaEjFTco7EBP3B/Pc0HgUGVpaVzjcGTTAL +Jwap8a6WKgd0q4LF3QoJbV/fXGa61SbqM3TvPjbwcLa45m6YhCqvZnwWlwy/pugM +FLP3CtzH2J344C/y9zEfizsSL87cp4miD4osvBkBAoGBAJA7HLh3Tq1CvJLqP2zn +bPQiN0yponMiIv7tXwffQSHsoW03kjzbTEgyAZKk2/GiBQOLvZs5SQ3/nU20Txqg +XLBY3PdOXitOQAgiLiCcqDTlhBwqzvpRMcdSYd/X0Ut+y30RF/CXYa3FH11D7SGq +SQs/kJRLnVIk9XNY0dyX8OR9 +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE REQUEST----- +MIICzjCCAbYCAQAwgYgxCzAJBgNVBAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjER +MA8GA1UEBwwITWFsbcODwrYxFzAVBgNVBAoMDk5lbyBUZWNobm9sb2d5MRkwFwYD +VQQDDBBkaXJlY3QubmVvNGoub3JnMR8wHQYJKoZIhvcNAQkBFhBkaXJlY3RAbmVv +NGoub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2d4u/i4o9br4 +oJMd4Zi8XF+cIyK3S+SUghg4bCebnF1aWbN6vtXoj++y9kHUc3OlefO8Z77Q/G2w +3Lg1xz6jvTbxRxVN1w+rlb7/YAOhAFHdfKm9WYLr+FGmuYw0JusDZnZhquW6uWvt +f8XzmsI5FD6i8jJjgU1CwbeJF9NMEgxhjfieGOORdXAFMOb8Owzr+0vpn9EeUeY/ +NzaWrUp8sPdY/jw755km+wx2NWTJzRGNcJFMHLIvIf3XIsAnDLbXfxFgjeU0TtT3 +zzBanm0WYk2KTiwBBXMeP+rYXhfWYY80JGDZfFfJkJxlTjp2+kEwJpvae8tfv/yc +sBcxP09gyQIDAQABoAAwDQYJKoZIhvcNAQEFBQADggEBAB81ziuZTwI7RdWXvtFL +pD/u4L/zOhG8o8aZ9lb0iqBhEZixLoMy9nzKz+eNn8uxZNWqppc7gLrK+Gn31K8f +r0WRyxqS20Pz33e0wa7yaLQg/zmxJniKzbL1fxwefaRtj8SmB3llPgxYw0FK5Vfi +EWHjB0i8H94f9ybQ3rskD/QCVMSdr4wQcFRbOp8BH+8QRTjn99fSES0myy2qwmTi +13CaxRItG6wBpSD2805qSjV/qNzn1giR0+Nw+S7g0sRAtKfoRXuiEnhCebo/9ONO +RvvqgKvtEmhqenoCN/SOK0Y8R6FH01HxaARCBVYUlhFDKb3aCxRkK7K4rY6WWlWY +kEA= +-----END CERTIFICATE REQUEST----- diff --git a/manual/server-docs/src/test/resources/certificates/intermediate_cert.pem b/manual/server-docs/src/test/resources/certificates/intermediate_cert.pem new file mode 100644 index 0000000000000..4f7cacb5fc130 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/intermediate_cert.pem @@ -0,0 +1,74 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 14895342359247964530 (0xceb6e2f4f2cdc972) + Signature Algorithm: sha1WithRSAEncryption + Issuer: C=SE, ST=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=ca.neo4j.org/emailAddress=ca@neo4j.org + Validity + Not Before: May 12 11:14:52 2013 GMT + Not After : Apr 18 11:14:52 2113 GMT + Subject: C=SE, ST=Malm\xC3\x83\xC2\xB6, L=Malm\xC3\x83\xC2\xB6, O=Neo Technology, CN=intermediate.neo4j.org/emailAddress=intermediate@neo4j.org + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:dd:c5:ff:86:48:0b:25:df:f5:72:a9:ba:d3:52: + 7e:c8:f3:e8:1d:05:4b:e8:f8:04:ba:e0:0a:af:05: + 88:84:06:25:06:40:76:fc:54:25:2a:bf:4b:67:8f: + ca:42:2d:03:df:be:95:3d:10:21:fb:96:e6:53:08: + 57:71:70:3e:f8:7a:e1:73:44:a8:86:2f:b4:cd:32: + 92:b8:49:70:c4:86:85:73:4d:f6:53:5e:84:00:d5: + db:b0:6b:45:d2:1f:39:5b:42:2c:7b:41:76:b8:3e: + 0e:e0:49:a0:a9:94:43:1d:38:11:9b:58:11:de:a6: + 44:51:3b:e0:36:71:4c:a4:cc:2e:73:38:0e:d5:86: + 45:77:5f:87:f4:a3:14:aa:0f:fc:9c:1a:97:5f:f4: + ea:47:56:58:e0:31:1e:74:fe:e1:72:93:90:58:43: + 53:05:18:22:2f:8c:93:ef:96:77:9f:07:3d:10:da: + 9d:d9:39:a3:44:10:ba:ee:12:37:44:95:f4:ba:a5: + 44:71:69:0f:8d:b6:8a:e0:d2:10:e0:22:df:a6:73: + e3:a1:ee:6d:a5:0e:c5:59:1d:65:76:fc:5a:19:ab: + a8:77:fc:ac:ab:1a:71:3d:8f:12:07:07:e1:ce:11: + fc:85:db:4c:b1:a0:c3:9d:48:8a:29:af:1d:05:dd: + 86:85 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha1WithRSAEncryption + 61:b8:6a:5b:29:17:75:44:61:81:49:2f:34:42:1e:aa:02:da: + d5:40:1e:d4:c7:24:27:0b:e3:1c:a5:c7:e7:1e:a2:f1:d5:f4: + 51:d7:7b:2c:c5:75:61:5e:9e:4f:bb:a2:9d:f9:1d:1e:fa:1f: + 5b:28:25:ab:9d:56:31:41:07:50:50:b3:ba:23:b2:d7:1a:ff: + 84:4b:b0:60:0c:77:85:75:3e:00:e2:c3:7a:34:67:a5:d2:53: + 13:71:ec:07:02:24:9a:5b:1e:eb:b5:c0:d5:77:9f:1e:99:8e: + 0d:3f:37:1c:3a:e3:a8:28:56:3f:e3:b2:b2:5d:27:45:e6:72: + 65:67:81:6a:fe:6e:d3:a6:89:03:41:e7:ed:23:9f:b4:97:df: + 68:79:cd:92:a2:74:d1:70:3b:d0:7e:b4:00:dc:f9:c4:0f:fe: + 76:d0:f8:cb:74:7a:68:2b:d7:6b:1a:56:02:c9:88:c2:e0:85: + ee:a1:02:e5:2b:24:24:ce:e4:2e:84:12:c2:3e:ca:d4:4c:04: + 1d:27:2d:5b:2d:e5:4c:ae:98:3e:89:2e:04:c9:2e:e4:da:3f: + f4:e2:23:08:51:1d:00:00:c9:3b:82:bc:25:88:ce:59:40:9f: + 58:62:19:24:70:d4:22:2a:8c:60:53:4c:69:55:3f:a9:82:91: + c7:b8:f9:3c +-----BEGIN CERTIFICATE----- +MIIDlzCCAn+gAwIBAgIJAM624vTyzclyMA0GCSqGSIb3DQEBBQUAMG0xCzAJBgNV +BAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjEXMBUGA1UECgwOTmVvIFRlY2hub2xv +Z3kxFTATBgNVBAMMDGNhLm5lbzRqLm9yZzEbMBkGCSqGSIb3DQEJARYMY2FAbmVv +NGoub3JnMCAXDTEzMDUxMjExMTQ1MloYDzIxMTMwNDE4MTExNDUyWjCBlDELMAkG +A1UEBhMCU0UxETAPBgNVBAgMCE1hbG3Dg8K2MREwDwYDVQQHDAhNYWxtw4PCtjEX +MBUGA1UECgwOTmVvIFRlY2hub2xvZ3kxHzAdBgNVBAMMFmludGVybWVkaWF0ZS5u +ZW80ai5vcmcxJTAjBgkqhkiG9w0BCQEWFmludGVybWVkaWF0ZUBuZW80ai5vcmcw +ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDdxf+GSAsl3/VyqbrTUn7I +8+gdBUvo+AS64AqvBYiEBiUGQHb8VCUqv0tnj8pCLQPfvpU9ECH7luZTCFdxcD74 +euFzRKiGL7TNMpK4SXDEhoVzTfZTXoQA1duwa0XSHzlbQix7QXa4Pg7gSaCplEMd +OBGbWBHepkRRO+A2cUykzC5zOA7VhkV3X4f0oxSqD/ycGpdf9OpHVljgMR50/uFy +k5BYQ1MFGCIvjJPvlnefBz0Q2p3ZOaNEELruEjdElfS6pURxaQ+Ntorg0hDgIt+m +c+Oh7m2lDsVZHWV2/FoZq6h3/KyrGnE9jxIHB+HOEfyF20yxoMOdSIoprx0F3YaF +AgMBAAGjEDAOMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADggEBAGG4alsp +F3VEYYFJLzRCHqoC2tVAHtTHJCcL4xylx+ceovHV9FHXeyzFdWFenk+7op35HR76 +H1soJaudVjFBB1BQs7ojstca/4RLsGAMd4V1PgDiw3o0Z6XSUxNx7AcCJJpbHuu1 +wNV3nx6Zjg0/Nxw646goVj/jsrJdJ0XmcmVngWr+btOmiQNB5+0jn7SX32h5zZKi +dNFwO9B+tADc+cQP/nbQ+Mt0emgr12saVgLJiMLghe6hAuUrJCTO5C6EEsI+ytRM +BB0nLVst5UyumD6JLgTJLuTaP/TiIwhRHQAAyTuCvCWIzllAn1hiGSRw1CIqjGBT +TGlVP6mCkce4+Tw= +-----END CERTIFICATE----- diff --git a/manual/server-docs/src/test/resources/certificates/intermediate_req.pem b/manual/server-docs/src/test/resources/certificates/intermediate_req.pem new file mode 100644 index 0000000000000..4b7dc515a3f95 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/intermediate_req.pem @@ -0,0 +1,46 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDdxf+GSAsl3/Vy +qbrTUn7I8+gdBUvo+AS64AqvBYiEBiUGQHb8VCUqv0tnj8pCLQPfvpU9ECH7luZT +CFdxcD74euFzRKiGL7TNMpK4SXDEhoVzTfZTXoQA1duwa0XSHzlbQix7QXa4Pg7g +SaCplEMdOBGbWBHepkRRO+A2cUykzC5zOA7VhkV3X4f0oxSqD/ycGpdf9OpHVljg +MR50/uFyk5BYQ1MFGCIvjJPvlnefBz0Q2p3ZOaNEELruEjdElfS6pURxaQ+Ntorg +0hDgIt+mc+Oh7m2lDsVZHWV2/FoZq6h3/KyrGnE9jxIHB+HOEfyF20yxoMOdSIop +rx0F3YaFAgMBAAECggEADgv3dlLj2d9IlZ2ohrAH9cR1JDlsqHHlIs0EADfygKI/ +ABlfL/ZIcF+zb/jMj8p4H1jb7QUzkdLp/cJef4HjiyuFUh68KdulsQWOA3wrV1dJ +5pa+jmNDKyZghtkUkA2IPX6AgMIRufO8T3p6YaNRaO1fOB6o6xhO5XJMbkZY22sx +Ho71Y4jEo6jcKGzTSYq0aeLxMVqkezB3h5CpgGnSMlUKytq/y3LzjqLCE49IrTHe +omCwi2VB1ebRyJIW8EXPyv3kue5CTpfQS2t00V5MqGsLE3E8rKK+MAfbAtfHuUcO +ZJkFc7mmA8s5iwBtC2vCmh8DS6C4kG4Noacarj/qdQKBgQD8G/w0qkMnkpOLrRNH +WNYFQKGxsgEfs3r8aasauqpHeJVYJl+vOFvnw/eArdzrz+p5TpTsbBmrrlyvAH0h +R38DmVnGiNMsrDzfFYN/3zkL1tdUPSa0JOcKCQc2LITY148sRSkvToWS7G0A1v7w +U2IowbT2DCdccwUZajmxFH34twKBgQDhMioIROhyyh+vev9Cx5NFFs87ocBd+9bA +zRlYNrPomUbcfsK6i2D0E/0iKWdidZe/vCWRGt3m/DknaN9OtFUKnNfQEPtCmsq3 +AQnZasv6ANgazcnAqZKuf8GhR3UwJ/nYiu4BarK70Kff1g680iZMKDSPGStwGSRq +ae+igCsmowKBgQDKF8jhksbIVxhNdE0q+Ux+42N6pT1/YwmogzA0+gN7zW6yPfif +5ibOL+ocTcL1iTLlURfSOYmOdPQ5GMQ1xDFN+kTcAFx/yAX3sjA6df8tvWfZUDfR +Wm+WPMx6Ic/QX2OW0k4EbQZoU3vBm5a7oavjDfx47B81XcJtsBhVGh8tGwKBgQCC +MkB+22gQxa8gA1qFP2FztkZF0bSoIav9eBbJG4dUWRnQ7TCkLWT2eIAHOizdjeMY +JS0LAZ99piFdGZLRrAzmv9s+HjNGRLwipeUN4GLhvY8zn/qe+uslHBPUrL2iA9q9 +SUVlaEzBrVcBYH7QyCcALNaGTSUCAGc/ZtP2/Wb1RQKBgQDHQP59qKbXnRKje79D +9wZXCnYDXlQTMo5aVlFReMTXLnPi4/2PwP03B39FyC4rrUsrj7OrvoSEwNYWpKFv +LgCTWOtj6NKlkxbA/Y9FQudIOZlIZ3t9A86B9WtiQd0WJE80aqLA4judOCh9tuPr +D7UzM1bwpSA+kQ5n6c0rxnZ10w== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE REQUEST----- +MIIC2jCCAcICAQAwgZQxCzAJBgNVBAYTAlNFMREwDwYDVQQIDAhNYWxtw4PCtjER +MA8GA1UEBwwITWFsbcODwrYxFzAVBgNVBAoMDk5lbyBUZWNobm9sb2d5MR8wHQYD +VQQDDBZpbnRlcm1lZGlhdGUubmVvNGoub3JnMSUwIwYJKoZIhvcNAQkBFhZpbnRl +cm1lZGlhdGVAbmVvNGoub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC +AQEA3cX/hkgLJd/1cqm601J+yPPoHQVL6PgEuuAKrwWIhAYlBkB2/FQlKr9LZ4/K +Qi0D376VPRAh+5bmUwhXcXA++Hrhc0Sohi+0zTKSuElwxIaFc032U16EANXbsGtF +0h85W0Ise0F2uD4O4EmgqZRDHTgRm1gR3qZEUTvgNnFMpMwuczgO1YZFd1+H9KMU +qg/8nBqXX/TqR1ZY4DEedP7hcpOQWENTBRgiL4yT75Z3nwc9ENqd2TmjRBC67hI3 +RJX0uqVEcWkPjbaK4NIQ4CLfpnPjoe5tpQ7FWR1ldvxaGauod/ysqxpxPY8SBwfh +zhH8hdtMsaDDnUiKKa8dBd2GhQIDAQABoAAwDQYJKoZIhvcNAQEFBQADggEBACF8 +bkWiCDU3SYBh9z7ct/nILNT8OGAWPB5w2IGl5YCeJSVODlGt16PZx06HUdFCYXer +mMEBmcNpRW4sAGg3ry3YwM3sQnlCIWi3gKeUQnGKmWF+dHK/UmOtCU2kuVJuOJ8W +r++45G7iGOVsUwAOPsbYvrf95CCIXy5ZHzzQSF2TkWjgSU2KkpePNH/aFLY+paB5 +x2TItwckodIRoMraBmnAhyhq/nF43nzm8wrb9fwiEyOAoVm3zLRgFGBbvJele+eL +VtWbZU/cLeOQCpDAKbBCiu9qwmWw2EAtk5FRbFMjXH/SIxL26nj5JFBE+aKg1d+r +ONU++5AgkO/1wOPaQA4= +-----END CERTIFICATE REQUEST----- diff --git a/manual/server-docs/src/test/resources/certificates/openssl.cnf b/manual/server-docs/src/test/resources/certificates/openssl.cnf new file mode 100644 index 0000000000000..5127df4444804 --- /dev/null +++ b/manual/server-docs/src/test/resources/certificates/openssl.cnf @@ -0,0 +1,354 @@ +# +# OpenSSL example configuration file. +# This is mostly being used for generation of certificate requests. +# + +# This definition stops the following lines choking if HOME isn't +# defined. +HOME = . +RANDFILE = $ENV::HOME/.rnd + +# Extra OBJECT IDENTIFIER info: +#oid_file = $ENV::HOME/.oid +oid_section = new_oids + +# To use this configuration file with the "-extfile" option of the +# "openssl x509" utility, name here the section containing the +# X.509v3 extensions to use: +# extensions = +# (Alternatively, use a configuration file that has only +# X.509v3 extensions in its main [= default] section.) + +[ new_oids ] + +# We can add new OIDs in here for use by 'ca', 'req' and 'ts'. +# Add a simple OID like this: +# testoid1=1.2.3.4 +# Or use config file substitution like this: +# testoid2=${testoid1}.5.6 + +# Policies used by the TSA examples. +tsa_policy1 = 1.2.3.4.1 +tsa_policy2 = 1.2.3.4.5.6 +tsa_policy3 = 1.2.3.4.5.7 + +#################################################################### +[ ca ] +default_ca = CA_default # The default ca section + +#################################################################### +[ CA_default ] + +dir = ./demoCA # Where everything is kept +certs = $dir/certs # Where the issued certs are kept +crl_dir = $dir/crl # Where the issued crl are kept +database = $dir/index.txt # database index file. +#unique_subject = no # Set to 'no' to allow creation of + # several ctificates with same subject. +new_certs_dir = $dir/newcerts # default place for new certs. + +certificate = $dir/cacert.pem # The CA certificate +serial = $dir/serial # The current serial number +crlnumber = $dir/crlnumber # the current crl number + # must be commented out to leave a V1 CRL +crl = $dir/crl.pem # The current CRL +private_key = $dir/private/cakey.pem# The private key +RANDFILE = $dir/private/.rand # private random number file + +x509_extensions = usr_cert # The extentions to add to the cert + +# Comment out the following two lines for the "traditional" +# (and highly broken) format. +name_opt = ca_default # Subject Name options +cert_opt = ca_default # Certificate field options + +# Extension copying option: use with caution. +# copy_extensions = copy + +# Extensions to add to a CRL. Note: Netscape communicator chokes on V2 CRLs +# so this is commented out by default to leave a V1 CRL. +# crlnumber must also be commented out to leave a V1 CRL. +# crl_extensions = crl_ext + +default_days = 36500 # how long to certify for +default_crl_days= 30 # how long before next CRL +default_md = default # use public key default MD +preserve = no # keep passed DN ordering + +# A few difference way of specifying how similar the request should look +# For type CA, the listed attributes must be the same, and the optional +# and supplied fields are just that :-) +policy = policy_match + +# For the CA policy +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +# For the 'anything' policy +# At this point in time, you must list all acceptable 'object' +# types. +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +#################################################################### +[ req ] +default_bits = 2048 +default_keyfile = privkey.pem +distinguished_name = req_distinguished_name +attributes = req_attributes +x509_extensions = v3_ca # The extentions to add to the self signed cert + +# Passwords for private keys if not present they will be prompted for +# input_password = secret +# output_password = secret + +# This sets a mask for permitted string types. There are several options. +# default: PrintableString, T61String, BMPString. +# pkix : PrintableString, BMPString (PKIX recommendation before 2004) +# utf8only: only UTF8Strings (PKIX recommendation after 2004). +# nombstr : PrintableString, T61String (no BMPStrings or UTF8Strings). +# MASK:XXXX a literal mask value. +# WARNING: ancient versions of Netscape crash on BMPStrings or UTF8Strings. +string_mask = utf8only + +# req_extensions = v3_req # The extensions to add to a certificate request + +[ req_distinguished_name ] +countryName = Country Name (2 letter code) +countryName_default = SE +countryName_min = 2 +countryName_max = 2 + +stateOrProvinceName = State or Province Name (full name) +stateOrProvinceName_default = Malmö + +localityName = Locality Name (eg, city) +localityName_default = Malmö + +0.organizationName = Organization Name (eg, company) +0.organizationName_default = Neo Technology + +# we can do this but it is not needed normally :-) +#1.organizationName = Second Organization Name (eg, company) +#1.organizationName_default = World Wide Web Pty Ltd + +organizationalUnitName = Organizational Unit Name (eg, section) +#organizationalUnitName_default = + +commonName = Common Name (e.g. server FQDN or YOUR name) +commonName_max = 64 + +emailAddress = Email Address +emailAddress_max = 64 + +# SET-ex3 = SET extension number 3 + +[ req_attributes ] +challengePassword = A challenge password +challengePassword_min = 4 +challengePassword_max = 20 + +unstructuredName = An optional company name + +[ usr_cert ] + +# These extensions are added when 'ca' signs a request. + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This is required for TSA certificates. +# extendedKeyUsage = critical,timeStamping + +[ v3_req ] + +# Extensions to add to a certificate request + +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +[ v3_ca ] + + +# Extensions for a typical CA + + +# PKIX recommendation. + +subjectKeyIdentifier=hash + +authorityKeyIdentifier=keyid:always,issuer + +# This is what PKIX recommends but some broken software chokes on critical +# extensions. +#basicConstraints = critical,CA:true +# So we do this instead. +basicConstraints = CA:true + +# Key usage: this is typical for a CA certificate. However since it will +# prevent it being used as an test self-signed certificate it is best +# left out by default. +# keyUsage = cRLSign, keyCertSign + +# Some might want this also +# nsCertType = sslCA, emailCA + +# Include email address in subject alt name: another PKIX recommendation +# subjectAltName=email:copy +# Copy issuer details +# issuerAltName=issuer:copy + +# DER hex encoding of an extension: beware experts only! +# obj=DER:02:03 +# Where 'obj' is a standard or added object +# You can even override a supported extension: +# basicConstraints= critical, DER:30:03:01:01:FF + +[ crl_ext ] + +# CRL extensions. +# Only issuerAltName and authorityKeyIdentifier make any sense in a CRL. + +# issuerAltName=issuer:copy +authorityKeyIdentifier=keyid:always + +[ proxy_cert_ext ] +# These extensions should be added when creating a proxy certificate + +# This goes against PKIX guidelines but some CAs do it and some software +# requires this to avoid interpreting an end user certificate as a CA. + +basicConstraints=CA:FALSE + +# Here are some examples of the usage of nsCertType. If it is omitted +# the certificate can be used for anything *except* object signing. + +# This is OK for an SSL server. +# nsCertType = server + +# For an object signing certificate this would be used. +# nsCertType = objsign + +# For normal client use this is typical +# nsCertType = client, email + +# and for everything including object signing: +# nsCertType = client, email, objsign + +# This is typical in keyUsage for a client certificate. +# keyUsage = nonRepudiation, digitalSignature, keyEncipherment + +# This will be displayed in Netscape's comment listbox. +nsComment = "OpenSSL Generated Certificate" + +# PKIX recommendations harmless if included in all certificates. +subjectKeyIdentifier=hash +authorityKeyIdentifier=keyid,issuer + +# This stuff is for subjectAltName and issuerAltname. +# Import the email address. +# subjectAltName=email:copy +# An alternative to produce certificates that aren't +# deprecated according to PKIX. +# subjectAltName=email:move + +# Copy subject details +# issuerAltName=issuer:copy + +#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem +#nsBaseUrl +#nsRevocationUrl +#nsRenewalUrl +#nsCaPolicyUrl +#nsSslServerName + +# This really needs to be in place for it to be a proxy certificate. +proxyCertInfo=critical,language:id-ppl-anyLanguage,pathlen:3,policy:foo + +#################################################################### +[ tsa ] + +default_tsa = tsa_config1 # the default TSA section + +[ tsa_config1 ] + +# These are used by the TSA reply generation only. +dir = ./demoCA # TSA root directory +serial = $dir/tsaserial # The current serial number (mandatory) +crypto_device = builtin # OpenSSL engine to use for signing +signer_cert = $dir/tsacert.pem # The TSA signing certificate + # (optional) +certs = $dir/cacert.pem # Certificate chain to include in reply + # (optional) +signer_key = $dir/private/tsakey.pem # The TSA private key (optional) + +default_policy = tsa_policy1 # Policy if request did not specify it + # (optional) +other_policies = tsa_policy2, tsa_policy3 # acceptable policies (optional) +digests = md5, sha1 # Acceptable message digests (mandatory) +accuracy = secs:1, millisecs:500, microsecs:100 # (optional) +clock_precision_digits = 0 # number of digits after dot. (optional) +ordering = yes # Is ordering defined for timestamps? + # (optional, default: no) +tsa_name = yes # Must the TSA name be included in the reply? + # (optional, default: no) +ess_cert_id_chain = no # Must the ESS cert id chain be included? + # (optional, default: no) + +[ my_v3_ext ] +basicConstraints = CA:true diff --git a/manual/server-examples/pom.xml b/manual/server-examples/pom.xml index 519fe09b748fa..301c28aea78b9 100644 --- a/manual/server-examples/pom.xml +++ b/manual/server-examples/pom.xml @@ -77,12 +77,13 @@ - org.neo4j.app - neo4j-server + org.neo4j.doc + neo4j-server-docs ${project.version} - test-jar test + test-jar + org.neo4j neo4j-io diff --git a/manual/server-examples/src/docs/dev/server-java-rest-client.asciidoc b/manual/server-examples/src/docs/dev/server-java-rest-client.asciidoc index 2c585accca647..7b1967067f1cd 100644 --- a/manual/server-examples/src/docs/dev/server-java-rest-client.asciidoc +++ b/manual/server-examples/src/docs/dev/server-java-rest-client.asciidoc @@ -13,7 +13,7 @@ which are easily https://jersey.java.net/nonav/documentation/1.9/user-guide.html == Start the server == -Before we can perform any actions on the server, we need to start it as per <>. +Before we can perform any actions on the server, we need to start it. Next up, we'll check the connection to the server: [snippet,java] diff --git a/manual/server-examples/src/test/java/org/neo4j/examples/server/AbstractPluginTestBase.java b/manual/server-examples/src/test/java/org/neo4j/examples/server/AbstractPluginTestBase.java index 8fb7034a50109..d88c708870ce1 100644 --- a/manual/server-examples/src/test/java/org/neo4j/examples/server/AbstractPluginTestBase.java +++ b/manual/server-examples/src/test/java/org/neo4j/examples/server/AbstractPluginTestBase.java @@ -18,18 +18,18 @@ */ package org.neo4j.examples.server; -import org.junit.BeforeClass; - import java.io.IOException; import java.util.Map; -import org.neo4j.server.helpers.FunctionalTestHelper; +import org.junit.BeforeClass; + +import org.neo4j.doc.server.rest.AbstractRestFunctionalTestBase; +import org.neo4j.doc.server.rest.RESTDocsGenerator; +import org.neo4j.doc.server.helpers.FunctionalTestHelper; import org.neo4j.server.plugins.PluginFunctionalTestHelper; import org.neo4j.server.plugins.PluginFunctionalTestHelper.RegExp; import org.neo4j.server.plugins.ServerPlugin; -import org.neo4j.server.rest.AbstractRestFunctionalTestBase; -import org.neo4j.server.rest.RESTDocsGenerator; -import org.neo4j.server.rest.domain.GraphDbHelper; +import org.neo4j.doc.server.rest.domain.GraphDbHelper; import org.neo4j.server.rest.domain.JsonParseException; import static org.hamcrest.CoreMatchers.notNullValue; diff --git a/manual/server-examples/src/test/java/org/neo4j/examples/server/unmanaged/UnmanagedExtensionsDocIT.java b/manual/server-examples/src/test/java/org/neo4j/examples/server/unmanaged/UnmanagedExtensionsDocIT.java index c43e56c5efde6..8749a9ffa2a3e 100644 --- a/manual/server-examples/src/test/java/org/neo4j/examples/server/unmanaged/UnmanagedExtensionsDocIT.java +++ b/manual/server-examples/src/test/java/org/neo4j/examples/server/unmanaged/UnmanagedExtensionsDocIT.java @@ -18,7 +18,6 @@ */ package org.neo4j.examples.server.unmanaged; -import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; @@ -27,16 +26,15 @@ import org.junit.Test; import org.neo4j.harness.junit.Neo4jRule; -import org.neo4j.server.ServerTestUtils; import org.neo4j.server.configuration.ServerSettings; -import org.neo4j.test.server.HTTP; +import org.neo4j.doc.server.HTTP; import static junit.framework.TestCase.assertEquals; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.IsCollectionContaining.hasItem; -import static org.neo4j.server.ServerTestUtils.*; -import static org.neo4j.server.ServerTestUtils.getSharedTestTemporaryFolder; +import static org.neo4j.doc.server.ServerTestUtils.*; +import static org.neo4j.doc.server.ServerTestUtils.getSharedTestTemporaryFolder; public class UnmanagedExtensionsDocIT { diff --git a/manual/shell/src/docs/dev/shell.asciidoc b/manual/shell/src/docs/dev/shell.asciidoc index f4e315b4ed532..0c38510f322a5 100644 --- a/manual/shell/src/docs/dev/shell.asciidoc +++ b/manual/shell/src/docs/dev/shell.asciidoc @@ -32,9 +32,6 @@ To connect to a running Neo4j database, use <> for local databas You need to make sure that the shell jar file is on the classpath when you start up your Neo4j instance. -For help with running shell using Windows, see the reference in <>. - - [[shell-enabling-remote]] === Enabling the shell server