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.
Files changed (191) hide show
  1. conftest.py +22 -0
  2. iwa/__init__.py +1 -0
  3. iwa/__main__.py +6 -0
  4. iwa/core/__init__.py +1 -0
  5. iwa/core/chain/__init__.py +68 -0
  6. iwa/core/chain/errors.py +47 -0
  7. iwa/core/chain/interface.py +514 -0
  8. iwa/core/chain/manager.py +38 -0
  9. iwa/core/chain/models.py +128 -0
  10. iwa/core/chain/rate_limiter.py +193 -0
  11. iwa/core/cli.py +210 -0
  12. iwa/core/constants.py +28 -0
  13. iwa/core/contracts/__init__.py +1 -0
  14. iwa/core/contracts/contract.py +297 -0
  15. iwa/core/contracts/erc20.py +79 -0
  16. iwa/core/contracts/multisend.py +71 -0
  17. iwa/core/db.py +317 -0
  18. iwa/core/keys.py +361 -0
  19. iwa/core/mnemonic.py +385 -0
  20. iwa/core/models.py +344 -0
  21. iwa/core/monitor.py +209 -0
  22. iwa/core/plugins.py +45 -0
  23. iwa/core/pricing.py +91 -0
  24. iwa/core/services/__init__.py +17 -0
  25. iwa/core/services/account.py +57 -0
  26. iwa/core/services/balance.py +113 -0
  27. iwa/core/services/plugin.py +88 -0
  28. iwa/core/services/safe.py +392 -0
  29. iwa/core/services/transaction.py +172 -0
  30. iwa/core/services/transfer/__init__.py +166 -0
  31. iwa/core/services/transfer/base.py +260 -0
  32. iwa/core/services/transfer/erc20.py +247 -0
  33. iwa/core/services/transfer/multisend.py +386 -0
  34. iwa/core/services/transfer/native.py +262 -0
  35. iwa/core/services/transfer/swap.py +326 -0
  36. iwa/core/settings.py +95 -0
  37. iwa/core/tables.py +60 -0
  38. iwa/core/test.py +27 -0
  39. iwa/core/tests/test_wallet.py +255 -0
  40. iwa/core/types.py +59 -0
  41. iwa/core/ui.py +99 -0
  42. iwa/core/utils.py +59 -0
  43. iwa/core/wallet.py +380 -0
  44. iwa/plugins/__init__.py +1 -0
  45. iwa/plugins/gnosis/__init__.py +5 -0
  46. iwa/plugins/gnosis/cow/__init__.py +6 -0
  47. iwa/plugins/gnosis/cow/quotes.py +148 -0
  48. iwa/plugins/gnosis/cow/swap.py +403 -0
  49. iwa/plugins/gnosis/cow/types.py +20 -0
  50. iwa/plugins/gnosis/cow_utils.py +44 -0
  51. iwa/plugins/gnosis/plugin.py +68 -0
  52. iwa/plugins/gnosis/safe.py +157 -0
  53. iwa/plugins/gnosis/tests/test_cow.py +227 -0
  54. iwa/plugins/gnosis/tests/test_safe.py +100 -0
  55. iwa/plugins/olas/__init__.py +5 -0
  56. iwa/plugins/olas/constants.py +106 -0
  57. iwa/plugins/olas/contracts/activity_checker.py +93 -0
  58. iwa/plugins/olas/contracts/base.py +10 -0
  59. iwa/plugins/olas/contracts/mech.py +49 -0
  60. iwa/plugins/olas/contracts/mech_marketplace.py +43 -0
  61. iwa/plugins/olas/contracts/service.py +215 -0
  62. iwa/plugins/olas/contracts/staking.py +403 -0
  63. iwa/plugins/olas/importer.py +736 -0
  64. iwa/plugins/olas/mech_reference.py +135 -0
  65. iwa/plugins/olas/models.py +110 -0
  66. iwa/plugins/olas/plugin.py +243 -0
  67. iwa/plugins/olas/scripts/test_full_mech_flow.py +259 -0
  68. iwa/plugins/olas/scripts/test_simple_lifecycle.py +74 -0
  69. iwa/plugins/olas/service_manager/__init__.py +60 -0
  70. iwa/plugins/olas/service_manager/base.py +113 -0
  71. iwa/plugins/olas/service_manager/drain.py +336 -0
  72. iwa/plugins/olas/service_manager/lifecycle.py +839 -0
  73. iwa/plugins/olas/service_manager/mech.py +322 -0
  74. iwa/plugins/olas/service_manager/staking.py +530 -0
  75. iwa/plugins/olas/tests/conftest.py +30 -0
  76. iwa/plugins/olas/tests/test_importer.py +128 -0
  77. iwa/plugins/olas/tests/test_importer_error_handling.py +349 -0
  78. iwa/plugins/olas/tests/test_mech_contracts.py +85 -0
  79. iwa/plugins/olas/tests/test_olas_contracts.py +249 -0
  80. iwa/plugins/olas/tests/test_olas_integration.py +561 -0
  81. iwa/plugins/olas/tests/test_olas_models.py +144 -0
  82. iwa/plugins/olas/tests/test_olas_view.py +258 -0
  83. iwa/plugins/olas/tests/test_olas_view_actions.py +137 -0
  84. iwa/plugins/olas/tests/test_olas_view_modals.py +120 -0
  85. iwa/plugins/olas/tests/test_plugin.py +70 -0
  86. iwa/plugins/olas/tests/test_plugin_full.py +212 -0
  87. iwa/plugins/olas/tests/test_service_lifecycle.py +150 -0
  88. iwa/plugins/olas/tests/test_service_manager.py +1065 -0
  89. iwa/plugins/olas/tests/test_service_manager_errors.py +208 -0
  90. iwa/plugins/olas/tests/test_service_manager_flows.py +497 -0
  91. iwa/plugins/olas/tests/test_service_manager_mech.py +135 -0
  92. iwa/plugins/olas/tests/test_service_manager_rewards.py +360 -0
  93. iwa/plugins/olas/tests/test_service_manager_validation.py +145 -0
  94. iwa/plugins/olas/tests/test_service_staking.py +342 -0
  95. iwa/plugins/olas/tests/test_staking_integration.py +269 -0
  96. iwa/plugins/olas/tests/test_staking_validation.py +109 -0
  97. iwa/plugins/olas/tui/__init__.py +1 -0
  98. iwa/plugins/olas/tui/olas_view.py +952 -0
  99. iwa/tools/check_profile.py +67 -0
  100. iwa/tools/release.py +111 -0
  101. iwa/tools/reset_env.py +111 -0
  102. iwa/tools/reset_tenderly.py +362 -0
  103. iwa/tools/restore_backup.py +82 -0
  104. iwa/tui/__init__.py +1 -0
  105. iwa/tui/app.py +174 -0
  106. iwa/tui/modals/__init__.py +5 -0
  107. iwa/tui/modals/base.py +406 -0
  108. iwa/tui/rpc.py +63 -0
  109. iwa/tui/screens/__init__.py +1 -0
  110. iwa/tui/screens/wallets.py +749 -0
  111. iwa/tui/tests/test_app.py +125 -0
  112. iwa/tui/tests/test_rpc.py +139 -0
  113. iwa/tui/tests/test_wallets_refactor.py +30 -0
  114. iwa/tui/tests/test_widgets.py +123 -0
  115. iwa/tui/widgets/__init__.py +5 -0
  116. iwa/tui/widgets/base.py +100 -0
  117. iwa/tui/workers.py +42 -0
  118. iwa/web/dependencies.py +76 -0
  119. iwa/web/models.py +76 -0
  120. iwa/web/routers/accounts.py +115 -0
  121. iwa/web/routers/olas/__init__.py +24 -0
  122. iwa/web/routers/olas/admin.py +169 -0
  123. iwa/web/routers/olas/funding.py +135 -0
  124. iwa/web/routers/olas/general.py +29 -0
  125. iwa/web/routers/olas/services.py +378 -0
  126. iwa/web/routers/olas/staking.py +341 -0
  127. iwa/web/routers/state.py +65 -0
  128. iwa/web/routers/swap.py +617 -0
  129. iwa/web/routers/transactions.py +153 -0
  130. iwa/web/server.py +155 -0
  131. iwa/web/tests/test_web_endpoints.py +713 -0
  132. iwa/web/tests/test_web_olas.py +430 -0
  133. iwa/web/tests/test_web_swap.py +103 -0
  134. iwa-0.0.1a2.dist-info/METADATA +234 -0
  135. iwa-0.0.1a2.dist-info/RECORD +186 -0
  136. iwa-0.0.1a2.dist-info/entry_points.txt +2 -0
  137. iwa-0.0.1a2.dist-info/licenses/LICENSE +21 -0
  138. iwa-0.0.1a2.dist-info/top_level.txt +4 -0
  139. tests/legacy_cow.py +248 -0
  140. tests/legacy_safe.py +93 -0
  141. tests/legacy_transaction_retry_logic.py +51 -0
  142. tests/legacy_tui.py +440 -0
  143. tests/legacy_wallets_screen.py +554 -0
  144. tests/legacy_web.py +243 -0
  145. tests/test_account_service.py +120 -0
  146. tests/test_balance_service.py +186 -0
  147. tests/test_chain.py +490 -0
  148. tests/test_chain_interface.py +210 -0
  149. tests/test_cli.py +139 -0
  150. tests/test_contract.py +195 -0
  151. tests/test_db.py +180 -0
  152. tests/test_drain_coverage.py +174 -0
  153. tests/test_erc20.py +95 -0
  154. tests/test_gnosis_plugin.py +111 -0
  155. tests/test_keys.py +449 -0
  156. tests/test_legacy_wallet.py +1285 -0
  157. tests/test_main.py +13 -0
  158. tests/test_mnemonic.py +217 -0
  159. tests/test_modals.py +109 -0
  160. tests/test_models.py +213 -0
  161. tests/test_monitor.py +202 -0
  162. tests/test_multisend.py +84 -0
  163. tests/test_plugin_service.py +119 -0
  164. tests/test_pricing.py +143 -0
  165. tests/test_rate_limiter.py +199 -0
  166. tests/test_reset_tenderly.py +202 -0
  167. tests/test_rpc_view.py +73 -0
  168. tests/test_safe_coverage.py +139 -0
  169. tests/test_safe_service.py +168 -0
  170. tests/test_service_manager_integration.py +61 -0
  171. tests/test_service_manager_structure.py +31 -0
  172. tests/test_service_transaction.py +176 -0
  173. tests/test_staking_router.py +71 -0
  174. tests/test_staking_simple.py +31 -0
  175. tests/test_tables.py +76 -0
  176. tests/test_transaction_service.py +161 -0
  177. tests/test_transfer_multisend.py +179 -0
  178. tests/test_transfer_native.py +220 -0
  179. tests/test_transfer_security.py +93 -0
  180. tests/test_transfer_structure.py +37 -0
  181. tests/test_transfer_swap_unit.py +155 -0
  182. tests/test_ui_coverage.py +66 -0
  183. tests/test_utils.py +53 -0
  184. tests/test_workers.py +91 -0
  185. tools/verify_drain.py +183 -0
  186. __init__.py +0 -2
  187. hello.py +0 -6
  188. iwa-0.0.0.dist-info/METADATA +0 -10
  189. iwa-0.0.0.dist-info/RECORD +0 -6
  190. iwa-0.0.0.dist-info/top_level.txt +0 -2
  191. {iwa-0.0.0.dist-info → iwa-0.0.1a2.dist-info}/WHEEL +0 -0
