Example #1
0
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 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 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);
  }
}
Example #4
0
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 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();
      }
    }
  }
}
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 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;
  }
}
Example #8
0
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 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();
  }
}