Projet

Général

Profil

0002-testdata-add-views-to-run-tests-71296.patch

Valentin Deniaud, 17 novembre 2022 15:59

Télécharger (11,8 ko)

Voir les différences:

Subject: [PATCH 2/2] testdata: add views to run tests (#71296)

 wcs/admin/forms.py                            |   6 ++
 wcs/admin/tests.py                            | 101 ++++++++++++++++++
 wcs/fields.py                                 |   3 +-
 .../wcs/backoffice/test_results.html          |  18 ++++
 wcs/templates/wcs/backoffice/tests.html       |  19 ++++
 wcs/testdata.py                               |  65 +++++++++++
 6 files changed, 211 insertions(+), 1 deletion(-)
 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
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
-