Skip to content
This repository was archived by the owner on Jan 13, 2023. It is now read-only.

Commit 050c76b

Browse files
committed
add were_addresses_spent_from api
1 parent d9d816b commit 050c76b

File tree

4 files changed

+236
-0
lines changed

4 files changed

+236
-0
lines changed

iota/api.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,6 +417,22 @@ def store_transactions(self, trytes):
417417
"""
418418
return core.StoreTransactionsCommand(self.adapter)(trytes=trytes)
419419

420+
def were_addresses_spent_from(self, addresses):
421+
# type: (Iterable[Address]) -> dict
422+
"""
423+
Check if a list of addresses was ever spent from, in the current
424+
epoch, or in previous epochs.
425+
426+
:param addresses:
427+
List of addresses to check.
428+
429+
References:
430+
- https://iota.readme.io/docs/wereaddressesspentfrom
431+
"""
432+
return core.WereAddressesSpentFromCommand(self.adapter)(
433+
addresses = addresses,
434+
)
435+
420436

421437
class Iota(StrictIota):
422438
"""

iota/commands/core/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@
2525
from .interrupt_attaching_to_tangle import *
2626
from .remove_neighbors import *
2727
from .store_transactions import *
28+
from .were_addresses_spent_from import *
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# coding=utf-8
2+
from __future__ import absolute_import, division, print_function, \
3+
unicode_literals
4+
5+
import filters as f
6+
7+
from iota.commands import FilterCommand, RequestFilter
8+
from iota.filters import AddressNoChecksum
9+
10+
__all__ = [
11+
'WereAddressesSpentFromCommand',
12+
]
13+
14+
15+
class WereAddressesSpentFromCommand(FilterCommand):
16+
"""
17+
Executes `wereAddressesSpentFrom` command.
18+
19+
See :py:meth:`iota.api.StrictIota.were_addresses_spent_from`.
20+
"""
21+
command = 'wereAddressesSpentFrom'
22+
23+
def get_request_filter(self):
24+
return WereAddressesSpentFromRequestFilter()
25+
26+
def get_response_filter(self):
27+
pass
28+
29+
30+
class WereAddressesSpentFromRequestFilter(RequestFilter):
31+
def __init__(self):
32+
super(WereAddressesSpentFromRequestFilter, self).__init__(
33+
{
34+
'addresses': (
35+
f.Required
36+
| f.Array
37+
| f.FilterRepeater(
38+
f.Required
39+
| AddressNoChecksum()
40+
| f.Unicode(encoding='ascii', normalize=False)
41+
)
42+
),
43+
}
44+
)
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
# coding=utf-8
2+
from __future__ import absolute_import, division, print_function, \
3+
unicode_literals
4+
5+
from unittest import TestCase
6+
7+
import filters as f
8+
from filters.test import BaseFilterTestCase
9+
10+
from iota import Address, Iota, TryteString
11+
from iota.adapter import MockAdapter
12+
from iota.commands.core.were_addresses_spent_from import WereAddressesSpentFromCommand
13+
from iota.filters import Trytes
14+
15+
16+
class WereAddressesSpentFromRequestFilterTestCase(BaseFilterTestCase):
17+
filter_type = WereAddressesSpentFromCommand(MockAdapter()).get_request_filter
18+
skip_value_check = True
19+
20+
# noinspection SpellCheckingInspection
21+
def setUp(self):
22+
super(WereAddressesSpentFromRequestFilterTestCase, self).setUp()
23+
24+
# Define a few valid values that we can reuse across tests.
25+
self.trytes1 = (
26+
'TESTVALUE9DONTUSEINPRODUCTION99999EKJZZT'
27+
'SOGJOUNVEWLDPKGTGAOIZIPMGBLHC9LMQNHLGXGYX'
28+
)
29+
30+
self.trytes2 = (
31+
'TESTVALUE9DONTUSEINPRODUCTION99999FDCDTZ'
32+
'ZWLL9MYGUTLSYVSIFJ9NGALTRMCQVIIOVEQOITYTE'
33+
)
34+
35+
def test_pass_happy_path(self):
36+
"""
37+
Typical invocation of ``wereAddressesSpentFrom``.
38+
"""
39+
request = {
40+
# Raw trytes are extracted to match the IRI's JSON protocol.
41+
'addresses': [self.trytes1, self.trytes2],
42+
}
43+
44+
filter_ = self._filter(request)
45+
46+
self.assertFilterPasses(filter_)
47+
self.assertDictEqual(filter_.cleaned_data, request)
48+
49+
def test_pass_compatible_types(self):
50+
"""
51+
The incoming request contains values that can be converted to the
52+
expected types.
53+
"""
54+
request = {
55+
'addresses': [
56+
Address(self.trytes1),
57+
bytearray(self.trytes2.encode('ascii')),
58+
],
59+
}
60+
61+
filter_ = self._filter(request)
62+
63+
self.assertFilterPasses(filter_)
64+
self.assertDictEqual(
65+
filter_.cleaned_data,
66+
67+
{
68+
'addresses': [self.trytes1, self.trytes2],
69+
},
70+
)
71+
72+
def test_fail_empty(self):
73+
"""
74+
The incoming request is empty.
75+
"""
76+
self.assertFilterErrors(
77+
{},
78+
79+
{
80+
'addresses': [f.FilterMapper.CODE_MISSING_KEY],
81+
},
82+
)
83+
84+
def test_fail_unexpected_parameters(self):
85+
"""
86+
The incoming request contains unexpected parameters.
87+
"""
88+
self.assertFilterErrors(
89+
{
90+
'addresses': [Address(self.trytes1)],
91+
92+
# I've had a perfectly wonderful evening.
93+
# But this wasn't it.
94+
'foo': 'bar',
95+
},
96+
97+
{
98+
'foo': [f.FilterMapper.CODE_EXTRA_KEY],
99+
},
100+
)
101+
102+
def test_fail_addresses_wrong_type(self):
103+
"""
104+
``addresses`` is not an array.
105+
"""
106+
self.assertFilterErrors(
107+
{
108+
'addresses': Address(self.trytes1),
109+
},
110+
111+
{
112+
'addresses': [f.Type.CODE_WRONG_TYPE],
113+
},
114+
)
115+
116+
def test_fail_addresses_empty(self):
117+
"""
118+
``addresses`` is an array, but it's empty.
119+
"""
120+
self.assertFilterErrors(
121+
{
122+
'addresses': [],
123+
},
124+
125+
{
126+
'addresses': [f.Required.CODE_EMPTY],
127+
},
128+
)
129+
130+
def test_fail_addresses_contents_invalid(self):
131+
"""
132+
``addresses`` is an array, but it contains invalid values.
133+
"""
134+
self.assertFilterErrors(
135+
{
136+
'addresses': [
137+
b'',
138+
True,
139+
None,
140+
b'not valid trytes',
141+
142+
# This is actually valid; I just added it to make sure the
143+
# filter isn't cheating!
144+
TryteString(self.trytes2),
145+
146+
2130706433,
147+
b'9' * 82,
148+
],
149+
},
150+
151+
{
152+
'addresses.0': [f.Required.CODE_EMPTY],
153+
'addresses.1': [f.Type.CODE_WRONG_TYPE],
154+
'addresses.2': [f.Required.CODE_EMPTY],
155+
'addresses.3': [Trytes.CODE_NOT_TRYTES],
156+
'addresses.5': [f.Type.CODE_WRONG_TYPE],
157+
'addresses.6': [Trytes.CODE_WRONG_FORMAT],
158+
},
159+
)
160+
161+
162+
class WereAddressesSpentFromCommandTestCase(TestCase):
163+
def setUp(self):
164+
super(WereAddressesSpentFromCommandTestCase, self).setUp()
165+
166+
self.adapter = MockAdapter()
167+
168+
def test_wireup(self):
169+
"""
170+
Verify that the command is wired up correctly.
171+
"""
172+
self.assertIsInstance(
173+
Iota(self.adapter).wereAddressesSpentFrom,
174+
WereAddressesSpentFromCommand,
175+
)

0 commit comments

Comments
 (0)