11package to.bitkit.repositories
22
33import app.cash.turbine.test
4+ import com.synonym.bitkitcore.CJitStateEnum
45import com.synonym.bitkitcore.FundingTx
56import com.synonym.bitkitcore.IBtChannel
67import com.synonym.bitkitcore.IBtInfo
@@ -215,6 +216,8 @@ class BlocktankRepoTest : BaseUnitTest() {
215216 // but it never opened a channel. It must not be mistaken for the freshly opened channel.
216217 val staleEntry = mock<IcJitEntry >()
217218 whenever(staleEntry.channel).thenReturn(null )
219+ whenever(staleEntry.state).thenReturn(CJitStateEnum .CREATED )
220+ seedCjitEntries(staleEntry)
218221 whenever(coreService.blocktank.cjitEntries(refresh = true )).thenReturn(listOf (staleEntry))
219222
220223 val channelDetails = mock<ChannelDetails >()
@@ -226,6 +229,7 @@ class BlocktankRepoTest : BaseUnitTest() {
226229 @Test
227230 fun `getCjitEntry matches the entry whose channel funding tx matches` () = test {
228231 sut = createSut()
232+ seedCjitEntries(pendingCjitEntry())
229233 val fundingTxId = " cjit-funding-tx"
230234 val matchingChannel = mock<IBtChannel >()
231235 whenever(matchingChannel.fundingTx).thenReturn(FundingTx (id = fundingTxId, vout = 0u ))
@@ -246,6 +250,7 @@ class BlocktankRepoTest : BaseUnitTest() {
246250 @Test
247251 fun `getCjitEntry returns null when no CJIT channel funding tx matches` () = test {
248252 sut = createSut()
253+ seedCjitEntries(pendingCjitEntry())
249254 val channel = mock<IBtChannel >()
250255 whenever(channel.fundingTx).thenReturn(FundingTx (id = " cjit-funding-tx" , vout = 0u ))
251256 val entry = mock<IcJitEntry >()
@@ -258,6 +263,53 @@ class BlocktankRepoTest : BaseUnitTest() {
258263 assertNull(sut.getCjitEntry(channelDetails))
259264 }
260265
266+ @Test
267+ fun `getCjitEntry does not refresh when no cached CJIT entry is awaiting a channel` () = test {
268+ sut = createSut()
269+ // Only an already-associated entry is cached (none awaiting a channel), so this ChannelReady cannot be a
270+ // CJIT: the server must not be hit even though a refresh would return a matching entry.
271+ val associatedChannel = mock<IBtChannel >()
272+ whenever(associatedChannel.fundingTx).thenReturn(FundingTx (id = " other-funding-tx" , vout = 0u ))
273+ val associatedEntry = mock<IcJitEntry >()
274+ whenever(associatedEntry.channel).thenReturn(associatedChannel)
275+ seedCjitEntries(associatedEntry)
276+
277+ val fundingTxId = " channel-order-funding-tx"
278+ val matchingChannel = mock<IBtChannel >()
279+ whenever(matchingChannel.fundingTx).thenReturn(FundingTx (id = fundingTxId, vout = 0u ))
280+ val matchingEntry = mock<IcJitEntry >()
281+ whenever(matchingEntry.channel).thenReturn(matchingChannel)
282+ whenever(coreService.blocktank.cjitEntries(refresh = true )).thenReturn(listOf (matchingEntry))
283+
284+ val channelDetails = mock<ChannelDetails >()
285+ whenever(channelDetails.fundingTxo).thenReturn(OutPoint (txid = fundingTxId, vout = 0u ))
286+
287+ assertNull(sut.getCjitEntry(channelDetails))
288+ }
289+
290+ @Test
291+ fun `getCjitEntry does not refresh for an expired CJIT entry awaiting no channel` () = test {
292+ sut = createSut()
293+ // An expired entry has no channel but can never open one, so it must not trigger a server refresh
294+ // even though a refresh would surface a matching entry.
295+ val expiredEntry = mock<IcJitEntry >()
296+ whenever(expiredEntry.channel).thenReturn(null )
297+ whenever(expiredEntry.state).thenReturn(CJitStateEnum .EXPIRED )
298+ seedCjitEntries(expiredEntry)
299+
300+ val fundingTxId = " channel-order-funding-tx"
301+ val matchingChannel = mock<IBtChannel >()
302+ whenever(matchingChannel.fundingTx).thenReturn(FundingTx (id = fundingTxId, vout = 0u ))
303+ val matchingEntry = mock<IcJitEntry >()
304+ whenever(matchingEntry.channel).thenReturn(matchingChannel)
305+ whenever(coreService.blocktank.cjitEntries(refresh = true )).thenReturn(listOf (matchingEntry))
306+
307+ val channelDetails = mock<ChannelDetails >()
308+ whenever(channelDetails.fundingTxo).thenReturn(OutPoint (txid = fundingTxId, vout = 0u ))
309+
310+ assertNull(sut.getCjitEntry(channelDetails))
311+ }
312+
261313 @Test
262314 fun `getCjitEntry returns cached entry without refreshing when already associated` () = test {
263315 sut = createSut()
@@ -266,10 +318,7 @@ class BlocktankRepoTest : BaseUnitTest() {
266318 whenever(channel.fundingTx).thenReturn(FundingTx (id = fundingTxId, vout = 0u ))
267319 val cachedEntry = mock<IcJitEntry >()
268320 whenever(cachedEntry.channel).thenReturn(channel)
269-
270- sut.restoreFromBackup(
271- BlocktankBackupV1 (createdAt = 0L , orders = emptyList(), cjitEntries = listOf (cachedEntry)),
272- )
321+ seedCjitEntries(cachedEntry)
273322
274323 val channelDetails = mock<ChannelDetails >()
275324 whenever(channelDetails.fundingTxo).thenReturn(OutPoint (txid = fundingTxId, vout = 0u ))
@@ -280,8 +329,9 @@ class BlocktankRepoTest : BaseUnitTest() {
280329 }
281330
282331 @Test
283- fun `getCjitEntry returns null when cache misses and refresh fails` () = test {
332+ fun `getCjitEntry returns null when a pending CJIT is awaiting but refresh fails` () = test {
284333 sut = createSut()
334+ seedCjitEntries(pendingCjitEntry())
285335 whenever(coreService.blocktank.cjitEntries(refresh = true )).thenThrow(RuntimeException (" Network error" ))
286336
287337 val channelDetails = mock<ChannelDetails >()
@@ -293,6 +343,7 @@ class BlocktankRepoTest : BaseUnitTest() {
293343 @Test
294344 fun `getCjitEntry retries the refresh and matches after a transient failure` () = test {
295345 sut = createSut()
346+ seedCjitEntries(pendingCjitEntry())
296347 val fundingTxId = " cjit-funding-tx"
297348 val channel = mock<IBtChannel >()
298349 whenever(channel.fundingTx).thenReturn(FundingTx (id = fundingTxId, vout = 0u ))
@@ -307,4 +358,15 @@ class BlocktankRepoTest : BaseUnitTest() {
307358
308359 assertEquals(entry, sut.getCjitEntry(channelDetails))
309360 }
361+
362+ private fun pendingCjitEntry (): IcJitEntry = mock<IcJitEntry >().apply {
363+ whenever(channel).thenReturn(null )
364+ whenever(state).thenReturn(CJitStateEnum .CREATED )
365+ }
366+
367+ private suspend fun seedCjitEntries (vararg entries : IcJitEntry ) {
368+ sut.restoreFromBackup(
369+ BlocktankBackupV1 (createdAt = 0L , orders = emptyList(), cjitEntries = entries.toList()),
370+ )
371+ }
310372}
0 commit comments