Files
octopus/myproject/customers/tests/test_search_strategies.py
Andrey Smakotin dbbac933af Добавлены тесты защиты системного клиента и рефакторинг структуры тестов
- Создан новый класс SystemCustomerProtectionTestCase с 5 критичными тестами
- Тест создания системного клиента с правильными атрибутами
- Тест защиты от удаления системного клиента (ValidationError)
- Тест защиты email системного клиента от изменения
- Тест защиты флага is_system_customer от изменения
- Тест что обычные клиенты не затронуты защитой

- Исправлена логика в Customer.save(): проверка теперь использует original.is_system_customer
- Добавлен импорт ValidationError из django.core.exceptions

- Рефакторинг структуры тестов customers:
  - Разделены тесты по отдельным модулям в папке customers/tests/
  - test_search_strategies.py - тесты стратегий поиска
  - test_system_customer.py - тесты защиты системного клиента
  - test_wallet_balance.py - тесты баланса кошелька
  - test_wallet_service.py - тесты WalletService
  - test_wallet_model.py - тесты модели WalletTransaction

- Обновлён анализ тестов: 50 тестов (было 45), все проходят успешно
- Критичная функциональность POS системы теперь покрыта тестами
- Учтена tenant-система (используется TenantTestCase)
2025-12-28 00:32:45 +03:00

