Coverage for tests/ledger/test_pin.py: 100%
90 statements
« prev ^ index » next coverage.py v7.5.3, created at 2025-07-10 13:43 +0000
« prev ^ index » next coverage.py v7.5.3, created at 2025-07-10 13:43 +0000
1# The MIT License (MIT)
2#
3# Copyright (c) 2021 RSK Labs Ltd
4#
5# Permission is hereby granted, free of charge, to any person obtaining a copy of
6# this software and associated documentation files (the "Software"), to deal in
7# the Software without restriction, including without limitation the rights to
8# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
9# of the Software, and to permit persons to whom the Software is furnished to do
10# so, subject to the following conditions:
11#
12# The above copyright notice and this permission notice shall be included in all
13# copies or substantial portions of the Software.
14#
15# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21# SOFTWARE.
23import string
24from unittest import TestCase
25from unittest.mock import MagicMock, call, patch
26from parameterized import parameterized
27import ledger.pin as pin
30class TestBasePin(TestCase):
31 def test_generate_pin(self):
32 for i in range(1000):
33 p = pin.BasePin.generate_pin()
34 self.assertEqual(8, len(p))
35 self.assertEqual(bytes, type(p))
36 self.assertTrue(
37 all(map(lambda c: chr(c) in string.ascii_letters + string.digits, p)))
38 self.assertTrue(any(map(lambda c: chr(c) in string.ascii_letters, p)))
40 @parameterized.expand([
41 ("abc", False),
42 ("abcd", False),
43 ("abc1", False),
44 ("abcde", False),
45 ("abc12", False),
46 ("abcdef", False),
47 ("abc12f", False),
48 ("abcdefg", False),
49 ("ab2d68g", False),
50 ("abcdefgh", True),
51 ("8b23ef1s", True),
52 ("abcdefghi", False),
53 ("MNO", False),
54 ("MNOP", False),
55 ("MNO2", False),
56 ("MNOPQ", False),
57 ("MN3P4", False),
58 ("MNOPQR", False),
59 ("M1245R", False),
60 ("MNOPQRS", False),
61 ("M656QR3", False),
62 ("MNOPQRST", True),
63 ("MN22P3S9", True),
64 ("MNOPQRSTU", False),
65 ("1NO4Q6S8U", False),
66 ("1234", False),
67 ("123456", False),
68 ("12345678", False),
69 ("some-th", False),
70 ("a1-@.;", False),
71 ("!@#$%^&*", False),
72 ("(),./;']", False),
73 ])
74 def test_is_valid(self, p, expected_validity):
75 self.assertEqual(pin.BasePin.is_valid(p.encode()), expected_validity)
77 @parameterized.expand([
78 ("abc", True),
79 ("abcd", True),
80 ("abc1", True),
81 ("abcde", True),
82 ("abc12", True),
83 ("abcdef", True),
84 ("abc12f", True),
85 ("abcdefg", True),
86 ("ab2d68g", True),
87 ("abcdefgh", True),
88 ("8b23ef1s", True),
89 ("abcdefghi", True),
90 ("MNO", True),
91 ("MNOP", True),
92 ("MNO2", True),
93 ("MNOPQ", True),
94 ("MN3P4", True),
95 ("MNOPQR", True),
96 ("M1245R", True),
97 ("MNOPQRS", True),
98 ("M656QR3", True),
99 ("MNOPQRST", True),
100 ("MN22P3S9", True),
101 ("MNOPQRSTU", True),
102 ("1NO4Q6S8U", True),
103 ("1234", True),
104 ("123456", True),
105 ("12345678", True),
106 ("some-th", False),
107 ("a1-@.;", False),
108 ("!@#$%^&*", False),
109 ("(),./;']", False),
110 ])
111 def test_is_valid_any_pin(self, p, expected_validity):
112 self.assertEqual(pin.BasePin.is_valid(p.encode(), any_pin=True),
113 expected_validity)
116class TestFileBasedPin(TestCase):
117 @patch("ledger.pin.open")
118 def test_new(self, mock_open):
119 mock_file = MagicMock()
120 mock_open.return_value = mock_file
121 p = pin.FileBasedPin.new("a-path")
122 self.assertTrue(pin.BasePin.is_valid(p))
123 self.assertEqual([call("a-path", "wb")], mock_open.call_args_list)
124 self.assertEqual([call(p)], mock_file.__enter__().write.call_args_list)
125 self.assertTrue(mock_file.__exit__.called)
127 @patch("os.path.isfile")
128 @patch("ledger.pin.open")
129 def test_init_pin_doesnotexist(self, mock_open, mock_isfile):
130 mock_isfile.return_value = False
131 p = pin.FileBasedPin("a-path", b"abcdefgh")
132 self.assertEqual([call("a-path")], mock_isfile.call_args_list)
133 self.assertFalse(mock_open.called)
134 self.assertEqual(b"abcdefgh", p.get_pin())
135 self.assertTrue(p.needs_change())
137 @patch("os.path.isfile")
138 @patch("ledger.pin.open")
139 def test_init_pin_exists(self, mock_open, mock_isfile):
140 mock_isfile.return_value = True
141 mock_file = MagicMock()
142 mock_file.__enter__().read.return_value = b"othpinab\n"
143 mock_open.return_value = mock_file
144 p = pin.FileBasedPin("a-path", b"abcdefgh")
145 self.assertEqual([call("a-path")], mock_isfile.call_args_list)
146 self.assertEqual([call("a-path", "rb")], mock_open.call_args_list)
147 self.assertEqual(b"othpinab", p.get_pin())
148 self.assertFalse(p.needs_change())
150 @patch("os.path.isfile")
151 @patch("ledger.pin.open")
152 def test_pin_change(self, mock_open, mock_isfile):
153 mock_isfile.return_value = False
154 mock_file = MagicMock()
155 mock_open.return_value = mock_file
156 p = pin.FileBasedPin("a-path", b"abcdefgh")
157 self.assertEqual(b"abcdefgh", p.get_pin())
158 self.assertEqual(None, p.get_new_pin())
159 self.assertTrue(p.needs_change())
160 p.start_change()
161 np = p.get_new_pin()
162 self.assertEqual(b"abcdefgh", p.get_pin())
163 self.assertNotEqual(None, np)
164 self.assertTrue(pin.BasePin.is_valid(np))
165 p.commit_change()
166 self.assertEqual(np, p.get_pin())
167 self.assertEqual(None, p.get_new_pin())
168 self.assertFalse(p.needs_change())
169 self.assertEqual([call("a-path", "wb")], mock_open.call_args_list)
170 self.assertEqual([call(np)], mock_file.__enter__().write.call_args_list)
171 self.assertTrue(mock_file.__exit__.called)
173 @patch("os.path.isfile")
174 @patch("ledger.pin.open")
175 def test_pin_change_aborted(self, mock_open, mock_isfile):
176 mock_isfile.return_value = False
177 p = pin.FileBasedPin("a-path", b"abcdefgh")
178 self.assertEqual(b"abcdefgh", p.get_pin())
179 self.assertEqual(None, p.get_new_pin())
180 self.assertTrue(p.needs_change())
181 p.start_change()
182 np = p.get_new_pin()
183 self.assertEqual(b"abcdefgh", p.get_pin())
184 self.assertNotEqual(None, np)
185 self.assertTrue(pin.BasePin.is_valid(np))
186 p.abort_change()
187 self.assertEqual(b"abcdefgh", p.get_pin())
188 self.assertEqual(None, p.get_new_pin())
189 self.assertTrue(p.needs_change())
190 self.assertFalse(mock_open.called)