blob: 24b19ba1ea800271002ad3c3491aad5b7656737d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package org.apache.hugegraph.api;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.http.util.TextUtils;
import org.apache.hugegraph.HugeException;
import org.apache.hugegraph.util.CollectionUtil;
import org.apache.hugegraph.util.JsonUtil;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import org.glassfish.jersey.client.filter.EncodingFilter;
import org.glassfish.jersey.message.GZipEncoder;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Multimap;
import jakarta.ws.rs.client.Client;
import jakarta.ws.rs.client.ClientBuilder;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.MultivaluedMap;
import jakarta.ws.rs.core.Response;
public class BaseApiTest {
private static final String BASE_URL = "http://127.0.0.1:8080";
private static final String GRAPH = "hugegraph";
private static final String USERNAME = "admin";
private static final String PASSWORD = "pa";
protected static final String URL_PREFIX = "graphs/" + GRAPH;
private static final String SCHEMA_PKS = "/schema/propertykeys";
private static final String SCHEMA_VLS = "/schema/vertexlabels";
private static final String SCHEMA_ELS = "/schema/edgelabels";
private static final String SCHEMA_ILS = "/schema/indexlabels";
private static final String GRAPH_VERTEX = "/graph/vertices";
private static final String GRAPH_EDGE = "/graph/edges";
private static final String BATCH = "/batch";
protected static final String TRAVERSERS_API = URL_PREFIX + "/traversers";
private static RestClient client;
private static final ObjectMapper MAPPER = new ObjectMapper();
@BeforeClass
public static void init() {
client = newClient();
BaseApiTest.clearData();
}
@AfterClass
public static void clear() throws Exception {
client.close();
}
@After
public void teardown() throws Exception {
BaseApiTest.clearData();
}
public RestClient client() {
return client;
}
public static RestClient newClient() {
return new RestClient(BASE_URL);
}
public static class RestClient {
private Client client;
private WebTarget target;
public RestClient(String url) {
this(url, true);
}
public RestClient(String url,Boolean enableAuth) {
this.client = ClientBuilder.newClient();
this.client.register(EncodingFilter.class);
this.client.register(GZipEncoder.class);
if(enableAuth) {
this.client.register(HttpAuthenticationFeature.basic(USERNAME,
PASSWORD));
}
this.target = this.client.target(url);
}
public void close() {
this.client.close();
}
public WebTarget target() {
return this.target;
}
public WebTarget target(String url) {
return this.client.target(url);
}
public Response get(String path) {
return this.target.path(path).request().get();
}
public Response get(String path, String id) {
return this.target.path(path).path(id).request().get();
}
public Response get(String path,
MultivaluedMap<String, Object> headers) {
return this.target.path(path).request().headers(headers).get();
}
public Response get(String path, Multimap<String, Object> params) {
WebTarget target = this.target.path(path);
for (Map.Entry<String, Object> entries : params.entries()) {
target = target.queryParam(entries.getKey(), entries.getValue());
}
return target.request().get();
}
public Response get(String path, Map<String, Object> params) {
WebTarget target = this.target.path(path);
for (Map.Entry<String, Object> i : params.entrySet()) {
target = target.queryParam(i.getKey(), i.getValue());
}
return target.request().get();
}
public Response post(String path, String content) {
return this.post(path, Entity.json(content));
}
public Response post(String path, Entity<?> entity) {
return this.target.path(path).request().post(entity);
}
public Response put(String path, String id, String content,
Map<String, Object> params) {
WebTarget target = this.target.path(path).path(id);
for (Map.Entry<String, Object> i : params.entrySet()) {
target = target.queryParam(i.getKey(), i.getValue());
}
return target.request().put(Entity.json(content));
}
public Response delete(String path, String id) {
return this.target.path(path).path(id).request().delete();
}
public Response delete(String path, Map<String, Object> params) {
WebTarget target = this.target.path(path);
for (Map.Entry<String, Object> i : params.entrySet()) {
target = target.queryParam(i.getKey(), i.getValue());
}
return target.request().delete();
}
public Response delete(String path,
MultivaluedMap<String, Object> headers) {
WebTarget target = this.target.path(path);
return target.request().headers(headers).delete();
}
}
/**
* Utils method to init some properties
*/
protected static void initPropertyKey() {
String path = URL_PREFIX + SCHEMA_PKS;
createAndAssert(path, "{\n" +
"\"name\": \"name\",\n" +
"\"data_type\": \"TEXT\",\n" +
"\"cardinality\": \"SINGLE\",\n" +
"\"check_exist\": false,\n" +
"\"properties\":[]\n" +
"}", 202);
createAndAssert(path, "{\n" +
"\"name\": \"age\",\n" +
"\"data_type\": \"INT\",\n" +
"\"cardinality\": \"SINGLE\",\n" +
"\"check_exist\": false,\n" +
"\"properties\":[]\n" +
"}", 202);
createAndAssert(path, "{\n" +
"\"name\": \"city\",\n" +
"\"data_type\": \"TEXT\",\n" +
"\"cardinality\": \"SINGLE\",\n" +
"\"check_exist\": false,\n" +
"\"properties\":[]\n" +
"}", 202);
createAndAssert(path, "{\n" +
"\"name\": \"lang\",\n" +
"\"data_type\": \"TEXT\",\n" +
"\"cardinality\": \"SINGLE\",\n" +
"\"check_exist\": false,\n" +
"\"properties\":[]\n" +
"}", 202);
createAndAssert(path, "{\n" +
"\"name\": \"date\",\n" +
"\"data_type\": \"TEXT\",\n" +
"\"cardinality\": \"SINGLE\",\n" +
"\"check_exist\": false,\n" +
"\"properties\":[]\n" +
"}", 202);
createAndAssert(path, "{\n" +
"\"name\": \"price\",\n" +
"\"data_type\": \"INT\",\n" +
"\"cardinality\": \"SINGLE\",\n" +
"\"check_exist\": false,\n" +
"\"properties\":[]\n" +
"}", 202);
createAndAssert(path, "{\n" +
"\"name\": \"weight\",\n" +
"\"data_type\": \"DOUBLE\",\n" +
"\"cardinality\": \"SINGLE\",\n" +
"\"check_exist\": false,\n" +
"\"properties\":[]\n" +
"}", 202);
}
protected static void initVertexLabel() {
String path = URL_PREFIX + SCHEMA_VLS;
createAndAssert(path, "{\n" +
"\"primary_keys\":[\"name\"],\n" +
"\"id_strategy\": \"PRIMARY_KEY\",\n" +
"\"name\": \"person\",\n" +
"\"properties\":[\"city\", \"name\", \"age\"],\n" +
"\"check_exist\": false,\n" +
"\"nullable_keys\":[]\n" +
"}");
createAndAssert(path, "{\n" +
"\"primary_keys\":[\"name\"],\n" +
"\"id_strategy\": \"PRIMARY_KEY\",\n" +
"\"name\": \"software\",\n" +
"\"properties\":[\"price\", \"name\", \"lang\"],\n" +
"\"check_exist\": false,\n" +
"\"nullable_keys\":[]\n" +
"}");
}
protected static void initEdgeLabel() {
String path = URL_PREFIX + SCHEMA_ELS;
createAndAssert(path, "{\n" +
"\"name\": \"created\",\n" +
"\"source_label\": \"person\",\n" +
"\"target_label\": \"software\",\n" +
"\"frequency\": \"SINGLE\",\n" +
"\"properties\":[\"date\", \"weight\"],\n" +
"\"sort_keys\":[],\n" +
"\"check_exist\": false,\n" +
"\"nullable_keys\":[]\n" +
"}");
createAndAssert(path, "{\n" +
"\"name\": \"knows\",\n" +
"\"source_label\": \"person\",\n" +
"\"target_label\": \"person\",\n" +
"\"frequency\": \"MULTIPLE\",\n" +
"\"properties\":[\"date\", \"weight\"],\n" +
"\"sort_keys\":[\"date\"],\n" +
"\"check_exist\": false,\n" +
"\"nullable_keys\":[]\n" +
"}");
}
protected static int initIndexLabel() {
String path = URL_PREFIX + SCHEMA_ILS;
Response r = client.post(path, "{\n" +
"\"name\": \"personByCity\",\n" +
"\"base_type\": \"VERTEX_LABEL\",\n" +
"\"base_value\": \"person\",\n" +
"\"index_type\": \"SECONDARY\",\n" +
"\"check_exist\": false,\n" +
"\"rebuild\": false,\n" +
"\"fields\": [\n" +
"\"city\"\n" +
"]\n" +
"}");
String content = assertResponseStatus(202, r);
return assertJsonContains(content, "task_id");
}
protected static void initEdge() {
String path = URL_PREFIX + GRAPH_EDGE + BATCH;
Map<String, String> ret = listAllVertexName2Ids();
String markoId = ret.get("marko");
String peterId = ret.get("peter");
String joshId = ret.get("josh");
String vadasId = ret.get("vadas");
String rippleId = ret.get("ripple");
String body = String.format("[{" +
"\"label\": \"knows\"," +
"\"outV\": \"%s\"," +
"\"inV\": \"%s\"," +
"\"outVLabel\": \"person\"," +
"\"inVLabel\": \"person\"," +
"\"properties\": {" +
" \"date\": \"2021-01-01\"," +
" \"weight\":0.5}},{" +
"\"label\": \"knows\"," +
"\"outV\": \"%s\"," +
"\"inV\": \"%s\"," +
"\"outVLabel\": \"person\"," +
"\"inVLabel\": \"person\"," +
"\"properties\": {" +
" \"date\": \"2021-01-01\"," +
" \"weight\":0.4}},{" +
"\"label\": \"knows\"," +
"\"outV\": \"%s\"," +
"\"inV\": \"%s\"," +
"\"outVLabel\": \"person\"," +
"\"inVLabel\": \"person\"," +
"\"properties\": {" +
" \"date\": \"2021-01-01\"," +
" \"weight\":0.3}},{" +
"\"label\": \"created\"," +
"\"outV\": \"%s\"," +
"\"inV\": \"%s\"," +
"\"outVLabel\": \"person\"," +
"\"inVLabel\": \"software\"," +
"\"properties\": {" +
" \"date\": \"2021-01-01\"," +
" \"weight\":0.2}" +
"},{" +
"\"label\": \"created\"," +
"\"outV\": \"%s\"," +
"\"inV\": \"%s\"," +
"\"outVLabel\": \"person\"," +
"\"inVLabel\": \"software\"," +
"\"properties\": {" +
" \"date\": \"2021-01-01\"," +
" \"weight\":0.1}}]",
markoId, peterId, peterId, joshId,
joshId, vadasId, markoId, rippleId,
peterId, rippleId);
createAndAssert(path, body);
}
protected static void initVertex() {
String path = URL_PREFIX + GRAPH_VERTEX;
createAndAssert(path, "{\n" +
"\"label\": \"person\",\n" +
"\"type\": \"vertex\",\n" +
"\"properties\":{" +
"\"name\": \"marko\"," +
"\"age\": 29," +
"\"city\": \"Beijing\"" +
"}\n" +
"}");
createAndAssert(path, "{\n" +
"\"label\": \"person\",\n" +
"\"type\": \"vertex\",\n" +
"\"properties\":{" +
"\"name\": \"vadas\"," +
"\"age\": 27," +
"\"city\": \"HongKong\"" +
"}\n" +
"}");
createAndAssert(path, "{\n" +
"\"label\": \"person\",\n" +
"\"type\": \"vertex\",\n" +
"\"properties\":{" +
"\"name\": \"josh\"," +
"\"age\": 32," +
"\"city\": \"Beijing\"" +
"}\n" +
"}");
createAndAssert(path, "{\n" +
"\"label\": \"person\",\n" +
"\"type\": \"vertex\",\n" +
"\"properties\":{" +
"\"name\": \"peter\"," +
"\"age\": 35," +
"\"city\": \"Shanghai\"" +
"}\n" +
"}");
createAndAssert(path, "{\n" +
"\"label\": \"software\",\n" +
"\"type\": \"vertex\",\n" +
"\"properties\":{" +
"\"name\": \"ripple\"," +
"\"lang\": \"java\"," +
"\"price\": 199" +
"}\n" +
"}");
createAndAssert(path, "{\n" +
"\"label\": \"software\",\n" +
"\"type\": \"vertex\",\n" +
"\"properties\":{" +
"\"name\": \"lop\"," +
"\"lang\": \"java\"," +
"\"price\": 328" +
"}\n" +
"}");
}
protected static Response createAndAssert(String path, String body) {
return createAndAssert(path, body, 201);
}
protected static Response createAndAssert(String path, String body,
int status) {
Response r = client.post(path, body);
assertResponseStatus(status, r);
return r;
}
@SuppressWarnings("rawtypes")
protected static Map<String, String> listAllVertexName2Ids() {
Response r = client.get(URL_PREFIX + GRAPH_VERTEX);
String content = assertResponseStatus(200, r);
List<Map> vertices = readList(content, "vertices", Map.class);
Map<String, String> vertexName2Ids = new HashMap<>();
for (Map vertex : vertices) {
Map properties = (Map) vertex.get("properties");
if (properties == null ||
!properties.containsKey("name") ||
!vertex.containsKey("id")) {
continue;
}
String name = (String) properties.get("name");
if (TextUtils.isEmpty(name)) {
continue;
}
String id = (String) vertex.get("id");
if (TextUtils.isEmpty(id)) {
continue;
}
vertexName2Ids.put(name, id);
}
return vertexName2Ids;
}
protected static String id2Json(String params) {
return String.format("\"%s\"", params);
}
protected static String getVertexId(String label, String key, String value)
throws IOException {
String props = MAPPER.writeValueAsString(ImmutableMap.of(key, value));
Map<String, Object> params = ImmutableMap.of(
"label", label,
"properties", URLEncoder.encode(props, "UTF-8")
);
Response r = client.get(URL_PREFIX + GRAPH_VERTEX, params);
String content = assertResponseStatus(200, r);
@SuppressWarnings("rawtypes")
List<Map> list = readList(content, "vertices", Map.class);
if (list.size() != 1) {
throw new HugeException("Failed to get vertex id: %s", content);
}
return (String) list.get(0).get("id");
}
protected static void clearGraph() {
Consumer<String> consumer = (urlSuffix) -> {
String path = URL_PREFIX + urlSuffix;
String type = urlSuffix.substring(urlSuffix.lastIndexOf('/') + 1);
Response r = client.get(path);
String content = assertResponseStatus(200, r);
@SuppressWarnings("rawtypes")
List<Map> list = readList(content, type, Map.class);
List<Object> ids = list.stream().map(e -> e.get("id"))
.collect(Collectors.toList());
ids.forEach(id -> {
client.delete(path, (String) id);
});
};
consumer.accept(GRAPH_EDGE);
consumer.accept(GRAPH_VERTEX);
}
protected static void clearSchema() {
Consumer<String> consumer = (urlSuffix) -> {
String path = URL_PREFIX + urlSuffix;
String type = urlSuffix.substring(urlSuffix.lastIndexOf('/') + 1);
Response r = client.get(path);
String content = assertResponseStatus(200, r);
@SuppressWarnings("rawtypes")
List<Map> list = readList(content, type, Map.class);
List<Object> names = list.stream().map(e -> e.get("name"))
.collect(Collectors.toList());
Assert.assertTrue("Expect all names are unique: " + names,
CollectionUtil.allUnique(names));
Set<Integer> tasks = new HashSet<>();
names.forEach(name -> {
Response response = client.delete(path, (String) name);
if (urlSuffix.equals(SCHEMA_PKS)) {
return;
}
String result = assertResponseStatus(202, response);
tasks.add(assertJsonContains(result, "task_id"));
});
for (Integer task : tasks) {
waitTaskSuccess(task);
}
};
consumer.accept(SCHEMA_ILS);
consumer.accept(SCHEMA_ELS);
consumer.accept(SCHEMA_VLS);
consumer.accept(SCHEMA_PKS);
}
protected static void waitTaskSuccess(int task) {
waitTaskStatus(task, ImmutableSet.of("success"));
}
protected static void waitTaskCompleted(int task) {
Set<String> completed = ImmutableSet.of("success",
"cancelled",
"failed");
waitTaskStatus(task, completed);
}
protected static void waitTaskStatus(int task, Set<String> expectedStatus) {
String status;
int times = 0;
int maxTimes = 100000;
do {
Response r = client.get("/graphs/hugegraph/tasks/",
String.valueOf(task));
String content = assertResponseStatus(200, r);
status = assertJsonContains(content, "task_status");
if (times++ > maxTimes) {
Assert.fail(String.format("Failed to wait for task %s " +
"due to timeout", task));
}
} while (!expectedStatus.contains(status));
}
protected static String parseId(String content) throws IOException {
Map<?, ?> map = MAPPER.readValue(content, Map.class);
return (String) map.get("id");
}
protected static <T> List<T> readList(String content,
String key,
Class<T> clazz) {
try {
JsonNode root = MAPPER.readTree(content);
JsonNode element = root.get(key);
if (element == null) {
throw new HugeException(String.format(
"Can't find value of the key: %s in json.", key));
}
JavaType type = MAPPER.getTypeFactory()
.constructParametricType(List.class, clazz);
return MAPPER.readValue(element.toString(), type);
} catch (IOException e) {
throw new HugeException(String.format(
"Failed to deserialize %s", content), e);
}
}
protected static void clearData() {
clearGraph();
clearSchema();
}
protected static void truncate() {
String token = "162f7848-0b6d-4faf-b557-3a0797869c55";
String message = "I'm sure to delete all data";
Map<String, Object> param = ImmutableMap.of("token", token,
"confirm_message", message);
client.delete("graphs/" + GRAPH + "/clear", param);
}
protected static String assertResponseStatus(int status,
Response response) {
String content = response.readEntity(String.class);
String message = String.format("Response with status %s and content %s",
response.getStatus(), content);
Assert.assertEquals(message, status, response.getStatus());
return content;
}
public static <T> T assertJsonContains(String response, String key) {
Map<?, ?> json = JsonUtil.fromJson(response, Map.class);
return assertMapContains(json, key);
}
@SuppressWarnings("unchecked")
public static <T> T assertMapContains(Map<?, ?> map, String key) {
String message = String.format("Expect contains key '%s' in %s",
key, map);
Assert.assertTrue(message, map.containsKey(key));
return (T) map.get(key);
}
public static Map<?, ?> assertArrayContains(List<Map<?, ?>> list,
String key, Object value) {
String message = String.format("Expect contains {'%s':'%s'} in list %s",
key, value, list);
Map<?, ?> found = null;
for (Map<?, ?> map : list) {
if (map.get(key).equals(value)) {
found = map;
break;
}
}
Assert.assertNotNull(message, found);
return found;
}
}