Skip to content

Commit

Permalink
feat: implemented content type matcher
Browse files Browse the repository at this point in the history
  • Loading branch information
Ronald Holshausen committed Jun 18, 2020
1 parent 9fb20fd commit c171797
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package au.com.dius.pact.core.matchers

import au.com.dius.pact.core.model.ContentType
import au.com.dius.pact.core.model.OptionalBody
import au.com.dius.pact.core.model.matchingrules.ContentTypeMatcher
import au.com.dius.pact.core.model.matchingrules.DateMatcher
import au.com.dius.pact.core.model.matchingrules.IncludeMatcher
import au.com.dius.pact.core.model.matchingrules.MatchingRule
Expand All @@ -17,6 +20,9 @@ import au.com.dius.pact.core.model.matchingrules.TypeMatcher
import au.com.dius.pact.core.support.json.JsonValue
import mu.KotlinLogging
import org.apache.commons.lang3.time.DateUtils
import org.apache.tika.config.TikaConfig
import org.apache.tika.io.TikaInputStream
import org.apache.tika.metadata.Metadata
import org.w3c.dom.Element
import org.w3c.dom.Text
import java.math.BigDecimal
Expand Down Expand Up @@ -120,6 +126,7 @@ fun <M : Mismatch> domatch(
matchMaxType(matcher.max, path, expected, actual, mismatchFn)
is IncludeMatcher -> matchInclude(matcher.value, path, expected, actual, mismatchFn)
is NullMatcher -> matchNull(path, actual, mismatchFn)
is ContentTypeMatcher -> matchContentType(path, ContentType.fromString(matcher.contentType), actual, mismatchFn)
else -> matchEquality(path, expected, actual, mismatchFn)
}
}
Expand Down Expand Up @@ -414,3 +421,32 @@ fun <M : Mismatch> matchNull(path: List<String>, actual: Any?, mismatchFactory:
listOf(mismatchFactory.create(null, actual, "Expected ${valueOf(actual)} to be null", path))
}
}

private val tika = TikaConfig()

fun <M : Mismatch> matchContentType(
path: List<String>,
contentType: ContentType,
actual: Any?,
mismatchFactory: MismatchFactory<M>
): List<M> {
val binaryData = when (actual) {
is ByteArray -> actual
else -> actual.toString().toByteArray(contentType.asCharset())
}
val metadata = Metadata()
val stream = TikaInputStream.get(binaryData)
val detectedContentType = stream.use { stream ->
tika.detector.detect(stream, metadata)
}
val matches = contentType.equals(detectedContentType)
logger.debug { "Matching binary contents by content type: " +
"expected '$contentType', detected '$detectedContentType' -> $matches" }
return if (matches) {
emptyList()
} else {
listOf(mismatchFactory.create(contentType.toString(), actual,
"Expected binary contents to have content type '$contentType' " +
"but detected contents was '$detectedContentType'", path))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package au.com.dius.pact.core.matchers

import spock.lang.Specification
import au.com.dius.pact.core.model.ContentType

class ContentTypeMatcherSpec extends Specification {
def 'matching binary data where content type matches'() {
given:
def path = []
def contentType = ContentType.fromString('application/pdf')
def actual = ContentTypeMatcherSpec.getResourceAsStream('/sample.pdf').bytes
def mismatchFactory = [create: { p1, p2, message, p3 ->
new BodyMismatch(p1, p2, message, 'path')
}] as MismatchFactory

when:
def result = MatcherExecutorKt.matchContentType(path, contentType, actual, mismatchFactory)

then:
result.empty
}

def 'matching binary data where content type does not match'() {
given:
def path = []
def contentType = ContentType.fromString('application/pdf')
def actual = '"I\'m a PDF!"'.bytes
def mismatchFactory = [create: { p1, p2, message, p3 ->
new BodyMismatch(p1, p2, message, 'path')
}] as MismatchFactory

when:
def result = MatcherExecutorKt.matchContentType(path, contentType, actual, mismatchFactory)

then:
!result.empty
result*.mismatch == [
'Expected binary contents to have content type \'application/pdf\' but detected contents was \'text/plain\'']
}
}
Binary file added core/matchers/src/test/resources/sample.pdf
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import java.nio.charset.Charset
private val jsonRegex = Regex(".*json")
private val xmlRegex = Regex(".*xml")

data class ContentType(val contentType: MediaType?) {
class ContentType(val contentType: MediaType?) {

constructor(contentType: String) : this(MediaType.parse(contentType))

Expand Down Expand Up @@ -74,6 +74,19 @@ data class ContentType(val contentType: MediaType?) {
contentType.baseType.type == "multipart"
else false

override fun equals(other: Any?): Boolean {
return when {
this === other -> true
other is MediaType -> contentType == other
other !is ContentType -> false
else -> contentType == other.contentType
}
}

override fun hashCode(): Int {
return contentType?.hashCode() ?: 0
}

companion object : KLogging() {
@JvmStatic
fun fromString(contentType: String?) = if (contentType.isNullOrEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,13 @@ object ValuesMatcher : MatchingRule {
override fun toMap(spec: PactSpecVersion) = mapOf("match" to "values")
}

/**
* Content type matcher. Matches the content type of binary data
*/
data class ContentTypeMatcher @JvmOverloads constructor (val contentType: String) : MatchingRule {
override fun toMap(spec: PactSpecVersion) = mapOf("match" to "contentType", "value" to contentType)
}

data class MatchingRuleGroup @JvmOverloads constructor(
val rules: MutableList<MatchingRule> = mutableListOf(),
val ruleLogic: RuleLogic = RuleLogic.AND
Expand Down Expand Up @@ -209,6 +216,7 @@ data class MatchingRuleGroup @JvmOverloads constructor(
if (map.containsKey(DATE)) DateMatcher(map[DATE].toString())
else DateMatcher()
"values" -> ValuesMatcher
"contentType" -> ContentTypeMatcher(map["value"].toString())
else -> {
logger.warn { "Unrecognised matcher ${map[MATCH]}, defaulting to equality matching" }
EqualsMatcher
Expand Down

0 comments on commit c171797

Please sign in to comment.