204 lines
9.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""
Тесты для функций поиска клиентов.
Используем TenantTestCase для корректной работы с tenant-системой.
"""
from django_tenants.test.cases import TenantTestCase
from customers.views import determine_search_strategy, is_query_phone_only
class DetermineSearchStrategyTestCase(TenantTestCase):
"""
Тесты для функции determine_search_strategy().
Компактная версия с параметризацией для избежания дублирования.
"""
def _test_strategy(self, query, expected_strategy, expected_value):
"""Вспомогательный метод для проверки стратегии."""
strategy, search_value = determine_search_strategy(query)
self.assertEqual(strategy, expected_strategy,
f"Query '{query}' должен вернуть стратегию '{expected_strategy}'")
self.assertEqual(search_value, expected_value,
f"Query '{query}' должен вернуть значение '{expected_value}'")
# ===== email_prefix: query заканчивается на @ =====
def test_email_prefix_strategy(self):
"""Различные варианты поиска по префиксу email."""
test_cases = [
('team_x3m@', 'team_x3m'),
('user_name@', 'user_name'),
('test123@', 'test123'),
]
for query, expected_value in test_cases:
self._test_strategy(query, 'email_prefix', expected_value)
# ===== email_domain: query начинается с @ =====
def test_email_domain_strategy(self):
"""Различные варианты поиска по домену email."""
test_cases = [
('@bk', 'bk'),
('@bk.ru', 'bk.ru'),
('@mail.google.com', 'mail.google.com'),
]
for query, expected_value in test_cases:
self._test_strategy(query, 'email_domain', expected_value)
# ===== email_full: query содержит и локальную часть, и домен =====
def test_email_full_strategy(self):
"""Различные варианты полного поиска email."""
test_cases = [
('test@bk.ru', 'test@bk.ru'),
('test@bk', 'test@bk'),
('user.name@mail.example.com', 'user.name@mail.example.com'),
]
for query, expected_value in test_cases:
self._test_strategy(query, 'email_full', expected_value)
# ===== universal: query без @, 3+ символов =====
def test_universal_strategy(self):
"""Универсальный поиск для запросов 3+ символов."""
test_cases = [
('abc', 'abc'), # минимум 3 символа
('natul', 'natul'),
('наталь', 'наталь'), # кириллица
('Test123', 'Test123'), # смешанный
('Ivan Petrov', 'Ivan Petrov'), # с пробелами
]
for query, expected_value in test_cases:
self._test_strategy(query, 'universal', expected_value)
# ===== name_only: очень короткие запросы (< 3 символов без @) =====
def test_name_only_strategy(self):
"""Поиск только по имени для коротких запросов."""
test_cases = [
('t', 't'), # 1 символ
('te', 'te'), # 2 символа
('на', 'на'), # 2 символа кириллица
('', ''), # пустая строка
]
for query, expected_value in test_cases:
self._test_strategy(query, 'name_only', expected_value)
# ===== edge cases =====
def test_edge_cases(self):
"""Граничные и специальные случаи."""
# Только символ @
self._test_strategy('@', 'email_domain', '')
# Множественные @ - берётся первый
self._test_strategy('test@example@com', 'email_full', 'test@example@com')
# ===== real-world критические сценарии =====
def test_real_world_email_prefix_no_false_match(self):
"""
КРИТИЧНЫЙ: query 'team_x3m@' НЕ должен найти 'natulj@bk.ru'.
Проверяем, что используется email_prefix (istartswith), а не universal (icontains).
"""
strategy, search_value = determine_search_strategy('team_x3m@')
self.assertEqual(strategy, 'email_prefix')
self.assertEqual(search_value, 'team_x3m')
# Важно: НЕ universal стратегия
self.assertNotEqual(strategy, 'universal')
def test_real_world_domain_search(self):
"""Real-world: '@bk' находит все email с @bk.*"""
self._test_strategy('@bk', 'email_domain', 'bk')
def test_real_world_universal_search(self):
"""Real-world: 'natul' находит и имя 'Наталья' и email 'natulj@bk.ru'"""
self._test_strategy('natul', 'universal', 'natul')
class IsQueryPhoneOnlyTestCase(TenantTestCase):
"""
Тесты для функции is_query_phone_only().
Проверяют, что функция правильно определяет, содержит ли query
только символы номера телефона (цифры, +, -, (), пробелы).
"""
# ===== Должны вернуть True (только телефонные символы) =====
def test_phone_only_digits(self):
"""Query '295' должен вернуть True (только цифры)"""
self.assertTrue(is_query_phone_only('295'))
def test_phone_only_single_digit(self):
"""Query '5' должен вернуть True (одна цифра)"""
self.assertTrue(is_query_phone_only('5'))
def test_phone_with_plus(self):
"""Query '+375291234567' должен вернуть True"""
self.assertTrue(is_query_phone_only('+375291234567'))
def test_phone_with_dashes(self):
"""Query '029-123-45' должен вернуть True"""
self.assertTrue(is_query_phone_only('029-123-45'))
def test_phone_with_parentheses(self):
"""Query '(029) 123-45' должен вернуть True"""
self.assertTrue(is_query_phone_only('(029) 123-45'))
def test_phone_with_spaces(self):
"""Query '029 123 45' должен вернуть True"""
self.assertTrue(is_query_phone_only('029 123 45'))
def test_phone_complex_format(self):
"""Query '+375 (29) 123-45-67' должен вернуть True"""
self.assertTrue(is_query_phone_only('+375 (29) 123-45-67'))
def test_phone_with_dot(self):
"""Query '029.123.45' должен вернуть True"""
self.assertTrue(is_query_phone_only('029.123.45'))
# ===== Должны вернуть False (содержат буквы или другие символы) =====
def test_query_with_letters_only(self):
"""Query 'abc' должен вернуть False (содержит буквы)"""
self.assertFalse(is_query_phone_only('abc'))
def test_query_with_mixed_letters_digits(self):
"""Query 'x3m' должен вернуть False (содержит буквы)"""
self.assertFalse(is_query_phone_only('x3m'))
def test_query_name_with_digits(self):
"""Query 'team_x3m' должен вернуть False (содержит буквы и _)"""
self.assertFalse(is_query_phone_only('team_x3m'))
def test_query_name_cyrillic(self):
"""Query 'Наталья' должен вернуть False (содержит кириллицу)"""
self.assertFalse(is_query_phone_only('Наталья'))
def test_query_with_underscore(self):
"""Query '123_456' должен вернуть False (содержит _)"""
self.assertFalse(is_query_phone_only('123_456'))
def test_query_with_hash(self):
"""Query '123#456' должен вернуть False (содержит #)"""
self.assertFalse(is_query_phone_only('123#456'))
def test_empty_string(self):
"""Query '' должен вернуть False (пустая строка)"""
self.assertFalse(is_query_phone_only(''))
def test_only_spaces(self):
"""Query ' ' должен вернуть False (пустой запрос)"""
self.assertFalse(is_query_phone_only(' '))
# ===== Real-world cases =====
def test_real_world_case_x3m_should_not_be_phone(self):
"""
Real-world case: "x3m" содержит букву, поэтому НЕ похож на телефон.
Это критично для решения проблемы с поиском Натальи.
"""
self.assertFalse(is_query_phone_only('x3m'))
# Значит, при поиске "x3m" НЕ будет поиска по цифре "3" в телефонах
def test_real_world_case_295_should_be_phone(self):
"""Real-world case: '295' только цифры, похож на телефон"""
self.assertTrue(is_query_phone_only('295'))
def test_real_world_full_phone_number(self):
"""Real-world case: полный номер в стандартном формате"""
self.assertTrue(is_query_phone_only('+375 (29) 598-62-62'))