Добавление промо кода по запросу

main
pilot 2 months ago
parent bb166bbae3
commit 37007e6caf

@ -3,10 +3,25 @@ import json
import re import re
import shutil import shutil
import subprocess import subprocess
import zipfile
from collections import defaultdict from collections import defaultdict
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
# ---------- ЦВЕТА ДЛЯ КОНСОЛИ (НЕЙТРАЛЬНАЯ ПАЛИТРА) ----------
COLOR_RESET = "\033[0m"
COLOR_WHITE = "\033[97m" # белый (для заголовков)
COLOR_LIGHT_GRAY = "\033[37m" # светло-серый (для меню)
COLOR_GRAY = "\033[90m" # тёмно-серый (для информации)
COLOR_YELLOW = "\033[93m" # жёлтый (для ввода/предупреждений)
COLOR_GREEN = "\033[32m" # обычный зелёный (успех)
COLOR_RED = "\033[31m" # красный (ошибки)
COLOR_CYAN = "\033[36m" # голубой (акценты, откат)
COLOR_BOLD = "\033[1m"
def color_text(text, color=COLOR_RESET):
return f"{color}{text}{COLOR_RESET}"
# ---------- Функции сбора проекта ---------- # ---------- Функции сбора проекта ----------
def collect_project_files(root_dir, extensions=('.py', '.html'), def collect_project_files(root_dir, extensions=('.py', '.html'),
exclude_dirs=('venv', '__pycache__', '.git', 'env', 'node_modules')): exclude_dirs=('venv', '__pycache__', '.git', 'env', 'node_modules')):
@ -146,11 +161,101 @@ def extract_json_from_text(text):
return None return None
# ---------- Применение изменений ---------- # ---------- Полное резервное копирование проекта ----------
def apply_changes(project_root, actions): def create_full_backup(project_root, ai_response_dir):
"""Создаёт ZIP-архив всего проекта (исключая папку _ai_response)."""
backup_full_dir = os.path.join(ai_response_dir, 'backup', 'full')
os.makedirs(backup_full_dir, exist_ok=True)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
zip_filename = f"project_backup_{timestamp}.zip"
zip_path = os.path.join(backup_full_dir, zip_filename)
ignore_dir = os.path.basename(ai_response_dir)
with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, dirs, files in os.walk(project_root):
if ignore_dir in root.split(os.sep):
continue
for file in files:
file_path = os.path.join(root, file)
arcname = os.path.relpath(file_path, project_root)
zipf.write(file_path, arcname)
return zip_path
def list_full_backups(ai_response_dir):
backup_full_dir = os.path.join(ai_response_dir, 'backup', 'full')
if not os.path.exists(backup_full_dir):
return []
backups = []
for f in os.listdir(backup_full_dir):
if f.startswith('project_backup_') and f.endswith('.zip'):
full_path = os.path.join(backup_full_dir, f)
try:
time_str = f.replace('project_backup_', '').replace('.zip', '')
dt = datetime.strptime(time_str, "%Y%m%d_%H%M%S")
except:
dt = datetime.fromtimestamp(os.path.getmtime(full_path))
backups.append((full_path, dt, f))
backups.sort(key=lambda x: x[1], reverse=True)
return backups
def restore_full_backup(project_root, backup_path):
for item in os.listdir(project_root):
if item == os.path.basename(os.path.join(project_root, '_ai_response')):
continue
item_path = os.path.join(project_root, item)
if os.path.isfile(item_path):
os.remove(item_path)
else:
shutil.rmtree(item_path)
with zipfile.ZipFile(backup_path, 'r') as zipf:
zipf.extractall(project_root)
return True
# ---------- Бэкапирование отдельных файлов (для отката) ----------
def backup_file(abs_path, ai_response_dir):
backup_root = os.path.join(ai_response_dir, 'backup')
os.makedirs(backup_root, exist_ok=True)
project_root = os.getcwd()
rel_path = os.path.relpath(abs_path, project_root)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
backup_rel = f"{rel_path}.{timestamp}.bak"
backup_abs = os.path.join(backup_root, backup_rel)
os.makedirs(os.path.dirname(backup_abs), exist_ok=True)
shutil.copy2(abs_path, backup_abs)
return backup_abs
# ---------- Применение изменений (с бэкапами и полным архивом) ----------
def apply_changes(project_root, actions, ai_response_dir, user_prompt=''):
reports = [] reports = []
errors = False errors = False
project_root = os.path.abspath(project_root) project_root = os.path.abspath(project_root)
# Добавляем запрос пользователя в отчёт
if user_prompt:
reports.append(f"📝 Запрос пользователя: {user_prompt}")
# Создаём полный бэкап проекта
try:
backup_zip = create_full_backup(project_root, ai_response_dir)
reports.append(f"📦 Полный бэкап проекта сохранён: {backup_zip}")
except Exception as e:
reports.append(f"❌ Ошибка при создании полного бэкапа: {e}")
errors = True
# Сохраняем данные о применении (включая запрос)
last_apply_file = os.path.join(ai_response_dir, 'last_apply.json')
apply_data = {
'user_prompt': user_prompt,
'actions': actions,
'timestamp': datetime.now().isoformat()
}
with open(last_apply_file, 'w', encoding='utf-8') as f:
json.dump(apply_data, f, ensure_ascii=False, indent=2)
for action in actions: for action in actions:
action_type = action.get('action') action_type = action.get('action')
file_path = action.get('file_path') file_path = action.get('file_path')
@ -176,11 +281,14 @@ def apply_changes(project_root, actions):
if not os.path.exists(abs_path): if not os.path.exists(abs_path):
reports.append(f"⚠️ Файл {file_path} не существует, создаём новый (как create)") reports.append(f"⚠️ Файл {file_path} не существует, создаём новый (как create)")
os.makedirs(os.path.dirname(abs_path), exist_ok=True) os.makedirs(os.path.dirname(abs_path), exist_ok=True)
else:
backup_file(abs_path, ai_response_dir)
with open(abs_path, 'w', encoding='utf-8') as f: with open(abs_path, 'w', encoding='utf-8') as f:
f.write(content) f.write(content)
reports.append(f"✅ Обновлён: {file_path}{description}") reports.append(f"✅ Обновлён: {file_path}{description}")
elif action_type == 'delete': elif action_type == 'delete':
if os.path.exists(abs_path): if os.path.exists(abs_path):
backup_file(abs_path, ai_response_dir)
os.remove(abs_path) os.remove(abs_path)
reports.append(f"✅ Удалён: {file_path}{description}") reports.append(f"✅ Удалён: {file_path}{description}")
else: else:
@ -194,6 +302,85 @@ def apply_changes(project_root, actions):
return reports, errors return reports, errors
# ---------- Откат изменений (только последнего набора) ----------
def rollback_last_apply(project_root, ai_response_dir):
reports = []
errors = False
last_apply_file = os.path.join(ai_response_dir, 'last_apply.json')
if not os.path.exists(last_apply_file):
reports.append("❌ Нет сохранённых действий для отката (файл last_apply.json не найден).")
return reports, True
try:
with open(last_apply_file, 'r', encoding='utf-8') as f:
data = json.load(f)
actions = data.get('actions', [])
except Exception as e:
reports.append(f"❌ Ошибка чтения last_apply.json: {e}")
return reports, True
for action in reversed(actions):
action_type = action.get('action')
file_path = action.get('file_path')
description = action.get('description', '')
if not action_type or not file_path:
continue
norm_path = os.path.normpath(file_path.replace('/', os.sep))
abs_path = os.path.join(project_root, norm_path)
if not os.path.abspath(abs_path).startswith(project_root):
reports.append(f"❌ Опасный путь: {file_path} — выход за пределы проекта")
errors = True
continue
try:
if action_type == 'create':
if os.path.exists(abs_path):
os.remove(abs_path)
reports.append(f"🗑️ Удалён (откат create): {file_path}{description}")
else:
reports.append(f"⚠️ Файл {file_path} не найден, пропускаем удаление (откат create)")
elif action_type == 'update':
backup_root = os.path.join(ai_response_dir, 'backup')
backup_files = []
for root, dirs, files in os.walk(backup_root):
for f in files:
if f.startswith(norm_path + '.') and f.endswith('.bak'):
full = os.path.join(root, f)
backup_files.append((full, os.path.getmtime(full)))
if backup_files:
backup_files.sort(key=lambda x: x[1], reverse=True)
latest_backup = backup_files[0][0]
shutil.copy2(latest_backup, abs_path)
reports.append(f"♻️ Восстановлен (откат update): {file_path} — из {os.path.basename(latest_backup)}")
else:
reports.append(f"Не найден бэкап для {file_path}, пропускаем (откат update)")
errors = True
elif action_type == 'delete':
backup_root = os.path.join(ai_response_dir, 'backup')
backup_files = []
for root, dirs, files in os.walk(backup_root):
for f in files:
if f.startswith(norm_path + '.') and f.endswith('.bak'):
full = os.path.join(root, f)
backup_files.append((full, os.path.getmtime(full)))
if backup_files:
backup_files.sort(key=lambda x: x[1], reverse=True)
latest_backup = backup_files[0][0]
os.makedirs(os.path.dirname(abs_path), exist_ok=True)
shutil.copy2(latest_backup, abs_path)
reports.append(f"♻️ Восстановлен (откат delete): {file_path} — из {os.path.basename(latest_backup)}")
else:
reports.append(f"Не найден бэкап для {file_path}, пропускаем (откат delete)")
errors = True
else:
reports.append(f"❌ Неизвестное действие '{action_type}' для {file_path} (откат)")
errors = True
except Exception as e:
reports.append(f"❌ Ошибка при откате {file_path}: {e}")
errors = True
return reports, errors
# ---------- Вспомогательные функции ---------- # ---------- Вспомогательные функции ----------
def get_downloads_folder(): def get_downloads_folder():
home = Path.home() home = Path.home()
@ -246,7 +433,7 @@ def run_tests(project_root, test_path=None):
return False, reports, str(e) return False, reports, str(e)
# ---------- Логирование в суточный файл (только для APPLY и кратких записей о тестах) ---------- # ---------- Логирование в суточный файл ----------
def write_log_entry(log_file_path, entry_type, lines, extra_info=''): def write_log_entry(log_file_path, entry_type, lines, extra_info=''):
with open(log_file_path, 'a', encoding='utf-8') as f: with open(log_file_path, 'a', encoding='utf-8') as f:
f.write("=" * 80 + "\n") f.write("=" * 80 + "\n")
@ -260,155 +447,252 @@ def write_log_entry(log_file_path, entry_type, lines, extra_info=''):
f.write("=" * 80 + "\n\n") f.write("=" * 80 + "\n\n")
# ---------- ОСНОВНАЯ ФУНКЦИЯ ---------- # ---------- ОСНОВНАЯ ФУНКЦИЯ (ЦИКЛ) ----------
def main(): def main():
print("=" * 60)
print(" Инструмент для работы с DeepSeek")
print("=" * 60)
mode = input(
"Выберите режим:\n"
" 1 — Создать промт (собрать проект и инструкцию)\n"
" 2 — Применить ответ DeepSeek (автоматически найти JSON в Downloads)\n"
" 3 — Запустить тесты (все или указать папку/файл)\n"
"По умолчанию (Enter) — режим 1\n"
"Ваш выбор: "
).strip()
project_root = os.getcwd() project_root = os.getcwd()
ai_response_dir = os.path.join(project_root, '_ai_response') ai_response_dir = os.path.join(project_root, '_ai_response')
os.makedirs(ai_response_dir, exist_ok=True) os.makedirs(ai_response_dir, exist_ok=True)
# Суточный лог-файл (только для изменений и кратких записей о тестах) print(color_text("=" * 60, COLOR_WHITE + COLOR_BOLD))
today = datetime.now().strftime('%Y-%m-%d') print(color_text(" Инструмент для работы с DeepSeek", COLOR_WHITE + COLOR_BOLD))
log_file = os.path.join(ai_response_dir, f'ai_log_{today}.txt') print(color_text("=" * 60, COLOR_WHITE + COLOR_BOLD))
while True:
print("\n" + color_text("Выберите режим:", COLOR_LIGHT_GRAY + COLOR_BOLD))
print(color_text(" 0 — Выход", COLOR_GRAY))
print(color_text(" 1 — Создать промт (собрать проект и инструкцию)", COLOR_LIGHT_GRAY))
print(color_text(" 2 — Применить ответ DeepSeek (автоматически найти JSON в Downloads)", COLOR_LIGHT_GRAY))
print(color_text(" 3 — Запустить тесты (все или указать папку/файл)", COLOR_LIGHT_GRAY))
print(color_text(" 4 — Откатить последние изменения (восстановить из бэкапов файлов)", COLOR_LIGHT_GRAY))
print(color_text(" 5 — Восстановить проект из полного бэкапа (архива)", COLOR_LIGHT_GRAY))
mode = input(color_text("Ваш выбор (0-5): ", COLOR_LIGHT_GRAY + COLOR_BOLD)).strip()
if mode == '0':
print(color_text("Выход. До свидания!", COLOR_GREEN))
break
if mode == '1':
print(color_text("\nСбор информации о проекте для отправки в DeepSeek.", COLOR_LIGHT_GRAY))
main_prompt = input(color_text("Введите ваш промт (одна строка, Enter для завершения): ", COLOR_YELLOW)).strip()
if not main_prompt:
print(color_text("Промт не может быть пустым. Завершение.", COLOR_RED))
continue
# Сохраняем запрос пользователя для последующего логирования
prompt_file = os.path.join(ai_response_dir, 'last_user_prompt.txt')
try:
with open(prompt_file, 'w', encoding='utf-8') as f:
f.write(main_prompt)
except Exception as e:
print(color_text(f"⚠️ Не удалось сохранить запрос: {e}", COLOR_YELLOW))
print(color_text(f"\nСканируем папку: {project_root}", COLOR_GRAY))
files_data = collect_project_files(project_root)
full_prompt = build_prompt(main_prompt, files_data)
output_file = os.path.join(ai_response_dir, "ai_promt.txt")
try:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(full_prompt)
print(color_text(f"\n✅ Готово! Промт сохранён в файл: {output_file}", COLOR_GREEN))
print(color_text(f"Всего собрано файлов: {len(files_data)}", COLOR_GRAY))
print(color_text("\nТеперь скопируйте содержимое этого файла и отправьте в DeepSeek.", COLOR_YELLOW))
print(color_text("DeepSeek вернёт ответ с описанием и JSON-блоком.", COLOR_YELLOW))
print(color_text("Скопируйте весь ответ и сохраните в файл с именем deepseek_json_YYYYMMDD_HHMMSS.json в папке Downloads.", COLOR_YELLOW))
print(color_text("Затем запустите этот скрипт в режиме 2 для автоматического применения.", COLOR_YELLOW))
except Exception as e:
print(color_text(f"❌ Ошибка при сохранении: {e}", COLOR_RED))
elif mode == '2':
downloads_dir = get_downloads_folder()
json_file = find_latest_json_file(downloads_dir)
if json_file is None:
print(color_text(f"Не найден JSON-файл с префиксом 'deepseek_json_' в папке {downloads_dir}", COLOR_RED))
continue
print(color_text(f"📂 Найден файл: {json_file}", COLOR_GRAY))
try:
with open(json_file, 'r', encoding='utf-8') as f:
text = f.read()
except Exception as e:
print(color_text(f"❌ Ошибка чтения файла: {e}", COLOR_RED))
continue
data = extract_json_from_text(text)
if data is None:
print(color_text("Не удалось извлечь JSON из ответа. Убедитесь, что в ответе есть блок ```json ... ``` или валидный JSON.", COLOR_RED))
continue
actions = data.get('actions')
if actions is None:
print(color_text("В JSON отсутствует поле 'actions'.", COLOR_RED))
continue
if not isinstance(actions, list):
print(color_text("❌ Поле 'actions' должно быть массивом.", COLOR_RED))
continue
if len(actions) == 0:
print(color_text(" Нет действий для применения.", COLOR_YELLOW))
continue
# Читаем сохранённый запрос пользователя
user_prompt = ''
prompt_file = os.path.join(ai_response_dir, 'last_user_prompt.txt')
if os.path.exists(prompt_file):
try:
with open(prompt_file, 'r', encoding='utf-8') as f:
user_prompt = f.read().strip()
except Exception as e:
print(color_text(f"⚠️ Не удалось прочитать запрос пользователя: {e}", COLOR_YELLOW))
print(color_text(f"\n📁 Применяем изменения в: {project_root}", COLOR_LIGHT_GRAY))
reports, errors = apply_changes(project_root, actions, ai_response_dir, user_prompt)
dest_filename = f"applied_{os.path.basename(json_file)}"
dest_path = os.path.join(ai_response_dir, dest_filename)
shutil.move(json_file, dest_path)
move_info = f"JSON перемещён в {dest_path}"
today = datetime.now().strftime('%Y-%m-%d')
log_file = os.path.join(ai_response_dir, f'ai_log_{today}.txt')
write_log_entry(log_file, 'APPLY', reports, extra_info=move_info)
print(color_text("\n" + "=" * 60, COLOR_WHITE + COLOR_BOLD))
print(color_text("ОТЧЁТ О ВЫПОЛНЕННЫХ ДЕЙСТВИЯХ", COLOR_WHITE + COLOR_BOLD))
print(color_text("=" * 60, COLOR_WHITE + COLOR_BOLD))
for line in reports:
if line.startswith(""):
print(color_text(line, COLOR_GREEN))
elif line.startswith(""):
print(color_text(line, COLOR_RED))
elif line.startswith("⚠️"):
print(color_text(line, COLOR_YELLOW))
elif line.startswith("📦"):
print(color_text(line, COLOR_CYAN))
else:
print(line)
if mode == '2': if errors:
# ---- РЕЖИМ 2: Применение изменений (пишем в суточный лог) ---- print(color_text("\n❌ Были ошибки при применении изменений.", COLOR_RED))
downloads_dir = get_downloads_folder() else:
json_file = find_latest_json_file(downloads_dir) print(color_text("\nВсе изменения применены успешно.", COLOR_GREEN))
if json_file is None: print(color_text(f"\n📄 Лог изменений записан в: {log_file}", COLOR_GRAY))
print(f"Не найден JSON-файл с префиксом 'deepseek_json_' в папке {downloads_dir}")
return elif mode == '3':
test_path = input(color_text("Введите относительный путь к папке или файлу с тестами (оставьте пустым для всех тестов): ", COLOR_YELLOW)).strip()
if test_path == '':
test_path = None
print(color_text("\n🔍 Запуск тестов...", COLOR_LIGHT_GRAY))
success, reports, output = run_tests(project_root, test_path)
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
test_report_file = os.path.join(ai_response_dir, f"test_report_{timestamp}.txt")
with open(test_report_file, 'w', encoding='utf-8') as f:
f.write("=" * 60 + "\n")
f.write("ОТЧЁТ О ТЕСТИРОВАНИИ\n")
f.write(f"Дата: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"Путь тестов: {test_path if test_path else 'все тесты'}\n")
f.write("=" * 60 + "\n")
f.write(output)
today = datetime.now().strftime('%Y-%m-%d')
log_file = os.path.join(ai_response_dir, f'ai_log_{today}.txt')
brief_lines = [
f"Тесты запущены для пути: {test_path if test_path else 'все тесты'}",
f"Результат: {'✅ УСПЕШНО' if success else '❌ ОШИБКА'}",
f"Полный отчёт сохранён в: {test_report_file}"
]
write_log_entry(log_file, 'TEST', brief_lines, extra_info=f"Путь: {test_path if test_path else 'все тесты'}")
print(color_text("\n" + "=" * 60, COLOR_WHITE + COLOR_BOLD))
print(color_text("РЕЗУЛЬТАТ ТЕСТИРОВАНИЯ", COLOR_WHITE + COLOR_BOLD))
print(color_text("=" * 60, COLOR_WHITE + COLOR_BOLD))
for line in reports:
if line.startswith(""):
print(color_text(line, COLOR_GREEN))
elif line.startswith(""):
print(color_text(line, COLOR_RED))
else:
print(line)
print(f"📂 Найден файл: {json_file}") if success:
print(color_text("\n✅ Тесты прошли успешно.", COLOR_GREEN))
else:
print(color_text("\n❌ Тесты завершились с ошибками. Проверьте полный отчёт.", COLOR_RED))
print(color_text(f"\n📄 Полный отчёт сохранён в: {test_report_file}", COLOR_GRAY))
print(color_text(f"📄 Краткая запись добавлена в суточный лог: {log_file}", COLOR_GRAY))
elif mode == '4':
print(color_text("\n🔄 Откат последних применённых изменений...", COLOR_LIGHT_GRAY))
reports, errors = rollback_last_apply(project_root, ai_response_dir)
today = datetime.now().strftime('%Y-%m-%d')
log_file = os.path.join(ai_response_dir, f'ai_log_{today}.txt')
write_log_entry(log_file, 'ROLLBACK', reports, extra_info="Откат выполненных изменений")
print(color_text("\n" + "=" * 60, COLOR_WHITE + COLOR_BOLD))
print(color_text("ОТЧЁТ ОБ ОТКАТЕ", COLOR_WHITE + COLOR_BOLD))
print(color_text("=" * 60, COLOR_WHITE + COLOR_BOLD))
for line in reports:
if line.startswith(""):
print(color_text(line, COLOR_GREEN))
elif line.startswith(""):
print(color_text(line, COLOR_RED))
elif line.startswith("⚠️"):
print(color_text(line, COLOR_YELLOW))
elif line.startswith("🗑️") or line.startswith("♻️"):
print(color_text(line, COLOR_CYAN))
else:
print(line)
try: if errors:
with open(json_file, 'r', encoding='utf-8') as f: print(color_text("\n❌ Были ошибки при откате.", COLOR_RED))
text = f.read() else:
except Exception as e: print(color_text("\n✅ Откат выполнен успешно.", COLOR_GREEN))
print(f"❌ Ошибка чтения файла: {e}") print(color_text(f"\n📄 Лог отката записан в: {log_file}", COLOR_GRAY))
return
elif mode == '5':
data = extract_json_from_text(text) backups = list_full_backups(ai_response_dir)
if data is None: if not backups:
print("Не удалось извлечь JSON из ответа. Убедитесь, что в ответе есть блок ```json ... ``` или валидный JSON.") print(color_text("❌ Нет доступных полных бэкапов.", COLOR_RED))
return continue
actions = data.get('actions') print(color_text("\nДоступные полные бэкапы:", COLOR_LIGHT_GRAY))
if actions is None: for idx, (path, dt, name) in enumerate(backups, 1):
print("В JSON отсутствует поле 'actions'.") print(color_text(f" {idx}. {name} ({dt.strftime('%Y-%m-%d %H:%M:%S')})", COLOR_GRAY))
return
if not isinstance(actions, list): choice = input(color_text("Выберите номер бэкапа для восстановления (или 0 для отмены): ", COLOR_YELLOW)).strip()
print("❌ Поле 'actions' должно быть массивом.") if choice == '0':
return continue
if len(actions) == 0: try:
print(" Нет действий для применения.") idx = int(choice) - 1
return if idx < 0 or idx >= len(backups):
print(color_text("❌ Неверный номер.", COLOR_RED))
print(f"\n📁 Применяем изменения в: {project_root}") continue
reports, errors = apply_changes(project_root, actions) backup_path = backups[idx][0]
except ValueError:
# Перемещаем JSON-файл в _ai_response print(color_text("❌ Неверный ввод.", COLOR_RED))
dest_filename = f"applied_{os.path.basename(json_file)}" continue
dest_path = os.path.join(ai_response_dir, dest_filename)
shutil.move(json_file, dest_path) print(color_text(f"\n⚠️ Восстановление из бэкапа {os.path.basename(backup_path)} полностью заменит текущий проект.", COLOR_YELLOW))
move_info = f"JSON перемещён в {dest_path}" confirm = input(color_text("Вы уверены? (введите 'да' для подтверждения): ", COLOR_YELLOW)).strip().lower()
if confirm != 'да':
# Пишем в суточный лог print(color_text("Восстановление отменено.", COLOR_GRAY))
write_log_entry(log_file, 'APPLY', reports, extra_info=move_info) continue
# Вывод в консоль try:
print("\n" + "=" * 60) restore_full_backup(project_root, backup_path)
print("ОТЧЁТ О ВЫПОЛНЕННЫХ ДЕЙСТВИЯХ") print(color_text(f"\n✅ Проект восстановлен из бэкапа: {os.path.basename(backup_path)}", COLOR_GREEN))
print("=" * 60) today = datetime.now().strftime('%Y-%m-%d')
for line in reports: log_file = os.path.join(ai_response_dir, f'ai_log_{today}.txt')
print(line) write_log_entry(log_file, 'RESTORE_FULL', [f"Восстановлен проект из {os.path.basename(backup_path)}"], extra_info="Полное восстановление")
except Exception as e:
if errors: print(color_text(f"❌ Ошибка при восстановлении: {e}", COLOR_RED))
print("\n❌ Были ошибки при применении изменений.")
else:
print("\nВсе изменения применены успешно.")
print(f"\n📄 Лог изменений записан в: {log_file}")
elif mode == '3':
# ---- РЕЖИМ 3: Тестирование (отдельный файл + краткая запись в суточный лог) ----
test_path = input("Введите относительный путь к папке или файлу с тестами (оставьте пустым для всех тестов): ").strip()
if test_path == '':
test_path = None
print("\n🔍 Запуск тестов...")
success, reports, output = run_tests(project_root, test_path)
# Сохраняем полный вывод в отдельный файл
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
test_report_file = os.path.join(ai_response_dir, f"test_report_{timestamp}.txt")
with open(test_report_file, 'w', encoding='utf-8') as f:
f.write("=" * 60 + "\n")
f.write("ОТЧЁТ О ТЕСТИРОВАНИИ\n")
f.write(f"Дата: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n")
f.write(f"Путь тестов: {test_path if test_path else 'все тесты'}\n")
f.write("=" * 60 + "\n")
f.write(output)
# Пишем краткую запись в суточный лог
brief_lines = [
f"Тесты запущены для пути: {test_path if test_path else 'все тесты'}",
f"Результат: {'✅ УСПЕШНО' if success else '❌ ОШИБКА'}",
f"Полный отчёт сохранён в: {test_report_file}"
]
write_log_entry(log_file, 'TEST', brief_lines, extra_info=f"Путь: {test_path if test_path else 'все тесты'}")
# Вывод в консоль
print("\n" + "=" * 60)
print("РЕЗУЛЬТАТ ТЕСТИРОВАНИЯ")
print("=" * 60)
for line in reports:
print(line)
if success:
print("\n✅ Тесты прошли успешно.")
else: else:
print("\n❌ Тесты завершились с ошибками. Проверьте полный отчёт.") print(color_text("❌ Неверный выбор. Пожалуйста, введите число от 0 до 5.", COLOR_RED))
print(f"\n📄 Полный отчёт сохранён в: {test_report_file}")
print(f"📄 Краткая запись добавлена в суточный лог: {log_file}")
else:
# ---- РЕЖИМ 1: Создание промта (сохраняем в _ai_response) ----
print("\nСбор информации о проекте для отправки в DeepSeek.")
main_prompt = input("\nВведите ваш промт (одна строка, Enter для завершения): ").strip()
if not main_prompt:
print("Промт не может быть пустым. Завершение.")
return
print(f"\nСканируем папку: {project_root}")
files_data = collect_project_files(project_root)
full_prompt = build_prompt(main_prompt, files_data)
output_file = os.path.join(ai_response_dir, "ai_promt.txt")
try:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(full_prompt)
print(f"\n✅ Готово! Промт сохранён в файл: {output_file}")
print(f"Всего собрано файлов: {len(files_data)}")
print("\nТеперь скопируйте содержимое этого файла и отправьте в DeepSeek.")
print("DeepSeek вернёт ответ с описанием и JSON-блоком.")
print("Скопируйте весь ответ и сохраните в файл с именем deepseek_json_YYYYMMDD_HHMMSS.json в папке Downloads.")
print("Затем запустите этот скрипт в режиме 2 для автоматического применения.")
except Exception as e:
print(f"❌ Ошибка при сохранении: {e}")
if __name__ == "__main__": if __name__ == "__main__":
main() main()

@ -1,8 +1,9 @@
from django.urls import path from django.urls import path
from content_bot.views import api_default_load from content_bot.views import api_default_load, ui_menu_view
from max_bot.views import api_start_max_v1, iiko_webhook, iiko_send_message from max_bot.views import api_start_max_v1, iiko_webhook, iiko_send_message
urlpatterns = [ urlpatterns = [
path('default-load', api_default_load), path('default-load', api_default_load),
path('ui/', ui_menu_view, name='ui_menu'),
] ]

@ -1,6 +1,7 @@
from django.http import JsonResponse from django.http import JsonResponse
from django.core.handlers.wsgi import WSGIRequest from django.core.handlers.wsgi import WSGIRequest
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
from django.shortcuts import render
from max_bot.models import Client, ProductCategory, Product from max_bot.models import Client, ProductCategory, Product
@ -9,3 +10,10 @@ from max_bot.models import Client, ProductCategory, Product
def api_default_load(request: WSGIRequest): def api_default_load(request: WSGIRequest):
rt = {'success': True, 'data': ''} rt = {'success': True, 'data': ''}
return JsonResponse(rt, status=200) return JsonResponse(rt, status=200)
def ui_menu_view(request):
"""Отображает главную страницу MAX UI с двумя кнопками."""
# В будущем здесь можно будет получать данные клиента из сессии или параметров запроса
# Сейчас просто рендерим шаблон
return render(request, 'max_bot/ui_menu.html')

@ -8,7 +8,8 @@ from max_bot.models import Partner, Integration, Client, BonusTransaction, App,
from restoran_max_bot import settings from restoran_max_bot import settings
from restoran_max_bot.bot_started import bot_started from restoran_max_bot.bot_started import bot_started
from restoran_max_bot.bot_stopped import bot_stopped from restoran_max_bot.bot_stopped import bot_stopped
from restoran_max_bot.bot_message import bot_message, bot_promo from restoran_max_bot.bot_message import bot_message
from restoran_max_bot.promo_handlers import bot_promo
from restoran_max_bot.common import common_get_contact, common_check_registration from restoran_max_bot.common import common_get_contact, common_check_registration
from restoran_max_bot.settings import DEBUG from restoran_max_bot.settings import DEBUG
from restoran_max_bot.utils import is_json, has_key from restoran_max_bot.utils import is_json, has_key
@ -62,7 +63,6 @@ def api_decorator(func):
@csrf_exempt @csrf_exempt
@api_decorator @api_decorator
def api_start_max_v1(request, token, **kwargs): def api_start_max_v1(request, token, **kwargs):
# основной обработчик MAX-БОТ
data = json.loads(request.body.decode('utf-8')) data = json.loads(request.body.decode('utf-8'))
client = kwargs['client'] client = kwargs['client']
settings_max = kwargs['settings_max'] settings_max = kwargs['settings_max']
@ -83,9 +83,6 @@ def api_start_max_v1(request, token, **kwargs):
# start - запуск бота # start - запуск бота
if data['update_type'] == 'bot_started': if data['update_type'] == 'bot_started':
# https://dev.max.ru/docs/chatbots/bots-coding/prepare
# https://max.ru/id032606883607_2_bot?start=source_site
# роверка на промокод
if data.get("payload", None): if data.get("payload", None):
bot_promo(client=client, message=data, settings_max=settings_max) bot_promo(client=client, message=data, settings_max=settings_max)
if not common_check_registration(client=client, chat_id=data['chat_id']): if not common_check_registration(client=client, chat_id=data['chat_id']):

@ -1,6 +1,7 @@
# ai_handler.py # ai_handler.py
from ai_agent.api_common import get_ai_agent, call_ai_agent from ai_agent.api_common import get_ai_agent, call_ai_agent
from max_bot.models import Client from max_bot.models import Client
from restoran_max_bot.user_logger import log_user_action
def process_with_ai(client: Client, chat_id: str, user_query: str, contact) -> dict: def process_with_ai(client: Client, chat_id: str, user_query: str, contact) -> dict:
@ -16,4 +17,17 @@ def process_with_ai(client: Client, chat_id: str, user_query: str, contact) -> d
user_query=user_query, user_query=user_query,
contact_name=real_name contact_name=real_name
) )
# Логируем сам факт обращения к AI (детали будут добавлены в bot_message)
# Здесь мы можем залогировать, что AI был вызван, но основное логирование уже есть в bot_message
# Поэтому оставляем пустым или можно залогировать вызов
log_user_action(
client=client,
chat_id=chat_id,
action_type='ai_call',
user_query=user_query,
details={
'ai_agent': ai_agent.get('ai_agent'),
'contact_name': real_name
}
)
return response return response

