Проблема: Поиск "x3m" неправильно находит клиента Наталью потому что её номер
содержит цифру "3". Это происходило, потому что система искала по любым цифрам
в query, даже если это явно не номер телефона.
Решение: Добавлена функция is_query_phone_only() которая проверяет, содержит ли
query ТОЛЬКО телефонные символы (цифры, +, -, (), пробелы, точка).
Поиск по номеру телефона происходит ТОЛЬКО если:
1. Query состоит ТОЛЬКО из телефонных символов (никаких букв)
2. И количество цифр >= 3
Примеры:
- "x3m" → НЕ ищет по цифре "3" (содержит букву)
- "29" → НЕ ищет по цифрам (только 2 цифры, нужно минимум 3)
- "295" → ИЩЕТ по цифрам "295" (только цифры, 3+ символов)
- "+375291234567" → ИЩЕТ по номеру (только телефонные символы)
- "team_x3m" → НЕ ищет по цифрам (содержит буквы и _)
Изменения:
1. Добавлена функция is_query_phone_only() в views.py
2. Обновлена api_search_customers() для использования новой функции
3. Обновлена customer_list() для использования новой функции
4. Добавлены 19 unit-тестов для is_query_phone_only()
Результаты тестирования:
✓ 42 теста всего (23 для determine_search_strategy + 19 для is_query_phone_only)
✓ Все тесты проходят успешно
Критические тест-кейсы:
✓ is_query_phone_only('x3m') == False (решает исходную проблему)
✓ is_query_phone_only('295') == True
✓ is_query_phone_only('+375291234567') == True
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
253 lines
12 KiB
Python
253 lines
12 KiB
Python
from django.test import TestCase
|
||
from .views import determine_search_strategy, is_query_phone_only
|
||
|
||
|
||
class DetermineSearchStrategyTestCase(TestCase):
|
||
"""
|
||
Тесты для функции determine_search_strategy().
|
||
|
||
Проверяют, что функция правильно определяет стратегию поиска
|
||
на основе содержимого query.
|
||
"""
|
||
|
||
# ===== email_prefix: query заканчивается на @ =====
|
||
def test_email_prefix_simple(self):
|
||
"""Query "team_x3m@" должен вернуть ('email_prefix', 'team_x3m')"""
|
||
strategy, search_value = determine_search_strategy('team_x3m@')
|
||
self.assertEqual(strategy, 'email_prefix')
|
||
self.assertEqual(search_value, 'team_x3m')
|
||
|
||
def test_email_prefix_with_domain_symbol(self):
|
||
"""Query "user_name@" должен вернуть ('email_prefix', 'user_name')"""
|
||
strategy, search_value = determine_search_strategy('user_name@')
|
||
self.assertEqual(strategy, 'email_prefix')
|
||
self.assertEqual(search_value, 'user_name')
|
||
|
||
def test_email_prefix_with_numbers(self):
|
||
"""Query "test123@" должен вернуть ('email_prefix', 'test123')"""
|
||
strategy, search_value = determine_search_strategy('test123@')
|
||
self.assertEqual(strategy, 'email_prefix')
|
||
self.assertEqual(search_value, 'test123')
|
||
|
||
# ===== email_domain: query начинается с @ =====
|
||
def test_email_domain_simple(self):
|
||
"""Query "@bk" должен вернуть ('email_domain', 'bk')"""
|
||
strategy, search_value = determine_search_strategy('@bk')
|
||
self.assertEqual(strategy, 'email_domain')
|
||
self.assertEqual(search_value, 'bk')
|
||
|
||
def test_email_domain_with_extension(self):
|
||
"""Query "@bk.ru" должен вернуть ('email_domain', 'bk.ru')"""
|
||
strategy, search_value = determine_search_strategy('@bk.ru')
|
||
self.assertEqual(strategy, 'email_domain')
|
||
self.assertEqual(search_value, 'bk.ru')
|
||
|
||
def test_email_domain_with_multiple_dots(self):
|
||
"""Query "@mail.google.com" должен вернуть ('email_domain', 'mail.google.com')"""
|
||
strategy, search_value = determine_search_strategy('@mail.google.com')
|
||
self.assertEqual(strategy, 'email_domain')
|
||
self.assertEqual(search_value, 'mail.google.com')
|
||
|
||
# ===== email_full: query содержит и локальную часть, и домен =====
|
||
def test_email_full_simple(self):
|
||
"""Query "test@bk.ru" должен вернуть ('email_full', 'test@bk.ru')"""
|
||
strategy, search_value = determine_search_strategy('test@bk.ru')
|
||
self.assertEqual(strategy, 'email_full')
|
||
self.assertEqual(search_value, 'test@bk.ru')
|
||
|
||
def test_email_full_partial(self):
|
||
"""Query "test@bk" должен вернуть ('email_full', 'test@bk')"""
|
||
strategy, search_value = determine_search_strategy('test@bk')
|
||
self.assertEqual(strategy, 'email_full')
|
||
self.assertEqual(search_value, 'test@bk')
|
||
|
||
def test_email_full_complex(self):
|
||
"""Query "user.name@mail.example.com" должен вернуть ('email_full', ...)"""
|
||
strategy, search_value = determine_search_strategy('user.name@mail.example.com')
|
||
self.assertEqual(strategy, 'email_full')
|
||
self.assertEqual(search_value, 'user.name@mail.example.com')
|
||
|
||
# ===== universal: query без @, 3+ символов =====
|
||
def test_universal_three_chars(self):
|
||
"""Query "natul" (5 символов) должен вернуть ('universal', 'natul')"""
|
||
strategy, search_value = determine_search_strategy('natul')
|
||
self.assertEqual(strategy, 'universal')
|
||
self.assertEqual(search_value, 'natul')
|
||
|
||
def test_universal_three_chars_exact(self):
|
||
"""Query "abc" (3 символа) должен вернуть ('universal', 'abc')"""
|
||
strategy, search_value = determine_search_strategy('abc')
|
||
self.assertEqual(strategy, 'universal')
|
||
self.assertEqual(search_value, 'abc')
|
||
|
||
def test_universal_cyrillic(self):
|
||
"""Query "наталь" (6 символов) должен вернуть ('universal', 'наталь')"""
|
||
strategy, search_value = determine_search_strategy('наталь')
|
||
self.assertEqual(strategy, 'universal')
|
||
self.assertEqual(search_value, 'наталь')
|
||
|
||
def test_universal_mixed(self):
|
||
"""Query "Test123" (7 символов) должен вернуть ('universal', 'Test123')"""
|
||
strategy, search_value = determine_search_strategy('Test123')
|
||
self.assertEqual(strategy, 'universal')
|
||
self.assertEqual(search_value, 'Test123')
|
||
|
||
# ===== name_only: очень короткие запросы (< 3 символов без @) =====
|
||
def test_name_only_single_char(self):
|
||
"""Query "t" должен вернуть ('name_only', 't')"""
|
||
strategy, search_value = determine_search_strategy('t')
|
||
self.assertEqual(strategy, 'name_only')
|
||
self.assertEqual(search_value, 't')
|
||
|
||
def test_name_only_two_chars(self):
|
||
"""Query "te" должен вернуть ('name_only', 'te')"""
|
||
strategy, search_value = determine_search_strategy('te')
|
||
self.assertEqual(strategy, 'name_only')
|
||
self.assertEqual(search_value, 'te')
|
||
|
||
def test_name_only_two_chars_cyrillic(self):
|
||
"""Query "на" (2 символа) должен вернуть ('name_only', 'на')"""
|
||
strategy, search_value = determine_search_strategy('на')
|
||
self.assertEqual(strategy, 'name_only')
|
||
self.assertEqual(search_value, 'на')
|
||
|
||
# ===== edge cases =====
|
||
def test_empty_string(self):
|
||
"""Query "" должен вернуть ('name_only', '')"""
|
||
strategy, search_value = determine_search_strategy('')
|
||
self.assertEqual(strategy, 'name_only')
|
||
self.assertEqual(search_value, '')
|
||
|
||
def test_only_at_symbol(self):
|
||
"""Query "@" должен вернуть ('email_domain', '')"""
|
||
strategy, search_value = determine_search_strategy('@')
|
||
self.assertEqual(strategy, 'email_domain')
|
||
self.assertEqual(search_value, '')
|
||
|
||
def test_multiple_at_symbols(self):
|
||
"""Query "test@example@com" должен обработать первый @"""
|
||
strategy, search_value = determine_search_strategy('test@example@com')
|
||
self.assertEqual(strategy, 'email_full')
|
||
self.assertEqual(search_value, 'test@example@com')
|
||
|
||
def test_spaces_in_query(self):
|
||
"""Query "Ivan Petrov" должен вернуть ('universal', 'Ivan Petrov')"""
|
||
strategy, search_value = determine_search_strategy('Ivan Petrov')
|
||
self.assertEqual(strategy, 'universal')
|
||
self.assertEqual(search_value, 'Ivan Petrov')
|
||
|
||
# ===== real-world examples =====
|
||
def test_real_world_problematic_case(self):
|
||
"""
|
||
Real-world case: query "team_x3m@" не должен найти "natulj@bk.ru"
|
||
Используется email_prefix со istartswith вместо icontains
|
||
"""
|
||
strategy, search_value = determine_search_strategy('team_x3m@')
|
||
self.assertEqual(strategy, 'email_prefix')
|
||
# Важно: стратегия email_prefix, не universal или email_full
|
||
self.assertNotEqual(strategy, 'universal')
|
||
|
||
def test_real_world_domain_search(self):
|
||
"""Real-world case: query "@bk" должен найти все @bk.ru"""
|
||
strategy, search_value = determine_search_strategy('@bk')
|
||
self.assertEqual(strategy, 'email_domain')
|
||
self.assertEqual(search_value, 'bk')
|
||
|
||
def test_real_world_name_search(self):
|
||
"""Real-world case: query "natul" должен найти "Наталья" и "natulj@bk.ru" """
|
||
strategy, search_value = determine_search_strategy('natul')
|
||
self.assertEqual(strategy, 'universal')
|
||
self.assertEqual(search_value, 'natul')
|
||
|
||
|
||
class IsQueryPhoneOnlyTestCase(TestCase):
|
||
"""
|
||
Тесты для функции 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 ' ' должен вернуть True (только пробелы разрешены)"""
|
||
self.assertTrue(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'))
|