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

com.rapatao.projects.ruleset.engine.BaseEvaluatorTest.kt Maven / Gradle / Ivy

Go to download

Simple yet powerful rules engine that offers the flexibility of using the built-in engine and creating a custom one.

The newest version!
package com.rapatao.projects.ruleset.engine

import com.rapatao.projects.ruleset.engine.cases.InvalidOperatorCases
import com.rapatao.projects.ruleset.engine.cases.OnFailureCases
import com.rapatao.projects.ruleset.engine.cases.TestData
import com.rapatao.projects.ruleset.engine.helper.ExposeEngineTestOperator.Companion.EXPOSED_OPERATOR_NAME
import com.rapatao.projects.ruleset.engine.helper.Helper.doEvaluationTest
import com.rapatao.projects.ruleset.engine.types.Expression
import com.rapatao.projects.ruleset.engine.types.OnFailure
import com.rapatao.projects.ruleset.engine.types.builder.MatcherBuilder.allMatch
import com.rapatao.projects.ruleset.engine.types.builder.extensions.equalsTo
import com.rapatao.projects.ruleset.engine.types.builder.extensions.ifFail
import com.rapatao.projects.ruleset.engine.types.operators.BuiltInOperators
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.emptyString
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.hasItem
import org.hamcrest.Matchers.not
import org.junit.jupiter.api.DisplayName
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import kotlin.reflect.full.memberProperties

abstract class BaseEvaluatorTest(
    private val evaluator: Evaluator
) {

    companion object {
        @JvmStatic
        fun tests() = TestData.cases()

        @JvmStatic
        fun onFailure() = OnFailureCases.cases()

        @JvmStatic
        fun invalidOperators() = InvalidOperatorCases.cases()
    }

    @ParameterizedTest
    @MethodSource("tests")
    fun runEvaluationTest(ruleSet: Expression, expected: Boolean) {
        println(ruleSet)

        doEvaluationTest(evaluator, ruleSet, expected)
    }

    @ParameterizedTest
    @MethodSource("onFailure")
    fun asserOnFailure(ruleSet: Expression, expected: Boolean, isError: Boolean) {
        println(ruleSet)

        if (isError) {
            assertThrows {
                doEvaluationTest(evaluator, ruleSet, expected)
            }
        } else {
            doEvaluationTest(evaluator, ruleSet, expected)
        }
    }

    @Test
    @Suppress("MagicNumber")
    fun assertSingleCaseForDebugging() {
        val caseNumber = 122

        val cases: List = tests()
        val test = cases[caseNumber - 1].get()
        runEvaluationTest(
            test[0] as Expression,
            test[1] as Boolean
        )
    }

    @Test
    @DisplayName("should support map as input data")
    fun assertMapAsInputDataSupport() {
        val input = mapOf(
            "item" to mapOf(
                "price" to 0,
            ),
            "attributes" to mapOf(
                "info" to mapOf(
                    "title" to "superb title",
                    "description" to "super description",
                ),
            )
        )

        val rule = allMatch(
            "item.price" equalsTo 0,
            "attributes.info.title" equalsTo "\"superb title\"",
            "attributes.info.description" equalsTo "\"super description\"",
        )

        val result = evaluator.evaluate(rule, input)

        assertThat(result, equalTo(true))
    }

    @Test
    @DisplayName("should throw exception when failure was not defined")
    @Suppress("MagicNumber")
    fun assertThrowExceptionWhenFailureIsNotDefined() {
        val invalidRule = "[]unkown$" equalsTo 10
        val input = mapOf()

        assertThrows {
            evaluator.evaluate(invalidRule, input)
        }
    }

    @Test
    @DisplayName("should override value when failure was defined")
    @Suppress("MagicNumber")
    fun assertOverrideValueWhenFailureIsDefined() {
        val invalidRule = "[]unkown$" equalsTo 10
        val input = mapOf()

        assertThat(
            evaluator.evaluate(invalidRule ifFail OnFailure.TRUE, input),
            equalTo(true)
        )

        assertThat(
            evaluator.evaluate(invalidRule ifFail OnFailure.FALSE, input),
            equalTo(false)
        )
    }

    @Test
    @DisplayName("evaluator must have a non empty name")
    fun assertEvaluatorMustHaveName() {
        assertThat(evaluator.name(), not(emptyString()))
    }

    @Test
    @DisplayName("all built-in operators should have at least one test case")
    fun assertBuiltInImplementations() {
        val operatorsTested: Set = TestData.cases().flatMap { it.get().toList() }
            .filterIsInstance()
            .mapNotNull { it.operator }
            .toSet()

        BuiltInOperators::class.memberProperties.forEach {
            assertThat(operatorsTested, hasItem(it.call()))
        }
    }

    @ParameterizedTest
    @MethodSource("invalidOperators")
    fun assertInvalidOperatorTests(expression: Expression, expected: Boolean) {
        val checker = when (expression.onFailure) {
            OnFailure.THROW -> fun(b: () -> Unit) {
                assertThrows { b() }
            }

            else -> fun(b: () -> Unit) { b() }
        }
        checker {
            assertThat(evaluator.evaluate(expression, TestData.inputData), equalTo(expected))
        }
    }

    @Test
    fun assertContextExposesEngine() {
        assertThat(
            evaluator.evaluate(
                Expression(
                    left = "\"${evaluator.name()}\"",
                    operator = EXPOSED_OPERATOR_NAME,
                    right = "\"${evaluator.name()}\"",
                ),
                TestData.inputData,
            ),
            equalTo(true),
        )
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy