Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class CompilationTest {
@Test
fun `delegated_vars compiles`() = compile("delegated_vars/delegated_vars.connekt.kts")

@Test
fun `ttl_dsl compiles`() = compile("ttl/ttl_dsl.connekt.kts")

@Test
fun `import_helper compiles`() {
val tempDir = kotlin.io.path.createTempDirectory("connekt-compile-test").toFile()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.amplicode.connekt.integration

import io.amplicode.connekt.BaseNonColorPrinter
import io.amplicode.connekt.ConnektAuthExtensionsImpl
import io.amplicode.connekt.Printer
import io.amplicode.connekt.RawOutputConnektInterceptor
import io.amplicode.connekt.SystemOutPrinter
import io.amplicode.connekt.auth.OAuthRunner
Expand Down Expand Up @@ -137,9 +138,9 @@ fun createIntegrationContext(
environmentStore: EnvironmentStore = NoopEnvironmentStore,
storage: Storage = InMemoryStorage(),
builderFactory: ((ConnektContext) -> ConnektBuilderFactory)? = null,
printer: Printer = SystemOutPrinter,
configure: ConnektContext.() -> Unit = {}
): ConnektContext {
val printer = SystemOutPrinter
return createConnektContext(
storage = storage,
environmentStore = environmentStore,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
package io.amplicode.connekt.integration

import io.amplicode.connekt.BaseNonColorPrinter
import io.amplicode.connekt.context.ValuesEnvironmentStore
import io.amplicode.connekt.context.execution.ExecutionScenario
import io.amplicode.connekt.context.persistence.InMemoryStorage
import io.amplicode.connekt.context.persistence.Storage
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertNotEquals
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import kotlin.reflect.typeOf

class TtlIntegrationTest : IntegrationTest() {

@Test
fun `cached value is reused while ttl is valid`() {
val storage = InMemoryStorage()

runTtlScript(storage, counterId = "ttl-valid", ttlMillis = 3_600_000)
val first = storage.token()

runTtlScript(storage, counterId = "ttl-valid", ttlMillis = 3_600_000)
val second = storage.token()

assertEquals(first, second)
}

@Test
fun `cached value is refreshed after ttl expires`() {
val storage = InMemoryStorage()

runTtlScript(storage, counterId = "ttl-expired", ttlMillis = 0)
val first = storage.token()

runTtlScript(storage, counterId = "ttl-expired", ttlMillis = 0)
val second = storage.token()

assertNotEquals(first, second)
}

@Test
fun `ttl derived from response is reused while valid`() {
val storage = InMemoryStorage()

runFromResponseScript(storage, expiresIn = 3600)
val first = storage.token()

runFromResponseScript(storage, expiresIn = 3600)
val second = storage.token()

assertEquals(first, second)
}

@Test
fun `ttl derived from response refreshes when expired`() {
val storage = InMemoryStorage()

runFromResponseScript(storage, expiresIn = 0)
val first = storage.token()

runFromResponseScript(storage, expiresIn = 0)
val second = storage.token()

assertNotEquals(first, second)
}

@Test
fun `ttl derived from header is reused while valid`() {
val storage = InMemoryStorage()

runFromHeaderScript(storage, expiresIn = 3600)
val first = storage.token()

runFromHeaderScript(storage, expiresIn = 3600)
val second = storage.token()

assertEquals(first, second)
}

@Test
fun `ttl derived from header refreshes when expired`() {
val storage = InMemoryStorage()

runFromHeaderScript(storage, expiresIn = 0)
val first = storage.token()

runFromHeaderScript(storage, expiresIn = 0)
val second = storage.token()

assertNotEquals(first, second)
}

@Test
fun `useCase value is reused while ttl is valid`() {
val storage = InMemoryStorage()

runUseCaseScript(storage, counterId = "uc-valid", ttlMillis = 3_600_000)
val first = storage.token()

runUseCaseScript(storage, counterId = "uc-valid", ttlMillis = 3_600_000)
val second = storage.token()

assertEquals(first, second)
}

@Test
fun `useCase value is refreshed after ttl expires`() {
val storage = InMemoryStorage()

runUseCaseScript(storage, counterId = "uc-expired", ttlMillis = 0)
val first = storage.token()

runUseCaseScript(storage, counterId = "uc-expired", ttlMillis = 0)
val second = storage.token()

assertNotEquals(first, second)
}

@Test
fun `value without ttl is cached indefinitely`() {
val storage = InMemoryStorage()

runAbsentTtlScript(storage, counterId = "ttl-absent")
val first = storage.token()

runAbsentTtlScript(storage, counterId = "ttl-absent")
val second = storage.token()

assertEquals(first, second)
}

private fun runFromResponseScript(storage: Storage, expiresIn: Long) {
val env = ValuesEnvironmentStore(
mapOf(
"host" to host,
"expiresIn" to expiresIn.toString()
)
)
runScriptFile(
scriptFile("ttl/ttl_from_response.connekt.kts"),
createIntegrationContext(env, storage),
ExecutionScenario.SingleExecution("echoed")
).assertSuccess()
}

private fun runFromHeaderScript(storage: Storage, expiresIn: Long) {
val env = ValuesEnvironmentStore(
mapOf(
"host" to host,
"expiresIn" to expiresIn.toString()
)
)
runScriptFile(
scriptFile("ttl/ttl_from_header.connekt.kts"),
createIntegrationContext(env, storage),
ExecutionScenario.SingleExecution("echoed")
).assertSuccess()
}

private fun runUseCaseScript(storage: Storage, counterId: String, ttlMillis: Long) {
val env = ValuesEnvironmentStore(
mapOf(
"host" to host,
"counterId" to counterId,
"ttlMillis" to ttlMillis.toString()
)
)
runScriptFile(
scriptFile("ttl/ttl_usecase.connekt.kts"),
createIntegrationContext(env, storage),
ExecutionScenario.SingleExecution("echoed")
).assertSuccess()
}

private fun runAbsentTtlScript(storage: Storage, counterId: String) {
val env = ValuesEnvironmentStore(mapOf("host" to host, "counterId" to counterId))
runScriptFile(
scriptFile("ttl/ttl_absent.connekt.kts"),
createIntegrationContext(env, storage),
ExecutionScenario.SingleExecution("echoed")
).assertSuccess()
}

@Test
fun `expired cache refresh is reported in output`() {
val storage = InMemoryStorage()
runTtlScript(storage, counterId = "ttl-log", ttlMillis = 0)

val output = StringBuilder()
val capturingPrinter = object : BaseNonColorPrinter() {
override fun print(s: String) {
output.append(s)
}
}
val env = ValuesEnvironmentStore(
mapOf("host" to host, "counterId" to "ttl-log", "ttlMillis" to "0")
)
runScriptFile(
scriptFile("ttl/ttl.connekt.kts"),
createIntegrationContext(env, storage, printer = capturingPrinter),
ExecutionScenario.SingleExecution("echoed")
).assertSuccess()

assertTrue(
output.contains("has expired, re-executing"),
"Expected TTL-expiry message in output, got:\n$output"
)
}

@Test
fun `expired useCase refresh is reported in output`() {
val storage = InMemoryStorage()
runUseCaseScript(storage, counterId = "uc-log", ttlMillis = 0)

val output = StringBuilder()
val capturingPrinter = object : BaseNonColorPrinter() {
override fun print(s: String) {
output.append(s)
}
}
val env = ValuesEnvironmentStore(
mapOf("host" to host, "counterId" to "uc-log", "ttlMillis" to "0")
)
runScriptFile(
scriptFile("ttl/ttl_usecase.connekt.kts"),
createIntegrationContext(env, storage, printer = capturingPrinter),
ExecutionScenario.SingleExecution("echoed")
).assertSuccess()

assertTrue(
output.contains("has expired, re-executing useCase"),
"Expected useCase TTL-expiry message in output, got:\n$output"
)
}

private fun runTtlScript(storage: Storage, counterId: String, ttlMillis: Long) {
val env = ValuesEnvironmentStore(
mapOf(
"host" to host,
"counterId" to counterId,
"ttlMillis" to ttlMillis.toString()
)
)
runScriptFile(
scriptFile("ttl/ttl.connekt.kts"),
createIntegrationContext(env, storage),
ExecutionScenario.SingleExecution("echoed")
).assertSuccess()
}

private fun Storage.token(): String? = getValue("token", typeOf<String>())
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fun Application.configureIntegrationRouting() {
}
jsonApi()
counterApi()
tokenApi()
echoApi()
cookiesApi()
oauthApi()
Expand Down Expand Up @@ -129,6 +130,20 @@ private fun Routing.counterApi() {
}
}

private fun Routing.tokenApi() {
val counter = AtomicInteger()
post("/token") {
val expiresIn = call.request.queryParameters["expires_in"]?.toLong() ?: 3600L
val token = "tok-${counter.incrementAndGet()}"
call.response.headers.append("X-Token-Expires-In", expiresIn.toString())
call.respondText(
//language=json
"""{"access_token": "$token", "expires_in": $expiresIn}""",
contentType = ContentType.Application.Json
)
}
}

@Serializable
data class SetCookieRequest(val cookieRequests: List<Cookie>)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import kotlin.time.Duration.Companion.milliseconds

val host: String by env
val counterId: String by env
val ttlMillis: Long by env

val token by POST("$host/counter/$counterId/inc") {
ttl(ttlMillis.milliseconds)
} then {
body!!.string()
}

val echoed by GET("$host/echo-query-params") {
queryParam("token", token)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
val host: String by env
val counterId: String by env

val token by POST("$host/counter/$counterId/inc") then {
body!!.string()
}

val echoed by GET("$host/echo-query-params") {
queryParam("token", token)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

val host: String by env

val fixed by POST("$host/auth/token") {
ttl(5.minutes)
} then {
decode<String>("$.access_token")
}

val fromBody by POST("$host/auth/token") {
ttl { decode<Long>("$.expires_in").seconds }
} then {
decode<String>("$.access_token")
}

val fromHeader by POST("$host/auth/token") {
ttl { header("X-Token-Expires-In")!!.toLong().seconds }
} then {
decode<String>("$.access_token")
}

val useCaseValue by useCase("token via useCase") {
ttl(5.minutes)
val response by POST("$host/auth/token")
response.decode<String>("$.access_token")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import kotlin.time.Duration.Companion.seconds

val host: String by env
val expiresIn: Long by env

val token by POST("$host/token") {
queryParam("expires_in", expiresIn)
ttl { header("X-Token-Expires-In")!!.toLong().seconds }
} then {
decode<String>("$.access_token")
}

val echoed by GET("$host/echo-query-params") {
queryParam("token", token)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import kotlin.time.Duration.Companion.seconds

val host: String by env
val expiresIn: Long by env

val token by POST("$host/token") {
queryParam("expires_in", expiresIn)
ttl { decode<Long>("$.expires_in").seconds }
} then {
decode<String>("$.access_token")
}

val echoed by GET("$host/echo-query-params") {
queryParam("token", token)
}
Loading
Loading