Skip to content

Commit 743508f

Browse files
committed
feat: add tests for db
1 parent 9c72cb9 commit 743508f

5 files changed

Lines changed: 125 additions & 0 deletions

File tree

bindings/dart/test/wallet_test.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,28 @@ void main() {
3030
expect(balance.value, equals(0));
3131
});
3232

33+
test('in-memory sqlite handles concurrent access', () async {
34+
final memoryWallet = Wallet(
35+
mintUrl: 'https://testnut.cashudevkit.org',
36+
unit: SatCurrencyUnit(),
37+
mnemonic: generateMnemonic(),
38+
store: SqliteWalletStore(':memory:'),
39+
config: WalletConfig(targetProofCount: null),
40+
);
41+
42+
try {
43+
final balances = await Future.wait(
44+
List.generate(64, (_) => memoryWallet.totalBalance()),
45+
);
46+
47+
for (final balance in balances) {
48+
expect(balance.value, equals(0));
49+
}
50+
} finally {
51+
memoryWallet.dispose();
52+
}
53+
});
54+
3355
test('mint flow', () async {
3456
final quote = await wallet.mintQuote(
3557
paymentMethod: Bolt11PaymentMethod(),

bindings/kotlin/cdk-jvm/src/test/kotlin/org/cashudevkit/WalletTest.kt

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package org.cashudevkit
22

3+
import kotlinx.coroutines.async
4+
import kotlinx.coroutines.awaitAll
5+
import kotlinx.coroutines.coroutineScope
36
import kotlinx.coroutines.runBlocking
47
import org.junit.jupiter.api.AfterEach
58
import org.junit.jupiter.api.Assertions.*
@@ -37,6 +40,31 @@ class WalletTest {
3740
assertEquals(0UL, balance.value)
3841
}
3942

43+
@Test
44+
fun `in-memory sqlite handles concurrent access`() = runBlocking {
45+
val memoryWallet = Wallet(
46+
mintUrl = "https://testnut.cashudevkit.org",
47+
unit = CurrencyUnit.Sat,
48+
mnemonic = generateMnemonic(),
49+
store = WalletStore.Sqlite(path = ":memory:"),
50+
config = WalletConfig(targetProofCount = null),
51+
)
52+
53+
try {
54+
val balances = coroutineScope {
55+
(0 until 64).map {
56+
async { memoryWallet.totalBalance() }
57+
}.awaitAll()
58+
}
59+
60+
balances.forEach { balance ->
61+
assertEquals(0UL, balance.value)
62+
}
63+
} finally {
64+
memoryWallet.close()
65+
}
66+
}
67+
4068
@Test
4169
fun `mint flow`() = runBlocking {
4270
val quote = wallet.mintQuote(

bindings/swift/Tests/CdkTests.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,36 @@ struct CdkTests {
2525
#expect(balance.value == 0, "New wallet should have zero balance")
2626
}
2727

28+
@Test("In-memory SQLite handles concurrent access")
29+
func inMemorySqliteHandlesConcurrentAccess() async throws {
30+
let memoryWallet = try Wallet(
31+
mintUrl: "https://testnut.cashudevkit.org",
32+
unit: .sat,
33+
mnemonic: try generateMnemonic(),
34+
store: .sqlite(path: ":memory:"),
35+
config: WalletConfig(targetProofCount: nil)
36+
)
37+
38+
let balances = try await withThrowingTaskGroup(of: Amount.self) { group in
39+
for _ in 0..<64 {
40+
group.addTask {
41+
try await memoryWallet.totalBalance()
42+
}
43+
}
44+
45+
var balances: [Amount] = []
46+
for try await balance in group {
47+
balances.append(balance)
48+
}
49+
return balances
50+
}
51+
52+
#expect(balances.count == 64, "All concurrent balance reads should complete")
53+
for balance in balances {
54+
#expect(balance.value == 0, "New in-memory wallet should have zero balance")
55+
}
56+
}
57+
2858
@Test("Mint flow completes successfully")
2959
func mintFlow() async throws {
3060
let quote = try await wallet.mintQuote(

crates/cdk-ffi/tests/test_kvstore.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,12 @@ def create_test_db():
4242
return db, db_path
4343

4444

45+
def create_test_memory_db():
46+
"""Create an in-memory SQLite database for testing"""
47+
backend = cdk_ffi.WalletDbBackend.SQLITE(path=":memory:")
48+
return cdk_ffi.create_wallet_db(backend)
49+
50+
4551
def cleanup_db(db_path):
4652
"""Clean up the temporary database file"""
4753
if os.path.exists(db_path):
@@ -374,6 +380,31 @@ async def test_kv_persistence_across_instances():
374380
os.unlink(db_path)
375381

376382

383+
async def test_kv_in_memory_concurrent_access():
384+
"""Test concurrent FFI access to the single-connection in-memory SQLite pool"""
385+
print("\n=== Test: KV In-Memory Concurrent Access ===")
386+
387+
db = create_test_memory_db()
388+
389+
async def write_and_read(index):
390+
key = f"key_{index}"
391+
value = f"value_{index}".encode()
392+
await db.kv_write("memory", "concurrent", key, value)
393+
result = await db.kv_read("memory", "concurrent", key)
394+
assert result is not None, f"Expected value for {key}"
395+
assert bytes(result) == value, f"Expected {value}, got {bytes(result)}"
396+
397+
await asyncio.wait_for(
398+
asyncio.gather(*(write_and_read(index) for index in range(64))),
399+
timeout=10.0,
400+
)
401+
402+
keys = await db.kv_list("memory", "concurrent")
403+
assert len(keys) == 64, f"Expected 64 keys, got {len(keys)}"
404+
405+
print(" Test passed: concurrent in-memory KV access works")
406+
407+
377408
async def main():
378409
"""Run all KV store tests"""
379410
print("Starting CDK FFI Key-Value Store Tests")
@@ -395,6 +426,8 @@ async def main():
395426
("KV Special Key Names", test_kv_special_key_names),
396427
# Persistence
397428
("KV Persistence Across Instances", test_kv_persistence_across_instances),
429+
# In-memory SQLite pool
430+
("KV In-Memory Concurrent Access", test_kv_in_memory_concurrent_access),
398431
]
399432

400433
passed = 0

crates/cdk-sqlite/src/mint/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub type MintSqliteAuthDatabase = SQLMintAuthDatabase<SqliteConnectionManager>;
1616
#[cfg(test)]
1717
mod test {
1818
use std::fs::remove_file;
19+
use std::time::Duration;
1920

2021
use cdk_common::mint_db_test;
2122
use cdk_sql_common::pool::Pool;
@@ -40,6 +41,17 @@ mod test {
4041
let _ = remove_file("test.db");
4142
}
4243

44+
#[tokio::test]
45+
async fn exhausted_in_memory_pool_times_out() {
46+
let config: Config = ":memory:".into();
47+
let pool = Pool::<SqliteConnectionManager>::new(config);
48+
49+
let _conn = pool.get().await.expect("valid connection");
50+
let result = pool.get_timeout(Duration::from_millis(10)).await;
51+
52+
assert!(matches!(result, Err(cdk_sql_common::pool::Error::Timeout)));
53+
}
54+
4355
#[tokio::test]
4456
async fn open_legacy_and_migrate() {
4557
let file = format!(

0 commit comments

Comments
 (0)