3
0
Fork 0

feat: add basic support for execute upgrades, download stdout/stderr

upgrade progress is still not being shown yet
This commit is contained in:
Lance Edgar 2024-08-25 12:20:28 -05:00
parent 1a8900c9f4
commit e5e31a7d32
17 changed files with 805 additions and 12 deletions

View file

@ -50,6 +50,26 @@ class TestView(WebTestCase):
self.assertIsInstance(error, HTTPFound)
self.assertEqual(error.location, '/')
def test_file_response(self):
view = self.make_view()
# default uses attachment behavior
datfile = self.write_file('dat.txt', 'hello')
response = view.file_response(datfile)
self.assertEqual(response.status_code, 200)
self.assertEqual(response.content_disposition, 'attachment; filename="dat.txt"')
# but can disable attachment behavior
datfile = self.write_file('dat.txt', 'hello')
response = view.file_response(datfile, attachment=False)
self.assertEqual(response.status_code, 200)
self.assertIsNone(response.content_disposition)
# path not found
crapfile = '/does/not/exist'
response = view.file_response(crapfile)
self.assertEqual(response.status_code, 404)
def test_json_response(self):
view = self.make_view()
response = view.json_response({'foo': 'bar'})

View file

@ -30,6 +30,8 @@ class TestMasterView(WebTestCase):
model_key='uuid',
deletable_bulk=True,
has_autocomplete=True,
downloadable=True,
executable=True,
configurable=True):
mod.MasterView.defaults(self.pyramid_config)
@ -1310,6 +1312,59 @@ class TestMasterView(WebTestCase):
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}')
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'}
# basic usage, redirects to view obj url
response = view.execute()
self.assertEqual(response.status_code, 302)
self.assertEqual(self.request.session.pop_flash(), ["Setting was executed."])
# execution error
with patch.object(view, 'execute_instance', side_effect=RuntimeError):
response = view.execute()
self.assertEqual(response.status_code, 302)
self.assertEqual(self.request.session.pop_flash(), [])
self.assertEqual(self.request.session.pop_flash('error'), ["RuntimeError"])
def test_configure(self):
self.pyramid_config.include('wuttaweb.views.common')
self.pyramid_config.include('wuttaweb.views.auth')

View file