@@ -0,0 +1,430 @@
1
+ """Tests for Olas Web API endpoints."""
2
+
3
+ from unittest.mock import MagicMock, patch
4
+
5
+ import pytest
6
+ from fastapi.testclient import TestClient
7
+
8
+ # We need to mock Wallet and ChainInterfaces BEFORE importing app from server
9
+ # Also mock _get_webui_password to bypass authentication in tests
10
+ with (
11
+ patch("iwa.core.wallet.Wallet"),
12
+ patch("iwa.core.chain.ChainInterfaces"),
13
+ patch("iwa.core.wallet.init_db"),
14
+ patch("iwa.web.dependencies._get_webui_password", return_value=None),
15
+ ):
16
+ from iwa.web.dependencies import verify_auth
17
+ from iwa.web.server import app
18
+
19
+ from iwa.plugins.olas.models import OlasConfig, Service, StakingStatus
20
+
21
+
22
+ # Override auth for all tests
23
+ async def override_verify_auth():
24
+ """Override auth for testing."""
25
+ return True
26
+
27
+
28
+ app.dependency_overrides[verify_auth] = override_verify_auth
29
+
30
+
31
+ @pytest.fixture
32
+ def client():
33
+ """TestClient for FastAPI app."""
34
+ return TestClient(app)
35
+
36
+
37
+ @pytest.fixture
38
+ def mock_olas_config():
39
+ """Mock Olas configuration."""
40
+ service = Service(
41
+ service_id=1,
42
+ service_name="Test Service",
43
+ chain_name="gnosis",
44
+ agent_address="0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB",
45
+ multisig_address="0x40A2aCCbd92BCA938b02010E17A5b8929b49130D",
46
+ service_owner_address="0x1111111111111111111111111111111111111111",
47
+ staking_contract_address="0x78731D3Ca6b7E34aC0F824c42a7cC18A495cabaB",
48
+ )
49
+ return OlasConfig(services={"gnosis:1": service})
50
+
51
+
52
+ def test_get_olas_price(client):
53
+ """Test /api/olas/price endpoint."""
54
+ with patch("iwa.core.pricing.PriceService") as mock_price_cls:
55
+ mock_price_cls.return_value.get_token_price.return_value = 5.0
56
+ response = client.get("/api/olas/price")
57
+ assert response.status_code == 200
58
+ assert response.json() == {"price_eur": 5.0, "symbol": "OLAS"}
59
+
60
+
61
+ def test_get_olas_services_basic(client, mock_olas_config):
62
+ """Test /api/olas/services/basic endpoint."""
63
+ with patch("iwa.web.routers.olas.services.Config") as mock_config_cls:
64
+ mock_config = mock_config_cls.return_value
65
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
66
+
67
+ # Mock wallet.key_storage.find_stored_account
68
+ from iwa.web.dependencies import wallet
69
+
70
+ wallet.key_storage.find_stored_account.return_value = MagicMock(tag="test_tag")
71
+
72
+ response = client.get("/api/olas/services/basic?chain=gnosis")
73
+ assert response.status_code == 200
74
+ data = response.json()
75
+ assert len(data) == 1
76
+ assert data[0]["name"] == "Test Service"
77
+ assert data[0]["accounts"]["agent"]["tag"] == "test_tag"
78
+
79
+
80
+ def test_get_olas_service_details(client, mock_olas_config):
81
+ """Test /api/olas/services/{service_key}/details endpoint."""
82
+ with (
83
+ patch("iwa.web.routers.olas.services.Config") as mock_config_cls,
84
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
85
+ ):
86
+ mock_config = mock_config_cls.return_value
87
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
88
+
89
+ mock_sm = mock_sm_cls.return_value
90
+ mock_sm.get_staking_status.return_value = StakingStatus(
91
+ is_staked=True,
92
+ staking_state="STAKED",
93
+ accrued_reward_olas=10.5,
94
+ remaining_epoch_seconds=3600,
95
+ )
96
+
97
+ from iwa.web.dependencies import wallet
98
+
99
+ wallet.get_native_balance_eth.return_value = 1.0
100
+ wallet.balance_service.get_erc20_balance_wei.return_value = 10**18
101
+ wallet.key_storage.find_stored_account.return_value = MagicMock(tag="test_tag")
102
+
103
+ response = client.get("/api/olas/services/gnosis:1/details")
104
+ assert response.status_code == 200
105
+ data = response.json()
106
+ assert data["staking"]["is_staked"] is True
107
+ assert data["staking"]["accrued_reward_olas"] == 10.5
108
+ assert data["accounts"]["agent"]["native"] == "1.00"
109
+
110
+
111
+ def test_get_olas_services_full(client, mock_olas_config):
112
+ """Test /api/olas/services (full) endpoint."""
113
+ with (
114
+ patch("iwa.web.routers.olas.services.Config") as mock_config_cls,
115
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
116
+ ):
117
+ mock_config = mock_config_cls.return_value
118
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
119
+
120
+ mock_sm = mock_sm_cls.return_value
121
+ mock_sm.get_staking_status.return_value = StakingStatus(
122
+ is_staked=True, staking_state="STAKED"
123
+ )
124
+
125
+ from iwa.web.dependencies import wallet
126
+
127
+ wallet.get_native_balance_eth.return_value = 1.0
128
+ wallet.balance_service.get_erc20_balance_wei.return_value = 10**18
129
+
130
+ response = client.get("/api/olas/services?chain=gnosis")
131
+ assert response.status_code == 200
132
+ data = response.json()
133
+ assert len(data) == 1
134
+ assert data[0]["staking"]["is_staked"] is True
135
+
136
+
137
+ def test_olas_actions(client, mock_olas_config):
138
+ """Test Olas action endpoints (claim, unstake, checkpoint)."""
139
+ with (
140
+ patch("iwa.web.routers.olas.staking.Config") as mock_config_cls,
141
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
142
+ ):
143
+ mock_config = mock_config_cls.return_value
144
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
145
+
146
+ mock_sm = mock_sm_cls.return_value
147
+ mock_sm.claim_rewards.return_value = (True, 10**18)
148
+ mock_sm.unstake.return_value = True
149
+ mock_sm.call_checkpoint.return_value = True
150
+
151
+ # Mock StakingContract.from_address and ChainInterfaces
152
+ with patch("iwa.plugins.olas.contracts.staking.StakingContract"):
153
+ from iwa.core.chain import ChainInterfaces
154
+
155
+ # Properly access the return value of the mocked class singleton-like usage
156
+ if hasattr(ChainInterfaces, "return_value"):
157
+ ChainInterfaces.return_value.get.return_value.chain = MagicMock()
158
+
159
+ # mock_sc = mock_sc_cls.from_address.return_value
160
+
161
+ # Claim
162
+ response = client.post("/api/olas/claim/gnosis:1")
163
+ assert response.status_code == 200
164
+ assert response.json()["status"] == "success"
165
+
166
+ # Unstake
167
+ response = client.post("/api/olas/unstake/gnosis:1")
168
+ assert response.status_code == 200
169
+ assert response.json()["status"] == "success"
170
+
171
+ # Checkpoint
172
+ response = client.post("/api/olas/checkpoint/gnosis:1")
173
+ assert response.status_code == 200
174
+ assert response.json()["status"] == "success"
175
+
176
+
177
+ # --- Additional tests for uncovered endpoints ---
178
+
179
+
180
+ def test_get_staking_contracts(client):
181
+ """Test /api/olas/staking-contracts endpoint - returns response with contracts and filter_info."""
182
+ response = client.get("/api/olas/staking-contracts?chain=gnosis")
183
+ assert response.status_code == 200
184
+ data = response.json()
185
+ # New format: {contracts: [...], filter_info: {...}}
186
+ assert isinstance(data, dict)
187
+ assert "contracts" in data
188
+ assert "filter_info" in data
189
+ assert isinstance(data["contracts"], list)
190
+ assert isinstance(data["filter_info"], dict)
191
+
192
+
193
+ def test_create_service(client, mock_olas_config):
194
+ """Test /api/olas/create endpoint."""
195
+ with (
196
+ patch(
197
+ "iwa.web.routers.olas.services.Config"
198
+ ) as mock_config_cls, # Not strictly used but kept for consistency if needed
199
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
200
+ ):
201
+ mock_config = mock_config_cls.return_value
202
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
203
+
204
+ mock_sm = mock_sm_cls.return_value
205
+ mock_sm.create.return_value = 123
206
+ mock_sm.spin_up.return_value = True
207
+ mock_sm.service = MagicMock()
208
+ mock_sm.service.service_id = 123
209
+
210
+ response = client.post(
211
+ "/api/olas/create",
212
+ json={
213
+ "service_name": "Test Service",
214
+ "chain": "gnosis",
215
+ "bond_amount_olas": 100,
216
+ },
217
+ )
218
+ assert response.status_code == 200
219
+ data = response.json()
220
+ assert data["status"] == "success"
221
+
222
+
223
+ def test_create_service_failure(client, mock_olas_config):
224
+ """Test /api/olas/create when creation fails."""
225
+ with (
226
+ patch("iwa.web.routers.olas.services.Config") as mock_config_cls,
227
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
228
+ ):
229
+ mock_config = mock_config_cls.return_value
230
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
231
+
232
+ mock_sm = mock_sm_cls.return_value
233
+ mock_sm.create.return_value = None # Creation fails
234
+
235
+ response = client.post(
236
+ "/api/olas/create",
237
+ json={
238
+ "service_name": "Test Service",
239
+ "chain": "gnosis",
240
+ },
241
+ )
242
+ # API returns 400 on creation failure
243
+ assert response.status_code == 400
244
+
245
+
246
+ def test_deploy_service(client, mock_olas_config):
247
+ """Test /api/olas/deploy/{service_key} endpoint."""
248
+ with (
249
+ patch("iwa.web.routers.olas.services.Config") as mock_config_cls,
250
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
251
+ ):
252
+ mock_config = mock_config_cls.return_value
253
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
254
+
255
+ mock_sm = mock_sm_cls.return_value
256
+ mock_sm.spin_up.return_value = True
257
+ mock_sm.service = mock_olas_config.services["gnosis:1"]
258
+ mock_sm.get_service_state.return_value = "PRE_REGISTRATION"
259
+
260
+ response = client.post("/api/olas/deploy/gnosis:1")
261
+ assert response.status_code == 200
262
+ assert response.json()["status"] == "success"
263
+
264
+
265
+ def test_deploy_service_failure(client, mock_olas_config):
266
+ """Test /api/olas/deploy/{service_key} when deployment fails."""
267
+ with (
268
+ patch("iwa.web.routers.olas.services.Config") as mock_config_cls,
269
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
270
+ ):
271
+ mock_config = mock_config_cls.return_value
272
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
273
+
274
+ mock_sm = mock_sm_cls.return_value
275
+ mock_sm.spin_up.return_value = False
276
+ mock_sm.get_service_state.return_value = "PRE_REGISTRATION"
277
+
278
+ response = client.post("/api/olas/deploy/gnosis:1")
279
+ # API returns 400 on spin_up failure
280
+ assert response.status_code == 400
281
+
282
+
283
+ def test_activate_registration(client, mock_olas_config):
284
+ """Test /api/olas/activate/{service_key} endpoint."""
285
+ with (
286
+ patch("iwa.web.routers.olas.admin.Config") as mock_config_cls,
287
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
288
+ ):
289
+ mock_config = mock_config_cls.return_value
290
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
291
+
292
+ mock_sm = mock_sm_cls.return_value
293
+ mock_sm.activate_registration.return_value = True
294
+
295
+ response = client.post("/api/olas/activate/gnosis:1")
296
+ assert response.status_code == 200
297
+ assert response.json()["status"] == "success"
298
+
299
+
300
+ def test_register_agent(client, mock_olas_config):
301
+ """Test /api/olas/register/{service_key} endpoint."""
302
+ with (
303
+ patch("iwa.web.routers.olas.admin.Config") as mock_config_cls,
304
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
305
+ ):
306
+ mock_config = mock_config_cls.return_value
307
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
308
+
309
+ mock_sm = mock_sm_cls.return_value
310
+ mock_sm.register_agent.return_value = True
311
+
312
+ # Correct URL is /register/ not /register-agent/
313
+ response = client.post("/api/olas/register/gnosis:1")
314
+ assert response.status_code == 200
315
+ assert response.json()["status"] == "success"
316
+
317
+
318
+ def test_deploy_step(client, mock_olas_config):
319
+ """Test /api/olas/deploy-step/{service_key} endpoint."""
320
+ with (
321
+ patch("iwa.web.routers.olas.admin.Config") as mock_config_cls,
322
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
323
+ ):
324
+ mock_config = mock_config_cls.return_value
325
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
326
+
327
+ mock_sm = mock_sm_cls.return_value
328
+ mock_sm.deploy.return_value = "0xMultisig123" # Returns multisig address
329
+
330
+ response = client.post("/api/olas/deploy-step/gnosis:1")
331
+ assert response.status_code == 200
332
+ data = response.json()
333
+ # API only returns {"status": "success"}, not multisig
334
+ assert data["status"] == "success"
335
+
336
+
337
+ def test_stake_service(client, mock_olas_config):
338
+ """Test /api/olas/stake/{service_key} endpoint."""
339
+ with (
340
+ patch("iwa.web.routers.olas.staking.Config") as mock_config_cls,
341
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
342
+ patch("iwa.plugins.olas.contracts.staking.StakingContract"),
343
+ ):
344
+ mock_config = mock_config_cls.return_value
345
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
346
+
347
+ mock_sm = mock_sm_cls.return_value
348
+ mock_sm.stake.return_value = True
349
+
350
+ response = client.post(
351
+ "/api/olas/stake/gnosis:1?staking_contract=0x1234567890123456789012345678901234567890"
352
+ )
353
+ assert response.status_code == 200
354
+ assert response.json()["status"] == "success"
355
+
356
+
357
+ def test_terminate_service(client, mock_olas_config):
358
+ """Test /api/olas/terminate/{service_key} endpoint."""
359
+ with (
360
+ patch("iwa.web.routers.olas.admin.Config") as mock_config_cls,
361
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
362
+ patch("iwa.plugins.olas.contracts.staking.StakingContract"),
363
+ ):
364
+ mock_config = mock_config_cls.return_value
365
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
366
+
367
+ mock_sm = mock_sm_cls.return_value
368
+ mock_sm.wind_down.return_value = True
369
+
370
+ response = client.post("/api/olas/terminate/gnosis:1")
371
+ assert response.status_code == 200
372
+ assert response.json()["status"] == "success"
373
+
374
+
375
+ def test_fund_service(client, mock_olas_config):
376
+ """Test /api/olas/fund/{service_key} endpoint."""
377
+ with (
378
+ patch("iwa.web.routers.olas.funding.Config") as mock_config_cls,
379
+ patch("iwa.web.routers.olas.funding.wallet") as mock_wallet,
380
+ ):
381
+ mock_config = mock_config_cls.return_value
382
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
383
+
384
+ mock_wallet.send.return_value = "0xTxHash"
385
+
386
+ response = client.post(
387
+ "/api/olas/fund/gnosis:1",
388
+ json={"agent_amount_eth": 1.0, "safe_amount_eth": 2.0},
389
+ )
390
+ assert response.status_code == 200
391
+ data = response.json()
392
+ assert data["status"] == "success"
393
+
394
+
395
+ def test_drain_service(client, mock_olas_config):
396
+ """Test /api/olas/drain/{service_key} endpoint."""
397
+ with (
398
+ patch("iwa.web.routers.olas.funding.Config") as mock_config_cls,
399
+ patch("iwa.plugins.olas.service_manager.ServiceManager") as mock_sm_cls,
400
+ ):
401
+ mock_config = mock_config_cls.return_value
402
+ mock_config.plugins = {"olas": mock_olas_config.model_dump()}
403
+
404
+ mock_sm = mock_sm_cls.return_value
405
+ mock_sm.drain_service.return_value = {
406
+ "safe": {"native": 1.5, "olas": 100.0},
407
+ "agent": {"native": 0.5},
408
+ }
409
+
410
+ response = client.post("/api/olas/drain/gnosis:1")
411
+ assert response.status_code == 200
412
+ data = response.json()
413
+ assert data["status"] == "success"
414
+ assert "safe" in data["drained"]
415
+
416
+
417
+ def test_service_not_found(client):
418
+ """Test endpoints return 404 when service not found."""
419
+ with patch("iwa.web.routers.olas.admin.Config") as mock_config_cls:
420
+ mock_config = mock_config_cls.return_value
421
+ mock_config.plugins = {"olas": OlasConfig().model_dump()}
422
+
423
+ response = client.post("/api/olas/activate/gnosis:999")
424
+ assert response.status_code == 404
425
+
426
+ response = client.post("/api/olas/register/gnosis:999")
427
+ assert response.status_code == 404
428
+
429
+ response = client.post("/api/olas/deploy-step/gnosis:999")
430
+ assert response.status_code == 404
@@ -0,0 +1,103 @@
1
+ """Tests for Swap Web API endpoints."""
2
+
3
+ from unittest.mock import patch
4
+
5
+ import pytest
6
+ from fastapi.testclient import TestClient
7
+
8
+ # We need to mock Wallet and ChainInterfaces BEFORE importing app from server
9
+ # Also mock _get_webui_password to bypass authentication in tests
10
+ with (
11
+ patch("iwa.core.wallet.Wallet"),
12
+ patch("iwa.core.chain.ChainInterfaces"),
13
+ patch("iwa.core.wallet.init_db"),
14
+ patch("iwa.web.dependencies._get_webui_password", return_value=None),
15
+ ):
16
+ from iwa.web.dependencies import verify_auth
17
+ from iwa.web.server import app
18
+
19
+
20
+ # Override auth for all tests
21
+ async def override_verify_auth():
22
+ """Override auth for testing."""
23
+ return True
24
+
25
+
26
+ app.dependency_overrides[verify_auth] = override_verify_auth
27
+
28
+
29
+ @pytest.fixture
30
+ def client():
31
+ """TestClient for FastAPI app."""
32
+ return TestClient(app)
33
+
34
+
35
+ def test_get_swap_quote_invalid_account(client):
36
+ """Test /api/swap/quote with invalid account."""
37
+ with patch("iwa.web.routers.swap.wallet") as mock_wallet:
38
+ mock_wallet.account_service.resolve_account.return_value = None
39
+
40
+ response = client.get(
41
+ "/api/swap/quote?account=invalid&sell_token=WXDAI&buy_token=OLAS&amount=1.0"
42
+ )
43
+ assert response.status_code == 400
44
+
45
+
46
+ def test_swap_tokens_invalid_order_type(client):
47
+ """Test /api/swap with invalid order type."""
48
+ response = client.post(
49
+ "/api/swap",
50
+ json={
51
+ "account": "master",
52
+ "sell_token": "WXDAI",
53
+ "buy_token": "OLAS",
54
+ "amount": 1.0,
55
+ "order_type": "invalid",
56
+ "chain": "gnosis",
57
+ },
58
+ )
59
+ # Should return 422 for validation error
60
+ assert response.status_code == 422
61
+
62
+
63
+ def test_swap_quote_invalid_mode(client):
64
+ """Test /api/swap/quote with invalid mode."""
65
+ response = client.get(
66
+ "/api/swap/quote?account=master&sell_token=WXDAI&buy_token=OLAS&amount=1.0&mode=invalid"
67
+ )
68
+ assert response.status_code == 400
69
+
70
+
71
+ def test_swap_tokens_negative_amount(client):
72
+ """Test /api/swap with negative amount."""
73
+ response = client.post(
74
+ "/api/swap",
75
+ json={
76
+ "account": "master",
77
+ "sell_token": "WXDAI",
78
+ "buy_token": "OLAS",
79
+ "amount": -1.0,
80
+ "order_type": "sell",
81
+ "chain": "gnosis",
82
+ },
83
+ )
84
+ # Should return 422 for validation error, but sometimes 400 depending on handler
85
+ # Accepted behavior is rejection.
86
+ assert response.status_code in (400, 422)
87
+
88
+
89
+ def test_swap_tokens_invalid_chain(client):
90
+ """Test /api/swap with invalid chain."""
91
+ response = client.post(
92
+ "/api/swap",
93
+ json={
94
+ "account": "master",
95
+ "sell_token": "WXDAI",
96
+ "buy_token": "OLAS",
97
+ "amount": 1.0,
98
+ "order_type": "sell",
99
+ "chain": "invalid!chain",
100
+ },
101
+ )
102
+ # Should return 422 for validation error
103
+ assert response.status_code == 422