-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #27 from lpascal-ledger/tests/unit
Bugfix + unit testing framework
- Loading branch information
Showing
12 changed files
with
362 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
name: Build and test LedgerWallet | ||
|
||
on: | ||
push: | ||
branches: | ||
- master | ||
- develop | ||
pull_request: | ||
branches: | ||
- develop | ||
- master | ||
|
||
jobs: | ||
build_install_test: | ||
name: Build, install and test LedgerWallet | ||
runs-on: ubuntu-latest | ||
steps: | ||
- name: Clone | ||
uses: actions/checkout@v2 | ||
- name: Install (with dependencies) | ||
run: pip install . | ||
- name: Install test dependencies | ||
run: pip install nose coverage | ||
- name: Running unit tests | ||
run: nosetests --with-coverage --cover-package ledgerwallet --cover-xml tests/unit/ | ||
- name: Upload coverage to Codecov | ||
uses: codecov/codecov-action@v1 | ||
with: | ||
name: codecov-ledgerwallet |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from unittest import TestCase | ||
|
||
from ledgerwallet.crypto import ecc | ||
|
||
|
||
RAW_PRIVATE = bytes.fromhex("c2cdf0a8b0a83b35ace53f097b5e6e6a0a1f2d40535eff1cf434f52a43d59d8f") | ||
RAW_PUBLIC = bytes.fromhex("6fcc37ea5e9e09fec6c83e5fbd7a745e3eee81d16ebd861c9e66f55518c19798" + | ||
"4e9f113c07f875691df8afc1029496fc4cb9509b39dcd38f251a83359cc8b4f7") | ||
|
||
|
||
class PrivateKeyTest(TestCase): | ||
def setUp(self): | ||
self.key = ecc.PrivateKey(RAW_PRIVATE) | ||
|
||
def test_pubkey(self): | ||
self.assertEqual(self.key.pubkey.serialize(compressed=False), | ||
bytes([0x04]) + RAW_PUBLIC) | ||
|
||
def test_serialize(self): | ||
self.assertEqual(self.key.serialize(), RAW_PRIVATE) | ||
|
||
def test_sign(self): | ||
blob = b'someblobofdata' | ||
signature = self.key.sign(blob) | ||
self.assertTrue(self.key.pubkey.verify(blob, signature)) | ||
|
||
|
||
class PublickKeyTest(TestCase): | ||
def setUp(self): | ||
self.public = bytes([0x04]) + RAW_PUBLIC | ||
self.key = ecc.PublicKey(self.public) | ||
|
||
def test___init__fail(self): | ||
with self.assertRaises(ValueError): | ||
ecc.PublicKey(b"") # not 65-bytes long | ||
with self.assertRaises(ValueError): | ||
self.public = bytes([0x00]) + self.public[1:] | ||
ecc.PublicKey(self.public) # must start with 0x04 | ||
|
||
def test_serialize_uncompressed(self): | ||
self.assertEqual(self.key.serialize(compressed=False), self.public) | ||
|
||
def test_serialize_compressed(self): | ||
with self.assertRaises(NotImplementedError): | ||
self.key.serialize() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
{ | ||
"targetId": "1234", | ||
"binary": "some binary", | ||
"name": "some name", | ||
"flags": "9999", | ||
"dataSize": 42, | ||
"version": "2.9.4-debug", | ||
"icon": "pied.jpeg", | ||
"derivationPath": { | ||
"curves": ["secp256k1", "ed25519"], | ||
"paths": ["44'/0'/255", "44'/0'/0'/1/400"] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"targetId": "1234", | ||
"binary": "some binary" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import json | ||
from pathlib import Path | ||
from unittest import TestCase | ||
from unittest.mock import patch | ||
|
||
from ledgerwallet.manifest import AppManifest | ||
|
||
|
||
class ManifestTest(TestCase): | ||
def setUp(self): | ||
self.data_dir = Path(__file__).parent / "data" | ||
self.json_path = self.data_dir / "manifest.json" | ||
self.assertTrue(self.json_path.is_file()) | ||
self.manifest = AppManifest(str(self.json_path)) | ||
|
||
def test___init__(self): | ||
self.assertEqual(self.manifest.path, str(self.json_path.parent)) | ||
with self.json_path.open() as filee: | ||
self.assertEqual(self.manifest.json, json.load(filee)) | ||
|
||
def test___init__error(self): | ||
with self.assertRaises(AssertionError): | ||
AppManifest(str(self.data_dir / "empty_manifest.json")) # missing key | ||
with self.assertRaises(FileNotFoundError): | ||
AppManifest("/this/path/should/not/exist") # file does not exist (right?) | ||
|
||
def test_app_name(self): | ||
self.assertEqual(self.manifest.app_name, "some name") | ||
|
||
def test_data_size(self): | ||
self.assertEqual(self.manifest.data_size, 42) | ||
|
||
def test_get_application_flags(self): | ||
self.assertEqual(self.manifest.get_application_flags(), 0x9999) | ||
|
||
def test_get_binary(self): | ||
self.assertEqual(self.manifest.get_binary(), str(self.data_dir / "some binary")) | ||
|
||
def test_get_target_id(self): | ||
self.assertEqual(self.manifest.get_target_id(), 0x1234) | ||
|
||
def test_properties_with_minimal_manifest(self): | ||
manifest = AppManifest(str(self.data_dir / "minimal_manifest.json")) | ||
with self.assertRaises(KeyError): | ||
manifest.app_name | ||
self.assertEqual(manifest.data_size, 0) | ||
self.assertEqual(manifest.get_application_flags(), 0) | ||
self.assertEqual(manifest.get_binary(), str(self.data_dir / "some binary")) | ||
self.assertEqual(manifest.get_target_id(), 0x1234) | ||
self.assertEqual(manifest.serialize_parameters(), b"") | ||
|
||
def test_serialize_parameters(self): | ||
expected = bytes.fromhex( | ||
"01" + # BolosTag 'AppName' | ||
"09" + "736f6d65206e616d65" + # "some name" | ||
"02" + # BolosTag 'Version' | ||
"0b" + "322e392e342d6465627567" + # 2.9.4-debug | ||
"03" + # BolosTag 'Icon' | ||
"04" + "01020304" + # mocked icon | ||
"04" + # BolosTag 'DerivationPath' | ||
"23" + # 35: following size | ||
"05" + # secp256k1 (1) + ed25519 (4) | ||
"03" + "8000002c80000000000000ff" + # "44'/0'/255" | ||
"05" + "8000002c80000000800000000000000100000190" # "44'/0'/0'/1/400" | ||
) | ||
with patch('ledgerwallet.manifest.icon_from_file', lambda x: b'\x01\x02\x03\x04'): | ||
result = self.manifest.serialize_parameters() | ||
self.assertEqual(result, expected) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
from unittest import TestCase | ||
|
||
from construct import StreamError | ||
|
||
from ledgerwallet.params import Asn1Length, Bip32Path, DerivationPath, \ | ||
Dependency, Dependencies | ||
|
||
|
||
class Asn1LengthTest(TestCase): | ||
sample = { | ||
0: bytes.fromhex("00"), | ||
4: bytes.fromhex("04"), | ||
127: bytes.fromhex("7f"), | ||
128: bytes.fromhex("8180"), | ||
160: bytes.fromhex("81a0"), | ||
255: bytes.fromhex("81ff"), | ||
256: bytes.fromhex("820100") | ||
} | ||
|
||
def test_parse(self): | ||
for k, v in self.sample.items(): | ||
self.assertEquals(Asn1Length.parse(v), k) | ||
|
||
def test_build(self): | ||
for k, v in self.sample.items(): | ||
self.assertEquals(Asn1Length.build(k), v) | ||
|
||
|
||
class Bip32PathTest(TestCase): | ||
sample = { | ||
"1": bytes.fromhex("01 00000001"), | ||
"1'": bytes.fromhex("01 80000001"), | ||
"0'/0": bytes.fromhex("02 80000000 00000000"), | ||
"44'/91223'/2": bytes.fromhex('03 8000002c 80016457 00000002'), | ||
"44'/0'/0'/1/400": bytes.fromhex("05 8000002c 80000000 80000000 00000001 00000190"), | ||
} | ||
|
||
def test_parse(self): | ||
for k, v in self.sample.items(): | ||
self.assertEquals(Bip32Path.parse(v), k) | ||
|
||
def test_parse_empty(self): | ||
# not in sample as not the parse/build behavior is not symetrical | ||
self.assertEqual(Bip32Path.parse(bytes.fromhex("00")), str()) | ||
|
||
def test_build(self): | ||
for k, v in self.sample.items(): | ||
self.assertEquals(Bip32Path.build(k), v) | ||
|
||
def test_parse_error(self): | ||
errors = [ | ||
b"", # empty string is not parsable | ||
bytes.fromhex("01"), # expecting 4 more bytes (Int32ub) | ||
] | ||
for error in errors: | ||
with self.assertRaises(StreamError): | ||
Bip32Path.parse(error) | ||
|
||
|
||
class DerivationPathTest(TestCase): | ||
|
||
def test_parse_empty(self): | ||
result = DerivationPath.parse(bytes.fromhex("01 00")) | ||
self.assertFalse(result.curve.secp256k1) | ||
self.assertFalse(result.curve.prime256r1) | ||
self.assertFalse(result.curve.ed25519) | ||
self.assertFalse(result.curve.bls12381g1) | ||
self.assertFalse(result.paths) | ||
|
||
def test_parse_keys(self): | ||
# [secp256k1, prime256r1, ed25519, bls12381g1] | ||
keys = [ | ||
[False, False, False, False], | ||
[True, False, False, False], | ||
[False, True, False, False], | ||
[True, True, False, False], | ||
[False, False, True, False], | ||
[True, False, True, False], | ||
[False, True, True, False], | ||
[True, True, True, False], | ||
[False, False, False, True], | ||
[True, False, False, True], | ||
[False, True, False, True], | ||
[True, True, False, True], | ||
[False, False, True, True], | ||
[True, False, True, True], | ||
[False, True, True, True], | ||
[True, True, True, True], | ||
] | ||
for i in range(8): | ||
result = DerivationPath.parse(bytes([1, i])) | ||
self.assertListEqual( | ||
[result.curve.secp256k1, result.curve.prime256r1, | ||
result.curve.ed25519, result.curve.bls12381g1], | ||
keys[i] | ||
) | ||
# bls12381g1 store on 5th bit (16), not 4th (8) | ||
# so there is a bit gap from 8 to 16 | ||
for i in range(16, 24): | ||
result = DerivationPath.parse(bytes([1, i])) | ||
self.assertListEqual( | ||
[result.curve.secp256k1, result.curve.prime256r1, | ||
result.curve.ed25519, result.curve.bls12381g1], | ||
keys[i-8] | ||
) | ||
|
||
def test_parse_with_paths(self): | ||
path1 = (bytes.fromhex("05 8000002c 80000000 80000000 00000001 00000190"), | ||
"44'/0'/0'/1/400") | ||
path2 = (bytes.fromhex("03 8000002c 80000000 000000ff"), | ||
"44'/0'/255") | ||
key = bytes.fromhex("11") | ||
size = len(path1[0]) + len(path2[0]) + len(key) | ||
result = DerivationPath.parse(bytes([size]) + key + path1[0] + path2[0]) | ||
self.assertTrue(result.curve.secp256k1) | ||
self.assertFalse(result.curve.prime256r1) | ||
self.assertFalse(result.curve.ed25519) | ||
self.assertTrue(result.curve.bls12381g1) | ||
self.assertEqual(result.paths, [path1[1], path2[1]]) | ||
|
||
def test_parse_error(self): | ||
errors = [ | ||
b"", # empty string is not parsable | ||
bytes.fromhex("01"), # expecting more bytes (curve) | ||
] | ||
for error in errors: | ||
with self.assertRaises(StreamError): | ||
DerivationPath.parse(error) | ||
|
||
|
||
class DependencyTest(TestCase): | ||
|
||
def test_parse_no_version(self): | ||
name = 'name' | ||
asn1_name = bytes([len(name)]) + name.encode() | ||
result = Dependency.parse(bytes([len(asn1_name)]) + asn1_name) | ||
self.assertEqual(result.name, name) | ||
self.assertIsNone(result.version) | ||
|
||
def test_parse_with_version(self): | ||
name = "name" | ||
version = "1.0.1" | ||
asn1_name = bytes([len(name)]) + name.encode() | ||
asn1_version = bytes([len(version)]) + version.encode() | ||
result = Dependency.parse(bytes([len(asn1_name + asn1_version)]) | ||
+ asn1_name | ||
+ asn1_version) | ||
self.assertEqual(result.name, name) | ||
self.assertEqual(result.version, version) | ||
|
||
|
||
class DependenciesTest(TestCase): | ||
|
||
def test_parse(self): | ||
name1 = "name1" | ||
version = "1.0.1" | ||
asn1_name1 = bytes([len(name1)]) + name1.encode() | ||
asn1_version = bytes([len(version)]) + version.encode() | ||
dep1 = bytes([len(asn1_name1 + asn1_version)]) + asn1_name1 + asn1_version | ||
name2 = "name2" | ||
asn1_name2 = bytes([len(name2)]) + name2.encode() | ||
dep2 = bytes([len(asn1_name2)]) + asn1_name2 | ||
result = Dependencies.parse(bytes([len(dep1 + dep2)]) + dep1 + dep2) | ||
self.assertEqual(result[0].name, name1) | ||
self.assertEqual(result[0].version, version) | ||
self.assertEqual(result[1].name, name2) | ||
self.assertIsNone(result[1].version) |
Oops, something went wrong.