Add unit tests for ProcessDueRecurringExpenses #15

Closed
opened 2026-06-28 09:42:41 +00:00 by admin · 1 comment
Owner

Context

PR #10 (feat/3-implement-recurring-interactors) ships the recurring interactors with ./gradlew assembleDebug as the only verification. ProcessDueRecurringExpenses.await has several non-trivial behaviors that warrant tests:

  • The isActive = 1 filter in getDue(today) (app/src/main/java/dev/achmad/ledgerr/data/local/dao/RecurringExpenseDao.kt:34-35)
  • The <= today boundary (templates due today are processed, not just past-due ones)
  • The per-row advance of nextDueDate by interval.advance(...)
  • The edge case from docs/03-function-todos.md:138: "if the app has not been opened for multiple intervals, advance nextDueDate by one interval per call" (no back-fill flooding)
  • The recurringExpenseId link on the created Expense matches the source template's id
  • The returned list propagates the new expense id (not 0L)

These are the kinds of things an in-memory Room test would catch cheaply.

What to do

  1. Add androidx.room:room-testing to app/build.gradle.kts dependencies (testImplementation).
  2. Create app/src/test/java/dev/achmad/ledgerr/domain/recurring/interactor/ProcessDueRecurringExpensesTest.kt with cases:
    • await_createsOneExpenseAndAdvancesNextDueDate_whenTemplateDueToday
    • await_returnsEmptyList_whenNoTemplatesDue
    • await_skipsInactiveTemplates
    • await_advancesByOneIntervalOnly_evenIfMultiplePeriodsOverdue (the edge case)
    • await_linksCreatedExpenseToTemplateId
    • await_processesAllIntervals_dailyWeeklyMonthlyYearly (parameterized)
  3. Use Room.inMemoryDatabaseBuilder to build an in-memory AppDatabase per test. Pre-seed one default category so foreign-key constraints on expenses are satisfied.
  4. Verify ./gradlew test passes.

Notes

  • This issue is independent of the proposed withTransaction fix (separate issue). Re-run these tests after that fix lands to confirm the transaction does not change observable behavior.
  • Tests run on the JVM, so the test should not need to seed a RecurringExpense through Room callbacks — direct dao.insert(...) is fine.

Acceptance

  • ProcessDueRecurringExpensesTest covers the cases above
  • ./gradlew test passes
  • (Optional) tests re-run after the withTransaction fix lands

Implementation rule

Per AGENTS.md — do not start implementation without explicit user sign-off on this issue. When working, check for related issues in the remote repo first.

## Context PR #10 (`feat/3-implement-recurring-interactors`) ships the recurring interactors with `./gradlew assembleDebug` as the only verification. `ProcessDueRecurringExpenses.await` has several non-trivial behaviors that warrant tests: - The `isActive = 1` filter in `getDue(today)` (`app/src/main/java/dev/achmad/ledgerr/data/local/dao/RecurringExpenseDao.kt:34-35`) - The `<= today` boundary (templates due *today* are processed, not just past-due ones) - The per-row advance of `nextDueDate` by `interval.advance(...)` - The edge case from `docs/03-function-todos.md:138`: "if the app has not been opened for multiple intervals, advance `nextDueDate` by **one** interval per call" (no back-fill flooding) - The `recurringExpenseId` link on the created `Expense` matches the source template's id - The returned list propagates the new expense id (not `0L`) These are the kinds of things an in-memory Room test would catch cheaply. ## What to do 1. Add `androidx.room:room-testing` to `app/build.gradle.kts` dependencies (testImplementation). 2. Create `app/src/test/java/dev/achmad/ledgerr/domain/recurring/interactor/ProcessDueRecurringExpensesTest.kt` with cases: - `await_createsOneExpenseAndAdvancesNextDueDate_whenTemplateDueToday` - `await_returnsEmptyList_whenNoTemplatesDue` - `await_skipsInactiveTemplates` - `await_advancesByOneIntervalOnly_evenIfMultiplePeriodsOverdue` (the edge case) - `await_linksCreatedExpenseToTemplateId` - `await_processesAllIntervals_dailyWeeklyMonthlyYearly` (parameterized) 3. Use `Room.inMemoryDatabaseBuilder` to build an in-memory `AppDatabase` per test. Pre-seed one default category so foreign-key constraints on `expenses` are satisfied. 4. Verify `./gradlew test` passes. ## Notes - This issue is independent of the proposed `withTransaction` fix (separate issue). Re-run these tests after that fix lands to confirm the transaction does not change observable behavior. - Tests run on the JVM, so the test should not need to seed a `RecurringExpense` through Room callbacks — direct `dao.insert(...)` is fine. ## Acceptance - [ ] `ProcessDueRecurringExpensesTest` covers the cases above - [ ] `./gradlew test` passes - [ ] (Optional) tests re-run after the `withTransaction` fix lands ## Implementation rule Per `AGENTS.md` — do not start implementation without explicit user sign-off on this issue. When working, check for related issues in the remote repo first.
Author
Owner

Closing — addressed as a review comment on PR #10 (#10) instead of a separate issue.

Closing — addressed as a review comment on PR #10 (https://git.achmad.dev/admin/ledgerr/pulls/10) instead of a separate issue.
admin closed this issue 2026-06-28 09:44:47 +00:00
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: admin/ledgerr#15