Release beta 1.0.3 Mora wallet updates

This commit is contained in:
JWeBot Contributors
2026-10-16 15:44:34 +08:00
parent 22edf6ff15
commit bf6b738baa
50 changed files with 551 additions and 213 deletions
+19 -2
View File
@@ -677,6 +677,23 @@ class LedgerAndApplicationTests(unittest.TestCase):
self.application.deepseek.complete.assert_called_once()
self.application.deliver.assert_called_once()
def test_semantic_duplicate_with_new_event_id_is_ignored(self):
first = self.application.accept(
{**event("event-1", "@JWeBot /金币"), "source": "rapidocr-local"}
)
duplicate = self.application.accept(
{**event("event-2", "@JWeBot /金币"), "source": "rapidocr-local"}
)
self.assertEqual(first["status"], "reply")
self.assertEqual(duplicate["status"], "ignored")
self.assertTrue(duplicate["duplicate"])
with self.application.ledger.lock:
count = self.application.ledger.connection.execute(
"SELECT COUNT(*) FROM events"
).fetchone()[0]
self.assertEqual(count, 1)
def test_failed_fast_ack_never_delivers_the_generated_reply(self):
self.application.deepseek.complete = mock.Mock(return_value="最终回复喵")
self.application.deliver = mock.Mock()
@@ -867,9 +884,9 @@ class LedgerAndApplicationTests(unittest.TestCase):
self.assertEqual(version["route"], "local")
self.assertIn("JWeBot 版本", version["text"])
self.assertIn("kernel", version["text"])
self.assertIn("最新版本:beta 1.0.2.1016", version["text"])
self.assertIn("最新版本:beta 1.0.3.1016", version["text"])
self.assertIn("更新时间:2026-10-16", version["text"])
self.assertIn("更新摘要:Cache OCR-negative", version["text"])
self.assertIn("更新摘要:Rename the virtual currency", version["text"])
self.assertEqual(debug["route"], "local")
self.assertIn("调试埋点", debug["text"])
self.assertEqual(balance["route"], "local")
+6 -6
View File
@@ -39,7 +39,7 @@ class Release061Tests(unittest.TestCase):
return json.loads(row[0])
def test_aliases_are_corresponding_and_case_insensitive(self):
pairs = [("上下文", "Context"), ("金币", "wallet"), ("抽奖", "draw"),
pairs = [("上下文", "Context"), ("摩拉", "wallet"), ("抽奖", "draw"),
("买彩票", "lottery"), ("21点", "blackjack"),
("人物列表", "people"), ("人物", "person"), ("游戏帮助", "games")]
for chinese, english in pairs:
@@ -82,18 +82,18 @@ class Release061Tests(unittest.TestCase):
self.assertEqual(ledger.search_repeat_history("group-a", "好"), [])
def test_initial_funds_aliases_groups_and_replay(self):
self.assertIn("金币 1000", self.command("/wallet", event_id="wallet-1"))
self.assertIn("摩拉 1000", self.command("/wallet", event_id="wallet-1"))
with mock.patch.object(self.local.players.rng, "randrange", return_value=99):
first = self.command("/draw 10", event_id="draw-1")
replay = self.command("/draw 10", sender="a1", event_id="draw-1")
self.assertEqual(first, replay)
self.assertEqual(self.state("a1")["balance"], 990)
self.assertIn("金币 1000", self.command("/wallet", sender="Bob"))
self.assertIn("金币 1000", self.command("/wallet", group="group-b"))
self.assertIn("摩拉 1000", self.command("/wallet", sender="Bob"))
self.assertIn("摩拉 1000", self.command("/wallet", group="group-b"))
self.engine.ledger.connection.close()
self.engine = MODULE.EdgeApplication(config(self.root))
self.local = self.engine.local
self.assertIn("金币 990", self.command("/wallet", event_id="restart"))
self.assertIn("摩拉 990", self.command("/wallet", event_id="restart"))
def test_concurrent_replay_debits_once_and_rolls_back_failure(self):
replies = []
@@ -123,7 +123,7 @@ class Release061Tests(unittest.TestCase):
with mock.patch.object(self.local.players.rng, "randrange", return_value=99):
for _ in range(10):
self.command("/draw 100")
self.assertIn("金币不足", self.command("/draw 2"))
self.assertIn("摩拉不足", self.command("/draw 2"))
self.assertEqual(self.state()["balance"], 0)
def test_blackjack_is_private_persistent_and_settles_exactly_once(self):
+2 -2
View File
@@ -119,8 +119,8 @@ class Release062BlackjackTests(unittest.TestCase):
self.assertIn("分两步", reply)
self.assertIn("100 是本局下注", reply)
self.assertIn("再单独发送", reply)
wallet = self.command("/金币")
self.assertIn("金币 1000", wallet)
wallet = self.command("/摩拉")
self.assertIn("摩拉 1000", wallet)
self.assertIn("无进行中的牌局", wallet)
+44 -23
View File
@@ -215,9 +215,9 @@ class Release063GameTests(unittest.TestCase):
settled = self.command("/21点 要牌", sender="Bob")
self.assertIn("这一桌每个人的变化", settled)
self.assertIn("alice #1:你赢了", settled)
self.assertIn("下注 200,返还 400,净变化 +200;金币 1200", settled)
self.assertIn("下注 200,返还 400,净变化 +200;摩拉 1200", settled)
self.assertIn("bob #1:你输了", settled)
self.assertIn("下注 100,返还 0,净变化 -100;金币 900", settled)
self.assertIn("下注 100,返还 0,净变化 -100;摩拉 900", settled)
self.assertEqual(self.state()["balance"], 1200)
self.assertEqual(self.state("Bob")["balance"], 900)
self.assertIn("alice #1:21点 你赢了", self.command("/游戏记录"))
@@ -225,6 +225,11 @@ class Release063GameTests(unittest.TestCase):
"bob #1:21点 你输了",
self.command("/游戏记录", sender="Bob"),
)
self.assertIn("+200:21点 获胜", self.command("/金币"))
self.assertIn(
"-100:21点 失败",
self.command("/金币", sender="Bob"),
)
def test_prize_draw_has_indexed_history(self):
with mock.patch.object(
@@ -242,12 +247,12 @@ class Release063GameTests(unittest.TestCase):
self.assertIn("alice #1:抽奖 未中奖", history)
def test_transfer_requires_initialized_target_and_replays_once(self):
self.assertIn("金币 1000", self.command("/金币"))
self.assertIn("摩拉 1000", self.command("/摩拉"))
missing = self.command("/转账 Bob 100", event_id="missing-target")
self.assertIn("bob 还没有金币账户", missing)
self.assertIn("bob 还没有摩拉账户", missing)
self.assertEqual(self.state()["balance"], 1000)
self.assertIn("金币 1000", self.command("/金币", sender="Bob"))
self.assertIn("摩拉 1000", self.command("/金币", sender="Bob"))
first = self.command("/转账 Bob 250", event_id="transfer-1")
replay = self.command(
"/转账 Bob 250",
@@ -256,40 +261,56 @@ class Release063GameTests(unittest.TestCase):
)
self.assertEqual(replay, first)
self.assertIn("alice:已向 bob 转账 250 金币", first)
self.assertIn("alice:已向 bob 转账 250 摩拉", first)
self.assertEqual(self.state()["balance"], 750)
self.assertEqual(self.state("Bob")["balance"], 1250)
alice_wallet = self.command("/金币", event_id="wallet-after-transfer-a")
bob_wallet = self.command(
"/金币",
sender="Bob",
event_id="wallet-after-transfer-b",
)
self.assertIn("alice:当前摩拉 750;无贷款;", alice_wallet)
self.assertIn("最近10次账户变动:", alice_wallet)
self.assertIn("-250:转账给 bob", alice_wallet)
self.assertIn("+1000:首次初始化", alice_wallet)
self.assertIn("bob:当前摩拉 1250;无贷款;", bob_wallet)
self.assertIn("+250:转账 来自 alice", bob_wallet)
def test_transfer_rejects_invalid_self_and_insufficient_cases(self):
self.assertIn("金币 1000", self.command("/金币"))
self.assertIn("摩拉 1000", self.command("/金币"))
self.assertIn("转账金额必须为 1~100000", self.command("/转账 Bob 0"))
self.assertIn("不能给自己转账", self.command("/转账 a1 100"))
self.assertIn("金币 1000", self.command("/金币", sender="Bob"))
self.assertIn("金币不足", self.command("/transfer 1001 Bob"))
self.assertIn("摩拉 1000", self.command("/金币", sender="Bob"))
self.assertIn("摩拉不足", self.command("/transfer 1001 Bob"))
self.assertEqual(self.state()["balance"], 1000)
self.assertEqual(self.state("Bob")["balance"], 1000)
def test_loan_repay_and_replay_once(self):
self.assertIn("金币 1000", self.command("/金币"))
self.assertIn("摩拉 1000", self.command("/金币"))
first = self.command("/贷款 300", event_id="loan-1")
replay = self.command("/贷款 300", event_id="loan-1")
self.assertEqual(replay, first)
self.assertIn("贷款 300 金币已到账", first)
self.assertIn("贷款 300 摩拉已到账", first)
self.assertEqual(self.state()["balance"], 1300)
self.assertEqual(self.state()["loan"]["principal"], 300)
self.assertIn("未还贷款 300", self.command("/金币"))
self.assertIn("已有未还贷款 300", self.command("/贷款 100"))
partial = self.command("/还款 120")
self.assertIn("已还款 120 金币", partial)
self.assertIn("已还款 120 摩拉", partial)
self.assertEqual(self.state()["balance"], 1180)
self.assertEqual(self.state()["loan"]["principal"], 180)
done = self.command("/还款 全部")
self.assertIn("已还清贷款 180 金币", done)
self.assertIn("已还清贷款 180 摩拉", done)
self.assertEqual(self.state()["balance"], 1000)
self.assertIsNone(self.state()["loan"])
wallet = self.command("/金币", event_id="wallet-after-repay")
self.assertIn("+300:贷款到账", wallet)
self.assertIn("-120:还款", wallet)
self.assertIn("-180:还款", wallet)
def test_loan_rejects_invalid_amounts_and_empty_repay(self):
self.assertIn("贷款金额必须为 1~500", self.command("/贷款 0"))
@@ -297,20 +318,20 @@ class Release063GameTests(unittest.TestCase):
self.assertIn("当前没有未还贷款", self.command("/还款 全部"))
def test_coin_ranking_lists_accounts_by_balance(self):
self.assertIn("金币 1000", self.command("/金币"))
self.assertIn("金币 1000", self.command("/金币", sender="Bob"))
self.assertIn("金币 1000", self.command("/金币", sender="Carol"))
self.assertIn("摩拉 1000", self.command("/金币"))
self.assertIn("摩拉 1000", self.command("/金币", sender="Bob"))
self.assertIn("摩拉 1000", self.command("/金币", sender="Carol"))
self.command("/转账 Bob 250")
self.command("/贷款 100", sender="Carol")
ranking = self.command("/大富翁榜")
self.assertIn("大富翁榜:", ranking)
self.assertIn("1. bob 1250 金币", ranking)
self.assertIn("2. carol 1100 金币(未还贷款 100)", ranking)
self.assertIn("3. alice 750 金币", ranking)
self.assertIn("1. bob 1250 摩拉", ranking)
self.assertIn("2. carol 1100 摩拉(未还贷款 100)", ranking)
self.assertIn("3. alice 750 摩拉", ranking)
self.assertNotIn("Dave", ranking)
self.assertIn("金币排行人数必须为 1~20", self.command("/金币排行 0"))
self.assertIn("摩拉排行人数必须为 1~20", self.command("/摩拉排行 0"))
def test_taffy_coin_stats_sums_all_game_history(self):
with mock.patch.object(
@@ -326,14 +347,14 @@ class Release063GameTests(unittest.TestCase):
):
self.command("/抽奖10", sender="Bob")
stats = self.command("/Taffy金币")
stats = self.command("/Taffy摩拉")
self.assertIn("玩家总下注 20,总返还 50", stats)
self.assertIn("Taffy累计倒贴 30 金币", stats)
self.assertIn("Taffy累计倒贴 30 摩拉", stats)
self.assertIn("最大上贡者排行(含负数):", stats)
self.assertIn("alice:上贡 10", stats)
self.assertIn("bob:从Taffy赢走 40", stats)
self.assertIn("Taffy金币人数必须为 1~20", self.command("/Taffy金币 0"))
self.assertIn("Taffy摩拉人数必须为 1~20", self.command("/Taffy摩拉 0"))
def test_open_round_and_history_survive_restart(self):
root = Path(self.temp.name)
+25
View File
@@ -423,6 +423,31 @@ class LayoutScalingTests(unittest.TestCase):
fast_ack_sender.assert_called_once()
legacy.assert_not_called()
def test_ignored_duplicate_does_not_fall_back(self):
legacy = mock.Mock(return_value="legacy")
response = {
"status": "ignored",
"route": "local",
"execution_path": "local_command",
"duplicate": True,
}
with mock.patch.object(
MODULE.jwebot_edge,
"request_edge",
return_value=response,
):
reply = MODULE.edge_router_reply(
{
"event_id": "event-2",
"group_fingerprint": "group-a",
"text": "@JWeBot /金币",
},
legacy_router=legacy,
)
self.assertEqual(reply, "NO_REPLY")
legacy.assert_not_called()
def test_hash_command_uses_legacy_router_when_edge_is_unavailable(self):
legacy = mock.Mock(return_value="legacy")
with mock.patch.object(