Skip to content

Commit

Permalink
Merge pull request #1468 from znsio/stateful-test-cleanup
Browse files Browse the repository at this point in the history
Add tests for stateful contract testing, payload processing, and assertions.
  • Loading branch information
joelrosario authored Dec 10, 2024
2 parents ef81464 + 8bbdda6 commit dd53f92
Show file tree
Hide file tree
Showing 8 changed files with 862 additions and 16 deletions.
18 changes: 12 additions & 6 deletions core/src/main/kotlin/io/specmatic/test/ExamplePreProcessor.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.specmatic.test

import io.ktor.http.*
import io.ktor.util.*
import io.specmatic.core.*
import io.specmatic.core.log.consoleLog
Expand All @@ -18,12 +19,12 @@ enum class StoreType { REPLACE, MERGE }

object ExampleProcessor {
private var runningEntity: Map<String, Value> = mapOf()
private val factStore: Map<String, Value> = loadConfig().toFactStore("CONFIG")
private var factStore: Map<String, Value> = loadConfig().toFactStore("CONFIG")

private fun loadConfig(): JSONObjectValue {
val configFilePath = runCatching {
loadSpecmaticConfig().additionalExampleParamsFilePath
}.getOrNull() ?: return JSONObjectValue(emptyMap())
}.getOrElse { SpecmaticConfig().additionalExampleParamsFilePath } ?: return JSONObjectValue(emptyMap())

val configFile = File(configFilePath)
if (!configFile.exists()) {
Expand All @@ -41,8 +42,13 @@ object ExampleProcessor {
}
}

fun cleanStores() {
factStore = loadConfig().toFactStore("CONFIG")
runningEntity = emptyMap()
}

private fun defaultIfNotExits(lookupKey: String, type: SubstitutionType = SubstitutionType.SIMPLE): Value {
throw ContractException("Could not resolve $lookupKey, key does not exist in fact store")
throw ContractException(breadCrumb = lookupKey, errorMessage = "Could not resolve ${lookupKey.quote()}, key does not exist in fact store")
}

private fun ifNotExitsToLookupPattern(lookupKey: String, type: SubstitutionType = SubstitutionType.SIMPLE): Value {
Expand All @@ -61,14 +67,14 @@ object ExampleProcessor {
if (type != SubstitutionType.DELAYED_RANDOM) return returnValue

val arrayValue = returnValue as? JSONArrayValue
?: throw ContractException("$key is not an array in fact store")
?: throw ContractException(breadCrumb = key, errorMessage = "${key.quote()} is not an array in fact store")

val entityKey = "ENTITY.${key.substringAfterLast('.')}"
val entityValue = factStore[entityKey] ?: runningEntity[entityKey]
?: throw ContractException("Could not resolve $entityKey in fact store")
?: throw ContractException(breadCrumb = entityKey, errorMessage = "Could not resolve ${entityKey.quote()} in fact store")

val filteredList = arrayValue.list.filterNot { it.toStringLiteral() == entityValue.toStringLiteral() }.ifEmpty {
throw ContractException("Couldn't pick a random value from $key that was not equal to $entityValue")
throw ContractException(breadCrumb = key, errorMessage = "Couldn't pick a random value from ${key.quote()} that was not equal to ${entityValue.displayableValue()}")
}

return filteredList.random()
Expand Down
11 changes: 6 additions & 5 deletions core/src/main/kotlin/io/specmatic/test/asserts/AssertArray.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
package io.specmatic.test.asserts

import io.ktor.http.*
import io.specmatic.core.Result
import io.specmatic.core.value.JSONArrayValue
import io.specmatic.core.value.Value

enum class ArrayAssertType { ARRAY_HAS }

class AssertArray(override val prefix: String, override val key: String, private val lookupKey: String, private val arrayAssertType: ArrayAssertType): Assert {
class AssertArray(override val prefix: String, override val key: String, val lookupKey: String, val arrayAssertType: ArrayAssertType): Assert {
override fun assert(currentFactStore: Map<String, Value>, actualFactStore: Map<String, Value>): Result {
val prefixValue = currentFactStore[prefix] ?: return Result.Failure(breadCrumb = prefix, message = "Could not resolve $prefix in current fact store")
val prefixValue = currentFactStore[prefix] ?: return Result.Failure(breadCrumb = prefix, message = "Could not resolve ${prefix.quote()} in current fact store")
if (prefixValue !is JSONArrayValue) {
return Result.Failure(breadCrumb = prefix, message = "Expected $prefix to be an array")
return Result.Failure(breadCrumb = prefix, message = "Expected ${prefix.quote()} to be an array")
}

return when (arrayAssertType) {
Expand All @@ -19,13 +20,13 @@ class AssertArray(override val prefix: String, override val key: String, private
}

private fun assertArrayHas(prefixValue: JSONArrayValue, currentFactStore: Map<String, Value>, actualFactStore: Map<String, Value>): Result {
val expectedValue = actualFactStore[lookupKey] ?: return Result.Failure(breadCrumb = lookupKey, message = "Could not resolve $lookupKey in actual fact store")
val expectedValue = actualFactStore[lookupKey] ?: return Result.Failure(breadCrumb = lookupKey, message = "Could not resolve ${lookupKey.quote()} in actual fact store")
val asserts = AssertComparison(prefix = prefix, key = key, lookupKey = lookupKey, isEqualityCheck = true).dynamicAsserts(prefixValue)
val result = asserts.map { it.assert(currentFactStore, actualFactStore) }.toResultIfAny()

return when (result) {
is Result.Success -> Result.Success()
is Result.Failure -> Result.Failure("None of the values in $prefix[*].$key matched $lookupKey of value ${expectedValue.displayableValue()}", breadCrumb = prefix)
is Result.Failure -> Result.Failure("None of the values in \"$prefix[*].$key\" matched ${lookupKey.quote()} of value ${expectedValue.displayableValue()}", breadCrumb = prefix)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
package io.specmatic.test.asserts

import io.ktor.http.*
import io.specmatic.core.Result
import io.specmatic.core.value.Value

val ASSERT_PATTERN = Regex("^\\$(\\w+)\\((.*)\\)$")

class AssertComparison(override val prefix: String, override val key: String, val lookupKey: String, private val isEqualityCheck: Boolean): Assert {
class AssertComparison(override val prefix: String, override val key: String, val lookupKey: String, val isEqualityCheck: Boolean): Assert {

override fun assert(currentFactStore: Map<String, Value>, actualFactStore: Map<String, Value>): Result {
val prefixValue = currentFactStore[prefix] ?: return Result.Failure(breadCrumb = prefix, message = "Could not resolve $prefix in current fact store")
val expectedValue = actualFactStore[lookupKey] ?: return Result.Failure(breadCrumb = lookupKey, message = "Could not resolve $lookupKey in actual fact store")
val prefixValue = currentFactStore[prefix] ?: return Result.Failure(breadCrumb = prefix, message = "Could not resolve ${prefix.quote()} in current fact store")
val expectedValue = actualFactStore[lookupKey] ?: return Result.Failure(breadCrumb = lookupKey, message = "Could not resolve ${lookupKey.quote()} in actual fact store")

val dynamicList = dynamicAsserts(prefixValue)
val results = dynamicList.map { newAssert ->
val finalKey = "${newAssert.prefix}.${newAssert.key}"
val actualValue = currentFactStore[finalKey] ?: return@map Result.Failure(breadCrumb = finalKey, message = "Could not resolve $finalKey in current fact store")
val actualValue = currentFactStore[finalKey] ?: return@map Result.Failure(breadCrumb = finalKey, message = "Could not resolve ${finalKey.quote()} in current fact store")
assert(finalKey, actualValue, expectedValue)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package io.specmatic.test.asserts

import io.ktor.http.*
import io.specmatic.core.Result
import io.specmatic.core.value.JSONObjectValue
import io.specmatic.core.value.Value

class AssertConditional(override val prefix: String, val conditionalAsserts: List<Assert>, val thenAsserts: List<Assert>, val elseAsserts: List<Assert>): Assert {

override fun assert(currentFactStore: Map<String, Value>, actualFactStore: Map<String, Value>): Result {
val prefixValue = currentFactStore[prefix] ?: return Result.Failure(breadCrumb = prefix, message = "Could not resolve $prefix in current fact store")
val prefixValue = currentFactStore[prefix] ?: return Result.Failure(breadCrumb = prefix, message = "Could not resolve ${prefix.quote()} in current fact store")

val dynamicAsserts = this.dynamicAsserts(prefixValue)
val results = dynamicAsserts.map {
Expand Down
Loading

0 comments on commit dd53f92

Please sign in to comment.