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;