# -*- coding: utf-8 -*-
"""
Набор интеграционных тестов
BaseIntegrationTest - Базовый класс интеграционных тестов
BaseIntegrationSimplePackTest - тесты для objectpack.BasePack
BaseIntegrationTestPack - тесты для objectpack.ObjectPack
BaseIntegrationTreeTestPack - тесты для BaseTreeDictionaryModelActions
"""
import copy
import json
import uuid
import os
from django.test.client import Client
from django.conf import settings
from m3.actions import ControllerCache
from m3.actions import _import_by_path
from objectpack.slave_object_pack import SlavePack
from .helpers import PackValueForContext
from .helpers import ModelValueForContext
from .helpers import gen_uniq
from .helpers import GMWindowField
from .base_test import BaseTest
path = getattr(settings, 'OBJECTPACK_BASE_TEST', None)
if path:
PATCHED_BASE_TEST = _import_by_path(path)
else:
PATCHED_BASE_TEST = BaseTest
[документация]class BaseIntegrationTest(PATCHED_BASE_TEST):
"""
Базовый класс интеграционных тестов
Внутри есть проверка офрографиии self.check_spell(word)
и клиент self.client_session для post/get запросов
"""
pack = None # instance
pack_class = None # class
def __init__(self, *args, **kwargs):
super(BaseIntegrationTest, self).__init__(*args, **kwargs)
ControllerCache.populate()
# Атрибуты пака (Названия Экшенов) - окна на проверку
self.windows_actions = []
# Атрибуты пака (Названия Экшенов) - данные на проверку
self.rows_actions = []
self.context = {} # Контекс для post
self.good_save_action = None
self.period_id = 1
self.condition = {} # фильтры для моделей в БД
self.exclude_actions = [] # исключения для test_all_actions
self.client_session = Client()
self.log_path = os.path.join(
settings.LOG_PATH, 'integration_tests_log')
if not os.path.exists(self.log_path):
os.mkdir(self.log_path)
[документация] def setUp(self):
"""
Предустановка значений
"""
super(BaseIntegrationTest, self).setUp()
[документация] def check_status(self, response):
"""
Провека кодов возврата http ответов
:param response: http ответ от сервера
"""
f_name = ''
if response.status_code != 200:
f_name = os.path.join(self.log_path, str(uuid.uuid4()))
f = open(f_name, 'w')
f.write(response.content)
f.close()
self.assertEqual(response.status_code, 200, (
'Status code is {0}. Error logged in {1} file'.format(
response.status_code, f_name)))
[документация] def check_row(self, rows):
"""
Вспомогательная ф-я, проверяет ключи total, rows в JSON
:param rows: json, который требует валидации
:type rows: json
:return: результат проверки
:rtype: bool
"""
if "total" not in rows or "rows" not in rows:
if "message" in rows:
self.assertTrue(
self.check_spell(rows["message"]),
rows["message"]
)
return False
self.assertTrue("total" in rows)
self.assertTrue("rows" in rows)
return True
[документация] def post_and_get_json(self, url, context):
"""
Отправляет POST-запрос и возвращает json
:param url: строка с адресом
:type url: str
:param context: Контекст
:type context: dict
:return: response
:rtype: json
"""
response = self.post_and_get_response(url, context)
rows = json.loads(response.content)
return response, rows
[документация] def post_and_get_response(self, url, context):
"""
Отправляет POST-запрос и возвращает response ответ
:param url: строка с адресом
:type url: str
:param context: Контекст
:type context: dict
:return: response
"""
response = self.client_session.post(url, context)
self.check_status(response)
return response
[документация] def get_value_for_context(self, key, val):
"""
Получить значение из БД. По key ищем id_param_name в паках
и достаем первый попавшийся id у объекта модели пака
:param key: имя параметра, для которого необходимо
сгенерировать/найти данные
:param val: type of key
:return: genereated object
"""
condition = {}
condition.update(self.condition)
if (hasattr(self.pack, 'model') and
hasattr(self.pack, 'id_param_name') and
self.pack.id_param_name == key):
res = ModelValueForContext(
model=self.pack.model,
condition=condition, gm=True
).get()
else:
res = PackValueForContext(
key=key,
val=val,
condition=condition, gm=True
).get()
return res
@classmethod
[документация] def get_pack_instance(cls, pack_class):
"""
В зависимости от используемых технологий, необходимо перегрузить
получение экземпляра пака или через Observer или m3.actions.url
:param pack_class: pack_class
:return: instanse of pack
"""
raise NotImplementedError()
@classmethod
[документация] def get_list_pack_from_observer(cls):
"""
Необходим для проверки _is_primary_for_model
Возвращает список всех зарегистрованных паков
можно взять из Observer: obs._model_register.values()
:return: список зарегистрованных паков
:rtype: list
"""
return []
path = getattr(settings, 'OBJECTPACK_BASE_INTEGRATION_TEST', None)
if path:
PATCHED_BASE_INTEGRATION_TEST = _import_by_path(path)
else:
PATCHED_BASE_INTEGRATION_TEST = BaseIntegrationTest
[документация]class BaseIntegrationSimplePackTest(PATCHED_BASE_INTEGRATION_TEST):
"""
Набор интеграционных тестов пака, на основе objectpack.BasePack
"""
def __init__(self, *args, **kwargs):
"""
Инициализация созданого объекта
"""
super(BaseIntegrationSimplePackTest, self).__init__(*args, **kwargs)
if not self.pack_class:
raise NotImplementedError()
self.pack = self.get_pack_instance(self.pack_class)
if not self.pack:
raise NotImplementedError(self.pack_class)
[документация] def post_and_get_grid_json(self, action, context):
"""
Отправляет POST-запрос и возвращает (response, json)
json check total and rows in json
:param action: экшн (вьюха)
:type action: objectpack.BaseAction
:param context: Контекст
:type context: dict
:return: (response, json)
"""
go_context = copy.copy(context)
self.get_acd(action, go_context)
response, rows = self.post_and_get_json(
action.get_absolute_url(), go_context)
self.check_row(rows)
return response, rows
[документация] def get_acd(self, action, context):
"""
Обновляет контекст, согласно declare_context или context_declaration
:param action: экшн (вьюха)
:type action: objectpack.BaseAction
:param context: Контекст
:type context: dict
"""
acd = action.context_declaration() if hasattr(
action, 'context_declaration') else []
if acd:
if isinstance(acd, dict):
for key, val in acd.iteritems():
if key not in context:
res = self.get_value_for_context(key, val)
if res:
context[key] = res
if isinstance(acd, list):
for j in acd:
if j.name not in context:
res = self.get_value_for_context(
j.name, {'type': j.type}
)
if res:
context[j.name] = res
[документация] def test_windows_actions(self):
"""
Тест проверки вызова окон
"""
for action_attr in self.windows_actions:
action = getattr(self.pack, action_attr, None)
self.setUp()
context = copy.copy(self.context)
if 'new_window_action' == action_attr:
acd = self.pack.declare_context(action)
if self.pack.id_param_name in acd:
context[self.pack.id_param_name] = ''
self.get_acd(action, context)
if action:
try:
self.post_and_get_response(
action.get_absolute_url(),
context
)
except Exception as ex:
ex.args = (ex.args if ex.args else tuple()) + (action,)
raise
[документация] def test_rows_actions(self):
"""
Тест получает все значения из Пака
"""
for action_attr in self.rows_actions:
action = getattr(self.pack, action_attr, None)
self.setUp()
context = copy.copy(self.context)
try:
_, rows = self.post_and_get_grid_json(
action,
context
)
except Exception as ex:
ex.args = (ex.args if ex.args else tuple()) + (action,)
raise
if 'rows' in rows:
self.assertEqual(len(rows["rows"]), rows["total"], action)
else:
self.assertFalse(rows["success"], action)
[документация] def another_save_action(self, win, save_action, context):
"""
Отправляет пост на save_action, с дополнительным контекстом context
и с парсенными данными win
:param objectpack.ObjectPack pack: инстанс пака
:param objectpack.BaseEditWindow win: инстанс окна
:param objectpack.dict context: инстанс окна
"""
context = GMWindowField(self.pack, win, context, self.condition).get()
self.get_acd(save_action, context)
context = gen_uniq(
context,
self.pack.model,
self.condition
)
self.post_and_get_response(
save_action.get_absolute_url(),
context
)
[документация]class BaseIntegrationTestPack(BaseIntegrationSimplePackTest):
"""
Набор интеграционных тестов пака, на основе objectpack.ObjectPack
"""
def __init__(self, *args, **kwargs):
"""
Инициализация созданого объекта
"""
super(BaseIntegrationTestPack, self).__init__(*args, **kwargs)
# setUp срабатывает несколько раз за instance
if self.pack.add_window and not self.pack.read_only:
self.windows_actions.append('new_window_action')
if self.pack.edit_window and not self.pack.read_only:
self.windows_actions.append('edit_window_action')
if self.pack.list_window:
self.windows_actions.append('list_window_action')
if self.pack.select_window:
self.windows_actions.append('select_window_action')
if not self.good_save_action:
save_action = getattr(self.pack, 'save_action', None)
self.good_save_action = save_action
[документация] def test_limit_row_pack(self):
"""
Тест получает значения с ограничеием (пагинация)
"""
if getattr(self.pack, 'allow_paging', None):
rows_action = getattr(self.pack, 'rows_action', None)
context_new = copy.copy(self.context)
context_new['limit'] = 25
context_new['start'] = 0
_, rows = self.post_and_get_grid_json(
rows_action,
context_new
)
if "rows" in rows:
self.assertTrue(len(rows["rows"]) <= 25)
[документация] def test_filter_row_pack(self):
"""
Тест получает значения по фильтру
"""
rows_action = getattr(self.pack, 'rows_action', None)
context_new = copy.copy(self.context)
need_filter = False
for row in self.pack.columns:
if 'searchable' in row and row['searchable']:
need_filter = True
break
if need_filter:
context_new['filter'] = '123'
self.post_and_get_grid_json(
rows_action,
context_new
)
[документация] def test_order_row_pack(self):
"""
Тест проверяет сортировку по всем полям column в Паке
"""
rows_action = getattr(self.pack, 'rows_action', None)
context_new = copy.copy(self.context)
for row in self.pack.columns:
# Проверяем включен ли атрибут сортировки у колонки
if row.get('sortable', None):
context_new['sort'] = row['data_index']
context_new['dir'] = 'ASC'
self.post_and_get_grid_json(
rows_action,
context_new
)
context_new['sort'] = row['data_index']
context_new['dir'] = 'DESC'
self.post_and_get_grid_json(
rows_action,
context_new
)
[документация] def test_all_actions(self):
"""
Тестируем все экшены, которые не тестируются другими тестами
"""
actions = self.pack.actions
# убираем actionы, которые уже тестируются в других местах
for i in self.rows_actions:
action = getattr(self.pack, i)
if action in actions:
actions.remove(action)
for i in self.windows_actions:
action = getattr(self.pack, i)
if action in actions:
actions.remove(action)
for attr_name in ['delete_action', 'save_action', 'rows_action']:
action = getattr(self.pack, attr_name)
if action in actions:
actions.remove(action)
edit_window_action = getattr(self.pack, 'edit_window_action')
if edit_window_action and edit_window_action in actions:
actions.remove(edit_window_action)
list_window_action = getattr(self.pack, 'list_window_action')
if list_window_action and list_window_action in actions:
actions.remove(list_window_action)
select_window_action = getattr(self.pack, 'select_window_action')
if select_window_action and select_window_action in actions:
actions.remove(select_window_action)
# неверные кастомные экшены, от них надо злостно избавляться!
save_window_action = getattr(self.pack, 'save_window_action', None)
if save_window_action and select_window_action in actions:
actions.remove(save_window_action)
for attr_name in self.exclude_actions:
action = getattr(self.pack, attr_name, None)
if action in actions:
actions.remove(action)
for action in actions:
self.setUp()
context = copy.copy(self.context)
self.get_acd(action, context)
try:
if action:
self.post_and_get_response(
action.get_absolute_url(),
context
)
except Exception as ex:
ex.args = (ex.args if ex.args else tuple()) + (action,)
raise
[документация] def test_delete_action(self):
"""
Тест на удаление с указанием id объекта
"""
# получаем action для удаления
if hasattr(self.pack, 'can_delete'):
delete_action = getattr(self.pack, 'delete_action', None)
if delete_action:
context = copy.copy(self.context)
self.get_acd(delete_action, context)
self.post_and_get_response(
delete_action.get_absolute_url(),
context)
[документация] def test_good_add_save_action(self):
"""
Тестируем сохранени хороших данных, т.е. успешное сохранение
"""
if self.good_save_action:
add_window = getattr(self.pack, 'add_window', None)
if add_window:
aw = add_window
if isinstance(self.pack, SlavePack):
try:
instance_aw = aw(model=self.pack.model)
except TypeError:
instance_aw = aw()
else:
instance_aw = aw()
context_new = copy.copy(self.context)
context_new[self.pack.id_param_name] = 0
self.another_save_action(
instance_aw,
self.good_save_action,
context_new
)
[документация] def test_is_primary_for_model(self):
"""
Проверка сущестования
одного и только одного пака с _is_primary_for_model=True
"""
list_of_packs = self.get_list_pack_from_observer()
if list_of_packs:
# Если пак первичный, то больше не должно быть таких
if self.pack._is_primary_for_model:
for pack in list_of_packs:
if hasattr(pack, 'model'):
self.assertFalse(
(self.pack != pack) and
(self.pack.model == pack.model) and
pack._is_primary_for_model, 'double primary pack')
# если пак вторичный, то где-то должен быть первичный
else:
is_prime = False
for pack in list_of_packs:
if hasattr(pack, 'model'):
if self.pack.model == pack.model and\
self.pack != pack and\
hasattr(pack, '_is_primary_for_model'):
is_prime = is_prime or pack._is_primary_for_model
self.assertTrue(is_prime, 'no primary pack')
[документация]class BaseIntegrationTreeTestPack(BaseIntegrationSimplePackTest):
"""
набор тестов для паков на основе BaseTreeDictionaryModelActions
"""
def __init__(self, *args, **kwargs):
"""
Инициализация созданого объекта
"""
super(BaseIntegrationTreeTestPack, self).__init__(*args, **kwargs)
window = getattr(self.pack, 'edit_node_window', None)
if window:
self.windows_actions.extend([
'new_node_window_action',
'edit_node_window_action',
])
self.windows_actions.extend([
'list_window_action',
'select_window_action',
'edit_grid_window_action'
# не окна, но пофиг
'delete_node_action',
# 'delete_row_action' # not used
'node_action',
'row_action',
'nodes_like_rows_action',
# 'last_used_action',
])
[документация] def test_node_pack(self):
"""
тест для получения всех нод
"""
nodes_action = getattr(self.pack, 'nodes_action', None)
self.post_and_get_response(
nodes_action.get_absolute_url(),
self.context
)
[документация] def test_filter_row_pack(self):
"""
тест для получения и фльтрации данных
"""
rows_action = getattr(self.pack, 'rows_action', None)
context_new = copy.copy(self.context)
context_new['filter'] = '12'
self.post_and_get_response(
rows_action.get_absolute_url(),
context_new
)
[документация] def test_new_grid_window_action(self):
"""
тестируем новое окно
"""
window = getattr(self.pack, 'edit_window', None)
if window:
action = getattr(self.pack, 'new_grid_window_action', None)
context_new = copy.copy(self.context)
context_new[self.pack.contextTreeIdName] = 0
context_new['id'] = 0
self.get_acd(action, context_new)
self.post_and_get_response(
action.get_absolute_url(),
context_new
)
[документация] def test_save_node_action(self):
"""
Тестируем сохранени хороших данных, т.е. успешное сохранение
save_action
"""
save_action = getattr(self.pack, 'save_node_action', None)
if save_action:
# hack by helpers
self.pack.model = self.pack.tree_model
context_new = copy.copy(self.context)
window = getattr(self.pack, 'edit_node_window', None)
if window:
self.another_save_action(
window(),
save_action,
context_new
)
[документация] def test_save_row_action(self):
"""
Тестируем сохранени хороших данных, т.е. успешное сохранение
save_action
"""
save_action = getattr(self.pack, 'save_row_action', None)
if save_action:
# hack by helpers
self.pack.model = self.pack.tree_model
context_new = copy.copy(self.context)
window = getattr(self.pack, 'edit_window', None)
if window:
self.another_save_action(
window(),
save_action,
context_new
)