test(desktop-cache): bind accountPubkey before consuming self kind-3

The consumeContactList scoping fix means kind-3 events only update
`_followedUsers` when `event.pubKey == accountPubkey`. Tests were
constructing a fresh DesktopLocalCache() (accountPubkey = null) and
publishing kind-3s authored by `userPubKey` / `ownerPubKey`, so the
guard silently rejected them and `followedUsers` stayed empty.

Bind `accountPubkey` in the test setup so the guard passes.
This commit is contained in:
nrobi144
2026-07-02 18:11:16 +03:00
parent 9284e12e86
commit ce3536ddf7
3 changed files with 40 additions and 40 deletions
@@ -152,7 +152,7 @@ class CoordinatorPipelineTest {
fun `consumeEvent routes text note into cache and triggers ViewModel update`() =
runBlocking {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val (coordinator, _) = createCoordinator(cache, scope)
val vm = DesktopFeedViewModel(DesktopGlobalFeedFilter(cache), cache)
@@ -191,7 +191,7 @@ class CoordinatorPipelineTest {
fun `consumeEvent updates lastEventAt timestamp`() =
runBlocking {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val (coordinator, _) = createCoordinator(cache, scope)
assertTrue(coordinator.lastEventAt.value == null, "lastEventAt should be null initially")
@@ -217,7 +217,7 @@ class CoordinatorPipelineTest {
fun `contact list consumed via coordinator updates followedUsers`() =
runBlocking {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val (coordinator, _) = createCoordinator(cache, scope)
val contactEvent =
@@ -244,7 +244,7 @@ class CoordinatorPipelineTest {
fun `following feed shows notes after contact list and text notes arrive via coordinator`() =
runBlocking {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val (coordinator, _) = createCoordinator(cache, scope)
// Step 1: Contact list arrives
@@ -294,7 +294,7 @@ class CoordinatorPipelineTest {
fun `following feed remains empty when no contact list has been consumed`() =
runBlocking {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val (coordinator, _) = createCoordinator(cache, scope)
// No contact list consumed — followedUsers is empty
@@ -334,7 +334,7 @@ class CoordinatorPipelineTest {
fun `duplicate events are not double-counted in feed`() =
runBlocking {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val (coordinator, _) = createCoordinator(cache, scope)
val vm = DesktopFeedViewModel(DesktopGlobalFeedFilter(cache), cache)
@@ -372,7 +372,7 @@ class CoordinatorPipelineTest {
fun `requestInteractions opens subscription on client`() =
runBlocking {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val (coordinator, client) = createCoordinator(cache, scope)
val noteIds = listOf("n1".padEnd(64, '0'))
@@ -393,7 +393,7 @@ class CoordinatorPipelineTest {
fun `requestInteractions with empty noteIds returns without opening subscription`() =
runBlocking {
val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val (coordinator, client) = createCoordinator(cache, scope)
coordinator.requestInteractions(emptyList(), setOf(relayUrl))
@@ -124,7 +124,7 @@ class DesktopCachePipelineTest {
@Test
fun `consume text note creates Note in cache`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val event = textNote("note1".padEnd(64, '0'), userPubKey)
val consumed = cache.consume(event, relayUrl, wasVerified = true)
@@ -137,7 +137,7 @@ class DesktopCachePipelineTest {
@Test
fun `consume same note twice returns false`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val event = textNote("note1".padEnd(64, '0'), userPubKey)
cache.consume(event, relayUrl, wasVerified = true)
@@ -148,7 +148,7 @@ class DesktopCachePipelineTest {
@Test
fun `consume contact list updates followedUsers`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val event = contactList("cl1".padEnd(64, '0'), userPubKey, listOf(followedPubKey))
cache.consume(event, relayUrl, wasVerified = true)
@@ -158,7 +158,7 @@ class DesktopCachePipelineTest {
@Test
fun `newer contact list replaces older`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val old = contactList("cl1".padEnd(64, '0'), userPubKey, listOf(followedPubKey), createdAt = 100)
val newer =
contactList(
@@ -176,7 +176,7 @@ class DesktopCachePipelineTest {
@Test
fun `older contact list is rejected`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val newer = contactList("cl2".padEnd(64, '0'), userPubKey, listOf(followedPubKey, unfollowedPubKey), createdAt = 200)
val old = contactList("cl1".padEnd(64, '0'), userPubKey, listOf(followedPubKey), createdAt = 100)
@@ -192,7 +192,7 @@ class DesktopCachePipelineTest {
@Test
fun `consume reaction links to target note`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val noteId = "note1".padEnd(64, '0')
val note = textNote(noteId, userPubKey)
val react = reaction("react1".padEnd(64, '0'), followedPubKey, noteId)
@@ -211,7 +211,7 @@ class DesktopCachePipelineTest {
@Test
fun `consume emits to eventStream`() =
runBlocking {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val collected = mutableListOf<Set<Note>>()
val job =
@@ -240,7 +240,7 @@ class DesktopCachePipelineTest {
@Test
fun `GlobalFeedFilter includes all text notes`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val filter = DesktopGlobalFeedFilter(cache)
// Add notes from different authors
@@ -254,7 +254,7 @@ class DesktopCachePipelineTest {
@Test
fun `FollowingFeedFilter only includes notes from followed users`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
cache.consume(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl, wasVerified = true)
cache.consume(textNote("n1".padEnd(64, '0'), followedPubKey, createdAt = 100), relayUrl, wasVerified = true)
@@ -269,7 +269,7 @@ class DesktopCachePipelineTest {
@Test
fun `FollowingFeedFilter returns empty when no follows`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
cache.consume(textNote("n1".padEnd(64, '0'), followedPubKey), relayUrl, wasVerified = true)
val filter = DesktopFollowingFeedFilter(cache) { emptySet() }
@@ -280,7 +280,7 @@ class DesktopCachePipelineTest {
@Test
fun `ProfileFeedFilter only shows notes from target pubkey`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
cache.consume(textNote("n1".padEnd(64, '0'), followedPubKey, createdAt = 100), relayUrl, wasVerified = true)
cache.consume(textNote("n2".padEnd(64, '0'), unfollowedPubKey, createdAt = 200), relayUrl, wasVerified = true)
@@ -293,7 +293,7 @@ class DesktopCachePipelineTest {
@Test
fun `ThreadFilter returns root and replies`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val rootId = "root".padEnd(64, '0')
val replyId = "reply".padEnd(64, '0')
@@ -308,7 +308,7 @@ class DesktopCachePipelineTest {
@Test
fun `NotificationFeedFilter shows events tagging user`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val noteId = "note1".padEnd(64, '0')
cache.consume(textNote(noteId, userPubKey, createdAt = 100), relayUrl, wasVerified = true)
@@ -333,7 +333,7 @@ class DesktopCachePipelineTest {
@Test
fun `ViewModel starts in Loading then transitions to Loaded after refresh`() =
runBlocking {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
cache.consume(textNote("n1".padEnd(64, '0'), userPubKey), relayUrl, wasVerified = true)
val vm = DesktopFeedViewModel(DesktopGlobalFeedFilter(cache), cache)
@@ -352,7 +352,7 @@ class DesktopCachePipelineTest {
@Test
fun `ViewModel shows Empty when cache has no matching notes`() =
runBlocking {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val vm = DesktopFeedViewModel(DesktopGlobalFeedFilter(cache), cache)
waitForBundler()
@@ -365,7 +365,7 @@ class DesktopCachePipelineTest {
@Test
fun `ViewModel updates when new notes arrive via eventStream`() =
runBlocking {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val vm = DesktopFeedViewModel(DesktopGlobalFeedFilter(cache), cache)
waitForBundler()
@@ -388,7 +388,7 @@ class DesktopCachePipelineTest {
@Test
fun `Following ViewModel only shows followed users notes via eventStream`() =
runBlocking {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
cache.consume(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl, wasVerified = true)
val filter = DesktopFollowingFeedFilter(cache) { cache.followedUsers.value }
@@ -418,7 +418,7 @@ class DesktopCachePipelineTest {
@Test
fun `Following ViewModel feed is empty when followedUsers is empty`() =
runBlocking {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
// No contact list consumed — followedUsers remains empty
val e1 = textNote("n1".padEnd(64, '0'), followedPubKey)
@@ -441,7 +441,7 @@ class DesktopCachePipelineTest {
@Test
fun `clear resets all cache state`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
cache.consume(textNote("n1".padEnd(64, '0'), userPubKey), relayUrl, wasVerified = true)
cache.consume(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl, wasVerified = true)
@@ -458,7 +458,7 @@ class DesktopCachePipelineTest {
@Test
fun `global feed is sorted newest first`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
cache.consume(textNote("old".padEnd(64, '0'), userPubKey, createdAt = 100), relayUrl, wasVerified = true)
cache.consume(textNote("mid".padEnd(64, '0'), userPubKey, createdAt = 200), relayUrl, wasVerified = true)
cache.consume(textNote("new".padEnd(64, '0'), userPubKey, createdAt = 300), relayUrl, wasVerified = true)
@@ -476,7 +476,7 @@ class DesktopCachePipelineTest {
@Test
fun `consumeMetadata updates user info`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val metadata =
com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent(
id = "meta1".padEnd(64, '0'),
@@ -501,7 +501,7 @@ class DesktopCachePipelineTest {
@Test
fun `GlobalFeedFilter applyFilter only accepts TextNoteEvents`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val filter = DesktopGlobalFeedFilter(cache)
// Create a text note
@@ -522,7 +522,7 @@ class DesktopCachePipelineTest {
@Test
fun `FollowingFeedFilter applyFilter respects follow set`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
cache.consume(contactList("cl".padEnd(64, '0'), userPubKey, listOf(followedPubKey)), relayUrl, wasVerified = true)
val filter = DesktopFollowingFeedFilter(cache) { cache.followedUsers.value }
@@ -547,7 +547,7 @@ class DesktopCachePipelineTest {
@Test
fun `profile follower count is cached and survives clear of note cache`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
assertEquals(0, cache.getCachedFollowerCount(userPubKey))
@@ -561,7 +561,7 @@ class DesktopCachePipelineTest {
@Test
fun `profile following count is cached`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
cache.cacheFollowingCount(userPubKey, 150)
assertEquals(150, cache.getCachedFollowingCount(userPubKey))
@@ -569,7 +569,7 @@ class DesktopCachePipelineTest {
@Test
fun `clear resets profile count caches`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
cache.cacheFollowerCount(userPubKey, 42)
cache.cacheFollowingCount(userPubKey, 150)
@@ -581,7 +581,7 @@ class DesktopCachePipelineTest {
@Test
fun `metadata is available from cache after consumption`() {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = userPubKey }
val metadata =
com.vitorpamplona.quartz.nip01Core.metadata.MetadataEvent(
id = "meta1".padEnd(64, '0'),
@@ -140,7 +140,7 @@ class LocalRelayStoreHydrationTest {
@Test
fun hydratingAnEmptyDatabaseSucceedsAndLeavesCacheEmpty() =
runTest {
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = ownerPubKey }
val store = newStore()
try {
store.hydrate(cache)
@@ -165,7 +165,7 @@ class LocalRelayStoreHydrationTest {
// empty when phase 2 ran and the metadata would never load.
seedDatabase(listOf(followeeMetadata, contactList))
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = ownerPubKey }
val store = newStore()
try {
store.hydrate(cache)
@@ -193,7 +193,7 @@ class LocalRelayStoreHydrationTest {
val recentNote = makeTextNote(author, "recent", createdAt = nowSeconds() - 3600)
seedDatabase(listOf(recentNote))
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = ownerPubKey }
val store = newStore()
try {
store.hydrate(cache)
@@ -213,7 +213,7 @@ class LocalRelayStoreHydrationTest {
val oldNote = makeTextNote(author, "stale", createdAt = eightDaysAgo)
seedDatabase(listOf(oldNote))
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = ownerPubKey }
val store = newStore()
try {
store.hydrate(cache)
@@ -243,7 +243,7 @@ class LocalRelayStoreHydrationTest {
val note = makeTextNote(author, "round-trip")
seedDatabase(listOf(note))
val cache = DesktopLocalCache()
val cache = DesktopLocalCache().apply { accountPubkey = ownerPubKey }
val store = newStore()
try {
store.hydrate(cache)