protected String doGremlinRestCall( String endpoint, String script, Status status, Pair<String, String>... params) { data.get(); String parameterString = createParameterString(params); String queryString = "{\"script\": \"" + createScript(script) + "\"," + parameterString + "},"; gen.get() .expectedStatus(status.getStatusCode()) .payload(queryString) .description(formatGroovy(createScript(script))); return gen.get().post(endpoint).entity(); }
protected String doCypherRestCall( String endpoint, String script, Status status, Pair<String, String>... params) { data.get(); String parameterString = createParameterString(params); String queryString = "{\"query\": \"" + createScript(script) + "\"," + parameterString + "},"; gen.get() .expectedStatus(status.getStatusCode()) .payload(queryString) .description(AsciidocHelper.createCypherSnippet(script)); return gen.get().post(endpoint).entity(); }
/** Set property on node. */ @Documented @Test public void shouldReturn204WhenPropertyIsSet() throws Exception { gen.get() .payload(JsonHelper.createJsonFrom("bar")) .expectedStatus(204) .put(getPropertyUri("foo").toString()); }
protected void captureScreenshot(String string) { WebDriver webDriver = wl.getWebDriver(); if (webDriver instanceof TakesScreenshot) { try { File screenshotFile = ((TakesScreenshot) webDriver).getScreenshotAs(FILE); System.out.println(screenshotFile.getAbsolutePath()); File dir = new File("target/docs/webadmin/images"); dir.mkdirs(); String imageName = string + ".png"; copyFile(screenshotFile, new File(dir, imageName)); gen.get().addImageSnippet(string, imageName, gen.get().getTitle()); } catch (Exception e) { e.printStackTrace(); } } }
/** * When auth is disabled * * <p>When auth has been disabled in the configuration, requests can be sent without an * +Authorization+ header. */ @Test @Documented public void auth_disabled() throws IOException { // Given startServer(false); // Document gen.get().noGraph().expectedStatus(200).get(dataURL()); }
/** * Property values can not be nested. * * <p>Nesting properties is not supported. You could for example store the nested json as a string * instead. */ @Documented @Test public void shouldReturn400WhenSendinIncompatibleJsonProperty() throws Exception { gen.get() .payload("{\"foo\" : {\"bar\" : \"baz\"}}") .expectedStatus(400) .post(functionalTestHelper.nodeUri()); JaxRsResponse response = setNodePropertyOnServer("jim", new HashMap<String, Object>()); assertEquals(400, response.getStatus()); response.close(); }
/** Update node properties. */ @Documented @Test public void shouldReturn204WhenPropertiesAreUpdated() throws JsonParseException { Map<String, Object> map = new HashMap<String, Object>(); map.put("jim", "tobias"); gen.get() .payload(JsonHelper.createJsonFrom(map)) .expectedStatus(204) .put(propertiesUri.toString()); JaxRsResponse response = updateNodePropertiesOnServer(map); assertEquals(204, response.getStatus()); }
@Test @Graph( value = {"John friend Sara", "John friend Joe", "Sara friend Maria", "Joe friend Steve"}, autoIndexNodes = true) public void intro_examples() throws Exception { try (Transaction ignored = graphdb.beginTx()) { Writer fw = AsciiDocGenerator.getFW(DOCS_TARGET, gen.get().getTitle()); data.get(); fw.append("\nImagine an example graph like the following one:\n\n"); fw.append( AsciiDocGenerator.dumpToSeparateFileWithType( new File(DOCS_TARGET), "intro.graph", AsciidocHelper.createGraphVizWithNodeId("Example Graph", graphdb(), "cypher-intro"))); fw.append( "\nFor example, here is a query which finds a user called John and John's friends (though not " + "his direct friends) before returning both John and any friends-of-friends that are found."); fw.append("\n\n"); String query = "MATCH (john {name: 'John'})-[:friend]->()-[:friend]->(fof) RETURN john, fof "; fw.append( AsciiDocGenerator.dumpToSeparateFileWithType( new File(DOCS_TARGET), "intro.query", createCypherSnippet(query))); fw.append("\nResulting in:\n\n"); fw.append( AsciiDocGenerator.dumpToSeparateFileWithType( new File(DOCS_TARGET), "intro.result", createQueryResultSnippet(engine.execute(query).dumpToString()))); fw.append( "\nNext up we will add filtering to set more parts " + "in motion:\n\nWe take a list of user names " + "and find all nodes with names from this list, match their friends and return " + "only those followed users who have a +name+ property starting with +S+."); query = "MATCH (user)-[:friend]->(follower) WHERE " + "user.name IN ['Joe', 'John', 'Sara', 'Maria', 'Steve'] AND follower.name =~ 'S.*' " + "RETURN user, follower.name "; fw.append("\n\n"); fw.append( AsciiDocGenerator.dumpToSeparateFileWithType( new File(DOCS_TARGET), "intro.query", createCypherSnippet(query))); fw.append("\nResulting in:\n\n"); fw.append( AsciiDocGenerator.dumpToSeparateFileWithType( new File(DOCS_TARGET), "intro.result", createQueryResultSnippet(engine.execute(query).dumpToString()))); fw.close(); } }
/** Get all relationships. */ @Documented @Test public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingAllRelationshipsForANode() throws JsonParseException { String entity = gen.get() .expectedStatus(200) .get( functionalTestHelper.nodeUri() + "/" + nodeWithRelationships + "/relationships" + "/all") .entity(); verifyRelReps(3, entity); }
/** * Authenticate to access the server * * <p>Authenticate by sending a username and a password to Neo4j using HTTP Basic Auth. Requests * should include an +Authorization+ header, with a value of +Basic realm="Neo4j" <payload>+, * where "payload" is a base64 encoded string of "username:password". */ @Test @Documented public void successful_authentication() throws PropertyValueException, IOException { // Given startServerWithConfiguredUser(); // Document RESTDocsGenerator.ResponseEntity response = gen.get() .noGraph() .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"))); }
/** * Missing authorization * * <p>If an +Authorization+ header is not supplied, the server will reply with an error. */ @Test @Documented public void missing_authorization() throws PropertyValueException, IOException { // Given startServerWithConfiguredUser(); // Document RESTDocsGenerator.ResponseEntity response = gen.get() .noGraph() .expectedStatus(401) .expectedHeader("WWW-Authenticate", "None") .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.AuthorizationFailed")); assertThat(firstError.get("message").asText(), equalTo("No authorization header supplied.")); }
/** * Incorrect authentication * * <p>If an incorrect username or password is provided, the server replies with an error. */ @Test @Documented public void incorrect_authentication() throws PropertyValueException, IOException { // Given startServerWithConfiguredUser(); // Document RESTDocsGenerator.ResponseEntity response = gen.get() .noGraph() .expectedStatus(401) .withHeader(HttpHeaders.AUTHORIZATION, challengeResponse("neo4j", "incorrect")) .expectedHeader("WWW-Authenticate", "None") .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.AuthorizationFailed")); assertThat(firstError.get("message").asText(), equalTo("Invalid username or password.")); }
/** * Required password changes * * <p>In some cases, like the very first time Neo4j is accessed, the user will be required to * choose a new password. The database will signal that a new password is required and deny * access. * * <p>See <<rest-api-security-user-status-and-password-changing>> for how to set a new password. */ @Test @Documented public void password_change_required() throws PropertyValueException, IOException { // Given startServer(true); // Document RESTDocsGenerator.ResponseEntity response = gen.get() .noGraph() .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.AuthorizationFailed")); assertThat( firstError.get("message").asText(), equalTo("User is required to change their password.")); assertThat(data.get("password_change").asText(), equalTo(passwordURL("neo4j"))); }
public class AbstractRestFunctionalTestBase extends SharedServerTestBase implements GraphHolder { protected static final String NODES = "http://localhost:7474/db/data/node/"; public @Rule TestData<Map<String, Node>> data = TestData.producedThrough(GraphDescription.createGraphFor(this, true)); public @Rule TestData<RESTDocsGenerator> gen = TestData.producedThrough(RESTDocsGenerator.PRODUCER); @Before public void setUp() { gen().setSection(getDocumentationSectionName()); } protected String doCypherRestCall( String endpoint, String script, Status status, Pair<String, String>... params) { data.get(); String parameterString = createParameterString(params); String queryString = "{\"query\": \"" + createScript(script) + "\"," + parameterString + "},"; gen.get() .expectedStatus(status.getStatusCode()) .payload(queryString) .description(AsciidocHelper.createCypherSnippet(script)); return gen.get().post(endpoint).entity(); } protected String doGremlinRestCall( String endpoint, String script, Status status, Pair<String, String>... params) { data.get(); String parameterString = createParameterString(params); String queryString = "{\"script\": \"" + createScript(script) + "\"," + parameterString + "},"; gen.get() .expectedStatus(status.getStatusCode()) .payload(queryString) .description(formatGroovy(createScript(script))); return gen.get().post(endpoint).entity(); } protected String formatGroovy(String script) { script = script.replace(";", "\n"); if (!script.endsWith("\n")) { script += "\n"; } return "_Raw script source_\n\n" + "[source, groovy]\n" + "----\n" + script + "----\n"; } protected String formatJavaScript(String script) { script = script.replace(";", "\n"); if (!script.endsWith("\n")) { script += "\n"; } return "_Raw script source_\n\n" + "[source, javascript]\n" + "----\n" + script + "----\n"; } private Long idFor(String name) { return data.get().get(name).getId(); } protected String createParameterString(Pair<String, String>[] params) { String paramString = "\"params\": {"; for (Pair<String, String> param : params) { String delimiter = paramString.endsWith("{") ? "" : ","; paramString += delimiter + "\"" + param.first() + "\":\"" + param.other() + "\""; } paramString += "}"; 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().graph; } @Before public void cleanContent() { cleanDatabase(); gen.get().setGraph(graphdb()); } protected String getDataUri() { return "http://localhost:7474/db/data/"; } protected String getDatabaseUri() { return "http://localhost:7474/db/"; } protected String getNodeUri(Node node) { return getDataUri() + "node/" + node.getId(); } protected String getRelationshipUri(Relationship node) { return getDataUri() + "relationship/" + node.getId(); } protected String getNodeIndexUri(String indexName, String key, String value) { return postNodeIndexUri(indexName) + "/" + key + "/" + value; } protected String postNodeIndexUri(String indexName) { return getDataUri() + "index/node/" + indexName; } protected String postRelationshipIndexUri(String indexName) { return getDataUri() + "index/relationship/" + indexName; } protected String getRelationshipIndexUri(String indexName, String key, String value) { return getDataUri() + "index/relationship/" + indexName + "/" + key + "/" + value; } protected Node getNode(String name) { return data.get().get(name); } protected Node[] getNodes(String... names) { Node[] nodes = {}; ArrayList<Node> result = new ArrayList<Node>(); 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.jsonToSingleValue(entity); assertEquals(expectedSize, hits.size()); } catch (PropertyValueException e) { // TODO Auto-generated catch block e.printStackTrace(); } } 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 RESTDocsGenerator gen() { return gen.get(); }
@Before public void cleanContent() { cleanDatabase(); gen.get().setGraph(graphdb()); }
@Before public void setUp() { gen.get().setSection("dev/rest-api"); }
public class AuthenticationDocIT extends ExclusiveServerTestBase { public @Rule TestData<RESTDocsGenerator> gen = TestData.producedThrough(RESTDocsGenerator.PRODUCER); private CommunityNeoServer server; @Before public void setUp() { gen.get().setSection("dev/rest-api"); } /** * Missing authorization * * <p>If an +Authorization+ header is not supplied, the server will reply with an error. */ @Test @Documented public void missing_authorization() throws PropertyValueException, IOException { // Given startServerWithConfiguredUser(); // Document RESTDocsGenerator.ResponseEntity response = gen.get() .noGraph() .expectedStatus(401) .expectedHeader("WWW-Authenticate", "None") .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.AuthorizationFailed")); assertThat(firstError.get("message").asText(), equalTo("No authorization header supplied.")); } /** * Authenticate to access the server * * <p>Authenticate by sending a username and a password to Neo4j using HTTP Basic Auth. Requests * should include an +Authorization+ header, with a value of +Basic realm="Neo4j" <payload>+, * where "payload" is a base64 encoded string of "username:password". */ @Test @Documented public void successful_authentication() throws PropertyValueException, IOException { // Given startServerWithConfiguredUser(); // Document RESTDocsGenerator.ResponseEntity response = gen.get() .noGraph() .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"))); } /** * Incorrect authentication * * <p>If an incorrect username or password is provided, the server replies with an error. */ @Test @Documented public void incorrect_authentication() throws PropertyValueException, IOException { // Given startServerWithConfiguredUser(); // Document RESTDocsGenerator.ResponseEntity response = gen.get() .noGraph() .expectedStatus(401) .withHeader(HttpHeaders.AUTHORIZATION, challengeResponse("neo4j", "incorrect")) .expectedHeader("WWW-Authenticate", "None") .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.AuthorizationFailed")); assertThat(firstError.get("message").asText(), equalTo("Invalid username or password.")); } /** * Required password changes * * <p>In some cases, like the very first time Neo4j is accessed, the user will be required to * choose a new password. The database will signal that a new password is required and deny * access. * * <p>See <<rest-api-security-user-status-and-password-changing>> for how to set a new password. */ @Test @Documented public void password_change_required() throws PropertyValueException, IOException { // Given startServer(true); // Document RESTDocsGenerator.ResponseEntity response = gen.get() .noGraph() .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.AuthorizationFailed")); assertThat( firstError.get("message").asText(), equalTo("User is required to change their password.")); assertThat(data.get("password_change").asText(), equalTo(passwordURL("neo4j"))); } /** * When auth is disabled * * <p>When auth has been disabled in the configuration, requests can be sent without an * +Authorization+ header. */ @Test @Documented public void auth_disabled() throws IOException { // Given startServer(false); // Document gen.get().noGraph().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 Authorization 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("webadmin").toString()).status()); assertEquals(200, HTTP.GET(server.baseUri().resolve("browser").toString()).status()); 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':'******', 'password':'******'}")); 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.")); } 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.AuthorizationFailed")); assertThat( response.get("errors").get(0).get("message").asText(), equalTo("No authorization header supplied.")); assertThat(response.header(HttpHeaders.WWW_AUTHENTICATE), equalTo("None")); // 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 Authorization 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.AuthorizationFailed")); assertThat( response.get("errors").get(0).get("message").asText(), equalTo("Invalid username or password.")); assertThat(response.header(HttpHeaders.WWW_AUTHENTICATE), equalTo("None")); // 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':'******'}")); assertEquals(200, post.status()); } public void startServer(boolean authEnabled) throws IOException { new File("neo4j-home/data/dbms/authorization") .delete(); // TODO: Implement a common component for managing Neo4j file structure and use // that here server = CommunityServerBuilder.server() .withProperty( ServerSettings.authorization_enabled.name(), Boolean.toString(authEnabled)) .build(); server.start(); } private String challengeResponse(String username, String password) { return "Basic realm=\"Neo4j\" " + 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 new String(Base64.encode(value), Charset.forName("UTF-8")); } }
public class CypherPluginTest implements GraphHolder { private static ImpermanentGraphDatabase db; public @Rule TestData<Map<String, Node>> data = TestData.producedThrough(GraphDescription.createGraphFor(this, true)); private CypherPlugin plugin; private OutputFormat json; @Before public void setUp() throws Exception { db = new ImpermanentGraphDatabase(); plugin = new CypherPlugin(); json = new OutputFormat(new JsonFormat(), new URI("http://localhost/"), null); } @Test @Graph(value = {"I know you"}) public void runSimpleQuery() throws Exception { Node i = data.get().get("I"); Representation result = testQuery("start n=node(" + i.getId() + ") return n"); assertTrue(json.assemble(result).contains("I")); } @Test @Graph( value = {"I know you", "I know him"}, nodes = { @NODE( name = "you", properties = { @PROP(key = "bool", value = "true", type = GraphDescription.PropType.BOOLEAN), @PROP(key = "name", value = "you"), @PROP(key = "int", value = "1", type = GraphDescription.PropType.INTEGER) }) }) public void checkColumns() throws Exception { Node i = data.get().get("I"); Representation result = testQuery( "start x =node(" + i.getId() + ") match (x) -- (n) return n, n.name?, n.bool?, n.int?"); String formatted = json.assemble(result); System.out.println(formatted); assertTrue(formatted.contains("columns")); assertTrue(formatted.contains("name")); } private Representation testQuery(String query) throws Exception { return plugin.executeScript(db, query, null, null); } @Override public GraphDatabaseService graphdb() { return db; } @BeforeClass public static void startDatabase() { db = new ImpermanentGraphDatabase(); } }
public class AbstractRestFunctionalTestBase extends SharedServerTestBase implements GraphHolder { protected static final String NODES = "http://localhost:7474/db/data/node/"; public @Rule TestData<Map<String, Node>> data = TestData.producedThrough(GraphDescription.createGraphFor(this, true)); public @Rule TestData<RESTDocsGenerator> gen = TestData.producedThrough(RESTDocsGenerator.PRODUCER); @Before public void setUp() { cleanDatabase(); gen().setSection(getDocumentationSectionName()); gen().setGraph(graphdb()); } @SafeVarargs public final String doCypherRestCall( String endpoint, String scriptTemplate, Status status, Pair<String, String>... 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.v2_0.prettifier.Prettifier$.MODULE$.apply(script); gen() .expectedStatus(status.getStatusCode()) .payload(queryString) .description(AsciidocHelper.createAsciiDocSnippet("cypher", snippet)); return gen().post(endpoint).entity(); } protected String formatJavaScript(String script) { script = script.replace(";", "\n"); if (!script.endsWith("\n")) { script += "\n"; } return "_Raw script source_\n\n" + "[source, javascript]\n" + "----\n" + script + "----\n"; } private Long idFor(String name) { return data.get().get(name).getId(); } private String createParameterString(Pair<String, String>[] params) { String paramString = ""; for (Pair<String, String> 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(); } protected 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 Node getNode(String name) { return data.get().get(name); } protected Node[] getNodes(String... names) { Node[] nodes = {}; ArrayList<Node> 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.jsonToSingleValue(entity); assertEquals(expectedSize, hits.size()); } catch (PropertyValueException 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), "UTF-8")); } 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 getSchemaConstraintLabelUniquenessPropertyUri(String label, String property) { return getDataUri() + PATH_SCHEMA_CONSTRAINT + "/" + label + "/uniqueness/" + property; } }
/** The service root is your starting point to discover the REST API. */ @Documented @Test @TestData.Title("Get service root") public void assert200OkFromGet() throws Exception { gen.get().expectedStatus(200).get(functionalTestHelper.dataUri()); }
public class SetNodePropertiesFunctionalTest { private URI propertiesUri; private URI badUri; private static NeoServerWithEmbeddedWebServer server; private static FunctionalTestHelper functionalTestHelper; private static GraphDbHelper helper; @BeforeClass public static void setupServer() throws IOException { server = ServerHelper.createServer(); functionalTestHelper = new FunctionalTestHelper(server); helper = functionalTestHelper.getGraphDbHelper(); } @Before public void setupTheDatabase() throws Exception { ServerHelper.cleanTheDatabase(server); long nodeId = helper.createNode(); propertiesUri = new URI(functionalTestHelper.nodeUri(nodeId) + "/properties"); badUri = new URI(functionalTestHelper.nodeUri(nodeId * 999) + "/properties"); } @AfterClass public static void stopServer() { server.stop(); } public @Rule TestData<RESTDocsGenerator> gen = TestData.producedThrough(RESTDocsGenerator.PRODUCER); /** Update node properties. */ @Documented @Test public void shouldReturn204WhenPropertiesAreUpdated() throws JsonParseException { Map<String, Object> map = new HashMap<String, Object>(); map.put("jim", "tobias"); gen.get() .payload(JsonHelper.createJsonFrom(map)) .expectedStatus(204) .put(propertiesUri.toString()); JaxRsResponse response = updateNodePropertiesOnServer(map); assertEquals(204, response.getStatus()); } @Test public void shouldReturn400WhenSendinIncompatibleJsonProperties() throws JsonParseException { Map<String, Object> map = new HashMap<String, Object>(); map.put("jim", new HashMap<String, Object>()); JaxRsResponse response = updateNodePropertiesOnServer(map); assertEquals(400, response.getStatus()); response.close(); } @Test public void shouldReturn400WhenSendingCorruptJsonProperties() { JaxRsResponse response = RestRequest.req().put(propertiesUri, "this:::Is::notJSON}"); assertEquals(400, response.getStatus()); response.close(); } @Test public void shouldReturn404WhenPropertiesSentToANodeWhichDoesNotExist() throws JsonParseException { Map<String, Object> map = new HashMap<String, Object>(); map.put("jim", "tobias"); JaxRsResponse response = RestRequest.req().put(badUri, JsonHelper.createJsonFrom(map)); assertEquals(404, response.getStatus()); response.close(); } private JaxRsResponse updateNodePropertiesOnServer(Map<String, Object> map) throws JsonParseException { return RestRequest.req().put(propertiesUri, JsonHelper.createJsonFrom(map)); } private URI getPropertyUri(String key) throws Exception { return new URI(propertiesUri.toString() + "/" + key); } /** Set property on node. */ @Documented @Test public void shouldReturn204WhenPropertyIsSet() throws Exception { gen.get() .payload(JsonHelper.createJsonFrom("bar")) .expectedStatus(204) .put(getPropertyUri("foo").toString()); } /** * Property values can not be nested. * * <p>Nesting properties is not supported. You could for example store the nested json as a string * instead. */ @Documented @Test public void shouldReturn400WhenSendinIncompatibleJsonProperty() throws Exception { gen.get() .payload("{\"foo\" : {\"bar\" : \"baz\"}}") .expectedStatus(400) .post(functionalTestHelper.nodeUri()); JaxRsResponse response = setNodePropertyOnServer("jim", new HashMap<String, Object>()); 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 setNodePropertyOnServer(String key, Object value) throws Exception { return RestRequest.req().put(getPropertyUri(key), JsonHelper.createJsonFrom(value)); } }
public class RetrieveRelationshipsFromNodeFunctionalTest { private long nodeWithRelationships; private long nodeWithoutRelationships; private long nonExistingNode; private static NeoServerWithEmbeddedWebServer server; private static FunctionalTestHelper functionalTestHelper; private static GraphDbHelper helper; @BeforeClass public static void setupServer() throws IOException { server = ServerHelper.createServer(); functionalTestHelper = new FunctionalTestHelper(server); helper = functionalTestHelper.getGraphDbHelper(); } @Before public void setupTheDatabase() { ServerHelper.cleanTheDatabase(server); createSimpleGraph(); } private void createSimpleGraph() { nodeWithRelationships = helper.createNode(); helper.createRelationship("LIKES", nodeWithRelationships, helper.createNode()); helper.createRelationship("LIKES", helper.createNode(), nodeWithRelationships); helper.createRelationship("HATES", nodeWithRelationships, helper.createNode()); nodeWithoutRelationships = helper.createNode(); nonExistingNode = nodeWithoutRelationships * 100; } @AfterClass public static void stopServer() { server.stop(); } public @Rule TestData<RESTDocsGenerator> gen = TestData.producedThrough(RESTDocsGenerator.PRODUCER); 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<Map<String, Object>> relreps = JsonHelper.jsonToList(json); assertEquals(expectedSize, relreps.size()); for (Map<String, Object> relrep : relreps) { RelationshipRepresentationTest.verifySerialisation(relrep); } } /** Get all relationships. */ @Documented @Test public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingAllRelationshipsForANode() throws JsonParseException { String entity = gen.get() .expectedStatus(200) .get( functionalTestHelper.nodeUri() + "/" + nodeWithRelationships + "/relationships" + "/all") .entity(); verifyRelReps(3, entity); } /** Get incoming relationships. */ @Documented @Test public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingIncomingRelationshipsForANode() throws JsonParseException { String entity = gen.get() .expectedStatus(200) .get( functionalTestHelper.nodeUri() + "/" + nodeWithRelationships + "/relationships" + "/in") .entity(); verifyRelReps(1, entity); } /** Get outgoing relationships. */ @Documented @Test public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingOutgoingRelationshipsForANode() throws JsonParseException { String entity = gen.get() .expectedStatus(200) .get( functionalTestHelper.nodeUri() + "/" + nodeWithRelationships + "/relationships" + "/out") .entity(); verifyRelReps(2, entity); } /** * Get typed relationships. * * <p>Note that the "+&+" needs to be escaped for example when using http://curl.haxx.se/[cURL] * from the terminal. */ @Documented @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()); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); verifyRelReps(1, response.getEntity(String.class)); response.close(); } @Test public void shouldRespondWith200AndListOfRelationshipRepresentationsWhenGettingOutgoingTypedRelationshipsForANode() throws JsonParseException { JaxRsResponse response = sendRetrieveRequestToServer(nodeWithRelationships, "/out/HATES"); assertEquals(200, response.getStatus()); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); verifyRelReps(1, response.getEntity(String.class)); response.close(); } /** Get relationships on a node without relationships. */ @Documented @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()); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); verifyRelReps(0, response.getEntity(String.class)); response.close(); } @Test public void shouldRespondWith200AndEmptyListOfRelationshipRepresentationsWhenGettingOutgoingRelationshipsForANodeWithoutRelationships() throws JsonParseException { JaxRsResponse response = sendRetrieveRequestToServer(nodeWithoutRelationships, "/out"); assertEquals(200, response.getStatus()); assertEquals(MediaType.APPLICATION_JSON_TYPE, response.getType()); verifyRelReps(0, response.getEntity(String.class)); 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 shouldRespondWith404WhenGettingOutgoingRelationshipsForNonExistingNode() { JaxRsResponse response = sendRetrieveRequestToServer(nonExistingNode, "/out"); assertEquals(404, response.getStatus()); response.close(); } @Test public void shouldGet200WhenRetrievingValidRelationship() throws DatabaseBlockedException { 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(String.class); assertNotNull(entity); isLegalJson(entity); response.close(); } private void isLegalJson(String entity) throws IOException, JsonParseException { JsonHelper.jsonToMap(entity); } }
public abstract class AbstractWebadminTest { public @Rule TestData<JavaTestDocsGenerator> gen = TestData.producedThrough(JavaTestDocsGenerator.PRODUCER); protected static WebadminWebdriverLibrary wl; private static NeoServerWithEmbeddedWebServer server; private static WebDriverFacade webdriverFacade; @BeforeClass public static void setup() throws Exception { server = ServerBuilder.server().build(); server.start(); webdriverFacade = new WebDriverFacade(); wl = new WebadminWebdriverLibrary(webdriverFacade, server.baseUri().toString()); } @After public void doc() { gen.get().document("target/docs", "webadmin"); } protected void captureScreenshot(String string) { WebDriver webDriver = wl.getWebDriver(); if (webDriver instanceof TakesScreenshot) { try { File screenshotFile = ((TakesScreenshot) webDriver).getScreenshotAs(FILE); System.out.println(screenshotFile.getAbsolutePath()); File dir = new File("target/docs/webadmin/images"); dir.mkdirs(); String imageName = string + ".png"; copyFile(screenshotFile, new File(dir, imageName)); gen.get().addImageSnippet(string, imageName, gen.get().getTitle()); } catch (Exception e) { e.printStackTrace(); } } } @Before public void cleanDatabase() { ServerHelper.cleanTheDatabase(server); } @AfterClass public static void tearDown() throws Exception { webdriverFacade.closeBrowser(); server.stop(); } private static void copyFile(File sourceFile, File destFile) throws IOException { if (!destFile.exists()) { destFile.createNewFile(); } FileChannel source = null; FileChannel destination = null; try { source = new FileInputStream(sourceFile).getChannel(); destination = new FileOutputStream(destFile).getChannel(); destination.transferFrom(source, 0, source.size()); } finally { if (source != null) { source.close(); } if (destination != null) { destination.close(); } } } }
@After public void doc() { gen.get().document("target/docs", "webadmin"); }
public class IntroDocTest implements GraphHolder { private static final String DOCS_TARGET = "target/docs/dev/general/"; public @Rule TestData<JavaTestDocsGenerator> gen = TestData.producedThrough(JavaTestDocsGenerator.PRODUCER); public @Rule TestData<Map<String, Node>> data = TestData.producedThrough(GraphDescription.createGraphFor(this, true)); private static ImpermanentGraphDatabase graphdb; private static ExecutionEngine engine; @Test @Graph( value = {"John friend Sara", "John friend Joe", "Sara friend Maria", "Joe friend Steve"}, autoIndexNodes = true) public void intro_examples() throws Exception { try (Transaction ignored = graphdb.beginTx()) { Writer fw = AsciiDocGenerator.getFW(DOCS_TARGET, gen.get().getTitle()); data.get(); fw.append("\nImagine an example graph like the following one:\n\n"); fw.append( AsciiDocGenerator.dumpToSeparateFileWithType( new File(DOCS_TARGET), "intro.graph", AsciidocHelper.createGraphVizWithNodeId("Example Graph", graphdb(), "cypher-intro"))); fw.append( "\nFor example, here is a query which finds a user called John and John's friends (though not " + "his direct friends) before returning both John and any friends-of-friends that are found."); fw.append("\n\n"); String query = "MATCH (john {name: 'John'})-[:friend]->()-[:friend]->(fof) RETURN john, fof "; fw.append( AsciiDocGenerator.dumpToSeparateFileWithType( new File(DOCS_TARGET), "intro.query", createCypherSnippet(query))); fw.append("\nResulting in:\n\n"); fw.append( AsciiDocGenerator.dumpToSeparateFileWithType( new File(DOCS_TARGET), "intro.result", createQueryResultSnippet(engine.execute(query).dumpToString()))); fw.append( "\nNext up we will add filtering to set more parts " + "in motion:\n\nWe take a list of user names " + "and find all nodes with names from this list, match their friends and return " + "only those followed users who have a +name+ property starting with +S+."); query = "MATCH (user)-[:friend]->(follower) WHERE " + "user.name IN ['Joe', 'John', 'Sara', 'Maria', 'Steve'] AND follower.name =~ 'S.*' " + "RETURN user, follower.name "; fw.append("\n\n"); fw.append( AsciiDocGenerator.dumpToSeparateFileWithType( new File(DOCS_TARGET), "intro.query", createCypherSnippet(query))); fw.append("\nResulting in:\n\n"); fw.append( AsciiDocGenerator.dumpToSeparateFileWithType( new File(DOCS_TARGET), "intro.result", createQueryResultSnippet(engine.execute(query).dumpToString()))); fw.close(); } } @BeforeClass public static void setup() throws IOException { graphdb = (ImpermanentGraphDatabase) new TestGraphDatabaseFactory().newImpermanentDatabase(); graphdb.cleanContent(); engine = new ExecutionEngine(graphdb); } @AfterClass public static void shutdown() { try { if (graphdb != null) graphdb.shutdown(); } finally { graphdb = null; } } @Override public GraphDatabaseService graphdb() { return graphdb; } }
public class GetOnRootFunctionalTest { private static NeoServerWithEmbeddedWebServer server; private static FunctionalTestHelper functionalTestHelper; @BeforeClass public static void setupServer() throws IOException { server = ServerHelper.createServer(); functionalTestHelper = new FunctionalTestHelper(server); } @Before public void cleanTheDatabase() { ServerHelper.cleanTheDatabase(server); } @AfterClass public static void stopServer() { server.stop(); } public @Rule TestData<DocsGenerator> gen = TestData.producedThrough(DocsGenerator.PRODUCER); /** The service root is your starting point to discover the REST API. */ @Documented @Test @TestData.Title("Get service root") public void assert200OkFromGet() throws Exception { gen.get().expectedStatus(200).get(functionalTestHelper.dataUri()); } @Test public void assertResponseHaveCorrectContentFromGet() throws Exception { JaxRsResponse response = RestRequest.req().get(functionalTestHelper.dataUri()); String body = response.getEntity(String.class); Map<String, Object> map = JsonHelper.jsonToMap(body); assertEquals(functionalTestHelper.nodeUri(), map.get("node")); assertNotNull(map.get("reference_node")); assertNotNull(map.get("node_index")); assertNotNull(map.get("relationship_index")); assertNotNull(map.get("extensions_info")); assertNotNull(map.get("batch")); response.close(); // Make sure advertised urls work response = RestRequest.req().get((String) map.get("reference_node")); assertEquals(200, response.getStatus()); response.close(); response = RestRequest.req().get((String) map.get("node_index")); assertEquals(204, response.getStatus()); response.close(); response = RestRequest.req().get((String) map.get("relationship_index")); assertEquals(204, response.getStatus()); 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(); } }