epic-12: after epic audit and init commit
This commit is contained in:
@@ -0,0 +1,11 @@
|
||||
plugins {
|
||||
id 'java-library'
|
||||
id 'org.jetbrains.kotlin.jvm'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization'
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation(project(":core:transitions"))
|
||||
testImplementation(project(":core:events"))
|
||||
testImplementation(project(":core:inference"))
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.events.types.TransitionId
|
||||
import com.correx.core.transitions.evaluation.EvaluationContext
|
||||
import com.correx.core.transitions.evaluation.TransitionConditionEvaluator
|
||||
import com.correx.core.transitions.graph.StageConfig
|
||||
import com.correx.core.transitions.graph.TransitionCondition
|
||||
import com.correx.core.transitions.graph.TransitionEdge
|
||||
import com.correx.core.transitions.graph.WorkflowGraph
|
||||
import com.correx.core.transitions.resolution.DefaultTransitionResolver
|
||||
import com.correx.core.transitions.resolution.TransitionDecision
|
||||
import com.correx.core.transitions.resolution.TransitionResolver
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertInstanceOf
|
||||
|
||||
class DefaultTransitionResolverTest {
|
||||
|
||||
private val evaluator = object : TransitionConditionEvaluator {
|
||||
override fun evaluate(
|
||||
condition: TransitionCondition,
|
||||
context: EvaluationContext
|
||||
): Boolean {
|
||||
return condition.evaluate(context)
|
||||
}
|
||||
}
|
||||
|
||||
private val resolver: TransitionResolver = DefaultTransitionResolver(evaluator)
|
||||
|
||||
@Test
|
||||
fun `returns NoMatch when no outgoing transitions exist for current stage`() {
|
||||
val graph = graph(
|
||||
start = "stage-a",
|
||||
transitions = setOf(
|
||||
edge(
|
||||
id = "t1",
|
||||
from = "stage-a",
|
||||
to = "stage-b",
|
||||
condition = alwaysTrue()
|
||||
)
|
||||
),
|
||||
stages = setOf("stage-a", "stage-b")
|
||||
)
|
||||
|
||||
val result = resolver.resolve(
|
||||
graph = graph,
|
||||
context = context(currentStage = "stage-b")
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
TransitionDecision.NoMatch,
|
||||
result
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns Stay when all conditions evaluate to false`() {
|
||||
val graph = graph(
|
||||
start = "stage-a",
|
||||
transitions = setOf(
|
||||
edge(
|
||||
id = "t1",
|
||||
from = "stage-a",
|
||||
to = "stage-b",
|
||||
condition = alwaysFalse()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val result = resolver.resolve(
|
||||
graph = graph,
|
||||
context = context(currentStage = "stage-a")
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
TransitionDecision.Stay,
|
||||
result
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `returns matching transition when condition evaluates to true`() {
|
||||
val graph = graph(
|
||||
start = "stage-a",
|
||||
transitions = setOf(
|
||||
edge(
|
||||
id = "t1",
|
||||
from = "stage-a",
|
||||
to = "stage-b",
|
||||
condition = alwaysTrue()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val result = resolver.resolve(
|
||||
graph = graph,
|
||||
context = context(currentStage = "stage-a")
|
||||
)
|
||||
|
||||
val move = assertInstanceOf<TransitionDecision.Move>(result)
|
||||
|
||||
assertEquals(
|
||||
TransitionId("t1"),
|
||||
move.transitionId
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
StageId("stage-b"),
|
||||
move.to
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `first matching transition wins deterministically`() {
|
||||
val graph = graph(
|
||||
start = "stage-a",
|
||||
transitions = setOf(
|
||||
edge(
|
||||
id = "t2",
|
||||
from = "stage-a",
|
||||
to = "stage-c",
|
||||
condition = alwaysTrue()
|
||||
),
|
||||
edge(
|
||||
id = "t1",
|
||||
from = "stage-a",
|
||||
to = "stage-b",
|
||||
condition = alwaysTrue()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val result = resolver.resolve(
|
||||
graph = graph,
|
||||
context = context(currentStage = "stage-a")
|
||||
)
|
||||
|
||||
val move = assertInstanceOf<TransitionDecision.Move>(result)
|
||||
|
||||
assertEquals(
|
||||
TransitionId("t1"),
|
||||
move.transitionId
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
StageId("stage-b"),
|
||||
move.to
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `only transitions from current stage are evaluated`() {
|
||||
val graph = graph(
|
||||
start = "stage-a",
|
||||
transitions = setOf(
|
||||
edge(
|
||||
id = "t1",
|
||||
from = "stage-x",
|
||||
to = "stage-y",
|
||||
condition = alwaysTrue()
|
||||
),
|
||||
edge(
|
||||
id = "t2",
|
||||
from = "stage-a",
|
||||
to = "stage-b",
|
||||
condition = alwaysTrue()
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val result = resolver.resolve(
|
||||
graph = graph,
|
||||
context = context(currentStage = "stage-a")
|
||||
)
|
||||
|
||||
val move = assertInstanceOf<TransitionDecision.Move>(result)
|
||||
|
||||
assertEquals(
|
||||
TransitionId("t2"),
|
||||
move.transitionId
|
||||
)
|
||||
}
|
||||
|
||||
private fun edge(
|
||||
id: String,
|
||||
from: String,
|
||||
to: String,
|
||||
condition: TransitionCondition
|
||||
): TransitionEdge {
|
||||
return TransitionEdge(
|
||||
id = TransitionId(id),
|
||||
from = StageId(from),
|
||||
to = StageId(to),
|
||||
condition = condition
|
||||
)
|
||||
}
|
||||
|
||||
private fun graph(
|
||||
start: String,
|
||||
transitions: Set<TransitionEdge>,
|
||||
stages: Set<String> = transitions.flatMap { listOf(it.from.value, it.to.value) }.toSet()
|
||||
) = WorkflowGraph(
|
||||
start = StageId(start),
|
||||
stages = stages.associate { StageId(it) to StageConfig() },
|
||||
transitions = transitions
|
||||
)
|
||||
|
||||
private fun context(
|
||||
currentSession: String = "s1",
|
||||
currentStage: String,
|
||||
): EvaluationContext {
|
||||
return EvaluationContext(
|
||||
sessionId = SessionId(currentSession),
|
||||
currentStage = StageId(currentStage),
|
||||
variables = emptyMap(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun alwaysTrue() =
|
||||
TransitionCondition { true }
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
private fun alwaysFalse() =
|
||||
TransitionCondition { false }
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import com.correx.core.events.events.EventPayload
|
||||
import com.correx.core.events.events.StageCompletedEvent
|
||||
import com.correx.core.events.events.StageFailedEvent
|
||||
import com.correx.core.events.events.StageStartedEvent
|
||||
import com.correx.core.events.events.TransitionExecutedEvent
|
||||
import com.correx.core.events.serialization.JsonEventSerializer
|
||||
import com.correx.core.events.types.SessionId
|
||||
import com.correx.core.events.types.StageId
|
||||
import com.correx.core.events.types.TransitionId
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
class TransitionEventSerializationTest {
|
||||
|
||||
@Test
|
||||
fun `StageStartedEvent serializes and deserializes`() {
|
||||
val event = StageStartedEvent(
|
||||
sessionId = SessionId("session-1"),
|
||||
stageId = StageId("stage-a"),
|
||||
transitionId = TransitionId("transition-a")
|
||||
)
|
||||
|
||||
assertRoundTrip(event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `StageCompletedEvent serializes and deserializes`() {
|
||||
val event = StageCompletedEvent(
|
||||
sessionId = SessionId("session-1"),
|
||||
stageId = StageId("stage-a"),
|
||||
transitionId = TransitionId("transition-a")
|
||||
)
|
||||
|
||||
assertRoundTrip(event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `StageFailedEvent serializes and deserializes`() {
|
||||
val event = StageFailedEvent(
|
||||
sessionId = SessionId("session-1"),
|
||||
stageId = StageId("stage-a"),
|
||||
transitionId = TransitionId("transition-a"),
|
||||
reason = "boom"
|
||||
)
|
||||
|
||||
assertRoundTrip(event)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `TransitionExecutedEvent serializes and deserializes`() {
|
||||
val event = TransitionExecutedEvent(
|
||||
sessionId = SessionId("session-1"),
|
||||
from = StageId("stage-a"),
|
||||
to = StageId("stage-b"),
|
||||
transitionId = TransitionId("transition-a")
|
||||
)
|
||||
|
||||
assertRoundTrip(event)
|
||||
}
|
||||
|
||||
private inline fun <reified T : EventPayload> assertRoundTrip(
|
||||
event: T
|
||||
) {
|
||||
val json = JsonEventSerializer()
|
||||
val serialized = json.serialize(event)
|
||||
|
||||
val deserialized =
|
||||
json.deserialize(serialized)
|
||||
|
||||
assertEquals(event, deserialized)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user