fix: bound chat latency and clarify blackjack flow
This commit is contained in:
@@ -1736,7 +1736,7 @@ class DirectClientTests(unittest.TestCase):
|
||||
)
|
||||
)
|
||||
response = mock.MagicMock()
|
||||
response.read.return_value = (
|
||||
response.read1.return_value = (
|
||||
'data: {"choices":[{"delta":{"content":"正"}}]}\n\n'
|
||||
'data: {"choices":[{"delta":{"content":"常"}}]}\n\n'
|
||||
"data: [DONE]\n\n"
|
||||
@@ -1782,6 +1782,55 @@ class DirectClientTests(unittest.TestCase):
|
||||
finally:
|
||||
temporary.cleanup()
|
||||
|
||||
def test_stream_reader_stops_at_done_without_waiting_for_eof(self):
|
||||
response = mock.MagicMock()
|
||||
response.read1.side_effect = [
|
||||
b'data: {"choices":[{"delta":{"content":"ok"}}]}\n'
|
||||
b"data: [DO",
|
||||
b"NE]\n\n",
|
||||
AssertionError("reader continued after the stream completed"),
|
||||
]
|
||||
|
||||
raw = MODULE.read_direct_api_response(response)
|
||||
|
||||
self.assertEqual(
|
||||
raw,
|
||||
b'data: {"choices":[{"delta":{"content":"ok"}}]}\n'
|
||||
b"data: [DONE]\n\n",
|
||||
)
|
||||
self.assertEqual(response.read1.call_count, 2)
|
||||
|
||||
def test_stream_reader_does_not_treat_content_as_done_marker(self):
|
||||
response = mock.MagicMock()
|
||||
response.read1.side_effect = [
|
||||
b'data: {"choices":[{"delta":{"content":"data: [DONE]"}}]}\n\n',
|
||||
b"data: [DONE]\n\n",
|
||||
AssertionError("reader continued after the stream completed"),
|
||||
]
|
||||
|
||||
raw = MODULE.read_direct_api_response(response)
|
||||
|
||||
self.assertIn(b'"content":"data: [DONE]"', raw)
|
||||
self.assertTrue(raw.endswith(b"data: [DONE]\n\n"))
|
||||
self.assertEqual(response.read1.call_count, 2)
|
||||
|
||||
def test_stream_reader_enforces_total_wall_time(self):
|
||||
response = mock.MagicMock()
|
||||
response.read1.return_value = b": keep-alive\n\n"
|
||||
with mock.patch.object(
|
||||
MODULE.time,
|
||||
"monotonic",
|
||||
side_effect=[0.0, 0.0, 31.0],
|
||||
):
|
||||
with self.assertRaisesRegex(
|
||||
MODULE.DeepSeekUnavailable,
|
||||
"exceeded total timeout",
|
||||
):
|
||||
MODULE.read_direct_api_response(response)
|
||||
|
||||
response.fp.raw._sock.settimeout.assert_called_once_with(18.0)
|
||||
response.read1.assert_called_once()
|
||||
|
||||
def test_stream_parser_accepts_a_non_streaming_compatibility_payload(self):
|
||||
self.assertEqual(
|
||||
MODULE.deepseek_response_content(
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Behavioral coverage for the alpha 0.6.2 latency and blackjack fixes."""
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest import mock
|
||||
|
||||
from test_jwebot_edge import MODULE, config
|
||||
|
||||
|
||||
class Release062BlackjackTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.temp = tempfile.TemporaryDirectory()
|
||||
self.engine = MODULE.EdgeApplication(config(Path(self.temp.name)))
|
||||
self.counter = 0
|
||||
|
||||
def tearDown(self):
|
||||
self.engine.ledger.connection.close()
|
||||
self.temp.cleanup()
|
||||
|
||||
def command(self, question, *, event_id=None, sender="Alice"):
|
||||
self.counter += 1
|
||||
row = {
|
||||
"question": question,
|
||||
"sender": sender,
|
||||
"group_fingerprint": "group-a",
|
||||
"event_id": event_id or f"test-{self.counter}",
|
||||
}
|
||||
return self.engine.local.handle(row, {})
|
||||
|
||||
def state(self, sender="Alice"):
|
||||
key = self.engine.ledger.resolve_person("group-a", sender)["person_key"]
|
||||
row = self.engine.ledger.connection.execute(
|
||||
"""
|
||||
SELECT state_json FROM player_accounts
|
||||
WHERE group_fingerprint = ? AND person_key = ?
|
||||
""",
|
||||
("group-a", key),
|
||||
).fetchone()
|
||||
return json.loads(row[0])
|
||||
|
||||
@staticmethod
|
||||
def hit_deck(cards):
|
||||
first = [4, 5, 8, 7]
|
||||
cards[:] = (
|
||||
first
|
||||
+ [card for card in cards if card not in first and card != 1]
|
||||
+ [1]
|
||||
)
|
||||
|
||||
def test_compact_start_sets_stake_then_separate_hit_replays_once(self):
|
||||
with mock.patch.object(
|
||||
self.engine.local.players.rng,
|
||||
"shuffle",
|
||||
side_effect=self.hit_deck,
|
||||
):
|
||||
start = self.command("/21点开始100", event_id="compact-start")
|
||||
self.assertTrue(start.startswith("alice:"))
|
||||
self.assertIn("下注 100", start)
|
||||
self.assertEqual(self.state()["balance"], 900)
|
||||
self.assertEqual(len(self.state()["game"]["player"]), 2)
|
||||
|
||||
first = self.command("/21点要牌", event_id="separate-hit")
|
||||
replay = self.command(
|
||||
"/21点 要牌",
|
||||
event_id="separate-hit",
|
||||
sender="a1",
|
||||
)
|
||||
|
||||
self.assertEqual(replay, first)
|
||||
self.assertTrue(first.startswith("alice:"))
|
||||
self.assertIn("13点", first)
|
||||
self.assertEqual(self.state()["balance"], 900)
|
||||
self.assertEqual(len(self.state()["game"]["player"]), 3)
|
||||
|
||||
def test_spaced_chinese_and_compact_english_start_use_bet(self):
|
||||
def tied_deck(cards):
|
||||
first = [9, 6, 8, 7]
|
||||
cards[:] = first + [card for card in cards if card not in first]
|
||||
|
||||
for command, sender in (
|
||||
("/21点 开始 50", "Alice"),
|
||||
("/blackjackstart50", "Bob"),
|
||||
):
|
||||
with self.subTest(command=command):
|
||||
with mock.patch.object(
|
||||
self.engine.local.players.rng,
|
||||
"shuffle",
|
||||
side_effect=tied_deck,
|
||||
):
|
||||
started = self.command(command, sender=sender)
|
||||
self.assertTrue(
|
||||
started.startswith(
|
||||
"alice:" if sender == "Alice" else "bob:"
|
||||
)
|
||||
)
|
||||
self.assertIn("下注 50", started)
|
||||
self.assertEqual(self.state(sender)["balance"], 950)
|
||||
self.assertIsNotNone(self.state(sender)["game"])
|
||||
self.command("/21点 结束", sender=sender)
|
||||
self.assertIsNone(self.state(sender)["game"])
|
||||
|
||||
def test_default_bet_can_start_without_spaces(self):
|
||||
with mock.patch.object(
|
||||
self.engine.local.players.rng,
|
||||
"shuffle",
|
||||
side_effect=self.hit_deck,
|
||||
):
|
||||
reply = self.command("/21点开始")
|
||||
|
||||
self.assertTrue(reply.startswith("alice:"))
|
||||
self.assertIn("下注 50", reply)
|
||||
self.assertEqual(self.state()["balance"], 950)
|
||||
|
||||
def test_combined_start_and_action_explains_two_step_flow(self):
|
||||
reply = self.command("/21点开始100要牌")
|
||||
|
||||
self.assertIn("分两步", reply)
|
||||
self.assertIn("100 是本局下注", reply)
|
||||
self.assertIn("再单独发送", reply)
|
||||
wallet = self.command("/金币")
|
||||
self.assertIn("金币 1000", wallet)
|
||||
self.assertIn("无进行中的牌局", wallet)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user