@@ -5,6 +5,7 @@ import com.orbitalhq.firstRawObject
55import com.orbitalhq.firstRawValue
66import com.orbitalhq.firstTypedInstace
77import com.orbitalhq.models.TypedInstance
8+ import com.orbitalhq.preflight.dsl.PreflightExtension.Companion.PreflightTestCaseKey
89import com.orbitalhq.query.QueryResult
910import com.orbitalhq.rawObjects
1011import com.orbitalhq.schemaServer.core.adaptors.taxi.TaxiSchemaSourcesAdaptor
@@ -25,9 +26,24 @@ import java.nio.file.Paths
2526import kotlin.io.path.absolute
2627import com.orbitalhq.schemaServer.core.file.packages.FileSystemPackageLoader
2728import com.orbitalhq.utils.files.ReactivePollingFileSystemMonitor
29+ import io.kotest.assertions.AssertionFailedError
30+ import io.kotest.core.extensions.TestCaseExtension
31+ import io.kotest.core.listeners.AfterTestListener
32+ import io.kotest.core.test.TestCase
33+ import io.kotest.core.test.TestResult
34+ import kotlinx.coroutines.withContext
35+ import lang.taxi.query.TaxiQLQueryString
2836import java.time.Duration
37+ import kotlin.coroutines.CoroutineContext
38+ import kotlin.coroutines.coroutineContext
39+
40+ class PreflightExtension (val projectRoot : Path = Paths .get("./")) : BeforeSpecListener, AfterTestListener,
41+ TestCaseExtension {
42+
43+ companion object {
44+ val PreflightTestCaseKey = object : CoroutineContext .Key <PreflightTestCaseContext > {}
45+ }
2946
30- class PreflightExtension (val projectRoot : Path = Paths .get("./")) : BeforeSpecListener {
3147 /* *
3248 * Provides access to the compiled taxi document.
3349 * This is lower-level than Orbital's schema object
@@ -38,22 +54,31 @@ class PreflightExtension(val projectRoot: Path = Paths.get("./")) : BeforeSpecLi
3854 lateinit var schema: TaxiSchema
3955 private set
4056
57+ lateinit var sourcePackage: SourcePackage
58+ private set
59+
4160 /* *
4261 * Provides access to the actual Taxi project (the equivalent of the
4362 * taxi.conf file)
4463 */
4564 lateinit var taxiProject: TaxiPackageProject
4665 private set
4766
67+ private val capturedScenarios = mutableMapOf<TestCase , CapturedQuery >()
68+
69+
4870 override suspend fun beforeSpec (spec : Spec ) {
4971 val loader = TaxiPackageLoader .forDirectoryContainingTaxiFile(projectRoot.absolute().normalize())
5072 this .taxiProject = loader.load()
5173
52- val sourcePackage = loadSourcePackage(this .taxiProject.packageRootPath!! )
74+ sourcePackage = loadSourcePackage(this .taxiProject.packageRootPath!! )
5375
5476 withClue(" Taxi project should compile without errors" ) {
5577 val taxiSchema = try {
56- TaxiSchema .from(sourcePackage, onErrorBehaviour = TaxiSchema .Companion .TaxiSchemaErrorBehaviour .THROW_EXCEPTION )
78+ TaxiSchema .from(
79+ sourcePackage,
80+ onErrorBehaviour = TaxiSchema .Companion .TaxiSchemaErrorBehaviour .THROW_EXCEPTION
81+ )
5782 } catch (e: CompilationException ) {
5883 fail(" Taxi project has errors: \n ${e.message} " )
5984 }
@@ -79,7 +104,7 @@ class PreflightExtension(val projectRoot: Path = Paths.get("./")) : BeforeSpecLi
79104 return sourcePackage
80105 }
81106
82- fun orbital ():Pair <Orbital , StubService > {
107+ fun orbital (): Pair <Orbital , StubService > {
83108 return testVyne(this .schema)
84109 }
85110
@@ -91,32 +116,96 @@ class PreflightExtension(val projectRoot: Path = Paths.get("./")) : BeforeSpecLi
91116 }
92117 }
93118
94- suspend fun queryForScalar (taxiQl : String , vararg stubScenarios : StubScenario ) = queryForScalar(taxiQl, stubScenariosToCustomizer(stubScenarios.toList()))
95- suspend fun queryForScalar (taxiQl : String , stubCustomizer : (StubService ) -> Unit = {}):Any? {
119+ suspend fun queryForScalar (taxiQl : String , vararg stubScenarios : StubScenario ) =
120+ queryForScalar(taxiQl, stubScenariosToCustomizer(stubScenarios.toList()))
121+
122+ suspend fun queryForScalar (taxiQl : String , stubCustomizer : (StubService ) -> Unit = {}): Any? {
96123 return query(taxiQl, stubCustomizer)
97124 .firstRawValue()
98125 }
99126
100127
101- suspend fun queryForObject (taxiQl : String , vararg stubScenarios : StubScenario ) = queryForObject(taxiQl, stubScenariosToCustomizer(stubScenarios.toList()))
102- suspend fun queryForObject (taxiQl : String , stubCustomizer : (StubService ) -> Unit = {}):Map <String ,Any ?> {
128+ suspend fun queryForObject (taxiQl : String , vararg stubScenarios : StubScenario ) =
129+ queryForObject(taxiQl, stubScenariosToCustomizer(stubScenarios.toList()))
130+
131+ suspend fun queryForObject (taxiQl : String , stubCustomizer : (StubService ) -> Unit = {}): Map <String , Any ?> {
103132 return query(taxiQl, stubCustomizer)
104133 .firstRawObject()
105134 }
106- suspend fun queryForCollection (taxiQl : String , vararg stubScenarios : StubScenario ) = queryForCollection(taxiQl, stubScenariosToCustomizer(stubScenarios.toList()))
107- suspend fun queryForCollection (taxiQl : String , stubCustomizer : (StubService ) -> Unit = {}): List <Map <String , Any ?>> {
135+
136+ suspend fun queryForCollection (taxiQl : String , vararg stubScenarios : StubScenario ) =
137+ queryForCollection(taxiQl, stubScenariosToCustomizer(stubScenarios.toList()))
138+
139+ suspend fun queryForCollection (
140+ taxiQl : String ,
141+ stubCustomizer : (StubService ) -> Unit = {}
142+ ): List <Map <String , Any ?>> {
108143 return query(taxiQl, stubCustomizer)
109144 .rawObjects()
110145 }
111- suspend fun queryForTypedInstance (taxiQl : String , vararg stubScenarios : StubScenario ) = queryForTypedInstance(taxiQl, stubScenariosToCustomizer(stubScenarios.toList()))
112- suspend fun queryForTypedInstance (taxiQl : String , stubCustomizer : (StubService ) -> Unit = {}):TypedInstance {
146+
147+ suspend fun queryForTypedInstance (taxiQl : String , vararg stubScenarios : StubScenario ) =
148+ queryForTypedInstance(taxiQl, stubScenariosToCustomizer(stubScenarios.toList()))
149+
150+ suspend fun queryForTypedInstance (taxiQl : String , stubCustomizer : (StubService ) -> Unit = {}): TypedInstance {
113151 return query(taxiQl, stubCustomizer)
114152 .firstTypedInstace()
115153 }
116154
117155 suspend fun query (taxiQl : String , stubCustomizer : (StubService ) -> Unit = {}): QueryResult {
118- val (orbital,stub) = orbital()
156+ val (orbital, stub) = orbital()
119157 stubCustomizer(stub)
158+ val testContext = coroutineContext[PreflightTestCaseKey ]
159+ if (testContext != null ) {
160+ capturedScenarios[testContext.testCase] = CapturedQuery (stub, taxiQl)
161+ } else {
162+ println (" A test is executing without a context - this shouldn't happen" )
163+ }
120164 return orbital.query(taxiQl)
121165 }
166+
167+ override suspend fun intercept (testCase : TestCase , execute : suspend (TestCase ) -> TestResult ): TestResult {
168+ val context = PreflightTestCaseContext (testCase)
169+ return withContext(context) {
170+ val testResult = execute(testCase)
171+ val capturedScenario = capturedScenarios[testCase]
172+ if (testResult.isErrorOrFailure && capturedScenario != null ) {
173+
174+ val failure = testResult as TestResult .Failure
175+ val originalError = failure.cause as AssertionFailedError
176+ val (_, playgroundLink) = PlaygroundScenarioFactory .buildPlaygroundScenario(
177+ capturedScenario,
178+ sourcePackage,
179+ schema,
180+ originalError,
181+ testCase
182+ )
183+ val errorMessageWithPlaygroundLink = """ ${originalError.message}
184+ |
185+ |This error is explorable in Taxi Playground at the following link: $playgroundLink
186+ """ .trimMargin()
187+ val failureWithPlaygroundLink = failure.copy(
188+ cause = AssertionFailedError (
189+ message = errorMessageWithPlaygroundLink,
190+ cause = originalError.cause,
191+ expectedValue = originalError.expectedValue,
192+ actualValue = originalError.actualValue,
193+ )
194+ )
195+ failureWithPlaygroundLink
196+ } else {
197+ testResult
198+ }
199+
200+ }
201+ }
202+ }
203+
204+ data class CapturedQuery (
205+ val stub : StubService ,
206+ val query : TaxiQLQueryString
207+ )
208+
209+ data class PreflightTestCaseContext (val testCase : TestCase ) : CoroutineContext.Element {
210+ override val key = PreflightTestCaseKey
122211}
0 commit comments