0002-testdef-add-views-to-run-tests-71296.patch
tests/admin_pages/test_tests.py | ||
---|---|---|
1 |
import datetime |
|
2 |
import json |
|
3 |
import os |
|
4 | ||
5 |
import pytest |
|
6 |
from django.utils.html import escape |
|
7 |
from webtest import Upload |
|
8 | ||
9 |
from wcs import fields |
|
10 |
from wcs.formdef import FormDef |
|
11 |
from wcs.qommon.http_request import HTTPRequest |
|
12 |
from wcs.testdef import TestDef |
|
13 | ||
14 |
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login |
|
15 |
from .test_all import create_superuser |
|
16 | ||
17 | ||
18 |
@pytest.fixture |
|
19 |
def pub(): |
|
20 |
pub = create_temporary_pub() |
|
21 | ||
22 |
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'}) |
|
23 |
pub.set_app_dir(req) |
|
24 |
pub.cfg['identification'] = {'methods': ['password']} |
|
25 |
pub.write_cfg() |
|
26 | ||
27 |
if not pub.site_options.has_section('options'): |
|
28 |
pub.site_options.add_section('options') |
|
29 |
pub.site_options.set('options', 'enable-tests', 'true') |
|
30 |
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: |
|
31 |
pub.site_options.write(fd) |
|
32 | ||
33 |
FormDef.wipe() |
|
34 |
TestDef.do_table() |
|
35 |
TestDef.wipe() |
|
36 |
return pub |
|
37 | ||
38 | ||
39 |
def teardown_module(module): |
|
40 |
clean_temporary_pub() |
|
41 | ||
42 | ||
43 |
def test_tests_link_on_formdef_page(pub): |
|
44 |
formdef = FormDef() |
|
45 |
formdef.name = 'test title' |
|
46 |
formdef.store() |
|
47 | ||
48 |
create_superuser(pub) |
|
49 |
app = login(get_app(pub)) |
|
50 | ||
51 |
resp = app.get(formdef.get_admin_url()) |
|
52 |
assert 'Tests' in resp.text |
|
53 | ||
54 |
pub.site_options.set('options', 'enable-tests', 'false') |
|
55 |
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd: |
|
56 |
pub.site_options.write(fd) |
|
57 | ||
58 |
resp = app.get(formdef.get_admin_url()) |
|
59 |
assert 'Tests' not in resp.text |
|
60 | ||
61 | ||
62 |
def test_tests_page(pub): |
|
63 |
user = create_superuser(pub) |
|
64 | ||
65 |
formdef = FormDef() |
|
66 |
formdef.name = 'test title' |
|
67 |
formdef.fields = [ |
|
68 |
fields.PageField( |
|
69 |
id='0', |
|
70 |
label='1st page', |
|
71 |
type='page', |
|
72 |
post_conditions=[ |
|
73 |
{'condition': {'type': 'django', 'value': 'form_var_text == "a"'}, 'error_message': 'Error'} |
|
74 |
], |
|
75 |
), |
|
76 |
fields.StringField(id='1', label='Text', varname='text'), |
|
77 |
] |
|
78 |
formdef.store() |
|
79 | ||
80 |
app = login(get_app(pub)) |
|
81 | ||
82 |
resp = app.get(formdef.get_admin_url()) |
|
83 |
resp = resp.click('Tests') |
|
84 |
assert 'There are no tests yet.' in resp.text |
|
85 |
assert 'Run' not in resp.text |
|
86 |
assert 'Tests cannot be created because there are no completed forms.' in resp.text |
|
87 |
assert 'New' not in resp.text |
|
88 | ||
89 |
formdata = formdef.data_class()() |
|
90 |
formdata.just_created() |
|
91 |
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() |
|
92 |
formdata.data['1'] = 'a' |
|
93 |
formdata.user_id = user.id |
|
94 |
formdata.store() |
|
95 | ||
96 |
resp = app.get(formdef.get_admin_url()) |
|
97 |
resp = resp.click('Tests') |
|
98 |
assert 'There are no tests yet.' in resp.text |
|
99 |
assert 'Run' not in resp.text |
|
100 |
assert 'New tests cannot be created' not in resp.text |
|
101 | ||
102 |
resp = resp.click('New') |
|
103 |
resp.form['name'] = 'First test' |
|
104 |
resp.form['formdata_id'].select(text='1-1 - admin') |
|
105 |
assert len(resp.form['formdata_id'].options) == 1 |
|
106 | ||
107 |
resp = resp.form.submit().follow() |
|
108 |
assert 'First test' in resp.text |
|
109 |
assert 'no tests yet' not in resp.text |
|
110 | ||
111 |
resp = resp.click('Run') |
|
112 |
assert 'First test: Success!' in resp.text |
|
113 | ||
114 |
formdata = formdef.data_class()() |
|
115 |
formdata.just_created() |
|
116 |
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() |
|
117 |
formdata.data['1'] = 'b' |
|
118 |
formdata.store() |
|
119 | ||
120 |
resp = app.get('/backoffice/forms/1/tests/new') |
|
121 |
resp.form['name'] = 'Second test' |
|
122 |
resp.form['formdata_id'].select(text='1-2 - Unknown User') |
|
123 |
assert len(resp.form['formdata_id'].options) == 2 |
|
124 | ||
125 |
resp = resp.form.submit().follow() |
|
126 |
assert 'First test' in resp.text |
|
127 |
assert 'Second test' in resp.text |
|
128 | ||
129 |
resp = resp.click('Run') |
|
130 |
assert 'First test: Success!' in resp.text |
|
131 |
assert 'Second test: Page 1 post condition was not met (form_var_text == "a").' in resp.text |
|
132 | ||
133 | ||
134 |
def test_tests_page_formdefs_isolation(pub): |
|
135 |
formdef = FormDef() |
|
136 |
formdef.name = 'dummy' |
|
137 |
formdef.store() |
|
138 | ||
139 |
formdata = formdef.data_class()() |
|
140 |
formdata.just_created() |
|
141 |
formdata.store() |
|
142 | ||
143 |
other_formdef = FormDef() |
|
144 |
other_formdef.name = 'dummy2' |
|
145 |
other_formdef.store() |
|
146 | ||
147 |
formdata2 = other_formdef.data_class()() |
|
148 |
formdata2.just_created() |
|
149 |
formdata2.store() |
|
150 | ||
151 |
create_superuser(pub) |
|
152 |
app = login(get_app(pub)) |
|
153 | ||
154 |
resp = app.get('/backoffice/forms/1/tests/new') |
|
155 |
assert len(resp.form['formdata_id'].options) == 1 |
|
156 |
assert resp.form['formdata_id'].options[0][2] == '1-1 - Unknown User' |
|
157 | ||
158 |
resp = app.get('/backoffice/forms/2/tests/new') |
|
159 |
assert len(resp.form['formdata_id'].options) == 1 |
|
160 |
assert resp.form['formdata_id'].options[0][2] == '2-1 - Unknown User' |
|
161 | ||
162 | ||
163 |
def test_tests_import_export(pub): |
|
164 |
user = create_superuser(pub) |
|
165 | ||
166 |
formdef = FormDef() |
|
167 |
formdef.name = 'test title' |
|
168 |
formdef.fields = [fields.StringField(id='1', varname='test field', label='Test', type='string')] |
|
169 |
formdef.store() |
|
170 | ||
171 |
formdata = formdef.data_class()() |
|
172 |
formdata.just_created() |
|
173 |
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() |
|
174 |
formdata.data['1'] = 'a' |
|
175 |
formdata.user_id = user.id |
|
176 |
formdata.store() |
|
177 | ||
178 |
testdef = TestDef.create_from_formdata(formdef, formdata) |
|
179 |
testdef.name = 'First test' |
|
180 |
testdef.store() |
|
181 | ||
182 |
app = login(get_app(pub)) |
|
183 | ||
184 |
resp = app.get('/backoffice/forms/1/tests/') |
|
185 |
export_resp = resp.click('First test') |
|
186 | ||
187 |
resp = resp.click('remove') |
|
188 |
resp = resp.form.submit().follow() |
|
189 |
assert 'First test' not in resp.text |
|
190 | ||
191 |
resp = resp.click('Import') |
|
192 |
resp.form['file'] = Upload('export.json', export_resp.body, 'application/json') |
|
193 |
resp = resp.form.submit().follow() |
|
194 |
assert TestDef.count() == 1 |
|
195 |
assert 'First test' in resp.text |
|
196 |
assert escape('Test "First test" has been successfully imported.') in resp.text |
|
197 | ||
198 |
imported_testdef = TestDef.select()[0] |
|
199 |
assert imported_testdef.export_to_json() == testdef.export_to_json() |
|
200 | ||
201 |
export_json = json.loads(export_resp.body) |
|
202 |
export_json['data']['fields']['1'] = 'b' |
|
203 | ||
204 |
resp = resp.click('Import') |
|
205 |
resp.form['file'] = Upload('export.json', json.dumps(export_json).encode(), 'application/json') |
|
206 |
resp = resp.form.submit().follow() |
|
207 |
assert TestDef.count() == 1 |
|
208 |
assert 'First test' in resp.text |
|
209 |
assert escape('Test "First test" has been successfully imported.') in resp.text |
|
210 | ||
211 |
imported_testdef = TestDef.get(imported_testdef.id) |
|
212 |
assert imported_testdef.data['fields']['1'] == 'b' |
|
213 | ||
214 |
resp = resp.click('Import') |
|
215 |
resp.form['file'] = Upload('export.json', b'invalid', 'application/json') |
|
216 |
resp = resp.form.submit() |
|
217 |
assert 'Expecting value: line 1 column 1' in resp.text |
tests/test_testdef.py | ||
---|---|---|
1 |
import datetime |
|
2 | ||
3 |
import pytest |
|
4 | ||
5 |
from wcs import fields |
|
6 |
from wcs.formdef import FormDef |
|
7 |
from wcs.qommon.http_request import HTTPRequest |
|
8 |
from wcs.testdef import TestDef, TestError |
|
9 | ||
10 |
from .utilities import clean_temporary_pub, create_temporary_pub |
|
11 | ||
12 | ||
13 |
@pytest.fixture |
|
14 |
def pub(): |
|
15 |
pub = create_temporary_pub() |
|
16 | ||
17 |
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'}) |
|
18 |
pub.set_app_dir(req) |
|
19 |
pub.write_cfg() |
|
20 | ||
21 |
FormDef.wipe() |
|
22 |
return pub |
|
23 | ||
24 | ||
25 |
def teardown_module(module): |
|
26 |
clean_temporary_pub() |
|
27 | ||
28 | ||
29 |
def test_testdef_slug_generation(pub): |
|
30 |
TestDef.do_table() |
|
31 | ||
32 |
testdef = TestDef() |
|
33 |
testdef.name = 'test' |
|
34 |
testdef.object_type = 'formdef' |
|
35 |
testdef.object_id = '1' |
|
36 |
testdef.store() |
|
37 |
assert testdef.slug == 'test' |
|
38 | ||
39 |
testdef.slug = testdef.id = None |
|
40 |
testdef.store() |
|
41 |
assert testdef.slug == 'test-2' |
|
42 | ||
43 |
testdef.slug = testdef.id = None |
|
44 |
testdef.object_id = '2' |
|
45 |
testdef.store() |
|
46 |
assert testdef.slug == 'test' |
|
47 | ||
48 | ||
49 |
def test_page_post_conditions(pub): |
|
50 |
formdef = FormDef() |
|
51 |
formdef.name = 'test title' |
|
52 |
formdef.fields = [ |
|
53 |
fields.TitleField(id='0', label='Title', type='title'), |
|
54 |
fields.PageField( |
|
55 |
id='1', |
|
56 |
label='1st page', |
|
57 |
type='page', |
|
58 |
post_conditions=[ |
|
59 |
{'condition': {'type': 'django', 'value': 'form_var_text == "a"'}, 'error_message': 'Error'} |
|
60 |
], |
|
61 |
), |
|
62 |
fields.StringField(id='2', label='Text', varname='text'), |
|
63 |
fields.PageField( |
|
64 |
id='3', |
|
65 |
label='2nd page', |
|
66 |
type='page', |
|
67 |
post_conditions=[ |
|
68 |
{'condition': {'type': 'django', 'value': 'form_var_text2 == "a"'}, 'error_message': 'Error'} |
|
69 |
], |
|
70 |
), |
|
71 |
fields.StringField(id='4', label='Text', varname='text2'), |
|
72 |
] |
|
73 |
formdef.store() |
|
74 | ||
75 |
formdata = formdef.data_class()() |
|
76 |
formdata.just_created() |
|
77 |
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() |
|
78 |
formdata.data['2'] = 'a' |
|
79 |
formdata.data['4'] = 'a' |
|
80 | ||
81 |
testdef = TestDef.create_from_formdata(formdef, formdata) |
|
82 |
testdef.run(formdef) |
|
83 | ||
84 |
formdata.data['4'] = 'b' |
|
85 |
testdef = TestDef.create_from_formdata(formdef, formdata) |
|
86 |
with pytest.raises(TestError) as excinfo: |
|
87 |
testdef.run(formdef) |
|
88 |
assert str(excinfo.value) == 'Page 2 post condition was not met (form_var_text2 == "a").' |
|
89 | ||
90 |
formdata.data['2'] = 'b' |
|
91 |
testdef = TestDef.create_from_formdata(formdef, formdata) |
|
92 |
with pytest.raises(TestError) as excinfo: |
|
93 |
testdef.run(formdef) |
|
94 |
assert str(excinfo.value) == 'Page 1 post condition was not met (form_var_text == "a").' |
|
95 | ||
96 | ||
97 |
def test_page_post_condition_invalid(pub): |
|
98 |
formdef = FormDef() |
|
99 |
formdef.name = 'test title' |
|
100 |
formdef.fields = [ |
|
101 |
fields.PageField( |
|
102 |
id='0', |
|
103 |
label='1st page', |
|
104 |
type='page', |
|
105 |
post_conditions=[{'condition': {'type': 'django', 'value': '{{}'}, 'error_message': 'Error'}], |
|
106 |
), |
|
107 |
] |
|
108 |
formdef.store() |
|
109 | ||
110 |
formdata = formdef.data_class()() |
|
111 |
formdata.just_created() |
|
112 |
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() |
|
113 | ||
114 |
testdef = TestDef.create_from_formdata(formdef, formdata) |
|
115 |
with pytest.raises(TestError) as excinfo: |
|
116 |
testdef.run(formdef) |
|
117 |
assert str(excinfo.value) == 'Failed to evaluate page 1 post condition.' |
wcs/admin/forms.py | ||
---|---|---|
614 | 614 |
'qrcode', |
615 | 615 |
'information', |
616 | 616 |
'inspect', |
617 |
'tests', |
|
617 | 618 |
('public-url', 'public_url'), |
618 | 619 |
('backoffice-submission-roles', 'backoffice_submission_roles'), |
619 | 620 |
('logged-errors', 'logged_errors_dir'), |
... | ... | |
641 | 642 |
inspect_template_name = 'wcs/backoffice/formdef-inspect.html' |
642 | 643 | |
643 | 644 |
def __init__(self, component, instance=None): |
645 |
from .tests import TestsDirectory |
|
646 | ||
644 | 647 |
try: |
645 | 648 |
self.formdef = instance or self.formdef_class.get(component) |
646 | 649 |
except KeyError: |
... | ... | |
653 | 656 |
self.role = WorkflowRoleDirectory(self.formdef) |
654 | 657 |
self.role.html_top = self.html_top |
655 | 658 |
self.options = self.options_directory_class(self.formdef, self.formdefui) |
659 |
self.tests = TestsDirectory(self.formdef) |
|
656 | 660 |
self.logged_errors_dir = LoggedErrorsDirectory( |
657 | 661 |
parent_dir=self, formdef_class=self.formdef_class, formdef_id=self.formdef.id |
658 | 662 |
) |
... | ... | |
938 | 942 |
r += htmltext('<li><a rel="popup" href="history/save">%s</a></li>') % _('Save snapshot') |
939 | 943 |
r += htmltext('<li><a href="history/">%s</a></li>') % _('History') |
940 | 944 |
r += htmltext('<li><a href="inspect">%s</a></li>') % _('Inspector') |
945 |
if get_publisher().has_site_option('enable-tests'): |
|
946 |
r += htmltext('<li><a href="tests/">%s</a></li>') % _('Tests') |
|
941 | 947 |
r += htmltext('</ul>') |
942 | 948 | |
943 | 949 |
r += htmltext('<ul>') |
wcs/admin/tests.py | ||
---|---|---|
1 |
# w.c.s. - web application for online forms |
|
2 |
# Copyright (C) 2005-2022 Entr'ouvert |
|
3 |
# |
|
4 |
# This program is free software; you can redistribute it and/or modify |
|
5 |
# it under the terms of the GNU General Public License as published by |
|
6 |
# the Free Software Foundation; either version 2 of the License, or |
|
7 |
# (at your option) any later version. |
|
8 |
# |
|
9 |
# This program is distributed in the hope that it will be useful, |
|
10 |
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
11 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
12 |
# GNU General Public License for more details. |
|
13 |
# |
|
14 |
# You should have received a copy of the GNU General Public License |
|
15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
|
16 | ||
17 |
import json |
|
18 | ||
19 |
from quixote import get_response, get_session, redirect |
|
20 |
from quixote.directory import Directory |
|
21 |
from quixote.html import TemplateIO, htmltext |
|
22 | ||
23 |
from wcs.qommon import _, template |
|
24 |
from wcs.qommon.backoffice.menu import html_top |
|
25 |
from wcs.qommon.errors import TraversalError |
|
26 |
from wcs.qommon.form import FileWidget, Form, SingleSelectWidget, StringWidget |
|
27 |
from wcs.qommon.storage import Equal, StrictNotEqual |
|
28 |
from wcs.testdef import TestDef, TestError |
|
29 | ||
30 | ||
31 |
class TestPage(Directory): |
|
32 |
_q_exports = ['delete', 'export'] |
|
33 | ||
34 |
def __init__(self, component): |
|
35 |
try: |
|
36 |
self.testdef = TestDef.get(component) |
|
37 |
except KeyError: |
|
38 |
raise TraversalError() |
|
39 | ||
40 |
def delete(self): |
|
41 |
form = Form(enctype='multipart/form-data') |
|
42 |
form.add_submit('delete', _('Submit')) |
|
43 |
form.add_submit('cancel', _('Cancel')) |
|
44 | ||
45 |
if form.get_widget('cancel').parse(): |
|
46 |
return redirect('.') |
|
47 |
if not form.is_submitted() or form.has_errors(): |
|
48 |
get_response().breadcrumb.append(('delete', _('Delete'))) |
|
49 |
r = TemplateIO(html=True) |
|
50 |
r += htmltext('<h2>%s %s</h2>') % (_('Deleting Test:'), self.testdef) |
|
51 |
r += form.render() |
|
52 |
return r.getvalue() |
|
53 |
else: |
|
54 |
TestDef.remove_object(self.testdef.id) |
|
55 |
return redirect('..') |
|
56 | ||
57 |
def export(self): |
|
58 |
get_response().set_content_type('application/json') |
|
59 |
get_response().set_header( |
|
60 |
'content-disposition', 'attachment; filename=wcs_test_%s.json' % self.testdef.name |
|
61 |
) |
|
62 |
return json.dumps(self.testdef.export_to_json()) |
|
63 | ||
64 | ||
65 |
class TestsDirectory(Directory): |
|
66 |
_q_exports = ['', 'new', 'run', ('import', 'p_import')] |
|
67 |
section = 'tests' |
|
68 | ||
69 |
def __init__(self, objectdef): |
|
70 |
self.objectdef = objectdef |
|
71 | ||
72 |
def _q_traverse(self, path): |
|
73 |
get_response().breadcrumb.append(('tests', _('Tests'))) |
|
74 |
html_top('tests', '%s - %s' % (self.objectdef.name, _('Tests'))) |
|
75 |
return super()._q_traverse(path) |
|
76 | ||
77 |
def _q_lookup(self, component): |
|
78 |
return TestPage(component) |
|
79 | ||
80 |
def _q_index(self): |
|
81 |
context = { |
|
82 |
'testdefs': TestDef.select( |
|
83 |
[Equal('object_type', self.objectdef.get_table_name()), Equal('object_id', self.objectdef.id)] |
|
84 |
), |
|
85 |
'formdata': self.objectdef.data_class().select([StrictNotEqual('status', 'draft')]), |
|
86 |
} |
|
87 |
return template.QommonTemplateResponse(templates=['wcs/backoffice/tests.html'], context=context) |
|
88 | ||
89 |
def new(self): |
|
90 |
form = Form(enctype='multipart/form-data') |
|
91 |
form.add(StringWidget, 'name', title=_('Name'), required=True, size=50) |
|
92 |
formadata_options = sorted( |
|
93 |
[ |
|
94 |
(x.id, '%s - %s' % (x.id_display, x.user or _('Unknown User'))) |
|
95 |
for x in self.objectdef.data_class().select([StrictNotEqual('status', 'draft')]) |
|
96 |
], |
|
97 |
key=lambda x: x[1], |
|
98 |
) |
|
99 |
form.add(SingleSelectWidget, 'formdata_id', title=_('Form'), required=True, options=formadata_options) |
|
100 |
form.add_submit('submit', _('Submit')) |
|
101 |
form.add_submit('cancel', _('Cancel')) |
|
102 | ||
103 |
if form.get_widget('cancel').parse(): |
|
104 |
return redirect('.') |
|
105 | ||
106 |
if not form.is_submitted() or form.has_errors(): |
|
107 |
get_response().breadcrumb.append(('new', _('New'))) |
|
108 |
r = TemplateIO(html=True) |
|
109 |
r += htmltext('<h2>%s</h2>') % _('New test') |
|
110 |
r += form.render() |
|
111 |
return r.getvalue() |
|
112 |
else: |
|
113 |
formdata_id = form.get_widget('formdata_id').parse() |
|
114 |
formdata = self.objectdef.data_class().get(formdata_id) |
|
115 | ||
116 |
testdef = TestDef.create_from_formdata(self.objectdef, formdata) |
|
117 |
testdef.name = form.get_widget('name').parse() |
|
118 |
testdef.store() |
|
119 | ||
120 |
return redirect('.') |
|
121 | ||
122 |
def run(self): |
|
123 |
get_response().breadcrumb.append(('run', _('Run tests'))) |
|
124 | ||
125 |
testdef = TestDef.select( |
|
126 |
[Equal('object_type', self.objectdef.get_table_name()), Equal('object_id', self.objectdef.id)] |
|
127 |
) |
|
128 |
for test in testdef: |
|
129 |
try: |
|
130 |
test.run(self.objectdef) |
|
131 |
except TestError as e: |
|
132 |
test.error = e |
|
133 | ||
134 |
return template.QommonTemplateResponse( |
|
135 |
templates=['wcs/backoffice/test_results.html'], context={'tests': testdef} |
|
136 |
) |
|
137 | ||
138 |
def p_import(self): |
|
139 |
form = Form(enctype='multipart/form-data') |
|
140 | ||
141 |
form.add(FileWidget, 'file', title=_('File'), required=True) |
|
142 |
form.add_submit('submit', _('Import Test')) |
|
143 |
form.add_submit('cancel', _('Cancel')) |
|
144 | ||
145 |
if form.get_submit() == 'cancel': |
|
146 |
return redirect('.') |
|
147 | ||
148 |
if form.is_submitted() and not form.has_errors(): |
|
149 |
try: |
|
150 |
return self.import_submit(form) |
|
151 |
except ValueError: |
|
152 |
pass |
|
153 | ||
154 |
get_response().breadcrumb.append(('import', _('Import'))) |
|
155 |
r = TemplateIO(html=True) |
|
156 |
r += htmltext('<h2>%s</h2>') % _('Import Test') |
|
157 |
r += htmltext('<p>%s</p>') % _( |
|
158 |
'You can add a new test or update an existing one by importing a JSON file.' |
|
159 |
) |
|
160 |
r += form.render() |
|
161 |
return r.getvalue() |
|
162 | ||
163 |
def import_submit(self, form): |
|
164 |
fp = form.get_widget('file').parse().fp |
|
165 | ||
166 |
try: |
|
167 |
testdef = TestDef.import_from_json(json.loads(fp.read())) |
|
168 |
except Exception as e: |
|
169 |
form.set_error('file', str(e)) |
|
170 |
raise ValueError() |
|
171 | ||
172 |
get_session().message = ('info', _('Test "%s" has been successfully imported.') % testdef.name) |
|
173 |
return redirect('.') |
wcs/fields.py | ||
---|---|---|
614 | 614 |
self.in_listing = None |
615 | 615 |
return changed |
616 | 616 | |
617 |
def evaluate_condition(self, dict_vars, formdef, condition): |
|
617 |
@staticmethod |
|
618 |
def evaluate_condition(dict_vars, formdef, condition): |
|
618 | 619 |
return PageCondition(condition, {'dict_vars': dict_vars, 'formdef': formdef}).evaluate() |
619 | 620 | |
620 | 621 |
def is_visible(self, dict, formdef): |
wcs/templates/wcs/backoffice/test_results.html | ||
---|---|---|
1 |
{% extends "wcs/backoffice/base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block appbar-title %}{% trans "Tests results" %}{% endblock %} |
|
5 | ||
6 |
{% block content %} |
|
7 |
<div class="section"> |
|
8 |
<ul> |
|
9 |
{% for test in tests %} |
|
10 |
<li>{{ test }}{% trans ":" %} {% firstof test.error _("Success!") %}</li> |
|
11 |
{% endfor %} |
|
12 |
</ul> |
|
13 |
</div> |
|
14 |
{% endblock %} |
wcs/templates/wcs/backoffice/tests.html | ||
---|---|---|
1 |
{% extends "wcs/backoffice/base.html" %} |
|
2 |
{% load i18n %} |
|
3 | ||
4 |
{% block appbar-title %}{% trans "Tests" %}{% endblock %} |
|
5 |
{% block appbar-actions %} |
|
6 |
<a href="import" rel="popup">{% trans "Import" %}</a> |
|
7 |
{% if testdefs %} |
|
8 |
<a href="run">{% trans "Run tests" %}</a> |
|
9 |
{% endif %} |
|
10 |
{% if formdata %} |
|
11 |
<a href="new" rel="popup">{% trans "New" %}</a> |
|
12 |
{% endif %} |
|
13 |
{% endblock %} |
|
14 | ||
15 |
{% block content %} |
|
16 |
<div class="section"> |
|
17 |
<h3>{% trans "Test form data" %}</h3> |
|
18 | ||
19 |
{% if not formdata %} |
|
20 |
<div class="infonotice"> |
|
21 |
<p>{% trans "Tests cannot be created because there are no completed forms." %}</p> |
|
22 |
</div> |
|
23 |
{% endif %} |
|
24 | ||
25 |
{% if testdefs %} |
|
26 |
<ul class="objects-list single-links"> |
|
27 |
{% for test in testdefs %} |
|
28 |
<li> |
|
29 |
<a download href="{{ test.id }}/export">{{ test }}</a> |
|
30 |
<a rel="popup" class="delete" href="{{ test.id }}/delete">{% trans "remove" %}</a> |
|
31 |
</li> |
|
32 |
{% endfor %} |
|
33 |
</ul> |
|
34 |
{% else %} |
|
35 |
<div><p>{% trans "There are no tests yet." %}<p></div> |
|
36 |
{% endif %} |
|
37 |
</div> |
|
38 |
{% endblock %} |
wcs/testdef.py | ||
---|---|---|
14 | 14 |
# You should have received a copy of the GNU General Public License |
15 | 15 |
# along with this program; if not, see <http://www.gnu.org/licenses/>. |
16 | 16 | |
17 |
from quixote import get_publisher |
|
18 | ||
17 | 19 |
from wcs import sql |
18 | 20 | |
21 |
from .qommon import _, misc |
|
22 |
from .qommon.storage import Equal |
|
23 | ||
24 | ||
25 |
class TestError(Exception): |
|
26 |
pass |
|
27 | ||
19 | 28 | |
20 | 29 |
class TestDef(sql.TestDef): |
21 | 30 |
_names = 'testdef' |
... | ... | |
26 | 35 |
object_id = None |
27 | 36 | |
28 | 37 |
data = None # (json export of formdata, carddata, etc.) |
38 | ||
39 |
def __str__(self): |
|
40 |
return self.name |
|
41 | ||
42 |
def store(self, *args, comment=None, snapshot_store_user=True, **kwargs): |
|
43 |
if not self.slug: |
|
44 |
existing_slugs = { |
|
45 |
x.slug |
|
46 |
for x in self.select( |
|
47 |
[Equal('object_type', self.object_type), Equal('object_id', self.object_id)] |
|
48 |
) |
|
49 |
} |
|
50 |
base_slug = misc.simplify(self.name) |
|
51 |
self.slug = base_slug |
|
52 |
i = 2 |
|
53 |
while self.slug in existing_slugs: |
|
54 |
self.slug = '%s-%s' % (base_slug, i) |
|
55 |
i += 1 |
|
56 |
super().store(*args, **kwargs) |
|
57 | ||
58 |
@classmethod |
|
59 |
def create_from_formdata(cls, formdef, formdata): |
|
60 |
testdef = cls() |
|
61 |
testdef.object_type = formdef.get_table_name() |
|
62 |
testdef.object_id = formdef.id |
|
63 |
testdef.data = { |
|
64 |
'fields': formdata.data, |
|
65 |
'user': formdata.user.get_json_export_dict() if formdata.user else None, |
|
66 |
} |
|
67 |
return testdef |
|
68 | ||
69 |
def run(self, objectdef): |
|
70 |
formdata = objectdef.data_class()() |
|
71 |
if self.data['user']: |
|
72 |
formdata.set_user_from_json(self.data['user']) |
|
73 | ||
74 |
get_publisher().substitutions.reset() |
|
75 |
get_publisher().substitutions.feed(get_publisher()) |
|
76 |
get_publisher().substitutions.feed(objectdef) |
|
77 |
get_publisher().substitutions.feed(formdata) |
|
78 | ||
79 |
page_no = 0 |
|
80 |
page_post_conditions = [] |
|
81 |
for field in objectdef.fields: |
|
82 |
if field.type == 'page': |
|
83 |
if page_post_conditions: |
|
84 |
self.evaluate_page_conditions(page_no, formdata, objectdef, page_post_conditions) |
|
85 | ||
86 |
page_post_conditions = field.post_conditions or [] |
|
87 |
page_no += 1 |
|
88 |
continue |
|
89 | ||
90 |
if field.type in ('subtitle', 'title', 'comment', 'computed'): |
|
91 |
continue |
|
92 | ||
93 |
formdata.data[field.id] = self.data['fields'].get(field.id) |
|
94 |
get_publisher().substitutions.invalidate_cache() |
|
95 | ||
96 |
# evaluate last page post condition |
|
97 |
self.evaluate_page_conditions(page_no, formdata, objectdef, page_post_conditions) |
|
98 | ||
99 |
def evaluate_page_conditions(self, page_no, form_data, objectdef, conditions): |
|
100 |
from wcs.fields import Field |
|
101 | ||
102 |
for post_condition in conditions: |
|
103 |
condition = post_condition.get('condition', {}) |
|
104 |
try: |
|
105 |
if not Field.evaluate_condition(form_data.data, objectdef, condition): |
|
106 |
raise TestError( |
|
107 |
_('Page %(no)s post condition was not met (%(condition)s).') |
|
108 |
% {'no': page_no, 'condition': condition.get('value')} |
|
109 |
) |
|
110 |
except RuntimeError: |
|
111 |
raise TestError(_('Failed to evaluate page %s post condition.') % page_no) |
|
112 | ||
113 |
def export_to_json(self): |
|
114 |
return { |
|
115 |
'name': self.name, |
|
116 |
'slug': self.slug, |
|
117 |
'object_type': self.object_type, |
|
118 |
'object_id': self.object_id, |
|
119 |
'data': self.data, |
|
120 |
} |
|
121 | ||
122 |
@classmethod |
|
123 |
def import_from_json(cls, data): |
|
124 |
testdefs = TestDef.select( |
|
125 |
[ |
|
126 |
Equal('object_type', data['object_type']), |
|
127 |
Equal('object_id', data['object_id']), |
|
128 |
Equal('slug', data['slug']), |
|
129 |
] |
|
130 |
) |
|
131 | ||
132 |
testdef = testdefs[0] if testdefs else TestDef() |
|
133 | ||
134 |
for k, v in data.items(): |
|
135 |
setattr(testdef, k, v) |
|
136 | ||
137 |
testdef.store() |
|
138 |
return testdef |
|
29 |
- |