From e57e7f945eea61bf5164ee4e656b1a3d4a66ef55 Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Mon, 28 Nov 2022 16:44:01 +0100 Subject: [PATCH 2/3] testdef: add views to run tests (#71296) --- tests/admin_pages/test_tests.py | 216 ++++++++++++++++++ tests/test_testdef.py | 97 ++++++++ wcs/admin/forms.py | 6 + wcs/admin/tests.py | 173 ++++++++++++++ wcs/fields.py | 3 +- .../wcs/backoffice/test_results.html | 14 ++ wcs/templates/wcs/backoffice/tests.html | 38 +++ wcs/testdef.py | 105 ++++++++- 8 files changed, 650 insertions(+), 2 deletions(-) create mode 100644 tests/admin_pages/test_tests.py create mode 100644 tests/test_testdef.py create mode 100644 wcs/admin/tests.py create mode 100644 wcs/templates/wcs/backoffice/test_results.html create mode 100644 wcs/templates/wcs/backoffice/tests.html diff --git a/tests/admin_pages/test_tests.py b/tests/admin_pages/test_tests.py new file mode 100644 index 000000000..6e94fde87 --- /dev/null +++ b/tests/admin_pages/test_tests.py @@ -0,0 +1,216 @@ +import datetime +import json +import os + +import pytest +from django.utils.html import escape +from webtest import Upload + +from wcs import fields +from wcs.formdef import FormDef +from wcs.qommon.http_request import HTTPRequest +from wcs.testdef import TestDef + +from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login +from .test_all import create_superuser + + +@pytest.fixture +def pub(): + pub = create_temporary_pub() + + req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'}) + pub.set_app_dir(req) + pub.cfg['identification'] = {'methods': ['password']} + pub.write_cfg() + + if not pub.site_options.has_section('options'): + pub.site_options.add_section('options') + pub.site_options.set('options', 'enable-tests', 'true') + with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: + pub.site_options.write(fd) + + FormDef.wipe() + TestDef.wipe() + return pub + + +def teardown_module(module): + clean_temporary_pub() + + +def test_tests_link_on_formdef_page(pub): + formdef = FormDef() + formdef.name = 'test title' + formdef.store() + + create_superuser(pub) + app = login(get_app(pub)) + + resp = app.get(formdef.get_admin_url()) + assert 'Tests' in resp.text + + pub.site_options.set('options', 'enable-tests', 'false') + with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: + pub.site_options.write(fd) + + resp = app.get(formdef.get_admin_url()) + assert 'Tests' not in resp.text + + +def test_tests_page(pub): + user = create_superuser(pub) + + formdef = FormDef() + formdef.name = 'test title' + formdef.fields = [ + fields.PageField( + id='0', + label='1st page', + type='page', + post_conditions=[ + {'condition': {'type': 'django', 'value': 'form_var_text == "a"'}, 'error_message': 'Error'} + ], + ), + fields.StringField(id='1', label='Text', varname='text'), + ] + formdef.store() + + app = login(get_app(pub)) + + resp = app.get(formdef.get_admin_url()) + resp = resp.click('Tests') + assert 'There are no tests yet.' in resp.text + assert 'Run' not in resp.text + assert 'Tests cannot be created because there are no completed forms.' in resp.text + assert 'New' not in resp.text + + formdata = formdef.data_class()() + formdata.just_created() + formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() + formdata.data['1'] = 'a' + formdata.user_id = user.id + formdata.store() + + resp = app.get(formdef.get_admin_url()) + resp = resp.click('Tests') + assert 'There are no tests yet.' in resp.text + assert 'Run' not in resp.text + assert 'New tests cannot be created' not in resp.text + + resp = resp.click('New') + resp.form['name'] = 'First test' + resp.form['formdata_id'].select(text='1-1 - admin') + assert len(resp.form['formdata_id'].options) == 1 + + resp = resp.form.submit().follow() + assert 'First test' in resp.text + assert 'no tests yet' not in resp.text + + resp = resp.click('Run') + assert 'First test: Success!' in resp.text + + formdata = formdef.data_class()() + formdata.just_created() + formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() + formdata.data['1'] = 'b' + formdata.store() + + resp = app.get('/backoffice/forms/1/tests/new') + resp.form['name'] = 'Second test' + resp.form['formdata_id'].select(text='1-2 - Unknown User') + assert len(resp.form['formdata_id'].options) == 2 + + resp = resp.form.submit().follow() + assert 'First test' in resp.text + assert 'Second test' in resp.text + + resp = resp.click('Run') + assert 'First test: Success!' in resp.text + assert 'Second test: Page 1 post condition was not met (form_var_text == "a").' in resp.text + + +def test_tests_page_formdefs_isolation(pub): + formdef = FormDef() + formdef.name = 'dummy' + formdef.store() + + formdata = formdef.data_class()() + formdata.just_created() + formdata.store() + + other_formdef = FormDef() + other_formdef.name = 'dummy2' + other_formdef.store() + + formdata2 = other_formdef.data_class()() + formdata2.just_created() + formdata2.store() + + create_superuser(pub) + app = login(get_app(pub)) + + resp = app.get('/backoffice/forms/1/tests/new') + assert len(resp.form['formdata_id'].options) == 1 + assert resp.form['formdata_id'].options[0][2] == '1-1 - Unknown User' + + resp = app.get('/backoffice/forms/2/tests/new') + assert len(resp.form['formdata_id'].options) == 1 + assert resp.form['formdata_id'].options[0][2] == '2-1 - Unknown User' + + +def test_tests_import_export(pub): + user = create_superuser(pub) + + formdef = FormDef() + formdef.name = 'test title' + formdef.fields = [fields.StringField(id='1', varname='test field', label='Test', type='string')] + formdef.store() + + formdata = formdef.data_class()() + formdata.just_created() + formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() + formdata.data['1'] = 'a' + formdata.user_id = user.id + formdata.store() + + testdef = TestDef.create_from_formdata(formdef, formdata) + testdef.name = 'First test' + testdef.store() + + app = login(get_app(pub)) + + resp = app.get('/backoffice/forms/1/tests/') + export_resp = resp.click('First test') + + resp = resp.click('remove') + resp = resp.form.submit().follow() + assert 'First test' not in resp.text + + resp = resp.click('Import') + resp.form['file'] = Upload('export.json', export_resp.body, 'application/json') + resp = resp.form.submit().follow() + assert TestDef.count() == 1 + assert 'First test' in resp.text + assert escape('Test "First test" has been successfully imported.') in resp.text + + imported_testdef = TestDef.select()[0] + assert imported_testdef.export_to_json() == testdef.export_to_json() + + export_json = json.loads(export_resp.body) + export_json['data']['fields']['1'] = 'b' + + resp = resp.click('Import') + resp.form['file'] = Upload('export.json', json.dumps(export_json).encode(), 'application/json') + resp = resp.form.submit().follow() + assert TestDef.count() == 1 + assert 'First test' in resp.text + assert escape('Test "First test" has been successfully imported.') in resp.text + + imported_testdef.refresh_from_storage() + assert imported_testdef.data['fields']['1'] == 'b' + + resp = resp.click('Import') + resp.form['file'] = Upload('export.json', b'invalid', 'application/json') + resp = resp.form.submit() + assert 'Expecting value: line 1 column 1' in resp.text diff --git a/tests/test_testdef.py b/tests/test_testdef.py new file mode 100644 index 000000000..d1dbc3a3f --- /dev/null +++ b/tests/test_testdef.py @@ -0,0 +1,97 @@ +import datetime + +import pytest + +from wcs import fields +from wcs.formdef import FormDef +from wcs.qommon.http_request import HTTPRequest +from wcs.testdef import TestDef, TestError + +from .utilities import clean_temporary_pub, create_temporary_pub + + +@pytest.fixture +def pub(): + pub = create_temporary_pub() + + req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'}) + pub.set_app_dir(req) + pub.write_cfg() + + FormDef.wipe() + return pub + + +def teardown_module(module): + clean_temporary_pub() + + +def test_page_post_conditions(pub): + formdef = FormDef() + formdef.name = 'test title' + formdef.fields = [ + fields.TitleField(id='0', label='Title', type='title'), + fields.PageField( + id='1', + label='1st page', + type='page', + post_conditions=[ + {'condition': {'type': 'django', 'value': 'form_var_text == "a"'}, 'error_message': 'Error'} + ], + ), + fields.StringField(id='2', label='Text', varname='text'), + fields.PageField( + id='3', + label='2nd page', + type='page', + post_conditions=[ + {'condition': {'type': 'django', 'value': 'form_var_text2 == "a"'}, 'error_message': 'Error'} + ], + ), + fields.StringField(id='4', label='Text', varname='text2'), + ] + formdef.store() + + formdata = formdef.data_class()() + formdata.just_created() + formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() + formdata.data['2'] = 'a' + formdata.data['4'] = 'a' + + testdef = TestDef.create_from_formdata(formdef, formdata) + testdef.run(formdef) + + formdata.data['4'] = 'b' + testdef = TestDef.create_from_formdata(formdef, formdata) + with pytest.raises(TestError) as excinfo: + testdef.run(formdef) + assert str(excinfo.value) == 'Page 2 post condition was not met (form_var_text2 == "a").' + + formdata.data['2'] = 'b' + testdef = TestDef.create_from_formdata(formdef, formdata) + with pytest.raises(TestError) as excinfo: + testdef.run(formdef) + assert str(excinfo.value) == 'Page 1 post condition was not met (form_var_text == "a").' + + +def test_page_post_condition_invalid(pub): + formdef = FormDef() + formdef.name = 'test title' + formdef.fields = [ + fields.PageField( + id='0', + label='1st page', + type='page', + post_conditions=[{'condition': {'type': 'django', 'value': '{{}'}, 'error_message': 'Error'}], + ), + ] + formdef.store() + + formdata = formdef.data_class()() + formdata.just_created() + formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() + + testdef = TestDef.create_from_formdata(formdef, formdata) + with pytest.raises(TestError) as excinfo: + testdef.run(formdef) + assert str(excinfo.value) == 'Failed to evaluate page 1 post condition.' diff --git a/wcs/admin/forms.py b/wcs/admin/forms.py index d61d0cc1a..62b2df2c1 100644 --- a/wcs/admin/forms.py +++ b/wcs/admin/forms.py @@ -59,6 +59,7 @@ from .categories import CategoriesDirectory, get_categories from .data_sources import NamedDataSourcesDirectory from .fields import FieldDefPage, FieldsDirectory from .logged_errors import LoggedErrorsDirectory +from .tests import TestsDirectory def is_global_accessible(section): @@ -614,6 +615,7 @@ class FormDefPage(Directory): 'qrcode', 'information', 'inspect', + 'tests', ('public-url', 'public_url'), ('backoffice-submission-roles', 'backoffice_submission_roles'), ('logged-errors', 'logged_errors_dir'), @@ -627,6 +629,7 @@ class FormDefPage(Directory): section = 'forms' options_directory_class = OptionsDirectory fields_directory_class = AdminFieldsDirectory + tests_directory_class = TestsDirectory delete_message = _('You are about to irrevocably delete this form.') delete_title = _('Deleting Form:') @@ -653,6 +656,7 @@ class FormDefPage(Directory): self.role = WorkflowRoleDirectory(self.formdef) self.role.html_top = self.html_top self.options = self.options_directory_class(self.formdef, self.formdefui) + self.tests = self.tests_directory_class(self.formdef) self.logged_errors_dir = LoggedErrorsDirectory( parent_dir=self, formdef_class=self.formdef_class, formdef_id=self.formdef.id ) @@ -938,6 +942,8 @@ class FormDefPage(Directory): r += htmltext('
  • %s
  • ') % _('Save snapshot') r += htmltext('
  • %s
  • ') % _('History') r += htmltext('
  • %s
  • ') % _('Inspector') + if get_publisher().has_site_option('enable-tests'): + r += htmltext('
  • %s
  • ') % _('Tests') r += htmltext('') r += htmltext('