Authored by Gino Zhang

搜索API测试和markdown说明生成

... ... @@ -51,4 +51,13 @@ public class ApiDefUtils {
return enums;
}
public static Map<String, Object> getRequestDef(String api)
{
return (Map<String, Object>)(getApiDef(api).get("_request"));
}
public static Map<String, Object> getResponseDef(String api)
{
return (Map<String, Object>)(getApiDef(api).get("_response"));
}
}
... ...
... ... @@ -4,29 +4,59 @@ import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.junit.Assert;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
/**
* Created by ginozhang on 2016/11/15.
*/
public class CheckStructureUtils {
public static void checkRequest(String api, String url)
{
// TODO:
private static final List<String> BASIC_TYPES = Arrays.asList("int", "long", "double", "string");
private static final List<String> COMPLEX_TYPES = Arrays.asList("array", "object", "enum_*", "connected_enum_*", "connected_<BASIC>", "ranged_int", "ranged_double", "optional(*), date(*)");
public static void checkRequest(String api, String url) {
// 1. 获取参数列表
Map<String, String> map = getParamMap(url);
// 2. 获取接口请求定义
Map<String, Object> requestDef = ApiDefUtils.getRequestDef(api);
// 3. 校验请求参数
for (Map.Entry<String, String> entry : map.entrySet()) {
checkSingleFiled(api, entry.getKey(), entry.getValue(), (Map<String, String>) requestDef.get(entry.getKey()));
}
}
private static Map<String, String> getParamMap(String api) {
Map<String, String> map = new LinkedHashMap<>();
String[] parts = api.split("\\?");
if (parts != null && parts.length == 2) {
String paramStr = parts[1];
String[] paramPairs = paramStr.split("\\&");
for (String paramItem : paramPairs) {
String[] keyValueArray = paramItem.split("=");
if (keyValueArray != null && keyValueArray.length == 2) {
map.put(keyValueArray[0], keyValueArray[1]);
}
}
}
System.out.println("[INFO]Get request url param map: " + map);
return map;
}
public static void main(String[] args) {
System.out.println(getParamMap("http://192.168.102.216:8080/yohosearch/productindex/productList?query=vans&brand=144&gender=2,3"));
}
public static void check(Map<String, Object> apiDef, JSONObject response) {
public static void check(String api, JSONObject response) {
Assert.assertTrue("The response should contain data.", response.containsKey("data") && response.get("data") != null);
Map<String, Object> responseDef = (Map<String, Object>) apiDef.get("_response");
Map<String, List<String>> enums = (Map<String, List<String>>) apiDef.get("_enums");
doCheck(responseDef, response, enums);
Map<String, Object> responseDef = ApiDefUtils.getResponseDef(api);
doCheck(api, responseDef, response);
}
private static void doCheck(Map<String, Object> responseDef, JSONObject response, Map<String, List<String>> enums) {
private static void doCheck(String api, Map<String, Object> responseDef, JSONObject response) {
if (responseDef == null || response.isEmpty()) {
return;
}
... ... @@ -41,77 +71,88 @@ public class CheckStructureUtils {
boolean required = pDef.containsKey("_required") ? (Boolean) pDef.get("_required") : false;
String type = pDef.containsKey("_type") ? (String) pDef.get("_type") : "string";
if (pResponse == null || pResponse.toString() == null || pResponse.toString().isEmpty())
{
if(required)
{
if (pResponse == null || pResponse.toString() == null || pResponse.toString().isEmpty()) {
if (required) {
Assert.fail("The property " + property + " is required!");
}
else
{
} else {
continue;
}
}
if ("object".equalsIgnoreCase(type)) {
doCheck((Map<String, Object>)pDef.get("_content"), (JSONObject) pResponse, enums);
continue;
}
else if ("array".equalsIgnoreCase(type)) {
doCheck(api, (Map<String, Object>) pDef.get("_content"), (JSONObject) pResponse);
} else if ("array".equalsIgnoreCase(type)) {
JSONArray jsonArray = (JSONArray) pResponse;
for(int i = 0; i < jsonArray.size(); i++)
{
doCheck((Map<String, Object>)pDef.get("_content"), jsonArray.getJSONObject(i), enums);
for (int i = 0; i < jsonArray.size(); i++) {
doCheck(api, (Map<String, Object>) pDef.get("_content"), jsonArray.getJSONObject(i));
}
continue;
} else {
checkSingleFiled(api, property, pResponse.toString(), (Map<String, String>) entry.getValue());
}
// 简单类型的处理 主要是类型的判断
String sValue = pResponse.toString();
if("int".equalsIgnoreCase(type) || "long".equalsIgnoreCase(type))
{
Integer.valueOf(sValue);
}
else if("long".equalsIgnoreCase(type))
{
Long.valueOf(sValue);
// 检查是否有字段未定义
Set<String> defKeys = responseDef.keySet();
Set<String> resKeys = response.keySet();
if (!defKeys.containsAll(resKeys)) {
resKeys.removeAll(defKeys);
System.out.println("[WARN]NOT SAME KEYS. There has new propertys: " + resKeys);
} else if (!resKeys.containsAll(defKeys)) {
defKeys.removeAll(resKeys);
System.out.println("[WARN]NOT SAME KEYS. There has propertys not set value: " + defKeys);
}
else if("double".equalsIgnoreCase(type))
{
Double.valueOf(sValue);
}
else if(type.startsWith("enum_"))
{
private static void checkSingleFiled(String api, String fieldName, String sValue, Map<String, String> fieldDef) {
System.out.println("[INFO]Begin to check field " + fieldName);
Assert.assertNotNull("field not defined for " + fieldName, fieldDef);
String type = fieldDef.containsKey("_type") ? (String) fieldDef.get("_type") : "string";
if (BASIC_TYPES.contains(type)) {
checkBasicType(type, sValue);
} else if (type.startsWith("optional")) {
// skip to check
} else if (type.startsWith("date")) {
// skip to check
} else if ("ranged_int".equals(type)) {
String[] limits = sValue.split(",");
Assert.assertTrue("The value for " + fieldName + " should contains comma", limits != null && limits.length == 2);
Assert.assertTrue("The upper limit should larger than the lower limit", Integer.valueOf(limits[1]) >= Integer.valueOf(limits[0]));
} else if ("ranged_double".equals(type)) {
String[] limits = sValue.split(",");
Assert.assertTrue("The value for " + fieldName + " should contains comma", limits != null && limits.length == 2);
Assert.assertTrue("The upper limit should larger than the lower limit", Double.valueOf(limits[1]) >= Double.valueOf(limits[0]));
} else if (type.startsWith("enum_")) {
// 校验枚举类型
String enumKey = type.substring("enum_".length());
Assert.assertTrue("cannot find enum "+enumKey, enums.containsKey(enumKey));
Assert.assertTrue("Invalid value "+sValue+" for enum "+enumKey, enums.get(enumKey).contains(sValue));
}
else if(type.startsWith("connected_enum_"))
{
Assert.assertTrue("Invalid value " + sValue + " for enum " + enumKey, ApiDefUtils.getEnumItems(api, enumKey).contains(sValue));
} else if (type.startsWith("connected_enum_")) {
// 校验枚举类型
String enumKey = type.substring("connected_enum_".length());
Assert.assertTrue("cannot find enum "+enumKey, enums.containsKey(enumKey));
Assert.assertTrue("Invalid value "+sValue+" for enum "+enumKey, enums.get(enumKey).containsAll(Arrays.asList(sValue.split(","))));
Assert.assertTrue("Invalid value " + sValue + " for enum " + enumKey, ApiDefUtils.getEnumItems(api, enumKey).containsAll(Arrays.asList(sValue.split(","))));
} else if (type.startsWith("connected_")) {
String type4Connected = type.substring("connected_".length());
for (String item : sValue.split(",")) {
checkBasicType(type4Connected, item);
}
else if(!"string".equalsIgnoreCase(type))
{
Assert.fail("Unknown property type "+type);
} else {
Assert.fail("Unknown property type " + type + " for property " + fieldName);
}
}
// 检查是否有字段未定义
Set<String> defKeys = responseDef.keySet();
Set<String> resKeys = response.keySet();
if(!defKeys.containsAll(resKeys))
{
resKeys.removeAll(defKeys);
System.out.println("[WARN]NOT SAME KEYS. There has new propertys: "+resKeys);
}
else if(!resKeys.containsAll(defKeys))
{
defKeys.removeAll(resKeys);
System.out.println("[WARN]NOT SAME KEYS. There has propertys not set value: "+defKeys);
private static void checkBasicType(String type, String sValue) {
switch (type) {
case "int":
Integer.valueOf(sValue);
break;
case "long":
Long.valueOf(sValue);
break;
case "double":
Double.valueOf(sValue);
break;
default:
break;
}
}
... ...
... ... @@ -49,7 +49,7 @@ public class MarkDownUtils {
// 3. 生成响应参数说明
sb.append(generateResponseMdTable(apiDef));
FileUtils.writeFile("test.md", sb.toString());
FileUtils.writeFile(api.replace("/", "_") + ".md", sb.toString());
}
private StringBuffer generateResponseMdTable(Map<String, Object> apiDef) {
... ... @@ -129,8 +129,7 @@ public class MarkDownUtils {
return "特定值{" + specialValue + "}或不传值";
}
StringBuffer sb = new StringBuffer(type);
return sb.toString();
return type;
}
}
... ...
... ... @@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.yoho.search.base.utils.HttpClientUtils;
import com.yoho.search.service.TestConstants;
import org.apache.commons.lang3.StringUtils;
import org.junit.Assert;
import org.junit.Test;
import org.springframework.web.bind.annotation.RequestMapping;
... ... @@ -34,10 +35,11 @@ public class SearchNewControllerTest {
appendParams.put("/group_shops", "shop=1");
appendParams.put("/suggest", "query=vans");
appendParams.put("/shops", "keyword=vans");
appendParams.put("/productindex/productList", "query=vans&brand=144&gender=2,3&attribute_not=2&status=1");
}
@Test
public void testAPI() {
public void testAPIs() {
SearchNewController c = new SearchNewController();
Method[] methods = c.getClass().getMethods();
if (methods == null || methods.length == 0) {
... ... @@ -61,6 +63,29 @@ public class SearchNewControllerTest {
}
@Test
public void generateAPIMd() {
SearchNewController c = new SearchNewController();
Method[] methods = c.getClass().getMethods();
if (methods == null || methods.length == 0) {
return;
}
for (Method m : methods) {
if (!m.isAnnotationPresent(RequestMapping.class)) {
continue;
}
String api = m.getAnnotation(RequestMapping.class).value()[0];
if (skippedApis.contains(api)) {
continue;
}
// 生成markdown接口说明文件
MarkDownUtils.getInstance(api).generateApiMd();
}
}
@Test
public void testShops() {
testSingle("/shops");
}
... ... @@ -71,9 +96,13 @@ public class SearchNewControllerTest {
}
private void testSingle(String api) {
testSingle(api, appendParams.get(api));
}
private void testSingle(String api, String paramStr) {
String url = urlBase + api;
if (appendParams.containsKey(api)) {
url = url + "?" + appendParams.get(api);
if (StringUtils.isNotEmpty(paramStr)) {
url = url + "?" + paramStr;
}
CheckStructureUtils.checkRequest(api, url);
... ... @@ -85,10 +114,6 @@ public class SearchNewControllerTest {
Assert.assertEquals(api, "200", jsonObject.getString("code"));
// 检查响应结果是否OK
Map<String, Object> def = ApiDefUtils.getApiDef(api);
CheckStructureUtils.check(def, jsonObject);
// 生成markdown接口说明文件
MarkDownUtils.getInstance(api).generateApiMd();
CheckStructureUtils.check(api, jsonObject);
}
}
... ...
... ... @@ -94,7 +94,7 @@ _request:
attribute:
_desc: '包含指定的商品属性(1:正常商品,2:赠品)'
_type: 'enum_Attribute'
attribute:
attribute_not:
_desc: '过滤指定的商品属性(1:正常商品,2:赠品)'
_type: 'enum_Attribute'
limited:
... ... @@ -252,8 +252,8 @@ _response:
_desc: '商品状态'
_type: 'enum_Status'
is_promotion:
_desc: 'TODO'
_type: 'enum_Status'
_desc: '优惠码'
_type: 'int'
is_student_price:
_desc: '是否有学生优惠'
_type: 'enum_YesOrNo'
... ...
_name: '根据关键词搜索品牌店铺接口。'
_request:
keyword:
_desc: '搜索店铺的关键词,不能为空'
... ...