@ -10,6 +10,7 @@ from restoran_max_bot.command_handlers import handle_standard_command, handle_ai
from restoran_max_bot.ai_handler import process_with_ai from restoran_max_bot.ai_handler import process_with_ai
from restoran_max_bot.settings import STATICFILES_DIRS, BASE_URL from restoran_max_bot.settings import STATICFILES_DIRS, BASE_URL
from restoran_max_bot.utils import is_json from restoran_max_bot.utils import is_json
from restoran_max_bot.user_logger import log_user_action
def bot_message(client: Client, message: dict, settings_max: dict, message_type: str) -> bool: def bot_message(client: Client, message: dict, settings_max: dict, message_type: str) -> bool:
@ -42,17 +43,54 @@ def bot_message(client: Client, message: dict, settings_max: dict, message_type:
send_menu(chat_id, token, client) send_menu(chat_id, token, client)
return True return True
# Логируем входящий запрос
log_user_action(
client=client,
chat_id=chat_id,
action_type='incoming_message',
user_query=user_query,
details={
'message_type': message_type,
'contact_name': contact.name,
'phone': contact.phone
}
)
# 4. Обработка стандартных команд (меню, бонусы и т.п.) # 4. Обработка стандартных команд (меню, бонусы и т.п.)
data = _get_command_data(client, message, message_type) data = _get_command_data(client, message, message_type)
if data: if data:
handled = handle_standard_command(client, chat_id, token, data, contact) handled = handle_standard_command(client, chat_id, settings_max, data, contact, command_key=user_query)
if handled: if handled:
log_user_action(
client=client,
chat_id=chat_id,
action_type='standard_command_handled',
command=user_query,
details={
'command_type': 'standard',
'contact_name': contact.name
}
)
return True return True
# 5. Если не стандартная команда — пробуем AI # 5. Если не стандартная команда — пробуем AI
response = process_with_ai(client, chat_id, user_query, contact) response = process_with_ai(client, chat_id, user_query, contact)
if response: if response:
handle_ai_response(client, chat_id, token, response, contact) handle_ai_response(client, chat_id, token, response, contact)
# Логируем ответ AI
log_user_action(
client=client,
chat_id=chat_id,
action_type='ai_response',
user_query=user_query,
intent=response.get('intent'),
entities=response.get('entities'),
response=response.get('message'),
details={
'status': response.get('status'),
'contact_name': contact.name
}
)
send_menu(chat_id, token, client) send_menu(chat_id, token, client)
return True return True
@ -93,53 +131,3 @@ def _get_command_data(client: Client, message: dict, message_type: str):
if not key: if not key:
return None return None
return common_get_data(client=client, key=str(key).lower()) return common_get_data(client=client, key=str(key).lower())
# ==================== bot_promo ====================
def bot_promo(client: Client, message: dict, settings_max: dict) -> bool:
"""Обработчик промокодов (QR-коды)."""
chat_id = message['chat_id']
promo = message['payload'].lower()
messenger = 'max-bot'
client_id = client.id
data = common_get_data(client=client, key=promo)
if not data:
return True
promo_obj, created = PromoCode.objects.get_or_create(
promo=promo,
chat_id=chat_id,
client=client,
defaults={'messenger': messenger, 'used': False}
)
if not created:
if promo_obj.used:
send_text(chat_id, settings_max['token'], "⚠️ Этот промокод уже был использован.")
else:
send_text(chat_id, settings_max['token'], " QR-код уже был отправлен.")
return True
# Генерируем QR
base_url = f"{BASE_URL}/promo/status/"
url_promo = f"{base_url}{promo}/{chat_id}/{client_id}"
img = qrcode.make(url_promo)
file_name = f"{promo}-{chat_id}-{client_id}.png"
img.save(f"{STATICFILES_DIRS[0]}/client_qr/{file_name}")
qr_url = f"{BASE_URL}/static/client_qr/{file_name}"
send_image(chat_id, settings_max['token'], qr_url, 'Ваш QR-код.')
# Отправляем дополнительный контент
for item in data:
if item.img:
img_url = f"https://cdn.telefon-ip.ru/{item.img}?thumb=600"
send_image(chat_id, settings_max['token'], img_url, item.descr)
if item.title and not item.img:
send_text(chat_id, settings_max['token'], item.descr)
if item.url:
send_text(chat_id, settings_max['token'], item.url)
return True

@ -1,6 +1,7 @@
from max_bot.max_api import * from max_bot.max_api import *
from max_bot.models import Client from max_bot.models import Client
from restoran_max_bot.common import common_get_data, common_get_contact, common_set_contact from restoran_max_bot.common import common_get_data, common_get_contact, common_set_contact
from restoran_max_bot.user_logger import log_user_action
def bot_started(client: Client, message: dict, settings: dict): def bot_started(client: Client, message: dict, settings: dict):
@ -8,6 +9,17 @@ def bot_started(client: Client, message: dict, settings: dict):
name = message['user']['name'] name = message['user']['name']
user_id = message['user']['user_id'] user_id = message['user']['user_id']
data = common_get_data(client=client, key='start') data = common_get_data(client=client, key='start')
log_user_action(
client=client,
chat_id=chat_id,
action_type='bot_started',
details={
'user_name': name,
'user_id': user_id
}
)
if data: if data:
for dt in data: for dt in data:
if dt.img: if dt.img:
@ -69,6 +81,17 @@ def check_registration(client: Client, chat_id: str, message: dict, settings: di
contact = common_set_contact(client=client, uid_client=chat_id, month=dt['data']) contact = common_set_contact(client=client, uid_client=chat_id, month=dt['data'])
maxbot_send_content(client=client, chat_id=chat_id, max_token=settings['token'], maxbot_send_content(client=client, chat_id=chat_id, max_token=settings['token'],
message='system_registration') message='system_registration')
# Логируем успешную регистрацию
log_user_action(
client=client,
chat_id=chat_id,
action_type='registration_completed',
details={
'name': contact.name,
'phone': contact.phone,
'birthday': f"{contact.field1}.{contact.field2}"
}
)
return contact return contact
else: else:
dt = maxbot_send_content(client=client, chat_id=chat_id, max_token=settings['token'], dt = maxbot_send_content(client=client, chat_id=chat_id, max_token=settings['token'],

@ -9,28 +9,68 @@ from restoran_max_bot.message_sender import send_text, send_image, send_menu, se
from restoran_max_bot.settings import STATICFILES_DIRS, BASE_URL from restoran_max_bot.settings import STATICFILES_DIRS, BASE_URL
from restoran_max_bot.utils import is_json from restoran_max_bot.utils import is_json
from restoran_max_bot.bot_started import check_registration from restoran_max_bot.bot_started import check_registration
from restoran_max_bot.promo_handlers import bot_promo
from restoran_max_bot.user_logger import log_user_action
def handle_standard_command(client: Client, chat_id: str, token: str, data, contact: Contact) -> bool: def handle_standard_command(client: Client, chat_id: str, settings_max: dict, data, contact: Contact, command_key: str = None) -> bool:
"""Обрабатывает стандартные команды (меню, бонусы, отзывы, бронирование). """Обрабатывает стандартные команды (меню, бонусы, отзывы, бронирование, промо).
Возвращает True, если команда была обработана, иначе False.""" Возвращает True, если команда была обработана, иначе False."""
if not data: if not data:
return False return False
token = settings_max['token']
for item in data: for item in data:
# Промокод (команда ##promo##)
if item.title == '##promo##' and command_key:
fake_message = {'chat_id': chat_id, 'payload': command_key}
result = bot_promo(client, fake_message, settings_max)
log_user_action(
client=client,
chat_id=chat_id,
action_type='standard_command',
command='promo',
details={'promo_key': command_key}
)
# После обработки промо-кода отправляем меню
send_menu(chat_id, token, client)
return result
# Бонусы # Бонусы
if item.title == '##bonus##': if item.title == '##bonus##':
_handle_bonus(client, chat_id, token, contact) _handle_bonus(client, chat_id, token, contact)
log_user_action(
client=client,
chat_id=chat_id,
action_type='standard_command',
command='bonus',
details={'contact_name': contact.name}
)
continue continue
# Отзыв # Отзыв
if item.title == '##feedback##': if item.title == '##feedback##':
send_feedback_buttons(chat_id, token, client) send_feedback_buttons(chat_id, token, client)
log_user_action(
client=client,
chat_id=chat_id,
action_type='standard_command',
command='feedback',
details={'contact_name': contact.name}
)
return True return True
# Бронирование # Бронирование
if item.title == '##booking##': if item.title == '##booking##':
send_booking_people_buttons(chat_id, token, client) send_booking_people_buttons(chat_id, token, client)
log_user_action(
client=client,
chat_id=chat_id,
action_type='standard_command',
command='booking',
details={'contact_name': contact.name}
)
return True return True
# Кнопка-ссылка # Кнопка-ссылка
@ -38,6 +78,13 @@ def handle_standard_command(client: Client, chat_id: str, token: str, data, cont
data_json = json.loads(item.title) data_json = json.loads(item.title)
if data_json.get('type') == 'linkbutton': if data_json.get('type') == 'linkbutton':
send_link_button(chat_id, token, item.descr, data_json['button'], item.url) send_link_button(chat_id, token, item.descr, data_json['button'], item.url)
log_user_action(
client=client,
chat_id=chat_id,
action_type='standard_command',
command='linkbutton',
details={'button_text': data_json['button']}
)
continue continue
# Изображение # Изображение
@ -158,6 +205,18 @@ def _handle_booking_intent(chat_id: str, token: str, client: Client, response: d
success = _send_booking_to_crm(client, date, time, people, phone, contact.name) success = _send_booking_to_crm(client, date, time, people, phone, contact.name)
if success: if success:
send_text(chat_id, token, f"✅ Бронирование на {date} в {time} на {people} чел. принято! Администратор свяжется с вами.") send_text(chat_id, token, f"✅ Бронирование на {date} в {time} на {people} чел. принято! Администратор свяжется с вами.")
log_user_action(
client=client,
chat_id=chat_id,
action_type='booking_completed',
details={
'date': date,
'time': time,
'people': people,
'phone': phone,
'contact_name': contact.name
}
)
else: else:
send_text(chat_id, token, "Не удалось забронировать. Попробуйте позже.") send_text(chat_id, token, "Не удалось забронировать. Попробуйте позже.")
else: else:
@ -185,6 +244,18 @@ def _handle_contact_admin_intent(chat_id: str, token: str, client: Client, respo
success = _send_contact_to_admin(client, phone, contact_method, question, preferred_time, contact.name) success = _send_contact_to_admin(client, phone, contact_method, question, preferred_time, contact.name)
if success: if success:
send_text(chat_id, token, f"✅ Ваш запрос передан администратору. Способ связи: {contact_method}. Скоро с вами свяжутся.") send_text(chat_id, token, f"✅ Ваш запрос передан администратору. Способ связи: {contact_method}. Скоро с вами свяжутся.")
log_user_action(
client=client,
chat_id=chat_id,
action_type='contact_admin_completed',
details={
'phone': phone,
'contact_method': contact_method,
'question': question,
'preferred_time': preferred_time,
'contact_name': contact.name
}
)
else: else:
send_text(chat_id, token, "Не удалось отправить запрос. Попробуйте позже.") send_text(chat_id, token, "Не удалось отправить запрос. Попробуйте позже.")
else: else:
@ -210,6 +281,16 @@ def _handle_feedback_intent(chat_id: str, token: str, client: Client, response:
success = _send_feedback_to_crm(client, rating, feedback_text, contact.name, contact.phone) success = _send_feedback_to_crm(client, rating, feedback_text, contact.name, contact.phone)
if success: if success:
send_text(chat_id, token, "✅ Спасибо за ваш отзыв! Мы учтём его.") send_text(chat_id, token, "✅ Спасибо за ваш отзыв! Мы учтём его.")
log_user_action(
client=client,
chat_id=chat_id,
action_type='feedback_completed',
details={
'rating': rating,
'feedback_text': feedback_text,
'contact_name': contact.name
}
)
else: else:
send_text(chat_id, token, "Не удалось отправить отзыв. Попробуйте позже.") send_text(chat_id, token, "Не удалось отправить отзыв. Попробуйте позже.")
else: else:

@ -32,7 +32,6 @@ if DEBUG:
else: else:
BASE_URL = "https://maxbot.telefon-ip.ru" BASE_URL = "https://maxbot.telefon-ip.ru"
ALLOWED_HOSTS = ['*'] ALLOWED_HOSTS = ['*']
@ -194,16 +193,5 @@ LOGGING = {
'level': 'ERROR', 'level': 'ERROR',
'propagate': False, 'propagate': False,
}, },
# Можно добавить логгеры для своих приложений, если нужно
# 'max_bot': {
# 'handlers': ['file'],
# 'level': 'ERROR',
# 'propagate': False,
# },
# 'ai_agent': {
# 'handlers': ['file'],
# 'level': 'ERROR',
# 'propagate': False,
# },
}, },
} }

@ -4,7 +4,8 @@ import unittest
from unittest.mock import Mock, patch, MagicMock from unittest.mock import Mock, patch, MagicMock
from django.test import TestCase from django.test import TestCase
from max_bot.models import Client, Contact, Product, ProductCategory, PromoCode from max_bot.models import Client, Contact, Product, ProductCategory, PromoCode
from restoran_max_bot.bot_message import bot_message, bot_promo, _get_attachments, _extract_query, _get_command_data from restoran_max_bot.bot_message import bot_message, _get_attachments, _extract_query, _get_command_data
from restoran_max_bot.promo_handlers import bot_promo
from restoran_max_bot.message_sender import send_text, send_image, send_menu from restoran_max_bot.message_sender import send_text, send_image, send_menu
from restoran_max_bot.command_handlers import handle_standard_command, handle_ai_response from restoran_max_bot.command_handlers import handle_standard_command, handle_ai_response
from restoran_max_bot.ai_handler import process_with_ai from restoran_max_bot.ai_handler import process_with_ai
@ -192,11 +193,11 @@ class BotMessageTestCase(TestCase):
# ---------- Тесты bot_promo ---------- # ---------- Тесты bot_promo ----------
def test_bot_promo_new(self): def test_bot_promo_new(self):
"""Создание нового промокода.""" """Создание нового промокода."""
with patch('restoran_max_bot.bot_message.common_get_data') as mock_get_data, \ with patch('restoran_max_bot.promo_handlers.common_get_data') as mock_get_data, \
patch('restoran_max_bot.bot_message.PromoCode.objects.get_or_create') as mock_get_or_create, \ patch('restoran_max_bot.promo_handlers.PromoCode.objects.get_or_create') as mock_get_or_create, \
patch('restoran_max_bot.bot_message.qrcode.make') as mock_qr, \ patch('restoran_max_bot.promo_handlers.qrcode.make') as mock_qr, \
patch('restoran_max_bot.bot_message.send_image') as mock_send_image, \ patch('restoran_max_bot.promo_handlers.send_image') as mock_send_image, \
patch('restoran_max_bot.bot_message.send_text') as mock_send_text: patch('restoran_max_bot.promo_handlers.send_text') as mock_send_text:
mock_get_data.return_value = [Mock(img='', title='', url='')] mock_get_data.return_value = [Mock(img='', title='', url='')]
mock_get_or_create.return_value = (Mock(used=False), True) # created=True mock_get_or_create.return_value = (Mock(used=False), True) # created=True
mock_qr.return_value = Mock() mock_qr.return_value = Mock()
@ -207,7 +208,7 @@ class BotMessageTestCase(TestCase):
mock_send_image.assert_called_once() mock_send_image.assert_called_once()
mock_send_text.assert_not_called() mock_send_text.assert_not_called()
@patch('restoran_max_bot.bot_message.common_get_data') @patch('restoran_max_bot.promo_handlers.common_get_data')
def test_bot_promo_no_data(self, mock_get_data): def test_bot_promo_no_data(self, mock_get_data):
"""Промокод не найден в системе.""" """Промокод не найден в системе."""
mock_get_data.return_value = None mock_get_data.return_value = None
@ -243,6 +244,7 @@ class CommandHandlersTestCase(TestCase):
) )
self.chat_id = 'chat123' self.chat_id = 'chat123'
self.token = 'token' self.token = 'token'
self.settings_max = {'token': self.token}
@patch('restoran_max_bot.command_handlers.send_menu') @patch('restoran_max_bot.command_handlers.send_menu')
@patch('restoran_max_bot.command_handlers.send_text') @patch('restoran_max_bot.command_handlers.send_text')
@ -254,7 +256,7 @@ class CommandHandlersTestCase(TestCase):
mock_item.descr = '' mock_item.descr = ''
mock_item.url = '' mock_item.url = ''
with patch('restoran_max_bot.command_handlers._handle_bonus') as mock_handle_bonus: with patch('restoran_max_bot.command_handlers._handle_bonus') as mock_handle_bonus:
result = handle_standard_command(self.client, self.chat_id, self.token, result = handle_standard_command(self.client, self.chat_id, self.settings_max,
[mock_item], self.contact) [mock_item], self.contact)
self.assertTrue(result) self.assertTrue(result)
mock_handle_bonus.assert_called_once() mock_handle_bonus.assert_called_once()
@ -266,7 +268,7 @@ class CommandHandlersTestCase(TestCase):
from restoran_max_bot.command_handlers import handle_standard_command from restoran_max_bot.command_handlers import handle_standard_command
mock_item = Mock() mock_item = Mock()
mock_item.title = '##feedback##' mock_item.title = '##feedback##'
result = handle_standard_command(self.client, self.chat_id, self.token, result = handle_standard_command(self.client, self.chat_id, self.settings_max,
[mock_item], self.contact) [mock_item], self.contact)
self.assertTrue(result) self.assertTrue(result)
mock_send_feedback.assert_called_once() mock_send_feedback.assert_called_once()
@ -278,7 +280,7 @@ class CommandHandlersTestCase(TestCase):
from restoran_max_bot.command_handlers import handle_standard_command from restoran_max_bot.command_handlers import handle_standard_command
mock_item = Mock() mock_item = Mock()
mock_item.title = '##booking##' mock_item.title = '##booking##'
result = handle_standard_command(self.client, self.chat_id, self.token, result = handle_standard_command(self.client, self.chat_id, self.settings_max,
[mock_item], self.contact) [mock_item], self.contact)
self.assertTrue(result) self.assertTrue(result)
mock_send_booking.assert_called_once() mock_send_booking.assert_called_once()
@ -292,7 +294,7 @@ class CommandHandlersTestCase(TestCase):
mock_item.title = json.dumps({'type': 'linkbutton', 'button': 'Click'}) mock_item.title = json.dumps({'type': 'linkbutton', 'button': 'Click'})
mock_item.descr = 'Text' mock_item.descr = 'Text'
mock_item.url = 'http://example.com' mock_item.url = 'http://example.com'
result = handle_standard_command(self.client, self.chat_id, self.token, result = handle_standard_command(self.client, self.chat_id, self.settings_max,
[mock_item], self.contact) [mock_item], self.contact)
self.assertTrue(result) self.assertTrue(result)
mock_send_link.assert_called_once() mock_send_link.assert_called_once()

@ -212,4 +212,4 @@
})(); })();
</script> </script>
</body> </body>
</html> </html>

Loading…
Cancel
Save