def test_non_writable_events_are_dealt_with_like_invalid_input(web_fixture): """If a form submits an Event with access rights that prohibit writing, a ValidationException is raised.""" fixture = web_fixture class ModelObject: @exposed def events(self, events): events.an_event = Event(label='click me', writable=Allowed(False), disallowed_message='you cannot do this') model_object = ModelObject() class TestPanel(Div): def __init__(self, view): super().__init__(view) form = self.add_child(Form(view, 'some_form')) form.define_event_handler(model_object.events.an_event) button = form.add_child(ButtonInput(form, model_object.events.an_event)) if button.validation_error: form.add_child(form.create_error_label(button)) fixture.form = form wsgi_app = web_fixture.new_wsgi_app(child_factory=TestPanel.factory()) browser = Browser(wsgi_app) browser.open('/') csrf_token = browser.get_value('//input[@name="some_form-_reahl_csrf_token"]') browser.post(fixture.form.event_channel.get_url().path, {'event.some_form-an_event?':'', 'some_form-_reahl_database_concurrency_digest':'', 'some_form-_reahl_csrf_token': csrf_token}) browser.follow_response() error_label = browser.get_html_for('//label') input_id = browser.get_id_of('//input[@name="event.some_form-an_event?"]') assert error_label == '<label for="%s" class="error">you cannot do this</label>' % input_id
def test_query_string_prepopulates_form(web_fixture, value_scenarios): """Widget query string arguments can be used on forms to pre-populate inputs based on the query string.""" fixture = value_scenarios class ModelObject: @exposed def fields(self, fields): fields.arg_on_other_object = fixture.field class FormWithQueryArguments(Form): def __init__(self, view): self.model_object = ModelObject() super().__init__(view, 'name') self.use_layout(FormLayout()) self.layout.add_input( TextInput(self, self.model_object.fields.arg_on_other_object)) @exposed def query_fields(self, fields): fields.arg_on_other_object = self.model_object.fields.arg_on_other_object wsgi_app = web_fixture.new_wsgi_app( enable_js=True, child_factory=FormWithQueryArguments.factory()) browser = Browser(wsgi_app) browser.open('/?%s' % fixture.field_on_query_string.format( field_name='name-arg_on_other_object')) assert browser.get_value( XPath.input_labelled('field')) == fixture.field_value_as_string
def test_non_writable_input_is_dealt_with_like_invalid_input(web_fixture): """If a form submits a value for an Input that is linked to Field with access rights that prohibit writing, the input is silently ignored.""" fixture = web_fixture class ModelObject: field_name = 'Original value' @exposed def events(self, events): events.an_event = Event(label='click me') @exposed def fields(self, fields): fields.field_name = Field(default='abc', writable=Allowed(False), disallowed_message='you are not allowed to write this') model_object = ModelObject() class TestPanel(Div): def __init__(self, view): super().__init__(view) form = self.add_child(Form(view, 'some_form')) form.define_event_handler(model_object.events.an_event) form.add_child(ButtonInput(form, model_object.events.an_event)) form.add_child(TextInput(form, model_object.fields.field_name)) fixture.form = form wsgi_app = web_fixture.new_wsgi_app(child_factory=TestPanel.factory()) browser = Browser(wsgi_app) browser.open('/') csrf_token = browser.get_value('//input[@name="some_form-_reahl_csrf_token"]') browser.post(fixture.form.event_channel.get_url().path, {'event.some_form-an_event?':'', 'some_form-field_name': 'illigitimate value', 'some_form-_reahl_database_concurrency_digest':'', 'some_form-_reahl_csrf_token': csrf_token}) browser.follow_response() assert model_object.field_name == 'Original value'
def test_alternative_event_trigerring(web_fixture): """Events can also be triggered by submitting a Form via Ajax. In such cases the normal redirect-after-submit behaviour of the underlying EventChannel is not desirable. This behaviour can be switched off by submitting an extra argument along with the Form in order to request the alternative behaviour. """ fixture = web_fixture class ModelObject: def handle_event(self): self.handled_event = True @exposed def events(self, events): events.an_event = Event(label='click me', action=Action(self.handle_event)) model_object = ModelObject() class MyForm(Form): def __init__(self, view, name, other_view): super().__init__(view, name) self.define_event_handler(model_object.events.an_event, target=other_view) self.add_child(ButtonInput(self, model_object.events.an_event)) class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout(BasicPageLayout()) home = self.define_view('/', title='Home page') other_view = self.define_view('/page2', title='Page 2') home.set_slot('main', MyForm.factory('myform', other_view)) wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) # when POSTing with _noredirect, the Action is executed, but the browser is not redirected to /page2 as usual browser.open('/') csrf_token = browser.get_value('//input[@name="myform-_reahl_csrf_token"]') browser.post( '/__myform_method', { 'event.myform-an_event?': '', '_noredirect': '', 'myform-_reahl_database_concurrency_digest': '', 'myform-_reahl_csrf_token': csrf_token }) browser.follow_response( ) # Needed to make the test break should a HTTPTemporaryRedirect response be sent assert model_object.handled_event assert browser.current_url.path != '/page2' assert browser.current_url.path == '/__myform_method' # the response is a json object reporting the success of the event and a new rendition of the form json_dict = json.loads(browser.raw_html) assert json_dict['success'] expected_html = '<div id="myform_hashes">' assert json_dict['result']['myform'].startswith(expected_html)
def test_event_names_are_canonicalised(web_fixture): """The name= attribute of a button is an url encoded string. There is more than one way to url encode the same string. The server ensures that different encodings of the same string are not mistaken for different names. """ fixture = web_fixture class ModelObject: def handle_event(self, some_argument): self.received_argument = some_argument @exposed def events(self, events): events.an_event = Event( label='click me', action=Action(self.handle_event, ['some_argument']), some_argument=Field(default='default value')) model_object = ModelObject() class MyForm(Form): def __init__(self, view, name): super().__init__(view, name) self.define_event_handler(model_object.events.an_event) self.add_child( ButtonInput( self, model_object.events.an_event.with_arguments( some_argument='f~nnystuff'))) class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout(BasicPageLayout()) home = self.define_view('/', title='Home page') home.set_slot('main', MyForm.factory('myform')) wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) # when the Action is executed, the correct arguments are passed browser.open('/') csrf_token = browser.get_value('//input[@name="myform-_reahl_csrf_token"]') browser.post( '/__myform_method', { 'event.myform-an_event?some_argument=f~nnystuff': '', 'myform-_reahl_database_concurrency_digest': '', 'myform-_reahl_csrf_token': csrf_token }) assert model_object.received_argument == 'f~nnystuff'
def test_email_retained(web_fixture, session_scope_fixture): """The email address used when last logged in is always pre-populated on the Log in page.""" browser = Browser(web_fixture.new_wsgi_app(site_root=SessionScopeUI)) user = session_scope_fixture.user browser.open('/') browser.click(XPath.link().with_text('Log in')) browser.type(XPath.input_labelled('Email'), '*****@*****.**') browser.type(XPath.input_labelled('Password'), 'topsecret') browser.click(XPath.button_labelled('Log in')) # Go away from the page, then back browser.click(XPath.link().with_text('Home')) browser.click(XPath.link().with_text('Log in')) # .. then the email is still pre-populated typed_value = browser.get_value(XPath.input_labelled('Email')) assert typed_value == '*****@*****.**'
def test_form_preserves_user_input_after_validation_exceptions_multichoice( web_fixture): """When a form is submitted and validation fails on the server, the user is presented with the values that were originally entered (even if they were invalid).""" fixture = web_fixture class ModelObject: @exposed def events(self, events): events.an_event = Event(label='click me') @exposed def fields(self, fields): choices = [ Choice(1, IntegerField(label='One')), Choice(2, IntegerField(label='Two')), Choice(3, IntegerField(label='Three')) ] fields.no_validation_exception_field = MultiChoiceField( choices, label='Make your invalid choice', default=[]) fields.validation_exception_field = MultiChoiceField( choices, label='Make your choice', default=[], required=True) model_object = ModelObject() class MyForm(Form): def __init__(self, view): super().__init__(view, 'my_form') self.define_event_handler(model_object.events.an_event) self.add_child(ButtonInput(self, model_object.events.an_event)) select_input = self.add_child( SelectInput(self, model_object.fields.no_validation_exception_field)) if select_input.validation_error: self.add_child(self.create_error_label(select_input)) select_input = self.add_child( SelectInput(self, model_object.fields.validation_exception_field)) if select_input.validation_error: self.add_child(self.create_error_label(select_input)) wsgi_app = web_fixture.new_wsgi_app(child_factory=MyForm.factory()) browser = Browser(wsgi_app) browser.open('/') no_validation_exception_input = '//select[@name="my_form-no_validation_exception_field[]"]' validation_exception_input = '//select[@name="my_form-validation_exception_field[]"]' browser.select_many(no_validation_exception_input, ['One', 'Two']) browser.select_none(validation_exception_input ) # select none to trigger the RequiredConstraint browser.click(XPath.button_labelled('click me')) assert browser.get_value(no_validation_exception_input) == ['1', '2'] assert not browser.get_value(validation_exception_input) label = browser.get_html_for('//label') input_id = browser.get_id_of(validation_exception_input) assert label == '<label for="%s" class="error">Make your choice is required</label>' % input_id #2. Submit again ths time not expecting validation exceptions, also expecting the validation error to be cleared and the domain should have all input browser.select_many(validation_exception_input, ['Two', 'Three']) browser.click(XPath.button_labelled('click me')) assert not browser.is_element_present('//label[@class="error"]') assert browser.get_value(no_validation_exception_input) == ['1', '2'] assert browser.get_value(validation_exception_input) == ['2', '3']
def test_exception_handling(reahl_system_fixture, web_fixture, sql_alchemy_fixture): """When a DomainException happens during the handling of an Event: The database is rolled back. The browser is redirected to GET the original view again (not the target). The screen still displays the values the user initially typed, not those on the ModelObject. """ fixture = web_fixture class ModelObject(Base): __tablename__ = 'test_event_handling_exception_handling' id = Column(Integer, primary_key=True) field_name = Column(Integer) def handle_event(self): self.field_name = 1 raise DomainException() @exposed def events(self, events): events.an_event = Event(label='click me', action=Action(self.handle_event)) @exposed def fields(self, fields): fields.field_name = IntegerField(default=3) with sql_alchemy_fixture.persistent_test_classes(ModelObject): model_object = ModelObject() Session.add(model_object) class MyForm(Form): def __init__(self, view, name, other_view): super().__init__(view, name) self.define_event_handler(model_object.events.an_event, target=other_view) self.add_child(ButtonInput(self, model_object.events.an_event)) self.add_child(TextInput(self, model_object.fields.field_name)) class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout(BasicPageLayout()) home = self.define_view('/', title='Home page') other_view = self.define_view('/page2', title='Page 2') home.set_slot('main', MyForm.factory('myform', other_view)) wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser( wsgi_app ) # Dont use a real browser, because it will also hit many other URLs for js and css which confuse the issue browser.open('/') assert not model_object.field_name browser.type("//input[@type='text']", '5') # any database stuff that happened when the form was submitted was rolled back # with CallMonitor(reahl_system_fixture.system_control.orm_control.rollback) as monitor: # browser.click(XPath.button_labelled('click me')) # assert monitor.times_called == 1 browser.click(XPath.button_labelled('click me')) # the value input by the user is still displayed on the form, NOT the actual value on the model object assert not model_object.field_name retained_value = browser.get_value("//input[@type='text']") assert retained_value == '5' # the browser is still on the page with the form which triggered the exception assert browser.current_url.path == '/'