refactor(quartz): swap FsEventStore clock ctor param for protected now()
The clock injection existed only for NIP-40 expiration tests. A public constructor parameter that ~every caller ignores is API clutter, so move it to a subclass seam: - FsEventStore is now `open class`; no more `clock: () -> Long` param. - `protected open fun now(): Long = TimeUtils.now()` is the override point. Production always takes the default; tests subclass. - FsExpirationTest gains a private `ClockedStore(root, source)` that overrides `now()`. Semantics unchanged, all 113 fs tests still green. Public `FsEventStore` ctor is now `(root, indexingStrategy, relay)` — no behavioural-drift surface that callers have to learn.
This commit is contained in:
+11
-8
@@ -57,14 +57,9 @@ import kotlin.io.path.readText
|
|||||||
* addressable slots, NIP-09 tombstones, NIP-40 expiration, NIP-50 FTS,
|
* addressable slots, NIP-09 tombstones, NIP-40 expiration, NIP-50 FTS,
|
||||||
* NIP-62 vanish, transactions, scrub.
|
* NIP-62 vanish, transactions, scrub.
|
||||||
*/
|
*/
|
||||||
class FsEventStore(
|
open class FsEventStore(
|
||||||
val root: Path,
|
val root: Path,
|
||||||
indexingStrategy: IndexingStrategy = DefaultIndexingStrategy(),
|
indexingStrategy: IndexingStrategy = DefaultIndexingStrategy(),
|
||||||
/**
|
|
||||||
* Source of "now" in unix seconds. Injectable so tests can drive
|
|
||||||
* NIP-40 expiration deterministically. Defaults to `TimeUtils.now()`.
|
|
||||||
*/
|
|
||||||
private val clock: () -> Long = { TimeUtils.now() },
|
|
||||||
/**
|
/**
|
||||||
* Optional relay URL the store is acting on behalf of. Used by
|
* Optional relay URL the store is acting on behalf of. Used by
|
||||||
* NIP-62 [RequestToVanishEvent.shouldVanishFrom] scoping. When
|
* NIP-62 [RequestToVanishEvent.shouldVanishFrom] scoping. When
|
||||||
@@ -153,6 +148,14 @@ class FsEventStore(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Source of "now" in unix seconds. Defaults to [TimeUtils.now]; tests
|
||||||
|
* override this via a subclass so NIP-40 expiration behaviour (insert
|
||||||
|
* guard and periodic sweep) can be driven deterministically without
|
||||||
|
* waiting on the wall clock or patching a global time source.
|
||||||
|
*/
|
||||||
|
protected open fun now(): Long = TimeUtils.now()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* NIP-40 pre-insert guard. Parity with SQLite's `reject_expired_events`
|
* NIP-40 pre-insert guard. Parity with SQLite's `reject_expired_events`
|
||||||
* trigger: an event whose expiration tag is `<= now` is rejected
|
* trigger: an event whose expiration tag is `<= now` is rejected
|
||||||
@@ -162,7 +165,7 @@ class FsEventStore(
|
|||||||
private fun isAlreadyExpired(event: Event): Boolean {
|
private fun isAlreadyExpired(event: Event): Boolean {
|
||||||
val exp = event.expiration() ?: return false
|
val exp = event.expiration() ?: return false
|
||||||
if (exp <= 0) return false
|
if (exp <= 0) return false
|
||||||
return exp <= clock()
|
return exp <= now()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -384,7 +387,7 @@ class FsEventStore(
|
|||||||
override fun deleteExpiredEvents() =
|
override fun deleteExpiredEvents() =
|
||||||
lockManager.withWriteLock {
|
lockManager.withWriteLock {
|
||||||
if (!Files.isDirectory(layout.idxExpiresAt)) return@withWriteLock
|
if (!Files.isDirectory(layout.idxExpiresAt)) return@withWriteLock
|
||||||
val now = clock()
|
val now = now()
|
||||||
val toDelete = ArrayList<HexKey>()
|
val toDelete = ArrayList<HexKey>()
|
||||||
Files.list(layout.idxExpiresAt).use { stream ->
|
Files.list(layout.idxExpiresAt).use { stream ->
|
||||||
for (entry in stream) {
|
for (entry in stream) {
|
||||||
|
|||||||
@@ -130,8 +130,6 @@ val store = FsEventStore(
|
|||||||
root = Path.of(System.getProperty("user.home"), ".amy", "events-store"),
|
root = Path.of(System.getProperty("user.home"), ".amy", "events-store"),
|
||||||
// optional NIP-62 relay scoping; null = only ALL_RELAYS vanish requests cascade
|
// optional NIP-62 relay scoping; null = only ALL_RELAYS vanish requests cascade
|
||||||
relay = null,
|
relay = null,
|
||||||
// optional clock for tests
|
|
||||||
clock = { com.vitorpamplona.quartz.utils.TimeUtils.now() },
|
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -140,6 +138,11 @@ bytes) is generated atomically on first open and read on every
|
|||||||
subsequent open — it salts every tag / owner hash, so it must persist
|
subsequent open — it salts every tag / owner hash, so it must persist
|
||||||
or every previously-written index entry becomes unreachable.
|
or every previously-written index entry becomes unreachable.
|
||||||
|
|
||||||
|
`FsEventStore` is `open` and exposes a `protected open fun now(): Long
|
||||||
|
= TimeUtils.now()`. Tests override it with a subclass to drive NIP-40
|
||||||
|
expiration at exact timestamps without relying on the wall clock; see
|
||||||
|
`FsExpirationTest.ClockedStore`.
|
||||||
|
|
||||||
### Insert
|
### Insert
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
|
|||||||
+13
-2
@@ -40,13 +40,24 @@ class FsExpirationTest {
|
|||||||
private lateinit var root: Path
|
private lateinit var root: Path
|
||||||
private var clockNow: Long = 1_000_000
|
private var clockNow: Long = 1_000_000
|
||||||
|
|
||||||
private lateinit var store: FsEventStore
|
/**
|
||||||
|
* Test subclass overriding the `now()` seam so we can drive NIP-40
|
||||||
|
* expiration at exact timestamps — no wall-clock sleeps, no flakes.
|
||||||
|
*/
|
||||||
|
private class ClockedStore(
|
||||||
|
root: Path,
|
||||||
|
private val source: () -> Long,
|
||||||
|
) : FsEventStore(root) {
|
||||||
|
override fun now(): Long = source()
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var store: ClockedStore
|
||||||
|
|
||||||
@BeforeTest
|
@BeforeTest
|
||||||
fun setup() {
|
fun setup() {
|
||||||
Secp256k1Instance
|
Secp256k1Instance
|
||||||
root = Files.createTempDirectory("fs-exp-")
|
root = Files.createTempDirectory("fs-exp-")
|
||||||
store = FsEventStore(root, clock = { clockNow })
|
store = ClockedStore(root) { clockNow }
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterTest
|
@AfterTest
|
||||||
|
|||||||
Reference in New Issue
Block a user