From 4ed8f091592544a1c61a6f777fac3dcb2790eadf Mon Sep 17 00:00:00 2001
From: Wi1l-B0t <201105916+Wi1l-B0t@users.noreply.github.com>
Date: Fri, 5 Sep 2025 21:36:30 +0800
Subject: [PATCH 1/2] Add: unit tests for SQLite Wallet
---
neo.sln | 7 +
src/Plugins/SQLiteWallet/Account.cs | 2 +-
src/Plugins/SQLiteWallet/Address.cs | 2 +-
src/Plugins/SQLiteWallet/Contract.cs | 2 +-
src/Plugins/SQLiteWallet/Key.cs | 2 +-
src/Plugins/SQLiteWallet/SQLiteWallet.cs | 27 +-
src/Plugins/SQLiteWallet/SQLiteWallet.csproj | 4 +
.../SQLiteWallet/SQLiteWalletAccount.cs | 2 +-
.../SQLiteWallet/VerificationContract.cs | 2 +-
src/Plugins/SQLiteWallet/WalletDataContext.cs | 8 +-
.../Neo.Plugins.SQLiteWallet.Tests.csproj | 19 +
.../UT_SQLiteWallet.cs | 398 ++++++++++++++++++
.../UT_SQLiteWalletFactory.cs | 113 +++++
.../UT_VerificationContract.cs | 70 +++
.../UT_WalletDataContext.cs | 197 +++++++++
15 files changed, 835 insertions(+), 20 deletions(-)
create mode 100644 tests/Neo.Plugins.SQLiteWallet.Tests/Neo.Plugins.SQLiteWallet.Tests.csproj
create mode 100644 tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWallet.cs
create mode 100644 tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWalletFactory.cs
create mode 100644 tests/Neo.Plugins.SQLiteWallet.Tests/UT_VerificationContract.cs
create mode 100644 tests/Neo.Plugins.SQLiteWallet.Tests/UT_WalletDataContext.cs
diff --git a/neo.sln b/neo.sln
index cb775b49a4..62904b97bd 100644
--- a/neo.sln
+++ b/neo.sln
@@ -99,6 +99,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RestServer", "src\Plugins\R
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Plugins.StateService.Tests", "tests\Neo.Plugins.StateService.Tests\Neo.Plugins.StateService.Tests.csproj", "{229C7877-C0FA-4399-A0DB-96E714A59481}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Neo.Plugins.SQLiteWallet.Tests", "tests\Neo.Plugins.SQLiteWallet.Tests\Neo.Plugins.SQLiteWallet.Tests.csproj", "{92E091FE-C7E0-4526-8352-779B73F55F13}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -277,6 +279,10 @@ Global
{229C7877-C0FA-4399-A0DB-96E714A59481}.Debug|Any CPU.Build.0 = Debug|Any CPU
{229C7877-C0FA-4399-A0DB-96E714A59481}.Release|Any CPU.ActiveCfg = Release|Any CPU
{229C7877-C0FA-4399-A0DB-96E714A59481}.Release|Any CPU.Build.0 = Release|Any CPU
+ {92E091FE-C7E0-4526-8352-779B73F55F13}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {92E091FE-C7E0-4526-8352-779B73F55F13}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {92E091FE-C7E0-4526-8352-779B73F55F13}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {92E091FE-C7E0-4526-8352-779B73F55F13}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -326,6 +332,7 @@ Global
{A7FE2B30-11F8-E88D-D5BF-AF1B11EFEC8E} = {7F257712-D033-47FF-B439-9D4320D06599}
{4865C487-C1A1-4E36-698D-1EC4CCF08FDB} = {C2DC830A-327A-42A7-807D-295216D30DBB}
{229C7877-C0FA-4399-A0DB-96E714A59481} = {7F257712-D033-47FF-B439-9D4320D06599}
+ {92E091FE-C7E0-4526-8352-779B73F55F13} = {7F257712-D033-47FF-B439-9D4320D06599}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BCBA19D9-F868-4C6D-8061-A2B91E06E3EC}
diff --git a/src/Plugins/SQLiteWallet/Account.cs b/src/Plugins/SQLiteWallet/Account.cs
index 8667123c83..facc3d112d 100644
--- a/src/Plugins/SQLiteWallet/Account.cs
+++ b/src/Plugins/SQLiteWallet/Account.cs
@@ -11,7 +11,7 @@
namespace Neo.Wallets.SQLite
{
- class Account
+ internal class Account
{
public byte[] PublicKeyHash { get; set; }
public string Nep2key { get; set; }
diff --git a/src/Plugins/SQLiteWallet/Address.cs b/src/Plugins/SQLiteWallet/Address.cs
index 687fd75c06..56642b081e 100644
--- a/src/Plugins/SQLiteWallet/Address.cs
+++ b/src/Plugins/SQLiteWallet/Address.cs
@@ -11,7 +11,7 @@
namespace Neo.Wallets.SQLite
{
- class Address
+ internal class Address
{
public byte[] ScriptHash { get; set; }
}
diff --git a/src/Plugins/SQLiteWallet/Contract.cs b/src/Plugins/SQLiteWallet/Contract.cs
index 5a380c3470..3dbcf91a0a 100644
--- a/src/Plugins/SQLiteWallet/Contract.cs
+++ b/src/Plugins/SQLiteWallet/Contract.cs
@@ -11,7 +11,7 @@
namespace Neo.Wallets.SQLite
{
- class Contract
+ internal class Contract
{
public byte[] RawData { get; set; }
public byte[] ScriptHash { get; set; }
diff --git a/src/Plugins/SQLiteWallet/Key.cs b/src/Plugins/SQLiteWallet/Key.cs
index b60f0bfffc..ed8614bb04 100644
--- a/src/Plugins/SQLiteWallet/Key.cs
+++ b/src/Plugins/SQLiteWallet/Key.cs
@@ -11,7 +11,7 @@
namespace Neo.Wallets.SQLite
{
- class Key
+ internal class Key
{
public string Name { get; set; }
public byte[] Value { get; set; }
diff --git a/src/Plugins/SQLiteWallet/SQLiteWallet.cs b/src/Plugins/SQLiteWallet/SQLiteWallet.cs
index bd3e843a80..8d7d5093b8 100644
--- a/src/Plugins/SQLiteWallet/SQLiteWallet.cs
+++ b/src/Plugins/SQLiteWallet/SQLiteWallet.cs
@@ -27,7 +27,7 @@ namespace Neo.Wallets.SQLite
///
/// A wallet implementation that uses SQLite as the underlying storage.
///
- class SQLiteWallet : Wallet
+ internal class SQLiteWallet : Wallet
{
private readonly Lock _lock = new();
private readonly byte[] _iv;
@@ -57,28 +57,35 @@ public override Version Version
}
}
+ ///
+ /// Opens a wallet at the specified path.
+ ///
private SQLiteWallet(string path, byte[] passwordKey, ProtocolSettings settings) : base(path, settings)
{
+ if (!File.Exists(path)) throw new InvalidOperationException($"Wallet file {path} not found");
+
using var ctx = new WalletDataContext(Path);
_salt = LoadStoredData(ctx, "Salt")
?? throw new FormatException("Salt was not found");
var passwordHash = LoadStoredData(ctx, "PasswordHash")
?? throw new FormatException("PasswordHash was not found");
if (!passwordHash.SequenceEqual(passwordKey.Concat(_salt).ToArray().Sha256()))
- throw new CryptographicException();
- _iv = LoadStoredData(ctx, "IV")
- ?? throw new FormatException("IV was not found");
+ throw new CryptographicException("Invalid password");
+
+ _iv = LoadStoredData(ctx, "IV") ?? throw new FormatException("IV was not found");
_masterKey = Decrypt(LoadStoredData(ctx, "MasterKey")
?? throw new FormatException("MasterKey was not found"), passwordKey, _iv);
- _scrypt = new ScryptParameters
- (
+ _scrypt = new ScryptParameters(
BinaryPrimitives.ReadInt32LittleEndian(LoadStoredData(ctx, "ScryptN") ?? throw new FormatException("ScryptN was not found")),
BinaryPrimitives.ReadInt32LittleEndian(LoadStoredData(ctx, "ScryptR") ?? throw new FormatException("ScryptR was not found")),
BinaryPrimitives.ReadInt32LittleEndian(LoadStoredData(ctx, "ScryptP") ?? throw new FormatException("ScryptP was not found"))
- );
+ );
_accounts = LoadAccounts(ctx);
}
+ ///
+ /// Creates a new wallet at the specified path.
+ ///
private SQLiteWallet(string path, byte[] passwordKey, ProtocolSettings settings, ScryptParameters scrypt) : base(path, settings)
{
_iv = new byte[16];
@@ -397,7 +404,7 @@ public override bool VerifyPassword(string password)
return ToAesKey(password).Concat(_salt).ToArray().Sha256().SequenceEqual(hash);
}
- private static byte[] Encrypt(byte[] data, byte[] key, byte[] iv)
+ internal static byte[] Encrypt(byte[] data, byte[] key, byte[] iv)
{
ArgumentNullException.ThrowIfNull(data, nameof(data));
ArgumentNullException.ThrowIfNull(key, nameof(key));
@@ -413,7 +420,7 @@ private static byte[] Encrypt(byte[] data, byte[] key, byte[] iv)
return encryptor.TransformFinalBlock(data, 0, data.Length);
}
- private static byte[] Decrypt(byte[] data, byte[] key, byte[] iv)
+ internal static byte[] Decrypt(byte[] data, byte[] key, byte[] iv)
{
ArgumentNullException.ThrowIfNull(data, nameof(data));
ArgumentNullException.ThrowIfNull(key, nameof(key));
@@ -429,7 +436,7 @@ private static byte[] Decrypt(byte[] data, byte[] key, byte[] iv)
return decryptor.TransformFinalBlock(data, 0, data.Length);
}
- private static byte[] ToAesKey(string password)
+ internal static byte[] ToAesKey(string password)
{
var passwordBytes = Encoding.UTF8.GetBytes(password);
var passwordHash = SHA256.HashData(passwordBytes);
diff --git a/src/Plugins/SQLiteWallet/SQLiteWallet.csproj b/src/Plugins/SQLiteWallet/SQLiteWallet.csproj
index e5c258e8cb..400adc98aa 100644
--- a/src/Plugins/SQLiteWallet/SQLiteWallet.csproj
+++ b/src/Plugins/SQLiteWallet/SQLiteWallet.csproj
@@ -11,4 +11,8 @@
+
+
+
+
diff --git a/src/Plugins/SQLiteWallet/SQLiteWalletAccount.cs b/src/Plugins/SQLiteWallet/SQLiteWalletAccount.cs
index 0bbbcda7e4..bb713357c6 100644
--- a/src/Plugins/SQLiteWallet/SQLiteWalletAccount.cs
+++ b/src/Plugins/SQLiteWallet/SQLiteWalletAccount.cs
@@ -11,7 +11,7 @@
namespace Neo.Wallets.SQLite
{
- sealed class SQLiteWalletAccount : WalletAccount
+ internal sealed class SQLiteWalletAccount : WalletAccount
{
public KeyPair Key;
diff --git a/src/Plugins/SQLiteWallet/VerificationContract.cs b/src/Plugins/SQLiteWallet/VerificationContract.cs
index f5e2052ad5..3857e6492b 100644
--- a/src/Plugins/SQLiteWallet/VerificationContract.cs
+++ b/src/Plugins/SQLiteWallet/VerificationContract.cs
@@ -15,7 +15,7 @@
namespace Neo.Wallets.SQLite
{
- class VerificationContract : SmartContract.Contract, IEquatable, ISerializable
+ internal class VerificationContract : SmartContract.Contract, IEquatable, ISerializable
{
public int Size => ParameterList.GetVarSize() + Script.GetVarSize();
diff --git a/src/Plugins/SQLiteWallet/WalletDataContext.cs b/src/Plugins/SQLiteWallet/WalletDataContext.cs
index 4da9d31627..0f6ac2db96 100644
--- a/src/Plugins/SQLiteWallet/WalletDataContext.cs
+++ b/src/Plugins/SQLiteWallet/WalletDataContext.cs
@@ -14,18 +14,18 @@
namespace Neo.Wallets.SQLite
{
- class WalletDataContext : DbContext
+ internal class WalletDataContext : DbContext
{
public DbSet Accounts { get; set; }
public DbSet Addresses { get; set; }
public DbSet Contracts { get; set; }
public DbSet Keys { get; set; }
- private readonly string filename;
+ private readonly string _filename;
public WalletDataContext(string filename)
{
- this.filename = filename;
+ _filename = filename;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
@@ -33,7 +33,7 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
base.OnConfiguring(optionsBuilder);
var sb = new SqliteConnectionStringBuilder()
{
- DataSource = filename
+ DataSource = _filename
};
optionsBuilder.UseSqlite(sb.ToString());
}
diff --git a/tests/Neo.Plugins.SQLiteWallet.Tests/Neo.Plugins.SQLiteWallet.Tests.csproj b/tests/Neo.Plugins.SQLiteWallet.Tests/Neo.Plugins.SQLiteWallet.Tests.csproj
new file mode 100644
index 0000000000..3909b9c1fe
--- /dev/null
+++ b/tests/Neo.Plugins.SQLiteWallet.Tests/Neo.Plugins.SQLiteWallet.Tests.csproj
@@ -0,0 +1,19 @@
+
+
+
+ net9.0
+ latest
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWallet.cs b/tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWallet.cs
new file mode 100644
index 0000000000..394f58a6f4
--- /dev/null
+++ b/tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWallet.cs
@@ -0,0 +1,398 @@
+// Copyright (C) 2015-2025 The Neo Project.
+//
+// UT_SQLiteWallet.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+
+using Microsoft.Data.Sqlite;
+using Neo.Extensions;
+using Neo.SmartContract;
+using Neo.Wallets.NEP6;
+using System.Security.Cryptography;
+
+namespace Neo.Wallets.SQLite
+{
+ [TestClass]
+ public class UT_SQLiteWallet
+ {
+ private const string TestPassword = "test_password_123";
+ private static readonly ProtocolSettings TestSettings = ProtocolSettings.Default;
+ private static int s_counter = 0;
+
+ private static string GetTestWalletPath()
+ {
+ return $"test_wallet_{++s_counter}.db3";
+ }
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ SqliteConnection.ClearAllPools();
+ var files = Directory.GetFiles(".", "test_wallet_*");
+ foreach (var file in files)
+ {
+ File.Delete(file);
+ }
+ }
+
+ [TestMethod]
+ public void TestCreateWallet()
+ {
+ var path = GetTestWalletPath();
+ var wallet = SQLiteWallet.Create(path, TestPassword, TestSettings);
+
+ Assert.IsNotNull(wallet);
+ Assert.AreEqual(Path.GetFileNameWithoutExtension(path), wallet.Name);
+ Assert.IsTrue(File.Exists(path));
+
+ // Test that wallet can be opened with correct password
+ var openedWallet = SQLiteWallet.Open(path, TestPassword, TestSettings);
+ Assert.IsNotNull(openedWallet);
+ Assert.AreEqual(wallet.Name, openedWallet.Name);
+ }
+
+ [TestMethod]
+ public void TestCreateWalletWithCustomScrypt()
+ {
+ var customScrypt = new ScryptParameters(16384, 8, 8);
+ var path = GetTestWalletPath();
+ var wallet = SQLiteWallet.Create(path, TestPassword, TestSettings, customScrypt);
+
+ Assert.IsNotNull(wallet);
+ Assert.IsTrue(File.Exists(path));
+ }
+
+ [TestMethod]
+ public void TestOpenWalletWithInvalidPassword()
+ {
+ var path = GetTestWalletPath();
+ // Create wallet first
+ SQLiteWallet.Create(path, TestPassword, TestSettings);
+
+ // Try to open with wrong password
+ Assert.ThrowsExactly(() => SQLiteWallet.Open(path, "wrong_password", TestSettings));
+ }
+
+ [TestMethod]
+ public void TestOpenNonExistentWallet()
+ {
+ Assert.ThrowsExactly(
+ () => SQLiteWallet.Open("test_non_existent.db3", TestPassword, TestSettings),
+ "Wallet file test_non_existent.db3 not found");
+ }
+
+ [TestMethod]
+ public void TestWalletName()
+ {
+ var path = GetTestWalletPath();
+ var wallet = SQLiteWallet.Create(path, TestPassword, TestSettings);
+ Assert.AreEqual(Path.GetFileNameWithoutExtension(path), wallet.Name);
+ }
+
+ [TestMethod]
+ public void TestWalletVersion()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+ var version = wallet.Version;
+ Assert.IsNotNull(version);
+ Assert.IsTrue(version.Major >= 0);
+ }
+
+ [TestMethod]
+ public void TestVerifyPassword()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+
+ Assert.IsTrue(wallet.VerifyPassword(TestPassword));
+ Assert.IsFalse(wallet.VerifyPassword("wrong_password"));
+ Assert.IsFalse(wallet.VerifyPassword(""));
+ }
+
+ [TestMethod]
+ public void TestChangePassword()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+ const string newPassword = "new_password_456";
+
+ // Test successful password change
+ Assert.IsTrue(wallet.ChangePassword(TestPassword, newPassword));
+ Assert.IsTrue(wallet.VerifyPassword(newPassword));
+ Assert.IsFalse(wallet.VerifyPassword(TestPassword));
+
+ // Test password change with wrong old password
+ Assert.IsFalse(wallet.ChangePassword("wrong_old_password", "another_password"));
+ }
+
+ [TestMethod]
+ public void TestCreateAccountWithPrivateKey()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+ var privateKey = new byte[32];
+ RandomNumberGenerator.Fill(privateKey);
+
+ var account = wallet.CreateAccount(privateKey);
+
+ Assert.IsNotNull(account);
+ Assert.IsTrue(account.HasKey);
+ Assert.IsNotNull(account.GetKey());
+ Assert.IsTrue(wallet.Contains(account.ScriptHash));
+ }
+
+ [TestMethod]
+ public void TestCreateAccountWithContract()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+ var privateKey = new byte[32];
+ RandomNumberGenerator.Fill(privateKey);
+ var keyPair = new KeyPair(privateKey);
+ var contract = new VerificationContract
+ {
+ Script = SmartContract.Contract.CreateSignatureRedeemScript(keyPair.PublicKey),
+ ParameterList = [ContractParameterType.Signature]
+ };
+
+ var account = wallet.CreateAccount(contract, keyPair);
+
+ Assert.IsNotNull(account);
+ Assert.IsTrue(account.HasKey);
+ Assert.AreEqual(contract.ScriptHash, account.ScriptHash);
+ Assert.IsTrue(wallet.Contains(account.ScriptHash));
+ }
+
+ [TestMethod]
+ public void TestCreateAccountWithScriptHash()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+ var scriptHash = UInt160.Zero;
+ var account = wallet.CreateAccount(scriptHash);
+ Assert.IsNotNull(account);
+ Assert.IsFalse(account.HasKey);
+ Assert.AreEqual(scriptHash, account.ScriptHash);
+ Assert.IsTrue(wallet.Contains(scriptHash));
+ }
+
+ [TestMethod]
+ public void TestGetAccount()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+ var privateKey = new byte[32];
+ RandomNumberGenerator.Fill(privateKey);
+ var account = wallet.CreateAccount(privateKey);
+
+ var retrievedAccount = wallet.GetAccount(account.ScriptHash);
+ Assert.IsNotNull(retrievedAccount);
+ Assert.AreEqual(account.ScriptHash, retrievedAccount.ScriptHash);
+
+ // Test getting non-existent account
+ var nonExistentAccount = wallet.GetAccount(UInt160.Zero);
+ Assert.IsNull(nonExistentAccount);
+ }
+
+ [TestMethod]
+ public void TestGetAccounts()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+
+ // Initially no accounts
+ var accounts = wallet.GetAccounts().ToArray();
+ Assert.AreEqual(0, accounts.Length);
+
+ // Add some accounts
+ var privateKey1 = new byte[32];
+ var privateKey2 = new byte[32];
+ RandomNumberGenerator.Fill(privateKey1);
+ RandomNumberGenerator.Fill(privateKey2);
+
+ var account1 = wallet.CreateAccount(privateKey1);
+ var account2 = wallet.CreateAccount(privateKey2);
+
+ accounts = wallet.GetAccounts().ToArray();
+ Assert.AreEqual(2, accounts.Length);
+ Assert.IsTrue(accounts.Any(a => a.ScriptHash == account1.ScriptHash));
+ Assert.IsTrue(accounts.Any(a => a.ScriptHash == account2.ScriptHash));
+ }
+
+ [TestMethod]
+ public void TestContains()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+ var privateKey = new byte[32];
+ RandomNumberGenerator.Fill(privateKey);
+ var account = wallet.CreateAccount(privateKey);
+
+ Assert.IsTrue(wallet.Contains(account.ScriptHash));
+ Assert.IsFalse(wallet.Contains(UInt160.Zero));
+ }
+
+ [TestMethod]
+ public void TestDeleteAccount()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+ var privateKey = new byte[32];
+ RandomNumberGenerator.Fill(privateKey);
+ var account = wallet.CreateAccount(privateKey);
+
+ Assert.IsTrue(wallet.Contains(account.ScriptHash));
+
+ // Delete account
+ Assert.IsTrue(wallet.DeleteAccount(account.ScriptHash));
+ Assert.IsFalse(wallet.Contains(account.ScriptHash));
+
+ // Try to delete non-existent account
+ Assert.IsFalse(wallet.DeleteAccount(UInt160.Zero));
+ }
+
+ [TestMethod]
+ public void TestDeleteWallet()
+ {
+ var path = GetTestWalletPath();
+ var wallet = SQLiteWallet.Create(path, TestPassword, TestSettings);
+ Assert.IsTrue(File.Exists(path));
+
+ wallet.Delete();
+ Assert.IsFalse(File.Exists(path));
+ }
+
+ [TestMethod]
+ public void TestSave()
+ {
+ var wallet = SQLiteWallet.Create(GetTestWalletPath(), TestPassword, TestSettings);
+
+ // Save should not throw exception (it's a no-op for SQLiteWallet)
+ wallet.Save();
+ }
+
+ [TestMethod]
+ public void TestEncryptDecrypt()
+ {
+ var data = new byte[32];
+ var key = new byte[32];
+ var iv = new byte[16];
+ RandomNumberGenerator.Fill(data);
+ RandomNumberGenerator.Fill(key);
+ RandomNumberGenerator.Fill(iv);
+
+ // Test encryption
+ var encrypted = SQLiteWallet.Encrypt(data, key, iv);
+ Assert.IsNotNull(encrypted);
+ Assert.AreEqual(data.Length, encrypted.Length);
+ Assert.IsFalse(data.SequenceEqual(encrypted));
+
+ // Test decryption
+ var decrypted = SQLiteWallet.Decrypt(encrypted, key, iv);
+ Assert.IsTrue(data.SequenceEqual(decrypted));
+ }
+
+ [TestMethod]
+ public void TestEncryptWithInvalidParameters()
+ {
+ var data = new byte[15]; // Not multiple of 16
+ var key = new byte[32];
+ var iv = new byte[16];
+ Assert.ThrowsExactly(() => SQLiteWallet.Encrypt(data, key, iv));
+
+ data = new byte[32];
+ key = new byte[31]; // Wrong key length
+ Assert.ThrowsExactly(() => SQLiteWallet.Encrypt(data, key, iv));
+
+ key = new byte[32];
+ iv = new byte[15]; // Wrong IV length
+ Assert.ThrowsExactly(() => SQLiteWallet.Encrypt(data, key, iv));
+ }
+
+ [TestMethod]
+ public void TestToAesKey()
+ {
+ const string password = "test_password";
+ var key1 = SQLiteWallet.ToAesKey(password);
+ var key2 = SQLiteWallet.ToAesKey(password);
+
+ Assert.IsNotNull(key1);
+ Assert.AreEqual(32, key1.Length);
+ Assert.IsTrue(key1.SequenceEqual(key2)); // Should be deterministic
+
+ // Test with different password
+ var key3 = SQLiteWallet.ToAesKey("different_password");
+ Assert.IsFalse(key1.SequenceEqual(key3));
+ }
+
+ [TestMethod]
+ public void TestAccountPersistence()
+ {
+ // Create wallet and add account
+ var path = GetTestWalletPath();
+ var wallet1 = SQLiteWallet.Create(path, TestPassword, TestSettings);
+ var privateKey = new byte[32];
+ RandomNumberGenerator.Fill(privateKey);
+ var account1 = wallet1.CreateAccount(privateKey);
+
+ // Close and reopen wallet
+ var wallet2 = SQLiteWallet.Open(path, TestPassword, TestSettings);
+
+ // Verify account still exists
+ Assert.IsTrue(wallet2.Contains(account1.ScriptHash));
+ var account2 = wallet2.GetAccount(account1.ScriptHash);
+ Assert.IsNotNull(account2);
+ Assert.AreEqual(account1.ScriptHash, account2.ScriptHash);
+ Assert.IsTrue(account2.HasKey);
+ }
+
+ [TestMethod]
+ public void TestMultipleAccounts()
+ {
+ var path = GetTestWalletPath();
+ var wallet = SQLiteWallet.Create(path, TestPassword, TestSettings);
+
+ // Create multiple accounts
+ var accounts = new WalletAccount[5];
+ for (int i = 0; i < 5; i++)
+ {
+ var privateKey = new byte[32];
+ RandomNumberGenerator.Fill(privateKey);
+ accounts[i] = wallet.CreateAccount(privateKey);
+ }
+
+ // Verify all accounts exist
+ var retrievedAccounts = wallet.GetAccounts().ToArray();
+ Assert.AreEqual(5, retrievedAccounts.Length);
+
+ foreach (var account in accounts)
+ {
+ Assert.IsTrue(wallet.Contains(account.ScriptHash));
+ var retrievedAccount = wallet.GetAccount(account.ScriptHash);
+ Assert.IsNotNull(retrievedAccount);
+ Assert.AreEqual(account.ScriptHash, retrievedAccount.ScriptHash);
+ }
+ }
+
+ [TestMethod]
+ public void TestAccountWithContractPersistence()
+ {
+ var path = GetTestWalletPath();
+ var wallet1 = SQLiteWallet.Create(path, TestPassword, TestSettings);
+ var privateKey = new byte[32];
+ RandomNumberGenerator.Fill(privateKey);
+ var keyPair = new KeyPair(privateKey);
+ var contract = new VerificationContract
+ {
+ Script = SmartContract.Contract.CreateSignatureRedeemScript(keyPair.PublicKey),
+ ParameterList = [ContractParameterType.Signature]
+ };
+ var account1 = wallet1.CreateAccount(contract, keyPair);
+
+ // Reopen wallet
+ var wallet2 = SQLiteWallet.Open(path, TestPassword, TestSettings);
+ var account2 = wallet2.GetAccount(account1.ScriptHash);
+
+ Assert.IsNotNull(account2);
+ Assert.IsTrue(account2.HasKey);
+ Assert.IsNotNull(account2.Contract);
+ }
+ }
+}
diff --git a/tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWalletFactory.cs b/tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWalletFactory.cs
new file mode 100644
index 0000000000..8be9788093
--- /dev/null
+++ b/tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWalletFactory.cs
@@ -0,0 +1,113 @@
+// Copyright (C) 2015-2025 The Neo Project.
+//
+// UT_SQLiteWalletFactory.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using Microsoft.Data.Sqlite;
+using System.Security.Cryptography;
+
+namespace Neo.Wallets.SQLite
+{
+ [TestClass]
+ public class UT_SQLiteWalletFactory
+ {
+ private const string TestPassword = "test_password_123";
+ private static readonly ProtocolSettings TestSettings = ProtocolSettings.Default;
+ private static int s_counter = 0;
+
+ private string GetTestWalletPath()
+ {
+ return $"test_factory_wallet_{++s_counter}.db3";
+ }
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ SqliteConnection.ClearAllPools();
+ // Clean up any remaining test database files
+ var testFiles = Directory.GetFiles(".", "test_factory_wallet_*");
+ foreach (var file in testFiles)
+ {
+ File.Delete(file);
+ }
+ }
+
+ [TestMethod]
+ public void TestFactoryName()
+ {
+ var factory = new SQLiteWalletFactory();
+ Assert.AreEqual("SQLiteWallet", factory.Name);
+ }
+
+ [TestMethod]
+ public void TestFactoryDescription()
+ {
+ var factory = new SQLiteWalletFactory();
+ Assert.AreEqual("A SQLite-based wallet provider that supports wallet files with .db3 suffix.", factory.Description);
+ }
+
+ [TestMethod]
+ public void TestHandleWithDb3Extension()
+ {
+ var factory = new SQLiteWalletFactory();
+
+ // Test with .db3 extension
+ Assert.IsTrue(factory.Handle("wallet.db3"));
+ Assert.IsTrue(factory.Handle("test.db3"));
+ Assert.IsTrue(factory.Handle("path/to/wallet.db3"));
+
+ // Test case insensitive
+ Assert.IsTrue(factory.Handle("wallet.DB3"));
+ Assert.IsTrue(factory.Handle("wallet.Db3"));
+ }
+
+ [TestMethod]
+ public void TestHandleWithNonDb3Extension()
+ {
+ var factory = new SQLiteWalletFactory();
+ Assert.IsFalse(factory.Handle("wallet.json"));
+ Assert.IsFalse(factory.Handle("wallet.dat"));
+ Assert.IsFalse(factory.Handle("wallet"));
+ Assert.IsFalse(factory.Handle(""));
+ }
+
+ [TestMethod]
+ public void TestCreateWallet()
+ {
+ var factory = new SQLiteWalletFactory();
+ var path = GetTestWalletPath();
+ var wallet = factory.CreateWallet("TestWallet", path, TestPassword, TestSettings);
+
+ Assert.IsNotNull(wallet);
+ Assert.IsInstanceOfType(wallet, typeof(SQLiteWallet));
+ Assert.IsTrue(File.Exists(path));
+ }
+
+ [TestMethod]
+ public void TestOpenWallet()
+ {
+ var factory = new SQLiteWalletFactory();
+ var path = GetTestWalletPath();
+ factory.CreateWallet("TestWallet", path, TestPassword, TestSettings);
+
+ var wallet = factory.OpenWallet(path, TestPassword, TestSettings);
+ Assert.IsNotNull(wallet);
+ Assert.IsInstanceOfType(wallet, typeof(SQLiteWallet));
+ }
+
+ [TestMethod]
+ public void TestOpenWalletWithInvalidPassword()
+ {
+ var factory = new SQLiteWalletFactory();
+ var path = GetTestWalletPath();
+ factory.CreateWallet("TestWallet", path, TestPassword, TestSettings);
+ Assert.ThrowsExactly(() => factory.OpenWallet(path, "wrong_password", TestSettings));
+ }
+ }
+}
diff --git a/tests/Neo.Plugins.SQLiteWallet.Tests/UT_VerificationContract.cs b/tests/Neo.Plugins.SQLiteWallet.Tests/UT_VerificationContract.cs
new file mode 100644
index 0000000000..4d2e071307
--- /dev/null
+++ b/tests/Neo.Plugins.SQLiteWallet.Tests/UT_VerificationContract.cs
@@ -0,0 +1,70 @@
+// Copyright (C) 2015-2025 The Neo Project.
+//
+// UT_VerificationContract.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using Neo.Extensions;
+using Neo.SmartContract;
+using Neo.Wallets;
+using System.Security.Cryptography;
+
+namespace Neo.Wallets.SQLite
+{
+ [TestClass]
+ public class UT_VerificationContract
+ {
+ [TestMethod]
+ public void TestContractCreation()
+ {
+ var privateKey = new byte[32];
+ RandomNumberGenerator.Fill(privateKey);
+ var keyPair = new KeyPair(privateKey);
+ var script = SmartContract.Contract.CreateSignatureRedeemScript(keyPair.PublicKey);
+ var parameters = new[] { ContractParameterType.Signature };
+
+ var contract = new VerificationContract
+ {
+ Script = script,
+ ParameterList = parameters
+ };
+
+ Assert.IsNotNull(contract);
+ Assert.AreEqual(script, contract.Script);
+ Assert.AreEqual(parameters, contract.ParameterList);
+ Assert.AreEqual(script.ToScriptHash(), contract.ScriptHash);
+ }
+
+ [TestMethod]
+ public void TestSerializeDeserialize()
+ {
+ var privateKey = new byte[32];
+ RandomNumberGenerator.Fill(privateKey);
+
+ var keyPair = new KeyPair(privateKey);
+ var script = SmartContract.Contract.CreateSignatureRedeemScript(keyPair.PublicKey);
+ var originalContract = new VerificationContract
+ {
+ Script = script,
+ ParameterList = [ContractParameterType.Signature]
+ };
+
+ // Serialize
+ var data = originalContract.ToArray();
+ Assert.IsNotNull(data);
+ Assert.IsTrue(data.Length > 0);
+
+ // Deserialize
+ var deserializedContract = data.AsSerializable();
+ Assert.IsNotNull(deserializedContract);
+ Assert.AreEqual(originalContract.ScriptHash, deserializedContract.ScriptHash);
+ Assert.AreEqual(originalContract.Script.Length, deserializedContract.Script.Length);
+ Assert.AreEqual(originalContract.ParameterList.Length, deserializedContract.ParameterList.Length);
+ }
+ }
+}
diff --git a/tests/Neo.Plugins.SQLiteWallet.Tests/UT_WalletDataContext.cs b/tests/Neo.Plugins.SQLiteWallet.Tests/UT_WalletDataContext.cs
new file mode 100644
index 0000000000..1bf3a47826
--- /dev/null
+++ b/tests/Neo.Plugins.SQLiteWallet.Tests/UT_WalletDataContext.cs
@@ -0,0 +1,197 @@
+// Copyright (C) 2015-2025 The Neo Project.
+//
+// UT_WalletDataContext.cs file belongs to the neo project and is free
+// software distributed under the MIT software license, see the
+// accompanying file LICENSE in the main directory of the
+// repository or http://www.opensource.org/licenses/mit-license.php
+// for more details.
+//
+// Redistribution and use in source and binary forms with or without
+// modifications are permitted.
+
+using Microsoft.Data.Sqlite;
+using Microsoft.EntityFrameworkCore;
+
+namespace Neo.Wallets.SQLite
+{
+ [TestClass]
+ public class UT_WalletDataContext
+ {
+ private static int s_counter = 0;
+
+ private string GetTestDbPath()
+ {
+ return $"test_context_{++s_counter}.db3";
+ }
+
+ [TestCleanup]
+ public void Cleanup()
+ {
+ SqliteConnection.ClearAllPools();
+ var testFiles = Directory.GetFiles(".", "test_context_*");
+ foreach (var file in testFiles)
+ {
+ File.Delete(file);
+ }
+ }
+
+ [TestMethod]
+ public void TestContextCreation()
+ {
+ using var context = new WalletDataContext(GetTestDbPath());
+ Assert.IsNotNull(context);
+ Assert.IsNotNull(context.Accounts);
+ Assert.IsNotNull(context.Addresses);
+ Assert.IsNotNull(context.Contracts);
+ Assert.IsNotNull(context.Keys);
+ }
+
+ [TestMethod]
+ public void TestDatabaseCreation()
+ {
+ var path = GetTestDbPath();
+ using var context = new WalletDataContext(path);
+ context.Database.EnsureCreated();
+
+ Assert.IsTrue(File.Exists(path));
+ }
+
+ [TestMethod]
+ public void TestAccountOperations()
+ {
+ using var context = new WalletDataContext(GetTestDbPath());
+ context.Database.EnsureCreated();
+
+ var account = new Account
+ {
+ PublicKeyHash = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20],
+ Nep2key = "test_nep2_key"
+ };
+
+ context.Accounts.Add(account);
+ context.SaveChanges();
+
+ var retrievedAccount = context.Accounts.FirstOrDefault(a => a.PublicKeyHash.SequenceEqual(account.PublicKeyHash));
+ Assert.IsNotNull(retrievedAccount);
+ Assert.AreEqual(account.Nep2key, retrievedAccount.Nep2key);
+ }
+
+ [TestMethod]
+ public void TestAddressOperations()
+ {
+ using var context = new WalletDataContext(GetTestDbPath());
+ context.Database.EnsureCreated();
+
+ var address = new Address { ScriptHash = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] };
+
+ context.Addresses.Add(address);
+ context.SaveChanges();
+
+ var retrievedAddress = context.Addresses.FirstOrDefault(a => a.ScriptHash.SequenceEqual(address.ScriptHash));
+ Assert.IsNotNull(retrievedAddress);
+ }
+
+ [TestMethod]
+ public void TestContractOperations()
+ {
+ using var context = new WalletDataContext(GetTestDbPath());
+ context.Database.EnsureCreated();
+
+ var hash = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+ var contract = new Contract
+ {
+ RawData = [1, 2, 3, 4, 5],
+ ScriptHash = hash,
+ PublicKeyHash = hash
+ };
+
+ context.Contracts.Add(contract);
+ Assert.ThrowsExactly(() => context.SaveChanges()); // FOREIGN KEY constraint failed
+
+ context.Accounts.Add(new Account { PublicKeyHash = hash, Nep2key = "" });
+ context.Addresses.Add(new Address { ScriptHash = hash });
+ context.SaveChanges();
+
+ var retrievedContract = context.Contracts.FirstOrDefault(c => c.ScriptHash.SequenceEqual(contract.ScriptHash));
+ Assert.IsNotNull(retrievedContract);
+ Assert.AreEqual(contract.RawData.Length, retrievedContract.RawData.Length);
+ }
+
+ [TestMethod]
+ public void TestKeyOperations()
+ {
+ using var context = new WalletDataContext(GetTestDbPath());
+ context.Database.EnsureCreated();
+
+ var key = new Key
+ {
+ Name = "test_key",
+ Value = [1, 2, 3, 4, 5]
+ };
+
+ context.Keys.Add(key);
+ context.SaveChanges();
+
+ var retrievedKey = context.Keys.FirstOrDefault(k => k.Name == key.Name);
+ Assert.IsNotNull(retrievedKey);
+ Assert.AreEqual(key.Name, retrievedKey.Name);
+ Assert.AreEqual(key.Value.Length, retrievedKey.Value.Length);
+ }
+
+ [TestMethod]
+ public void TestDatabaseDeletion()
+ {
+ var path = GetTestDbPath();
+ using var context = new WalletDataContext(path);
+ context.Database.EnsureCreated();
+ Assert.IsTrue(File.Exists(path));
+
+ context.Database.EnsureDeleted();
+ Assert.IsFalse(File.Exists(path));
+ }
+
+ [TestMethod]
+ public void TestMultipleOperations()
+ {
+ var path = GetTestDbPath();
+ using var context = new WalletDataContext(path);
+ context.Database.EnsureCreated();
+
+ var hash = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 };
+ var account = new Account { PublicKeyHash = hash, Nep2key = "test_nep2_key" };
+ var address = new Address { ScriptHash = hash };
+ var key = new Key { Name = "test_key", Value = [1, 2, 3, 4, 5] };
+
+ context.Accounts.Add(account);
+ context.Addresses.Add(address);
+ context.Keys.Add(key);
+ context.SaveChanges();
+
+ // Verify all entities were saved
+ Assert.AreEqual(1, context.Accounts.Count());
+ Assert.AreEqual(1, context.Addresses.Count());
+ Assert.AreEqual(1, context.Keys.Count());
+ }
+
+ [TestMethod]
+ public void TestUpdateOperations()
+ {
+ var path = GetTestDbPath();
+ using var context = new WalletDataContext(path);
+ context.Database.EnsureCreated();
+
+ var key = new Key { Name = "test_key", Value = [1, 2, 3, 4, 5] };
+ context.Keys.Add(key);
+ context.SaveChanges();
+
+ // Update the key
+ key.Value = [6, 7, 8, 9, 10];
+ context.SaveChanges();
+
+ var retrievedKey = context.Keys.FirstOrDefault(k => k.Name == key.Name);
+ Assert.IsNotNull(retrievedKey);
+ Assert.AreEqual(5, retrievedKey.Value.Length);
+ Assert.AreEqual(6, retrievedKey.Value[0]);
+ }
+ }
+}
From b4f2468ea7037617695c05549963b028dc42f4c9 Mon Sep 17 00:00:00 2001
From: Shargon
Date: Mon, 8 Sep 2025 09:18:53 +0200
Subject: [PATCH 2/2] Remove two blank lines
---
tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWallet.cs | 1 -
1 file changed, 1 deletion(-)
diff --git a/tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWallet.cs b/tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWallet.cs
index 394f58a6f4..795fe1ed2d 100644
--- a/tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWallet.cs
+++ b/tests/Neo.Plugins.SQLiteWallet.Tests/UT_SQLiteWallet.cs
@@ -9,7 +9,6 @@
// Redistribution and use in source and binary forms with or without
// modifications are permitted.
-
using Microsoft.Data.Sqlite;
using Neo.Extensions;
using Neo.SmartContract;