Authored by Gino Zhang

搜索API测试和markdown说明生成

@@ -51,4 +51,13 @@ public class ApiDefUtils { @@ -51,4 +51,13 @@ public class ApiDefUtils {
51 return enums; 51 return enums;
52 } 52 }
53 53
  54 + public static Map<String, Object> getRequestDef(String api)
  55 + {
  56 + return (Map<String, Object>)(getApiDef(api).get("_request"));
  57 + }
  58 +
  59 + public static Map<String, Object> getResponseDef(String api)
  60 + {
  61 + return (Map<String, Object>)(getApiDef(api).get("_response"));
  62 + }
54 } 63 }
@@ -4,29 +4,59 @@ import com.alibaba.fastjson.JSONArray; @@ -4,29 +4,59 @@ import com.alibaba.fastjson.JSONArray;
4 import com.alibaba.fastjson.JSONObject; 4 import com.alibaba.fastjson.JSONObject;
5 import org.junit.Assert; 5 import org.junit.Assert;
6 6
7 -import java.util.Arrays;  
8 -import java.util.List;  
9 -import java.util.Map;  
10 -import java.util.Set; 7 +import java.util.*;
11 8
12 /** 9 /**
13 * Created by ginozhang on 2016/11/15. 10 * Created by ginozhang on 2016/11/15.
14 */ 11 */
15 public class CheckStructureUtils { 12 public class CheckStructureUtils {
16 13
17 - public static void checkRequest(String api, String url)  
18 - {  
19 - // TODO: 14 + private static final List<String> BASIC_TYPES = Arrays.asList("int", "long", "double", "string");
  15 +
  16 + private static final List<String> COMPLEX_TYPES = Arrays.asList("array", "object", "enum_*", "connected_enum_*", "connected_<BASIC>", "ranged_int", "ranged_double", "optional(*), date(*)");
  17 +
  18 + public static void checkRequest(String api, String url) {
  19 + // 1. 获取参数列表
  20 + Map<String, String> map = getParamMap(url);
  21 +
  22 + // 2. 获取接口请求定义
  23 + Map<String, Object> requestDef = ApiDefUtils.getRequestDef(api);
  24 +
  25 + // 3. 校验请求参数
  26 + for (Map.Entry<String, String> entry : map.entrySet()) {
  27 + checkSingleFiled(api, entry.getKey(), entry.getValue(), (Map<String, String>) requestDef.get(entry.getKey()));
  28 + }
20 } 29 }
21 30
22 - public static void check(Map<String, Object> apiDef, JSONObject response) { 31 + private static Map<String, String> getParamMap(String api) {
  32 + Map<String, String> map = new LinkedHashMap<>();
  33 + String[] parts = api.split("\\?");
  34 + if (parts != null && parts.length == 2) {
  35 + String paramStr = parts[1];
  36 + String[] paramPairs = paramStr.split("\\&");
  37 + for (String paramItem : paramPairs) {
  38 + String[] keyValueArray = paramItem.split("=");
  39 + if (keyValueArray != null && keyValueArray.length == 2) {
  40 + map.put(keyValueArray[0], keyValueArray[1]);
  41 + }
  42 + }
  43 + }
  44 +
  45 + System.out.println("[INFO]Get request url param map: " + map);
  46 + return map;
  47 + }
  48 +
  49 + public static void main(String[] args) {
  50 + System.out.println(getParamMap("http://192.168.102.216:8080/yohosearch/productindex/productList?query=vans&brand=144&gender=2,3"));
  51 + }
  52 +
  53 + public static void check(String api, JSONObject response) {
23 Assert.assertTrue("The response should contain data.", response.containsKey("data") && response.get("data") != null); 54 Assert.assertTrue("The response should contain data.", response.containsKey("data") && response.get("data") != null);
24 - Map<String, Object> responseDef = (Map<String, Object>) apiDef.get("_response");  
25 - Map<String, List<String>> enums = (Map<String, List<String>>) apiDef.get("_enums");  
26 - doCheck(responseDef, response, enums); 55 + Map<String, Object> responseDef = ApiDefUtils.getResponseDef(api);
  56 + doCheck(api, responseDef, response);
27 } 57 }
28 58
29 - private static void doCheck(Map<String, Object> responseDef, JSONObject response, Map<String, List<String>> enums) { 59 + private static void doCheck(String api, Map<String, Object> responseDef, JSONObject response) {
30 if (responseDef == null || response.isEmpty()) { 60 if (responseDef == null || response.isEmpty()) {
31 return; 61 return;
32 } 62 }
@@ -41,77 +71,88 @@ public class CheckStructureUtils { @@ -41,77 +71,88 @@ public class CheckStructureUtils {
41 boolean required = pDef.containsKey("_required") ? (Boolean) pDef.get("_required") : false; 71 boolean required = pDef.containsKey("_required") ? (Boolean) pDef.get("_required") : false;
42 String type = pDef.containsKey("_type") ? (String) pDef.get("_type") : "string"; 72 String type = pDef.containsKey("_type") ? (String) pDef.get("_type") : "string";
43 73
44 - if (pResponse == null || pResponse.toString() == null || pResponse.toString().isEmpty())  
45 - {  
46 - if(required)  
47 - { 74 + if (pResponse == null || pResponse.toString() == null || pResponse.toString().isEmpty()) {
  75 + if (required) {
48 Assert.fail("The property " + property + " is required!"); 76 Assert.fail("The property " + property + " is required!");
49 - }  
50 - else  
51 - { 77 + } else {
52 continue; 78 continue;
53 } 79 }
54 } 80 }
55 81
56 if ("object".equalsIgnoreCase(type)) { 82 if ("object".equalsIgnoreCase(type)) {
57 - doCheck((Map<String, Object>)pDef.get("_content"), (JSONObject) pResponse, enums);  
58 - continue;  
59 - }  
60 - else if ("array".equalsIgnoreCase(type)) { 83 + doCheck(api, (Map<String, Object>) pDef.get("_content"), (JSONObject) pResponse);
  84 + } else if ("array".equalsIgnoreCase(type)) {
61 JSONArray jsonArray = (JSONArray) pResponse; 85 JSONArray jsonArray = (JSONArray) pResponse;
62 - for(int i = 0; i < jsonArray.size(); i++)  
63 - {  
64 - doCheck((Map<String, Object>)pDef.get("_content"), jsonArray.getJSONObject(i), enums); 86 + for (int i = 0; i < jsonArray.size(); i++) {
  87 + doCheck(api, (Map<String, Object>) pDef.get("_content"), jsonArray.getJSONObject(i));
65 } 88 }
66 - continue;  
67 - }  
68 -  
69 - // 简单类型的处理 主要是类型的判断  
70 - String sValue = pResponse.toString();  
71 - if("int".equalsIgnoreCase(type) || "long".equalsIgnoreCase(type))  
72 - {  
73 - Integer.valueOf(sValue);  
74 - }  
75 - else if("long".equalsIgnoreCase(type))  
76 - {  
77 - Long.valueOf(sValue);  
78 - }  
79 - else if("double".equalsIgnoreCase(type))  
80 - {  
81 - Double.valueOf(sValue);  
82 - }  
83 - else if(type.startsWith("enum_"))  
84 - {  
85 - // 校验枚举类型  
86 - String enumKey = type.substring("enum_".length());  
87 - Assert.assertTrue("cannot find enum "+enumKey, enums.containsKey(enumKey));  
88 - Assert.assertTrue("Invalid value "+sValue+" for enum "+enumKey, enums.get(enumKey).contains(sValue));  
89 - }  
90 - else if(type.startsWith("connected_enum_"))  
91 - {  
92 - // 校验枚举类型  
93 - String enumKey = type.substring("connected_enum_".length());  
94 - Assert.assertTrue("cannot find enum "+enumKey, enums.containsKey(enumKey));  
95 - Assert.assertTrue("Invalid value "+sValue+" for enum "+enumKey, enums.get(enumKey).containsAll(Arrays.asList(sValue.split(","))));  
96 - }  
97 - else if(!"string".equalsIgnoreCase(type))  
98 - {  
99 - Assert.fail("Unknown property type "+type); 89 + } else {
  90 + checkSingleFiled(api, property, pResponse.toString(), (Map<String, String>) entry.getValue());
100 } 91 }
101 } 92 }
102 93
103 // 检查是否有字段未定义 94 // 检查是否有字段未定义
104 Set<String> defKeys = responseDef.keySet(); 95 Set<String> defKeys = responseDef.keySet();
105 Set<String> resKeys = response.keySet(); 96 Set<String> resKeys = response.keySet();
106 - if(!defKeys.containsAll(resKeys))  
107 - { 97 + if (!defKeys.containsAll(resKeys)) {
108 resKeys.removeAll(defKeys); 98 resKeys.removeAll(defKeys);
109 - System.out.println("[WARN]NOT SAME KEYS. There has new propertys: "+resKeys);  
110 - }  
111 - else if(!resKeys.containsAll(defKeys))  
112 - { 99 + System.out.println("[WARN]NOT SAME KEYS. There has new propertys: " + resKeys);
  100 + } else if (!resKeys.containsAll(defKeys)) {
113 defKeys.removeAll(resKeys); 101 defKeys.removeAll(resKeys);
114 - System.out.println("[WARN]NOT SAME KEYS. There has propertys not set value: "+defKeys); 102 + System.out.println("[WARN]NOT SAME KEYS. There has propertys not set value: " + defKeys);
  103 + }
  104 + }
  105 +
  106 + private static void checkSingleFiled(String api, String fieldName, String sValue, Map<String, String> fieldDef) {
  107 + System.out.println("[INFO]Begin to check field " + fieldName);
  108 + Assert.assertNotNull("field not defined for " + fieldName, fieldDef);
  109 + String type = fieldDef.containsKey("_type") ? (String) fieldDef.get("_type") : "string";
  110 + if (BASIC_TYPES.contains(type)) {
  111 + checkBasicType(type, sValue);
  112 + } else if (type.startsWith("optional")) {
  113 + // skip to check
  114 + } else if (type.startsWith("date")) {
  115 + // skip to check
  116 + } else if ("ranged_int".equals(type)) {
  117 + String[] limits = sValue.split(",");
  118 + Assert.assertTrue("The value for " + fieldName + " should contains comma", limits != null && limits.length == 2);
  119 + Assert.assertTrue("The upper limit should larger than the lower limit", Integer.valueOf(limits[1]) >= Integer.valueOf(limits[0]));
  120 + } else if ("ranged_double".equals(type)) {
  121 + String[] limits = sValue.split(",");
  122 + Assert.assertTrue("The value for " + fieldName + " should contains comma", limits != null && limits.length == 2);
  123 + Assert.assertTrue("The upper limit should larger than the lower limit", Double.valueOf(limits[1]) >= Double.valueOf(limits[0]));
  124 + } else if (type.startsWith("enum_")) {
  125 + // 校验枚举类型
  126 + String enumKey = type.substring("enum_".length());
  127 + Assert.assertTrue("Invalid value " + sValue + " for enum " + enumKey, ApiDefUtils.getEnumItems(api, enumKey).contains(sValue));
  128 + } else if (type.startsWith("connected_enum_")) {
  129 + // 校验枚举类型
  130 + String enumKey = type.substring("connected_enum_".length());
  131 + Assert.assertTrue("Invalid value " + sValue + " for enum " + enumKey, ApiDefUtils.getEnumItems(api, enumKey).containsAll(Arrays.asList(sValue.split(","))));
  132 +
  133 + } else if (type.startsWith("connected_")) {
  134 + String type4Connected = type.substring("connected_".length());
  135 + for (String item : sValue.split(",")) {
  136 + checkBasicType(type4Connected, item);
  137 + }
  138 + } else {
  139 + Assert.fail("Unknown property type " + type + " for property " + fieldName);
  140 + }
  141 + }
  142 +
  143 + private static void checkBasicType(String type, String sValue) {
  144 + switch (type) {
  145 + case "int":
  146 + Integer.valueOf(sValue);
  147 + break;
  148 + case "long":
  149 + Long.valueOf(sValue);
  150 + break;
  151 + case "double":
  152 + Double.valueOf(sValue);
  153 + break;
  154 + default:
  155 + break;
115 } 156 }
116 } 157 }
117 158
@@ -49,7 +49,7 @@ public class MarkDownUtils { @@ -49,7 +49,7 @@ public class MarkDownUtils {
49 // 3. 生成响应参数说明 49 // 3. 生成响应参数说明
50 sb.append(generateResponseMdTable(apiDef)); 50 sb.append(generateResponseMdTable(apiDef));
51 51
52 - FileUtils.writeFile("test.md", sb.toString()); 52 + FileUtils.writeFile(api.replace("/", "_") + ".md", sb.toString());
53 } 53 }
54 54
55 private StringBuffer generateResponseMdTable(Map<String, Object> apiDef) { 55 private StringBuffer generateResponseMdTable(Map<String, Object> apiDef) {
@@ -129,8 +129,7 @@ public class MarkDownUtils { @@ -129,8 +129,7 @@ public class MarkDownUtils {
129 return "特定值{" + specialValue + "}或不传值"; 129 return "特定值{" + specialValue + "}或不传值";
130 } 130 }
131 131
132 - StringBuffer sb = new StringBuffer(type);  
133 - return sb.toString(); 132 + return type;
134 } 133 }
135 134
136 } 135 }
@@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON; @@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON;
4 import com.alibaba.fastjson.JSONObject; 4 import com.alibaba.fastjson.JSONObject;
5 import com.yoho.search.base.utils.HttpClientUtils; 5 import com.yoho.search.base.utils.HttpClientUtils;
6 import com.yoho.search.service.TestConstants; 6 import com.yoho.search.service.TestConstants;
  7 +import org.apache.commons.lang3.StringUtils;
