Projet

Général

Profil

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

Valentin Deniaud, 29 novembre 2022 17:23

Télécharger (26 ko)

Voir les différences:

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
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.wipe()
35
    return pub
36

  
37

  
38
def teardown_module(module):
39
    clean_temporary_pub()
40

  
41

  
42
def test_tests_link_on_formdef_page(pub):
43
    formdef = FormDef()
44
    formdef.name = 'test title'
45
    formdef.store()
46

  
47
    create_superuser(pub)
48
    app = login(get_app(pub))
49

  
50
    resp = app.get(formdef.get_admin_url())
51
    assert 'Tests' in resp.text
52

  
53
    pub.site_options.set('options', 'enable-tests', 'false')
54
    with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
55
        pub.site_options.write(fd)
56

  
57
    resp = app.get(formdef.get_admin_url())
58
    assert 'Tests' not in resp.text
59

  
60

  
61
def test_tests_page(pub):
62
    user = create_superuser(pub)
63

  
64
    formdef = FormDef()
65
    formdef.name = 'test title'
66
    formdef.fields = [
67
        fields.PageField(
68
            id='0',
69
            label='1st page',
70
            type='page',
71
            post_conditions=[
72
                {'condition': {'type': 'django', 'value': 'form_var_text == "a"'}, 'error_message': 'Error'}
73
            ],
74
        ),
75
        fields.StringField(id='1', label='Text', varname='text'),
76
    ]
77
    formdef.store()
78

  
79
    app = login(get_app(pub))
80

  
81
    resp = app.get(formdef.get_admin_url())
82
    resp = resp.click('Tests')
83
    assert 'There are no tests yet.' in resp.text
84
    assert 'Run' not in resp.text
85
    assert 'Tests cannot be created because there are no completed forms.' in resp.text
86
    assert 'New' not in resp.text
87

  
88
    formdata = formdef.data_class()()
89
    formdata.just_created()
90
    formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
91
    formdata.data['1'] = 'a'
92
    formdata.user_id = user.id
93
    formdata.store()
94

  
95
    resp = app.get(formdef.get_admin_url())
96
    resp = resp.click('Tests')
97
    assert 'There are no tests yet.' in resp.text
98
    assert 'Run' not in resp.text
99
    assert 'New tests cannot be created' not in resp.text
100

  
101
    resp = resp.click('New')
102
    resp.form['name'] = 'First test'
103
    resp.form['formdata_id'].select(text='1-1 - admin')
104
    assert len(resp.form['formdata_id'].options) == 1
105

  
106
    resp = resp.form.submit().follow()
107
    assert 'First test' in resp.text
108
    assert 'no tests yet' not in resp.text
109

  
110
    resp = resp.click('Run')
111
    assert 'First test: Success!' in resp.text
112

  
113
    formdata = formdef.data_class()()
114
    formdata.just_created()
115
    formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
116
    formdata.data['1'] = 'b'
117
    formdata.store()
118

  
119
    resp = app.get('/backoffice/forms/1/tests/new')
120
    resp.form['name'] = 'Second test'
121
    resp.form['formdata_id'].select(text='1-2 - Unknown User')
122
    assert len(resp.form['formdata_id'].options) == 2
123

  
124
    resp = resp.form.submit().follow()
125
    assert 'First test' in resp.text
126
    assert 'Second test' in resp.text
127

  
128
    resp = resp.click('Run')
129
    assert 'First test: Success!' in resp.text
130
    assert 'Second test: Page 1 post condition was not met (form_var_text == "a").' in resp.text
131

  
132

  
133
def test_tests_page_formdefs_isolation(pub):
134
    formdef = FormDef()
135
    formdef.name = 'dummy'
136
    formdef.store()
137

  
138
    formdata = formdef.data_class()()
139
    formdata.just_created()
140
    formdata.store()
141

  
142
    other_formdef = FormDef()
143
    other_formdef.name = 'dummy2'
144
    other_formdef.store()
145

  
146
    formdata2 = other_formdef.data_class()()
147
    formdata2.just_created()
148
    formdata2.store()
149

  
150
    create_superuser(pub)
151
    app = login(get_app(pub))
152

  
153
    resp = app.get('/backoffice/forms/1/tests/new')
154
    assert len(resp.form['formdata_id'].options) == 1
