Skip to content

Commit

Permalink
#17 - Assertion rename and simplification (#25)
Browse files Browse the repository at this point in the history
* #17 - Assertion rename and simplification
* Renamed assertion json tag to responseAction.
* Refactored related logic and doc.
* Simplified json files for ResponseAction - ResponseActions group and name by method with meaning "group.name". In code this value is separated for more intuitive usage.
  • Loading branch information
miroslavpojer authored Aug 25, 2023
1 parent 335be93 commit 6bb4b24
Show file tree
Hide file tree
Showing 30 changed files with 323 additions and 339 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ScAPI

### How to run api tests
TBD - work in progress
TODO - work in progress

### How to run unit tests
Run unit tests from path `{project-root}`
Expand Down
18 changes: 7 additions & 11 deletions testApi/src/main/resources/schema/after.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,18 @@
"$ref": "#/definitions/Action"
}
},
"assertions": {
"responseActions": {
"type": "array",
"items": {
"$ref": "#/definitions/Assertion"
"$ref": "#/definitions/ResponseAction"
}
}
},
"required": [
"name",
"headers",
"actions",
"assertions"
"responseActions"
],
"title": "Method"
},
Expand Down Expand Up @@ -100,14 +100,11 @@
],
"title": "Action"
},
"Assertion": {
"ResponseAction": {
"type": "object",
"additionalProperties": false,
"properties": {
"group": {
"type": "string"
},
"name": {
"method": {
"type": "string"
}
},
Expand All @@ -117,11 +114,10 @@
}
},
"required": [
"group",
"name",
"method",
"param_1"
],
"title": "Assertion"
"title": "ResponseAction"
},
"Param": {
"type": "object",
Expand Down
18 changes: 7 additions & 11 deletions testApi/src/main/resources/schema/before.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,18 +42,18 @@
"$ref": "#/definitions/Action"
}
},
"assertions": {
"responseActions": {
"type": "array",
"items": {
"$ref": "#/definitions/Assertion"
"$ref": "#/definitions/ResponseAction"
}
}
},
"required": [
"name",
"headers",
"actions",
"assertions"
"responseActions"
],
"title": "Method"
},
Expand Down Expand Up @@ -100,14 +100,11 @@
],
"title": "Action"
},
"Assertion": {
"ResponseAction": {
"type": "object",
"additionalProperties": false,
"properties": {
"group": {
"type": "string"
},
"name": {
"method": {
"type": "string"
}
},
Expand All @@ -117,11 +114,10 @@
}
},
"required": [
"group",
"name",
"method",
"param_1"
],
"title": "Assertion"
"title": "ResponseAction"
},
"Param": {
"type": "object",
Expand Down
18 changes: 7 additions & 11 deletions testApi/src/main/resources/schema/suite.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,10 @@
"$ref": "#/definitions/Action"
}
},
"assertions": {
"responseActions": {
"type": "array",
"items": {
"$ref": "#/definitions/Assertion"
"$ref": "#/definitions/ResponseAction"
}
}
},
Expand All @@ -63,7 +63,7 @@
"categories",
"headers",
"actions",
"assertions"
"responseActions"
],
"title": "Test"
},
Expand Down Expand Up @@ -110,14 +110,11 @@
],
"title": "Action"
},
"Assertion": {
"ResponseAction": {
"type": "object",
"additionalProperties": false,
"properties": {
"group": {
"type": "string"
},
"name": {
"method": {
"type": "string"
}
},
Expand All @@ -127,11 +124,10 @@
}
},
"required": [
"group",
"name",
"method",
"param_1"
],
"title": "Assertion"
"title": "ResponseAction"
},
"Param": {
"type": "object",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ case class SuiteLoadFailed(detail: String) extends Exception(s"Problems during p
case class UndefinedHeaderType(undefinedType: String)
extends Exception(s"Undefined Header content type: '$undefinedType'")

case class UndefinedAssertionType(undefinedType: String)
extends Exception(s"Undefined Assertion content type: '$undefinedType'")
case class UndefinedResponseActionType(undefinedType: String)
extends Exception(s"Undefined response action content type: '$undefinedType'")

case class ContentValidationFailed(value: String, message: String)
extends Exception(s"Content validation failed for value: '$value': $message")
Original file line number Diff line number Diff line change
Expand Up @@ -198,35 +198,39 @@ case class Action private(methodName: String, url: String, body: Option[String]
}

/**
* Case class that represents Assertion.
* This class is used to hold test assertions.
* Case class that represents ResponseAction.
* This class is used to hold test response action.
* It implements the `ReferenceResolver` trait to support resolution of reference constants.
*
* @constructor create a new Assertion with a name and value.
* @param name the name of the assertion.
* @param params the map containing the parameters of the assertion. Each key-value pair in the map
* @constructor create a new ResponseAction with a name and value.
* @param method the "group and name" of the response action.
* @param params the map containing the parameters of the response action. Each key-value pair in the map
* represents a parameter name and its corresponding value.
*/
case class Assertion private(group: String,
name: String,
params: Map[String, String]) extends ReferenceResolver {
case class ResponseAction private(method: String,
params: Map[String, String]) extends ReferenceResolver {

private def splitter: Seq[String] = method.split("\\.").toSeq

def group : String = splitter.head
def name : String = splitter.tail.head

/**
* Method to resolve references.
*
* @param references the map of references that may be used to resolve references in the value.
* @return a new Assertion instance with resolved references.
* @return a new ResponseAction instance with resolved references.
*/
def resolveReferences(references: Map[String, String]): Assertion = this.copy(
def resolveReferences(references: Map[String, String]): ResponseAction = this.copy(
params = this.params.map { case (k, v) => k -> getResolved(v, references) }
)

/**
* Method to resolve references using Runtime Cache. This method is used when the resolution of a reference is not possible at compile-time.
*
* @return A new Assertion instance with resolved references.
* @return A new ResponseAction instance with resolved references.
*/
def resolveByRuntimeCache(): Assertion = this.copy(
def resolveByRuntimeCache(): ResponseAction = this.copy(
params = this.params.map { case (k, v) => k -> RuntimeCache.resolve(v) }
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,5 @@ trait Requestable {

def actions: Set[Action]

def assertions: Set[Assertion]
def responseActions: Set[ResponseAction]
}
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ object SuiteFactory {

/**
* This method validates the content of a SuiteBundle.
* It checks the suite's headers, body, parameters, and assertions.
* It checks the suite's headers, body, parameters, and response action.
*
* @param suiteBundle The SuiteBundle to be validated.
*/
Expand All @@ -269,7 +269,7 @@ object SuiteFactory {
test.headers.foreach(header => RequestHeaders.validateContent(header))
RequestBody.validateContent(test.actions.head.body)
RequestParams.validateContent(test.actions.head.params)
test.assertions.foreach(assertion => Response.validate(assertion))
test.responseActions.foreach(responseAction => Response.validate(responseAction))
})
}
}
Expand All @@ -281,7 +281,7 @@ object SuiteJsonProtocol extends DefaultJsonProtocol {
implicit val headerFormat: RootJsonFormat[Header] = jsonFormat2(Header)
implicit val paramFormat: RootJsonFormat[Param] = jsonFormat2(Param)
implicit val testActionFormat: RootJsonFormat[Action] = jsonFormat4(Action)
implicit val assertionFormat: RootJsonFormat[Assertion] = AssertionJsonProtocol.AssertionJsonFormat
implicit val assertionFormat: RootJsonFormat[ResponseAction] = ResponseActionJsonProtocol.ResponseActionJsonFormat
implicit val suiteTestFormat: RootJsonFormat[SuiteTestScenario] = jsonFormat6(SuiteTestScenario)
implicit val methodFormat: RootJsonFormat[Method] = jsonFormat4(Method)
implicit val suiteFormat: RootJsonFormat[Suite] = jsonFormat2(Suite)
Expand All @@ -301,7 +301,7 @@ object SuiteBeforeJsonProtocol extends DefaultJsonProtocol {
implicit val headerFormat: RootJsonFormat[Header] = jsonFormat2(Header)
implicit val paramFormat: RootJsonFormat[Param] = jsonFormat2(Param)
implicit val testActionFormat: RootJsonFormat[Action] = jsonFormat4(Action)
implicit val assertionFormat: RootJsonFormat[Assertion] = AssertionJsonProtocol.AssertionJsonFormat
implicit val responseActionFormat: RootJsonFormat[ResponseAction] = ResponseActionJsonProtocol.ResponseActionJsonFormat
implicit val methodFormat: RootJsonFormat[Method] = jsonFormat4(Method)
implicit val suiteBeforeFormat: RootJsonFormat[SuiteBefore] = jsonFormat2(SuiteBefore)
}
Expand All @@ -313,29 +313,28 @@ object SuiteAfterJsonProtocol extends DefaultJsonProtocol {
implicit val headerFormat: RootJsonFormat[Header] = jsonFormat2(Header)
implicit val paramFormat: RootJsonFormat[Param] = jsonFormat2(Param)
implicit val testActionFormat: RootJsonFormat[Action] = jsonFormat4(Action)
implicit val assertionFormat: RootJsonFormat[Assertion] = AssertionJsonProtocol.AssertionJsonFormat
implicit val assertionFormat: RootJsonFormat[ResponseAction] = ResponseActionJsonProtocol.ResponseActionJsonFormat
implicit val methodFormat: RootJsonFormat[Method] = jsonFormat4(Method)
implicit val suiteAfterFormat: RootJsonFormat[SuiteAfter] = jsonFormat2(SuiteAfter)
}

/**
* Object that provides implicit JSON format for Assertion class.
* Object that provides implicit JSON format for ResponseAction class.
*/
object AssertionJsonProtocol extends DefaultJsonProtocol {
object ResponseActionJsonProtocol extends DefaultJsonProtocol {

implicit object AssertionJsonFormat extends RootJsonFormat[Assertion] {
def write(a: Assertion): JsObject = JsObject(
("group" -> JsString(a.group)) +:
("name" -> JsString(a.name)) +:
implicit object ResponseActionJsonFormat extends RootJsonFormat[ResponseAction] {
def write(a: ResponseAction): JsObject = JsObject(
("method" -> JsString(a.method)) +:
a.params.view.mapValues(JsString(_)).toSeq: _*
)

def read(value: JsValue): Assertion = {
value.asJsObject.getFields("group", "name") match {
case Seq(JsString(group), JsString(name)) =>
def read(value: JsValue): ResponseAction = {
value.asJsObject.getFields("method") match {
case Seq(JsString(method)) =>
val params = value.asJsObject.fields.view.filterKeys(_.startsWith("param_")).toMap
Assertion(group, name, params.map { case (k, v) => k -> v.convertTo[String] })
case _ => throw new DeserializationException("Assertion expected")
ResponseAction(method, params.map { case (k, v) => k -> v.convertTo[String] })
case _ => throw DeserializationException("Assertion expected")
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,20 @@

package africa.absa.testing.scapi.model

import africa.absa.testing.scapi.json.{Action, Assertion, Header, Requestable}
import africa.absa.testing.scapi.json.{Action, Header, Requestable, ResponseAction}

/**
* Case class that represents a single method.
*
* @param name The name of the method.
* @param headers The set of header options for the method.
* @param actions The set of action objects for the method.
* @param assertions The set of assertion objects for the method.
* @param responseActions The set of responseAction objects for the method.
*/
case class Method(name: String,
headers: Set[Header],
actions: Set[Action],
assertions: Set[Assertion]) extends Requestable {
responseActions: Set[ResponseAction]) extends Requestable {
/**
* Method to resolve references within the Method instance.
*
Expand All @@ -41,7 +41,7 @@ case class Method(name: String,
name,
headers.map(c => c.resolveReferences(references)),
actions.map(c => c.resolveReferences(references)),
assertions.map(c => c.resolveReferences(references))
responseActions.map(c => c.resolveReferences(references))
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

package africa.absa.testing.scapi.model

import africa.absa.testing.scapi.json.{Action, Assertion, Header, Requestable}
import africa.absa.testing.scapi.json.{Action, Header, Requestable, ResponseAction}

/**
* Case class that represents a suite test scenario.
Expand All @@ -25,14 +25,14 @@ import africa.absa.testing.scapi.json.{Action, Assertion, Header, Requestable}
* @param categories The required test categories of the test scenario.
* @param headers The set of header options for the test scenario.
* @param actions The set of action objects for the test scenario.
* @param assertions The set of assertion objects for the test scenario.
* @param responseActions The set of responseAction objects for the test scenario.
* @param only The control if test should be only be running when set to true.
*/
case class SuiteTestScenario(name: String,
categories: Set[String],
headers: Set[Header],
actions: Set[Action],
assertions: Set[Assertion],
responseActions: Set[ResponseAction],
only: Option[Boolean] = Some(false)) extends Requestable {
/**
* Method to resolve references within the SuiteTestScenario instance.
Expand All @@ -46,7 +46,7 @@ case class SuiteTestScenario(name: String,
categories,
headers.map(c => c.resolveReferences(references)),
actions.map(c => c.resolveReferences(references)),
assertions.map(c => c.resolveReferences(references)),
responseActions.map(c => c.resolveReferences(references)),
only
)
}
Expand Down
Loading

0 comments on commit 6bb4b24

Please sign in to comment.