]>
Commit | Line | Data |
---|---|---|
c682bdf4 IB |
1 | from .helper import * |
2 | import main, market | |
3 | ||
3080f31d | 4 | @unittest.skipUnless("unit" in limits, "Unit skipped") |
c682bdf4 IB |
5 | class MainTest(WebMockTestCase): |
6 | def test_make_order(self): | |
7 | self.m.get_ticker.return_value = { | |
8 | "inverted": False, | |
9 | "average": D("0.1"), | |
10 | "bid": D("0.09"), | |
11 | "ask": D("0.11"), | |
12 | } | |
13 | ||
14 | with self.subTest(description="nominal case"): | |
15 | main.make_order(self.m, 10, "ETH") | |
16 | ||
17 | self.m.report.log_stage.assert_has_calls([ | |
18 | mock.call("make_order_begin"), | |
19 | mock.call("make_order_end"), | |
20 | ]) | |
21 | self.m.balances.fetch_balances.assert_has_calls([ | |
22 | mock.call(tag="make_order_begin"), | |
23 | mock.call(tag="make_order_end"), | |
24 | ]) | |
25 | self.m.trades.all.append.assert_called_once() | |
26 | trade = self.m.trades.all.append.mock_calls[0][1][0] | |
27 | self.assertEqual(False, trade.orders[0].close_if_possible) | |
28 | self.assertEqual(0, trade.value_from) | |
29 | self.assertEqual("ETH", trade.currency) | |
30 | self.assertEqual("BTC", trade.base_currency) | |
31 | self.m.report.log_orders.assert_called_once_with([trade.orders[0]], None, "average") | |
32 | self.m.trades.run_orders.assert_called_once_with() | |
33 | self.m.follow_orders.assert_called_once_with() | |
34 | ||
35 | order = trade.orders[0] | |
36 | self.assertEqual(D("0.10"), order.rate) | |
37 | ||
38 | self.m.reset_mock() | |
39 | with self.subTest(compute_value="default"): | |
40 | main.make_order(self.m, 10, "ETH", action="dispose", | |
41 | compute_value="ask") | |
42 | ||
43 | trade = self.m.trades.all.append.mock_calls[0][1][0] | |
44 | order = trade.orders[0] | |
45 | self.assertEqual(D("0.11"), order.rate) | |
46 | ||
47 | self.m.reset_mock() | |
48 | with self.subTest(follow=False): | |
49 | result = main.make_order(self.m, 10, "ETH", follow=False) | |
50 | ||
51 | self.m.report.log_stage.assert_has_calls([ | |
52 | mock.call("make_order_begin"), | |
53 | mock.call("make_order_end_not_followed"), | |
54 | ]) | |
55 | self.m.balances.fetch_balances.assert_called_once_with(tag="make_order_begin") | |
56 | ||
57 | self.m.trades.all.append.assert_called_once() | |
58 | trade = self.m.trades.all.append.mock_calls[0][1][0] | |
59 | self.assertEqual(0, trade.value_from) | |
60 | self.assertEqual("ETH", trade.currency) | |
61 | self.assertEqual("BTC", trade.base_currency) | |
62 | self.m.report.log_orders.assert_called_once_with([trade.orders[0]], None, "average") | |
63 | self.m.trades.run_orders.assert_called_once_with() | |
64 | self.m.follow_orders.assert_not_called() | |
65 | self.assertEqual(trade.orders[0], result) | |
66 | ||
67 | self.m.reset_mock() | |
68 | with self.subTest(base_currency="USDT"): | |
69 | main.make_order(self.m, 1, "BTC", base_currency="USDT") | |
70 | ||
71 | trade = self.m.trades.all.append.mock_calls[0][1][0] | |
72 | self.assertEqual("BTC", trade.currency) | |
73 | self.assertEqual("USDT", trade.base_currency) | |
74 | ||
75 | self.m.reset_mock() | |
76 | with self.subTest(close_if_possible=True): | |
77 | main.make_order(self.m, 10, "ETH", close_if_possible=True) | |
78 | ||
79 | trade = self.m.trades.all.append.mock_calls[0][1][0] | |
80 | self.assertEqual(True, trade.orders[0].close_if_possible) | |
81 | ||
82 | self.m.reset_mock() | |
83 | with self.subTest(action="dispose"): | |
84 | main.make_order(self.m, 10, "ETH", action="dispose") | |
85 | ||
86 | trade = self.m.trades.all.append.mock_calls[0][1][0] | |
87 | self.assertEqual(0, trade.value_to) | |
88 | self.assertEqual(1, trade.value_from.value) | |
89 | self.assertEqual("ETH", trade.currency) | |
90 | self.assertEqual("BTC", trade.base_currency) | |
91 | ||
92 | self.m.reset_mock() | |
93 | with self.subTest(compute_value="default"): | |
94 | main.make_order(self.m, 10, "ETH", action="dispose", | |
95 | compute_value="bid") | |
96 | ||
97 | trade = self.m.trades.all.append.mock_calls[0][1][0] | |
98 | self.assertEqual(D("0.9"), trade.value_from.value) | |
99 | ||
100 | def test_get_user_market(self): | |
101 | with mock.patch("main.fetch_markets") as main_fetch_markets,\ | |
102 | mock.patch("main.parse_args") as main_parse_args,\ | |
103 | mock.patch("main.parse_config") as main_parse_config: | |
104 | with self.subTest(debug=False): | |
105 | main_parse_args.return_value = self.market_args() | |
ef8fa5e5 | 106 | main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3, { "foo": "bar" })] |
c682bdf4 IB |
107 | m = main.get_user_market("config_path.ini", 1) |
108 | ||
109 | self.assertIsInstance(m, market.Market) | |
110 | self.assertFalse(m.debug) | |
ef8fa5e5 | 111 | self.assertEqual("bar", m.options["foo"]) |
c682bdf4 IB |
112 | main_parse_args.assert_called_once_with(["--config", "config_path.ini"]) |
113 | ||
114 | main_parse_args.reset_mock() | |
115 | with self.subTest(debug=True): | |
116 | main_parse_args.return_value = self.market_args(debug=True) | |
ef8fa5e5 | 117 | main_fetch_markets.return_value = [(1, {"key": "market_config"}, 3, { "foo": "bar" })] |
c682bdf4 IB |
118 | m = main.get_user_market("config_path.ini", 1, debug=True) |
119 | ||
120 | self.assertIsInstance(m, market.Market) | |
121 | self.assertTrue(m.debug) | |
122 | main_parse_args.assert_called_once_with(["--config", "config_path.ini", "--debug"]) | |
123 | ||
ef8fa5e5 IB |
124 | def test_parse_liquidity(self): |
125 | self.assertEqual("high", main.parse_liquidity("high-liquidity")) | |
126 | self.assertEqual("medium", main.parse_liquidity("medium-liquidity")) | |
127 | self.assertIsNone(main.parse_liquidity("foo")) | |
128 | ||
c682bdf4 IB |
129 | def test_process(self): |
130 | with mock.patch("market.Market") as market_mock,\ | |
131 | mock.patch('sys.stdout', new_callable=StringIO) as stdout_mock: | |
132 | ||
133 | args_mock = mock.Mock() | |
134 | args_mock.action = "action" | |
135 | args_mock.config = "config" | |
136 | args_mock.user = "user" | |
137 | args_mock.debug = "debug" | |
138 | args_mock.before = "before" | |
139 | args_mock.after = "after" | |
140 | self.assertEqual("", stdout_mock.getvalue()) | |
141 | ||
ef8fa5e5 | 142 | main.process("config", 3, 1, args_mock, "options") |
c682bdf4 IB |
143 | |
144 | market_mock.from_config.assert_has_calls([ | |
ef8fa5e5 | 145 | mock.call("config", args_mock, market_id=3, user_id=1, options="options"), |
c682bdf4 IB |
146 | mock.call().process("action", before="before", after="after"), |
147 | ]) | |
148 | ||
149 | with self.subTest(exception=True): | |
150 | market_mock.from_config.side_effect = Exception("boo") | |
ef8fa5e5 | 151 | main.process(3, "config", 1, args_mock, "options") |
c682bdf4 IB |
152 | self.assertEqual("Exception: boo\n", stdout_mock.getvalue()) |
153 | ||
154 | def test_main(self): | |
155 | with self.subTest(parallel=False): | |
156 | with mock.patch("main.parse_args") as parse_args,\ | |
157 | mock.patch("main.parse_config") as parse_config,\ | |
158 | mock.patch("main.fetch_markets") as fetch_markets,\ | |
159 | mock.patch("main.process") as process: | |
160 | ||
161 | args_mock = mock.Mock() | |
162 | args_mock.parallel = False | |
163 | args_mock.user = "user" | |
164 | parse_args.return_value = args_mock | |
165 | ||
ef8fa5e5 IB |
166 | fetch_markets.return_value = [ |
167 | [3, "config1", 1, "options"], | |
168 | [1, "config2", 2, "options"] | |
169 | ] | |
c682bdf4 IB |
170 | |
171 | main.main(["Foo", "Bar"]) | |
172 | ||
173 | parse_args.assert_called_with(["Foo", "Bar"]) | |
174 | parse_config.assert_called_with(args_mock) | |
30700830 | 175 | fetch_markets.assert_called_with("user") |
c682bdf4 IB |
176 | |
177 | self.assertEqual(2, process.call_count) | |
178 | process.assert_has_calls([ | |
ef8fa5e5 IB |
179 | mock.call("config1", 3, 1, args_mock, "options"), |
180 | mock.call("config2", 1, 2, args_mock, "options"), | |
c682bdf4 | 181 | ]) |
ef8fa5e5 | 182 | |
c682bdf4 IB |
183 | with self.subTest(parallel=True): |
184 | with mock.patch("main.parse_args") as parse_args,\ | |
185 | mock.patch("main.parse_config") as parse_config,\ | |
186 | mock.patch("main.fetch_markets") as fetch_markets,\ | |
187 | mock.patch("main.process") as process,\ | |
e7d7c0e5 IB |
188 | mock.patch("store.Portfolio.start_worker") as start,\ |
189 | mock.patch("store.Portfolio.stop_worker") as stop: | |
c682bdf4 IB |
190 | |
191 | args_mock = mock.Mock() | |
192 | args_mock.parallel = True | |
193 | args_mock.user = "user" | |
194 | parse_args.return_value = args_mock | |
195 | ||
ef8fa5e5 IB |
196 | fetch_markets.return_value = [ |
197 | [3, "config1", 1, "options"], | |
198 | [1, "config2", 2, "options"] | |
199 | ] | |
c682bdf4 IB |
200 | |
201 | main.main(["Foo", "Bar"]) | |
202 | ||
203 | parse_args.assert_called_with(["Foo", "Bar"]) | |
204 | parse_config.assert_called_with(args_mock) | |
30700830 | 205 | fetch_markets.assert_called_with("user") |
c682bdf4 | 206 | |
e7d7c0e5 | 207 | stop.assert_called_once_with() |
c682bdf4 IB |
208 | start.assert_called_once_with() |
209 | self.assertEqual(2, process.call_count) | |
210 | process.assert_has_calls([ | |
211 | mock.call.__bool__(), | |
ef8fa5e5 | 212 | mock.call("config1", 3, 1, args_mock, "options"), |
c682bdf4 | 213 | mock.call.__bool__(), |
ef8fa5e5 | 214 | mock.call("config2", 1, 2, args_mock, "options"), |
c682bdf4 | 215 | ]) |
ef8fa5e5 | 216 | |
a0dcf4e0 IB |
217 | with self.subTest(quiet=True): |
218 | with mock.patch("main.parse_args") as parse_args,\ | |
219 | mock.patch("main.parse_config") as parse_config,\ | |
220 | mock.patch("main.fetch_markets") as fetch_markets,\ | |
221 | mock.patch("store.Portfolio.report") as report,\ | |
222 | mock.patch("main.process") as process: | |
223 | ||
224 | args_mock = mock.Mock() | |
225 | args_mock.parallel = False | |
226 | args_mock.quiet = True | |
227 | args_mock.user = "user" | |
228 | parse_args.return_value = args_mock | |
229 | ||
ef8fa5e5 IB |
230 | fetch_markets.return_value = [ |
231 | [3, "config1", 1, "options"], | |
232 | [1, "config2", 2, "options"] | |
233 | ] | |
a0dcf4e0 IB |
234 | |
235 | main.main(["Foo", "Bar"]) | |
236 | ||
237 | report.set_verbose.assert_called_once_with(False) | |
238 | ||
239 | with self.subTest(quiet=False): | |
240 | with mock.patch("main.parse_args") as parse_args,\ | |
241 | mock.patch("main.parse_config") as parse_config,\ | |
242 | mock.patch("main.fetch_markets") as fetch_markets,\ | |
243 | mock.patch("store.Portfolio.report") as report,\ | |
244 | mock.patch("main.process") as process: | |
245 | ||
246 | args_mock = mock.Mock() | |
247 | args_mock.parallel = False | |
248 | args_mock.quiet = False | |
249 | args_mock.user = "user" | |
250 | parse_args.return_value = args_mock | |
251 | ||
ef8fa5e5 IB |
252 | fetch_markets.return_value = [ |
253 | [3, "config1", 1, "options"], | |
254 | [1, "config2", 2, "options"] | |
255 | ] | |
a0dcf4e0 IB |
256 | |
257 | main.main(["Foo", "Bar"]) | |
258 | ||
259 | report.set_verbose.assert_called_once_with(True) | |
260 | ||
c682bdf4 IB |
261 | |
262 | @mock.patch.object(main.sys, "exit") | |
263 | @mock.patch("main.os") | |
264 | def test_parse_config(self, os, exit): | |
30700830 IB |
265 | with self.subTest(report_path=None),\ |
266 | mock.patch.object(main.dbs, "connect_psql") as psql,\ | |
267 | mock.patch.object(main.dbs, "connect_redis") as redis: | |
c682bdf4 IB |
268 | args = main.configargparse.Namespace(**{ |
269 | "db_host": "host", | |
1593c7a9 | 270 | "redis_host": "rhost", |
c682bdf4 IB |
271 | "report_path": None, |
272 | }) | |
273 | ||
30700830 IB |
274 | main.parse_config(args) |
275 | psql.assert_called_once_with(args) | |
276 | redis.assert_called_once_with(args) | |
277 | ||
278 | with self.subTest(report_path=None, db=None),\ | |
279 | mock.patch.object(main.dbs, "connect_psql") as psql,\ | |
280 | mock.patch.object(main.dbs, "connect_redis") as redis: | |
281 | args = main.configargparse.Namespace(**{ | |
282 | "db_host": None, | |
283 | "redis_host": "rhost", | |
284 | "report_path": None, | |
285 | }) | |
1593c7a9 | 286 | |
30700830 IB |
287 | main.parse_config(args) |
288 | psql.assert_not_called() | |
289 | redis.assert_called_once_with(args) | |
1593c7a9 | 290 | |
30700830 IB |
291 | with self.subTest(report_path=None, redis=None),\ |
292 | mock.patch.object(main.dbs, "connect_psql") as psql,\ | |
293 | mock.patch.object(main.dbs, "connect_redis") as redis: | |
1593c7a9 IB |
294 | args = main.configargparse.Namespace(**{ |
295 | "db_host": "host", | |
30700830 | 296 | "redis_host": None, |
1593c7a9 IB |
297 | "report_path": None, |
298 | }) | |
299 | ||
30700830 IB |
300 | main.parse_config(args) |
301 | redis.assert_not_called() | |
302 | psql.assert_called_once_with(args) | |
c682bdf4 | 303 | |
30700830 IB |
304 | with self.subTest(report_path="present"),\ |
305 | mock.patch.object(main.dbs, "connect_psql") as psql,\ | |
306 | mock.patch.object(main.dbs, "connect_redis") as redis: | |
c682bdf4 IB |
307 | args = main.configargparse.Namespace(**{ |
308 | "db_host": "host", | |
1593c7a9 | 309 | "redis_host": "rhost", |
c682bdf4 IB |
310 | "report_path": "report_path", |
311 | }) | |
312 | ||
313 | os.path.exists.return_value = False | |
314 | ||
30700830 | 315 | main.parse_config(args) |
c682bdf4 IB |
316 | |
317 | os.path.exists.assert_called_once_with("report_path") | |
318 | os.makedirs.assert_called_once_with("report_path") | |
319 | ||
320 | def test_parse_args(self): | |
321 | with self.subTest(config="config.ini"): | |
322 | args = main.parse_args([]) | |
323 | self.assertEqual("config.ini", args.config) | |
324 | self.assertFalse(args.before) | |
325 | self.assertFalse(args.after) | |
326 | self.assertFalse(args.debug) | |
327 | ||
328 | args = main.parse_args(["--before", "--after", "--debug"]) | |
329 | self.assertTrue(args.before) | |
330 | self.assertTrue(args.after) | |
331 | self.assertTrue(args.debug) | |
332 | ||
333 | with self.subTest(config="inexistant"), \ | |
334 | self.assertRaises(SystemExit), \ | |
335 | mock.patch('sys.stderr', new_callable=StringIO) as stdout_mock: | |
336 | args = main.parse_args(["--config", "foo.bar"]) | |
337 | ||
30700830 IB |
338 | @mock.patch.object(main.dbs, "psql") |
339 | def test_fetch_markets(self, psql): | |
c682bdf4 | 340 | cursor_mock = mock.MagicMock() |
ef8fa5e5 IB |
341 | cursor_mock.__iter__.return_value = [ |
342 | (1, "cfg", 1, "high-liquidity"), | |
343 | (2, "cfg2", 3, "medium-liquidity") | |
344 | ] | |
c682bdf4 | 345 | |
30700830 | 346 | psql.cursor.return_value = cursor_mock |
c682bdf4 IB |
347 | |
348 | with self.subTest(user=None): | |
30700830 | 349 | rows = list(main.fetch_markets(None)) |
c682bdf4 | 350 | |
ef8fa5e5 | 351 | cursor_mock.execute.assert_called_once_with("SELECT id,config,user_id,portfolio_profile FROM market_configs_augmented WHERE status='enabled'") |
c682bdf4 | 352 | |
ef8fa5e5 IB |
353 | self.assertEqual([ |
354 | (1, 'cfg', 1, {'liquidity': 'high'}), | |
355 | (2, 'cfg2', 3, {'liquidity': 'medium'}) | |
356 | ], rows) | |
c682bdf4 | 357 | |
c682bdf4 IB |
358 | cursor_mock.execute.reset_mock() |
359 | with self.subTest(user=1): | |
30700830 | 360 | rows = list(main.fetch_markets(1)) |
c682bdf4 | 361 | |
ef8fa5e5 | 362 | cursor_mock.execute.assert_called_once_with("SELECT id,config,user_id,portfolio_profile FROM market_configs_augmented WHERE status='enabled' AND user_id = %s", [1]) |
c682bdf4 | 363 | |
ef8fa5e5 IB |
364 | self.assertEqual([ |
365 | (1, 'cfg', 1, {'liquidity': 'high'}), | |
366 | (2, 'cfg2', 3, {'liquidity': 'medium'}) | |
367 | ], rows) | |
c682bdf4 IB |
368 | |
369 |