From 17f95315cfe92bdff0784666d87cf414269f0012 Mon Sep 17 00:00:00 2001 From: Bharath S Date: Thu, 12 May 2022 10:43:49 +0530 Subject: [PATCH] Add API wrapper support (#42) * handle wrapper and test definition via interface * optimized foreach to stream * add xlsx with API wrappers * handle wrapper sheet as optional feature * excel expected response Restel Variables * custom string equals comparator * add test for apiwrapper and custom string comparator * Changes to add the request resposne to the test api. Removed unused code * add demo and integration test sheets with wrapper * fix wrapper sheet column name is dependant on DTO Co-authored-by: Kannan R --- quickstart/jsonbox_test.xlsx | Bin 39999 -> 41257 bytes .../restel/core/RestelRunner.java | 6 +- .../core/managers/ExcelParseManager.java | 53 +++-- .../managers/RestelDefinitionManager.java | 196 ++++++------------ .../core/managers/RestelTestManager.java | 34 +-- .../core/model/RestelApiDefinition.java | 77 +++++++ ...thod.java => RestelTestApiDefinition.java} | 19 +- .../core/model/RestelTestApiWrapper.java | 179 ++++++++++++++++ .../comparator/ExactMatchComparator.java | 2 + .../PartialJsonMatchComparator.java | 3 +- .../restel/core/parser/ParserEnums.java | 1 + .../core/parser/config/ParserConfig.java | 12 ++ .../core/parser/dto/TestApiWrappers.java | 12 ++ .../comparator/StringJsonComparator.java | 19 ++ .../restel/testng/TestCaseExecutor.java | 15 +- .../techconative/restel/utils/Constants.java | 7 + .../restel/utils/RestelUtils.java | 37 +++- .../manager/RestelDefinitionManagerTest.java | 22 +- .../core/model/RestelTestMethodTest.java | 8 +- .../core/model/RestelTestWrapperTest.java | 68 ++++++ .../StringJsonMatchComparatorTest.java | 30 +++ .../restel/testng/TestCaseExecutorTest.java | 4 +- src/test/resources/jsonbox_test.xlsx | Bin 39557 -> 44119 bytes 23 files changed, 590 insertions(+), 214 deletions(-) create mode 100644 src/main/java/com/techconative/restel/core/model/RestelApiDefinition.java rename src/main/java/com/techconative/restel/core/model/{RestelTestMethod.java => RestelTestApiDefinition.java} (75%) create mode 100644 src/main/java/com/techconative/restel/core/model/RestelTestApiWrapper.java create mode 100644 src/main/java/com/techconative/restel/core/parser/dto/TestApiWrappers.java create mode 100644 src/main/java/com/techconative/restel/extension/jsoncompare/comparator/StringJsonComparator.java create mode 100644 src/test/java/restel/core/model/RestelTestWrapperTest.java create mode 100644 src/test/java/restel/core/model/comparators/StringJsonMatchComparatorTest.java diff --git a/quickstart/jsonbox_test.xlsx b/quickstart/jsonbox_test.xlsx index e5542e953a4e62cc2ccd884dbe83f6ca9c798c2d..dac8ac8d29bf4b6c53266e60e982d80e11dbcf93 100644 GIT binary patch literal 41257 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 wK#^zDQBBE6z-kJ36D_(a$QcjSl getTestList( diff --git a/src/main/java/com/techconative/restel/core/managers/ExcelParseManager.java b/src/main/java/com/techconative/restel/core/managers/ExcelParseManager.java index 8fc6f7a..d07a7d1 100644 --- a/src/main/java/com/techconative/restel/core/managers/ExcelParseManager.java +++ b/src/main/java/com/techconative/restel/core/managers/ExcelParseManager.java @@ -1,16 +1,10 @@ package com.techconative.restel.core.managers; -import com.techconative.restel.core.model.BaseConfiguration; -import com.techconative.restel.core.model.RestelSuite; -import com.techconative.restel.core.model.RestelTestMethod; -import com.techconative.restel.core.model.RestelTestScenario; +import com.techconative.restel.core.model.*; import com.techconative.restel.core.parser.Parser; import com.techconative.restel.core.parser.ParserEnums; import com.techconative.restel.core.parser.config.ParserConfig; -import com.techconative.restel.core.parser.dto.BaseConfig; -import com.techconative.restel.core.parser.dto.TestApiDefinitions; -import com.techconative.restel.core.parser.dto.TestScenarios; -import com.techconative.restel.core.parser.dto.TestSuites; +import com.techconative.restel.core.parser.dto.*; import com.techconative.restel.exception.RestelException; import com.techconative.restel.utils.RestelUtils; import java.io.File; @@ -27,7 +21,9 @@ public class ExcelParseManager { private String filepath; - private List testMethods; + private List testMethods; + private Map testMethodMap; + private List testApiWrappers; private List suites; private List execGroups; private BaseConfiguration baseConfig; @@ -55,6 +51,9 @@ private void init() { List testDefs = (List) excelData.get(ParserEnums.TEST_API_DEFINITIONS.toString().toLowerCase()); + List testWrappers = + (List) + excelData.get(ParserEnums.TEST_API_WRAPPERS.toString().toLowerCase()); List testSuites = (List) excelData.get(ParserEnums.TEST_SUITES.toString().toLowerCase()); List testSuiteExecutions = @@ -64,14 +63,21 @@ private void init() { (BaseConfig) excelData.get(ParserEnums.BASE_CONFIG.toString().toLowerCase())); testMethods = createTestMethod(testDefs, baseConfig); + if ((testWrappers != null) && !testWrappers.isEmpty()) { + testApiWrappers = createTestApiWrapper(testWrappers); + } suites = createSuites(testSuites); execGroups = createExecGroups(testSuiteExecutions); } - public List getTestMethods() { + public List getTestMethods() { return testMethods; } + public List getTestApiWrappers() { + return testApiWrappers; + } + public List getSuites() { return suites; } @@ -92,28 +98,29 @@ private BaseConfiguration createBaseConfigure(BaseConfig config) { * creates List of RestelTestMethod from TestDefinitions * * @param testApiDefinitions List of {@link TestApiDefinitions} - * @return list of {@link RestelTestMethod} + * @return list of {@link RestelTestApiDefinition} */ - private List createTestMethod( + private List createTestMethod( List testApiDefinitions, BaseConfiguration baseConfig) { if (testApiDefinitions.isEmpty()) { throw new RestelException("TEST_DEF_EMPTY"); } // Create a Map od case name and its Method definition. - Map testMethodMap = new HashMap<>(); - testApiDefinitions.forEach( - testDefinition -> - testMethodMap.put( - testDefinition.getApiUniqueName(), - RestelUtils.createTestMethod(testDefinition, baseConfig))); + testMethodMap = + testApiDefinitions.stream() + .collect( + Collectors.toMap( + TestApiDefinitions::getApiUniqueName, + x -> RestelUtils.createTestMethod(x, baseConfig))); return testApiDefinitions.stream() .map( testDefinition -> { - RestelTestMethod testMethod = testMethodMap.get(testDefinition.getApiUniqueName()); + RestelTestApiDefinition testMethod = + testMethodMap.get(testDefinition.getApiUniqueName()); if (!StringUtils.isEmpty(testDefinition.getDependsOn())) { - List dependents = + List dependents = Arrays.asList(testDefinition.getDependsOn().split(",")).stream() .map( name -> { @@ -133,6 +140,12 @@ private List createTestMethod( .collect(Collectors.toList()); } + private List createTestApiWrapper(List testWrappers) { + return testWrappers.stream() + .map(x -> RestelUtils.createTestWrapper(x, testMethodMap)) + .collect(Collectors.toList()); + } + /** * creates List of RestelSuites from TestSuites * diff --git a/src/main/java/com/techconative/restel/core/managers/RestelDefinitionManager.java b/src/main/java/com/techconative/restel/core/managers/RestelDefinitionManager.java index 7f2ae67..0125183 100644 --- a/src/main/java/com/techconative/restel/core/managers/RestelDefinitionManager.java +++ b/src/main/java/com/techconative/restel/core/managers/RestelDefinitionManager.java @@ -11,9 +11,7 @@ import com.techconative.restel.core.middleware.request.RequestMiddleware; import com.techconative.restel.core.middleware.response.ResponseMiddleware; import com.techconative.restel.core.middleware.response.ResponseWriterMiddleware; -import com.techconative.restel.core.model.GlobalContext; -import com.techconative.restel.core.model.RestelTestMethod; -import com.techconative.restel.core.model.TestContext; +import com.techconative.restel.core.model.*; import com.techconative.restel.core.model.comparator.ResponseComparator; import com.techconative.restel.core.model.oauth.BasicAuth; import com.techconative.restel.core.model.oauth.ClientCredentials; @@ -28,7 +26,6 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.util.CollectionUtils; import org.testng.Assert; -import org.testng.collections.Maps; /** */ @Slf4j @@ -36,12 +33,12 @@ public class RestelDefinitionManager { private RequestManager requestManager; private MatcherFactory matcherFactory; - private List testDefinitions; + private List testDefinitions; private TestContext testContext; public RestelDefinitionManager( - List testDefinitions, + List testDefinitions, RequestManager requestManager, MatcherFactory matcherFactory, TestContext testContext) { @@ -61,7 +58,7 @@ public boolean executeTestScenario(String scenarioName, String suiteName) { // TODO: Add support to execute dependents // executeDependents(scenarioName, suiteName); - for (RestelTestMethod restelTestMethod : testDefinitions) { + for (RestelApiDefinition restelTestMethod : testDefinitions) { boolean result = executeTestMethod(scenarioName, suiteName, restelTestMethod); if (!result) { return result; @@ -71,13 +68,20 @@ public boolean executeTestScenario(String scenarioName, String suiteName) { } private boolean executeTestMethod( - String scenarioName, String suiteName, RestelTestMethod restelTestMethod) { + String scenarioName, String suiteName, RestelApiDefinition restelTestMethod) { + + TestContext apiContext = new TestContext(restelTestMethod.getApiUniqueName(), testContext); + if (restelTestMethod.getApiParameters() != null) { + apiContext.putAll(restelTestMethod.getApiParameters()); + } // Prepare the request object RESTRequest request = createRequest(restelTestMethod); + // add test level context params + // Populate the request to context, so that it can be referenced in // other test - populateRequestToContext(request, scenarioName, suiteName, restelTestMethod); + populateRequestToContext(apiContext, request); // Make the API call and get the response RESTResponse response = @@ -91,15 +95,18 @@ private boolean executeTestMethod( // Populate the response to context, so that it can be referenced in // other test - populateResponseToContext(response, restelTestMethod, scenarioName, suiteName); + populateResponseToContext(apiContext, response); // validate response status - validateStatus(response, restelTestMethod); + validateStatus(apiContext, response, restelTestMethod); // Test if the header matches as per the config List headerMatchers = getHeaderMatchers(restelTestMethod); boolean isHeaderMatched = - doMatching(headerMatchers, response.getHeaders(), getExpectedHeaders(restelTestMethod)); + doMatching( + headerMatchers, + response.getHeaders(), + getExpectedHeaders(apiContext, restelTestMethod)); log.info("Headers matched for the response of " + scenarioName + ":" + isHeaderMatched); if (!isHeaderMatched) { @@ -109,17 +116,18 @@ private boolean executeTestMethod( // Test if the body matches as per the config List responseMatchers = getResponseMatchers(restelTestMethod); boolean isBodyMatched = - doMatching(responseMatchers, response, getExpectedBody(restelTestMethod)); + doMatching(responseMatchers, response, getExpectedBody(apiContext, restelTestMethod)); log.info( "Response content matched for the response of " + scenarioName + ":" + ":" + isBodyMatched); return isBodyMatched; } - private void validateStatus(RESTResponse response, RestelTestMethod restelTestMethod) { + private void validateStatus( + TestContext apiContext, RESTResponse response, RestelApiDefinition restelTestMethod) { List expectedStatus = (List) - replaceContextVariables(testContext, restelTestMethod.getAcceptedStatusCodes()); + replaceContextVariables(apiContext, restelTestMethod.getAcceptedStatusCodes()); if (!expectedStatus.contains(String.valueOf(response.getStatus()))) { Assert.fail( "Invalid Response Status Code: " @@ -128,58 +136,31 @@ private void validateStatus(RESTResponse response, RestelTestMethod restelTestMe } } - // /** - // * @param scenarioName Test scenario name - // * @param suiteName Test suite name - // */ - // private void executeDependents(String scenarioName, String suiteName) { - // if (testDefinition.getDependentOn() != null) { - // testDefinition - // .getDependentOn() - // .forEach( - // testcase -> { - // // pass on the same textContext so that request and response body can be stored - // and - // // reused. - // RestelDefinitionManager manager = - // new RestelDefinitionManager( - // testcase, requestManager, matcherFactory, testContext); - // if (!manager.executeTestScenario(scenarioName, suiteName)) { - // Allure.step( - // "Execution failed for testcase: " - // + scenarioName - // + " for dependent case: " - // + testcase.getCaseUniqueName()); - // } - // }); - // } - // } - /** * Gets the expected body for the given test name. * - * @return The expected response object. + * @param apiContext * @param restelTestMethod + * @return The expected response object. */ - private Object getExpectedBody(RestelTestMethod restelTestMethod) { + private Object getExpectedBody(TestContext apiContext, RestelApiDefinition restelTestMethod) { // Check if expected body is Json type if (Objects.isNull(restelTestMethod.getExpectedResponse())) { - return replaceContextVariables(testContext, restelTestMethod.getExpectedResponse()); + return replaceContextVariables(apiContext, restelTestMethod.getExpectedResponse()); } if (ObjectMapperUtils.isJSONValid(restelTestMethod.getExpectedResponse().toString())) { boolean isArray = Utils.isArray(restelTestMethod.getExpectedResponse().toString()); if (!isArray) { return replaceContextVariables( - testContext, + apiContext, ObjectMapperUtils.convertToMap(restelTestMethod.getExpectedResponse().toString())); } else { return ObjectMapperUtils.convertToArray( - replaceContextVariables(testContext, restelTestMethod.getExpectedResponse()) - .toString()); + replaceContextVariables(apiContext, restelTestMethod.getExpectedResponse()).toString()); } } - return replaceContextVariables(testContext, restelTestMethod.getExpectedResponse()); + return replaceContextVariables(apiContext, restelTestMethod.getExpectedResponse()); } /** @@ -188,7 +169,7 @@ private Object getExpectedBody(RestelTestMethod restelTestMethod) { * @return {@link RESTRequest} instance corresponding to the given test name. * @param restelTestMethod */ - private RESTRequest createRequest(RestelTestMethod restelTestMethod) { + private RESTRequest createRequest(RestelApiDefinition restelTestMethod) { return RESTRequest.builder() .method(restelTestMethod.getRequestMethod()) .endpoint(getRequestURL(restelTestMethod)) @@ -201,14 +182,16 @@ private RESTRequest createRequest(RestelTestMethod restelTestMethod) { /** * Gets the expected headers for the given test name. * + * @param apiContext The context in which the variables in headers have to be resolved. + * @param restelTestMethod The test whose header has to be returned. * @return The expected response object. - * @param restelTestMethod */ - private Map getExpectedHeaders(RestelTestMethod restelTestMethod) { + private Map getExpectedHeaders( + TestContext apiContext, RestelApiDefinition restelTestMethod) { if (CollectionUtils.isEmpty(restelTestMethod.getExpectedHeader())) { return null; } - return replaceContextVariables(testContext, restelTestMethod.getExpectedHeader()); + return replaceContextVariables(apiContext, restelTestMethod.getExpectedHeader()); } /** @@ -217,7 +200,7 @@ private Map getExpectedHeaders(RestelTestMethod restelTestMethod * @return The request headers map. * @param restelTestMethod */ - private Map getRequestQueryParams(RestelTestMethod restelTestMethod) { + private Map getRequestQueryParams(RestelApiDefinition restelTestMethod) { if (CollectionUtils.isEmpty(restelTestMethod.getRequestQueryParams())) { return restelTestMethod.getRequestQueryParams(); } @@ -230,7 +213,7 @@ private Map getRequestQueryParams(RestelTestMethod restelTestMet * @return The request headers map. * @param restelTestMethod */ - private Map getRequestHeaders(RestelTestMethod restelTestMethod) { + private Map getRequestHeaders(RestelApiDefinition restelTestMethod) { if (CollectionUtils.isEmpty(restelTestMethod.getRequestHeaders())) { return restelTestMethod.getRequestHeaders(); } @@ -243,7 +226,7 @@ private Map getRequestHeaders(RestelTestMethod restelTestMethod) * @return The request headers map. * @param restelTestMethod */ - private Object getRequestBody(RestelTestMethod restelTestMethod) { + private Object getRequestBody(RestelApiDefinition restelTestMethod) { if (Objects.isNull(restelTestMethod.getRequestBodyParams())) { return restelTestMethod.getRequestBodyParams(); } @@ -264,17 +247,17 @@ private Object getRequestBody(RestelTestMethod restelTestMethod) { * @return The expected response object. * @param restelTestMethod */ - private String getRequestURL(RestelTestMethod restelTestMethod) { + private String getRequestURL(RestelApiDefinition restelTestMethod) { return replaceContextVariables(testContext, restelTestMethod.getRequestUrl()).toString(); } /** * Get the list of matchers that does the response body matching. * - * @return List of {@link ResponseComparator} instances for the given test name. * @param restelTestMethod + * @return List of {@link ResponseComparator} instances for the given test name. */ - private List getResponseMatchers(RestelTestMethod restelTestMethod) { + private List getResponseMatchers(RestelApiDefinition restelTestMethod) { return Collections.singletonList(getMatcher(restelTestMethod.getExpectedResponseMatcher())); } @@ -318,92 +301,32 @@ private boolean doMatching( /** * Gets the list of header matchers as configured in the test. * + * @param restelTestMethod The method to get headers for. * @return The list of matchers for the header as configured in the test. - * @param restelTestMethod */ - private List getHeaderMatchers(RestelTestMethod restelTestMethod) { + private List getHeaderMatchers(RestelApiDefinition restelTestMethod) { return Collections.singletonList(getMatcher(restelTestMethod.getExpectedHeaderMatcher())); } /** * Populates the given response to the context. * + * @param apiContext The context to which the response to be populated. Typically, the test api + * context. * @param response The response object to be populated to the context. - * @param restelTestMethod */ - private void populateResponseToContext( - RESTResponse response, RestelTestMethod restelTestMethod, String testName, String suiteName) { + private void populateResponseToContext(TestContext apiContext, RESTResponse response) { if (response.getResponse() != null) { if (!Objects.isNull(response.getResponse().getBody())) { if (StringUtils.isNotEmpty(response.getResponse().getBody().toString())) { - Map updatedContext = - getResponseUpdatedContext(response, restelTestMethod); - testContext.addValue(restelTestMethod.getApiUniqueName(), updatedContext); - } - } - } - } - private Map getResponseUpdatedContext( - RESTResponse response, RestelTestMethod restelTestMethod) { - Map existingContextMap; - Object body = response.getResponse().getBody(); - Object existingContextVal = testContext.resolveValue(restelTestMethod.getApiUniqueName()); - if (existingContextVal != null) { - existingContextMap = Maps.newHashMap((Map) existingContextVal); - if (ObjectMapperUtils.isJSONValid(body.toString())) { - existingContextMap.put(Constants.RESPONSE, ObjectMapperUtils.convertToMap(body.toString())); - } else { - existingContextMap.put(Constants.RESPONSE, body); - } - } else { - if (ObjectMapperUtils.isJSONValid(body.toString())) { - body = - Utils.isArray(body.toString()) - ? ObjectMapperUtils.convertToArray(body.toString()) - : ObjectMapperUtils.convertToMap(body.toString()); + Object opToStore = response.getResponse().getBody(); + if (ObjectMapperUtils.isJSONValid(opToStore.toString())) { + opToStore = ObjectMapperUtils.convertToMap(opToStore.toString()); + } + apiContext.addValue(Constants.RESPONSE, opToStore); + } } - existingContextMap = Maps.newHashMap(Map.of(Constants.RESPONSE, body)); - } - return existingContextMap; - } - - /** - * @param suiteName Test suite name. - * @param scenarioName Test suite execution name. - * @param testName test definition name. - * @param response response body - */ - private void addToGlobalContext( - String suiteName, String scenarioName, String testName, Map response) { - GlobalContext globalContext = GlobalContext.getInstance(); - Map testValue = Maps.newHashMap(Map.of(testName, response)); - Object scenarioContext = globalContext.getContextValues().get(scenarioName); - if (Objects.isNull(scenarioContext)) { - scenarioContext = new HashMap<>(); - } - - // If the testName params are already present for child test include the parent testDefinition - // params in to the List. - Map testRes = (Map) scenarioContext; - testRes.put(testName, response); - globalContext.addValue(scenarioName, testRes); - - if (Objects.isNull(globalContext.getContextValues().get(suiteName))) { - globalContext.addValue( - suiteName, - new ArrayList<>( - Collections.singletonList( - Maps.newHashMap( - Map.of(scenarioName, globalContext.getContextValues().get(scenarioName)))))); - } else { - // If suite param are already present for child testName include additional params from parent - // testName. - List> suiteRes = (List) globalContext.getContextValues().get(suiteName); - suiteRes.add( - Maps.newHashMap( - Map.of(scenarioName, globalContext.getContextValues().get(scenarioName)))); - globalContext.addValue(suiteName, suiteRes); } } @@ -413,7 +336,7 @@ private void addToGlobalContext( * @return List of {@link ResponseMiddleware} instances configured for the given test. * @param restelTestMethod */ - private List getPostRequestMiddlewares(RestelTestMethod restelTestMethod) { + private List getPostRequestMiddlewares(RestelApiDefinition restelTestMethod) { List middlewares = new ArrayList<>(); if (!Objects.isNull(restelTestMethod.getRequestPostCallHook())) { String path = restelTestMethod.getRequestPostCallHook().get(Constants.WRITE).toString(); @@ -430,7 +353,7 @@ private List getPostRequestMiddlewares(RestelTestMethod rest * @return List of {@link ResponseMiddleware} instances configured for the given test. * @param restelTestMethod */ - private List getPreRequestMiddlewares(RestelTestMethod restelTestMethod) { + private List getPreRequestMiddlewares(RestelApiDefinition restelTestMethod) { List middlewares = new ArrayList<>(); if (!Objects.isNull(restelTestMethod.getRequestPreCallHook())) { try { @@ -467,14 +390,13 @@ private List getPreRequestMiddlewares(RestelTestMethod restel /** * Populates the request to the context for the given test name * + * @param apiContext The context to which the response to be populated. Typically, the test api * + * context. * @param request The request for the given test. - * @param restelTestMethod */ - private void populateRequestToContext( - RESTRequest request, String testName, String suiteName, RestelTestMethod restelTestMethod) { + private void populateRequestToContext(TestContext apiContext, RESTRequest request) { if (request.getRequestBody() != null) { - Map reqMap = Map.of(Constants.REQUEST, request.getRequestBody()); - testContext.addValue(restelTestMethod.getApiUniqueName(), reqMap); + apiContext.addValue(Constants.REQUEST, request.getRequestBody()); } } } diff --git a/src/main/java/com/techconative/restel/core/managers/RestelTestManager.java b/src/main/java/com/techconative/restel/core/managers/RestelTestManager.java index 00c74f6..c74cc59 100644 --- a/src/main/java/com/techconative/restel/core/managers/RestelTestManager.java +++ b/src/main/java/com/techconative/restel/core/managers/RestelTestManager.java @@ -1,9 +1,6 @@ package com.techconative.restel.core.managers; -import com.techconative.restel.core.model.BaseConfiguration; -import com.techconative.restel.core.model.RestelSuite; -import com.techconative.restel.core.model.RestelTestMethod; -import com.techconative.restel.core.model.RestelTestScenario; +import com.techconative.restel.core.model.*; import com.techconative.restel.exception.RestelException; import java.util.*; import javax.annotation.PostConstruct; @@ -19,10 +16,10 @@ @Component public class RestelTestManager { - private List testDefinitions; + private List testDefinitions; private List testScenarios; - private Map indexedTestDefinitions; + private Map indexedTestDefinitions; private Map indexedTestScenarios; private Map indexedTestSuites; private BaseConfiguration baseConfig; @@ -46,9 +43,15 @@ private void configure() { indexedTestScenarios = new HashMap<>(); indexedTestSuites = new HashMap<>(); - for (RestelTestMethod testMethod : excelParseManager.getTestMethods()) { + for (RestelApiDefinition testMethod : excelParseManager.getTestMethods()) { indexedTestDefinitions.put(testMethod.getApiUniqueName(), testMethod); } + List testApiWrappers = excelParseManager.getTestApiWrappers(); + if (testApiWrappers != null) { + for (RestelApiDefinition testApiWrapper : testApiWrappers) { + indexedTestDefinitions.put(testApiWrapper.getApiUniqueName(), testApiWrapper); + } + } for (RestelSuite suite : excelParseManager.getSuites()) { indexedTestSuites.put(suite.getSuiteName(), suite); } @@ -133,17 +136,17 @@ private void isCyclic(RestelTestScenario executionGroup, List testMethods) { + private void validateDefinition(List testMethods) { testMethods.forEach(testMethod -> isCyclic(testMethod, testMethod.getDependentOn())); } /** * checks if there is any cyclic dependencies for testMethod. * - * @param testMethod {@link RestelTestMethod} - * @param childMethods list of child {@link RestelTestMethod} for testMethod. + * @param testMethod {@link RestelApiDefinition} + * @param childMethods list of child {@link RestelApiDefinition} for testMethod. */ - private void isCyclic(RestelTestMethod testMethod, List childMethods) { + private void isCyclic(RestelApiDefinition testMethod, List childMethods) { if (!CollectionUtils.isEmpty(childMethods)) { childMethods.forEach( m -> { @@ -165,10 +168,11 @@ public BaseConfiguration getBaseConfig() { /** * Gets the method with the given name * - * @param methodName The method name for which the {@link RestelTestMethod} to be searched for. - * @return {@link RestelTestMethod} with the given name + * @param methodName The method name for which the {@link RestelTestApiDefinition} to be searched + * for. + * @return {@link RestelTestApiDefinition} with the given name */ - public RestelTestMethod getTestMethod(String methodName) { + public RestelApiDefinition getTestMethod(String methodName) { return indexedTestDefinitions.get(methodName); } @@ -177,7 +181,7 @@ public RestelTestMethod getTestMethod(String methodName) { * * @return List of test methods */ - public List getTestDefintions() { + public List getTestDefintions() { return Collections.unmodifiableList(testDefinitions); } diff --git a/src/main/java/com/techconative/restel/core/model/RestelApiDefinition.java b/src/main/java/com/techconative/restel/core/model/RestelApiDefinition.java new file mode 100644 index 0000000..db59991 --- /dev/null +++ b/src/main/java/com/techconative/restel/core/model/RestelApiDefinition.java @@ -0,0 +1,77 @@ +package com.techconative.restel.core.model; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.List; +import java.util.Map; + +public interface RestelApiDefinition { + String getApiUniqueName(); + + void setApiUniqueName(String apiUniqueName); + + String getApiDescription(); + + void setApiDescription(String apiDescription); + + String getRequestUrl(); + + void setRequestUrl(String requestUrl); + + String getRequestMethod(); + + void setRequestMethod(String requestMethod); + + Map getRequestHeaders(); + + void setRequestHeaders(Map requestHeaders); + + Map getRequestQueryParams(); + + void setRequestQueryParams(Map requestQueryParams); + + Object getRequestBodyParams(); + + void setRequestBodyParams(Object requestBodyParams); + + JsonNode getRequestPreCallHook(); + + void setRequestPreCallHook(JsonNode requestPreCallHook); + + JsonNode getRequestPostCallHook(); + + void setRequestPostCallHook(JsonNode requestPostCallHook); + + Object getExpectedResponse(); + + void setExpectedResponse(Object expectedResponse); + + String getExpectedResponseMatcher(); + + void setExpectedResponseMatcher(String expectedResponseMatcher); + + Map getExpectedHeader(); + + void setExpectedHeader(Map expectedHeader); + + String getExpectedHeaderMatcher(); + + void setExpectedHeaderMatcher(String expectedHeaderMatcher); + + List getAcceptedStatusCodes(); + + void setAcceptedStatusCodes(List acceptedStatusCodes); + + List getDependentOn(); + + void setDependentOn(List dependentOn); + + List getParentTests(); + + void setParentTests(List parentTests); + + Map getApiParameters(); + + void setApiParameters(Map apiParameters); + + public void addParentTest(String parentTest); +} diff --git a/src/main/java/com/techconative/restel/core/model/RestelTestMethod.java b/src/main/java/com/techconative/restel/core/model/RestelTestApiDefinition.java similarity index 75% rename from src/main/java/com/techconative/restel/core/model/RestelTestMethod.java rename to src/main/java/com/techconative/restel/core/model/RestelTestApiDefinition.java index 88e1631..d7e6ef8 100644 --- a/src/main/java/com/techconative/restel/core/model/RestelTestMethod.java +++ b/src/main/java/com/techconative/restel/core/model/RestelTestApiDefinition.java @@ -6,14 +6,8 @@ import java.util.Map; import lombok.Data; -/** - * Represents a test as parsed from the excel sheet.
- * Note: Each of this will be translated to a TestNG test method, smallest unit of TestNG. - * - * @author kannanr - */ @Data -public class RestelTestMethod { +public class RestelTestApiDefinition implements RestelApiDefinition { private String apiUniqueName; private String apiDescription; private String requestUrl; @@ -28,10 +22,17 @@ public class RestelTestMethod { private Map expectedHeader; private String expectedHeaderMatcher; private List acceptedStatusCodes; - - @Deprecated private List dependentOn; + @Deprecated private List dependentOn; private List parentTests = new ArrayList<>(); + @Override + public Map getApiParameters() { + return null; + } + + @Override + public void setApiParameters(Map apiParameters) {} + @Deprecated public void addParentTest(String parentTest) { parentTests.add(parentTest); diff --git a/src/main/java/com/techconative/restel/core/model/RestelTestApiWrapper.java b/src/main/java/com/techconative/restel/core/model/RestelTestApiWrapper.java new file mode 100644 index 0000000..b8ef16b --- /dev/null +++ b/src/main/java/com/techconative/restel/core/model/RestelTestApiWrapper.java @@ -0,0 +1,179 @@ +package com.techconative.restel.core.model; + +import com.fasterxml.jackson.databind.JsonNode; +import java.util.List; +import java.util.Map; +import lombok.Data; + +@Data +public class RestelTestApiWrapper implements RestelApiDefinition { + private RestelTestApiDefinition testApiDefinition; + private String testApiWrapperName; + private String testApiWrapperDescription; + private Map apiParameters; + + @Override + public String getApiUniqueName() { + return testApiWrapperName; + } + + @Override + public void setApiUniqueName(String apiUniqueName) { + this.testApiWrapperName = apiUniqueName; + } + + @Override + public String getApiDescription() { + return testApiWrapperDescription; + } + + @Override + public void setApiDescription(String apiDescription) { + this.testApiWrapperDescription = apiDescription; + } + + @Override + public String getRequestUrl() { + return testApiDefinition.getRequestUrl(); + } + + @Override + public void setRequestUrl(String requestUrl) { + testApiDefinition.setRequestUrl(requestUrl); + } + + @Override + public String getRequestMethod() { + return testApiDefinition.getRequestMethod(); + } + + @Override + public void setRequestMethod(String requestMethod) { + testApiDefinition.setRequestMethod(requestMethod); + } + + @Override + public Map getRequestHeaders() { + return testApiDefinition.getRequestHeaders(); + } + + @Override + public void setRequestHeaders(Map requestHeaders) { + testApiDefinition.setRequestHeaders(requestHeaders); + } + + @Override + public Map getRequestQueryParams() { + return testApiDefinition.getRequestQueryParams(); + } + + @Override + public void setRequestQueryParams(Map requestQueryParams) { + testApiDefinition.setRequestQueryParams(requestQueryParams); + } + + @Override + public Object getRequestBodyParams() { + return testApiDefinition.getRequestBodyParams(); + } + + @Override + public void setRequestBodyParams(Object requestBodyParams) { + testApiDefinition.setRequestBodyParams(requestBodyParams); + } + + @Override + public JsonNode getRequestPreCallHook() { + return testApiDefinition.getRequestPreCallHook(); + } + + @Override + public void setRequestPreCallHook(JsonNode requestPreCallHook) { + testApiDefinition.setRequestPreCallHook(requestPreCallHook); + } + + @Override + public JsonNode getRequestPostCallHook() { + return testApiDefinition.getRequestPostCallHook(); + } + + @Override + public void setRequestPostCallHook(JsonNode requestPostCallHook) { + testApiDefinition.setRequestPostCallHook(requestPostCallHook); + } + + @Override + public Object getExpectedResponse() { + return testApiDefinition.getExpectedResponse(); + } + + @Override + public void setExpectedResponse(Object expectedResponse) { + testApiDefinition.setExpectedResponse(expectedResponse); + } + + @Override + public String getExpectedResponseMatcher() { + return testApiDefinition.getExpectedResponseMatcher(); + } + + @Override + public void setExpectedResponseMatcher(String expectedResponseMatcher) { + testApiDefinition.setExpectedResponseMatcher(expectedResponseMatcher); + } + + @Override + public Map getExpectedHeader() { + return testApiDefinition.getExpectedHeader(); + } + + @Override + public void setExpectedHeader(Map expectedHeader) { + testApiDefinition.setExpectedHeader(expectedHeader); + } + + @Override + public String getExpectedHeaderMatcher() { + return testApiDefinition.getExpectedHeaderMatcher(); + } + + @Override + public void setExpectedHeaderMatcher(String expectedHeaderMatcher) { + testApiDefinition.setExpectedHeaderMatcher(expectedHeaderMatcher); + } + + @Override + public List getAcceptedStatusCodes() { + return testApiDefinition.getAcceptedStatusCodes(); + } + + @Override + public void setAcceptedStatusCodes(List acceptedStatusCodes) { + testApiDefinition.setAcceptedStatusCodes(acceptedStatusCodes); + } + + @Override + public List getDependentOn() { + return testApiDefinition.getDependentOn(); + } + + @Override + public void setDependentOn(List dependentOn) { + testApiDefinition.setDependentOn(dependentOn); + } + + @Override + public List getParentTests() { + return testApiDefinition.getParentTests(); + } + + @Override + public void setParentTests(List parentTests) { + testApiDefinition.setParentTests(parentTests); + } + + @Override + public void addParentTest(String parentTest) { + testApiDefinition.addParentTest(parentTest); + } +} diff --git a/src/main/java/com/techconative/restel/core/model/comparator/ExactMatchComparator.java b/src/main/java/com/techconative/restel/core/model/comparator/ExactMatchComparator.java index d532318..352ab12 100644 --- a/src/main/java/com/techconative/restel/core/model/comparator/ExactMatchComparator.java +++ b/src/main/java/com/techconative/restel/core/model/comparator/ExactMatchComparator.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.techconative.restel.core.http.RESTResponse; +import com.techconative.restel.extension.jsoncompare.comparator.StringJsonComparator; import com.techconative.restel.utils.ObjectMapperUtils; import com.techconative.restel.utils.Utils; import io.qameta.allure.Allure; @@ -68,6 +69,7 @@ private void evalJson(Object restResponse, Object expectedOutput) { JSONCompare.assertEquals( expectedOutputnode, actualOutputNode, + new StringJsonComparator(), CompareMode.JSON_OBJECT_NON_EXTENSIBLE, CompareMode.JSON_ARRAY_STRICT_ORDER); } diff --git a/src/main/java/com/techconative/restel/core/model/comparator/PartialJsonMatchComparator.java b/src/main/java/com/techconative/restel/core/model/comparator/PartialJsonMatchComparator.java index f2aedb9..dd118d7 100644 --- a/src/main/java/com/techconative/restel/core/model/comparator/PartialJsonMatchComparator.java +++ b/src/main/java/com/techconative/restel/core/model/comparator/PartialJsonMatchComparator.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.techconative.restel.core.http.RESTResponse; import com.techconative.restel.exception.RestelException; +import com.techconative.restel.extension.jsoncompare.comparator.StringJsonComparator; import com.techconative.restel.utils.ObjectMapperUtils; import com.techconative.restel.utils.Utils; import io.qameta.allure.Allure; @@ -42,6 +43,6 @@ private void evalJson(Object restResponse, Object expectedOutput) { + " Expected :- " + expectedOutputnode.toPrettyString()); // First pass the expectedOutput(which is partial response) then actual in JsonCompare. - JSONCompare.assertEquals(expectedOutputnode, actualOutputNode); + JSONCompare.assertEquals(expectedOutputnode, actualOutputNode, new StringJsonComparator()); } } diff --git a/src/main/java/com/techconative/restel/core/parser/ParserEnums.java b/src/main/java/com/techconative/restel/core/parser/ParserEnums.java index f21d4e5..bcff848 100644 --- a/src/main/java/com/techconative/restel/core/parser/ParserEnums.java +++ b/src/main/java/com/techconative/restel/core/parser/ParserEnums.java @@ -2,6 +2,7 @@ public enum ParserEnums { TEST_API_DEFINITIONS("TestApiDefinitions"), + TEST_API_WRAPPERS("TestApiWrappers"), TEST_SUITES("TestSuites"), TEST_SCENARIOS("TestScenarios"), BASE_CONFIG("BaseConfig"); diff --git a/src/main/java/com/techconative/restel/core/parser/config/ParserConfig.java b/src/main/java/com/techconative/restel/core/parser/config/ParserConfig.java index ae847ca..09b602e 100644 --- a/src/main/java/com/techconative/restel/core/parser/config/ParserConfig.java +++ b/src/main/java/com/techconative/restel/core/parser/config/ParserConfig.java @@ -26,6 +26,8 @@ public class ParserConfig { Map.of( ParserEnums.TEST_API_DEFINITIONS.toString(), ParserEnums.TEST_API_DEFINITIONS.getValue(), + ParserEnums.TEST_API_WRAPPERS.toString(), + ParserEnums.TEST_API_WRAPPERS.getValue(), ParserEnums.TEST_SCENARIOS.toString(), ParserEnums.TEST_SCENARIOS.getValue(), ParserEnums.TEST_SUITES.toString(), @@ -93,6 +95,16 @@ private void init() { this.sheetMap.put(Constants.TEST_SCENARIOS, new SheetConfig(SheetType.COLUMN, fieldMap)); // Test_Scenarios sheet - end + // Test_Api_Wrapper sheet - start + fieldMap = new HashMap<>(); + fieldMap.put(Constants.WRAPPER_NAME, Functions.STRING_FUNCTION); + fieldMap.put(Constants.WRAPPER_DESCRIPTION, Functions.STRING_FUNCTION); + fieldMap.put(Constants.WRAPPER_PARAMS, Functions.STRING_FUNCTION); + fieldMap.put(Constants.API_NAME, Functions.STRING_FUNCTION); + + this.sheetMap.put(Constants.TEST_API_WRAPPERS, new SheetConfig(SheetType.COLUMN, fieldMap)); + // Test_Api_Wrapper sheet - end + // Test_Api_Definitions sheet - start fieldMap = new HashMap<>(); fieldMap.put(Constants.API_UNIQUE_NAME, Functions.STRING_FUNCTION); diff --git a/src/main/java/com/techconative/restel/core/parser/dto/TestApiWrappers.java b/src/main/java/com/techconative/restel/core/parser/dto/TestApiWrappers.java new file mode 100644 index 0000000..5520aeb --- /dev/null +++ b/src/main/java/com/techconative/restel/core/parser/dto/TestApiWrappers.java @@ -0,0 +1,12 @@ +package com.techconative.restel.core.parser.dto; + +import lombok.Data; + +/** TestApiWrappers type dto for sheet test_api_wrappers */ +@Data +public class TestApiWrappers { + private String wrapperName; + private String wrapperDescription; + private String wrapperParams; + private String apiName; +} diff --git a/src/main/java/com/techconative/restel/extension/jsoncompare/comparator/StringJsonComparator.java b/src/main/java/com/techconative/restel/extension/jsoncompare/comparator/StringJsonComparator.java new file mode 100644 index 0000000..e5da7a8 --- /dev/null +++ b/src/main/java/com/techconative/restel/extension/jsoncompare/comparator/StringJsonComparator.java @@ -0,0 +1,19 @@ +package com.techconative.restel.extension.jsoncompare.comparator; + +import ro.skyah.comparator.JsonComparator; + +/** + * String equals check. To override regex based pattern matching used in DefaultJsonComparator + * implementation. + */ +public class StringJsonComparator implements JsonComparator { + @Override + public boolean compareValues(Object expected, Object actual) { + return expected.equals(actual); + } + + @Override + public boolean compareFields(String expected, String actual) { + return expected.equals(actual); + } +} diff --git a/src/main/java/com/techconative/restel/testng/TestCaseExecutor.java b/src/main/java/com/techconative/restel/testng/TestCaseExecutor.java index 2c3e0a7..d617d93 100644 --- a/src/main/java/com/techconative/restel/testng/TestCaseExecutor.java +++ b/src/main/java/com/techconative/restel/testng/TestCaseExecutor.java @@ -5,10 +5,7 @@ import com.techconative.restel.core.managers.RequestManager; import com.techconative.restel.core.managers.RestelDefinitionManager; import com.techconative.restel.core.managers.RestelTestManager; -import com.techconative.restel.core.model.RestelSuite; -import com.techconative.restel.core.model.RestelTestMethod; -import com.techconative.restel.core.model.RestelTestScenario; -import com.techconative.restel.core.model.TestContext; +import com.techconative.restel.core.model.*; import com.techconative.restel.core.model.functions.RestelFunction; import com.techconative.restel.core.resolver.assertion.RestelAssertionResolver; import com.techconative.restel.core.resolver.function.RestelFunctionExecutor; @@ -29,7 +26,7 @@ /** * Executor takes care of resolving the variables, making API call along with the configured - * middlewares and does the testing as configured in the {@link RestelTestMethod}. + * middlewares and does the testing as configured in the {@link RestelTestApiDefinition}. */ @Slf4j public class TestCaseExecutor { @@ -39,7 +36,7 @@ public class TestCaseExecutor { @Autowired private MatcherFactory matcherFactory; - private List testDefinition; + private List testDefinition; private RestelTestScenario testExecutionDefinition; @@ -227,17 +224,17 @@ private void validateFunctionDataPattern(String data) { } /** - * @param testDefinitions {@link RestelTestMethod} + * @param testDefinitions {@link RestelTestApiDefinition} * @param definitionName name of the test definition which needs to be checked if its belongs to * the given test definition or its child test definition * @return Check whether the definitionName is equals to given tesDefinitions or its child * testDefinitions, */ - private boolean hasDefinitionName(RestelTestMethod testDefinitions, String definitionName) { + private boolean hasDefinitionName(RestelApiDefinition testDefinitions, String definitionName) { if (StringUtils.equals(testDefinitions.getApiUniqueName(), definitionName)) { return true; } else { - for (RestelTestMethod testMethod : testDefinitions.getDependentOn()) { + for (RestelApiDefinition testMethod : testDefinitions.getDependentOn()) { if (hasDefinitionName(testMethod, definitionName)) { return true; } diff --git a/src/main/java/com/techconative/restel/utils/Constants.java b/src/main/java/com/techconative/restel/utils/Constants.java index 488b505..94cd7ca 100644 --- a/src/main/java/com/techconative/restel/utils/Constants.java +++ b/src/main/java/com/techconative/restel/utils/Constants.java @@ -60,6 +60,13 @@ private Constants() {} public static final String TEST_ASSERTION = "assertion"; public static final String TEST_FUNCTION = "function"; + // Test api wrapper + public static final String TEST_API_WRAPPERS = "test_api_wrappers"; + public static final String WRAPPER_NAME = "wrapper_name"; + public static final String WRAPPER_DESCRIPTION = "wrapper_description"; + public static final String WRAPPER_PARAMS = "wrapper_params"; + public static final String API_NAME = "api_name"; + // Test Definition public static final String API_UNIQUE_NAME = "api_unique_name"; public static final String API_DESCRIPTION = "api_description"; diff --git a/src/main/java/com/techconative/restel/utils/RestelUtils.java b/src/main/java/com/techconative/restel/utils/RestelUtils.java index 0107489..97594df 100644 --- a/src/main/java/com/techconative/restel/utils/RestelUtils.java +++ b/src/main/java/com/techconative/restel/utils/RestelUtils.java @@ -7,6 +7,7 @@ import com.techconative.restel.core.model.assertion.RestelAssertion; import com.techconative.restel.core.model.functions.FunctionOps; import com.techconative.restel.core.model.functions.RestelFunction; +import com.techconative.restel.core.parser.dto.*; import com.techconative.restel.core.parser.dto.BaseConfig; import com.techconative.restel.core.parser.dto.TestApiDefinitions; import com.techconative.restel.core.parser.dto.TestScenarios; @@ -27,12 +28,12 @@ private RestelUtils() {} * creates ReselTestMethod from testDefinition. * * @param testDefinition The {@link TestApiDefinitions} Object. - * @return {@link RestelTestMethod} + * @return {@link RestelTestApiDefinition} */ - public static RestelTestMethod createTestMethod( + public static RestelTestApiDefinition createTestMethod( TestApiDefinitions testDefinition, BaseConfiguration baseConfig) { validate(testDefinition); - RestelTestMethod testMethod = new RestelTestMethod(); + RestelTestApiDefinition testMethod = new RestelTestApiDefinition(); testMethod.setApiUniqueName(testDefinition.getApiUniqueName()); testMethod.setApiDescription(testDefinition.getApiDescription()); @@ -80,6 +81,21 @@ public static RestelTestMethod createTestMethod( return testMethod; } + public static RestelTestApiWrapper createTestWrapper( + TestApiWrappers testWrapper, Map testMethodMap) { + validate(testWrapper); + Map params = + StringUtils.isEmpty(testWrapper.getWrapperParams()) + ? null + : ObjectMapperUtils.convertToMap(testWrapper.getWrapperParams()); + RestelTestApiWrapper restelTestWrapper = new RestelTestApiWrapper(); + restelTestWrapper.setTestApiWrapperName(testWrapper.getWrapperName()); + restelTestWrapper.setTestApiWrapperDescription(testWrapper.getWrapperDescription()); + restelTestWrapper.setTestApiDefinition(testMethodMap.get(testWrapper.getApiName())); + restelTestWrapper.setApiParameters(params); + return restelTestWrapper; + } + /** * Creates a Restelsuite from testSuite * @@ -238,6 +254,21 @@ private static void validate(TestApiDefinitions testApiDefinitions) { } } + private static void validate(TestApiWrappers testApiWrappers) { + if (StringUtils.isEmpty(testApiWrappers.getApiName())) { + throw new RestelException("TEST_API_NAME_EMPTY"); + } + if (StringUtils.isEmpty(testApiWrappers.getWrapperName())) { + throw new RestelException("TEST_API_WRAPPER_NAME_EMPTY"); + } + if (StringUtils.isEmpty(testApiWrappers.getWrapperDescription())) { + throw new RestelException("TEST_API_WRAPPER_DESC_EMPTY"); + } + if (StringUtils.isEmpty(testApiWrappers.getWrapperParams())) { + throw new RestelException("TEST_API_WRAPPER_PARAM_EMPTY"); + } + } + private static void validate(TestSuites testSuites) { if (StringUtils.isEmpty(testSuites.getSuiteUniqueName())) { throw new RestelException("SUITE_NAME_EMPTY"); diff --git a/src/test/java/restel/core/manager/RestelDefinitionManagerTest.java b/src/test/java/restel/core/manager/RestelDefinitionManagerTest.java index c541b72..1711a63 100644 --- a/src/test/java/restel/core/manager/RestelDefinitionManagerTest.java +++ b/src/test/java/restel/core/manager/RestelDefinitionManagerTest.java @@ -10,7 +10,7 @@ import com.techconative.restel.core.managers.RequestManager; import com.techconative.restel.core.managers.RestelDefinitionManager; import com.techconative.restel.core.model.GlobalContext; -import com.techconative.restel.core.model.RestelTestMethod; +import com.techconative.restel.core.model.RestelTestApiDefinition; import com.techconative.restel.core.model.TestContext; import com.techconative.restel.core.model.comparator.ExactMatchComparator; import com.techconative.restel.core.model.comparator.NoOPMatcher; @@ -71,7 +71,7 @@ public void testExecuteTest() throws NoSuchFieldException { @Test public void testExecuteTestReqBody() throws NoSuchFieldException { - RestelTestMethod method = createTestDef(); + RestelTestApiDefinition method = createTestDef(); method.setRequestHeaders(new HashMap<>()); method.setRequestBodyParams("Body"); FieldSetter.setField( @@ -89,7 +89,7 @@ public void testExecuteTestReqBody() throws NoSuchFieldException { @Test public void testExecuteTestResBody() throws NoSuchFieldException { - RestelTestMethod method = createTestDef(); + RestelTestApiDefinition method = createTestDef(); method.setRequestHeaders(new HashMap<>()); method.setExpectedResponse("{\"key\": \"value\"}"); FieldSetter.setField( @@ -109,7 +109,7 @@ public void testExecuteTestResBody() throws NoSuchFieldException { @Test public void testExecuteTestOauth2Client() throws NoSuchFieldException { - RestelTestMethod method = createTestDef(); + RestelTestApiDefinition method = createTestDef(); method.setRequestHeaders(new HashMap<>()); method.setExpectedResponse("{\"key\": \"value\"}"); method.setRequestPreCallHook(clientCredential()); @@ -130,7 +130,7 @@ public void testExecuteTestOauth2Client() throws NoSuchFieldException { @Test public void testExecuteTestOauth2Resource() throws NoSuchFieldException { - RestelTestMethod method = createTestDef(); + RestelTestApiDefinition method = createTestDef(); method.setRequestHeaders(new HashMap<>()); method.setExpectedResponse("{\"key\": \"value\"}"); method.setRequestPreCallHook(resourceOwner()); @@ -151,7 +151,7 @@ public void testExecuteTestOauth2Resource() throws NoSuchFieldException { @Test public void testExecuteTestResBodyJson() throws NoSuchFieldException { - RestelTestMethod method = createTestDef(); + RestelTestApiDefinition method = createTestDef(); method.setRequestHeaders(new HashMap<>()); method.setExpectedResponse(Map.of("key", "value")); FieldSetter.setField( @@ -171,7 +171,7 @@ public void testExecuteTestResBodyJson() throws NoSuchFieldException { @Test public void testRequestResponseAvailableFromGlobalContext() throws NoSuchFieldException { - RestelTestMethod method = createTestDef(); + RestelTestApiDefinition method = createTestDef(); method.setRequestHeaders(new HashMap<>()); method.setExpectedResponse(Map.of("key", "value")); FieldSetter.setField( @@ -203,7 +203,7 @@ public void testRequestResponseAvailableFromGlobalContext() throws NoSuchFieldEx @Test(expected = AssertionError.class) public void testExecuteTestStatusCodeEmpty() throws NoSuchFieldException { - RestelTestMethod method = createTestDef(); + RestelTestApiDefinition method = createTestDef(); method.setRequestHeaders(new HashMap<>()); method.setRequestBodyParams("Body"); method.setAcceptedStatusCodes(Arrays.asList("500")); @@ -221,7 +221,7 @@ public void testExecuteTestStatusCodeEmpty() throws NoSuchFieldException { @Test public void testExecuteTestStatusCodeParameter() throws NoSuchFieldException { - RestelTestMethod method = createTestDef(); + RestelTestApiDefinition method = createTestDef(); method.setRequestHeaders(new HashMap<>()); method.setRequestBodyParams("Body"); method.setAcceptedStatusCodes(Arrays.asList("200", "${accepted_status_code}")); @@ -240,8 +240,8 @@ public void testExecuteTestStatusCodeParameter() throws NoSuchFieldException { Assert.assertTrue(manager.executeTestScenario("Sample", "suite")); } - private RestelTestMethod createTestDef() { - RestelTestMethod definitions = new RestelTestMethod(); + private RestelTestApiDefinition createTestDef() { + RestelTestApiDefinition definitions = new RestelTestApiDefinition(); definitions.setApiUniqueName(defaultApiName); definitions.setRequestUrl("/test"); definitions.setRequestQueryParams(Map.of("k", "v")); diff --git a/src/test/java/restel/core/model/RestelTestMethodTest.java b/src/test/java/restel/core/model/RestelTestMethodTest.java index b665f02..494fa3f 100644 --- a/src/test/java/restel/core/model/RestelTestMethodTest.java +++ b/src/test/java/restel/core/model/RestelTestMethodTest.java @@ -1,6 +1,6 @@ package restel.core.model; -import com.techconative.restel.core.model.RestelTestMethod; +import com.techconative.restel.core.model.RestelTestApiDefinition; import com.techconative.restel.utils.ObjectMapperUtils; import java.util.Arrays; import java.util.Map; @@ -10,7 +10,7 @@ public class RestelTestMethodTest { @Test public void testRestelTestMethod() { - RestelTestMethod testMethod = new RestelTestMethod(); + RestelTestApiDefinition testMethod = new RestelTestApiDefinition(); testMethod.setAcceptedStatusCodes(Arrays.asList("200")); Assert.assertEquals(Arrays.asList("200"), testMethod.getAcceptedStatusCodes()); @@ -61,8 +61,8 @@ public void testRestelTestMethod() { testMethod.setRequestUrl("url"); Assert.assertEquals("url", testMethod.getRequestUrl()); - Assert.assertNotEquals(new RestelTestMethod(), testMethod); - Assert.assertNotEquals(testMethod.hashCode(), new RestelTestMethod().hashCode()); + Assert.assertNotEquals(new RestelTestApiDefinition(), testMethod); + Assert.assertNotEquals(testMethod.hashCode(), new RestelTestApiDefinition().hashCode()); Assert.assertNotNull(testMethod.toString()); } } diff --git a/src/test/java/restel/core/model/RestelTestWrapperTest.java b/src/test/java/restel/core/model/RestelTestWrapperTest.java new file mode 100644 index 0000000..c35e4c3 --- /dev/null +++ b/src/test/java/restel/core/model/RestelTestWrapperTest.java @@ -0,0 +1,68 @@ +package restel.core.model; + +import com.techconative.restel.core.model.RestelTestApiDefinition; +import com.techconative.restel.core.model.RestelTestApiWrapper; +import com.techconative.restel.utils.ObjectMapperUtils; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import org.junit.Assert; +import org.junit.Test; + +public class RestelTestWrapperTest { + @Test + public void testRestelTestMethod() { + RestelTestApiWrapper restelTestWrapper = new RestelTestApiWrapper(); + + RestelTestApiDefinition restelTestApiDefinition = new RestelTestApiDefinition(); + restelTestApiDefinition.setAcceptedStatusCodes(List.of("200")); + restelTestApiDefinition.setApiDescription("desc"); + restelTestApiDefinition.setApiUniqueName("name"); + restelTestApiDefinition.setDependentOn(null); + restelTestApiDefinition.setExpectedHeader(Map.of("k", "v")); + restelTestApiDefinition.setExpectedHeaderMatcher("matcher"); + restelTestApiDefinition.setExpectedResponse("response"); + restelTestApiDefinition.setExpectedResponseMatcher("resp_match"); + restelTestApiDefinition.setRequestBodyParams("body_param"); + restelTestApiDefinition.setRequestHeaders(Map.of("k", "v")); + restelTestApiDefinition.setRequestPostCallHook( + ObjectMapperUtils.convertToJsonNode(Map.of("name", "post"))); + restelTestApiDefinition.setRequestPreCallHook( + ObjectMapperUtils.convertToJsonNode(Map.of("name", "pre"))); + restelTestApiDefinition.setRequestQueryParams(Map.of("k", "v")); + restelTestApiDefinition.setRequestUrl("url"); + restelTestApiDefinition.setRequestMethod("GET"); + restelTestWrapper.setTestApiDefinition(restelTestApiDefinition); + + restelTestWrapper.setTestApiWrapperName("testApiWrapperName"); + Assert.assertEquals("testApiWrapperName", restelTestWrapper.getApiUniqueName()); + + restelTestWrapper.setTestApiWrapperDescription("a read test that returns 500 status code"); + Assert.assertEquals( + "a read test that returns 500 status code", restelTestWrapper.getApiDescription()); + + restelTestWrapper.setApiParameters(Map.of("param1", "param2")); + Assert.assertEquals(Map.of("param1", "param2"), restelTestWrapper.getApiParameters()); + + Assert.assertEquals(Arrays.asList("200"), restelTestWrapper.getAcceptedStatusCodes()); + Assert.assertEquals(null, restelTestWrapper.getDependentOn()); + Assert.assertEquals(Map.of("k", "v"), restelTestWrapper.getExpectedHeader()); + Assert.assertEquals("matcher", restelTestWrapper.getExpectedHeaderMatcher()); + Assert.assertEquals("response", restelTestWrapper.getExpectedResponse()); + Assert.assertEquals("resp_match", restelTestWrapper.getExpectedResponseMatcher()); + Assert.assertEquals("body_param", restelTestWrapper.getRequestBodyParams()); + Assert.assertEquals(Map.of("k", "v"), restelTestWrapper.getRequestHeaders()); + Assert.assertEquals("GET", restelTestWrapper.getRequestMethod()); + Assert.assertEquals( + ObjectMapperUtils.convertToJsonNode(Map.of("name", "post")), + restelTestWrapper.getRequestPostCallHook()); + Assert.assertEquals( + ObjectMapperUtils.convertToJsonNode(Map.of("name", "pre")), + restelTestWrapper.getRequestPreCallHook()); + Assert.assertEquals(Map.of("k", "v"), restelTestWrapper.getRequestQueryParams()); + Assert.assertEquals("url", restelTestWrapper.getRequestUrl()); + + Assert.assertNotEquals(restelTestWrapper.hashCode(), new RestelTestWrapperTest().hashCode()); + Assert.assertNotNull(restelTestWrapper.toString()); + } +} diff --git a/src/test/java/restel/core/model/comparators/StringJsonMatchComparatorTest.java b/src/test/java/restel/core/model/comparators/StringJsonMatchComparatorTest.java new file mode 100644 index 0000000..5f1f439 --- /dev/null +++ b/src/test/java/restel/core/model/comparators/StringJsonMatchComparatorTest.java @@ -0,0 +1,30 @@ +package restel.core.model.comparators; + +import com.techconative.restel.extension.jsoncompare.comparator.StringJsonComparator; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class StringJsonMatchComparatorTest { + + private StringJsonComparator comparator; + + @Before + public void setup() { + comparator = new StringJsonComparator(); + } + + @Test + public void testCompareFields() { + Assert.assertTrue( + comparator.compareFields( + "([A-Z][A-Z0-9]*)\\b[^>]*>(.*?)]*>(.*?)]*>(.*?)]*>(.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