7 import org.junit.Assert; 8 import org.junit.Assert;
8 import org.junit.Test; 9 import org.junit.Test;
9 import org.springframework.web.bind.annotation.RequestMapping; 10 import org.springframework.web.bind.annotation.RequestMapping;
@@ -34,10 +35,11 @@ public class SearchNewControllerTest { @@ -34,10 +35,11 @@ public class SearchNewControllerTest {
34 appendParams.put("/group_shops", "shop=1"); 35 appendParams.put("/group_shops", "shop=1");
35 appendParams.put("/suggest", "query=vans"); 36 appendParams.put("/suggest", "query=vans");
36 appendParams.put("/shops", "keyword=vans"); 37 appendParams.put("/shops", "keyword=vans");
  38 + appendParams.put("/productindex/productList", "query=vans&brand=144&gender=2,3&attribute_not=2&status=1");
37 } 39 }
38 40
39 @Test 41 @Test
40 - public void testAPI() { 42 + public void testAPIs() {
41 SearchNewController c = new SearchNewController(); 43 SearchNewController c = new SearchNewController();
42 Method[] methods = c.getClass().getMethods(); 44 Method[] methods = c.getClass().getMethods();
43 if (methods == null || methods.length == 0) { 45 if (methods == null || methods.length == 0) {
@@ -61,6 +63,29 @@ public class SearchNewControllerTest { @@ -61,6 +63,29 @@ public class SearchNewControllerTest {
61 } 63 }
62 64
63 @Test 65 @Test
  66 + public void generateAPIMd() {
  67 + SearchNewController c = new SearchNewController();
  68 + Method[] methods = c.getClass().getMethods();
  69 + if (methods == null || methods.length == 0) {
  70 + return;
  71 + }
  72 +
  73 + for (Method m : methods) {
  74 + if (!m.isAnnotationPresent(RequestMapping.class)) {
  75 + continue;
  76 + }
  77 +
  78 + String api = m.getAnnotation(RequestMapping.class).value()[0];
  79 + if (skippedApis.contains(api)) {
  80 + continue;
  81 + }
  82 +
  83 + // 生成markdown接口说明文件
  84 + MarkDownUtils.getInstance(api).generateApiMd();
  85 + }
  86 + }
  87 +
  88 + @Test
64 public void testShops() { 89 public void testShops() {
65 testSingle("/shops"); 90 testSingle("/shops");
66 } 91 }
@@ -71,9 +96,13 @@ public class SearchNewControllerTest { @@ -71,9 +96,13 @@ public class SearchNewControllerTest {
71 } 96 }
72 97
73 private void testSingle(String api) { 98 private void testSingle(String api) {
  99 + testSingle(api, appendParams.get(api));
  100 + }
  101 +
  102 + private void testSingle(String api, String paramStr) {
74 String url = urlBase + api; 103 String url = urlBase + api;
75 - if (appendParams.containsKey(api)) {  
76 - url = url + "?" + appendParams.get(api); 104 + if (StringUtils.isNotEmpty(paramStr)) {
  105 + url = url + "?" + paramStr;
77 } 106 }
78 107
79 CheckStructureUtils.checkRequest(api, url); 108 CheckStructureUtils.checkRequest(api, url);
@@ -85,10 +114,6 @@ public class SearchNewControllerTest { @@ -85,10 +114,6 @@ public class SearchNewControllerTest {
85 Assert.assertEquals(api, "200", jsonObject.getString("code")); 114 Assert.assertEquals(api, "200", jsonObject.getString("code"));
86 115
87 // 检查响应结果是否OK 116 // 检查响应结果是否OK
88 - Map<String, Object> def = ApiDefUtils.getApiDef(api);  
89 - CheckStructureUtils.check(def, jsonObject);  
90 -  
91 - // 生成markdown接口说明文件  
92 - MarkDownUtils.getInstance(api).generateApiMd(); 117 + CheckStructureUtils.check(api, jsonObject);
93 } 118 }
94 } 119 }
@@ -94,7 +94,7 @@ _request: @@ -94,7 +94,7 @@ _request:
94 attribute: 94 attribute:
95 _desc: '包含指定的商品属性(1:正常商品,2:赠品)' 95 _desc: '包含指定的商品属性(1:正常商品,2:赠品)'
96 _type: 'enum_Attribute' 96 _type: 'enum_Attribute'
97 - attribute: 97 + attribute_not:
98 _desc: '过滤指定的商品属性(1:正常商品,2:赠品)' 98 _desc: '过滤指定的商品属性(1:正常商品,2:赠品)'
99 _type: 'enum_Attribute' 99 _type: 'enum_Attribute'
100 limited: 100 limited:
@@ -252,8 +252,8 @@ _response: @@ -252,8 +252,8 @@ _response:
252 _desc: '商品状态' 252 _desc: '商品状态'
253 _type: 'enum_Status' 253 _type: 'enum_Status'
254 is_promotion: 254 is_promotion:
255 - _desc: 'TODO'  
256 - _type: 'enum_Status' 255 + _desc: '优惠码'
  256 + _type: 'int'
257 is_student_price: 257 is_student_price:
258 _desc: '是否有学生优惠' 258 _desc: '是否有学生优惠'
259 _type: 'enum_YesOrNo' 259 _type: 'enum_YesOrNo'
  1 +_name: '根据关键词搜索品牌店铺接口。'
1 _request: 2 _request:
2 keyword: 3 keyword:
3 _desc: '搜索店铺的关键词,不能为空' 4 _desc: '搜索店铺的关键词,不能为空'