@ -1,9 +1,12 @@
# -*- coding: utf-8; -*-
import datetime
import os
import sys
from unittest.mock import patch, MagicMock
from wuttaweb.views import upgrades as mod
from wuttjamaican.exc import ConfigurationError
from tests.util import WebTestCase
@ -42,6 +45,7 @@ class TestUpgradeView(WebTestCase):
self.assertEqual(view.grid_row_class(upgrade, data, 1), 'has-background-warning')
def test_configure_form(self):
self.pyramid_config.add_route('upgrades.download', '/upgrades/{uuid}/download')
model = self.app.model
enum = self.app.enum
user = model.User(username='barney')
@ -66,7 +70,7 @@ class TestUpgradeView(WebTestCase):
view.configure_form(form)
self.assertNotIn('created', form)
# test executed field when viewing
# test executed, stdout/stderr when viewing
with patch.object(view, 'viewing', new=True):
# executed is *not* shown by default
@ -74,13 +78,18 @@ class TestUpgradeView(WebTestCase):
self.assertIn('executed', form)
view.configure_form(form)
self.assertNotIn('executed', form)
self.assertNotIn('stdout_file', form)
self.assertNotIn('stderr_file', form)
# but it *is* shown if upgrade is executed
upgrade.executed = datetime.datetime.now()
upgrade.status = enum.UpgradeStatus.SUCCESS
form = view.make_form(model_class=model.Upgrade, model_instance=upgrade)
self.assertIn('executed', form)
view.configure_form(form)
self.assertIn('executed', form)
self.assertIn('stdout_file', form)
self.assertIn('stderr_file', form)
def test_objectify(self):
model = self.app.model
@ -101,3 +110,167 @@ class TestUpgradeView(WebTestCase):
self.assertEqual(upgrade.description, "new one")
self.assertIs(upgrade.created_by, user)
self.assertEqual(upgrade.status, enum.UpgradeStatus.PENDING)
def test_download_path(self):
model = self.app.model
enum = self.app.enum
appdir = self.mkdir('app')
self.config.setdefault('wutta.appdir', appdir)
self.assertEqual(self.app.get_appdir(), appdir)
user = model.User(username='barney')
upgrade = model.Upgrade(description='test', created_by=user,
status=enum.UpgradeStatus.PENDING)
self.session.add(upgrade)
self.session.commit()
view = self.make_view()
uuid = upgrade.uuid
# no filename
path = view.download_path(upgrade, None)
self.assertIsNone(path)
# with filename
path = view.download_path(upgrade, 'foo.txt')
self.assertEqual(path, os.path.join(appdir, 'data', 'upgrades',
uuid[:2], uuid[2:], 'foo.txt'))
def test_get_upgrade_filepath(self):
model = self.app.model
enum = self.app.enum
appdir = self.mkdir('app')
self.config.setdefault('wutta.appdir', appdir)
self.assertEqual(self.app.get_appdir(), appdir)
user = model.User(username='barney')
upgrade = model.Upgrade(description='test', created_by=user,
status=enum.UpgradeStatus.PENDING)
self.session.add(upgrade)
self.session.commit()
view = self.make_view()
uuid = upgrade.uuid
# no filename
path = view.get_upgrade_filepath(upgrade)
self.assertEqual(path, os.path.join(appdir, 'data', 'upgrades',
uuid[:2], uuid[2:]))
# with filename
path = view.get_upgrade_filepath(upgrade, 'foo.txt')
self.assertEqual(path, os.path.join(appdir, 'data', 'upgrades',
uuid[:2], uuid[2:], 'foo.txt'))
def test_delete_instance(self):
model = self.app.model
enum = self.app.enum
appdir = self.mkdir('app')
self.config.setdefault('wutta.appdir', appdir)
self.assertEqual(self.app.get_appdir(), appdir)
user = model.User(username='barney')
upgrade = model.Upgrade(description='test', created_by=user,
status=enum.UpgradeStatus.PENDING)
self.session.add(upgrade)
self.session.commit()
view = self.make_view()
# mock stdout/stderr files
upgrade_dir = view.get_upgrade_filepath(upgrade)
stdout = view.get_upgrade_filepath(upgrade, 'stdout.log')
with open(stdout, 'w') as f:
f.write('stdout')
stderr = view.get_upgrade_filepath(upgrade, 'stderr.log')
with open(stderr, 'w') as f:
f.write('stderr')
# both upgrade and files are deleted
self.assertTrue(os.path.exists(upgrade_dir))
self.assertTrue(os.path.exists(stdout))
self.assertTrue(os.path.exists(stderr))
self.assertEqual(self.session.query(model.Upgrade).count(), 1)
with patch.object(view, 'Session', return_value=self.session):
view.delete_instance(upgrade)
self.assertFalse(os.path.exists(upgrade_dir))
self.assertFalse(os.path.exists(stdout))
self.assertFalse(os.path.exists(stderr))
self.assertEqual(self.session.query(model.Upgrade).count(), 0)
def test_execute_instance(self):
model = self.app.model
enum = self.app.enum
appdir = self.mkdir('app')
self.config.setdefault('wutta.appdir', appdir)
self.assertEqual(self.app.get_appdir(), appdir)
user = model.User(username='barney')
upgrade = model.Upgrade(description='test', created_by=user,
status=enum.UpgradeStatus.PENDING)
self.session.add(upgrade)
self.session.commit()
view = self.make_view()
self.request.user = user
python = sys.executable
# script not yet confiugred
self.assertRaises(ConfigurationError, view.execute_instance, upgrade)
# script w/ success
goodpy = self.write_file('good.py', """
import sys
sys.stdout.write('hello from good.py')
sys.exit(0)
""")
self.app.save_setting(self.session, 'wutta.upgrades.command', f'{python} {goodpy}')
self.assertIsNone(upgrade.executed)
self.assertIsNone(upgrade.executed_by)
self.assertEqual(upgrade.status, enum.UpgradeStatus.PENDING)
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.config, 'usedb', new=True):
view.execute_instance(upgrade)
self.assertIsNotNone(upgrade.executed)
self.assertIs(upgrade.executed_by, user)
self.assertEqual(upgrade.status, enum.UpgradeStatus.SUCCESS)
with open(view.get_upgrade_filepath(upgrade, 'stdout.log')) as f:
self.assertEqual(f.read(), 'hello from good.py')
with open(view.get_upgrade_filepath(upgrade, 'stderr.log')) as f:
self.assertEqual(f.read(), '')
# need a new record for next test
upgrade = model.Upgrade(description='test', created_by=user,
status=enum.UpgradeStatus.PENDING)
self.session.add(upgrade)
self.session.commit()
# script w/ failure
badpy = self.write_file('bad.py', """
import sys
sys.stderr.write('hello from bad.py')
sys.exit(42)
""")
self.app.save_setting(self.session, 'wutta.upgrades.command', f'{python} {badpy}')
self.assertIsNone(upgrade.executed)
self.assertIsNone(upgrade.executed_by)
self.assertEqual(upgrade.status, enum.UpgradeStatus.PENDING)
with patch.object(view, 'Session', return_value=self.session):
with patch.object(self.config, 'usedb', new=True):
view.execute_instance(upgrade)
self.assertIsNotNone(upgrade.executed)
self.assertIs(upgrade.executed_by, user)
self.assertEqual(upgrade.status, enum.UpgradeStatus.FAILURE)
with open(view.get_upgrade_filepath(upgrade, 'stdout.log')) as f:
self.assertEqual(f.read(), '')
with open(view.get_upgrade_filepath(upgrade, 'stderr.log')) as f:
self.assertEqual(f.read(), 'hello from bad.py')
def test_configure_get_simple_settings(self):
# sanity/coverage check
view = self.make_view()
simple = view.configure_get_simple_settings()