Skip to content

Commit

Permalink
Retry tests
Browse files Browse the repository at this point in the history
  • Loading branch information
smaugfm committed Nov 26, 2023
1 parent d93c94f commit ac358e9
Show file tree
Hide file tree
Showing 8 changed files with 306 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.github.smaugfm.monobudget.common.retry

import io.github.oshai.kotlinlogging.KotlinLogging
import io.github.smaugfm.monobudget.common.exception.BudgetBackendError
import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext
import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingEventListener
Expand All @@ -13,6 +14,8 @@ import org.koin.core.annotation.Single
import org.koin.core.component.KoinComponent
import org.koin.core.component.inject

private val log = KotlinLogging.logger {}

@Single
class RetryStatementSource(
private val scope: CoroutineScope,
Expand All @@ -35,6 +38,7 @@ class RetryStatementSource(
ctx,
retrySettings.interval,
)
log.warn(e) { "Error processing transaction. Will retry in ${retrySettings.interval}..." }
scheduleRetry(request)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ class TelegramErrorHandlerEventListener(
ctx: StatementProcessingContext,
e: BudgetBackendError,
) {
// Send retry message only on first retry
if (ctx.attempt != 1) {
// Send retry message only on first attempt
if (ctx.attempt != 0) {
return
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.github.smaugfm.monobudget.integration

import io.github.smaugfm.lunchmoney.exception.LunchmoneyApiResponseException
import io.ktor.http.HttpStatusCode
import reactor.core.publisher.Mono
import java.util.function.Function

class FailTrackerTransformation<T>(private val configs: List<IntegrationFailConfig>) :
Function<Mono<T>, Mono<T>> {
private var attempt = 0

override fun apply(mono: Mono<T>): Mono<T> =
(
if (configs.any { it.attemptFailRange.contains(attempt) }) {
Mono.error(LunchmoneyApiResponseException(HttpStatusCode.BadRequest.value))
} else {
mono
}
)
.also { attempt++ }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.github.smaugfm.monobudget.integration

sealed class IntegrationFailConfig(val attemptFailRange: IntRange) {
class Update(attemptFailRange: IntRange) :
IntegrationFailConfig(attemptFailRange)

class Insert(attemptFailRange: IntRange) :
IntegrationFailConfig(attemptFailRange)

class GetSingle(attemptFailRange: IntRange) :
IntegrationFailConfig(attemptFailRange)

class CreateTransactionGroup(attemptFailRange: IntRange) :
IntegrationFailConfig(attemptFailRange)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.github.smaugfm.lunchmoney.api.LunchmoneyApi
import io.github.smaugfm.lunchmoney.model.LunchmoneyCategoryMultiple
import io.github.smaugfm.lunchmoney.model.LunchmoneyInsertTransaction
import io.github.smaugfm.lunchmoney.model.LunchmoneyTransaction
import io.github.smaugfm.lunchmoney.response.LunchmoneyUpdateTransactionResponse
import io.github.smaugfm.monobudget.Application
import io.github.smaugfm.monobudget.TestBase
import io.github.smaugfm.monobudget.common.lifecycle.StatementProcessingContext
Expand Down Expand Up @@ -41,7 +42,7 @@ import kotlin.coroutines.cancellation.CancellationException

private val log = KotlinLogging.logger {}

@Suppress("MagicNumber")
@Suppress("MagicNumber", "LongMethod")
abstract class IntegrationTestBase : TestBase(), CoroutineScope {
override val coroutineContext = Dispatchers.Default

Expand Down Expand Up @@ -90,7 +91,7 @@ abstract class IntegrationTestBase : TestBase(), CoroutineScope {
this@IntegrationTestBase,
Settings.load(
Paths.get(
IntegrationTest::class.java.classLoader.getResource("test-settings.yml")!!.path,
TransactionsTest::class.java.classLoader.getResource("test-settings.yml")!!.path,
),
),
MonoWebhookSettings(false, URI.create(""), 0),
Expand All @@ -106,22 +107,36 @@ abstract class IntegrationTestBase : TestBase(), CoroutineScope {
)
}

protected fun setupTransferMocks(isFirst: (LunchmoneyInsertTransaction) -> Boolean): Pair<Long, Long> {
protected fun setupTransferMocks(isFirst: (LunchmoneyInsertTransaction) -> Boolean): Pair<Long, Long> =
setupTransferMocks(emptyList(), isFirst)

protected fun setupTransferMocks(
fails: List<IntegrationFailConfig>,
isFirst: (LunchmoneyInsertTransaction) -> Boolean,
): Pair<Long, Long> {
var insertTransaction: LunchmoneyInsertTransaction? = null
var insertTransaction2: LunchmoneyInsertTransaction? = null
val newTransactionId = 1L
val newTransactionId2 = 2L
val trGroupId = 3L
every { lunchmoneyMock.insertTransactions(any(), any(), any(), any(), any(), any()) } answers {
val i = firstArg<List<LunchmoneyInsertTransaction>>()[0]
if (isFirst(i)) {
insertTransaction = i
Mono.just(listOf(newTransactionId))
} else {
insertTransaction2 = i
Mono.just(listOf(newTransactionId2))
}
val mono =
if (isFirst(i)) {
insertTransaction = i
Mono.just(listOf(newTransactionId))
} else {
insertTransaction2 = i
Mono.just(listOf(newTransactionId2))
}
mono.transform(
FailTrackerTransformation(fails.filterIsInstance<IntegrationFailConfig.Insert>()),
)
}
val singleTransform =
FailTrackerTransformation<LunchmoneyTransaction>(
fails.filterIsInstance<IntegrationFailConfig.GetSingle>(),
)
every { lunchmoneyMock.getSingleTransaction(newTransactionId, any()) } answers {
Mono.just(
LunchmoneyTransaction(
Expand All @@ -136,7 +151,7 @@ abstract class IntegrationTestBase : TestBase(), CoroutineScope {
categoryId = insertTransaction?.categoryId,
status = insertTransaction!!.status!!,
),
)
).transform(singleTransform)
}
every { lunchmoneyMock.getSingleTransaction(newTransactionId2, any()) } answers {
Mono.just(
Expand All @@ -152,14 +167,24 @@ abstract class IntegrationTestBase : TestBase(), CoroutineScope {
categoryId = insertTransaction2?.categoryId,
status = insertTransaction2!!.status!!,
),
)
).transform(singleTransform)
}
every {
lunchmoneyMock.updateTransaction(any(), any(), any(), any(), any())
} returns Mono.just(mockk())
} returns
Mono.just(mockk<LunchmoneyUpdateTransactionResponse>())
.transform(
FailTrackerTransformation(fails.filterIsInstance<IntegrationFailConfig.Update>()),
)
every {
lunchmoneyMock.createTransactionGroup(any(), any(), any(), any(), any(), any())
} returns Mono.just(trGroupId)
} returns
Mono.just(trGroupId)
.transform(
FailTrackerTransformation(
fails.filterIsInstance<IntegrationFailConfig.CreateTransactionGroup>(),
),
)

return Pair(newTransactionId, newTransactionId2)
}
Expand All @@ -178,4 +203,34 @@ abstract class IntegrationTestBase : TestBase(), CoroutineScope {
}
}
}

protected fun setupSingleTransactionMocks(fails: List<IntegrationFailConfig> = emptyList()): Long {
var insertTransaction: LunchmoneyInsertTransaction? = null
val newTransactionId = 1L

every { lunchmoneyMock.insertTransactions(any(), any(), any(), any(), any(), any()) } answers {
insertTransaction = firstArg<List<LunchmoneyInsertTransaction>>()[0]
Mono.just(listOf(newTransactionId))
.transform(FailTrackerTransformation(fails.filterIsInstance<IntegrationFailConfig.Insert>()))
}
every { lunchmoneyMock.getSingleTransaction(newTransactionId, any()) } answers {
Mono.just(
LunchmoneyTransaction(
id = newTransactionId,
isGroup = false,
date = insertTransaction!!.date,
payee = insertTransaction!!.payee!!,
amount = insertTransaction!!.amount,
currency = insertTransaction!!.currency!!,
toBase = 1.0,
notes = insertTransaction?.notes,
categoryId = insertTransaction?.categoryId,
status = insertTransaction!!.status!!,
),
).transform(
FailTrackerTransformation(fails.filterIsInstance<IntegrationFailConfig.GetSingle>()),
)
}
return newTransactionId
}
}
Loading

0 comments on commit ac358e9

Please sign in to comment.