Skip to content

Commit

Permalink
# 42 - Add ExtractJson method - string from json path (#43)
Browse files Browse the repository at this point in the history
* # 42 - Add ExtractJson method - string from json path
- Introduced method 'string-from-json-path' in group: 'extractJson'.
- Improved error message in method 'body-contains-text' in group: 'assert'
  • Loading branch information
miroslavpojer authored Nov 24, 2023
1 parent 2ec99bb commit 048f44b
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 10 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,13 @@ line break:\\nAnd a tab:\\tEnd .... Matches the string "line break
- jsonKey: The key in the JSON object from which to extract the string.
- runtimeCacheLevel: The expiration level to use when storing the extracted string in the runtime cache. [Global, Suite, Test]

- `extractJson.string-from-json-path`
- description: Extracts a string from a JSON response at a given json path and stores it in a runtime cache with a given key and expiration level.
- arguments:
- cacheKey: The key to use when storing the extracted string in the runtime cache.
- jsonPath: The json path in the JSON from which to extract the string.
- runtimeCacheLevel: The expiration level to use when storing the extracted string in the runtime cache. [Global, Suite, Test]


## Known issue
- Error in Response.perform.
Expand Down
2 changes: 1 addition & 1 deletion testApi/src/main/resources/schema/after.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@
"properties": {
"method": {
"type": "string",
"enum": ["assert.response-time-is-below", "assert.response-time-is-above", "assert.status-code-equals", "assert.status-code-is-success", "assert.status-code-is-client-error", "assert.status-code-is-server-error", "assert.header-exists", "assert.header-value-equals", "assert.content-type-is-json", "assert.content-type-is-xml", "assert.content-type-is-html", "assert.cookie-exists", "assert.cookie-value-equals", "assert.cookie-is-secured", "assert.cookie-is-not-secured", "assert.body-equals", "assert.body-contains-text", "assert.body-is-empty", "assert.body-is-not-empty", "assert.body-length-equals", "assert.body-starts-with", "assert.body-ends-with", "assert.body-matches-regex", "assert.body-json-is-json-array", "assert.body-json-is-json-object", "assert.body-json-path-exists", "log.error", "log.warn", "log.info", "log.debug", "log.log-info-response", "extractJson.string-from-list"],
"enum": ["assert.response-time-is-below", "assert.response-time-is-above", "assert.status-code-equals", "assert.status-code-is-success", "assert.status-code-is-client-error", "assert.status-code-is-server-error", "assert.header-exists", "assert.header-value-equals", "assert.content-type-is-json", "assert.content-type-is-xml", "assert.content-type-is-html", "assert.cookie-exists", "assert.cookie-value-equals", "assert.cookie-is-secured", "assert.cookie-is-not-secured", "assert.body-equals", "assert.body-contains-text", "assert.body-is-empty", "assert.body-is-not-empty", "assert.body-length-equals", "assert.body-starts-with", "assert.body-ends-with", "assert.body-matches-regex", "assert.body-json-is-json-array", "assert.body-json-is-json-object", "assert.body-json-path-exists", "log.error", "log.warn", "log.info", "log.debug", "log.log-info-response", "extractJson.string-from-list", "extractJson.string-from-json-path"],
"description": "The method to be used for the response action. Restricted to specific values."
}
},
Expand Down
2 changes: 1 addition & 1 deletion testApi/src/main/resources/schema/before.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@
"properties": {
"method": {
"type": "string",
"enum": ["assert.response-time-is-below", "assert.response-time-is-above", "assert.status-code-equals", "assert.status-code-is-success", "assert.status-code-is-client-error", "assert.status-code-is-server-error", "assert.header-exists", "assert.header-value-equals", "assert.content-type-is-json", "assert.content-type-is-xml", "assert.content-type-is-html", "assert.cookie-exists", "assert.cookie-value-equals", "assert.cookie-is-secured", "assert.cookie-is-not-secured", "assert.body-equals", "assert.body-contains-text", "assert.body-is-empty", "assert.body-is-not-empty", "assert.body-length-equals", "assert.body-starts-with", "assert.body-ends-with", "assert.body-matches-regex", "assert.body-json-is-json-array", "assert.body-json-is-json-object", "assert.body-json-path-exists", "log.error", "log.warn", "log.info", "log.debug", "log.log-info-response", "extractJson.string-from-list"],
"enum": ["assert.response-time-is-below", "assert.response-time-is-above", "assert.status-code-equals", "assert.status-code-is-success", "assert.status-code-is-client-error", "assert.status-code-is-server-error", "assert.header-exists", "assert.header-value-equals", "assert.content-type-is-json", "assert.content-type-is-xml", "assert.content-type-is-html", "assert.cookie-exists", "assert.cookie-value-equals", "assert.cookie-is-secured", "assert.cookie-is-not-secured", "assert.body-equals", "assert.body-contains-text", "assert.body-is-empty", "assert.body-is-not-empty", "assert.body-length-equals", "assert.body-starts-with", "assert.body-ends-with", "assert.body-matches-regex", "assert.body-json-is-json-array", "assert.body-json-is-json-object", "assert.body-json-path-exists", "log.error", "log.warn", "log.info", "log.debug", "log.log-info-response", "extractJson.string-from-list", "extractJson.string-from-json-path"],
"description": "The method to be used for the response action."
}
},
Expand Down
2 changes: 1 addition & 1 deletion testApi/src/main/resources/schema/suite.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@
"properties": {
"method": {
"type": "string",
"enum": ["assert.response-time-is-below", "assert.response-time-is-above", "assert.status-code-equals", "assert.status-code-is-success", "assert.status-code-is-client-error", "assert.status-code-is-server-error", "assert.header-exists", "assert.header-value-equals", "assert.content-type-is-json", "assert.content-type-is-xml", "assert.content-type-is-html", "assert.cookie-exists", "assert.cookie-value-equals", "assert.cookie-is-secured", "assert.cookie-is-not-secured", "assert.body-equals", "assert.body-contains-text", "assert.body-is-empty", "assert.body-is-not-empty", "assert.body-length-equals", "assert.body-starts-with", "assert.body-ends-with", "assert.body-matches-regex", "assert.body-json-is-json-array", "assert.body-json-is-json-object", "assert.body-json-path-exists", "log.error", "log.warn", "log.info", "log.debug", "log.log-info-response", "extractJson.string-from-list"],
"enum": ["assert.response-time-is-below", "assert.response-time-is-above", "assert.status-code-equals", "assert.status-code-is-success", "assert.status-code-is-client-error", "assert.status-code-is-server-error", "assert.header-exists", "assert.header-value-equals", "assert.content-type-is-json", "assert.content-type-is-xml", "assert.content-type-is-html", "assert.cookie-exists", "assert.cookie-value-equals", "assert.cookie-is-secured", "assert.cookie-is-not-secured", "assert.body-equals", "assert.body-contains-text", "assert.body-is-empty", "assert.body-is-not-empty", "assert.body-length-equals", "assert.body-starts-with", "assert.body-ends-with", "assert.body-matches-regex", "assert.body-json-is-json-array", "assert.body-json-is-json-object", "assert.body-json-path-exists", "log.error", "log.warn", "log.info", "log.debug", "log.log-info-response", "extractJson.string-from-list", "extractJson.string-from-json-path"],
"description": "The method to be used for the response action."
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ object AssertionResponseAction extends ResponseActions {
*/
private def assertBodyContainsText(response: Response, text: String): Try[Unit] = Try {
if (!response.body.contains(text)) {
val errMsg = s"Expected body to contain $text"
val errMsg = s"Expected body to contains: '$text'. Content of body: '${response.body}'"
Logger.error(errMsg)
throw AssertionException(errMsg)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ import africa.absa.testing.scapi.rest.response.action.types.ExtractJsonResponseA
import africa.absa.testing.scapi.utils.cache.RuntimeCache
import africa.absa.testing.scapi.utils.validation.ContentValidator
import africa.absa.testing.scapi.{AssertionException, UndefinedResponseActionTypeException}
import com.jayway.jsonpath.{Configuration, JsonPath, Option => JsonPathOption}
import spray.json._

import scala.util.Try


/**
* ExtractJsonResponseAction is an object that extends ResponsePerformer.
* It is designed to extract specific data from a JSON response and perform validations.
Expand All @@ -45,6 +47,7 @@ object ExtractJsonResponseAction extends ResponseActions {
val action = fromString(responseAction.name.toLowerCase).getOrElse(None)
action match {
case StringFromList => validateStringFromList(responseAction)
case StringFromJsonPath => validateStringFromJsonPath(responseAction)
case _ => throw UndefinedResponseActionTypeException(responseAction.name)
}
}
Expand All @@ -62,16 +65,26 @@ object ExtractJsonResponseAction extends ResponseActions {
val action = fromString(responseAction.name.toLowerCase).getOrElse(None)
action match {
case StringFromList =>
val cacheKey = responseAction.params("cacheKey")
val listIndex = responseAction.params("listIndex").toInt
val (cacheKey, cacheLevel) = getCacheKeyAndLevel(responseAction)
val jsonKey = responseAction.params("jsonKey")
val cacheLevel = responseAction.params("cacheLevel")

val listIndex = responseAction.params("listIndex").toInt
stringFromList(response, cacheKey, listIndex, jsonKey, cacheLevel)

case StringFromJsonPath =>
val (cacheKey, cacheLevel) = getCacheKeyAndLevel(responseAction)
val jsonPath = responseAction.params("jsonPath")
stringFromJsonPath(response, jsonPath, cacheKey, cacheLevel)

case _ => throw UndefinedResponseActionTypeException(s"Unsupported assertion[group: extract]: ${responseAction.name}")
}
}

private def getCacheKeyAndLevel(responseAction: ResponseAction) = {
val cacheKey = responseAction.params("cacheKey")
val cacheLevel = responseAction.params("cacheLevel")
(cacheKey, cacheLevel)
}

/*
dedicated actions
*/
Expand Down Expand Up @@ -137,4 +150,45 @@ object ExtractJsonResponseAction extends ResponseActions {
ContentValidator.validateIntegerString(listIndex, s"ExtractJson.$StringFromList.listIndex")
}

/**
* This method validates the parameters of the StringFromJsonPath type of response action.
*
* @param assertion The ResponseAction instance containing the response action details.
*/
private def validateStringFromJsonPath(assertion: ResponseAction): Unit = {
val cacheKey = assertion.params.getOrElse("cacheKey", throw new IllegalArgumentException(s"Missing required 'cacheKey' parameter for extract $StringFromJsonPath logic"))
val jsonPath = assertion.params.getOrElse("jsonPath", throw new IllegalArgumentException(s"Missing required 'jsonPath' parameter for extract $StringFromJsonPath logic"))
val cacheLevel = assertion.params.getOrElse("cacheLevel", throw new IllegalArgumentException(s"Missing required 'cacheLevel' parameter for extract $StringFromJsonPath logic"))

ContentValidator.validateNonEmptyString(jsonPath, s"ExtractJson.$StringFromJsonPath.jsonPath")
ContentValidator.validateNonEmptyString(cacheKey, s"ExtractJson.$StringFromJsonPath.cacheKey")
ContentValidator.validateNonEmptyString(cacheLevel, s"ExtractJson.$StringFromJsonPath.cacheLevel")
}

/**
* This method extracts a string from a JSON array response at a given json path
* and stores it in a runtime cache with a given key and expiration level.
*
* @param response The Response instance containing the JSON body.
* @param jsonPath The json path in the JSON from which to extract the string.
* @param cacheKey The key to use when storing the extracted string in the runtime cache.
* @param runtimeCacheLevel The expiration level to use when storing the extracted string in the runtime cache.
* @return A Try[Unit] indicating whether the string extraction and caching operation was successful or not.
*/
private def stringFromJsonPath(response: Response, jsonPath: String, cacheKey: String, runtimeCacheLevel: String): Try[Unit] = Try {
val configuration = Configuration.defaultConfiguration().addOptions(JsonPathOption.SUPPRESS_EXCEPTIONS)
val json = JsonPath.using(configuration).parse(response.body)
val extractedValueOpt = Option(json.read[Any](jsonPath))

extractedValueOpt match {
case Some(extractedValue) =>
RuntimeCache.put(key = cacheKey, value = extractedValue.toString, RuntimeCache.determineLevel(runtimeCacheLevel))
Logger.debug(s"Extracted string '${extractedValue.toString}' from json path '$jsonPath' and stored it in runtime cache with key '$cacheKey' and expiration level '$runtimeCacheLevel'.")

case None =>
val errMsg = s"Expected JSON path '$jsonPath' does not exist in the response body"
Logger.error(errMsg)
throw AssertionException(errMsg)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ object ExtractJsonResponseActionType extends Enumeration {
type ExtractJsonResponseActionType = Value

val StringFromList: ExtractJsonResponseActionType.Value = Value("string-from-list")
val StringFromJsonPath: ExtractJsonResponseActionType.Value = Value("string-from-json-path")

def fromString(s: String): Option[ExtractJsonResponseActionType] = Try(this.withName(s)).toOption
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -773,7 +773,7 @@ class ResponseAssertionsTest extends FunSuite {
val result = AssertionResponseAction.performResponseAction(response, bodyContainsTextResponseAction)
result match {
case Failure(exception) =>
assert(clue(exception.getMessage) == "Assertion failed: Expected body to contain dummies")
assert(clue(exception.getMessage) == "Assertion failed: Expected body to contains: 'dummies'. Content of body: 'This is a dummy body'")
case _ =>
fail("Expected a failure of test but test Passed")
}
Expand Down
Loading

0 comments on commit 048f44b

Please sign in to comment.