Skip to content

Commit c7bf2c9

Browse files
committed
Bug fix
1 parent 50712c7 commit c7bf2c9

File tree

2 files changed

+141
-1
lines changed

2 files changed

+141
-1
lines changed

bot/main.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ async def poll_and_notify(context: ContextTypes.DEFAULT_TYPE):
290290

291291
async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
292292
"""Handle /start and /menu commands."""
293+
if not update.message:
294+
return # Ignore edited messages or other update types
293295
user_id = get_user_id(update)
294296
if user_id:
295297
register_user(user_id)
@@ -308,6 +310,8 @@ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
308310

309311
async def price_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
310312
"""Handle /price command."""
313+
if not update.message:
314+
return
311315
user_id = get_user_id(update)
312316
if user_id:
313317
register_user(user_id)
@@ -320,6 +324,8 @@ async def price_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
320324

321325
async def weishen_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
322326
"""Handle /weishen command."""
327+
if not update.message:
328+
return
323329
user_id = get_user_id(update)
324330
if user_id:
325331
register_user(user_id)
@@ -329,6 +335,8 @@ async def weishen_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
329335

330336
async def me_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
331337
"""Handle /me command."""
338+
if not update.message:
339+
return
332340
user_id = get_user_id(update)
333341
if not user_id:
334342
return # Ignore channel posts or anonymous admins
@@ -362,6 +370,8 @@ async def me_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
362370

363371
async def set_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
364372
"""Handle /set command to set user's position."""
373+
if not update.message:
374+
return
365375
user_id = get_user_id(update)
366376
if not user_id:
367377
return # Ignore channel posts or anonymous admins
@@ -463,6 +473,8 @@ async def button_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
463473

464474
async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
465475
"""Handle user message with BTC size input (quick scale)."""
476+
if not update.message or not update.message.text:
477+
return
466478
user_id = get_user_id(update)
467479
if user_id:
468480
register_user(user_id)

tests/test_bot.py

Lines changed: 129 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import pytest
44
from decimal import Decimal
55

6-
from bot.main import detect_changes, format_changes
6+
from bot.main import detect_changes, format_changes, get_user_id, TELEGRAM_MAX_LENGTH
77

88

99
def parse_input(text: str) -> tuple[str, Decimal]:
@@ -172,3 +172,131 @@ def test_handles_missing_fields(self):
172172
curr = {}
173173
result = format_changes(changes, curr)
174174
assert "Test change" in result
175+
176+
def test_truncates_long_messages(self):
177+
"""Test that messages exceeding Telegram limit are truncated."""
178+
# Create many changes that would exceed the limit
179+
changes = [f"Change {i}: Order modified at price ${90000 + i:,}" for i in range(200)]
180+
curr = {"direction": "long", "size": Decimal("1"), "entry_price": Decimal("95000")}
181+
result = format_changes(changes, curr)
182+
assert len(result) <= TELEGRAM_MAX_LENGTH
183+
assert "more changes" in result
184+
185+
186+
class TestDetectChangesEdgeCases:
187+
"""Additional edge case tests for detect_changes."""
188+
189+
def test_string_vs_number_sz_no_spurious_change(self):
190+
"""String "0.5" and number 0.5 should not trigger a change."""
191+
prev = {
192+
"direction": "long",
193+
"size": "1",
194+
"entry_price": "95000",
195+
"orders": [{"oid": 123, "side": "B", "sz": "0.5", "limitPx": "90000"}]
196+
}
197+
curr = {
198+
"direction": "long",
199+
"size": Decimal("1"),
200+
"entry_price": Decimal("95000"),
201+
"orders": [{"oid": 123, "side": "B", "sz": 0.5, "limitPx": 90000}] # numbers instead of strings
202+
}
203+
changes = detect_changes(prev, curr)
204+
assert len(changes) == 0
205+
206+
def test_int_vs_string_oid_matched(self):
207+
"""Integer oid 123 and string "123" should match the same order."""
208+
prev = {
209+
"direction": "long",
210+
"size": "1",
211+
"entry_price": "95000",
212+
"orders": [{"oid": 123, "side": "B", "sz": "0.5", "limitPx": "90000"}] # int oid
213+
}
214+
curr = {
215+
"direction": "long",
216+
"size": Decimal("1"),
217+
"entry_price": Decimal("95000"),
218+
"orders": [{"oid": "123", "side": "B", "sz": "0.5", "limitPx": "90000"}] # string oid
219+
}
220+
changes = detect_changes(prev, curr)
221+
assert len(changes) == 0
222+
223+
def test_orders_with_none_oid_filtered(self):
224+
"""Orders with None oid should be filtered out."""
225+
prev = {
226+
"direction": "long",
227+
"size": "1",
228+
"entry_price": "95000",
229+
"orders": [{"oid": None, "side": "B", "sz": "0.5", "limitPx": "90000"}]
230+
}
231+
curr = {
232+
"direction": "long",
233+
"size": Decimal("1"),
234+
"entry_price": Decimal("95000"),
235+
"orders": [{"oid": None, "side": "B", "sz": "0.6", "limitPx": "91000"}]
236+
}
237+
changes = detect_changes(prev, curr)
238+
# Both orders have None oid, so they're filtered out - no changes detected
239+
assert len(changes) == 0
240+
241+
def test_malformed_price_handled(self):
242+
"""Malformed limitPx should not crash the function."""
243+
prev = {
244+
"direction": "long",
245+
"size": "1",
246+
"entry_price": "95000",
247+
"orders": [{"oid": 123, "side": "B", "sz": "0.5", "limitPx": "invalid"}]
248+
}
249+
curr = {
250+
"direction": "long",
251+
"size": Decimal("1"),
252+
"entry_price": Decimal("95000"),
253+
"orders": []
254+
}
255+
# Should not raise, should produce "Order removed" with fallback price display
256+
changes = detect_changes(prev, curr)
257+
assert len(changes) == 1
258+
assert "Order removed" in changes[0]
259+
260+
def test_multiple_changes_detected(self):
261+
"""Multiple simultaneous changes are all detected."""
262+
prev = {
263+
"direction": "long",
264+
"size": "1",
265+
"entry_price": "95000",
266+
"orders": [{"oid": 123, "side": "B", "sz": "0.5", "limitPx": "90000"}]
267+
}
268+
curr = {
269+
"direction": "short", # changed
270+
"size": Decimal("2"), # changed
271+
"entry_price": Decimal("96000"), # changed
272+
"orders": [
273+
{"oid": 123, "side": "B", "sz": "0.6", "limitPx": "91000"}, # modified
274+
{"oid": 456, "side": "A", "sz": "0.3", "limitPx": "100000"} # added
275+
]
276+
}
277+
changes = detect_changes(prev, curr)
278+
# direction + size + entry + order modified + order added = 5 changes
279+
assert len(changes) == 5
280+
281+
282+
class TestGetUserId:
283+
"""Tests for the get_user_id helper function."""
284+
285+
def test_returns_none_for_none_effective_user(self):
286+
"""Should return None when effective_user is None."""
287+
class MockUpdate:
288+
effective_user = None
289+
290+
result = get_user_id(MockUpdate())
291+
assert result is None
292+
293+
def test_returns_id_when_effective_user_exists(self):
294+
"""Should return user ID when effective_user exists."""
295+
class MockUser:
296+
id = 12345
297+
298+
class MockUpdate:
299+
effective_user = MockUser()
300+
301+
result = get_user_id(MockUpdate())
302+
assert result == 12345

0 commit comments

Comments
 (0)