iwa 0.0.0__py3-none-any.whl → 0.0.1a2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- conftest.py +22 -0
- iwa/__init__.py +1 -0
- iwa/__main__.py +6 -0
- iwa/core/__init__.py +1 -0
- iwa/core/chain/__init__.py +68 -0
- iwa/core/chain/errors.py +47 -0
- iwa/core/chain/interface.py +514 -0
- iwa/core/chain/manager.py +38 -0
- iwa/core/chain/models.py +128 -0
- iwa/core/chain/rate_limiter.py +193 -0
- iwa/core/cli.py +210 -0
- iwa/core/constants.py +28 -0
- iwa/core/contracts/__init__.py +1 -0
- iwa/core/contracts/contract.py +297 -0
- iwa/core/contracts/erc20.py +79 -0
- iwa/core/contracts/multisend.py +71 -0
- iwa/core/db.py +317 -0
- iwa/core/keys.py +361 -0
- iwa/core/mnemonic.py +385 -0
- iwa/core/models.py +344 -0
- iwa/core/monitor.py +209 -0
- iwa/core/plugins.py +45 -0
- iwa/core/pricing.py +91 -0
- iwa/core/services/__init__.py +17 -0
- iwa/core/services/account.py +57 -0
- iwa/core/services/balance.py +113 -0
- iwa/core/services/plugin.py +88 -0
- iwa/core/services/safe.py +392 -0
- iwa/core/services/transaction.py +172 -0
- iwa/core/services/transfer/__init__.py +166 -0
- iwa/core/services/transfer/base.py +260 -0
- iwa/core/services/transfer/erc20.py +247 -0
- iwa/core/services/transfer/multisend.py +386 -0
- iwa/core/services/transfer/native.py +262 -0
- iwa/core/services/transfer/swap.py +326 -0
- iwa/core/settings.py +95 -0
- iwa/core/tables.py +60 -0
- iwa/core/test.py +27 -0
- iwa/core/tests/test_wallet.py +255 -0
- iwa/core/types.py +59 -0
- iwa/core/ui.py +99 -0
- iwa/core/utils.py +59 -0
- iwa/core/wallet.py +380 -0
- iwa/plugins/__init__.py +1 -0
- iwa/plugins/gnosis/__init__.py +5 -0
- iwa/plugins/gnosis/cow/__init__.py +6 -0
- iwa/plugins/gnosis/cow/quotes.py +148 -0
- iwa/plugins/gnosis/cow/swap.py +403 -0
- iwa/plugins/gnosis/cow/types.py +20 -0
- iwa/plugins/gnosis/cow_utils.py +44 -0
- iwa/plugins/gnosis/plugin.py +68 -0
- iwa/plugins/gnosis/safe.py +157 -0
- iwa/plugins/gnosis/tests/test_cow.py +227 -0
- iwa/plugins/gnosis/tests/test_safe.py +100 -0
- iwa/plugins/olas/__init__.py +5 -0
- iwa/plugins/olas/constants.py +106 -0
- iwa/plugins/olas/contracts/activity_checker.py +93 -0
- iwa/plugins/olas/contracts/base.py +10 -0
- iwa/plugins/olas/contracts/mech.py +49 -0
- iwa/plugins/olas/contracts/mech_marketplace.py +43 -0
- iwa/plugins/olas/contracts/service.py +215 -0
- iwa/plugins/olas/contracts/staking.py +403 -0
- iwa/plugins/olas/importer.py +736 -0
- iwa/plugins/olas/mech_reference.py +135 -0
- iwa/plugins/olas/models.py +110 -0
- iwa/plugins/olas/plugin.py +243 -0
- iwa/plugins/olas/scripts/test_full_mech_flow.py +259 -0
- iwa/plugins/olas/scripts/test_simple_lifecycle.py +74 -0
- iwa/plugins/olas/service_manager/__init__.py +60 -0
- iwa/plugins/olas/service_manager/base.py +113 -0
- iwa/plugins/olas/service_manager/drain.py +336 -0
- iwa/plugins/olas/service_manager/lifecycle.py +839 -0
- iwa/plugins/olas/service_manager/mech.py +322 -0
- iwa/plugins/olas/service_manager/staking.py +530 -0
- iwa/plugins/olas/tests/conftest.py +30 -0
- iwa/plugins/olas/tests/test_importer.py +128 -0
- iwa/plugins/olas/tests/test_importer_error_handling.py +349 -0
- iwa/plugins/olas/tests/test_mech_contracts.py +85 -0
- iwa/plugins/olas/tests/test_olas_contracts.py +249 -0
- iwa/plugins/olas/tests/test_olas_integration.py +561 -0
- iwa/plugins/olas/tests/test_olas_models.py +144 -0
- iwa/plugins/olas/tests/test_olas_view.py +258 -0
- iwa/plugins/olas/tests/test_olas_view_actions.py +137 -0
- iwa/plugins/olas/tests/test_olas_view_modals.py +120 -0
- iwa/plugins/olas/tests/test_plugin.py +70 -0
- iwa/plugins/olas/tests/test_plugin_full.py +212 -0
- iwa/plugins/olas/tests/test_service_lifecycle.py +150 -0
- iwa/plugins/olas/tests/test_service_manager.py +1065 -0
- iwa/plugins/olas/tests/test_service_manager_errors.py +208 -0
- iwa/plugins/olas/tests/test_service_manager_flows.py +497 -0
- iwa/plugins/olas/tests/test_service_manager_mech.py +135 -0
- iwa/plugins/olas/tests/test_service_manager_rewards.py +360 -0
- iwa/plugins/olas/tests/test_service_manager_validation.py +145 -0
- iwa/plugins/olas/tests/test_service_staking.py +342 -0
- iwa/plugins/olas/tests/test_staking_integration.py +269 -0
- iwa/plugins/olas/tests/test_staking_validation.py +109 -0
- iwa/plugins/olas/tui/__init__.py +1 -0
- iwa/plugins/olas/tui/olas_view.py +952 -0
- iwa/tools/check_profile.py +67 -0
- iwa/tools/release.py +111 -0
- iwa/tools/reset_env.py +111 -0
- iwa/tools/reset_tenderly.py +362 -0
- iwa/tools/restore_backup.py +82 -0
- iwa/tui/__init__.py +1 -0
- iwa/tui/app.py +174 -0
- iwa/tui/modals/__init__.py +5 -0
- iwa/tui/modals/base.py +406 -0
- iwa/tui/rpc.py +63 -0
- iwa/tui/screens/__init__.py +1 -0
- iwa/tui/screens/wallets.py +749 -0
- iwa/tui/tests/test_app.py +125 -0
- iwa/tui/tests/test_rpc.py +139 -0
- iwa/tui/tests/test_wallets_refactor.py +30 -0
- iwa/tui/tests/test_widgets.py +123 -0
- iwa/tui/widgets/__init__.py +5 -0
- iwa/tui/widgets/base.py +100 -0
- iwa/tui/workers.py +42 -0
- iwa/web/dependencies.py +76 -0
- iwa/web/models.py +76 -0
- iwa/web/routers/accounts.py +115 -0
- iwa/web/routers/olas/__init__.py +24 -0
- iwa/web/routers/olas/admin.py +169 -0
- iwa/web/routers/olas/funding.py +135 -0
- iwa/web/routers/olas/general.py +29 -0
- iwa/web/routers/olas/services.py +378 -0
- iwa/web/routers/olas/staking.py +341 -0
- iwa/web/routers/state.py +65 -0
- iwa/web/routers/swap.py +617 -0
- iwa/web/routers/transactions.py +153 -0
- iwa/web/server.py +155 -0
- iwa/web/tests/test_web_endpoints.py +713 -0
- iwa/web/tests/test_web_olas.py +430 -0
- iwa/web/tests/test_web_swap.py +103 -0
- iwa-0.0.1a2.dist-info/METADATA +234 -0
- iwa-0.0.1a2.dist-info/RECORD +186 -0
- iwa-0.0.1a2.dist-info/entry_points.txt +2 -0
- iwa-0.0.1a2.dist-info/licenses/LICENSE +21 -0
- iwa-0.0.1a2.dist-info/top_level.txt +4 -0
- tests/legacy_cow.py +248 -0
- tests/legacy_safe.py +93 -0
- tests/legacy_transaction_retry_logic.py +51 -0
- tests/legacy_tui.py +440 -0
- tests/legacy_wallets_screen.py +554 -0
- tests/legacy_web.py +243 -0
- tests/test_account_service.py +120 -0
- tests/test_balance_service.py +186 -0
- tests/test_chain.py +490 -0
- tests/test_chain_interface.py +210 -0
- tests/test_cli.py +139 -0
- tests/test_contract.py +195 -0
- tests/test_db.py +180 -0
- tests/test_drain_coverage.py +174 -0
- tests/test_erc20.py +95 -0
- tests/test_gnosis_plugin.py +111 -0
- tests/test_keys.py +449 -0
- tests/test_legacy_wallet.py +1285 -0
- tests/test_main.py +13 -0
- tests/test_mnemonic.py +217 -0
- tests/test_modals.py +109 -0
- tests/test_models.py +213 -0
- tests/test_monitor.py +202 -0
- tests/test_multisend.py +84 -0
- tests/test_plugin_service.py +119 -0
- tests/test_pricing.py +143 -0
- tests/test_rate_limiter.py +199 -0
- tests/test_reset_tenderly.py +202 -0
- tests/test_rpc_view.py +73 -0
- tests/test_safe_coverage.py +139 -0
- tests/test_safe_service.py +168 -0
- tests/test_service_manager_integration.py +61 -0
- tests/test_service_manager_structure.py +31 -0
- tests/test_service_transaction.py +176 -0
- tests/test_staking_router.py +71 -0
- tests/test_staking_simple.py +31 -0
- tests/test_tables.py +76 -0
- tests/test_transaction_service.py +161 -0
- tests/test_transfer_multisend.py +179 -0
- tests/test_transfer_native.py +220 -0
- tests/test_transfer_security.py +93 -0
- tests/test_transfer_structure.py +37 -0
- tests/test_transfer_swap_unit.py +155 -0
- tests/test_ui_coverage.py +66 -0
- tests/test_utils.py +53 -0
- tests/test_workers.py +91 -0
- tools/verify_drain.py +183 -0
- __init__.py +0 -2
- hello.py +0 -6
- iwa-0.0.0.dist-info/METADATA +0 -10
- iwa-0.0.0.dist-info/RECORD +0 -6
- iwa-0.0.0.dist-info/top_level.txt +0 -2
- {iwa-0.0.0.dist-info → iwa-0.0.1a2.dist-info}/WHEEL +0 -0
tests/legacy_tui.py
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
from unittest.mock import MagicMock, PropertyMock, patch
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from textual.widgets import Button, Checkbox, DataTable, Input, Select, SelectionList
|
|
5
|
+
|
|
6
|
+
from iwa.tui.app import IwaApp
|
|
7
|
+
from iwa.tui.modals import CreateEOAModal, CreateSafeModal
|
|
8
|
+
from iwa.tui.screens.wallets import WalletsScreen
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@pytest.fixture
|
|
12
|
+
def mock_wallet():
|
|
13
|
+
with patch("iwa.tui.app.Wallet") as mock:
|
|
14
|
+
mock_inst = mock.return_value
|
|
15
|
+
mock_inst.key_storage = MagicMock()
|
|
16
|
+
mock_inst.account_service = MagicMock()
|
|
17
|
+
mock_inst.account_service.accounts = mock_inst.key_storage.accounts
|
|
18
|
+
mock_inst.account_service.get_account_data.side_effect = (
|
|
19
|
+
lambda: mock_inst.account_service.accounts
|
|
20
|
+
)
|
|
21
|
+
mock_inst.balance_service = MagicMock()
|
|
22
|
+
mock_inst.balance_service.get_native_balance_eth.return_value = 0.0
|
|
23
|
+
mock_inst.balance_service.get_erc20_balance_with_retry.return_value = 0.0
|
|
24
|
+
|
|
25
|
+
# Mock PluginService
|
|
26
|
+
mock_inst.plugin_service = MagicMock()
|
|
27
|
+
mock_inst.plugin_service.get_all_plugins.return_value = {}
|
|
28
|
+
yield mock_inst
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@pytest.fixture(autouse=True)
|
|
32
|
+
def mock_deps():
|
|
33
|
+
with (
|
|
34
|
+
patch("iwa.tui.screens.wallets.EventMonitor"),
|
|
35
|
+
patch("iwa.tui.screens.wallets.PriceService") as mock_price,
|
|
36
|
+
patch("iwa.core.db.SentTransaction") as mock_sent_tx,
|
|
37
|
+
patch("iwa.core.db.log_transaction"),
|
|
38
|
+
patch("iwa.tui.screens.wallets.ChainInterfaces") as mock_chains,
|
|
39
|
+
):
|
|
40
|
+
# Setup Price Service
|
|
41
|
+
mock_price.return_value.get_token_price.return_value = 10.0
|
|
42
|
+
|
|
43
|
+
# Setup Chain Interface Mock
|
|
44
|
+
# Setup distinct chain mocks
|
|
45
|
+
gnosis_mock = MagicMock()
|
|
46
|
+
gnosis_mock.tokens = {"TOKEN": "0xToken", "DAI": "0xDAI"}
|
|
47
|
+
gnosis_mock.chain.rpc = "http://gnosis"
|
|
48
|
+
gnosis_mock.chain.native_currency = "xDAI"
|
|
49
|
+
|
|
50
|
+
eth_mock = MagicMock()
|
|
51
|
+
eth_mock.tokens = {"USDC": "0xUSDC", "USDT": "0xUSDT"}
|
|
52
|
+
eth_mock.chain.rpc = "http://eth"
|
|
53
|
+
eth_mock.chain.native_currency = "ETH"
|
|
54
|
+
|
|
55
|
+
def get_chain(name):
|
|
56
|
+
if name == "ethereum":
|
|
57
|
+
return eth_mock
|
|
58
|
+
return gnosis_mock
|
|
59
|
+
|
|
60
|
+
mock_chains.return_value.get.side_effect = get_chain
|
|
61
|
+
mock_chains.return_value.items.return_value = [
|
|
62
|
+
("gnosis", gnosis_mock),
|
|
63
|
+
("ethereum", eth_mock),
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
# Make yield return a dict or object to access specific mocks
|
|
67
|
+
yield {"chains": mock_chains, "pricing": mock_price, "sent_tx": mock_sent_tx}
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@pytest.mark.asyncio
|
|
71
|
+
async def test_app_startup(mock_wallet, mock_deps):
|
|
72
|
+
app = IwaApp()
|
|
73
|
+
async with app.run_test(size=(120, 60)):
|
|
74
|
+
assert app.title == "Iwa"
|
|
75
|
+
assert app.query_one(WalletsScreen)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@pytest.mark.asyncio
|
|
79
|
+
async def test_create_eoa_modal_press(mock_wallet, mock_deps):
|
|
80
|
+
app = IwaApp()
|
|
81
|
+
async with app.run_test(size=(120, 60)) as pilot:
|
|
82
|
+
_ = app.query_one(WalletsScreen)
|
|
83
|
+
await pilot.click("#create_eoa_btn")
|
|
84
|
+
assert isinstance(app.screen, CreateEOAModal)
|
|
85
|
+
|
|
86
|
+
# Type name
|
|
87
|
+
await pilot.click("#tag_input")
|
|
88
|
+
await pilot.press(*list("TestEOA"))
|
|
89
|
+
|
|
90
|
+
# Click Create
|
|
91
|
+
await pilot.click("#create")
|
|
92
|
+
await pilot.pause(0.5)
|
|
93
|
+
|
|
94
|
+
mock_wallet.key_storage.create_account.assert_called_with("TestEOA")
|
|
95
|
+
assert not isinstance(app.screen, CreateEOAModal)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.asyncio
|
|
99
|
+
async def test_create_safe_modal_compose(mock_wallet, mock_deps):
|
|
100
|
+
app = IwaApp()
|
|
101
|
+
async with app.run_test() as pilot:
|
|
102
|
+
# Setup accounts for owner selection
|
|
103
|
+
mock_wallet.key_storage.accounts = {
|
|
104
|
+
"0x1": MagicMock(address="0x1", tag="Owner1"),
|
|
105
|
+
"0x2": MagicMock(address="0x2", tag="Owner2"),
|
|
106
|
+
}
|
|
107
|
+
mock_wallet.account_service.accounts = mock_wallet.key_storage.accounts
|
|
108
|
+
mock_wallet.account_service.get_account_data.return_value = {}
|
|
109
|
+
mock_wallet.account_service.resolve_account.side_effect = (
|
|
110
|
+
lambda tag: mock_wallet.key_storage.get_account(tag)
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
# Unit test compose structure directly
|
|
114
|
+
modal = CreateSafeModal(
|
|
115
|
+
[(acc.tag, acc.address) for acc in mock_wallet.key_storage.accounts.values()]
|
|
116
|
+
)
|
|
117
|
+
await app.push_screen(modal)
|
|
118
|
+
await pilot.pause()
|
|
119
|
+
|
|
120
|
+
# Check if we have the inputs
|
|
121
|
+
assert modal.query_one("#tag_input", Input)
|
|
122
|
+
assert modal.query_one("#threshold_input", Input)
|
|
123
|
+
assert modal.query_one("#owners_list", SelectionList)
|
|
124
|
+
assert modal.query_one("#chains_list", SelectionList)
|
|
125
|
+
assert modal.query_one("#create", Button)
|
|
126
|
+
assert modal.query_one("#cancel", Button)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@pytest.mark.asyncio
|
|
130
|
+
async def test_create_safe_modal_handlers():
|
|
131
|
+
app = IwaApp()
|
|
132
|
+
async with app.run_test() as _:
|
|
133
|
+
# Unit test CreateSafeModal handlers
|
|
134
|
+
modal = CreateSafeModal([])
|
|
135
|
+
modal.dismiss = MagicMock()
|
|
136
|
+
|
|
137
|
+
# Test Cancel
|
|
138
|
+
cancel_event = MagicMock()
|
|
139
|
+
cancel_event.button.id = "cancel"
|
|
140
|
+
modal.on_button_pressed(cancel_event)
|
|
141
|
+
modal.dismiss.assert_called_once()
|
|
142
|
+
|
|
143
|
+
# Test Create (placeholder logic)
|
|
144
|
+
create_event = MagicMock()
|
|
145
|
+
create_event.button.id = "create"
|
|
146
|
+
|
|
147
|
+
# Mock query_one since modal is not mounted
|
|
148
|
+
modal.query_one = MagicMock()
|
|
149
|
+
|
|
150
|
+
# Mock return values for tag and threshold inputs
|
|
151
|
+
tag_input_mock = MagicMock()
|
|
152
|
+
tag_input_mock.value = "TestSafe"
|
|
153
|
+
threshold_input_mock = MagicMock()
|
|
154
|
+
threshold_input_mock.value = "1"
|
|
155
|
+
|
|
156
|
+
# Mock SelectionList
|
|
157
|
+
owners_list_mock = MagicMock()
|
|
158
|
+
owners_list_mock.selected = ["0x1", "0x2"]
|
|
159
|
+
|
|
160
|
+
# Mock SelectionList for chains
|
|
161
|
+
chains_list_mock = MagicMock()
|
|
162
|
+
chains_list_mock.selected = ["gnosis"]
|
|
163
|
+
|
|
164
|
+
def query_side_effect(selector, *args):
|
|
165
|
+
if selector == "#tag_input":
|
|
166
|
+
return tag_input_mock
|
|
167
|
+
if selector == "#threshold_input":
|
|
168
|
+
return threshold_input_mock
|
|
169
|
+
if selector == "#owners_list":
|
|
170
|
+
return owners_list_mock
|
|
171
|
+
if selector == "#chains_list":
|
|
172
|
+
return chains_list_mock
|
|
173
|
+
return MagicMock()
|
|
174
|
+
|
|
175
|
+
modal.query_one.side_effect = query_side_effect
|
|
176
|
+
|
|
177
|
+
modal.on_button_pressed(create_event)
|
|
178
|
+
|
|
179
|
+
# Teardown
|
|
180
|
+
from iwa.core.db import db
|
|
181
|
+
|
|
182
|
+
if not db.is_closed():
|
|
183
|
+
db.close()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@pytest.mark.asyncio
|
|
187
|
+
async def test_wallets_screen_buttons(mock_wallet):
|
|
188
|
+
# Unit test WalletsScreen handler for Create Safe
|
|
189
|
+
view = WalletsScreen(mock_wallet)
|
|
190
|
+
|
|
191
|
+
# Mock 'app' property using PropertyMock since it's read-only
|
|
192
|
+
with patch.object(WalletsScreen, "app", new_callable=PropertyMock) as mock_app_prop:
|
|
193
|
+
mock_app = MagicMock()
|
|
194
|
+
mock_app_prop.return_value = mock_app
|
|
195
|
+
|
|
196
|
+
# Mock wallet key storage for accounts list
|
|
197
|
+
mock_wallet.key_storage.accounts = {}
|
|
198
|
+
|
|
199
|
+
safe_btn_event = MagicMock()
|
|
200
|
+
safe_btn_event.button.id = "create_safe_btn"
|
|
201
|
+
|
|
202
|
+
view.on_button_pressed(safe_btn_event)
|
|
203
|
+
|
|
204
|
+
args = mock_app.push_screen.call_args[0]
|
|
205
|
+
assert isinstance(args[0], CreateSafeModal)
|
|
206
|
+
callback = args[1]
|
|
207
|
+
|
|
208
|
+
# Test callback logic
|
|
209
|
+
with patch.object(view, "create_safe_worker") as mock_worker:
|
|
210
|
+
# Case 1: Success
|
|
211
|
+
callback(
|
|
212
|
+
{"tag": "MySafe", "threshold": 2, "owners": ["0x1", "0x2"], "chains": ["gnosis"]}
|
|
213
|
+
)
|
|
214
|
+
mock_worker.assert_called_with("MySafe", 2, ["0x1", "0x2"], ["gnosis"])
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
@pytest.mark.asyncio
|
|
218
|
+
async def test_send_transaction_ui(mock_wallet, mock_deps):
|
|
219
|
+
app = IwaApp()
|
|
220
|
+
async with app.run_test(size=(200, 200)) as pilot:
|
|
221
|
+
view = app.query_one(WalletsScreen)
|
|
222
|
+
mock_wallet.key_storage.accounts = {
|
|
223
|
+
"addr1": MagicMock(address="0x1", tag="Acc1"),
|
|
224
|
+
"addr2": MagicMock(address="0x2", tag="Acc2"),
|
|
225
|
+
}
|
|
226
|
+
await view.refresh_ui_for_chain()
|
|
227
|
+
await pilot.pause()
|
|
228
|
+
|
|
229
|
+
# Force table height to avoid pushing button off screen
|
|
230
|
+
app.query_one("#accounts_table").styles.height = 10
|
|
231
|
+
await pilot.pause()
|
|
232
|
+
|
|
233
|
+
app.query_one("#from_addr", Select).value = "0x1"
|
|
234
|
+
app.query_one("#to_addr", Select).value = "0x2"
|
|
235
|
+
app.query_one("#amount", Input).value = "1.0"
|
|
236
|
+
mock_wallet.send.return_value = "0xTxHash"
|
|
237
|
+
|
|
238
|
+
# Click by focus/enter to avoid layout/OutOfBounds issues
|
|
239
|
+
btn = app.query_one("#send_btn")
|
|
240
|
+
btn.focus()
|
|
241
|
+
await pilot.press("enter")
|
|
242
|
+
await pilot.pause()
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
@pytest.mark.asyncio
|
|
246
|
+
async def test_view_methods_direct(mock_wallet, mock_deps):
|
|
247
|
+
"""Test methods directly for coverage."""
|
|
248
|
+
app = IwaApp()
|
|
249
|
+
async with app.run_test(size=(160, 80)) as pilot:
|
|
250
|
+
view = app.query_one(WalletsScreen)
|
|
251
|
+
|
|
252
|
+
mock_acc = MagicMock(address="0xABC", tag="Tag1")
|
|
253
|
+
mock_wallet.key_storage.get_account.side_effect = (
|
|
254
|
+
lambda tag: mock_acc if tag == "0xABC" else None
|
|
255
|
+
)
|
|
256
|
+
mock_wallet.account_service.accounts = {"a1": mock_acc}
|
|
257
|
+
assert view.resolve_tag("0xABC") == "Tag1"
|
|
258
|
+
assert view.resolve_tag("0xXYZ") == "0xXYZ...xXYZ"
|
|
259
|
+
|
|
260
|
+
view.refresh_accounts(force=True)
|
|
261
|
+
|
|
262
|
+
txs = [
|
|
263
|
+
{
|
|
264
|
+
"hash": "0xH",
|
|
265
|
+
"from": "0xF",
|
|
266
|
+
"to": "0xT",
|
|
267
|
+
"value": 10**18,
|
|
268
|
+
"token": "NATIVE",
|
|
269
|
+
"timestamp": 1234567890,
|
|
270
|
+
}
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
# Enrich txs needs web3 mock
|
|
274
|
+
chains_mock = mock_deps["chains"]
|
|
275
|
+
mock_interface = chains_mock.return_value.get.return_value
|
|
276
|
+
mock_interface.web3.eth.get_transaction_receipt.return_value = {
|
|
277
|
+
"gasUsed": 21000,
|
|
278
|
+
"effectiveGasPrice": 10**9,
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
# Mock from_wei to return float compatible
|
|
282
|
+
mock_interface.web3.from_wei.return_value = 1.0
|
|
283
|
+
|
|
284
|
+
# Execute
|
|
285
|
+
view.enrich_and_log_txs(txs)
|
|
286
|
+
await pilot.pause()
|
|
287
|
+
|
|
288
|
+
view.on_checkbox_changed(MagicMock(checkbox=MagicMock(id="cb_TOKEN"), value=True))
|
|
289
|
+
if view.active_chain in view.chain_token_states:
|
|
290
|
+
assert "TOKEN" in view.chain_token_states[view.active_chain]
|
|
291
|
+
view.on_checkbox_changed(MagicMock(checkbox=MagicMock(id="cb_TOKEN"), value=False))
|
|
292
|
+
assert "TOKEN" not in view.chain_token_states[view.active_chain]
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@pytest.mark.asyncio
|
|
296
|
+
async def test_load_recent_txs(mock_wallet, mock_deps):
|
|
297
|
+
# Setup Mock SentTransaction from fixture
|
|
298
|
+
mock_sent_tx_cls = mock_deps["sent_tx"]
|
|
299
|
+
|
|
300
|
+
mock_tx = MagicMock()
|
|
301
|
+
mock_tx.timestamp.strftime.return_value = "2025-01-01 12:00:00"
|
|
302
|
+
mock_tx.from_address = "0x1"
|
|
303
|
+
mock_tx.to_address = "0x2"
|
|
304
|
+
mock_tx.value_eur = 10.5
|
|
305
|
+
mock_tx.amount_wei = 10**18
|
|
306
|
+
mock_tx.token_symbol = "ETH"
|
|
307
|
+
mock_tx.tx_hash = "0xHash"
|
|
308
|
+
mock_tx.chain = "ethereum"
|
|
309
|
+
mock_tx.tags = '["tag1"]'
|
|
310
|
+
mock_tx.gas_cost = 1000
|
|
311
|
+
mock_tx.gas_value_eur = 0.1
|
|
312
|
+
mock_tx.from_tag = "FromTag"
|
|
313
|
+
mock_tx.to_tag = "ToTag"
|
|
314
|
+
mock_tx.token = "ETH"
|
|
315
|
+
|
|
316
|
+
# Configure mock chain
|
|
317
|
+
# Allow timestamp > datetime comparison
|
|
318
|
+
mock_ts_field = MagicMock()
|
|
319
|
+
mock_ts_field.__gt__ = MagicMock(return_value=True)
|
|
320
|
+
# Also need desc() for order_by
|
|
321
|
+
mock_ts_field.desc.return_value = "DESC_ORDER"
|
|
322
|
+
mock_sent_tx_cls.timestamp = mock_ts_field
|
|
323
|
+
|
|
324
|
+
mock_sent_tx_cls.select.return_value.where.return_value.order_by.return_value = [mock_tx]
|
|
325
|
+
|
|
326
|
+
app = IwaApp()
|
|
327
|
+
async with app.run_test(size=(160, 80)) as pilot:
|
|
328
|
+
_ = app.query_one(WalletsScreen)
|
|
329
|
+
# Give time for on_mount -> load_recent_txs to run
|
|
330
|
+
await pilot.pause(0.5)
|
|
331
|
+
|
|
332
|
+
# Verify load_recent_txs was called
|
|
333
|
+
table = app.query_one("#tx_table", DataTable)
|
|
334
|
+
assert table.row_count > 0
|
|
335
|
+
|
|
336
|
+
# Check first row presence
|
|
337
|
+
assert "0xHash" in table.rows
|
|
338
|
+
|
|
339
|
+
# Verify content of the row
|
|
340
|
+
row = table.get_row("0xHash")
|
|
341
|
+
# Check date format
|
|
342
|
+
assert "2025-01-01 12:00:00" in str(row[0])
|
|
343
|
+
# Check value in EUR (now at index 6: Time, Chain, From, To, Token, Amount, Value)
|
|
344
|
+
assert "€10.50" in str(row[6])
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
@pytest.mark.asyncio
|
|
348
|
+
async def test_chain_switching(mock_wallet, mock_deps):
|
|
349
|
+
"""Test chain selector functionality."""
|
|
350
|
+
app = IwaApp()
|
|
351
|
+
async with app.run_test(size=(160, 80)) as pilot:
|
|
352
|
+
view = app.query_one(WalletsScreen)
|
|
353
|
+
|
|
354
|
+
# Initial chain is Gnosis (default)
|
|
355
|
+
assert view.active_chain == "gnosis"
|
|
356
|
+
|
|
357
|
+
# Change to Ethereum
|
|
358
|
+
chain_select = app.query_one("#chain_select", Select)
|
|
359
|
+
|
|
360
|
+
# Stop existing monitor workers before switching chain
|
|
361
|
+
if hasattr(view, "monitor_workers"):
|
|
362
|
+
for w in view.monitor_workers:
|
|
363
|
+
w.stop()
|
|
364
|
+
|
|
365
|
+
# But setting .value property on Select DOES trigger Changed event.
|
|
366
|
+
chain_select.value = "ethereum"
|
|
367
|
+
|
|
368
|
+
# Wait for event to process
|
|
369
|
+
await pilot.pause()
|
|
370
|
+
assert len(view.monitor_workers) > 0
|
|
371
|
+
# worker = view.monitor_workers[0]
|
|
372
|
+
# assert worker.monitor.chain_interface.chain.name in ["gnosis", "ethereum"]
|
|
373
|
+
|
|
374
|
+
# Verify active chain updated
|
|
375
|
+
assert view.active_chain == "ethereum"
|
|
376
|
+
|
|
377
|
+
# Verify columns updated
|
|
378
|
+
# Gnosis had TOKEN, DAI. Ethereum has USDC, USDT.
|
|
379
|
+
table = app.query_one("#accounts_table", DataTable)
|
|
380
|
+
col_labels = [c.label.plain.upper() for c in table.columns.values()]
|
|
381
|
+
|
|
382
|
+
# Should contain standard columns + ETH native + USDC + USDT
|
|
383
|
+
assert "TAG" in col_labels
|
|
384
|
+
assert "ADDRESS" in col_labels
|
|
385
|
+
assert "ETH" in col_labels # Native
|
|
386
|
+
assert "USDC" in col_labels
|
|
387
|
+
assert "USDT" in col_labels
|
|
388
|
+
assert "DAI" not in col_labels
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
@pytest.mark.asyncio
|
|
392
|
+
async def test_token_overwrite(mock_wallet, mock_deps):
|
|
393
|
+
"""Test that enabling a second token does not overwrite the first."""
|
|
394
|
+
app = IwaApp()
|
|
395
|
+
async with app.run_test(size=(160, 80)) as pilot:
|
|
396
|
+
view = app.query_one(WalletsScreen)
|
|
397
|
+
|
|
398
|
+
# Setup mock account
|
|
399
|
+
mock_wallet.key_storage.accounts = {"0x1": MagicMock(address="0x1", tag="TestAcc")}
|
|
400
|
+
mock_wallet.account_service.accounts = mock_wallet.key_storage.accounts
|
|
401
|
+
view.refresh_accounts(force=True)
|
|
402
|
+
|
|
403
|
+
# Switch to Ethereum (has USDC, USDT)
|
|
404
|
+
chain_select = app.query_one("#chain_select", Select)
|
|
405
|
+
chain_select.value = "ethereum"
|
|
406
|
+
await pilot.pause()
|
|
407
|
+
|
|
408
|
+
# Configure wallet mock to return balances
|
|
409
|
+
mock_wallet.balance_service.get_erc20_balance_with_retry.return_value = 100.0
|
|
410
|
+
|
|
411
|
+
cb_usdc = app.query_one("#cb_USDC", Checkbox)
|
|
412
|
+
cb_usdc.value = True
|
|
413
|
+
await pilot.pause()
|
|
414
|
+
await pilot.pause() # Wait for workers
|
|
415
|
+
|
|
416
|
+
# Enable USDT (Second token)
|
|
417
|
+
cb_usdt = app.query_one("#cb_USDT", Checkbox)
|
|
418
|
+
cb_usdt.value = True
|
|
419
|
+
# Manually ensure state is updated to avoid race conditions in test
|
|
420
|
+
if "USDT" not in view.chain_token_states.get("ethereum", set()):
|
|
421
|
+
view.chain_token_states.setdefault("ethereum", set()).add("USDT")
|
|
422
|
+
view.refresh_accounts()
|
|
423
|
+
await pilot.pause()
|
|
424
|
+
await pilot.pause() # Wait for workers
|
|
425
|
+
|
|
426
|
+
# Check table content
|
|
427
|
+
table = app.query_one("#accounts_table", DataTable)
|
|
428
|
+
|
|
429
|
+
addr = list(mock_wallet.key_storage.accounts.values())[0].address
|
|
430
|
+
row_idx = table.get_row_index(addr)
|
|
431
|
+
|
|
432
|
+
# Tag(0), Address(1), Type(2), Native(3), USDC(4), USDT(5)
|
|
433
|
+
# Check USDC column (4)
|
|
434
|
+
usdc_cell = table.get_row_at(row_idx)[4]
|
|
435
|
+
# Check USDT column (5)
|
|
436
|
+
usdt_cell = table.get_row_at(row_idx)[5]
|
|
437
|
+
|
|
438
|
+
# Let's check that col 5 is NOT empty string
|
|
439
|
+
assert str(usdt_cell) != "", "USDT column should not be empty"
|
|
440
|
+
assert str(usdc_cell) != "", "USDC column should not be empty"
|