0002-testdata-add-views-to-run-tests-71296.patch
wcs/admin/forms.py | ||
---|---|---|
59 | 59 |
from .data_sources import NamedDataSourcesDirectory |
60 | 60 |
from .fields import FieldDefPage, FieldsDirectory |
61 | 61 |
from .logged_errors import LoggedErrorsDirectory |
62 |
from .tests import TestsDirectory |
|
62 | 63 | |
63 | 64 | |
64 | 65 |
def is_global_accessible(section): |
... | ... | |
614 | 615 |
'qrcode', |
615 | 616 |
'information', |
616 | 617 |
'inspect', |
618 |
'tests', |
|
617 | 619 |
('public-url', 'public_url'), |
618 | 620 |
('backoffice-submission-roles', 'backoffice_submission_roles'), |
619 | 621 |
('logged-errors', 'logged_errors_dir'), |
... | ... | |
627 | 629 |
section = 'forms' |
628 | 630 |
options_directory_class = OptionsDirectory |
629 | 631 |
fields_directory_class = AdminFieldsDirectory |
632 |
tests_directory_class = TestsDirectory |
|
630 | 633 | |
631 | 634 |
delete_message = _('You are about to irrevocably delete this form.') |
632 | 635 |
delete_title = _('Deleting Form:') |
... | ... | |
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 = self.tests_directory_class(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 |
from quixote import get_response, redirect |
|
18 |
from quixote.directory import Directory |
|
19 |
from quixote.html import TemplateIO, htmltext |
|
20 | ||
21 |
from wcs.qommon import _, template |
|
22 |
from wcs.qommon.backoffice.menu import html_top |
|
23 |
from wcs.qommon.form import Form, SingleSelectWidget, StringWidget |
|
24 |
from wcs.qommon.storage import Equal, StrictNotEqual |
|
25 |
from wcs.testdata import TestData, TestError |
|
26 | ||
27 | ||
28 |
class TestsDirectory(Directory): |
|
29 |
_q_exports = ['', 'new', 'run'] |
|
30 |
section = 'tests' |
|
31 | ||
32 |
def __init__(self, objectdef): |
|
33 |
self.objectdef = objectdef |
|
34 | ||
35 |
def html_top(self, title): |
|
36 |
return html_top(self.section, title) |
|
37 | ||
38 |
def _q_index(self): |
|
39 |
self.html_top(self.objectdef.name) # TODO improve |
|
40 |
get_response().breadcrumb.append(('tests', _('Tests'))) |
|
41 |
context = { |
|
42 |
'testdata': TestData.select( |
|
43 |
[Equal('object_type', self.objectdef.get_table_name()), Equal('object_id', self.objectdef.id)] |
|
44 |
) |
|
45 |
} |
|
46 |
return template.QommonTemplateResponse(templates=['wcs/backoffice/tests.html'], context=context) |
|
47 | ||
48 |
def new(self): |
|
49 |
form = Form(enctype='multipart/form-data') |
|
50 |
form.add(StringWidget, 'name', title=_('Name'), required=True, size=50) |
|
51 |
form.add( |
|
52 |
SingleSelectWidget, |
|
53 |
'formdata_id', |
|
54 |
title=_('Form'), |
|
55 |
required=True, |
|
56 |
options=sorted( |
|
57 |
(x.id_display, '%s - %s' % (x.id_display, x.user)) |
|
58 |
for x in self.objectdef.data_class().select([StrictNotEqual('status', 'draft')]) |
|
59 |
), |
|
60 |
) |
|
61 |
form.add_submit('submit', _('Submit')) |
|
62 |
form.add_submit('cancel', _('Cancel')) |
|
63 | ||
64 |
if form.get_widget('cancel').parse(): |
|
65 |
return redirect('..') |
|
66 | ||
67 |
if not form.is_submitted() or form.has_errors(): |
|
68 |
get_response().breadcrumb.append(('new', _('New'))) |
|
69 |
self.html_top(title=_('New test')) |
|
70 |
r = TemplateIO(html=True) |
|
71 |
r += htmltext('<h2>%s</h2>') % _('New test') |
|
72 |
r += form.render() |
|
73 |
return r.getvalue() |
|
74 |
else: |
|
75 |
formdata_id = form.get_widget('formdata_id').parse() |
|
76 |
formdata = self.objectdef.data_class().select([Equal('id_display', formdata_id)])[0] |
|
77 | ||
78 |
testdata = TestData() |
|
79 |
testdata.name = form.get_widget('name').parse() |
|
80 |
testdata.object_type = self.objectdef.get_table_name() |
|
81 |
testdata.object_id = self.objectdef.id |
|
82 |
testdata.data = {'fields': formdata.data, 'user': formdata.user.get_json_export_dict()} |
|
83 |
testdata.store() |
|
84 | ||
85 |
return redirect('..') |
|
86 | ||
87 |
def run(self): |
|
88 |
get_response().breadcrumb.append(('tests', _('Tests'))) |
|
89 | ||
90 |
testdata = TestData.select( |
|
91 |
[Equal('object_type', self.objectdef.get_table_name()), Equal('object_id', self.objectdef.id)] |
|
92 |
) |
|
93 |
for test in testdata: |
|
94 |
try: |
|
95 |
test.run(self.objectdef) |
|
96 |
except TestError as e: |
|
97 |
test.error = e |
|
98 | ||
99 |
return template.QommonTemplateResponse( |
|
100 |
templates=['wcs/backoffice/test_results.html'], context={'tests': testdata} |
|
101 |
) |
wcs/fields.py | ||
---|---|---|
619 | 619 |
setattr(self, key, deep_bytes2str(getattr(self, key))) |
620 | 620 |
return changed |
621 | 621 | |
622 |
def evaluate_condition(self, dict_vars, formdef, condition): |
|
622 |
@staticmethod |
|
623 |
def evaluate_condition(dict_vars, formdef, condition): |
|
623 | 624 |
return PageCondition(condition, {'dict_vars': dict_vars, 'formdef': formdef}).evaluate() |
624 | 625 | |
625 | 626 |
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 |
<ul> |
|
8 |
{% for test in tests %} |
|
9 |
<li> |
|
10 |
{{ test }}{% trans ":" %} |
|
11 |
{% if not test.error %} |
|
12 |
{% trans "Success!" %} |
|
13 |
{% else %} |
|
14 |
{{ test.error }} |
|
15 |
{% endif %} |
|
16 |
</li> |
|
17 |
{% endfor %} |
|
18 |
{% 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="new" rel="popup">{% trans "New" %}</a> |
|
7 |
<a href="run">{% trans "Run" %}</a> |
|
8 |
{% endblock %} |
|
9 | ||
10 |
{% block content %} |
|
11 |
<div class="section"> |
|
12 |
<h3>{% trans "Test form data" %}</h3> |
|
13 |
<ul class="objects-list single-links"> |
|
14 |
{% for test in testdata %} |
|
15 |
<li><a href="#">{{ test }}</a></li> |
|
16 |
{% endfor %} |
|
17 |
</ul> |
|
18 |
</div> |
|
19 |
{% endblock %} |
wcs/testdata.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 | ||
19 |
from .qommon import _, misc |
|
17 | 20 |
from .qommon.storage import StorableObject |
18 | 21 | |
19 | 22 | |
23 |
class TestError(Exception): |
|
24 |
pass |
|
25 | ||
26 | ||
20 | 27 |
class TestData(StorableObject): |
21 | 28 |
_names = 'testdata' |
22 | 29 | |
... | ... | |
29 | 36 | |
30 | 37 |
def __str__(self): |
31 | 38 |
return self.name |
39 | ||
40 |
def store(self, *args, comment=None, snapshot_store_user=True, **kwargs): |
|
41 |
if not self.slug: |
|
42 |
existing_slugs = {x.slug for x in self.select(ignore_migration=True, ignore_errors=True)} |
|
43 |
base_slug = misc.simplify(self.name) |
|
44 |
self.slug = base_slug |
|
45 |
i = 2 |
|
46 |
while self.slug in existing_slugs: |
|
47 |
self.slug = '%s-%s' % (base_slug, i) |
|
48 |
i += 1 |
|
49 |
super().store(*args, **kwargs) |
|
50 | ||
51 |
def run(self, objectdef): |
|
52 |
formdata = objectdef.data_class()() |
|
53 |
formdata.set_user_from_json(self.data['user']) |
|
54 | ||
55 |
get_publisher().substitutions.reset() |
|
56 |
get_publisher().substitutions.feed(get_publisher()) |
|
57 |
get_publisher().substitutions.feed(objectdef) |
|
58 |
get_publisher().substitutions.feed(formdata) |
|
59 | ||
60 |
page_no = 0 |
|
61 |
page_post_conditions = [] |
|
62 |
for field in objectdef.fields: |
|
63 |
if field.type == 'page': |
|
64 |
if page_post_conditions: |
|
65 |
self.evaluate_page_conditions(page_no, formdata, objectdef, page_post_conditions) |
|
66 | ||
67 |
page_post_conditions = field.post_conditions or [] |
|
68 |
page_no += 1 |
|
69 |
continue |
|
70 |
elif field.type in ('subtitle', 'title', 'comment', 'computed'): |
|
71 |
continue |
|
72 | ||
73 |
if not field.is_visible(formdata.data, objectdef): |
|
74 |
if field.id in self.data['fields']: |
|
75 |
raise TestError(_('Tried to fill field "%s" but it is hidden.') % field.label) |
|
76 |
continue |
|
77 | ||
78 |
formdata.data[field.id] = self.data['fields'].get(field.id) |
|
79 |
get_publisher().substitutions.invalidate_cache() |
|
80 | ||
81 |
# evaluate last page post condition |
|
82 |
self.evaluate_page_conditions(page_no, formdata, objectdef, page_post_conditions) |
|
83 | ||
84 |
def evaluate_page_conditions(self, page_no, form_data, objectdef, conditions): |
|
85 |
from wcs.fields import Field |
|
86 | ||
87 |
for post_condition in conditions: |
|
88 |
condition = post_condition.get('condition', {}) |
|
89 |
try: |
|
90 |
if not Field.evaluate_condition(form_data.data, objectdef, condition): |
|
91 |
raise TestError( |
|
92 |
_('Page %(no)s post condition was not met (%(condition)s).') |
|
93 |
% {'no': page_no, 'condition': condition.get('value')} |
|
94 |
) |
|
95 |
except RuntimeError: |
|
96 |
raise TestError(_('Failed to evaluate page %s post condition.') % page_no) |
|
32 |
- |