in.specmatic.test.reports.coverage.OpenApiCoverageReportInput.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of junit5-support Show documentation
Show all versions of junit5-support Show documentation
Run contracts as tests in Junit tests using Specmatic.
Deprecation Notice for group ID "in.specmatic"
******************************************************************************************************
Updates for "junit5-support" will no longer be available under the deprecated group ID "in.specmatic".
Please update your dependencies to use the new group ID "io.specmatic".
******************************************************************************************************
package `in`.specmatic.test.reports.coverage
import `in`.specmatic.conversions.SERVICE_TYPE_HTTP
import `in`.specmatic.conversions.convertPathParameterStyle
import `in`.specmatic.core.TestResult
import `in`.specmatic.test.API
import `in`.specmatic.test.TestResultRecord
import `in`.specmatic.test.reports.coverage.console.OpenAPICoverageConsoleReport
import `in`.specmatic.test.reports.coverage.console.OpenApiCoverageConsoleRow
import `in`.specmatic.test.reports.coverage.console.Remarks
import `in`.specmatic.test.reports.coverage.json.OpenApiCoverageJsonReport
import kotlin.math.min
import kotlin.math.roundToInt
class OpenApiCoverageReportInput(
private var configFilePath:String,
internal val testResultRecords: MutableList = mutableListOf(),
private val applicationAPIs: MutableList = mutableListOf(),
private val excludedAPIs: MutableList = mutableListOf(),
private val allEndpoints: MutableList = mutableListOf(),
private var endpointsAPISet: Boolean = false
) {
fun addTestReportRecords(testResultRecord: TestResultRecord) {
testResultRecords.add(testResultRecord)
}
fun addAPIs(apis: List) {
applicationAPIs.addAll(apis)
}
fun addExcludedAPIs(apis: List){
excludedAPIs.addAll(apis)
}
fun addEndpoints(endpoints: List) {
allEndpoints.addAll(endpoints)
}
fun setEndpointsAPIFlag(isSet: Boolean) {
endpointsAPISet = isSet
}
fun generate(): OpenAPICoverageConsoleReport {
val testResults = testResultRecords.filter { testResult -> excludedAPIs.none { it == testResult.path } }
val testResultsWithNotImplementedEndpoints = identifyTestsThatFailedBecauseOfEndpointsThatWereNotImplemented(testResults)
var allTests = addTestResultsForMissingEndpoints(testResultsWithNotImplementedEndpoints)
allTests = addTestResultsForTestsNotGeneratedBySpecmatic(allTests, allEndpoints)
allTests = sortByPathMethodResponseStatus(allTests)
val apiTestsGrouped = groupTestsByPathMethodAndResponseStatus(allTests)
val apiCoverageRows: MutableList = mutableListOf()
apiTestsGrouped.forEach { (route, methodMap) ->
val routeAPIRows: MutableList = mutableListOf()
val topLevelCoverageRow = createTopLevelApiCoverageRow(route, methodMap)
methodMap.forEach { (method, responseCodeMap) ->
responseCodeMap.forEach { (responseStatus, testResults) ->
if (routeAPIRows.isEmpty()) {
routeAPIRows.add(topLevelCoverageRow)
} else {
val rowMethod = if (routeAPIRows.none { it.method == method }) method else ""
routeAPIRows.add(
topLevelCoverageRow.copy(
method = rowMethod,
path ="",
responseStatus = responseStatus.toString(),
count = testResults.count{it.isExercised}.toString(),
coveragePercentage = 0,
remarks = Remarks.resolve(testResults)
)
)
}
}
}
apiCoverageRows.addAll(routeAPIRows)
}
val totalAPICount = apiTestsGrouped.keys.size
val missedAPICount = allTests.groupBy { it.path }.filter { pathMap -> pathMap.value.all { it.result == TestResult.Skipped } }.size
val notImplementedAPICount = allTests.groupBy { it.path }.filter { pathMap -> pathMap.value.all { it.result in setOf(TestResult.NotImplemented, TestResult.DidNotRun) } }.size
val partiallyMissedAPICount = allTests.groupBy { it.path }
.count { (_, tests) ->
tests.any { it.result == TestResult.Skipped } && tests.any {it.result != TestResult.Skipped }
}
val partiallyNotImplementedAPICount = allTests.groupBy { it.path }
.count { (_, tests) ->
tests.any { it.result == TestResult.NotImplemented } && tests.any {it.result in setOf(TestResult.Success , TestResult.Skipped, TestResult.Failed) }
}
return OpenAPICoverageConsoleReport(apiCoverageRows, totalAPICount, missedAPICount, notImplementedAPICount, partiallyMissedAPICount, partiallyNotImplementedAPICount)
}
private fun addTestResultsForTestsNotGeneratedBySpecmatic(allTests: List, allEndpoints: List): List {
val endpointsWithoutTests =
allEndpoints.filter { endpoint ->
allTests.none { it.path == endpoint.path && it.method == endpoint.method && it.responseStatus == endpoint.responseStatus }
&& excludedAPIs.none { it == endpoint.path }
}
return allTests.plus(
endpointsWithoutTests.map { endpoint -> TestResultRecord(
endpoint.path,
endpoint.method,
endpoint.responseStatus,
TestResult.DidNotRun,
endpoint.sourceProvider,
endpoint.sourceRepository,
endpoint.sourceRepositoryBranch,
endpoint.specification,
endpoint.serviceType
) }
)
}
fun generateJsonReport(): OpenApiCoverageJsonReport {
val testResults = testResultRecords.filter { testResult -> excludedAPIs.none { it == testResult.path } }
val testResultsWithNotImplementedEndpoints = identifyTestsThatFailedBecauseOfEndpointsThatWereNotImplemented(testResults)
val allTests = addTestResultsForMissingEndpoints(testResultsWithNotImplementedEndpoints)
return OpenApiCoverageJsonReport(configFilePath, allTests)
}
private fun groupTestsByPathMethodAndResponseStatus(allAPITests: List): MutableMap>>> {
return allAPITests.groupBy { it.path }
.mapValues { (_, pathResults) ->
pathResults.groupBy { it.method }
.mapValues { (_, methodResults) ->
methodResults.groupBy { it.responseStatus }
.mapValues { (_, responseResults) ->
responseResults.toMutableList()
}.toMutableMap()
}.toMutableMap()
}.toMutableMap()
}
private fun sortByPathMethodResponseStatus(testResultRecordList: List): List {
val recordsWithFixedURLs = testResultRecordList.map {
it.copy(path = convertPathParameterStyle(it.path))
}
return recordsWithFixedURLs.groupBy {
"${it.path}-${it.method}-${it.responseStatus}"
}.let { sortedRecords: Map> ->
sortedRecords.keys.sorted().map { key ->
sortedRecords.getValue(key)
}
}.flatten()
}
private fun addTestResultsForMissingEndpoints(testResults: List): List {
val testReportRecordsIncludingMissingAPIs = testResults.toMutableList()
if(endpointsAPISet) {
applicationAPIs.forEach { api ->
if (allEndpoints.none { it.path == api.path && it.method == api.method } && excludedAPIs.none { it == api.path }) {
testReportRecordsIncludingMissingAPIs.add(
TestResultRecord(
api.path,
api.method,
0,
TestResult.Skipped,
serviceType = SERVICE_TYPE_HTTP
)
)
}
}
}
return testReportRecordsIncludingMissingAPIs
}
private fun createTopLevelApiCoverageRow(
route: String,
methodMap: MutableMap>>,
): OpenApiCoverageConsoleRow {
val method = methodMap.keys.first()
val responseStatus = methodMap[method]?.keys?.first()
val remarks = Remarks.resolve(methodMap[method]?.get(responseStatus)!!)
val exercisedCount = methodMap[method]?.get(responseStatus)?.count { it.isExercised }
val totalMethodResponseCodeCount = methodMap.values.sumOf { it.keys.size }
var totalMethodResponseCodeCoveredCount = 0
methodMap.forEach { (_, responses) ->
responses.forEach { (_, testResults) ->
val increment = min(testResults.count { it.isCovered }, 1)
totalMethodResponseCodeCoveredCount += increment
}
}
val coveragePercentage =
((totalMethodResponseCodeCoveredCount.toFloat() / totalMethodResponseCodeCount.toFloat()) * 100).roundToInt()
return OpenApiCoverageConsoleRow(
method,
route,
responseStatus!!,
exercisedCount!!,
coveragePercentage,
remarks
)
}
private fun identifyTestsThatFailedBecauseOfEndpointsThatWereNotImplemented(testResults: List): List {
val notImplementedTests =
testResults.filter { it.result == TestResult.Failed && (endpointsAPISet && applicationAPIs.none { api -> api.path == it.path && api.method == it.method }) }
return testResults.minus(notImplementedTests.toSet())
.plus(notImplementedTests.map { it.copy(result = TestResult.NotImplemented) })
}
}
data class CoverageGroupKey(
val sourceProvider: String?,
val sourceRepository: String?,
val sourceRepositoryBranch: String?,
val specification: String?,
val serviceType: String?
)
data class Endpoint(
val path: String,
val method: String,
val responseStatus: Int,
val sourceProvider: String? = null,
val sourceRepository: String? = null,
val sourceRepositoryBranch: String? = null,
val specification: String? = null,
val serviceType: String? = null
)