From 24e64cd96e334437dccd89da5d0a57794972e375 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Struewer Date: Mon, 28 Oct 2024 16:57:20 +0100 Subject: [PATCH 01/13] feat: Updated WeightedMaximumKPICalculationStrategy behavior - we now calculate the weighted max as score * weight, but propagate the score --- .../core/strategy/WeightedMaximumKPICalculationStrategy.kt | 2 +- .../strategy/WeightedMaximumKPICalculationStrategyTest.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategy.kt index ba5916c..b19afa4 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategy.kt @@ -24,7 +24,7 @@ internal object WeightedMaximumKPICalculationStrategy : BaseKpiCalculationStrate override fun internalCalculateKpi(edges: Collection): KpiCalculationResult { - val max = if (edges.isEmpty()) 0 else edges.maxOf { it.to.score * it.actualWeight }.toInt() + val max = edges.maxByOrNull { it.to.score * it.actualWeight }?.to?.score ?: 0 return KpiCalculationResult.Success(score = max) } diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategyTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategyTest.kt index 9357df0..54956aa 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategyTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategyTest.kt @@ -81,7 +81,7 @@ class WeightedWeightedMaximumKPICalculationStrategyTest { assert(calcRelaxed is KpiCalculationResult.Success) assert(calcStrict is KpiCalculationResult.Success) - assertEquals(10, (calcStrict as KpiCalculationResult.Success).score) - assertEquals(10, (calcRelaxed as KpiCalculationResult.Success).score) + assertEquals(20, (calcStrict as KpiCalculationResult.Success).score) + assertEquals(20, (calcRelaxed as KpiCalculationResult.Success).score) } } From 927ff5d5779e97fbd76f08e19b86501edfc8ba5a Mon Sep 17 00:00:00 2001 From: Jan-Niclas Struewer Date: Mon, 28 Oct 2024 17:06:07 +0100 Subject: [PATCH 02/13] breaking: renaming - renamed AggregationKPICalculationStrategy.kt to WeightedAverageKPICalculationStrategy.kt - renamed RatioKPICalculationStrategy.kt to WeightedRatioKPICalculationStrategy.kt --- .../core/strategy/KpiCalculationStrategy.kt | 4 +- ... WeightedAverageKPICalculationStrategy.kt} | 4 +- ...=> WeightedRatioKPICalculationStrategy.kt} | 4 +- .../iem/spha/core/KpiCalculatorTest.kt | 14 ++--- .../core/hierarchy/HierarchyValidatorTest.kt | 6 +-- .../strategy/AbstractKpiCalculationTest.kt | 6 +-- .../AggregationKpiCalculationStrategyTest.kt | 8 +-- ...eightedRatioKPICalculationStrategyTest.kt} | 52 +++++++++---------- .../iem/spha/model/kpi/KpiStrategyId.kt | 4 +- .../model/kpi/hierarchy/DefaultHierarchy.kt | 16 +++--- .../model/kpi/hierarchy/KpiHierarchyTest.kt | 8 +-- 11 files changed, 63 insertions(+), 63 deletions(-) rename core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/{AggregationKPICalculationStrategy.kt => WeightedAverageKPICalculationStrategy.kt} (91%) rename core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/{RatioKPICalculationStrategy.kt => WeightedRatioKPICalculationStrategy.kt} (96%) rename core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/{RatioKPICalculationStrategyTest.kt => WeightedRatioKPICalculationStrategyTest.kt} (82%) diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt index cbc9805..8f0b09f 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt @@ -17,9 +17,9 @@ import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode internal fun getKpiCalculationStrategy(strategyId: KpiStrategyId): KpiCalculationStrategy { return when (strategyId) { KpiStrategyId.RAW_VALUE_STRATEGY -> RawValueKpiCalculationStrategy - KpiStrategyId.RATIO_STRATEGY -> RatioKPICalculationStrategy + KpiStrategyId.WEIGHTED_RATIO_STRATEGY -> WeightedRatioKPICalculationStrategy - KpiStrategyId.AGGREGATION_STRATEGY -> AggregationKPICalculationStrategy + KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY -> WeightedAverageKPICalculationStrategy KpiStrategyId.MAXIMUM_STRATEGY -> MaximumKPICalculationStrategy diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKPICalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedAverageKPICalculationStrategy.kt similarity index 91% rename from core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKPICalculationStrategy.kt rename to core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedAverageKPICalculationStrategy.kt index c95983f..35bbcf2 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKPICalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedAverageKPICalculationStrategy.kt @@ -17,10 +17,10 @@ import io.github.oshai.kotlinlogging.KotlinLogging private val logger = KotlinLogging.logger {} -internal object AggregationKPICalculationStrategy : BaseKpiCalculationStrategy() { +internal object WeightedAverageKPICalculationStrategy : BaseKpiCalculationStrategy() { override val kpiStrategyId: KpiStrategyId - get() = KpiStrategyId.AGGREGATION_STRATEGY + get() = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY /** * This function calculates the aggregate sum of all given children. If a child is empty it is diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/RatioKPICalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedRatioKPICalculationStrategy.kt similarity index 96% rename from core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/RatioKPICalculationStrategy.kt rename to core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedRatioKPICalculationStrategy.kt index bd38d3e..299804f 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/RatioKPICalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedRatioKPICalculationStrategy.kt @@ -17,10 +17,10 @@ import io.github.oshai.kotlinlogging.KotlinLogging private val logger = KotlinLogging.logger {} -internal object RatioKPICalculationStrategy : BaseKpiCalculationStrategy() { +internal object WeightedRatioKPICalculationStrategy : BaseKpiCalculationStrategy() { override val kpiStrategyId: KpiStrategyId - get() = KpiStrategyId.RATIO_STRATEGY + get() = KpiStrategyId.WEIGHTED_RATIO_STRATEGY /** Returns smallerValue / biggerValue, regardless in which order the values are given. */ override fun internalCalculateKpi(edges: Collection): KpiCalculationResult { diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/KpiCalculatorTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/KpiCalculatorTest.kt index ac1c043..e86e13c 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/KpiCalculatorTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/KpiCalculatorTest.kt @@ -56,7 +56,7 @@ class KpiCalculatorTest { val root = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge( @@ -106,7 +106,7 @@ class KpiCalculatorTest { val root = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge( @@ -166,14 +166,14 @@ class KpiCalculatorTest { val root = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge( target = KpiNode( kpiId = KpiId.SIGNED_COMMITS_RATIO, - kpiStrategyId = KpiStrategyId.RATIO_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY, edges = listOf( KpiEdge( @@ -247,7 +247,7 @@ class KpiCalculatorTest { val root = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge( @@ -284,7 +284,7 @@ class KpiCalculatorTest { val root = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge( @@ -338,7 +338,7 @@ class KpiCalculatorTest { val root = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge( diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/hierarchy/HierarchyValidatorTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/hierarchy/HierarchyValidatorTest.kt index 5d3ba52..98ab2a9 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/hierarchy/HierarchyValidatorTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/hierarchy/HierarchyValidatorTest.kt @@ -27,7 +27,7 @@ class HierarchyValidatorTest { rootNode = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = emptyList(), ) ) @@ -43,7 +43,7 @@ class HierarchyValidatorTest { rootNode = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge( @@ -72,7 +72,7 @@ class HierarchyValidatorTest { rootNode = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge( diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt index 56795d8..a746d6c 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt @@ -26,7 +26,7 @@ internal fun getNodeWithErrorResult(plannedWeight: Double): KpiHierarchyNode { val node = KpiNode( kpiId = KpiId.NUMBER_OF_COMMITS, - kpiStrategyId = KpiStrategyId.RATIO_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY, edges = listOf( KpiEdge( @@ -51,7 +51,7 @@ internal fun getNodeIncompleteResult(plannedWeight: Double): KpiHierarchyNode { val node = KpiNode( kpiId = KpiId.SECRETS, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge( @@ -103,7 +103,7 @@ class AbstractKpiCalculationTest { val nodeCorrectChildren = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.RATIO_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY, edges = listOf( KpiEdge( diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKpiCalculationStrategyTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKpiCalculationStrategyTest.kt index 4cc7725..3cc2614 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKpiCalculationStrategyTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKpiCalculationStrategyTest.kt @@ -25,12 +25,12 @@ class AggregationKpiCalculationStrategyTest { fun calculateEmpty() { val calcRelaxed = - AggregationKPICalculationStrategy.calculateKpi( + WeightedAverageKPICalculationStrategy.calculateKpi( hierarchyEdges = listOf(), strict = false, ) val calcStrict = - AggregationKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = true) + WeightedAverageKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = true) assertEquals(true, calcRelaxed is KpiCalculationResult.Empty) assertEquals(true, calcStrict is KpiCalculationResult.Empty) @@ -72,9 +72,9 @@ class AggregationKpiCalculationStrategyTest { ) val calcRelaxed = - AggregationKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) + WeightedAverageKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) val calcStrict = - AggregationKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) + WeightedAverageKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) assert(calcRelaxed is KpiCalculationResult.Success) assert(calcStrict is KpiCalculationResult.Success) diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/RatioKPICalculationStrategyTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedRatioKPICalculationStrategyTest.kt similarity index 82% rename from core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/RatioKPICalculationStrategyTest.kt rename to core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedRatioKPICalculationStrategyTest.kt index 9653106..2174918 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/RatioKPICalculationStrategyTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedRatioKPICalculationStrategyTest.kt @@ -20,34 +20,34 @@ import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode import org.junit.jupiter.api.Assertions.* import org.junit.jupiter.api.Test -class RatioKPICalculationStrategyTest { +class WeightedRatioKPICalculationStrategyTest { @Test fun isValidEmpty() { val node = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.RATIO_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY, edges = listOf(), ) - assertEquals(true, RatioKPICalculationStrategy.isValid(node = node, strict = false)) - assertEquals(true, RatioKPICalculationStrategy.isValid(node = node, strict = true)) + assertEquals(true, WeightedRatioKPICalculationStrategy.isValid(node = node, strict = false)) + assertEquals(true, WeightedRatioKPICalculationStrategy.isValid(node = node, strict = true)) val incorrectStrategy = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf(), ) assertEquals( true, - RatioKPICalculationStrategy.isValid(node = incorrectStrategy, strict = false), + WeightedRatioKPICalculationStrategy.isValid(node = incorrectStrategy, strict = false), ) assertEquals( true, - RatioKPICalculationStrategy.isValid(node = incorrectStrategy, strict = true), + WeightedRatioKPICalculationStrategy.isValid(node = incorrectStrategy, strict = true), ) } @@ -55,9 +55,9 @@ class RatioKPICalculationStrategyTest { fun calculateEmpty() { val calcRelaxed = - RatioKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = false) + WeightedRatioKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = false) val calcStrict = - RatioKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = true) + WeightedRatioKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = true) assertEquals(true, calcRelaxed is KpiCalculationResult.Empty) assertEquals(true, calcStrict is KpiCalculationResult.Empty) @@ -68,7 +68,7 @@ class RatioKPICalculationStrategyTest { val nodeCorrectChildren = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.RATIO_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY, edges = listOf( KpiEdge( @@ -94,11 +94,11 @@ class RatioKPICalculationStrategyTest { assertEquals( true, - RatioKPICalculationStrategy.isValid(node = nodeCorrectChildren, strict = false), + WeightedRatioKPICalculationStrategy.isValid(node = nodeCorrectChildren, strict = false), ) assertEquals( true, - RatioKPICalculationStrategy.isValid(node = nodeCorrectChildren, strict = true), + WeightedRatioKPICalculationStrategy.isValid(node = nodeCorrectChildren, strict = true), ) } @@ -138,12 +138,12 @@ class RatioKPICalculationStrategyTest { ) val calcRelaxed = - RatioKPICalculationStrategy.calculateKpi( + WeightedRatioKPICalculationStrategy.calculateKpi( hierarchyEdges = root.hierarchyEdges, strict = false, ) val calcStrict = - RatioKPICalculationStrategy.calculateKpi( + WeightedRatioKPICalculationStrategy.calculateKpi( hierarchyEdges = root.hierarchyEdges, strict = true, ) @@ -160,7 +160,7 @@ class RatioKPICalculationStrategyTest { val nodeManyChildren = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.RATIO_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY, edges = listOf( KpiEdge( @@ -195,11 +195,11 @@ class RatioKPICalculationStrategyTest { assertEquals( true, - RatioKPICalculationStrategy.isValid(node = nodeManyChildren, strict = false), + WeightedRatioKPICalculationStrategy.isValid(node = nodeManyChildren, strict = false), ) assertEquals( false, - RatioKPICalculationStrategy.isValid(node = nodeManyChildren, strict = true), + WeightedRatioKPICalculationStrategy.isValid(node = nodeManyChildren, strict = true), ) val root = @@ -208,8 +208,8 @@ class RatioKPICalculationStrategyTest { listOf(RawValueKpi(kind = KpiId.NUMBER_OF_COMMITS, score = 15)), ) - val relaxed = RatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) - val strict = RatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) + val relaxed = WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) + val strict = WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) assertEquals(true, relaxed is KpiCalculationResult.Error) assertEquals(true, strict is KpiCalculationResult.Error) } @@ -219,7 +219,7 @@ class RatioKPICalculationStrategyTest { val nestedError = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.RATIO_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY, edges = listOf( KpiEdge( @@ -235,7 +235,7 @@ class RatioKPICalculationStrategyTest { target = KpiNode( kpiId = KpiId.SECURITY, - kpiStrategyId = KpiStrategyId.RATIO_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY, edges = listOf( KpiEdge( @@ -278,8 +278,8 @@ class RatioKPICalculationStrategyTest { assertEquals(true, root.result is KpiCalculationResult.Error) - val relaxed = RatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) - val strict = RatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) + val relaxed = WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) + val strict = WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) assertEquals(true, relaxed is KpiCalculationResult.Error) assertEquals(true, strict is KpiCalculationResult.Error) @@ -298,7 +298,7 @@ class RatioKPICalculationStrategyTest { val nodeToFewChildren = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.RATIO_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY, edges = listOf( KpiEdge( @@ -315,11 +315,11 @@ class RatioKPICalculationStrategyTest { assertEquals( false, - RatioKPICalculationStrategy.isValid(node = nodeToFewChildren, strict = false), + WeightedRatioKPICalculationStrategy.isValid(node = nodeToFewChildren, strict = false), ) assertEquals( false, - RatioKPICalculationStrategy.isValid(node = nodeToFewChildren, strict = true), + WeightedRatioKPICalculationStrategy.isValid(node = nodeToFewChildren, strict = true), ) } } diff --git a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt index a69b2cf..8887ab0 100644 --- a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt +++ b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt @@ -11,8 +11,8 @@ package de.fraunhofer.iem.spha.model.kpi enum class KpiStrategyId { RAW_VALUE_STRATEGY, - AGGREGATION_STRATEGY, + WEIGHTED_AVERAGE_STRATEGY, MAXIMUM_STRATEGY, WEIGHTED_MAXIMUM_STRATEGY, - RATIO_STRATEGY, + WEIGHTED_RATIO_STRATEGY, } diff --git a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/DefaultHierarchy.kt b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/DefaultHierarchy.kt index a1ff58d..a6c9a48 100644 --- a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/DefaultHierarchy.kt +++ b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/DefaultHierarchy.kt @@ -67,7 +67,7 @@ object DefaultHierarchy { val signedCommitsRatio = KpiNode( kpiId = KpiId.SIGNED_COMMITS_RATIO, - kpiStrategyId = KpiStrategyId.RATIO_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_RATIO_STRATEGY, edges = listOf( KpiEdge(target = numberOfCommits, weight = 1.0), @@ -78,7 +78,7 @@ object DefaultHierarchy { val documentation = KpiNode( kpiId = KpiId.DOCUMENTATION, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge(target = documentationInfrastructure, weight = 0.6), @@ -89,7 +89,7 @@ object DefaultHierarchy { val processComplianceKpi = KpiNode( kpiId = KpiId.PROCESS_COMPLIANCE, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge(target = checkedInBinaries, weight = 0.2), @@ -102,7 +102,7 @@ object DefaultHierarchy { val processTransparency = KpiNode( kpiId = KpiId.PROCESS_TRANSPARENCY, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf(KpiEdge(target = signedCommitsRatio, weight = 1.0)), ) @@ -123,7 +123,7 @@ object DefaultHierarchy { val security = KpiNode( kpiId = KpiId.SECURITY, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge(target = secrets, weight = 0.3), @@ -135,21 +135,21 @@ object DefaultHierarchy { val internalQuality = KpiNode( kpiId = KpiId.INTERNAL_QUALITY, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf(KpiEdge(target = documentation, weight = 1.0)), ) val externalQuality = KpiNode( kpiId = KpiId.EXTERNAL_QUALITY, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf(KpiEdge(target = documentation, weight = 1.0)), ) val root = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, edges = listOf( KpiEdge(target = processTransparency, weight = 0.1), diff --git a/model/src/test/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/KpiHierarchyTest.kt b/model/src/test/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/KpiHierarchyTest.kt index 9c76c38..b528f31 100644 --- a/model/src/test/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/KpiHierarchyTest.kt +++ b/model/src/test/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/KpiHierarchyTest.kt @@ -34,7 +34,7 @@ class KpiHierarchyTest { KpiEdge( KpiNode( kpiId = KpiId.SECURITY, - KpiStrategyId.AGGREGATION_STRATEGY, + KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, listOf(), ), weight = 0.3, @@ -42,7 +42,7 @@ class KpiHierarchyTest { KpiEdge( KpiNode( kpiId = KpiId.PROCESS_COMPLIANCE, - KpiStrategyId.AGGREGATION_STRATEGY, + KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, listOf(), ), weight = 0.3, @@ -50,7 +50,7 @@ class KpiHierarchyTest { KpiEdge( KpiNode( kpiId = KpiId.INTERNAL_QUALITY, - KpiStrategyId.AGGREGATION_STRATEGY, + KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, listOf(), ), weight = 0.3, @@ -59,7 +59,7 @@ class KpiHierarchyTest { val root = KpiNode( kpiId = KpiId.ROOT, - kpiStrategyId = KpiStrategyId.AGGREGATION_STRATEGY, + kpiStrategyId = KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY, childNodes, ) val hierarchy = KpiHierarchy.create(root) From 9868f53c0a9e700af7a3746d32d2e2ff6ef84319 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Struewer Date: Mon, 28 Oct 2024 17:14:56 +0100 Subject: [PATCH 03/13] feat: added WeightedMinimumKPICalculationStrategy --- .../core/strategy/KpiCalculationStrategy.kt | 5 +- .../WeightedMinimumKPICalculationStrategy.kt | 44 ++++++++++ ...ightedMinimumKPICalculationStrategyTest.kt | 87 +++++++++++++++++++ .../iem/spha/model/kpi/KpiStrategyId.kt | 3 +- 4 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategy.kt create mode 100644 core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategyTest.kt diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt index 8f0b09f..5e744b8 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt @@ -17,13 +17,16 @@ import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode internal fun getKpiCalculationStrategy(strategyId: KpiStrategyId): KpiCalculationStrategy { return when (strategyId) { KpiStrategyId.RAW_VALUE_STRATEGY -> RawValueKpiCalculationStrategy + KpiStrategyId.WEIGHTED_RATIO_STRATEGY -> WeightedRatioKPICalculationStrategy KpiStrategyId.WEIGHTED_AVERAGE_STRATEGY -> WeightedAverageKPICalculationStrategy KpiStrategyId.MAXIMUM_STRATEGY -> MaximumKPICalculationStrategy - KpiStrategyId.WEIGHTED_MAXIMUM_STRATEGY -> MaximumKPICalculationStrategy + KpiStrategyId.WEIGHTED_MAXIMUM_STRATEGY -> WeightedMaximumKPICalculationStrategy + + KpiStrategyId.WEIGHTED_MINIMUM_STRATEGY -> WeightedMinimumKPICalculationStrategy } } diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategy.kt new file mode 100644 index 0000000..d658f33 --- /dev/null +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategy.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Fraunhofer IEM. All rights reserved. + * + * Licensed under the MIT license. See LICENSE file in the project root for details. + * + * SPDX-License-Identifier: MIT + * License-Filename: LICENSE + */ + +package de.fraunhofer.iem.spha.core.strategy + +import de.fraunhofer.iem.spha.core.hierarchy.KpiHierarchyEdge +import de.fraunhofer.iem.spha.model.kpi.KpiStrategyId +import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiCalculationResult +import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode +import io.github.oshai.kotlinlogging.KotlinLogging + +private val logger = KotlinLogging.logger {} + +internal object WeightedMinimumKPICalculationStrategy : BaseKpiCalculationStrategy() { + + override val kpiStrategyId: KpiStrategyId + get() = KpiStrategyId.WEIGHTED_MINIMUM_STRATEGY + + override fun internalCalculateKpi(edges: Collection): KpiCalculationResult { + + val min = edges.minByOrNull { it.to.score * it.actualWeight }?.to?.score ?: 0 + + return KpiCalculationResult.Success(score = min) + } + + /** There is no validity requirement for this strategy. */ + override fun internalIsValid(node: KpiNode, strict: Boolean): Boolean { + + if (node.edges.size == 1) { + logger.warn { + "Maximum KPI calculation strategy for node $node is planned " + + "for a single child." + } + } + + return true + } +} diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategyTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategyTest.kt new file mode 100644 index 0000000..d32c8d3 --- /dev/null +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategyTest.kt @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2024 Fraunhofer IEM. All rights reserved. + * + * Licensed under the MIT license. See LICENSE file in the project root for details. + * + * SPDX-License-Identifier: MIT + * License-Filename: LICENSE + */ + +package de.fraunhofer.iem.spha.core.strategy + +import de.fraunhofer.iem.spha.core.hierarchy.KpiHierarchyNode +import de.fraunhofer.iem.spha.model.kpi.KpiId +import de.fraunhofer.iem.spha.model.kpi.KpiStrategyId +import de.fraunhofer.iem.spha.model.kpi.RawValueKpi +import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiCalculationResult +import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiEdge +import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode +import kotlin.test.Test +import kotlin.test.assertEquals + +class WeightedWeightedMinimumKPICalculationStrategyTest { + + @Test + fun calculateEmpty() { + + val calcRelaxed = + WeightedMinimumKPICalculationStrategy.calculateKpi( + hierarchyEdges = listOf(), + strict = false, + ) + val calcStrict = + WeightedMinimumKPICalculationStrategy.calculateKpi( + hierarchyEdges = listOf(), + strict = true, + ) + + assertEquals(true, calcRelaxed is KpiCalculationResult.Empty) + assertEquals(true, calcStrict is KpiCalculationResult.Empty) + } + + @Test + fun calculateCorrect() { + val root = + KpiHierarchyNode.from( + KpiNode( + kpiId = KpiId.ROOT, + kpiStrategyId = KpiStrategyId.MAXIMUM_STRATEGY, + edges = + listOf( + KpiEdge( + target = + KpiNode( + kpiId = KpiId.NUMBER_OF_SIGNED_COMMITS, + kpiStrategyId = KpiStrategyId.RAW_VALUE_STRATEGY, + edges = listOf(), + ), + weight = 0.5, + ), + KpiEdge( + target = + KpiNode( + kpiId = KpiId.NUMBER_OF_COMMITS, + kpiStrategyId = KpiStrategyId.RAW_VALUE_STRATEGY, + edges = listOf(), + ), + weight = 0.5, + ), + ), + ), + listOf( + RawValueKpi(kind = KpiId.NUMBER_OF_SIGNED_COMMITS, score = 15), + RawValueKpi(kind = KpiId.NUMBER_OF_COMMITS, score = 20), + ), + ) + + val calcRelaxed = + WeightedMinimumKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) + val calcStrict = + WeightedMinimumKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) + + assert(calcRelaxed is KpiCalculationResult.Success) + assert(calcStrict is KpiCalculationResult.Success) + assertEquals(15, (calcStrict as KpiCalculationResult.Success).score) + assertEquals(15, (calcRelaxed as KpiCalculationResult.Success).score) + } +} diff --git a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt index 8887ab0..b209419 100644 --- a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt +++ b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt @@ -11,8 +11,9 @@ package de.fraunhofer.iem.spha.model.kpi enum class KpiStrategyId { RAW_VALUE_STRATEGY, - WEIGHTED_AVERAGE_STRATEGY, MAXIMUM_STRATEGY, + WEIGHTED_AVERAGE_STRATEGY, WEIGHTED_MAXIMUM_STRATEGY, + WEIGHTED_MINIMUM_STRATEGY, WEIGHTED_RATIO_STRATEGY, } From 461b3605999f2193d6c0113549424ce14cb28dd5 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Struewer Date: Mon, 28 Oct 2024 20:06:51 +0100 Subject: [PATCH 04/13] fix: min and max strategies return Empty() if edges are empty --- .../WeightedMaximumKPICalculationStrategy.kt | 6 ++++- .../WeightedMinimumKPICalculationStrategy.kt | 6 ++++- .../AggregationKpiCalculationStrategyTest.kt | 5 ++++- ...WeightedRatioKPICalculationStrategyTest.kt | 22 ++++++++++++++----- 4 files changed, 30 insertions(+), 9 deletions(-) diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategy.kt index b19afa4..9602274 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategy.kt @@ -24,7 +24,11 @@ internal object WeightedMaximumKPICalculationStrategy : BaseKpiCalculationStrate override fun internalCalculateKpi(edges: Collection): KpiCalculationResult { - val max = edges.maxByOrNull { it.to.score * it.actualWeight }?.to?.score ?: 0 + val max = edges.maxByOrNull { it.to.score * it.actualWeight }?.to?.score + + if (max == null) { + return KpiCalculationResult.Empty() + } return KpiCalculationResult.Success(score = max) } diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategy.kt index d658f33..95ada14 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategy.kt @@ -24,7 +24,11 @@ internal object WeightedMinimumKPICalculationStrategy : BaseKpiCalculationStrate override fun internalCalculateKpi(edges: Collection): KpiCalculationResult { - val min = edges.minByOrNull { it.to.score * it.actualWeight }?.to?.score ?: 0 + val min = edges.minByOrNull { it.to.score * it.actualWeight }?.to?.score + + if (min == null) { + return KpiCalculationResult.Empty() + } return KpiCalculationResult.Success(score = min) } diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKpiCalculationStrategyTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKpiCalculationStrategyTest.kt index 3cc2614..bf7523c 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKpiCalculationStrategyTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AggregationKpiCalculationStrategyTest.kt @@ -30,7 +30,10 @@ class AggregationKpiCalculationStrategyTest { strict = false, ) val calcStrict = - WeightedAverageKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = true) + WeightedAverageKPICalculationStrategy.calculateKpi( + hierarchyEdges = listOf(), + strict = true, + ) assertEquals(true, calcRelaxed is KpiCalculationResult.Empty) assertEquals(true, calcStrict is KpiCalculationResult.Empty) diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedRatioKPICalculationStrategyTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedRatioKPICalculationStrategyTest.kt index 2174918..c5dd729 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedRatioKPICalculationStrategyTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedRatioKPICalculationStrategyTest.kt @@ -55,9 +55,15 @@ class WeightedRatioKPICalculationStrategyTest { fun calculateEmpty() { val calcRelaxed = - WeightedRatioKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = false) + WeightedRatioKPICalculationStrategy.calculateKpi( + hierarchyEdges = listOf(), + strict = false, + ) val calcStrict = - WeightedRatioKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = true) + WeightedRatioKPICalculationStrategy.calculateKpi( + hierarchyEdges = listOf(), + strict = true, + ) assertEquals(true, calcRelaxed is KpiCalculationResult.Empty) assertEquals(true, calcStrict is KpiCalculationResult.Empty) @@ -208,8 +214,10 @@ class WeightedRatioKPICalculationStrategyTest { listOf(RawValueKpi(kind = KpiId.NUMBER_OF_COMMITS, score = 15)), ) - val relaxed = WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) - val strict = WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) + val relaxed = + WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) + val strict = + WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) assertEquals(true, relaxed is KpiCalculationResult.Error) assertEquals(true, strict is KpiCalculationResult.Error) } @@ -278,8 +286,10 @@ class WeightedRatioKPICalculationStrategyTest { assertEquals(true, root.result is KpiCalculationResult.Error) - val relaxed = WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) - val strict = WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) + val relaxed = + WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) + val strict = + WeightedRatioKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) assertEquals(true, relaxed is KpiCalculationResult.Error) assertEquals(true, strict is KpiCalculationResult.Error) From 02071d3115f14b0fd2b9fc237896536afdab0df5 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Struewer Date: Tue, 29 Oct 2024 11:20:59 +0100 Subject: [PATCH 05/13] feat: added MinimumKPICalculationStrategy --- .../iem/spha/adapter/kpis/cve/CveAdapter.kt | 2 +- .../core/strategy/KpiCalculationStrategy.kt | 2 + .../strategy/MinimumKPICalculationStrategy.kt | 44 ++++++++++ .../MinimumKPICalculationStrategyTest.kt | 81 +++++++++++++++++++ .../iem/spha/model/kpi/KpiStrategyId.kt | 1 + .../model/kpi/hierarchy/DefaultHierarchy.kt | 2 +- 6 files changed, 130 insertions(+), 2 deletions(-) create mode 100644 core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategy.kt create mode 100644 core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategyTest.kt diff --git a/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/kpis/cve/CveAdapter.kt b/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/kpis/cve/CveAdapter.kt index 66be798..c3ed182 100644 --- a/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/kpis/cve/CveAdapter.kt +++ b/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/kpis/cve/CveAdapter.kt @@ -23,7 +23,7 @@ object CveAdapter { AdapterResult.Success.Kpi( RawValueKpi( kind = KpiId.VULNERABILITY_SCORE, - score = (it.severity * 10).toInt(), + score = 100 - (it.severity * 10).toInt(), ) ) } else { diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt index 5e744b8..a6a979d 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt @@ -24,6 +24,8 @@ internal fun getKpiCalculationStrategy(strategyId: KpiStrategyId): KpiCalculatio KpiStrategyId.MAXIMUM_STRATEGY -> MaximumKPICalculationStrategy + KpiStrategyId.MINIMUM_STRATEGY -> MaximumKPICalculationStrategy + KpiStrategyId.WEIGHTED_MAXIMUM_STRATEGY -> WeightedMaximumKPICalculationStrategy KpiStrategyId.WEIGHTED_MINIMUM_STRATEGY -> WeightedMinimumKPICalculationStrategy diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategy.kt new file mode 100644 index 0000000..ee65bb9 --- /dev/null +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategy.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Fraunhofer IEM. All rights reserved. + * + * Licensed under the MIT license. See LICENSE file in the project root for details. + * + * SPDX-License-Identifier: MIT + * License-Filename: LICENSE + */ + +package de.fraunhofer.iem.spha.core.strategy + +import de.fraunhofer.iem.spha.core.hierarchy.KpiHierarchyEdge +import de.fraunhofer.iem.spha.model.kpi.KpiStrategyId +import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiCalculationResult +import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode +import io.github.oshai.kotlinlogging.KotlinLogging + +private val logger = KotlinLogging.logger {} + +internal object MinimumKPICalculationStrategy : BaseKpiCalculationStrategy() { + + override val kpiStrategyId: KpiStrategyId + get() = KpiStrategyId.MINIMUM_STRATEGY + + override fun internalCalculateKpi(edges: Collection): KpiCalculationResult { + + val max = if (edges.isEmpty()) 0 else edges.minOf { it.to.score } + + return KpiCalculationResult.Success(score = max) + } + + /** There is no validity requirement for this strategy. */ + override fun internalIsValid(node: KpiNode, strict: Boolean): Boolean { + + if (node.edges.size == 1) { + logger.warn { + "Minimum KPI calculation strategy for node $node is planned " + + "for a single child." + } + } + + return true + } +} diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategyTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategyTest.kt new file mode 100644 index 0000000..576d4fe --- /dev/null +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategyTest.kt @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2024 Fraunhofer IEM. All rights reserved. + * + * Licensed under the MIT license. See LICENSE file in the project root for details. + * + * SPDX-License-Identifier: MIT + * License-Filename: LICENSE + */ + +package de.fraunhofer.iem.spha.core.strategy + +import de.fraunhofer.iem.spha.core.hierarchy.KpiHierarchyNode +import de.fraunhofer.iem.spha.model.kpi.KpiId +import de.fraunhofer.iem.spha.model.kpi.KpiStrategyId +import de.fraunhofer.iem.spha.model.kpi.RawValueKpi +import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiCalculationResult +import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiEdge +import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode +import kotlin.test.Test +import org.junit.jupiter.api.Assertions.assertEquals + +class MinimumKPICalculationStrategyTest { + + @Test + fun calculateEmpty() { + + val calcRelaxed = + MinimumKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = false) + val calcStrict = + MinimumKPICalculationStrategy.calculateKpi(hierarchyEdges = listOf(), strict = true) + + assertEquals(true, calcRelaxed is KpiCalculationResult.Empty) + assertEquals(true, calcStrict is KpiCalculationResult.Empty) + } + + @Test + fun calculateCorrect() { + val root = + KpiHierarchyNode.from( + KpiNode( + kpiId = KpiId.ROOT, + kpiStrategyId = KpiStrategyId.MINIMUM_STRATEGY, + edges = + listOf( + KpiEdge( + target = + KpiNode( + kpiId = KpiId.NUMBER_OF_SIGNED_COMMITS, + kpiStrategyId = KpiStrategyId.RAW_VALUE_STRATEGY, + edges = listOf(), + ), + weight = 0.5, + ), + KpiEdge( + target = + KpiNode( + kpiId = KpiId.NUMBER_OF_COMMITS, + kpiStrategyId = KpiStrategyId.RAW_VALUE_STRATEGY, + edges = listOf(), + ), + weight = 0.5, + ), + ), + ), + listOf( + RawValueKpi(kind = KpiId.NUMBER_OF_SIGNED_COMMITS, score = 15), + RawValueKpi(kind = KpiId.NUMBER_OF_COMMITS, score = 20), + ), + ) + + val calcRelaxed = + MinimumKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = false) + val calcStrict = + MinimumKPICalculationStrategy.calculateKpi(root.hierarchyEdges, strict = true) + + assert(calcRelaxed is KpiCalculationResult.Success) + assert(calcStrict is KpiCalculationResult.Success) + assertEquals(15, (calcStrict as KpiCalculationResult.Success).score) + assertEquals(15, (calcRelaxed as KpiCalculationResult.Success).score) + } +} diff --git a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt index b209419..be138a9 100644 --- a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt +++ b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/KpiStrategyId.kt @@ -12,6 +12,7 @@ package de.fraunhofer.iem.spha.model.kpi enum class KpiStrategyId { RAW_VALUE_STRATEGY, MAXIMUM_STRATEGY, + MINIMUM_STRATEGY, WEIGHTED_AVERAGE_STRATEGY, WEIGHTED_MAXIMUM_STRATEGY, WEIGHTED_MINIMUM_STRATEGY, diff --git a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/DefaultHierarchy.kt b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/DefaultHierarchy.kt index a6c9a48..a4a575b 100644 --- a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/DefaultHierarchy.kt +++ b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/kpi/hierarchy/DefaultHierarchy.kt @@ -116,7 +116,7 @@ object DefaultHierarchy { val maxDepVulnerability = KpiNode( kpiId = KpiId.MAXIMAL_VULNERABILITY, - kpiStrategyId = KpiStrategyId.MAXIMUM_STRATEGY, + kpiStrategyId = KpiStrategyId.MINIMUM_STRATEGY, edges = listOf(KpiEdge(target = vulnerabilities, weight = 1.0)), ) From 766bdc3d3f7fe6f96a0e2a0546a801c19f595385 Mon Sep 17 00:00:00 2001 From: Jan-Niclas Struewer Date: Mon, 28 Oct 2024 16:01:27 +0100 Subject: [PATCH 06/13] feat: check the score of all KpiCalculationResults to guarantee that they are in the range of 0...100 --- .../core/strategy/KpiCalculationStrategy.kt | 39 ++++++++++++++- .../strategy/AbstractKpiCalculationTest.kt | 50 +++++++++++++++++++ 2 files changed, 88 insertions(+), 1 deletion(-) diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt index a6a979d..e2c48c5 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt @@ -13,6 +13,7 @@ import de.fraunhofer.iem.spha.core.hierarchy.KpiHierarchyEdge import de.fraunhofer.iem.spha.model.kpi.KpiStrategyId import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiCalculationResult import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode +import io.github.oshai.kotlinlogging.KotlinLogging internal fun getKpiCalculationStrategy(strategyId: KpiStrategyId): KpiCalculationStrategy { return when (strategyId) { @@ -65,6 +66,8 @@ internal interface KpiCalculationStrategy { fun isValid(node: KpiNode, strict: Boolean = false): Boolean } +private val logger = KotlinLogging.logger {} + internal abstract class BaseKpiCalculationStrategy : KpiCalculationStrategy { abstract val kpiStrategyId: KpiStrategyId @@ -87,7 +90,7 @@ internal abstract class BaseKpiCalculationStrategy : KpiCalculationStrategy { updateEdgeWeights(edges = hierarchyEdges, strict) - val result = internalCalculateKpi(hierarchyEdges) + val result = getResultInValidRange(internalCalculateKpi(hierarchyEdges)) if ( hierarchyEdges.any { it.to.result !is KpiCalculationResult.Success } && @@ -154,4 +157,38 @@ internal abstract class BaseKpiCalculationStrategy : KpiCalculationStrategy { } protected abstract fun internalIsValid(node: KpiNode, strict: Boolean): Boolean + + companion object { + fun getResultInValidRange(result: KpiCalculationResult): KpiCalculationResult { + return when (result) { + is KpiCalculationResult.Success -> + validateScore(result, result.score) { score -> + KpiCalculationResult.Success(score) + } + is KpiCalculationResult.Incomplete -> + validateScore(result, result.score) { score -> + KpiCalculationResult.Incomplete(score, result.reason) + } + else -> result + } + } + + private fun validateScore( + result: T, + score: Int, + createResult: (Int) -> T, + ): T { + return when { + score < 0 -> { + logger.warn { "Calculation result score $result is out of bounds." } + createResult(0) + } + score > 100 -> { + logger.warn { "Calculation result score $result is out of bounds." } + createResult(100) + } + else -> result + } + } + } } diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt index a746d6c..9b030ac 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt @@ -18,6 +18,7 @@ import de.fraunhofer.iem.spha.model.kpi.RawValueKpi import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiCalculationResult import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiEdge import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode +import kotlin.random.Random import kotlin.test.fail import org.junit.jupiter.api.Assertions.assertEquals import org.junit.jupiter.api.Test @@ -173,4 +174,53 @@ class AbstractKpiCalculationTest { assertEquals(0.5, incompleteNode.hierarchyEdges.last().plannedWeight) assertEquals(1.0, incompleteNode.hierarchyEdges.last().actualWeight) } + + @Test + fun isValidResultRange() { + (0..100).forEach { score -> + val successResult = KpiCalculationResult.Success(score) + val incompleteResult = KpiCalculationResult.Incomplete(score, "Incomplete") + assertEquals( + successResult, + BaseKpiCalculationStrategy.getResultInValidRange(successResult), + ) + assertEquals( + incompleteResult, + BaseKpiCalculationStrategy.getResultInValidRange(incompleteResult), + ) + } + } + + @Test + fun isInvalidResultRange() { + + val smallerThanZero = List(10) { Random.nextInt(-100, 0) } + val largerThanHundred = List(10) { Random.nextInt(101, 200) } + + smallerThanZero.forEach { score -> + val successResult = KpiCalculationResult.Success(score) + val incompleteResult = KpiCalculationResult.Incomplete(score, "Incomplete") + assertEquals( + KpiCalculationResult.Success(0), + BaseKpiCalculationStrategy.getResultInValidRange(successResult), + ) + assertEquals( + KpiCalculationResult.Incomplete(0, "Incomplete"), + BaseKpiCalculationStrategy.getResultInValidRange(incompleteResult), + ) + } + + largerThanHundred.forEach { score -> + val successResult = KpiCalculationResult.Success(score) + val incompleteResult = KpiCalculationResult.Incomplete(score, "Incomplete") + assertEquals( + KpiCalculationResult.Success(100), + BaseKpiCalculationStrategy.getResultInValidRange(successResult), + ) + assertEquals( + KpiCalculationResult.Incomplete(100, "Incomplete"), + BaseKpiCalculationStrategy.getResultInValidRange(incompleteResult), + ) + } + } } From 1cc7843a078c039d0e6478159f266d7d776bd7de Mon Sep 17 00:00:00 2001 From: Jan-Niclas Struewer Date: Mon, 28 Oct 2024 16:13:24 +0100 Subject: [PATCH 07/13] test: added rangeCheck test for KpiCalculationResults without a score --- .../iem/spha/core/strategy/AbstractKpiCalculationTest.kt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt index 9b030ac..b59d2ff 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/AbstractKpiCalculationTest.kt @@ -223,4 +223,13 @@ class AbstractKpiCalculationTest { ) } } + + @Test + fun rangeCheckForNoScoreResults() { + val errorResult = KpiCalculationResult.Error("Error") + val emptyResult = KpiCalculationResult.Empty() + + assertEquals(emptyResult, BaseKpiCalculationStrategy.getResultInValidRange(emptyResult)) + assertEquals(errorResult, BaseKpiCalculationStrategy.getResultInValidRange(errorResult)) + } } From fdbc006d44f9ae8127aad0ffce49342a2aacb67b Mon Sep 17 00:00:00 2001 From: Jan-Niclas Struewer Date: Mon, 28 Oct 2024 16:49:08 +0100 Subject: [PATCH 08/13] chore: renamed validity check function and added javadoc --- .../iem/spha/core/strategy/KpiCalculationStrategy.kt | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt index e2c48c5..7087100 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/KpiCalculationStrategy.kt @@ -159,21 +159,27 @@ internal abstract class BaseKpiCalculationStrategy : KpiCalculationStrategy { protected abstract fun internalIsValid(node: KpiNode, strict: Boolean): Boolean companion object { + /** + * Checks if `result.score` is in a valid range (0..100). If the score is lower 0 or higher + * than 100, we return 0 or 100 respectively. + * + * @param result + */ fun getResultInValidRange(result: KpiCalculationResult): KpiCalculationResult { return when (result) { is KpiCalculationResult.Success -> - validateScore(result, result.score) { score -> + createValidScore(result, result.score) { score -> KpiCalculationResult.Success(score) } is KpiCalculationResult.Incomplete -> - validateScore(result, result.score) { score -> + createValidScore(result, result.score) { score -> KpiCalculationResult.Incomplete(score, result.reason) } else -> result } } - private fun validateScore( + private fun createValidScore( result: T, score: Int, createResult: (Int) -> T, From 986bf76d6f11b7ffc0e5d40976ff967144e63dcb Mon Sep 17 00:00:00 2001 From: Hutomo Saleh Date: Tue, 29 Oct 2024 12:05:39 +0100 Subject: [PATCH 09/13] fix: use PkgIdentifier and PURL to support non-OS targets --- .../spha/adapter/tools/trivy/TrivyAdapter.kt | 2 +- .../adapter/tools/trivy/TrivyAdapterTest.kt | 6 +- .../src/test/resources/trivy-result-v2.json | 55 +++++++++++++++++++ .../iem/spha/model/adapter/trivy/TrivyDto.kt | 9 ++- 4 files changed, 67 insertions(+), 5 deletions(-) diff --git a/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapter.kt b/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapter.kt index bd48540..d09863d 100644 --- a/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapter.kt +++ b/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapter.kt @@ -92,7 +92,7 @@ object TrivyAdapter { val score = getHighestCvssScore(cvssData) logger.trace { "Selected CVSS score $score for vulnerability '${it.vulnerabilityID}'" } - VulnerabilityDto(it.vulnerabilityID, it.pkgID, score) + VulnerabilityDto(it.vulnerabilityID, it.pkgName, score) } } diff --git a/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapterTest.kt b/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapterTest.kt index 3f629bf..56d3715 100644 --- a/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapterTest.kt +++ b/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapterTest.kt @@ -53,11 +53,11 @@ class TrivyAdapterTest { fun testResult2Dto() { Files.newInputStream(Path("src/test/resources/trivy-result-v2.json")).use { val dto = assertDoesNotThrow { TrivyAdapter.dtoFromJson(it) } - assertEquals(1, dto.vulnerabilities.count()) + assertEquals(2, dto.vulnerabilities.count()) val vuln = dto.vulnerabilities.first() assertEquals("CVE-2011-3374", vuln.cveIdentifier) - assertEquals("apt@2.6.1", vuln.packageName) + assertEquals("apt", vuln.packageName) assertEquals(4.3, vuln.severity) } } @@ -69,7 +69,7 @@ class TrivyAdapterTest { assertEquals(2, dto.vulnerabilities.count()) assertTrue { dto.vulnerabilities.all { it.cveIdentifier == "CVE-2005-2541" } } - assertEquals("tar@1.34+dfsg-1.2", dto.vulnerabilities.first().packageName) + assertEquals("tar", dto.vulnerabilities.first().packageName) assertEquals(10.0, dto.vulnerabilities.first().severity) } } diff --git a/adapter/src/test/resources/trivy-result-v2.json b/adapter/src/test/resources/trivy-result-v2.json index 9b70adb..12c50f4 100644 --- a/adapter/src/test/resources/trivy-result-v2.json +++ b/adapter/src/test/resources/trivy-result-v2.json @@ -129,6 +129,61 @@ } } ] + }, + { + "Target": "Python", + "Class": "lang-pkgs", + "Type": "python-pkg", + "Vulnerabilities": [ + { + "VulnerabilityID": "CVE-2024-22190", + "PkgName": "GitPython", + "PkgPath": "home/redacted/.local/lib/python3.11/site-packages/GitPython-3.1.37.dist-info/METADATA", + "PkgIdentifier": { + "PURL": "pkg:pypi/gitpython@3.1.37", + "UID": "5591a0dc57c78b2" + }, + "InstalledVersion": "3.1.37", + "FixedVersion": "3.1.41", + "Status": "fixed", + "Layer": { + "Digest": "sha256:13b0acb9b68e8a74b8e6152932c5bd6c6968e13fa32feba83cc2310346a9b7f9", + "DiffID": "sha256:feea6321b4864eb2cb16188d1619323db7c8738adaca6982c8005da9fe227961" + }, + "SeveritySource": "ghsa", + "PrimaryURL": "https://avd.aquasec.com/nvd/cve-2024-22190", + "DataSource": { + "ID": "ghsa", + "Name": "GitHub Security Advisory pip", + "URL": "https://github.com/advisories?query=type%3Areviewed+ecosystem%3Apip" + }, + "Title": "Untrusted search path under some conditions on Windows allows arbitrary code execution", + "Description": "GitPython is a python library used to interact with Git repositories. There is an incomplete fix for CVE-2023-40590. On Windows, GitPython uses an untrusted search path if it uses a shell to run `git`, as well as when it runs `bash.exe` to interpret hooks. If either of those features are used on Windows, a malicious `git.exe` or `bash.exe` may be run from an untrusted repository. This issue has been patched in version 3.1.41.", + "Severity": "HIGH", + "CweIDs": [ + "CWE-426" + ], + "VendorSeverity": { + "ghsa": 3, + "nvd": 3 + }, + "CVSS": { + "ghsa": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + }, + "nvd": { + "V3Vector": "CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H", + "V3Score": 7.8 + } + }, + "References": [ + "https://github.com/gitpython-developers/GitPython" + ], + "PublishedDate": "2024-01-11T02:15:48.25Z", + "LastModifiedDate": "2024-01-18T13:48:07.553Z" + } + ] } ] } diff --git a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt index e066784..9711abc 100644 --- a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt +++ b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt @@ -38,7 +38,14 @@ data class TrivyVulnerabilityDto( // This way we can iterate over those when required. Their type is always CVSSData. @SerialName("CVSS") val cvss: JsonObject?, @SerialName("VulnerabilityID") val vulnerabilityID: String, - @SerialName("PkgID") val pkgID: String, + @SerialName("PkgIdentifier") val pkgID: PkgIdentifier, + @SerialName("PkgName") val pkgName: String, +) + +@Serializable +data class PkgIdentifier( + @SerialName("PURL") val purl: String, + @SerialName("UID") val uid: String, ) @Serializable From b4d2baa21a92f11c0cfa7656c69a02a8cb7c66fe Mon Sep 17 00:00:00 2001 From: Hutomo Saleh Date: Tue, 29 Oct 2024 16:47:16 +0100 Subject: [PATCH 10/13] chore: apply ktfmtFormat --- .../de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt index 9711abc..d4a93d5 100644 --- a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt +++ b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt @@ -43,10 +43,7 @@ data class TrivyVulnerabilityDto( ) @Serializable -data class PkgIdentifier( - @SerialName("PURL") val purl: String, - @SerialName("UID") val uid: String, -) +data class PkgIdentifier(@SerialName("PURL") val purl: String, @SerialName("UID") val uid: String) @Serializable data class CVSSData( From e4ae6e6c2f4530cd848d56c6be7b52136f8d22ec Mon Sep 17 00:00:00 2001 From: Hutomo Saleh Date: Wed, 30 Oct 2024 09:08:36 +0100 Subject: [PATCH 11/13] fix: use PkgName and Installedversion for package identifier --- .../fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapter.kt | 3 ++- .../iem/spha/adapter/tools/trivy/TrivyAdapterTest.kt | 4 ++-- .../de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt | 5 +---- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapter.kt b/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapter.kt index d09863d..3eecb58 100644 --- a/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapter.kt +++ b/adapter/src/main/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapter.kt @@ -91,8 +91,9 @@ object TrivyAdapter { val cvssData = it.cvss!!.values.map { jsonParser.decodeFromJsonElement(it) } val score = getHighestCvssScore(cvssData) + val packageID = "${it.pkgName}@${it.installedVersion}" logger.trace { "Selected CVSS score $score for vulnerability '${it.vulnerabilityID}'" } - VulnerabilityDto(it.vulnerabilityID, it.pkgName, score) + VulnerabilityDto(it.vulnerabilityID, packageID, score) } } diff --git a/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapterTest.kt b/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapterTest.kt index 56d3715..582ca40 100644 --- a/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapterTest.kt +++ b/adapter/src/test/kotlin/de/fraunhofer/iem/spha/adapter/tools/trivy/TrivyAdapterTest.kt @@ -57,7 +57,7 @@ class TrivyAdapterTest { val vuln = dto.vulnerabilities.first() assertEquals("CVE-2011-3374", vuln.cveIdentifier) - assertEquals("apt", vuln.packageName) + assertEquals("apt@2.6.1", vuln.packageName) assertEquals(4.3, vuln.severity) } } @@ -69,7 +69,7 @@ class TrivyAdapterTest { assertEquals(2, dto.vulnerabilities.count()) assertTrue { dto.vulnerabilities.all { it.cveIdentifier == "CVE-2005-2541" } } - assertEquals("tar", dto.vulnerabilities.first().packageName) + assertEquals("tar@1.34+dfsg-1.2", dto.vulnerabilities.first().packageName) assertEquals(10.0, dto.vulnerabilities.first().severity) } } diff --git a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt index d4a93d5..4be1458 100644 --- a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt +++ b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt @@ -38,13 +38,10 @@ data class TrivyVulnerabilityDto( // This way we can iterate over those when required. Their type is always CVSSData. @SerialName("CVSS") val cvss: JsonObject?, @SerialName("VulnerabilityID") val vulnerabilityID: String, - @SerialName("PkgIdentifier") val pkgID: PkgIdentifier, + @SerialName("InstalledVersion") val installedVersion: String, @SerialName("PkgName") val pkgName: String, ) -@Serializable -data class PkgIdentifier(@SerialName("PURL") val purl: String, @SerialName("UID") val uid: String) - @Serializable data class CVSSData( @SerialName("V2Score") val v2Score: Double?, From 8e80e88b22b79f8bbed72db6dbd35d7f6ace6f77 Mon Sep 17 00:00:00 2001 From: Hutomo Saleh Date: Wed, 30 Oct 2024 09:26:40 +0100 Subject: [PATCH 12/13] feat: add severity field in TrivyVulnerabilityDto --- .../de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt index 4be1458..ff3ba19 100644 --- a/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt +++ b/model/src/main/kotlin/de/fraunhofer/iem/spha/model/adapter/trivy/TrivyDto.kt @@ -40,6 +40,7 @@ data class TrivyVulnerabilityDto( @SerialName("VulnerabilityID") val vulnerabilityID: String, @SerialName("InstalledVersion") val installedVersion: String, @SerialName("PkgName") val pkgName: String, + @SerialName("Severity") val severity: String, ) @Serializable From 68815d17f7df44188c18980b15f0ae7dbc681faa Mon Sep 17 00:00:00 2001 From: Jan-Niclas Struewer Date: Wed, 30 Oct 2024 13:32:52 +0100 Subject: [PATCH 13/13] chore: fixed naming issues and explicitly return Empty() for max and min strategy --- .../spha/core/strategy/MaximumKPICalculationStrategy.kt | 6 +++++- .../spha/core/strategy/MinimumKPICalculationStrategy.kt | 8 ++++++-- .../strategy/WeightedMaximumKPICalculationStrategyTest.kt | 2 +- .../strategy/WeightedMinimumKPICalculationStrategyTest.kt | 2 +- 4 files changed, 13 insertions(+), 5 deletions(-) diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MaximumKPICalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MaximumKPICalculationStrategy.kt index 103c013..cfc40f4 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MaximumKPICalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MaximumKPICalculationStrategy.kt @@ -24,7 +24,11 @@ internal object MaximumKPICalculationStrategy : BaseKpiCalculationStrategy() { override fun internalCalculateKpi(edges: Collection): KpiCalculationResult { - val max = if (edges.isEmpty()) 0 else edges.maxOf { it.to.score } + val max = edges.maxOfOrNull { it.to.score } + + if (max == null) { + return KpiCalculationResult.Empty() + } return KpiCalculationResult.Success(score = max) } diff --git a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategy.kt b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategy.kt index ee65bb9..1e3811a 100644 --- a/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategy.kt +++ b/core/src/main/kotlin/de/fraunhofer/iem/spha/core/strategy/MinimumKPICalculationStrategy.kt @@ -24,9 +24,13 @@ internal object MinimumKPICalculationStrategy : BaseKpiCalculationStrategy() { override fun internalCalculateKpi(edges: Collection): KpiCalculationResult { - val max = if (edges.isEmpty()) 0 else edges.minOf { it.to.score } + val min = edges.minOfOrNull { it.to.score } - return KpiCalculationResult.Success(score = max) + if (min == null) { + return KpiCalculationResult.Empty() + } + + return KpiCalculationResult.Success(score = min) } /** There is no validity requirement for this strategy. */ diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategyTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategyTest.kt index 54956aa..c81f7c5 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategyTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMaximumKPICalculationStrategyTest.kt @@ -19,7 +19,7 @@ import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode import kotlin.test.Test import kotlin.test.assertEquals -class WeightedWeightedMaximumKPICalculationStrategyTest { +class WeightedMaximumKPICalculationStrategyTest { @Test fun calculateEmpty() { diff --git a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategyTest.kt b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategyTest.kt index d32c8d3..1de07f0 100644 --- a/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategyTest.kt +++ b/core/src/test/kotlin/de/fraunhofer/iem/spha/core/strategy/WeightedMinimumKPICalculationStrategyTest.kt @@ -19,7 +19,7 @@ import de.fraunhofer.iem.spha.model.kpi.hierarchy.KpiNode import kotlin.test.Test import kotlin.test.assertEquals -class WeightedWeightedMinimumKPICalculationStrategyTest { +class WeightedMinimumKPICalculationStrategyTest { @Test fun calculateEmpty() {