Commit d88f2d46 authored by xie.qin's avatar xie.qin

bug fixed to support sanity test stably.

parent a0dc9565
...@@ -22,11 +22,17 @@ English, please click [here](./HELP.md) ...@@ -22,11 +22,17 @@ English, please click [here](./HELP.md)
- 复合数据类型 array,需要验证响应消息中是否包含复合类型 object ?是,则需要验证 key 和数据类型;否则仅需要验证数据类型是否匹配期望。 - 复合数据类型 array,需要验证响应消息中是否包含复合类型 object ?是,则需要验证 key 和数据类型;否则仅需要验证数据类型是否匹配期望。
##### 用例参考 ##### 用例参考
[点击此链接](/src/test/resources/features/sanitytest/backend.feature) - [点击此链接](/src/test/resources/features/sanitytest/backend.feature)
- ![结果截图](/gallery/sanity_test.png)
##### 优点 ##### 优点
无需测试人员手动编写测试脚本,测试框架全自动完成 OpenAPI 分析及 REST 消息的发送和结果验证。 - 无需测试人员手动编写测试脚本,测试框架全自动完成 OpenAPI 分析及 REST 消息的发送和结果验证。
- 根据不同微服务的自我定义,自动适配其支持的协议类型(如,http, https, http2等)进行消息的发送和接收。
- 执行速度快,436个 API,请求和验证可以在40秒内完成。
##### 局限性 ##### 局限性
未考虑实际的业务需求;仅限于最基本的可行性验证。 - 未考虑实际的业务需求;仅限于最基本的可行性验证。
- 返回失败的请求需要人工二次校验其准确性(如果事先把测试数据准备好,则可规避此问题,但需要付出更多的人力)。
#### 1.1.2 编写符合实际业务需求的 API 测试用例(功能测试) #### 1.1.2 编写符合实际业务需求的 API 测试用例(功能测试)
##### 测试方法 ##### 测试方法
...@@ -43,15 +49,15 @@ English, please click [here](./HELP.md) ...@@ -43,15 +49,15 @@ English, please click [here](./HELP.md)
- 否则打印错误断言,测试退出并标记为失败。 - 否则打印错误断言,测试退出并标记为失败。
##### 用例参考 ##### 用例参考
[点击此链接](/src/test/resources/features/apitest/restful/user_management/user_register.feature) - [点击此链接](/src/test/resources/features/apitest/restful/user_management/user_register.feature)
##### 优点 ##### 优点
- 用例编写简单,核心测试脚本仅数行。 - 用例编写简单,核心测试脚本仅数行。
- 消息的发送、接收以及结果验证对用例编写人员透明。测试用例编写人员需要关心的只是产品业务需求。 - 消息的发送、接收以及结果验证对用例编写人员透明。测试用例编写人员需要关心的只是产品业务需求。
- 测试数据与功能需求关联,支持复用。不同测试脚本(用例)可使用相同测试数据。 - 测试数据与功能需求关联,支持复用。不同测试脚本(用例)可使用相同测试数据。
##### 局限性 ##### 局限性
更多需要讨论的是 API 测试本身的局限性,和本测试框架无关。 - 更多需要讨论的是 API 测试本身的局限性,和本测试框架无关。
#### 1.1.3 构造微服务 mock server,根据不同的请求返回不同的响应结果 (集成测试) #### 1.1.3 构造微服务 mock server,根据不同的请求返回不同的响应结果 (集成测试)
...@@ -45,10 +45,9 @@ public class OpenApiParser { ...@@ -45,10 +45,9 @@ public class OpenApiParser {
return openApiFilePath; return openApiFilePath;
} }
public Boolean analyzeOpenApiAndGenerateRequests(String serviceName, String targetServiceApiFilePath) { public void analyzeOpenApiAndGenerateRequests(String serviceName, String targetServiceApiFilePath) throws Throwable {
ObjectMapper objectMapper = new ObjectMapper(); ObjectMapper objectMapper = new ObjectMapper();
Boolean result = Boolean.TRUE;
try {
JsonNode rootNode = objectMapper.readTree(new File(targetServiceApiFilePath)); JsonNode rootNode = objectMapper.readTree(new File(targetServiceApiFilePath));
JsonNode pathsNode = rootNode.get("paths"); JsonNode pathsNode = rootNode.get("paths");
JsonNode definitionsNode = rootNode.get("definitions"); JsonNode definitionsNode = rootNode.get("definitions");
...@@ -56,24 +55,12 @@ public class OpenApiParser { ...@@ -56,24 +55,12 @@ public class OpenApiParser {
Iterator<Map.Entry<String, JsonNode>> it = pathsNode.fields(); Iterator<Map.Entry<String, JsonNode>> it = pathsNode.fields();
while (it.hasNext()) { while (it.hasNext()) {
Map.Entry<String, JsonNode> entry = it.next(); Map.Entry<String, JsonNode> entry = it.next();
result = parseEachPathInOpenApiFile(entry, definitionsNode, serviceName); parseEachPathInOpenApiFile(entry, definitionsNode, serviceName);
}
}
catch (JsonProcessingException e){
e.printStackTrace();
result = Boolean.FALSE;
}
catch (IOException e){
e.printStackTrace();
result = Boolean.FALSE;
} }
return result;
} }
public Boolean parseEachPathInOpenApiFile(Map.Entry<String, JsonNode> eachPathNode, JsonNode definitionNode, String serviceName) { public void parseEachPathInOpenApiFile(Map.Entry<String, JsonNode> eachPathNode, JsonNode definitionNode, String serviceName) {
log.debug("in parsing JsonNode{}", eachPathNode); log.debug("in parsing JsonNode{}", eachPathNode);
Boolean result = Boolean.TRUE;
try{
String path = eachPathNode.getKey(); String path = eachPathNode.getKey();
JsonNode pathNode = eachPathNode.getValue(); //{"post": {}, "get": {}} JsonNode pathNode = eachPathNode.getValue(); //{"post": {}, "get": {}}
Method httpMethod = null; Method httpMethod = null;
...@@ -86,18 +73,11 @@ public class OpenApiParser { ...@@ -86,18 +73,11 @@ public class OpenApiParser {
handleEachHttpMethodInOpenApiFile(path, httpMethod, pathValue.getValue(), definitionNode, serviceName); handleEachHttpMethodInOpenApiFile(path, httpMethod, pathValue.getValue(), definitionNode, serviceName);
} }
} }
catch (Exception e) {
e.printStackTrace();
result = Boolean.FALSE;
}
return result;
}
public void handleEachHttpMethodInOpenApiFile(String URL, Method httpMethod, JsonNode methodNode, JsonNode definitionNode, String serviceName) { public void handleEachHttpMethodInOpenApiFile(String URL, Method httpMethod, JsonNode methodNode, JsonNode definitionNode, String serviceName) {
log.debug("in handling HTTP request {} {}", httpMethod, URL);
String path = ""; String path = "";
//parse field consumes //parse field consumes
ContentType contentType = null; ContentType contentType = ContentType.JSON;
if (methodNode.has("consumes")) { if (methodNode.has("consumes")) {
ArrayNode consumesArrayNode = (ArrayNode) methodNode.get("consumes"); ArrayNode consumesArrayNode = (ArrayNode) methodNode.get("consumes");
for (JsonNode node : consumesArrayNode){ for (JsonNode node : consumesArrayNode){
...@@ -108,11 +88,13 @@ public class OpenApiParser { ...@@ -108,11 +88,13 @@ public class OpenApiParser {
//get whole path //get whole path
switch (serviceName.toLowerCase()){ switch (serviceName.toLowerCase()){
case "backend": case "backend":
String httpSchema = "http://"; path = backendServiceProperties.getHost() + ":" + backendServiceProperties.getPort() + URL;
if (backendServiceProperties.getHttpschema() != null && !backendServiceProperties.getHttpschema().isEmpty()){ if (backendServiceProperties.getHttps()){
httpSchema = backendServiceProperties.getHttpschema(); path = "https://" + path;
}
else {
path = "http://" + path;
} }
path = httpSchema + backendServiceProperties.getHost() + ":" + backendServiceProperties.getPort() + URL;
//batch replace variables in-path //batch replace variables in-path
if (path.contains("chain_type")){ if (path.contains("chain_type")){
path.replace("{chain_type}", globalProperties.getIntmin().toString()); path.replace("{chain_type}", globalProperties.getIntmin().toString());
...@@ -120,7 +102,7 @@ public class OpenApiParser { ...@@ -120,7 +102,7 @@ public class OpenApiParser {
if (path.matches("\\{.*?\\}")){ if (path.matches("\\{.*?\\}")){
path.replaceAll("\\{.*?\\}", globalProperties.getInt32max().toString()); //replaced by 9223372036854775807 ?? path.replaceAll("\\{.*?\\}", globalProperties.getInt32max().toString()); //replaced by 9223372036854775807 ??
} }
log.debug("path changed to: {}", path); log.info("now we're handling REST request: '{} {}'", httpMethod.name(), path);
} }
//specify headers. only two conditions: with User-ID, User-Name or without User-ID, User-Name //specify headers. only two conditions: with User-ID, User-Name or without User-ID, User-Name
...@@ -131,6 +113,7 @@ public class OpenApiParser { ...@@ -131,6 +113,7 @@ public class OpenApiParser {
//parse field parameters(path, request.json_body) //parse field parameters(path, request.json_body)
ArrayNode parametersArrayNode = null; ArrayNode parametersArrayNode = null;
Map<String, Object> reqJsonBody = new HashMap<String, Object>(); Map<String, Object> reqJsonBody = new HashMap<String, Object>();
List<Object> reqJsonArrayBody = new ArrayList<>();
/*if(contentType.compareTo(ContentType.JSON) == 0) { /*if(contentType.compareTo(ContentType.JSON) == 0) {
} }
else { else {
...@@ -143,14 +126,32 @@ public class OpenApiParser { ...@@ -143,14 +126,32 @@ public class OpenApiParser {
String txtInVal = paraNode.get("in").asText(); String txtInVal = paraNode.get("in").asText();
//String strInVal = paraNode.get("in").toString(); //String strInVal = paraNode.get("in").toString();
if (txtInVal.equalsIgnoreCase("query")) { if (txtInVal.equalsIgnoreCase("query")) {
if (!paraNode.get("type").asText().equalsIgnoreCase("boolean")){ //path must be required if (!paraNode.has("type") || paraNode.get("type").asText().equalsIgnoreCase("string")){
if (!path.contains("?")){ if (!path.contains("?")){
path = path + "?" + paraNode.get("name").asText() + "=" + globalProperties.getDftstring();
}
else {
path = path + "&" + paraNode.get("name").asText() + "=" + globalProperties.getDftstring();
}
}
else if (paraNode.get("type").asText().equalsIgnoreCase("integer")){ //path must be required
if (!path.contains("?")){
if(paraNode.has("format") && paraNode.get("format").asText().equalsIgnoreCase("int64")){
path = path + "?" + paraNode.get("name").asText() + "=" + globalProperties.getInt64max();
}
else {
path = path + "?" + paraNode.get("name").asText() + "=" + globalProperties.getInt32max().toString(); path = path + "?" + paraNode.get("name").asText() + "=" + globalProperties.getInt32max().toString();
} }
}
else{ else{
if(paraNode.has("format") && paraNode.get("format").asText().equalsIgnoreCase("int64")){
path = path + "&" + paraNode.get("name").asText() + "=" + globalProperties.getInt64max();
}
else {
path = path + "&" + paraNode.get("name").asText() + "=" + globalProperties.getInt32max().toString(); path = path + "&" + paraNode.get("name").asText() + "=" + globalProperties.getInt32max().toString();
} }
} }
}
else{ else{
if (!path.contains("?")){ if (!path.contains("?")){
path = path + "?" + paraNode.get("name").asText() + "=" + globalProperties.getDftboolean().toString(); path = path + "?" + paraNode.get("name").asText() + "=" + globalProperties.getDftboolean().toString();
...@@ -216,31 +217,23 @@ public class OpenApiParser { ...@@ -216,31 +217,23 @@ public class OpenApiParser {
if (bodySchema.has("items") && bodySchema.get("items").has("type")){ if (bodySchema.has("items") && bodySchema.get("items").has("type")){
switch (bodySchema.get("items").get("type").asText()){ switch (bodySchema.get("items").get("type").asText()){
case "string": case "string":
List<String> tmpStrArr = new ArrayList<>(); reqJsonArrayBody.add(globalProperties.getDftstring());
tmpStrArr.add(globalProperties.getDftstring()); reqJsonArrayBody.add(globalProperties.getDftstring());
tmpStrArr.add(globalProperties.getDftstring());
reqJsonBody.put(paraNode.get("name").asText(), tmpStrArr);
break; break;
case "integer": case "integer":
if (bodySchema.get("items").has("format")){ if (bodySchema.get("items").has("format")){
if (bodySchema.get("items").get("format").asText().equalsIgnoreCase("int64")){ if (bodySchema.get("items").get("format").asText().equalsIgnoreCase("int64")){
List<String> tmpInt64Arr = new ArrayList<>(); reqJsonArrayBody.add(globalProperties.getInt64max());
tmpInt64Arr.add(globalProperties.getInt64max()); reqJsonArrayBody.add(globalProperties.getInt64max());
tmpInt64Arr.add(globalProperties.getInt64max());
reqJsonBody.put(paraNode.get("name").asText(), tmpInt64Arr);
} }
else{ else{
List<Integer> tmpInt32Arr = new ArrayList<>(); reqJsonArrayBody.add(globalProperties.getInt32max());
tmpInt32Arr.add(globalProperties.getInt32max()); reqJsonArrayBody.add(globalProperties.getInt32max());
tmpInt32Arr.add(globalProperties.getInt32max());
reqJsonBody.put(paraNode.get("name").asText(), tmpInt32Arr);
} }
} }
else{ else{
List<Integer> tmpInt32Arr = new ArrayList<>(); reqJsonArrayBody.add(globalProperties.getInt32max());
tmpInt32Arr.add(globalProperties.getInt32max()); reqJsonArrayBody.add(globalProperties.getInt32max());
tmpInt32Arr.add(globalProperties.getInt32max());
reqJsonBody.put(paraNode.get("name").asText(), tmpInt32Arr);
} }
break; break;
default: default:
...@@ -250,7 +243,7 @@ public class OpenApiParser { ...@@ -250,7 +243,7 @@ public class OpenApiParser {
else if(bodySchema.has("items") && bodySchema.get("items").has("originalRef")){ else if(bodySchema.has("items") && bodySchema.get("items").has("originalRef")){
String bodyRef = bodySchema.get("items").get("originalRef").asText(); String bodyRef = bodySchema.get("items").get("originalRef").asText();
JsonNode jsonNodeReqBody = definitionNode.get(bodyRef).get("properties"); JsonNode jsonNodeReqBody = definitionNode.get(bodyRef).get("properties");
reqJsonBody = translatePropertyToJsonBody(bodyRef, jsonNodeReqBody, definitionNode); reqJsonArrayBody.add(translatePropertyToJsonBody(bodyRef, jsonNodeReqBody, definitionNode));
} }
else{ else{
log.error("This array structure is not defined in handling scenario. Its structure is: {}", bodySchema.toString()); log.error("This array structure is not defined in handling scenario. Its structure is: {}", bodySchema.toString());
...@@ -279,11 +272,34 @@ public class OpenApiParser { ...@@ -279,11 +272,34 @@ public class OpenApiParser {
reqJsonBody.put("file", binaryStrPath); reqJsonBody.put("file", binaryStrPath);
} }
} }
else if (txtInVal.equalsIgnoreCase("path")){
String tmpPathName = paraNode.get("name").asText();
switch(paraNode.get("type").asText().toLowerCase()){
case "boolean":
path = path.replace("{"+tmpPathName+"}", globalProperties.getDftboolean().toString());
break;
case "integer":
if (paraNode.has("format") && paraNode.get("format").asText().equalsIgnoreCase("int64")){
path = path.replace("{"+tmpPathName+"}", globalProperties.getInt64max());
}
else{
path = path.replace("{"+tmpPathName+"}", globalProperties.getInt32max().toString());
}
break;
case "number":
path = path.replace("{"+tmpPathName+"}", globalProperties.getDftnumber().toString());
break;
default:
path = path.replace("{"+tmpPathName+"}", globalProperties.getDftstring());
break;
}
}
} }
} }
//parse field responses //parse field responses
Map<String, Object> respJsonBody = new HashMap<String, Object>(); Map<String, Object> respJsonBody = new HashMap<String, Object>();
List<Object> respJsonArrayBody = new ArrayList<>();
int statusCode = 200; int statusCode = 200;
if (methodNode.has("responses")) { if (methodNode.has("responses")) {
JsonNode respJsonNode = methodNode.get("responses"); JsonNode respJsonNode = methodNode.get("responses");
...@@ -308,7 +324,8 @@ public class OpenApiParser { ...@@ -308,7 +324,8 @@ public class OpenApiParser {
if (eachRespNodeSchemaValue.get("items").has("originalRef")){ if (eachRespNodeSchemaValue.get("items").has("originalRef")){
String tmpRef = eachRespNodeSchemaValue.get("items").get("originalRef").asText(); String tmpRef = eachRespNodeSchemaValue.get("items").get("originalRef").asText();
JsonNode jsonNodeRespBody = definitionNode.get(tmpRef).get("properties"); JsonNode jsonNodeRespBody = definitionNode.get(tmpRef).get("properties");
respJsonBody = produceExpectedResponseBody(tmpRef, jsonNodeRespBody, definitionNode); //object array only save 1st element(a object) for matching. Map<String, Object> tmpArrayObject = produceExpectedResponseBody(tmpRef, jsonNodeRespBody, definitionNode); //object array only save 1st element(a object) for matching.
respJsonArrayBody.add(tmpArrayObject);
/*Map<String, Object> eachEleInRespArr = translatePropertyToJsonBody(jsonNodeRespBody, definitionNode); /*Map<String, Object> eachEleInRespArr = translatePropertyToJsonBody(jsonNodeRespBody, definitionNode);
//List<Map<String, Object>> mapArrayData = new ArrayList<>(); //List<Map<String, Object>> mapArrayData = new ArrayList<>();
//mapArrayData.add(eachEleInRespArr); //mapArrayData.add(eachEleInRespArr);
...@@ -326,7 +343,7 @@ public class OpenApiParser { ...@@ -326,7 +343,7 @@ public class OpenApiParser {
} }
else if (eachRespNodeSchemaValue.has("type") && eachRespNodeSchemaValue.get("type").asText().equalsIgnoreCase("object") && eachRespNodeSchemaValue.has("additionalProperties")) { else if (eachRespNodeSchemaValue.has("type") && eachRespNodeSchemaValue.get("type").asText().equalsIgnoreCase("object") && eachRespNodeSchemaValue.has("additionalProperties")) {
if (eachRespNodeSchemaValue.get("additionalProperties").has("format")){ if (eachRespNodeSchemaValue.get("additionalProperties").has("format")){
respJsonBody.put("type", "array.object." + eachRespNodeSchemaValue.get("format").asText().toLowerCase()); respJsonBody.put("type", "array.object." + eachRespNodeSchemaValue.get("additionalProperties").get("format").asText().toLowerCase());
} }
else { else {
respJsonBody.put("type", "array.object." + eachRespNodeSchemaValue.get("type").asText().toLowerCase()); respJsonBody.put("type", "array.object." + eachRespNodeSchemaValue.get("type").asText().toLowerCase());
...@@ -371,6 +388,26 @@ public class OpenApiParser { ...@@ -371,6 +388,26 @@ public class OpenApiParser {
.respJsonBody(respJsonBody) .respJsonBody(respJsonBody)
.build(); .build();
} }
else if (!respJsonArrayBody.isEmpty()) {
restfulMsgWithHeaders = RestfulMessageEntity.builder()
.headers(notNullHeaders)
.contentType(contentType)
.method(httpMethod)
.path(path)
.reqJsonBody(reqJsonBody)
.statusCode(statusCode)
.respJsonArrayBody(respJsonArrayBody)
.build();
restfulMsgWithoutHeaders = RestfulMessageEntity.builder()
.contentType(contentType)
.method(httpMethod)
.path(path)
.reqJsonBody(reqJsonBody)
.statusCode(statusCode)
.respJsonArrayBody(respJsonArrayBody)
.build();
}
else { else {
restfulMsgWithHeaders = RestfulMessageEntity.builder() restfulMsgWithHeaders = RestfulMessageEntity.builder()
.headers(notNullHeaders) .headers(notNullHeaders)
...@@ -392,6 +429,68 @@ public class OpenApiParser { ...@@ -392,6 +429,68 @@ public class OpenApiParser {
.build(); .build();
} }
} }
else if (!reqJsonArrayBody.isEmpty()){
if (!respJsonBody.isEmpty()) {
restfulMsgWithHeaders = RestfulMessageEntity.builder()
.headers(notNullHeaders)
.contentType(contentType)
.method(httpMethod)
.path(path)
.reqJsonArrayBody(reqJsonArrayBody)
.statusCode(statusCode)
.respJsonBody(respJsonBody)
.build();
restfulMsgWithoutHeaders = RestfulMessageEntity.builder()
.contentType(contentType)
.method(httpMethod)
.path(path)
.reqJsonArrayBody(reqJsonArrayBody)
.statusCode(statusCode)
.respJsonBody(respJsonBody)
.build();
}
else if (!respJsonArrayBody.isEmpty()){
restfulMsgWithHeaders = RestfulMessageEntity.builder()
.headers(notNullHeaders)
.contentType(contentType)
.method(httpMethod)
.path(path)
.reqJsonArrayBody(reqJsonArrayBody)
.statusCode(statusCode)
.respJsonArrayBody(respJsonArrayBody)
.build();
restfulMsgWithoutHeaders = RestfulMessageEntity.builder()
.contentType(contentType)
.method(httpMethod)
.path(path)
.reqJsonArrayBody(reqJsonArrayBody)
.statusCode(statusCode)
.respJsonArrayBody(respJsonArrayBody)
.build();
}
else {
restfulMsgWithHeaders = RestfulMessageEntity.builder()
.headers(notNullHeaders)
.contentType(contentType)
.method(httpMethod)
.path(path)
.reqJsonArrayBody(reqJsonArrayBody)
.statusCode(statusCode)
//.respJsonBody(respJsonBody)
.build();
restfulMsgWithoutHeaders = RestfulMessageEntity.builder()
.contentType(contentType)
.method(httpMethod)
.path(path)
.reqJsonArrayBody(reqJsonArrayBody)
.statusCode(statusCode)
//.respJsonBody(respJsonBody)
.build();
}
}
else { else {
if (!respJsonBody.isEmpty()) { if (!respJsonBody.isEmpty()) {
restfulMsgWithHeaders = RestfulMessageEntity.builder() restfulMsgWithHeaders = RestfulMessageEntity.builder()
...@@ -411,6 +510,24 @@ public class OpenApiParser { ...@@ -411,6 +510,24 @@ public class OpenApiParser {
.respJsonBody(respJsonBody) .respJsonBody(respJsonBody)
.build(); .build();
} }
else if (!respJsonArrayBody.isEmpty()) {
restfulMsgWithHeaders = RestfulMessageEntity.builder()
.headers(notNullHeaders)
.contentType(contentType)
.method(httpMethod)
.path(path)
.statusCode(statusCode)
.respJsonArrayBody(respJsonArrayBody)
.build();
restfulMsgWithoutHeaders = RestfulMessageEntity.builder()
.contentType(contentType)
.method(httpMethod)
.path(path)
.statusCode(statusCode)
.respJsonArrayBody(respJsonArrayBody)
.build();
}
else { else {
restfulMsgWithHeaders = RestfulMessageEntity.builder() restfulMsgWithHeaders = RestfulMessageEntity.builder()
.headers(notNullHeaders) .headers(notNullHeaders)
...@@ -475,7 +592,7 @@ public class OpenApiParser { ...@@ -475,7 +592,7 @@ public class OpenApiParser {
return reqJsonBody; return reqJsonBody;
} }
if (definitionNode.has(refField) && definitionNode.get(refField).has("properties")){ if (definitionNode.has(refField) && definitionNode.get(refField).has("properties")){
Map<String, Object> subReqJsonBody = translatePropertyToJsonBody(refField, definitionNode.get(refField), definitionNode); Map<String, Object> subReqJsonBody = translatePropertyToJsonBody(refField, definitionNode.get(refField).get("properties"), definitionNode);
reqJsonBody.put(property.getKey(), subReqJsonBody); reqJsonBody.put(property.getKey(), subReqJsonBody);
} }
else{ else{
...@@ -547,7 +664,7 @@ public class OpenApiParser { ...@@ -547,7 +664,7 @@ public class OpenApiParser {
return reqJsonBody; return reqJsonBody;
} }
if (definitionNode.has(refField) && definitionNode.get(refField).has("properties")){ if (definitionNode.has(refField) && definitionNode.get(refField).has("properties")){
Map<String, Object> subReqJsonBody = translatePropertyToJsonBody(refField, definitionNode.get(refField), definitionNode); Map<String, Object> subReqJsonBody = translatePropertyToJsonBody(refField, definitionNode.get(refField).get("properties"), definitionNode);
reqJsonBody.put(property.getKey(), subReqJsonBody); reqJsonBody.put(property.getKey(), subReqJsonBody);
} }
else{ else{
...@@ -570,7 +687,7 @@ public class OpenApiParser { ...@@ -570,7 +687,7 @@ public class OpenApiParser {
return reqJsonBody; return reqJsonBody;
} }
if (definitionNode.has(refField) && definitionNode.get(refField).has("properties")){ if (definitionNode.has(refField) && definitionNode.get(refField).has("properties")){
Map<String, Object> subReqJsonBody = translatePropertyToJsonBody(refField, definitionNode.get(refField), definitionNode); Map<String, Object> subReqJsonBody = translatePropertyToJsonBody(refField, definitionNode.get(refField).get("properties"), definitionNode);
reqJsonBody.put(property.getKey(), subReqJsonBody); reqJsonBody.put(property.getKey(), subReqJsonBody);
} }
else{ else{
...@@ -645,8 +762,10 @@ public class OpenApiParser { ...@@ -645,8 +762,10 @@ public class OpenApiParser {
return respJsonBody; return respJsonBody;
} }
if (definitionNode.has(refField) && definitionNode.get(refField).has("properties")){ if (definitionNode.has(refField) && definitionNode.get(refField).has("properties")){
Map<String, Object> subRespJsonBody = produceExpectedResponseBody(refField, definitionNode.get(refField), definitionNode); List<Map<String, Object>> tmpArrayObject = new ArrayList<>();
respJsonBody.put(property.getKey(), subRespJsonBody); Map<String, Object> subRespJsonBody = produceExpectedResponseBody(refField, definitionNode.get(refField).get("properties"), definitionNode);
tmpArrayObject.add(subRespJsonBody);
respJsonBody.put(property.getKey(), tmpArrayObject);
} }
else{ else{
respJsonBody.put(property.getKey(), "array"); respJsonBody.put(property.getKey(), "array");
...@@ -674,15 +793,12 @@ public class OpenApiParser { ...@@ -674,15 +793,12 @@ public class OpenApiParser {
} }
public Boolean produceRestReqWithTestData(JsonNode testReqData, JsonNode testRespData) { public Boolean produceRestReqWithTestData(JsonNode testReqData, JsonNode testRespData) {
assertThat("There is not destination service in request.", true, equalTo(testReqData.has("service"))); assertThat("There is not destination service in request.", testReqData.has("service"), equalTo(true));
//path //path
String path = ""; String path = "";
switch (testReqData.get("service").asText().toLowerCase()){ switch (testReqData.get("service").asText().toLowerCase()){
case "backend": case "backend":
String httpSchema = "http://"; String httpSchema = "http://";
if (backendServiceProperties.getHttpschema() != null && !backendServiceProperties.getHttpschema().isEmpty()){
httpSchema = backendServiceProperties.getHttpschema();
}
path += httpSchema + backendServiceProperties.getHost() + ":" + backendServiceProperties.getPort() + testReqData.get("path").asText(); path += httpSchema + backendServiceProperties.getHost() + ":" + backendServiceProperties.getPort() + testReqData.get("path").asText();
break; break;
} }
......
...@@ -10,6 +10,7 @@ import lombok.Builder; ...@@ -10,6 +10,7 @@ import lombok.Builder;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import java.util.List;
import java.util.Map; import java.util.Map;
@Data @Data
...@@ -27,11 +28,15 @@ public class RestfulMessageEntity { ...@@ -27,11 +28,15 @@ public class RestfulMessageEntity {
private Map<String,Object> reqJsonBody; private Map<String,Object> reqJsonBody;
private List<Object> reqJsonArrayBody;
private JsonNode mapperReqJsonBody; private JsonNode mapperReqJsonBody;
private int statusCode; private int statusCode;
private Map<String,Object> respJsonBody; private Map<String,Object> respJsonBody;
private List<Object> respJsonArrayBody;
private JsonNode mapperRespJsonBody; private JsonNode mapperRespJsonBody;
} }
...@@ -2,6 +2,8 @@ package com.fuzamei.autotest.openapi; ...@@ -2,6 +2,8 @@ package com.fuzamei.autotest.openapi;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fuzamei.autotest.properties.BackendServiceProperties;
import com.fuzamei.autotest.properties.GlobalProperties;
import com.fuzamei.autotest.properties.SpecificProperties; import com.fuzamei.autotest.properties.SpecificProperties;
import io.qameta.allure.restassured.AllureRestAssured; import io.qameta.allure.restassured.AllureRestAssured;
import io.restassured.RestAssured; import io.restassured.RestAssured;
...@@ -11,6 +13,7 @@ import io.restassured.config.RestAssuredConfig; ...@@ -11,6 +13,7 @@ import io.restassured.config.RestAssuredConfig;
import io.restassured.http.ContentType; import io.restassured.http.ContentType;
import io.restassured.mapper.ObjectMapperType; import io.restassured.mapper.ObjectMapperType;
import io.restassured.path.json.JsonPath; import io.restassured.path.json.JsonPath;
import io.restassured.path.json.exception.JsonPathException;
import io.restassured.response.Response; import io.restassured.response.Response;
import io.restassured.specification.MultiPartSpecification; import io.restassured.specification.MultiPartSpecification;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
...@@ -20,6 +23,8 @@ import org.springframework.beans.factory.annotation.Autowired; ...@@ -20,6 +23,8 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.File;
import java.lang.reflect.Array;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.util.*; import java.util.*;
...@@ -35,10 +40,28 @@ public class RestfulMessageEventListener { ...@@ -35,10 +40,28 @@ public class RestfulMessageEventListener {
@Autowired @Autowired
SpecificProperties specificProperties; SpecificProperties specificProperties;
@Autowired
BackendServiceProperties backendServiceProperties;
@Autowired
GlobalProperties globalProperties;
@EventListener @EventListener
public void processRestfulMessageEvent(RestfulMessageEvent restfulMessageEvent){ public void processRestfulMessageEvent(RestfulMessageEvent restfulMessageEvent){
log.debug("subscriber received a notification.");
RestfulMessageEntity restfulMessageEntity = restfulMessageEvent.getMsg(); RestfulMessageEntity restfulMessageEntity = restfulMessageEvent.getMsg();
log.debug("subscriber received a message and the message content is {}. ", restfulMessageEntity.toString());
if (backendServiceProperties.getHttpschema().equalsIgnoreCase("http1x")){
processRestMsgEventWithHttp1x(restfulMessageEntity);
}
else{
processRestMsgEventWithHttp2(restfulMessageEntity);
}
}
private void processRestMsgEventWithHttp1x(RestfulMessageEntity restfulMessageEntity) {
final String tmpTarget = restfulMessageEntity.getMethod().name() + " " + restfulMessageEntity.getPath();
RequestConfig requestConfig = RequestConfig.custom() RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(specificProperties.getRestConnTimeout().intValue()) .setConnectTimeout(specificProperties.getRestConnTimeout().intValue())
...@@ -55,7 +78,7 @@ public class RestfulMessageEventListener { ...@@ -55,7 +78,7 @@ public class RestfulMessageEventListener {
if (restfulMessageEntity.getHeaders() != null) { if (restfulMessageEntity.getHeaders() != null) {
if (restfulMessageEntity.getReqJsonBody() != null) { if (restfulMessageEntity.getReqJsonBody() != null) {
if (restfulMessageEntity.getContentType().equals(ContentType.MULTIPART)) { if (restfulMessageEntity.getContentType().equals(ContentType.MULTIPART)) {
String filePathName = restfulMessageEntity.getRespJsonBody().get("file").toString(); String filePathName = restfulMessageEntity.getReqJsonBody().get("file").toString();
restfulMessageEntity.getReqJsonBody().remove("file"); restfulMessageEntity.getReqJsonBody().remove("file");
MultiPartSpecification multiPartSpecBuilder = new MultiPartSpecBuilder(restfulMessageEntity.getReqJsonBody(), ObjectMapperType.JACKSON_2) MultiPartSpecification multiPartSpecBuilder = new MultiPartSpecBuilder(restfulMessageEntity.getReqJsonBody(), ObjectMapperType.JACKSON_2)
...@@ -92,7 +115,8 @@ public class RestfulMessageEventListener { ...@@ -92,7 +115,8 @@ public class RestfulMessageEventListener {
.extract() .extract()
.response(); .response();
} }
} else if (restfulMessageEntity.getMapperReqJsonBody() != null) { }
else if (restfulMessageEntity.getMapperReqJsonBody() != null) {
response = response =
given() given()
.filter(new AllureRestAssured()) .filter(new AllureRestAssured())
...@@ -107,7 +131,24 @@ public class RestfulMessageEventListener { ...@@ -107,7 +131,24 @@ public class RestfulMessageEventListener {
//.statusCode(restfulMessageEntity.getStatusCode()) //.statusCode(restfulMessageEntity.getStatusCode())
.extract() .extract()
.response(); .response();
} else { }
else if (restfulMessageEntity.getReqJsonArrayBody() != null) {
response =
given()
.filter(new AllureRestAssured())
//.config(config)
.body(restfulMessageEntity.getReqJsonArrayBody())
.when()
.contentType(restfulMessageEntity.getContentType())
.headers(restfulMessageEntity.getHeaders())
.request(restfulMessageEntity.getMethod(), restfulMessageEntity.getPath())
.then()
//.assertThat()
//.statusCode(restfulMessageEntity.getStatusCode())
.extract()
.response();
}
else {
response = response =
given() given()
.filter(new AllureRestAssured()) .filter(new AllureRestAssured())
...@@ -125,7 +166,7 @@ public class RestfulMessageEventListener { ...@@ -125,7 +166,7 @@ public class RestfulMessageEventListener {
} else { } else {
if (restfulMessageEntity.getReqJsonBody() != null) { if (restfulMessageEntity.getReqJsonBody() != null) {
if (restfulMessageEntity.getContentType().equals(ContentType.MULTIPART)) { if (restfulMessageEntity.getContentType().equals(ContentType.MULTIPART)) {
String filePathName = restfulMessageEntity.getRespJsonBody().get("file").toString(); String filePathName = restfulMessageEntity.getReqJsonBody().get("file").toString();
restfulMessageEntity.getReqJsonBody().remove("file"); restfulMessageEntity.getReqJsonBody().remove("file");
MultiPartSpecification multiPartSpecBuilder = new MultiPartSpecBuilder(restfulMessageEntity.getReqJsonBody(), ObjectMapperType.JACKSON_2) MultiPartSpecification multiPartSpecBuilder = new MultiPartSpecBuilder(restfulMessageEntity.getReqJsonBody(), ObjectMapperType.JACKSON_2)
...@@ -146,6 +187,8 @@ public class RestfulMessageEventListener { ...@@ -146,6 +187,8 @@ public class RestfulMessageEventListener {
//.statusCode(restfulMessageEntity.getStatusCode()) //.statusCode(restfulMessageEntity.getStatusCode())
.extract() .extract()
.response(); .response();
final String binaryStrPath = "src" + File.separatorChar + "test" + File.separatorChar + "resources" + File.separatorChar + "testdatacollection" + File.separatorChar + "common" + File.separatorChar + globalProperties.getLatestchain33version() + ".tar.gz";
restfulMessageEntity.getReqJsonBody().put("file", binaryStrPath);
} else { } else {
response = response =
given() given()
...@@ -161,7 +204,8 @@ public class RestfulMessageEventListener { ...@@ -161,7 +204,8 @@ public class RestfulMessageEventListener {
.extract() .extract()
.response(); .response();
} }
} else if (restfulMessageEntity.getMapperReqJsonBody() != null) { }
else if (restfulMessageEntity.getMapperReqJsonBody() != null) {
response = response =
given() given()
.filter(new AllureRestAssured()) .filter(new AllureRestAssured())
...@@ -175,7 +219,23 @@ public class RestfulMessageEventListener { ...@@ -175,7 +219,23 @@ public class RestfulMessageEventListener {
//.statusCode(restfulMessageEntity.getStatusCode()) //.statusCode(restfulMessageEntity.getStatusCode())
.extract() .extract()
.response(); .response();
} else { }
else if (restfulMessageEntity.getReqJsonArrayBody() != null) {
response =
given()
.filter(new AllureRestAssured())
//.config(config)
.body(restfulMessageEntity.getReqJsonArrayBody())
.when()
.contentType(restfulMessageEntity.getContentType())
.request(restfulMessageEntity.getMethod(), restfulMessageEntity.getPath())
.then()
//.assertThat()
//.statusCode(restfulMessageEntity.getStatusCode())
.extract()
.response();
}
else {
response = response =
given() given()
.filter(new AllureRestAssured()) .filter(new AllureRestAssured())
...@@ -192,96 +252,171 @@ public class RestfulMessageEventListener { ...@@ -192,96 +252,171 @@ public class RestfulMessageEventListener {
} }
} }
catch (Exception ex){ catch (Exception ex){
//log.error("send rest request failed as error: {}", ex.toString()); log.error("HTTP1.x REST message send failed as error: {}", ex.toString());
ex.printStackTrace(); ex.printStackTrace();
throw new AssertionError(ex.toString()); throwAssertionErrorInFeatureTest(restfulMessageEntity, ex.toString());
return;
}
try {
log.info("received the response, the status_code is: {}, response json_body is: {}", response.getStatusCode(), response.getBody().asString());
}
catch (JsonPathException jsonPathException){
log.error("Failed to parse json_body from response as error {}", jsonPathException.toString());
throwAssertionErrorInFeatureTest(restfulMessageEntity, jsonPathException.toString());
return;
} }
//response format: {code: "baas.err.success", data: {}, message: "OK", status: "OK"} //response format: {code: "baas.err.success", data: {}, message: "OK", status: "OK"}
JsonPath jsonPathEvaluator = null; JsonPath jsonPathEvaluator = null;
try {
assertThat("http status code is not the expectation.", response.getStatusCode(), equalTo(restfulMessageEntity.getStatusCode())); assertThat("http status code is not the expectation.", response.getStatusCode(), equalTo(restfulMessageEntity.getStatusCode()));
//check field code in response body //check field code in response body
jsonPathEvaluator = response.jsonPath(); jsonPathEvaluator = response.jsonPath();
String respCode = jsonPathEvaluator.get("code"); String respCode = jsonPathEvaluator.get("code");
assertThat("status code in http response is not the expectation.", respCode, equalTo("baas.err.success")); assertThat("status code in http response is not the expectation.", respCode, equalTo("baas.err.success"));
}
catch (AssertionError err){
log.error("HTTP1.x REST message [{}] failed to verify status_code. Received [{}], but expected [{}]", tmpTarget, response.getStatusCode(), restfulMessageEntity.getStatusCode());
throwAssertionErrorInFeatureTest(restfulMessageEntity, err.toString());
return;
}
catch (JsonPathException jsonPathException){
log.error("Failed to parse json_body from response as error {}", jsonPathException.toString());
throwAssertionErrorInFeatureTest(restfulMessageEntity, jsonPathException.toString());
return;
}
//response json body only match key, not value //response json body only match key, not value
//check field data contains all of needed key //check field data contains all of needed key
try {
if (restfulMessageEntity.getRespJsonBody() == null && restfulMessageEntity.getRespJsonArrayBody() == null && restfulMessageEntity.getMapperRespJsonBody() == null) {
assertThat("Expected response is null, but received valid data.", jsonPathEvaluator.get("data"), equalTo(null));
return;
}
if (jsonPathEvaluator.get("data") == null) {
assertThat("Expected response is not null, but received null.", true, equalTo(
restfulMessageEntity.getRespJsonBody() != null ||
restfulMessageEntity.getRespJsonArrayBody() != null ||
restfulMessageEntity.getMapperRespJsonBody() != null));
return;
}
}
catch (AssertionError err){
log.error("HTTP1.x REST message [{}] failed to verify response_body. Received is [{}], but expected is not.", tmpTarget, jsonPathEvaluator.get("data"));
throwAssertionErrorInFeatureTest(restfulMessageEntity, err.toString());
return;
}
try{ try{
//object //object
Map<String, Object> objRespBody = new HashMap<>(); Map<String, Object> objRespBody = new HashMap<>();
if (jsonPathEvaluator != null) {
objRespBody = jsonPathEvaluator.getMap("data"); objRespBody = jsonPathEvaluator.getMap("data");
log.debug("response json_body is a object. its value is: {}", objRespBody);
//no need to assert expected response data type is map here as expected response is a object {"type": expected_data_type}
if (restfulMessageEntity.getMapperRespJsonBody() != null) { if (restfulMessageEntity.getMapperRespJsonBody() != null) {
matchKeyAndDataValueInRespWithExpectation(objRespBody, restfulMessageEntity.getMapperRespJsonBody()); matchKeyAndDataValueInRespWithExpectation(objRespBody, restfulMessageEntity.getMapperRespJsonBody());
} }
else { else {
try {
matchKeyAndDataTypeInRespWithExpectation(objRespBody, restfulMessageEntity.getRespJsonBody()); matchKeyAndDataTypeInRespWithExpectation(objRespBody, restfulMessageEntity.getRespJsonBody());
} }
catch (AssertionError error){
log.error("HTTP1.x REST message [{}] failed to verify response_body. Received is [{}], but expected is [{}].", tmpTarget, objRespBody, restfulMessageEntity.getRespJsonBody());
throwAssertionErrorInFeatureTest(restfulMessageEntity, error.toString());
return;
}
} }
} }
catch (ClassCastException e){ catch (ClassCastException e){
try{ try{
//array //array
List<Map<String, Object>> arrRespBody = new ArrayList<>(); List<Object> arrRespBody = new ArrayList<>();
arrRespBody = jsonPathEvaluator.getList("data"); arrRespBody = jsonPathEvaluator.getList("data");
log.debug("response json_body is a array, its value is: {}", arrRespBody);
//objects in array //objects in array
try{ try{
Map<String, Object> arrEleRespBody = arrRespBody.get(0); if (arrRespBody.size() > 0) {
Map<String, Object> arrEleRespBody = (Map<String, Object>) arrRespBody.get(0);
if (restfulMessageEntity.getRespJsonArrayBody() != null) {
matchKeyAndDataTypeInRespWithExpectation(arrEleRespBody, (Map<String, Object>) restfulMessageEntity.getRespJsonArrayBody().get(0));
}
else if (restfulMessageEntity.getMapperRespJsonBody() != null) {
matchKeyAndDataValueInRespWithExpectation(arrEleRespBody, (ArrayNode)restfulMessageEntity.getMapperRespJsonBody().get(0));
}
else {
//not available behaviour.
matchKeyAndDataTypeInRespWithExpectation(arrEleRespBody, restfulMessageEntity.getRespJsonBody()); matchKeyAndDataTypeInRespWithExpectation(arrEleRespBody, restfulMessageEntity.getRespJsonBody());
}
}
else {
assertThat("Received data type does not match the expectation.", true, equalTo(arrRespBody instanceof ArrayList<?>));
}
}catch (ClassCastException exception){ }catch (ClassCastException exception){
//normal array, not objects in array //normal array, not objects in array
try {
String expectedDataType = restfulMessageEntity.getRespJsonBody().get("type").toString(); String expectedDataType = restfulMessageEntity.getRespJsonBody().get("type").toString();
matchDataTypeInRespWithExpectation(arrRespBody.get(0), expectedDataType); matchDataTypeInRespWithExpectation(arrRespBody.get(0), expectedDataType);
} }
catch (AssertionError error){
log.error("HTTP1.x REST message [{}] failed to verify data type in response_body array. Received is [{}], but expected is [{}].", tmpTarget, arrRespBody.get(0), restfulMessageEntity.getRespJsonBody().get("type").toString());
throwAssertionErrorInFeatureTest(restfulMessageEntity, error.toString());
return;
}
}
catch (AssertionError error){
log.error("HTTP1.x REST message [{}] failed to verify array type response_body. Received is [{}], but expected is [{}].", tmpTarget, jsonPathEvaluator.get("data"), restfulMessageEntity);
throwAssertionErrorInFeatureTest(restfulMessageEntity, error.toString());
return;
}
}catch (ClassCastException ex){ }catch (ClassCastException ex){
//neither object, nor array //neither object, nor array
if (restfulMessageEntity.getMapperRespJsonBody() != null){ try {
if (restfulMessageEntity.getMapperRespJsonBody() != null) {
String actual = jsonPathEvaluator.get("data").toString(); String actual = jsonPathEvaluator.get("data").toString();
String expected = restfulMessageEntity.getMapperRespJsonBody().asText(); String expected = restfulMessageEntity.getMapperRespJsonBody().asText();
assertThat("Received data does not match the expectation.", actual.equals(expected), equalTo(true)); assertThat("Received data does not match the expectation.", actual.equals(expected), equalTo(true));
} } else if (restfulMessageEntity.getRespJsonBody() != null) {
else {
String expectedDataType = restfulMessageEntity.getRespJsonBody().get("type").toString(); String expectedDataType = restfulMessageEntity.getRespJsonBody().get("type").toString();
matchDataTypeInRespWithExpectation(jsonPathEvaluator.get("data"), expectedDataType); matchDataTypeInRespWithExpectation(jsonPathEvaluator.get("data"), expectedDataType);
} }
} }
catch (AssertionError error){
log.error("HTTP1.x REST message [{}] failed to verify data type of response_body. Received is [{}], but expected is [{}].", tmpTarget, jsonPathEvaluator.get("data"), restfulMessageEntity);
throwAssertionErrorInFeatureTest(restfulMessageEntity, error.toString());
return;
}
} }
} }
}
private void processRestMsgEventWithHttp2(RestfulMessageEntity restfulMessageEntity) {
}
private void matchKeyAndDataTypeInRespWithExpectation(Map<String, Object> recvRespBody, Map<String, Object> expectedRespBody) { private void matchKeyAndDataTypeInRespWithExpectation(Map<String, Object> recvRespBody, Map<String, Object> expectedRespBody) {
//how to match: traverse expectedRespBody and found corresponding key in recvRespBody //how to match: traverse expectedRespBody and found corresponding key in recvRespBody
for (Map.Entry<String, Object> entry : expectedRespBody.entrySet()) { for (Map.Entry<String, Object> entry : expectedRespBody.entrySet()) {
log.debug("traversing expectedRespBody now, the key is {}", entry.getKey()); log.debug("traversing expectedRespBody now, the key is [{}]", entry.getKey());
try{
//to match key //to match key
assertThat("Cannot found out same key in Response Body.", true, equalTo(recvRespBody.containsKey(entry.getKey()))); assertThat("Cannot found out same key in Response Body.", true, equalTo(recvRespBody.containsKey(entry.getKey())));
//to match type of value //to match type of value
if(entry.getValue() instanceof Map){ if (entry.getValue() instanceof Map) {
matchKeyAndDataTypeInRespWithExpectation((Map<String, Object>) recvRespBody.get(entry.getKey()), (Map<String, Object>) entry.getValue()); matchKeyAndDataTypeInRespWithExpectation((Map<String, Object>) recvRespBody.get(entry.getKey()), (Map<String, Object>) entry.getValue());
} } else if (entry.getValue() instanceof Map[]) {
else if (entry.getValue() instanceof Map[]){ List<Map<String, Object>> recv = (List<Map<String, Object>>) recvRespBody.get(entry.getKey());
List<Map<String, Object>> recv = (List<Map<String, Object>>)recvRespBody.get(entry.getKey());
Map<String, Object> matchRecv = recv.get(0); Map<String, Object> matchRecv = recv.get(0);
List<Map<String, Object>> expected = (List<Map<String, Object>>)entry.getValue(); List<Map<String, Object>> expected = (List<Map<String, Object>>) entry.getValue();
Map<String, Object> matchExpected = expected.get(0); Map<String, Object> matchExpected = expected.get(0);
matchKeyAndDataTypeInRespWithExpectation(matchRecv, matchExpected); matchKeyAndDataTypeInRespWithExpectation(matchRecv, matchExpected);
} else if (entry.getValue().getClass().isArray()) {
assertThat("Received data type is not array", recvRespBody.get(entry.getKey()).getClass().isArray(), equalTo(true));
if (Array.getLength(recvRespBody.get(entry.getKey())) > 0) {
int tmpArrayLength = Array.getLength(recvRespBody.get(entry.getKey()));
matchDataTypeInRespWithExpectation(Array.get(recvRespBody.get(entry.getKey()), 0), Array.get(entry.getValue(), 0).toString());
} }
else { } else {
try {
matchDataTypeInRespWithExpectation(recvRespBody.get(entry.getKey()), entry.getValue().toString()); matchDataTypeInRespWithExpectation(recvRespBody.get(entry.getKey()), entry.getValue().toString());
}catch (AssertionError error){
log.error("After matched, the key:{}'s data type does not match the expectation.", entry.getKey());
break;
}
}
}
catch (AssertionError e){
log.error("After matched, the key {} missed in received response message.", entry.getKey());
break;
} }
} }
...@@ -292,7 +427,7 @@ public class RestfulMessageEventListener { ...@@ -292,7 +427,7 @@ public class RestfulMessageEventListener {
Iterator<Map.Entry<String, JsonNode>> it = expectedRespBody.fields(); Iterator<Map.Entry<String, JsonNode>> it = expectedRespBody.fields();
while (it.hasNext()) { while (it.hasNext()) {
Map.Entry<String, JsonNode> entry = it.next(); Map.Entry<String, JsonNode> entry = it.next();
log.debug("traversing expectedRespBody now, the key is {}", entry.getKey()); log.debug("traversing expectedRespBody now, the key is [{}]", entry.getKey());
//to match key //to match key
assertThat("Cannot found out same key in Response Body.", true, equalTo(recvRespBody.containsKey(entry.getKey()))); assertThat("Cannot found out same key in Response Body.", true, equalTo(recvRespBody.containsKey(entry.getKey())));
...@@ -370,6 +505,19 @@ public class RestfulMessageEventListener { ...@@ -370,6 +505,19 @@ public class RestfulMessageEventListener {
break; break;
} }
assertThat("response data type cannot match the expectation.", matched, equalTo(true)); assertThat("response data type cannot match the expectation.", matched, equalTo(true));
/*try {
assertThat("response data type cannot match the expectation.", matched, equalTo(true));
}
catch (AssertionError error){
log.error("failed to match data type [{}]. Expected data type is [{}]", recvDataType, expectedDataType);
throw new AssertionError(error.toString());
}*/
}
private void throwAssertionErrorInFeatureTest(RestfulMessageEntity restfulMessageEntity, String errMsg){
if (restfulMessageEntity.getMapperReqJsonBody() != null || restfulMessageEntity.getMapperRespJsonBody() != null){
throw new AssertionError(errMsg);
}
} }
} }
...@@ -16,4 +16,5 @@ public class BackendServiceProperties { ...@@ -16,4 +16,5 @@ public class BackendServiceProperties {
private String port; private String port;
private String openapifilepath; private String openapifilepath;
private String httpschema; private String httpschema;
private Boolean https;
} }
...@@ -14,9 +14,12 @@ server.port=8080 ...@@ -14,9 +14,12 @@ server.port=8080
logging.level.root=WARN logging.level.root=WARN
logging.level.org.springframework=ERROR logging.level.org.springframework=ERROR
logging.level.com.fuzamei.autotest=DEBUG logging.level.com.fuzamei.autotest=DEBUG
logging.config=classpath:logback-spring.xml
spring.jackson.default-property-inclusion=non_null spring.jackson.default-property-inclusion=non_null
service.backend.httpschema=http:// service.backend.https=false
service.backend.httpschema=http1x
#service.backend.httpschema=http2
service.backend.host=172.22.18.152 service.backend.host=172.22.18.152
service.backend.port=2345 service.backend.port=2345
service.backend.openapidefinitionpath=/ service.backend.openapidefinitionpath=/
...@@ -32,6 +35,6 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver ...@@ -32,6 +35,6 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
mybatis.type-aliases-package=com.fuzamei.autotest.testdata.entity mybatis.type-aliases-package=com.fuzamei.autotest.testdata.entity
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
restassured.connecttimeout=3000 restassured.connecttimeout=10000
restassured.requesttimeout=3000 restassured.requesttimeout=10000
restassured.sockettimeout=3000 restassured.sockettimeout=10000
\ No newline at end of file \ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 -->
<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true -->
<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。
当scan为true时,此属性生效。默认的时间间隔为1分钟。 -->
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 -->
<configuration scan="true" scanPeriod="10 seconds">
<contextName>logback-spring</contextName>
<!-- name的值是变量的名称,value的值时变量定义的值。通过定义的值会被插入到logger上下文中。定义后,可以使“${}”来使用变量。 -->
<property name="logging.path" value="./target/cucumber/log" />
<!--0. 日志格式和颜色渲染 -->
<!-- 彩色日志依赖的渲染类 -->
<conversionRule conversionWord="clr" converterClass="org.springframework.boot.logging.logback.ColorConverter" />
<conversionRule conversionWord="wex" converterClass="org.springframework.boot.logging.logback.WhitespaceThrowableProxyConverter" />
<conversionRule conversionWord="wEx" converterClass="org.springframework.boot.logging.logback.ExtendedWhitespaceThrowableProxyConverter" />
<!-- 彩色日志格式 -->
<property name="CONSOLE_LOG_PATTERN" value="${CONSOLE_LOG_PATTERN:-%clr(%d{yyyy-MM-dd HH:mm:ss.SSS}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}}"/>
<!--1. 输出到控制台-->
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<!--此日志appender是为开发使用,只配置最底级别,控制台输出的日志级别是大于或等于此级别的日志信息-->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
<encoder>
<Pattern>${CONSOLE_LOG_PATTERN}</Pattern>
<!-- 设置字符集 -->
<charset>UTF-8</charset>
</encoder>
</appender>
<!--2. 输出到文档-->
<!-- 2.1 level为 DEBUG 日志,时间滚动输出 -->
<appender name="CUSTOM_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${logging.path}/autotest_custom.log</file>
<!--日志文档输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志归档 -->
<fileNamePattern>${logging.path}/autotest-custom-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文档保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文档只记录debug级别的 -->
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>debug</level>
</filter>
</appender>
<!-- 2.2 level为 INFO 日志,时间滚动输出 -->
<appender name="INFO_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${logging.path}/autotest_info.log</file>
<!--日志文档输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset>
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 每天日志归档路径以及格式 -->
<fileNamePattern>${logging.path}/autotest-info-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文档保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文档只记录info级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>info</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 2.3 level为 WARN 日志,时间滚动输出 -->
<appender name="WARN_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${logging.path}/autotest_warn.log</file>
<!--日志文档输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logging.path}/autotest-warn-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文档保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文档只记录warn级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>warn</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 2.4 level为 ERROR 日志,时间滚动输出 -->
<appender name="ERROR_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<!-- 正在记录的日志文档的路径及文档名 -->
<file>${logging.path}/autotest_error.log</file>
<!--日志文档输出格式-->
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
<charset>UTF-8</charset> <!-- 此处设置字符集 -->
</encoder>
<!-- 日志记录器的滚动策略,按日期,按大小记录 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>${logging.path}/web-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
<maxFileSize>100MB</maxFileSize>
</timeBasedFileNamingAndTriggeringPolicy>
<!--日志文档保留天数-->
<maxHistory>15</maxHistory>
</rollingPolicy>
<!-- 此日志文档只记录ERROR级别的 -->
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<level>ERROR</level>
<onMatch>ACCEPT</onMatch>
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!--
<logger>用来设置某一个包或者具体的某一个类的日志打印级别、
以及指定<appender>。<logger>仅有一个name属性,
一个可选的level和一个可选的addtivity属性。
name:用来指定受此logger约束的某一个包或者具体的某一个类。
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
还有一个特俗值INHERITED或者同义词NULL,代表强制执行上级的级别。
如果未设置此属性,那么当前logger将会继承上级的级别。
addtivity:是否向上级logger传递打印信息。默认是true。
<logger name="org.springframework.web" level="info"/>
<logger name="org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor" level="INFO"/>
-->
<!--
使用mybatis的时候,sql语句是debug下才会打印,而这里我们只配置了info,所以想要查看sql语句的话,有以下两种操作:
第一种把<root level="info">改成<root level="DEBUG">这样就会打印sql,不过这样日志那边会出现很多其他消息
第二种就是单独给dao下目录配置debug模式,代码如下,这样配置sql语句会打印,其他还是正常info级别:
【logging.level.org.mybatis=debug logging.level.dao=debug】
-->
<!--
root节点是必选节点,用来指定最基础的日志输出级别,只有一个level属性
level:用来设置打印级别,大小写无关:TRACE, DEBUG, INFO, WARN, ERROR, ALL 和 OFF,
不能设置为INHERITED或者同义词NULL。默认是DEBUG
可以包含零个或多个元素,标识这个appender将会添加到这个logger。
-->
<!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
<logger name="org.springframework" level="WARN"></logger>
<logger name="org.mybatis" level="WARN"></logger>
<logger name="org.apache.zookeeper" level="WARN"></logger>
<!-- 4. 最终的策略 -->
<!-- 4.1 开发环境:打印控制台-->
<springProfile name="dev">
<logger name="com.fuzamei.autotest" level="debug"/><!-- 修改此处扫描包名 -->
</springProfile>
<root level="debug">
<appender-ref ref="CONSOLE" />
<appender-ref ref="CUSTOM_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="WARN_FILE" />
<appender-ref ref="ERROR_FILE" />
</root>
4.2 生产环境:输出到文档
<springProfile name="pro">
<root level="info">
<appender-ref ref="CONSOLE" />
<appender-ref ref="CUSTOM_FILE" />
<appender-ref ref="INFO_FILE" />
<appender-ref ref="ERROR_FILE" />
<appender-ref ref="WARN_FILE" />
</root>
</springProfile>
</configuration>
...@@ -55,7 +55,7 @@ public class ApiVerification { ...@@ -55,7 +55,7 @@ public class ApiVerification {
default: default:
break; break;
} }
assertThat(openApiParser.analyzeOpenApiAndGenerateRequests(serviceName, targetServiceApiFilePath), is(Boolean.TRUE)); openApiParser.analyzeOpenApiAndGenerateRequests(serviceName, targetServiceApiFilePath);
} }
@Then("^Send RESTful request with dataNum (.*?) to verify interface .*?$") @Then("^Send RESTful request with dataNum (.*?) to verify interface .*?$")
......
...@@ -4,4 +4,4 @@ Feature: API tests for service of backend ...@@ -4,4 +4,4 @@ Feature: API tests for service of backend
Scenario: Verify the exposed APIs work as design Scenario: Verify the exposed APIs work as design
Given Found OpenAPI definition for backend service Given Found OpenAPI definition for backend service
When The testing PERSISTENCE data orgUser is ready for backend service When The testing PERSISTENCE data orgUser is ready for backend service
#Then Analyze OpenAPI file of backend service and generate REST-ful requests automatically Then Analyze OpenAPI file of backend service and generate REST-ful requests automatically
\ No newline at end of file \ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment