All Downloads are FREE. Search and download functionalities are using the official Maven repository.

application.ValidateViaLogs.kt Maven / Gradle / Ivy

There is a newer version: 1.3.39
Show newest version
package application

import `in`.specmatic.conversions.OpenApiSpecification
import `in`.specmatic.core.*
import `in`.specmatic.core.log.logger
import `in`.specmatic.core.pattern.parsedJSONArray
import `in`.specmatic.core.value.JSONObjectValue
import `in`.specmatic.core.value.Value
import `in`.specmatic.mock.NoMatchingScenario
import `in`.specmatic.mock.ScenarioStub
import `in`.specmatic.mock.mockFromJSON
import picocli.CommandLine
import java.io.File
import java.net.URI
import java.util.concurrent.Callable

@CommandLine.Command(
    name = "validate-via-logs",
    description = ["Validate a contract against log files to ensure that the contract matches all valid logs, stubs and requests"],
    mixinStandardHelpOptions = true
)
class ValidateViaLogs : Callable {
    @CommandLine.Parameters(index = "0", description = ["Contract path"])
    lateinit var contractPath: String

    @CommandLine.Parameters(index = "1", description = ["Log directory path"])
    lateinit var logDirPath: String

    @CommandLine.Parameters(index = "2", description = ["urlPathFilter"])
    lateinit var urlPathFilter: String

    override fun call() {
        val feature = OpenApiSpecification.fromFile(contractPath).toFeature()

        val httpUrlMatchers: List> = findMatchingURLMatchers(feature)

        val requestLogs = parsedJSONArray(File(logDirPath).readText())

        val stubsMatchingURLs: List> = requestLogs.list.mapNotNull { requestLog ->
            val log = requestLog as JSONObjectValue

            when (val path = log.findFirstChildByPath("http-request.path")?.toStringLiteral()) {
                "/_specmatic/expectations" -> stubFromExpectationLog(log, httpUrlMatchers)
                null -> null
                else -> stubFromRequestLog(path, httpUrlMatchers, log)?.let { Pair(mockFromJSON(log.jsonObject), it) }
            }
        }

        val matchResults = stubsMatchingURLs.map { (container, matchingScenario) ->
            try {
                feature.matchingStub(matchingScenario.request, matchingScenario.response)
                Triple(Result.Success(), container, matchingScenario)
            } catch (e: NoMatchingScenario) {
                Triple(e.results.toResultIfAny(), container, matchingScenario)
            }
        }


        val matchFailures: List> = matchResults.filter {
            it.first is Result.Failure
        }

        val countOfSuccessfulMatches = matchResults.size - matchFailures.size

        if (matchFailures.isEmpty())
            logger.log("The contract is compatible with all stubs")
        else {
            logger.log("The contract is not compatible with the following:")
            matchFailures.forEach { (result, container, _) ->
                logger.log("--------------------")
                logger.log(container.toJSON().toStringLiteral())
                logger.log(result.reportString())
                logger.newLine()
            }
        }

        logger.newLine()
        logger.log("Matched ${matchResults.size}, Succeeded: $countOfSuccessfulMatches, Failed: ${matchFailures.size}")
        logger.newLine()
    }

    private fun stubFromExpectationLog(
        log: JSONObjectValue,
        httpUrlMatchers: List>
    ): Pair? {
        val status = log.findFirstChildByPath("http-response.status")?.toStringLiteral()

        if (status != "200")
            return null

        log.findFirstChildByPath("http-request.body.http-request.path")?.let { stubRequestPathLog ->
            if (log.findFirstChildByPath("http-response.status")?.toStringLiteral() == "200")
                return stubFromExpectationLog(stubRequestPathLog, log, httpUrlMatchers)?.let {
                    Pair(
                        mockFromJSON(log.jsonObject),
                        it
                    )
                }
        }

        return null
    }

    private fun stubFromExpectationLog(
        stubRequestPathLog: Value,
        log: JSONObjectValue,
        httpUrlMatchers: List>
    ): ScenarioStub? {
        val path = stubRequestPathLog.toStringLiteral()

        val body = log.findFirstChildByPath("http-request.body") as JSONObjectValue

        if (httpUrlMatchers.any { (matcher, resolver) ->
                matcher.matches(path, resolver) is Result.Success
            })
            return mockFromJSON(body.jsonObject)

        return null
    }

    private fun stubFromRequestLog(
        path: String,
        httpUrlMatchers: List>,
        log: JSONObjectValue
    ): ScenarioStub? {
        val headers = log.findFirstChildByPath("http-response.headers") as JSONObjectValue?
        val specmaticResult = headers?.jsonObject?.get("X-Specmatic-Result")?.toStringLiteral()

        if (specmaticResult != "success")
            return null

        if (httpUrlMatchers.any { (matcher, resolver) ->
                matcher.matches(path, resolver) is Result.Success
            })
            return mockFromJSON(log.jsonObject)

        return null
    }

    private fun findMatchingURLMatchers(feature: Feature): List> {
        val httpUrlMatchers: List> = feature.scenarios.map {
            Pair(it.httpRequestPattern.httpPathPattern, it.resolver)
        }.map { (matcher, resolver) ->
            Triple(matcher, matcher?.matches(URI.create(urlPathFilter)), resolver)
        }.filter {
            it.second is Result.Success
        }.map {
            Pair(it.first!!, it.third)
        }
        return httpUrlMatchers
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy