feat: confirm training reset and polish bot messages
This commit is contained in:
1 parent
92771c9d9f
commit
1dffdcf32d
7 files changed
+236
-59
No files matched your search
+17
-3
@@ -51,10 +51,24 @@ class KeyboardLayoutTests(unittest.TestCase):
|
||||
self.assertEqual(labels, set(keyboards.MODE_BUTTONS))
|
||||
self.assertNotIn("🏠 Лобби", labels)
|
||||
|
||||
def test_each_mode_keyboard_has_only_main_menu_button(self):
|
||||
def test_each_mode_keyboard_shows_expected_actions(self):
|
||||
for mode in bot.MODE_STATES:
|
||||
labels = [b.text for row in keyboards.mode_keyboard(mode).keyboard for b in row]
|
||||
self.assertEqual(labels, [keyboards.MAIN_MENU_BUTTON], mode)
|
||||
expected = [keyboards.MAIN_MENU_BUTTON]
|
||||
if mode == "training":
|
||||
expected.append(keyboards.TRAINING_RESET_BUTTON)
|
||||
self.assertEqual(labels, expected, mode)
|
||||
|
||||
def test_mode_entry_copy_uses_short_paragraphs(self):
|
||||
self.assertIn("\n", bot.MODE_COPY["training"][1])
|
||||
self.assertIn("\n", bot.MODE_COPY["generation"][1])
|
||||
self.assertIn("\n", bot.MODE_COPY["subscription"][1])
|
||||
|
||||
def test_training_reset_button_routes_to_confirmation_action(self):
|
||||
self.assertEqual(
|
||||
keyboards.resolve_button_text(keyboards.TRAINING_RESET_BUTTON),
|
||||
keyboards.TRAINING_RESET_VALUE,
|
||||
)
|
||||
|
||||
def test_every_button_is_resolvable(self):
|
||||
markups = [keyboards.main_menu_keyboard()]
|
||||
@@ -165,7 +179,7 @@ class ModeRoutingTests(unittest.IsolatedAsyncioTestCase):
|
||||
self.assertEqual(await state.get_state(), bot.BotMode.subscription.state)
|
||||
self.assertEqual(len(message.answers), 2)
|
||||
mode_keyboard = message.answers[0][1]["reply_markup"]
|
||||
self.assertEqual(mode_keyboard.keyboard[0][0].text, keyboards.MAIN_MENU_BUTTON)
|
||||
self.assertEqual(message.answers[0][1]["reply_markup"].keyboard[0][0].text, keyboards.MAIN_MENU_BUTTON)
|
||||
self.assertIn("Подписка не активирована", message.answers[1][0])
|
||||
renew_keyboard = message.answers[1][1]["reply_markup"]
|
||||
self.assertEqual(
|
||||
|
||||
+102
-27
@@ -386,6 +386,7 @@ class ScreenshotToneWorkflowTests(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(bot, "add_post_samples", return_value=["Старый пост", "Новый пост"]) as add_samples,
|
||||
patch.object(bot, "create_tone_profile", new=AsyncMock(return_value="Обновлённый стиль")) as create_profile,
|
||||
patch.object(bot, "save_tone_of_voice") as save_tone,
|
||||
patch.object(bot, "send_rich_message", new=AsyncMock()) as send_rich,
|
||||
patch.object(bot.asyncio, "to_thread", side_effect=run_in_thread),
|
||||
):
|
||||
await bot._process_image_group([message], AsyncMock())
|
||||
@@ -395,8 +396,9 @@ class ScreenshotToneWorkflowTests(unittest.IsolatedAsyncioTestCase):
|
||||
["Старый пост", "Новый пост"], "Старый профиль", user_id=7
|
||||
)
|
||||
save_tone.assert_called_once_with(7, "Обновлённый стиль")
|
||||
self.assertIn("только правила стиля", message.answer.await_args.args[0])
|
||||
self.assertIn("не переписывались", message.answer.await_args.args[0])
|
||||
self.assertIn("Стиль обновлён", str(send_rich.await_args))
|
||||
self.assertIn("не переписывались", str(send_rich.await_args))
|
||||
self.assertIn("обновления стиля", str(send_rich.await_args))
|
||||
|
||||
|
||||
async def test_clear_command_resets_only_calling_user_profile_and_samples(self):
|
||||
@@ -424,11 +426,15 @@ class ScreenshotToneWorkflowTests(unittest.IsolatedAsyncioTestCase):
|
||||
patch.object(bot.asyncio, "to_thread", side_effect=run_in_thread),
|
||||
):
|
||||
await bot.clear_command(message, _StubState("training"))
|
||||
clear_data.assert_called_once_with(25)
|
||||
msg_calls = [c.args[0] for c in message.answer.call_args_list]
|
||||
self.assertTrue(any("API-логи" in m for m in msg_calls))
|
||||
self.assertTrue(any("скриншоты" in m for m in msg_calls))
|
||||
self.assertTrue(any("Подписка и триал не изменены" in m for m in msg_calls))
|
||||
self.assertEqual(message.answer.await_count, 1)
|
||||
warning, kwargs = message.answer.await_args.args[0], message.answer.await_args.kwargs
|
||||
self.assertIn("Сбросить данные обучения?", warning)
|
||||
self.assertIn("Подписка, триал", warning)
|
||||
buttons = kwargs["reply_markup"].inline_keyboard[0]
|
||||
self.assertEqual([button.text for button in buttons], ["✅ Подтвердить", "↩️ Отмена"])
|
||||
self.assertEqual(buttons[0].callback_data, "training:reset:confirm:25")
|
||||
self.assertEqual(buttons[1].callback_data, "training:reset:cancel:25")
|
||||
clear_data.assert_not_called()
|
||||
|
||||
|
||||
class StorageTests(unittest.TestCase):
|
||||
@@ -698,34 +704,103 @@ class SubscriptionTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_clear_preserves_subscription(self):
|
||||
user_store.ensure_subscription(105, self.db_path)
|
||||
user_store.activate_subscription(105, 1, self.db_path)
|
||||
message = SimpleNamespace(
|
||||
from_user=SimpleNamespace(id=105),
|
||||
user_store.save_tone_of_voice(105, "test style", self.db_path)
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
from aiogram.types import Chat, Message
|
||||
|
||||
message_bot = AsyncMock()
|
||||
message = Message(message_id=10, date=datetime.now(), chat=Chat(id=555, type="private")).as_(message_bot)
|
||||
state = _StubState("training")
|
||||
async def run_in_thread(function, *args, **kwargs):
|
||||
return function(*args, **kwargs)
|
||||
|
||||
with patch.object(bot.asyncio, "to_thread", side_effect=run_in_thread):
|
||||
await bot.clear_command(message, cast(Any, state))
|
||||
callback = SimpleNamespace(
|
||||
data="training:reset:confirm:105",
|
||||
from_user=SimpleNamespace(id=105),
|
||||
message=message,
|
||||
answer=AsyncMock(),
|
||||
)
|
||||
await bot.confirm_training_reset(callback, state)
|
||||
|
||||
self.assertEqual(user_store.get_tone_of_voice(105, self.db_path), "")
|
||||
sub = user_store.subscription_row(105, self.db_path)
|
||||
self.assertEqual(sub["is_active"], 1)
|
||||
|
||||
async def test_training_reset_cancel_does_not_clear_data(self):
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
from aiogram.types import Chat, Message
|
||||
|
||||
message_bot = AsyncMock()
|
||||
message = Message(message_id=1, date=datetime.now(), chat=Chat(id=555, type="private")).as_(message_bot)
|
||||
callback = SimpleNamespace(
|
||||
data="training:reset:cancel:107",
|
||||
from_user=SimpleNamespace(id=107),
|
||||
message=message,
|
||||
answer=AsyncMock(),
|
||||
)
|
||||
with patch.object(bot, "clear_user_data") as clear_data:
|
||||
await bot.confirm_training_reset(callback, _StubState("training"))
|
||||
clear_data.assert_not_called()
|
||||
callback.answer.assert_awaited_once_with()
|
||||
message_bot.assert_awaited_once()
|
||||
self.assertIn("Сброс отменён", str(message_bot.await_args))
|
||||
|
||||
async def test_training_reset_confirm_requires_owner_and_training_mode(self):
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
from aiogram.types import Chat, Message
|
||||
|
||||
message = Message(message_id=1, date=datetime.now(), chat=Chat(id=555, type="private")).as_(AsyncMock())
|
||||
callback = SimpleNamespace(
|
||||
data="training:reset:confirm:108",
|
||||
from_user=SimpleNamespace(id=999),
|
||||
message=message,
|
||||
answer=AsyncMock(),
|
||||
)
|
||||
with patch.object(bot, "clear_user_data") as clear_data:
|
||||
await bot.confirm_training_reset(callback, _StubState("training"))
|
||||
clear_data.assert_not_called()
|
||||
callback.answer.assert_awaited_once()
|
||||
self.assertIn("другому пользователю", callback.answer.await_args.args[0])
|
||||
|
||||
callback.from_user.id = 108
|
||||
callback.answer.reset_mock()
|
||||
with patch.object(bot, "clear_user_data") as clear_data:
|
||||
await bot.confirm_training_reset(callback, _StubState("generation"))
|
||||
clear_data.assert_not_called()
|
||||
self.assertIn("только в режиме", callback.answer.await_args.args[0])
|
||||
|
||||
async def test_clear_preserves_trial(self):
|
||||
user_store.ensure_subscription(106, self.db_path)
|
||||
user_store.save_tone_of_voice(106, "test style", self.db_path)
|
||||
before = user_store.subscription_row(106, self.db_path)
|
||||
assert before is not None
|
||||
from datetime import datetime
|
||||
from types import SimpleNamespace
|
||||
from aiogram.types import Chat, Message
|
||||
|
||||
message_bot = AsyncMock()
|
||||
message = Message(message_id=10, date=datetime.now(), chat=Chat(id=555, type="private")).as_(message_bot)
|
||||
state = _StubState("training")
|
||||
|
||||
async def run_in_thread(function, *args, **kwargs):
|
||||
return function(*args, **kwargs)
|
||||
|
||||
with patch.object(bot.asyncio, "to_thread", side_effect=run_in_thread):
|
||||
await bot.clear_command(message, _StubState("training"))
|
||||
|
||||
msg_calls = [c.args[0] for c in message.answer.call_args_list]
|
||||
self.assertTrue(any("Подписка и триал не изменены" in msg for msg in msg_calls))
|
||||
sub = user_store.subscription_row(105, self.db_path)
|
||||
self.assertEqual(sub["is_active"], 1)
|
||||
|
||||
async def test_clear_preserves_trial(self):
|
||||
user_store.ensure_subscription(106, self.db_path)
|
||||
before = user_store.subscription_row(106, self.db_path)
|
||||
assert before is not None
|
||||
message = SimpleNamespace(
|
||||
from_user=SimpleNamespace(id=106),
|
||||
answer=AsyncMock(),
|
||||
)
|
||||
|
||||
with patch.object(bot.asyncio, "to_thread", side_effect=lambda f, *a: f(*a)):
|
||||
await bot.clear_command(cast(Any, message), cast(Any, _StubState("training")))
|
||||
await bot.clear_command(cast(Any, message), cast(Any, state))
|
||||
callback = SimpleNamespace(
|
||||
data="training:reset:confirm:106",
|
||||
from_user=SimpleNamespace(id=106),
|
||||
message=message,
|
||||
answer=AsyncMock(),
|
||||
)
|
||||
await bot.confirm_training_reset(callback, cast(Any, state))
|
||||
|
||||
self.assertEqual(user_store.get_tone_of_voice(106, self.db_path), "")
|
||||
after = user_store.subscription_row(106, self.db_path)
|
||||
assert after is not None
|
||||
self.assertEqual(after["is_active"], before["is_active"])
|
||||
|
||||
Reference in new issue
Block a user