155
    assert resp.form['formdata_id'].options[0][2] == '1-1 - Unknown User'
156

  
157
    resp = app.get('/backoffice/forms/2/tests/new')
158
    assert len(resp.form['formdata_id'].options) == 1
159
    assert resp.form['formdata_id'].options[0][2] == '2-1 - Unknown User'
160

  
161

  
162
def test_tests_import_export(pub):
163
    user = create_superuser(pub)
164

  
165
    formdef = FormDef()
166
    formdef.name = 'test title'
167
    formdef.fields = [fields.StringField(id='1', varname='test field', label='Test', type='string')]
168
    formdef.store()
169

  
170
    formdata = formdef.data_class()()
171
    formdata.just_created()
172
    formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
173
    formdata.data['1'] = 'a'
174
    formdata.user_id = user.id
175
    formdata.store()
176

  
177
    testdef = TestDef.create_from_formdata(formdef, formdata)
178
    testdef.name = 'First test'
179
    testdef.store()
180

  
181
    app = login(get_app(pub))
182

  
183
    resp = app.get('/backoffice/forms/1/tests/')
184
    export_resp = resp.click('First test')
185

  
186
    resp = resp.click('remove')
187
    resp = resp.form.submit().follow()
188
    assert 'First test' not in resp.text
189

  
190
    resp = resp.click('Import')
191
    resp.form['file'] = Upload('export.json', export_resp.body, 'application/json')
192
    resp = resp.form.submit().follow()
193
    assert TestDef.count() == 1
194
    assert 'First test' in resp.text
195
    assert escape('Test "First test" has been successfully imported.') in resp.text
196

  
197
    imported_testdef = TestDef.select()[0]
198
    assert imported_testdef.export_to_json() == testdef.export_to_json()
199

  
200
    export_json = json.loads(export_resp.body)
201
    export_json['data']['fields']['1'] = 'b'
202

  
203
    resp = resp.click('Import')
204
    resp.form['file'] = Upload('export.json', json.dumps(export_json).encode(), 'application/json')
205
    resp = resp.form.submit().follow()
206
    assert TestDef.count() == 1
207
    assert 'First test' in resp.text
208
    assert escape('Test "First test" has been successfully imported.') in resp.text
209

  
210
    imported_testdef.refresh_from_storage()
211
    assert imported_testdef.data['fields']['1'] == 'b'
212

  
213
    resp = resp.click('Import')
214
    resp.form['file'] = Upload('export.json', b'invalid', 'application/json')
215
    resp = resp.form.submit()
216
    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_page_post_conditions(pub):
30
    formdef = FormDef()
31
    formdef.name = 'test title'
32
    formdef.fields = [
33
        fields.TitleField(id='0', label='Title', type='title'),
34
        fields.PageField(
35
            id='1',
36
            label='1st page',
37
            type='page',
38
            post_conditions=[
39
                {'condition': {'type': 'django', 'value': 'form_var_text == "a"'}, 'error_message': 'Error'}
40
            ],
41
        ),
42
        fields.StringField(id='2', label='Text', varname='text'),
43
        fields.PageField(
44
            id='3',
45
            label='2nd page',
46
            type='page',
47
            post_conditions=[
48
                {'condition': {'type': 'django', 'value': 'form_var_text2 == "a"'}, 'error_message': 'Error'}
49
            ],
50
        ),
51
        fields.StringField(id='4', label='Text', varname='text2'),
52
    ]
53
    formdef.store()
54

  
55
    formdata = formdef.data_class()()
56
    formdata.just_created()
57
    formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
58
    formdata.data['2'] = 'a'
59
    formdata.data['4'] = 'a'
60

  
61
    testdef = TestDef.create_from_formdata(formdef, formdata)
62
    testdef.run(formdef)
63

  
64
    formdata.data['4'] = 'b'
65
    testdef = TestDef.create_from_formdata(formdef, formdata)
66
    with pytest.raises(TestError) as excinfo:
67
        testdef.run(formdef)
68
    assert str(excinfo.value) == 'Page 2 post condition was not met (form_var_text2 == "a").'
69

  
70
    formdata.data['2'] = 'b'
71
    testdef = TestDef.create_from_formdata(formdef, formdata)
72
    with pytest.raises(TestError) as excinfo:
73
        testdef.run(formdef)
