8669ca2283
show scrolling stdout from subprocess nb. this does *not* show stderr, although that is captured
1469 lines
61 KiB
Python
1469 lines
61 KiB
Python
# -*- coding: utf-8; -*-
|
|
|
|
import datetime
|
|
import decimal
|
|
import functools
|
|
from unittest import TestCase
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from sqlalchemy import orm
|
|
from pyramid import testing
|
|
from pyramid.response import Response
|
|
from pyramid.httpexceptions import HTTPNotFound
|
|
|
|
from wuttjamaican.conf import WuttaConfig
|
|
from wuttaweb.views import master as mod
|
|
from wuttaweb.views import View
|
|
from wuttaweb.progress import SessionProgress
|
|
from wuttaweb.subscribers import new_request_set_user
|
|
from tests.util import WebTestCase
|
|
|
|
|
|
class TestMasterView(WebTestCase):
|
|
|
|
def make_view(self):
|
|
return mod.MasterView(self.request)
|
|
|
|
def test_defaults(self):
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Widget',
|
|
model_key='uuid',
|
|
deletable_bulk=True,
|
|
has_autocomplete=True,
|
|
downloadable=True,
|
|
executable=True,
|
|
configurable=True):
|
|
mod.MasterView.defaults(self.pyramid_config)
|
|
|
|
##############################
|
|
# class methods
|
|
##############################
|
|
|
|
def test_get_model_class(self):
|
|
|
|
# no model class by default
|
|
self.assertIsNone(mod.MasterView.get_model_class())
|
|
|
|
# subclass may specify
|
|
MyModel = MagicMock()
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=MyModel):
|
|
self.assertIs(mod.MasterView.get_model_class(), MyModel)
|
|
|
|
def test_get_model_name(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_model_name)
|
|
|
|
# subclass may specify model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Widget'):
|
|
self.assertEqual(mod.MasterView.get_model_name(), 'Widget')
|
|
|
|
# or it may specify model class
|
|
MyModel = MagicMock(__name__='Blaster')
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=MyModel):
|
|
self.assertEqual(mod.MasterView.get_model_name(), 'Blaster')
|
|
|
|
def test_get_model_name_normalized(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_model_name_normalized)
|
|
|
|
# subclass may specify *normalized* model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name_normalized='widget'):
|
|
self.assertEqual(mod.MasterView.get_model_name_normalized(), 'widget')
|
|
|
|
# or it may specify *standard* model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Blaster'):
|
|
self.assertEqual(mod.MasterView.get_model_name_normalized(), 'blaster')
|
|
|
|
# or it may specify model class
|
|
MyModel = MagicMock(__name__='Dinosaur')
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=MyModel):
|
|
self.assertEqual(mod.MasterView.get_model_name_normalized(), 'dinosaur')
|
|
|
|
def test_get_model_title(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_model_title)
|
|
|
|
# subclass may specify model title
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_title='Wutta Widget'):
|
|
self.assertEqual(mod.MasterView.get_model_title(), "Wutta Widget")
|
|
|
|
# or it may specify model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Blaster'):
|
|
self.assertEqual(mod.MasterView.get_model_title(), "Blaster")
|
|
|
|
# or it may specify model class
|
|
MyModel = MagicMock(__name__='Dinosaur')
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=MyModel):
|
|
self.assertEqual(mod.MasterView.get_model_title(), "Dinosaur")
|
|
|
|
def test_get_model_title_plural(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_model_title_plural)
|
|
|
|
# subclass may specify *plural* model title
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_title_plural='People'):
|
|
self.assertEqual(mod.MasterView.get_model_title_plural(), "People")
|
|
|
|
# or it may specify *singular* model title
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_title='Wutta Widget'):
|
|
self.assertEqual(mod.MasterView.get_model_title_plural(), "Wutta Widgets")
|
|
|
|
# or it may specify model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Blaster'):
|
|
self.assertEqual(mod.MasterView.get_model_title_plural(), "Blasters")
|
|
|
|
# or it may specify model class
|
|
MyModel = MagicMock(__name__='Dinosaur')
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=MyModel):
|
|
self.assertEqual(mod.MasterView.get_model_title_plural(), "Dinosaurs")
|
|
|
|
def test_get_model_key(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_model_key)
|
|
|
|
# subclass may specify model key
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_key='uuid'):
|
|
self.assertEqual(mod.MasterView.get_model_key(), ('uuid',))
|
|
|
|
def test_get_route_prefix(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_route_prefix)
|
|
|
|
# subclass may specify route prefix
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
route_prefix='widgets'):
|
|
self.assertEqual(mod.MasterView.get_route_prefix(), 'widgets')
|
|
|
|
# subclass may specify *normalized* model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name_normalized='blaster'):
|
|
self.assertEqual(mod.MasterView.get_route_prefix(), 'blasters')
|
|
|
|
# or it may specify *standard* model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name = 'Dinosaur'):
|
|
self.assertEqual(mod.MasterView.get_route_prefix(), 'dinosaurs')
|
|
|
|
# or it may specify model class
|
|
MyModel = MagicMock(__name__='Truck')
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=MyModel):
|
|
self.assertEqual(mod.MasterView.get_route_prefix(), 'trucks')
|
|
|
|
def test_get_permission_prefix(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_permission_prefix)
|
|
|
|
# subclass may specify permission prefix
|
|
with patch.object(mod.MasterView, 'permission_prefix', new='widgets', create=True):
|
|
self.assertEqual(mod.MasterView.get_permission_prefix(), 'widgets')
|
|
|
|
# subclass may specify route prefix
|
|
with patch.object(mod.MasterView, 'route_prefix', new='widgets', create=True):
|
|
self.assertEqual(mod.MasterView.get_permission_prefix(), 'widgets')
|
|
|
|
# or it may specify model class
|
|
Truck = MagicMock(__name__='Truck')
|
|
with patch.object(mod.MasterView, 'model_class', new=Truck, create=True):
|
|
self.assertEqual(mod.MasterView.get_permission_prefix(), 'trucks')
|
|
|
|
def test_get_url_prefix(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_url_prefix)
|
|
|
|
# subclass may specify url prefix
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
url_prefix='/widgets'):
|
|
self.assertEqual(mod.MasterView.get_url_prefix(), '/widgets')
|
|
|
|
# or it may specify route prefix
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
route_prefix='trucks'):
|
|
self.assertEqual(mod.MasterView.get_url_prefix(), '/trucks')
|
|
|
|
# or it may specify *normalized* model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name_normalized='blaster'):
|
|
self.assertEqual(mod.MasterView.get_url_prefix(), '/blasters')
|
|
|
|
# or it may specify *standard* model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Dinosaur'):
|
|
self.assertEqual(mod.MasterView.get_url_prefix(), '/dinosaurs')
|
|
|
|
# or it may specify model class
|
|
MyModel = MagicMock(__name__='Machine')
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=MyModel):
|
|
self.assertEqual(mod.MasterView.get_url_prefix(), '/machines')
|
|
|
|
def test_get_instance_url_prefix(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_instance_url_prefix)
|
|
|
|
# typical example with url_prefix and simple key
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
url_prefix='/widgets',
|
|
model_key='uuid'):
|
|
self.assertEqual(mod.MasterView.get_instance_url_prefix(), '/widgets/{uuid}')
|
|
|
|
# typical example with composite key
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
url_prefix='/widgets',
|
|
model_key=('foo', 'bar')):
|
|
self.assertEqual(mod.MasterView.get_instance_url_prefix(), '/widgets/{foo}|{bar}')
|
|
|
|
def test_get_template_prefix(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_template_prefix)
|
|
|
|
# subclass may specify template prefix
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
template_prefix='/widgets'):
|
|
self.assertEqual(mod.MasterView.get_template_prefix(), '/widgets')
|
|
|
|
# or it may specify url prefix
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
url_prefix='/trees'):
|
|
self.assertEqual(mod.MasterView.get_template_prefix(), '/trees')
|
|
|
|
# or it may specify route prefix
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
route_prefix='trucks'):
|
|
self.assertEqual(mod.MasterView.get_template_prefix(), '/trucks')
|
|
|
|
# or it may specify *normalized* model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name_normalized='blaster'):
|
|
self.assertEqual(mod.MasterView.get_template_prefix(), '/blasters')
|
|
|
|
# or it may specify *standard* model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Dinosaur'):
|
|
self.assertEqual(mod.MasterView.get_template_prefix(), '/dinosaurs')
|
|
|
|
# or it may specify model class
|
|
MyModel = MagicMock(__name__='Machine')
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=MyModel):
|
|
self.assertEqual(mod.MasterView.get_template_prefix(), '/machines')
|
|
|
|
def test_get_grid_key(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_grid_key)
|
|
|
|
# subclass may specify grid key
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
grid_key='widgets'):
|
|
self.assertEqual(mod.MasterView.get_grid_key(), 'widgets')
|
|
|
|
# or it may specify route prefix
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
route_prefix='trucks'):
|
|
self.assertEqual(mod.MasterView.get_grid_key(), 'trucks')
|
|
|
|
# or it may specify *normalized* model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name_normalized='blaster'):
|
|
self.assertEqual(mod.MasterView.get_grid_key(), 'blasters')
|
|
|
|
# or it may specify *standard* model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Dinosaur'):
|
|
self.assertEqual(mod.MasterView.get_grid_key(), 'dinosaurs')
|
|
|
|
# or it may specify model class
|
|
MyModel = MagicMock(__name__='Machine')
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=MyModel):
|
|
self.assertEqual(mod.MasterView.get_grid_key(), 'machines')
|
|
|
|
def test_get_config_title(self):
|
|
|
|
# error by default (since no model class)
|
|
self.assertRaises(AttributeError, mod.MasterView.get_config_title)
|
|
|
|
# subclass may specify config title
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
config_title='Widgets'):
|
|
self.assertEqual(mod.MasterView.get_config_title(), "Widgets")
|
|
|
|
# subclass may specify *plural* model title
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_title_plural='People'):
|
|
self.assertEqual(mod.MasterView.get_config_title(), "People")
|
|
|
|
# or it may specify *singular* model title
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_title='Wutta Widget'):
|
|
self.assertEqual(mod.MasterView.get_config_title(), "Wutta Widgets")
|
|
|
|
# or it may specify model name
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Blaster'):
|
|
self.assertEqual(mod.MasterView.get_config_title(), "Blasters")
|
|
|
|
# or it may specify model class
|
|
MyModel = MagicMock(__name__='Dinosaur')
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=MyModel):
|
|
self.assertEqual(mod.MasterView.get_config_title(), "Dinosaurs")
|
|
|
|
##############################
|
|
# support methods
|
|
##############################
|
|
|
|
def test_get_class_hierarchy(self):
|
|
class MyView(mod.MasterView):
|
|
pass
|
|
|
|
view = MyView(self.request)
|
|
classes = view.get_class_hierarchy()
|
|
self.assertEqual(classes, [View, mod.MasterView, MyView])
|
|
|
|
def test_has_perm(self):
|
|
model = self.app.model
|
|
auth = self.app.get_auth_handler()
|
|
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Setting'):
|
|
view = self.make_view()
|
|
|
|
# anonymous user
|
|
self.assertFalse(view.has_perm('list'))
|
|
self.assertFalse(self.request.has_perm('list'))
|
|
|
|
# reset
|
|
del self.request.user_permissions
|
|
|
|
# make user with perms
|
|
barney = model.User(username='barney')
|
|
self.session.add(barney)
|
|
blokes = model.Role(name="Blokes")
|
|
self.session.add(blokes)
|
|
barney.roles.append(blokes)
|
|
auth.grant_permission(blokes, 'settings.list')
|
|
self.session.commit()
|
|
|
|
# this user has perms
|
|
self.request.user = barney
|
|
self.assertTrue(view.has_perm('list'))
|
|
self.assertTrue(self.request.has_perm('settings.list'))
|
|
|
|
def test_has_any_perm(self):
|
|
model = self.app.model
|
|
auth = self.app.get_auth_handler()
|
|
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Setting'):
|
|
view = self.make_view()
|
|
|
|
# anonymous user
|
|
self.assertFalse(view.has_any_perm('list', 'view'))
|
|
self.assertFalse(self.request.has_any_perm('settings.list', 'settings.view'))
|
|
|
|
# reset
|
|
del self.request.user_permissions
|
|
|
|
# make user with perms
|
|
barney = model.User(username='barney')
|
|
self.session.add(barney)
|
|
blokes = model.Role(name="Blokes")
|
|
self.session.add(blokes)
|
|
barney.roles.append(blokes)
|
|
auth.grant_permission(blokes, 'settings.view')
|
|
self.session.commit()
|
|
|
|
# this user has perms
|
|
self.request.user = barney
|
|
self.assertTrue(view.has_any_perm('list', 'view'))
|
|
self.assertTrue(self.request.has_any_perm('settings.list', 'settings.view'))
|
|
|
|
def test_make_button(self):
|
|
view = self.make_view()
|
|
|
|
# normal
|
|
html = view.make_button('click me')
|
|
self.assertIn('<b-button ', html)
|
|
self.assertIn('click me', html)
|
|
self.assertNotIn('is-primary', html)
|
|
|
|
# primary as primary
|
|
html = view.make_button('click me', primary=True)
|
|
self.assertIn('<b-button ', html)
|
|
self.assertIn('click me', html)
|
|
self.assertIn('is-primary', html)
|
|
|
|
# primary as variant
|
|
html = view.make_button('click me', variant='is-primary')
|
|
self.assertIn('<b-button ', html)
|
|
self.assertIn('click me', html)
|
|
self.assertIn('is-primary', html)
|
|
|
|
# primary as type
|
|
html = view.make_button('click me', type='is-primary')
|
|
self.assertIn('<b-button ', html)
|
|
self.assertIn('click me', html)
|
|
self.assertIn('is-primary', html)
|
|
|
|
def test_make_progress(self):
|
|
|
|
# basic
|
|
view = self.make_view()
|
|
self.request.session.id = 'mockid'
|
|
progress = view.make_progress('foo')
|
|
self.assertIsInstance(progress, SessionProgress)
|
|
|
|
def test_render_progress(self):
|
|
self.pyramid_config.add_route('progress', '/progress/{key}')
|
|
|
|
# sanity / coverage check
|
|
view = self.make_view()
|
|
progress = MagicMock()
|
|
response = view.render_progress(progress)
|
|
|
|
def test_render_to_response(self):
|
|
self.pyramid_config.include('wuttaweb.views.common')
|
|
self.pyramid_config.include('wuttaweb.views.auth')
|
|
self.pyramid_config.add_route('appinfo', '/appinfo/')
|
|
|
|
def widgets(request): return {}
|
|
self.pyramid_config.add_route('widgets', '/widgets/')
|
|
self.pyramid_config.add_view(widgets, route_name='widgets')
|
|
|
|
# basic sanity check using /master/index.mako
|
|
# (nb. it skips /widgets/index.mako since that doesn't exist)
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Widget',
|
|
creatable=False):
|
|
view = mod.MasterView(self.request)
|
|
response = view.render_to_response('index', {})
|
|
self.assertIsInstance(response, Response)
|
|
|
|
# basic sanity check using /appinfo/index.mako
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='AppInfo',
|
|
route_prefix='appinfo',
|
|
url_prefix='/appinfo',
|
|
creatable=False):
|
|
view = mod.MasterView(self.request)
|
|
response = view.render_to_response('index', {
|
|
# nb. grid is required for this template
|
|
'grid': MagicMock(),
|
|
})
|
|
self.assertIsInstance(response, Response)
|
|
|
|
# bad template name causes error
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Widget'):
|
|
self.assertRaises(IOError, view.render_to_response, 'nonexistent', {})
|
|
|
|
def test_get_index_title(self):
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_title_plural = "Wutta Widgets"):
|
|
view = mod.MasterView(self.request)
|
|
self.assertEqual(view.get_index_title(), "Wutta Widgets")
|
|
|
|
def test_collect_labels(self):
|
|
|
|
# no labels by default
|
|
view = self.make_view()
|
|
labels = view.collect_labels()
|
|
self.assertEqual(labels, {})
|
|
|
|
# labels come from all classes; subclass wins
|
|
with patch.object(View, 'labels', new={'foo': "Foo", 'bar': "Bar"}, create=True):
|
|
with patch.object(mod.MasterView, 'labels', new={'foo': "FOO FIGHTERS"}, create=True):
|
|
view = self.make_view()
|
|
labels = view.collect_labels()
|
|
self.assertEqual(labels, {'foo': "FOO FIGHTERS", 'bar': "Bar"})
|
|
|
|
def test_set_labels(self):
|
|
model = self.app.model
|
|
with patch.object(mod.MasterView, 'model_class', new=model.Setting, create=True):
|
|
|
|
# no labels by default
|
|
view = self.make_view()
|
|
grid = view.make_model_grid(session=self.session)
|
|
view.set_labels(grid)
|
|
self.assertEqual(grid.labels, {})
|
|
|
|
# labels come from all classes; subclass wins
|
|
with patch.object(mod.MasterView, 'labels', new={'name': "SETTING NAME"}, create=True):
|
|
view = self.make_view()
|
|
view.set_labels(grid)
|
|
self.assertEqual(grid.labels, {'name': "SETTING NAME"})
|
|
|
|
def test_make_model_grid(self):
|
|
self.pyramid_config.add_route('settings.delete_bulk', '/settings/delete-bulk')
|
|
model = self.app.model
|
|
|
|
# no model class
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Widget',
|
|
model_key='uuid'):
|
|
view = mod.MasterView(self.request)
|
|
grid = view.make_model_grid()
|
|
self.assertIsNone(grid.model_class)
|
|
|
|
# explicit model class
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
grid = view.make_model_grid(session=self.session)
|
|
self.assertIs(grid.model_class, model.Setting)
|
|
|
|
# no row class by default
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
grid = view.make_model_grid(session=self.session)
|
|
self.assertIsNone(grid.row_class)
|
|
|
|
# can specify row class
|
|
get_row_class = MagicMock()
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting,
|
|
grid_row_class=get_row_class):
|
|
grid = view.make_model_grid(session=self.session)
|
|
self.assertIs(grid.row_class, get_row_class)
|
|
|
|
# no actions by default
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
grid = view.make_model_grid(session=self.session)
|
|
self.assertEqual(grid.actions, [])
|
|
|
|
# now let's test some more actions logic
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting,
|
|
viewable=True,
|
|
editable=True,
|
|
deletable=True):
|
|
|
|
# should have 3 actions now, but for lack of perms
|
|
grid = view.make_model_grid(session=self.session)
|
|
self.assertEqual(len(grid.actions), 0)
|
|
|
|
# but root user has perms, so gets 3 actions
|
|
with patch.object(self.request, 'is_root', new=True):
|
|
grid = view.make_model_grid(session=self.session)
|
|
self.assertEqual(len(grid.actions), 3)
|
|
|
|
# no tools by default
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
grid = view.make_model_grid(session=self.session)
|
|
self.assertEqual(grid.tools, {})
|
|
|
|
# delete-results tool added if master/perms allow
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting,
|
|
deletable_bulk=True):
|
|
with patch.object(self.request, 'is_root', new=True):
|
|
grid = view.make_model_grid(session=self.session)
|
|
self.assertIn('delete-results', grid.tools)
|
|
|
|
def test_get_grid_data(self):
|
|
model = self.app.model
|
|
self.app.save_setting(self.session, 'foo', 'bar')
|
|
self.session.commit()
|
|
setting = self.session.query(model.Setting).one()
|
|
view = self.make_view()
|
|
|
|
# empty by default
|
|
self.assertFalse(hasattr(mod.MasterView, 'model_class'))
|
|
data = view.get_grid_data(session=self.session)
|
|
self.assertEqual(data, [])
|
|
|
|
# grid with model class will produce data query
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
view = mod.MasterView(self.request)
|
|
query = view.get_grid_data(session=self.session)
|
|
self.assertIsInstance(query, orm.Query)
|
|
data = query.all()
|
|
self.assertEqual(len(data), 1)
|
|
self.assertIs(data[0], setting)
|
|
|
|
def test_configure_grid(self):
|
|
model = self.app.model
|
|
|
|
# uuid field is pruned
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
view = mod.MasterView(self.request)
|
|
grid = view.make_grid(model_class=model.Setting,
|
|
columns=['uuid', 'name', 'value'])
|
|
self.assertIn('uuid', grid.columns)
|
|
view.configure_grid(grid)
|
|
self.assertNotIn('uuid', grid.columns)
|
|
|
|
def test_grid_render_bool(self):
|
|
model = self.app.model
|
|
view = self.make_view()
|
|
user = model.User(username='barney', active=None)
|
|
|
|
# null
|
|
value = view.grid_render_bool(user, 'active', None)
|
|
self.assertIsNone(value)
|
|
|
|
# true
|
|
user.active = True
|
|
value = view.grid_render_bool(user, 'active', True)
|
|
self.assertEqual(value, "Yes")
|
|
|
|
# false
|
|
user.active = False
|
|
value = view.grid_render_bool(user, 'active', False)
|
|
self.assertEqual(value, "No")
|
|
|
|
def test_grid_render_currency(self):
|
|
view = self.make_view()
|
|
obj = {'amount': None}
|
|
|
|
# null
|
|
value = view.grid_render_currency(obj, 'amount', None)
|
|
self.assertIsNone(value)
|
|
|
|
# normal amount
|
|
obj['amount'] = decimal.Decimal('100.42')
|
|
value = view.grid_render_currency(obj, 'amount', '100.42')
|
|
self.assertEqual(value, "$100.42")
|
|
|
|
# negative amount
|
|
obj['amount'] = decimal.Decimal('-100.42')
|
|
value = view.grid_render_currency(obj, 'amount', '-100.42')
|
|
self.assertEqual(value, "($100.42)")
|
|
|
|
def test_grid_render_datetime(self):
|
|
view = self.make_view()
|
|
obj = {'dt': None}
|
|
|
|
# null
|
|
value = view.grid_render_datetime(obj, 'dt', None)
|
|
self.assertIsNone(value)
|
|
|
|
# normal
|
|
obj['dt'] = datetime.datetime(2024, 8, 24, 11)
|
|
value = view.grid_render_datetime(obj, 'dt', '2024-08-24T11:00:00')
|
|
self.assertEqual(value, '2024-08-24 11:00:00 AM')
|
|
|
|
def test_grid_render_enum(self):
|
|
enum = self.app.enum
|
|
view = self.make_view()
|
|
obj = {'status': None}
|
|
|
|
# null
|
|
value = view.grid_render_enum(obj, 'status', None, enum=enum.UpgradeStatus)
|
|
self.assertIsNone(value)
|
|
|
|
# normal
|
|
obj['status'] = enum.UpgradeStatus.SUCCESS
|
|
value = view.grid_render_enum(obj, 'status', 'SUCCESS', enum=enum.UpgradeStatus)
|
|
self.assertEqual(value, 'SUCCESS')
|
|
|
|
def test_grid_render_notes(self):
|
|
model = self.app.model
|
|
view = self.make_view()
|
|
|
|
# null
|
|
text = None
|
|
role = model.Role(name="Foo", notes=text)
|
|
value = view.grid_render_notes(role, 'notes', text)
|
|
self.assertIsNone(value)
|
|
|
|
# short string
|
|
text = "hello world"
|
|
role = model.Role(name="Foo", notes=text)
|
|
value = view.grid_render_notes(role, 'notes', text)
|
|
self.assertEqual(value, text)
|
|
|
|
# long string
|
|
text = "hello world " * 20
|
|
role = model.Role(name="Foo", notes=text)
|
|
value = view.grid_render_notes(role, 'notes', text)
|
|
self.assertIn('<span ', value)
|
|
|
|
def test_get_instance(self):
|
|
model = self.app.model
|
|
self.app.save_setting(self.session, 'foo', 'bar')
|
|
self.session.commit()
|
|
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
|
|
|
# default not implemented
|
|
view = mod.MasterView(self.request)
|
|
self.assertRaises(NotImplementedError, view.get_instance)
|
|
|
|
# fetch from DB if model class is known
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
view = mod.MasterView(self.request)
|
|
|
|
# existing setting is returned
|
|
self.request.matchdict = {'name': 'foo'}
|
|
setting = view.get_instance(session=self.session)
|
|
self.assertIsInstance(setting, model.Setting)
|
|
self.assertEqual(setting.name, 'foo')
|
|
self.assertEqual(setting.value, 'bar')
|
|
|
|
# missing setting not found
|
|
self.request.matchdict = {'name': 'blarg'}
|
|
self.assertRaises(HTTPNotFound, view.get_instance, session=self.session)
|
|
|
|
def test_get_action_url_view(self):
|
|
model = self.app.model
|
|
setting = model.Setting(name='foo', value='bar')
|
|
self.session.add(setting)
|
|
self.session.commit()
|
|
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
mod.MasterView.defaults(self.pyramid_config)
|
|
view = self.make_view()
|
|
url = view.get_action_url_view(setting, 0)
|
|
self.assertEqual(url, self.request.route_url('settings.view', name='foo'))
|
|
|
|
def test_get_action_url_edit(self):
|
|
model = self.app.model
|
|
setting = model.Setting(name='foo', value='bar')
|
|
self.session.add(setting)
|
|
self.session.commit()
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
mod.MasterView.defaults(self.pyramid_config)
|
|
view = self.make_view()
|
|
|
|
# typical
|
|
url = view.get_action_url_edit(setting, 0)
|
|
self.assertEqual(url, self.request.route_url('settings.edit', name='foo'))
|
|
|
|
# but null if instance not editable
|
|
with patch.object(view, 'is_editable', return_value=False):
|
|
url = view.get_action_url_edit(setting, 0)
|
|
self.assertIsNone(url)
|
|
|
|
def test_get_action_url_delete(self):
|
|
model = self.app.model
|
|
setting = model.Setting(name='foo', value='bar')
|
|
self.session.add(setting)
|
|
self.session.commit()
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
mod.MasterView.defaults(self.pyramid_config)
|
|
view = self.make_view()
|
|
|
|
# typical
|
|
url = view.get_action_url_delete(setting, 0)
|
|
self.assertEqual(url, self.request.route_url('settings.delete', name='foo'))
|
|
|
|
# but null if instance not deletable
|
|
with patch.object(view, 'is_deletable', return_value=False):
|
|
url = view.get_action_url_delete(setting, 0)
|
|
self.assertIsNone(url)
|
|
|
|
def test_make_model_form(self):
|
|
model = self.app.model
|
|
|
|
# no model class
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Widget',
|
|
model_key='uuid'):
|
|
view = mod.MasterView(self.request)
|
|
form = view.make_model_form()
|
|
self.assertIsNone(form.model_class)
|
|
|
|
# explicit model class
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
form = view.make_model_form()
|
|
self.assertIs(form.model_class, model.Setting)
|
|
|
|
def test_configure_form(self):
|
|
model = self.app.model
|
|
|
|
# uuid field is pruned
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
view = mod.MasterView(self.request)
|
|
form = view.make_form(model_class=model.Setting,
|
|
fields=['uuid', 'name', 'value'])
|
|
self.assertIn('uuid', form.fields)
|
|
view.configure_form(form)
|
|
self.assertNotIn('uuid', form.fields)
|
|
|
|
def test_objectify(self):
|
|
model = self.app.model
|
|
self.app.save_setting(self.session, 'foo', 'bar')
|
|
self.session.commit()
|
|
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
|
|
|
# no model class
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Widget',
|
|
model_key='uuid'):
|
|
view = mod.MasterView(self.request)
|
|
form = view.make_model_form(fields=['name', 'description'])
|
|
form.validated = {'name': 'first'}
|
|
obj = view.objectify(form)
|
|
self.assertIs(obj, form.validated)
|
|
|
|
# explicit model class (editing)
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting,
|
|
editing=True):
|
|
form = view.make_model_form()
|
|
form.validated = {'name': 'foo', 'value': 'blarg'}
|
|
form.model_instance = self.session.query(model.Setting).one()
|
|
obj = view.objectify(form)
|
|
self.assertIsInstance(obj, model.Setting)
|
|
self.assertEqual(obj.name, 'foo')
|
|
self.assertEqual(obj.value, 'blarg')
|
|
|
|
# explicit model class (creating)
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting,
|
|
creating=True):
|
|
form = view.make_model_form()
|
|
form.validated = {'name': 'another', 'value': 'whatever'}
|
|
obj = view.objectify(form)
|
|
self.assertIsInstance(obj, model.Setting)
|
|
self.assertEqual(obj.name, 'another')
|
|
self.assertEqual(obj.value, 'whatever')
|
|
|
|
def test_persist(self):
|
|
model = self.app.model
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
view = mod.MasterView(self.request)
|
|
|
|
# new instance is persisted
|
|
setting = model.Setting(name='foo', value='bar')
|
|
self.assertEqual(self.session.query(model.Setting).count(), 0)
|
|
view.persist(setting, session=self.session)
|
|
self.session.commit()
|
|
setting = self.session.query(model.Setting).one()
|
|
self.assertEqual(setting.name, 'foo')
|
|
self.assertEqual(setting.value, 'bar')
|
|
|
|
##############################
|
|
# view methods
|
|
##############################
|
|
|
|
def test_index(self):
|
|
self.pyramid_config.include('wuttaweb.views.common')
|
|
self.pyramid_config.include('wuttaweb.views.auth')
|
|
self.pyramid_config.add_route('settings.create', '/settings/new')
|
|
self.pyramid_config.add_route('settings.view', '/settings/{name}')
|
|
self.pyramid_config.add_route('settings.edit', '/settings/{name}/edit')
|
|
self.pyramid_config.add_route('settings.delete', '/settings/{name}/delete')
|
|
|
|
# sanity/coverage check using /settings/
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Setting',
|
|
model_key='name',
|
|
get_index_url=MagicMock(return_value='/settings/'),
|
|
grid_columns=['name', 'value']):
|
|
view = mod.MasterView(self.request)
|
|
response = view.index()
|
|
|
|
# then again with data, to include view action url
|
|
data = [{'name': 'foo', 'value': 'bar'}]
|
|
with patch.object(view, 'get_grid_data', return_value=data):
|
|
response = view.index()
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.content_type, 'text/html')
|
|
|
|
# then once more as 'partial' - aka. data only
|
|
self.request.GET = {'partial': '1'}
|
|
response = view.index()
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.content_type, 'application/json')
|
|
|
|
# redirects when view is reset
|
|
self.request.GET = {'reset-view': '1', 'hash': 'foo'}
|
|
with patch.object(self.request, 'current_route_url'):
|
|
response = view.index()
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
def test_create(self):
|
|
self.pyramid_config.include('wuttaweb.views.common')
|
|
self.pyramid_config.include('wuttaweb.views.auth')
|
|
self.pyramid_config.add_route('settings.view', '/settings/{name}')
|
|
model = self.app.model
|
|
|
|
# sanity/coverage check using /settings/new
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Setting',
|
|
model_key='name',
|
|
get_index_url=MagicMock(return_value='/settings/'),
|
|
form_fields=['name', 'value']):
|
|
view = mod.MasterView(self.request)
|
|
|
|
# no setting yet
|
|
self.assertIsNone(self.app.get_setting(self.session, 'foo.bar'))
|
|
|
|
# get the form page
|
|
response = view.create()
|
|
self.assertIsInstance(response, Response)
|
|
self.assertEqual(response.status_code, 200)
|
|
# self.assertIn('frazzle', response.text)
|
|
# nb. no error
|
|
self.assertNotIn('Required', response.text)
|
|
|
|
def persist(setting):
|
|
self.app.save_setting(self.session, setting['name'], setting['value'])
|
|
self.session.commit()
|
|
|
|
# post request to save setting
|
|
self.request.method = 'POST'
|
|
self.request.POST = {
|
|
'name': 'foo.bar',
|
|
'value': 'fraggle',
|
|
}
|
|
with patch.object(view, 'persist', new=persist):
|
|
response = view.create()
|
|
# nb. should get redirect back to view page
|
|
self.assertEqual(response.status_code, 302)
|
|
# setting should now be in DB
|
|
self.assertEqual(self.app.get_setting(self.session, 'foo.bar'), 'fraggle')
|
|
|
|
# try another post with invalid data (value is required)
|
|
self.request.method = 'POST'
|
|
self.request.POST = {}
|
|
with patch.object(view, 'persist', new=persist):
|
|
response = view.create()
|
|
# nb. should get a form with errors
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertIn('Required', response.text)
|
|
# setting did not change in DB
|
|
self.assertEqual(self.app.get_setting(self.session, 'foo.bar'), 'fraggle')
|
|
|
|
def test_view(self):
|
|
self.pyramid_config.include('wuttaweb.views.common')
|
|
self.pyramid_config.include('wuttaweb.views.auth')
|
|
self.pyramid_config.add_route('settings.create', '/settings/new')
|
|
self.pyramid_config.add_route('settings.edit', '/settings/{name}/edit')
|
|
self.pyramid_config.add_route('settings.delete', '/settings/{name}/delete')
|
|
|
|
# sanity/coverage check using /settings/XXX
|
|
setting = {'name': 'foo.bar', 'value': 'baz'}
|
|
self.request.matchdict = {'name': 'foo.bar'}
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Setting',
|
|
model_key='name',
|
|
get_index_url=MagicMock(return_value='/settings/'),
|
|
grid_columns=['name', 'value'],
|
|
form_fields=['name', 'value']):
|
|
view = mod.MasterView(self.request)
|
|
with patch.object(view, 'get_instance', return_value=setting):
|
|
response = view.view()
|
|
|
|
def test_edit(self):
|
|
self.pyramid_config.include('wuttaweb.views.common')
|
|
self.pyramid_config.include('wuttaweb.views.auth')
|
|
self.pyramid_config.add_route('settings.create', '/settings/new')
|
|
self.pyramid_config.add_route('settings.view', '/settings/{name}')
|
|
self.pyramid_config.add_route('settings.delete', '/settings/{name}/delete')
|
|
model = self.app.model
|
|
self.app.save_setting(self.session, 'foo.bar', 'frazzle')
|
|
self.session.commit()
|
|
|
|
def get_instance():
|
|
setting = self.session.query(model.Setting).get('foo.bar')
|
|
return {
|
|
'name': setting.name,
|
|
'value': setting.value,
|
|
}
|
|
|
|
# sanity/coverage check using /settings/XXX/edit
|
|
self.request.matchdict = {'name': 'foo.bar'}
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Setting',
|
|
model_key='name',
|
|
get_index_url=MagicMock(return_value='/settings/'),
|
|
form_fields=['name', 'value']):
|
|
view = mod.MasterView(self.request)
|
|
with patch.object(view, 'get_instance', new=get_instance):
|
|
|
|
# get the form page
|
|
response = view.edit()
|
|
self.assertIsInstance(response, Response)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertIn('frazzle', response.text)
|
|
# nb. no error
|
|
self.assertNotIn('Required', response.text)
|
|
|
|
def persist(setting):
|
|
self.app.save_setting(self.session, 'foo.bar', setting['value'])
|
|
self.session.commit()
|
|
|
|
# post request to save settings
|
|
self.request.method = 'POST'
|
|
self.request.POST = {
|
|
'name': 'foo.bar',
|
|
'value': 'froogle',
|
|
}
|
|
with patch.object(view, 'persist', new=persist):
|
|
response = view.edit()
|
|
# nb. should get redirect back to view page
|
|
self.assertEqual(response.status_code, 302)
|
|
# setting should be updated in DB
|
|
self.assertEqual(self.app.get_setting(self.session, 'foo.bar'), 'froogle')
|
|
|
|
# try another post with invalid data (value is required)
|
|
self.request.method = 'POST'
|
|
self.request.POST = {}
|
|
with patch.object(view, 'persist', new=persist):
|
|
response = view.edit()
|
|
# nb. should get a form with errors
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertIn('Required', response.text)
|
|
# setting did not change in DB
|
|
self.assertEqual(self.app.get_setting(self.session, 'foo.bar'), 'froogle')
|
|
|
|
def test_delete(self):
|
|
self.pyramid_config.include('wuttaweb.views.common')
|
|
self.pyramid_config.include('wuttaweb.views.auth')
|
|
self.pyramid_config.add_route('settings.create', '/settings/new')
|
|
self.pyramid_config.add_route('settings.view', '/settings/{name}')
|
|
self.pyramid_config.add_route('settings.edit', '/settings/{name}/edit')
|
|
model = self.app.model
|
|
self.app.save_setting(self.session, 'foo.bar', 'frazzle')
|
|
self.session.commit()
|
|
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
|
|
|
def get_instance():
|
|
setting = self.session.query(model.Setting).get('foo.bar')
|
|
return {
|
|
'name': setting.name,
|
|
'value': setting.value,
|
|
}
|
|
|
|
# sanity/coverage check using /settings/XXX/delete
|
|
self.request.matchdict = {'name': 'foo.bar'}
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='Setting',
|
|
model_key='name',
|
|
get_index_url=MagicMock(return_value='/settings/'),
|
|
form_fields=['name', 'value']):
|
|
view = mod.MasterView(self.request)
|
|
with patch.object(view, 'get_instance', new=get_instance):
|
|
|
|
# get the form page
|
|
response = view.delete()
|
|
self.assertIsInstance(response, Response)
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertIn('frazzle', response.text)
|
|
|
|
def delete_instance(setting):
|
|
self.app.delete_setting(self.session, setting['name'])
|
|
|
|
self.request.method = 'POST'
|
|
self.request.POST = {}
|
|
with patch.object(view, 'delete_instance', new=delete_instance):
|
|
|
|
# enforces "instance not deletable" rules
|
|
with patch.object(view, 'is_deletable', return_value=False):
|
|
response = view.delete()
|
|
# nb. should get redirect back to view page
|
|
self.assertEqual(response.status_code, 302)
|
|
# setting remains in DB
|
|
self.assertEqual(self.session.query(model.Setting).count(), 1)
|
|
|
|
# post request to delete setting
|
|
response = view.delete()
|
|
# nb. should get redirect back to view page
|
|
self.assertEqual(response.status_code, 302)
|
|
# setting should be gone from DB
|
|
self.assertEqual(self.session.query(model.Setting).count(), 0)
|
|
|
|
def test_delete_instance(self):
|
|
model = self.app.model
|
|
self.app.save_setting(self.session, 'foo.bar', 'frazzle')
|
|
self.session.commit()
|
|
setting = self.session.query(model.Setting).one()
|
|
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting,
|
|
form_fields=['name', 'value']):
|
|
view = mod.MasterView(self.request)
|
|
view.delete_instance(setting)
|
|
self.session.commit()
|
|
self.assertEqual(self.session.query(model.Setting).count(), 0)
|
|
|
|
def test_delete_bulk(self):
|
|
self.pyramid_config.add_route('settings', '/settings/')
|
|
self.pyramid_config.add_route('progress', '/progress/{key}')
|
|
model = self.app.model
|
|
sample_data = [
|
|
{'name': 'foo1', 'value': 'ONE'},
|
|
{'name': 'foo2', 'value': 'two'},
|
|
{'name': 'foo3', 'value': 'three'},
|
|
{'name': 'foo4', 'value': 'four'},
|
|
{'name': 'foo5', 'value': 'five'},
|
|
{'name': 'foo6', 'value': 'six'},
|
|
{'name': 'foo7', 'value': 'seven'},
|
|
{'name': 'foo8', 'value': 'eight'},
|
|
{'name': 'foo9', 'value': 'nine'},
|
|
]
|
|
for setting in sample_data:
|
|
self.app.save_setting(self.session, setting['name'], setting['value'])
|
|
self.session.commit()
|
|
sample_query = self.session.query(model.Setting)
|
|
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
view = self.make_view()
|
|
|
|
# sanity check on sample data
|
|
grid = view.make_model_grid(session=self.session)
|
|
data = grid.get_visible_data()
|
|
self.assertEqual(len(data), 9)
|
|
|
|
# and then let's filter it a little
|
|
self.request.GET = {'value': 's', 'value.verb': 'contains'}
|
|
grid = view.make_model_grid(session=self.session)
|
|
self.assertEqual(len(grid.filters), 2)
|
|
self.assertEqual(len(grid.active_filters), 1)
|
|
data = grid.get_visible_data()
|
|
self.assertEqual(len(data), 2)
|
|
|
|
# okay now let's delete those via quick method
|
|
# (user should be redirected back to index)
|
|
with patch.multiple(view,
|
|
deletable_bulk_quick=True,
|
|
make_model_grid=MagicMock(return_value=grid)):
|
|
response = view.delete_bulk()
|
|
self.assertEqual(response.status_code, 302)
|
|
self.assertEqual(self.session.query(model.Setting).count(), 7)
|
|
|
|
# now use another filter since those records are gone
|
|
self.request.GET = {'name': 'foo2', 'name.verb': 'equal'}
|
|
grid = view.make_model_grid(session=self.session)
|
|
self.assertEqual(len(grid.filters), 2)
|
|
self.assertEqual(len(grid.active_filters), 1)
|
|
data = grid.get_visible_data()
|
|
self.assertEqual(len(data), 1)
|
|
|
|
# this time we delete "slowly" with progress
|
|
self.request.session.id = 'ignorethis'
|
|
with patch.multiple(view,
|
|
deletable_bulk_quick=False,
|
|
make_model_grid=MagicMock(return_value=grid)):
|
|
with patch.object(mod, 'threading') as threading:
|
|
response = view.delete_bulk()
|
|
threading.Thread.return_value.start.assert_called_once_with()
|
|
# nb. user is shown progress page
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_delete_bulk_action(self):
|
|
self.pyramid_config.add_route('settings', '/settings/')
|
|
model = self.app.model
|
|
sample_data = [
|
|
{'name': 'foo1', 'value': 'ONE'},
|
|
{'name': 'foo2', 'value': 'two'},
|
|
{'name': 'foo3', 'value': 'three'},
|
|
{'name': 'foo4', 'value': 'four'},
|
|
{'name': 'foo5', 'value': 'five'},
|
|
{'name': 'foo6', 'value': 'six'},
|
|
{'name': 'foo7', 'value': 'seven'},
|
|
{'name': 'foo8', 'value': 'eight'},
|
|
{'name': 'foo9', 'value': 'nine'},
|
|
]
|
|
for setting in sample_data:
|
|
self.app.save_setting(self.session, setting['name'], setting['value'])
|
|
self.session.commit()
|
|
sample_query = self.session.query(model.Setting)
|
|
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
view = self.make_view()
|
|
|
|
# basic bulk delete
|
|
self.assertEqual(self.session.query(model.Setting).count(), 9)
|
|
settings = self.session.query(model.Setting)\
|
|
.filter(model.Setting.value.ilike('%s%'))\
|
|
.all()
|
|
self.assertEqual(len(settings), 2)
|
|
view.delete_bulk_action(settings)
|
|
self.session.commit()
|
|
self.assertEqual(self.session.query(model.Setting).count(), 7)
|
|
|
|
def test_delete_bulk_thread(self):
|
|
self.pyramid_config.add_route('settings', '/settings/')
|
|
model = self.app.model
|
|
sample_data = [
|
|
{'name': 'foo1', 'value': 'ONE'},
|
|
{'name': 'foo2', 'value': 'two'},
|
|
{'name': 'foo3', 'value': 'three'},
|
|
{'name': 'foo4', 'value': 'four'},
|
|
{'name': 'foo5', 'value': 'five'},
|
|
{'name': 'foo6', 'value': 'six'},
|
|
{'name': 'foo7', 'value': 'seven'},
|
|
{'name': 'foo8', 'value': 'eight'},
|
|
{'name': 'foo9', 'value': 'nine'},
|
|
]
|
|
for setting in sample_data:
|
|
self.app.save_setting(self.session, setting['name'], setting['value'])
|
|
self.session.commit()
|
|
sample_query = self.session.query(model.Setting)
|
|
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting):
|
|
view = self.make_view()
|
|
|
|
# basic delete, no progress
|
|
self.assertEqual(self.session.query(model.Setting).count(), 9)
|
|
settings = self.session.query(model.Setting)\
|
|
.filter(model.Setting.value.ilike('%s%'))
|
|
self.assertEqual(settings.count(), 2)
|
|
with patch.object(self.app, 'make_session', return_value=self.session):
|
|
view.delete_bulk_thread(settings)
|
|
self.assertEqual(self.session.query(model.Setting).count(), 7)
|
|
|
|
# basic delete, with progress
|
|
settings = self.session.query(model.Setting)\
|
|
.filter(model.Setting.name == 'foo1')
|
|
self.assertEqual(settings.count(), 1)
|
|
with patch.object(self.app, 'make_session', return_value=self.session):
|
|
view.delete_bulk_thread(settings, progress=MagicMock())
|
|
self.assertEqual(self.session.query(model.Setting).count(), 6)
|
|
|
|
# error, no progress
|
|
settings = self.session.query(model.Setting)\
|
|
.filter(model.Setting.name == 'foo2')
|
|
self.assertEqual(settings.count(), 1)
|
|
with patch.object(self.app, 'make_session', return_value=self.session):
|
|
with patch.object(view, 'delete_bulk_action', side_effect=RuntimeError):
|
|
view.delete_bulk_thread(settings)
|
|
# nb. nothing was deleted
|
|
self.assertEqual(self.session.query(model.Setting).count(), 6)
|
|
|
|
# error, with progress
|
|
self.assertEqual(settings.count(), 1)
|
|
with patch.object(self.app, 'make_session', return_value=self.session):
|
|
with patch.object(view, 'delete_bulk_action', side_effect=RuntimeError):
|
|
view.delete_bulk_thread(settings, progress=MagicMock())
|
|
# nb. nothing was deleted
|
|
self.assertEqual(self.session.query(model.Setting).count(), 6)
|
|
|
|
def test_autocomplete(self):
|
|
model = self.app.model
|
|
|
|
person1 = model.Person(full_name="George Jones")
|
|
self.session.add(person1)
|
|
person2 = model.Person(full_name="George Strait")
|
|
self.session.add(person2)
|
|
self.session.commit()
|
|
|
|
# no results for empty term
|
|
self.request.GET = {}
|
|
view = self.make_view()
|
|
results = view.autocomplete()
|
|
self.assertEqual(len(results), 0)
|
|
|
|
# search yields no results
|
|
self.request.GET = {'term': 'sally'}
|
|
view = self.make_view()
|
|
with patch.object(view, 'autocomplete_data', return_value=[]):
|
|
view = self.make_view()
|
|
results = view.autocomplete()
|
|
self.assertEqual(len(results), 0)
|
|
|
|
# search yields 2 results
|
|
self.request.GET = {'term': 'george'}
|
|
view = self.make_view()
|
|
with patch.object(view, 'autocomplete_data', return_value=[person1, person2]):
|
|
results = view.autocomplete()
|
|
self.assertEqual(len(results), 2)
|
|
self.assertEqual([res['value'] for res in results],
|
|
[p.uuid for p in [person1, person2]])
|
|
|
|
def test_autocomplete_normalize(self):
|
|
model = self.app.model
|
|
view = self.make_view()
|
|
|
|
person = model.Person(full_name="Betty Boop", uuid='bogus')
|
|
normal = view.autocomplete_normalize(person)
|
|
self.assertEqual(normal, {'value': 'bogus',
|
|
'label': "Betty Boop"})
|
|
|
|
def test_download(self):
|
|
model = self.app.model
|
|
self.app.save_setting(self.session, 'foo', 'bar')
|
|
self.session.commit()
|
|
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting,
|
|
model_key='name',
|
|
Session=MagicMock(return_value=self.session)):
|
|
view = self.make_view()
|
|
self.request.matchdict = {'name': 'foo'}
|
|
|
|
# 404 if no filename
|
|
response = view.download()
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
# 404 if bad filename
|
|
self.request.GET = {'filename': 'doesnotexist'}
|
|
response = view.download()
|
|
self.assertEqual(response.status_code, 404)
|
|
|
|
# 200 if good filename
|
|
foofile = self.write_file('foo.txt', 'foo')
|
|
with patch.object(view, 'download_path', return_value=foofile):
|
|
response = view.download()
|
|
self.assertEqual(response.status_code, 200)
|
|
self.assertEqual(response.content_disposition, 'attachment; filename="foo.txt"')
|
|
|
|
def test_execute(self):
|
|
self.pyramid_config.add_route('settings.view', '/settings/{name}')
|
|
self.pyramid_config.add_route('progress', '/progress/{key}')
|
|
model = self.app.model
|
|
self.app.save_setting(self.session, 'foo', 'bar')
|
|
user = model.User(username='barney')
|
|
self.session.add(user)
|
|
self.session.commit()
|
|
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Setting,
|
|
model_key='name',
|
|
Session=MagicMock(return_value=self.session)):
|
|
view = self.make_view()
|
|
self.request.matchdict = {'name': 'foo'}
|
|
self.request.session.id = 'mockid'
|
|
self.request.user = user
|
|
|
|
# basic usage; user is shown progress page
|
|
with patch.object(mod, 'threading') as threading:
|
|
response = view.execute()
|
|
threading.Thread.return_value.start.assert_called_once_with()
|
|
self.assertEqual(response.status_code, 200)
|
|
|
|
def test_execute_thread(self):
|
|
model = self.app.model
|
|
enum = self.app.enum
|
|
user = model.User(username='barney')
|
|
self.session.add(user)
|
|
upgrade = model.Upgrade(description='test', created_by=user,
|
|
status=enum.UpgradeStatus.PENDING)
|
|
self.session.add(upgrade)
|
|
self.session.commit()
|
|
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_class=model.Upgrade):
|
|
view = self.make_view()
|
|
|
|
# basic execute, no progress
|
|
with patch.object(view, 'execute_instance') as execute_instance:
|
|
view.execute_thread({'uuid': upgrade.uuid}, user.uuid)
|
|
execute_instance.assert_called_once()
|
|
|
|
# basic execute, with progress
|
|
with patch.object(view, 'execute_instance') as execute_instance:
|
|
progress = MagicMock()
|
|
view.execute_thread({'uuid': upgrade.uuid}, user.uuid, progress=progress)
|
|
execute_instance.assert_called_once()
|
|
progress.handle_success.assert_called_once_with()
|
|
|
|
# error, no progress
|
|
with patch.object(view, 'execute_instance') as execute_instance:
|
|
execute_instance.side_effect = RuntimeError
|
|
view.execute_thread({'uuid': upgrade.uuid}, user.uuid)
|
|
execute_instance.assert_called_once()
|
|
|
|
# error, with progress
|
|
with patch.object(view, 'execute_instance') as execute_instance:
|
|
progress = MagicMock()
|
|
execute_instance.side_effect = RuntimeError
|
|
view.execute_thread({'uuid': upgrade.uuid}, user.uuid, progress=progress)
|
|
execute_instance.assert_called_once()
|
|
progress.handle_error.assert_called_once()
|
|
|
|
def test_configure(self):
|
|
self.pyramid_config.include('wuttaweb.views.common')
|
|
self.pyramid_config.include('wuttaweb.views.auth')
|
|
model = self.app.model
|
|
|
|
# mock settings
|
|
settings = [
|
|
{'name': 'wutta.app_title'},
|
|
{'name': 'wutta.foo', 'value': 'bar'},
|
|
{'name': 'wutta.flag', 'type': bool},
|
|
{'name': 'wutta.number', 'type': int, 'default': 42},
|
|
{'name': 'wutta.value1', 'save_if_empty': True},
|
|
{'name': 'wutta.value2', 'save_if_empty': False},
|
|
]
|
|
|
|
view = mod.MasterView(self.request)
|
|
with patch.object(self.request, 'current_route_url', return_value='/appinfo/configure'):
|
|
with patch.object(mod, 'Session', return_value=self.session):
|
|
with patch.multiple(mod.MasterView, create=True,
|
|
model_name='AppInfo',
|
|
route_prefix='appinfo',
|
|
template_prefix='/appinfo',
|
|
creatable=False,
|
|
get_index_url=MagicMock(return_value='/appinfo/'),
|
|
configure_get_simple_settings=MagicMock(return_value=settings)):
|
|
|
|
# get the form page
|
|
response = view.configure(session=self.session)
|
|
self.assertIsInstance(response, Response)
|
|
|
|
# post request to save settings
|
|
self.request.method = 'POST'
|
|
self.request.POST = {
|
|
'wutta.app_title': 'Wutta',
|
|
'wutta.foo': 'bar',
|
|
'wutta.flag': 'true',
|
|
}
|
|
response = view.configure(session=self.session)
|
|
# nb. should get redirect back to configure page
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
# should now have 5 settings
|
|
count = self.session.query(model.Setting).count()
|
|
self.assertEqual(count, 5)
|
|
get_setting = functools.partial(self.app.get_setting, self.session)
|
|
self.assertEqual(get_setting('wutta.app_title'), 'Wutta')
|
|
self.assertEqual(get_setting('wutta.foo'), 'bar')
|
|
self.assertEqual(get_setting('wutta.flag'), 'true')
|
|
self.assertEqual(get_setting('wutta.number'), '42')
|
|
self.assertEqual(get_setting('wutta.value1'), '')
|
|
self.assertEqual(get_setting('wutta.value2'), None)
|
|
|
|
# post request to remove settings
|
|
self.request.method = 'POST'
|
|
self.request.POST = {'remove_settings': '1'}
|
|
response = view.configure(session=self.session)
|
|
# nb. should get redirect back to configure page
|
|
self.assertEqual(response.status_code, 302)
|
|
|
|
# should now have 0 settings
|
|
count = self.session.query(model.Setting).count()
|
|
self.assertEqual(count, 0)
|