74
    assert str(excinfo.value) == 'Page 1 post condition was not met (form_var_text == "a").'
75

  
76

  
77
def test_page_post_condition_invalid(pub):
78
    formdef = FormDef()
79
    formdef.name = 'test title'
80
    formdef.fields = [
81
        fields.PageField(
82
            id='0',
83
            label='1st page',
84
            type='page',
85
            post_conditions=[{'condition': {'type': 'django', 'value': '{{}'}, 'error_message': 'Error'}],
86
        ),
87
    ]
88
    formdef.store()
89

  
90
    formdata = formdef.data_class()()
91
    formdata.just_created()
92
    formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
93

  
94
    testdef = TestDef.create_from_formdata(formdef, formdata)
95
    with pytest.raises(TestError) as excinfo:
96
        testdef.run(formdef)
97
    assert str(excinfo.value) == 'Failed to evaluate page 1 post condition.'
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
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
            self.testdef.remove_self()
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 .qommon.storage import StorableObject
17
from quixote import get_publisher
18

  
19
from .qommon import _, misc
20
from .qommon.storage import Equal, StorableObject
21

  
22

  
23
class TestError(Exception):
24
    pass
18 25

  
19 26

  
20 27
class TestDef(StorableObject):
......
26 33
    object_id = None
27 34

  
28 35
    data = None  # (json export of formdata, carddata, etc.)
36

  
37
    def __str__(self):
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
    @classmethod
52
    def create_from_formdata(cls, formdef, formdata):
53
        testdef = cls()
54
        testdef.object_type = formdef.get_table_name()
55
        testdef.object_id = formdef.id
56
        testdef.data = {
57
            'fields': formdata.data,
58
            'user': formdata.user.get_json_export_dict() if formdata.user else None,
59
        }
60
        return testdef
61

  
62
    def run(self, objectdef):
63
        formdata = objectdef.data_class()()
64
        if self.data['user']:
65
            formdata.set_user_from_json(self.data['user'])
66

  
67
        get_publisher().substitutions.reset()
68
        get_publisher().substitutions.feed(get_publisher())
69
        get_publisher().substitutions.feed(objectdef)
70
        get_publisher().substitutions.feed(formdata)
71

  
72
        page_no = 0
73
        page_post_conditions = []
74
        for field in objectdef.fields:
75
            if field.type == 'page':
76
                if page_post_conditions:
77
                    self.evaluate_page_conditions(page_no, formdata, objectdef, page_post_conditions)
78

  
79
                page_post_conditions = field.post_conditions or []
80
                page_no += 1
81
                continue
82

  
83
            if field.type in ('subtitle', 'title', 'comment', 'computed'):
84
                continue
85

  
86
            formdata.data[field.id] = self.data['fields'].get(field.id)
87
            get_publisher().substitutions.invalidate_cache()
88

  
89
        # evaluate last page post condition
90
        self.evaluate_page_conditions(page_no, formdata, objectdef, page_post_conditions)
91

  
92
    def evaluate_page_conditions(self, page_no, form_data, objectdef, conditions):
93
        from wcs.fields import Field
94

  
95
        for post_condition in conditions:
96
            condition = post_condition.get('condition', {})
97
            try:
98
                if not Field.evaluate_condition(form_data.data, objectdef, condition):
99
                    raise TestError(
100
                        _('Page %(no)s post condition was not met (%(condition)s).')
101
                        % {'no': page_no, 'condition': condition.get('value')}
102
                    )
103
            except RuntimeError:
104
                raise TestError(_('Failed to evaluate page %s post condition.') % page_no)
105

  
106
    def export_to_json(self):
107
        return {
108
            'name': self.name,
109
            'slug': self.slug,
110
            'object_type': self.object_type,
111
            'object_id': self.object_id,
112
            'data': self.data,
113
        }
114

  
115
    @classmethod
116
    def import_from_json(cls, data):
117
        testdefs = TestDef.select(
118
            [
119
                Equal('object_type', data['object_type']),
120
                Equal('object_id', data['object_id']),
121
                Equal('slug', data['slug']),
122
            ]
123
        )
124

  
125
        testdef = testdefs[0] if testdefs else TestDef()
126

  
127
        for k, v in data.items():
128
            setattr(testdef, k, v)
129

  
130
        testdef.store()
131
        return testdef
29
-