def remote_methods(self, fixture): """A RemoteMethod is a SubResource representing a method on the server side which can be invoked via POSTing to an URL.""" def callable_object(): return 'value returned from method' encoding = 'koi8_r' # Deliberate remote_method = RemoteMethod( 'amethod', callable_object, MethodResult(mime_type='ttext/hhtml', encoding=encoding)) @stubclass(Widget) class WidgetWithRemoteMethod(Widget): def __init__(self, view): super(WidgetWithRemoteMethod, self).__init__(view) view.add_resource(remote_method) wsgi_app = fixture.new_wsgi_app( view_slots={'main': WidgetWithRemoteMethod.factory()}) browser = Browser(wsgi_app) # By default you cannot GET, since the method is not immutable browser.open('/_amethod_method', status=405) # POSTing to the URL, returns the result of the method browser.post('/_amethod_method', {}) vassert(browser.raw_html == 'value returned from method') vassert(browser.last_response.charset == encoding) vassert(browser.last_response.content_type == 'ttext/hhtml')
def test_checked_arguments(web_fixture, remote_method_fixture, argument_scenarios): """A CheckedRemoteMethod checks and marshalls its parameters using Fields.""" fixture = argument_scenarios def callable_object(anint=None, astring=None): fixture.method_kwargs = {'anint': anint, 'astring': astring} return '' remote_method = CheckedRemoteMethod(web_fixture.view, 'amethod', callable_object, MethodResult(), idempotent=fixture.idempotent, anint=IntegerField(), astring=Field()) wsgi_app = remote_method_fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) if fixture.idempotent: browser.open( '/_amethod_method?anint=5&astring=SupercalifraGilisticexpialidocious' ) else: browser.post('/_amethod_method', { 'anint': '5', 'astring': 'SupercalifraGilisticexpialidocious' }) assert fixture.method_kwargs == { 'anint': 5, 'astring': 'SupercalifraGilisticexpialidocious' }
def test_arguments_to_remote_methods(web_fixture, remote_method_fixture, argument_scenarios): """A RemoteMethod can get arguments from a query string or submitted form values, depending on the scenario.""" fixture = argument_scenarios def callable_object(**kwargs): fixture.method_kwargs = kwargs return '' remote_method = RemoteMethod(web_fixture.view, 'amethod', callable_object, MethodResult(), idempotent=fixture.idempotent) wsgi_app = remote_method_fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) kwargs_sent = {'a': 'AAA', 'b': 'BBB'} if fixture.idempotent: browser.open('/_amethod_method?a=AAA&b=BBB') else: browser.post('/_amethod_method', kwargs_sent) assert fixture.method_kwargs == kwargs_sent
def non_writable_events_are_dealt_with_like_invalid_input(self, fixture): """If a form submits an Event with access rights that prohibit writing, a ValidationException is raised.""" class ModelObject(object): @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(TestPanel, self).__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 = fixture.new_wsgi_app(child_factory=TestPanel.factory()) browser = Browser(wsgi_app) browser.open('/') browser.post(fixture.form.event_channel.get_url().path, {'event.an_event?': ''}) browser.follow_response() input_id = browser.get_id_of('//input[@name="event.an_event?"]') error_label = browser.get_html_for('//label') vassert(error_label == '<label for="%s" class="error">you cannot do this</label>' % input_id)
def checked_arguments(self, fixture): """A CheckedRemoteMethod checks and marshalls its parameters using Fields.""" def callable_object(anint=None, astring=None): fixture.method_kwargs = {'anint': anint, 'astring': astring} return '' remote_method = CheckedRemoteMethod('amethod', callable_object, MethodResult(), immutable=fixture.immutable, anint=IntegerField(), astring=Field()) wsgi_app = fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) if fixture.immutable: browser.open( '/_amethod_method?anint=5&astring=SupercalifraGilisticexpialidocious' ) else: browser.post('/_amethod_method', { 'anint': '5', 'astring': 'SupercalifraGilisticexpialidocious' }) vassert(fixture.method_kwargs == { 'anint': 5, 'astring': 'SupercalifraGilisticexpialidocious' })
def view_preconditions(self, fixture): """Views can have Conditions attached to them - if a ViewPreCondition fails upon a GET or HEAD request, the specified exception is raised. For all other requests, HTTPNotFound is raised.""" class SomeException(Exception): pass class MainUI(UserInterface): def assemble(self): self.define_page(HTML5Page).use_layout( PageLayout( contents_layout=ColumnLayout('main').with_slots())) slot_definitions = {'main': Form.factory('the_form')} view = self.define_view('/', title='Hello', slot_definitions=slot_definitions) failing_precondition = ViewPreCondition( lambda: False, exception=SomeException) passing_precondition = ViewPreCondition(lambda: True) view.add_precondition(passing_precondition) view.add_precondition(failing_precondition) wsgi_app = fixture.new_wsgi_app(site_root=MainUI) browser = Browser(wsgi_app) with expected(SomeException): browser.open('/') browser.post('/_the_form', {}, status=404)
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(object): 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(MyForm, self).__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.post( '/__myform_method', { 'event.myform-an_event?': '', '_noredirect': '', 'myform-_reahl_client_concurrency_digest': '', 'myform-_reahl_database_concurrency_digest': '' }) 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'] browser.open('/') expected_html = browser.get_inner_html_for('//form[1]') assert json_dict['widgets']['myform'].startswith(expected_html + '<script')
def test_exception_handling(web_fixture, remote_method_fixture): """The RemoteMethod sends back the str() of an exception raised for the specified exception class.""" def fail(): raise Exception('I failed') remote_method = RemoteMethod(web_fixture.view, 'amethod', fail, MethodResult(catch_exception=Exception)) wsgi_app = remote_method_fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) browser.post('/_amethod_method', {}) assert browser.raw_html == 'I failed'
def widgets_that_change_during_method_processing(self, fixture): """The Widget rendered by WidgetResult reflects its Widget as it would have looked if it were constructed AFTER the changes effected by executing its RemoteMethod have been committed. """ wsgi_app = fixture.new_wsgi_app( child_factory=fixture.WidgetWithRemoteMethod.factory()) browser = Browser(wsgi_app) browser.post('/_amethod_method', {}) json_response = json.loads(browser.raw_html) vassert(json_response == fixture.expected_response)
def exception_handling(self, fixture): """The RemoteMethod sends back the six.text_type() of an exception raised for the specified exception class.""" def fail(): raise Exception('I failed') remote_method = RemoteMethod('amethod', fail, MethodResult(catch_exception=Exception)) wsgi_app = fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) browser.post('/_amethod_method', {}) vassert(browser.raw_html == 'I failed')
def exception_handling_for_widgets(self, fixture): """How exceptions are handled with WidgetResult.""" def fail(): raise Exception('exception text') remote_method = RemoteMethod('amethod', fail, default_result=fixture.method_result) wsgi_app = fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) with expected(Exception): browser.post('/_amethod_method', {})
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(object): 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(MyForm, self).__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.post( '/__myform_method', { 'event.myform-an_event?some_argument=f~nnystuff': '', 'myform-_reahl_client_concurrency_digest': '', 'myform-_reahl_database_concurrency_digest': '' }) assert model_object.received_argument == 'f~nnystuff'
def regenerating_method_results(self, fixture): """If a MethodResult is set up to replay_request=True, the view it is part of (and thus itself) is recreated before the (new incarnation of the) MethodResult generates its actual response. Replaying the request means recreating all Widgets on the current View as well as the MethodResult itself. The construction of any of these objects may happen differently because of the changes made during the RemoteMethod's execution. Replaying the request ensures that the MethodResult reflects such changes, yet ensures that the RemoteMethod is not executed twice. """ wsgi_app = fixture.new_wsgi_app(remote_method=fixture.remote_method) browser = Browser(wsgi_app) with CallMonitor(fixture.system_control.orm_control.commit) as monitor: browser.post('/_amethod_method', {}) vassert(browser.raw_html == fixture.expected_response) vassert(monitor.times_called == 2)
def exception_handling_for_json(self, fixture): """How exceptions are handled with JsonResult.""" def fail(): raise Exception('exception text') remote_method = RemoteMethod('amethod', fail, default_result=fixture.method_result) wsgi_app = fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) browser.post('/_amethod_method', {}) vassert(browser.raw_html == fixture.exception_response) vassert(browser.last_response.charset == fixture.expected_charset) vassert(browser.last_response.content_type == fixture.expected_content_type)
def different_kinds_of_result(self, fixture): """Different kinds of MethodResult can be specified for a method.""" def callable_object(): return fixture.value_to_return remote_method = RemoteMethod('amethod', callable_object, default_result=fixture.method_result) wsgi_app = fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) browser.post('/_amethod_method', {}) vassert( fixture.results_match(fixture.expected_response, browser.raw_html)) vassert(browser.last_response.charset == fixture.expected_charset) vassert(browser.last_response.content_type == fixture.expected_content_type)
def immutable_remote_methods(self, fixture): """A RemoteMethod that is immutable is accessible via GET (instead of POST).""" def callable_object(): return 'value returned from method' remote_method = RemoteMethod('amethod', callable_object, MethodResult(), immutable=True) wsgi_app = fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) # GET, since the method is immutable browser.open('/_amethod_method') vassert(browser.raw_html == 'value returned from method') # POSTing to the URL, is not supported browser.post('/_amethod_method', {}, status=405)
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(object): 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(TestPanel, self).__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('/') browser.post( fixture.form.event_channel.get_url().path, { 'event.some_form-an_event?': '', 'field_name': 'illigitimate value', 'some_form-_reahl_client_concurrency_digest': '', 'some_form-_reahl_database_concurrency_digest': '' }) browser.follow_response() assert model_object.field_name == 'Original value'
def test_exception_handling_for_widgets(web_fixture, remote_method_fixture, widget_result_scenario): """How exceptions are handled with WidgetResult.""" fixture = widget_result_scenario def fail(): raise Exception('exception text') remote_method = RemoteMethod(web_fixture.view, 'amethod', fail, default_result=fixture.method_result) wsgi_app = remote_method_fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) with expected(Exception): browser.post('/_amethod_method', {})
def test_coactive_widgets(web_fixture): """Coactive Widgets of a Widget are Widgets that are included in a WidgetResult for that Widget. Included are: the coactive widgets of children of the result widget as well as the coactive widgets of coactive widgets. """ @stubclass(Widget) class WidgetWithRemoteMethod(Widget): def __init__(self, view): super(WidgetWithRemoteMethod, self).__init__(view) coactive_widgets = [ self.add_child( CoactiveWidgetStub(view, 'coactive1', [ self.add_child( CoactiveWidgetStub(view, 'coactive2', [])) ])) ] result_widget = self.add_child(CoactiveWidgetStub( view, 'main', [])) result_widget.add_child( CoactiveWidgetStub(view, 'child', coactive_widgets)) method_result = WidgetResult(result_widget, as_json_and_result=True) remote_method = RemoteMethod(view, 'amethod', lambda: None, default_result=method_result) view.add_resource(remote_method) wsgi_app = web_fixture.new_wsgi_app( child_factory=WidgetWithRemoteMethod.factory()) browser = Browser(wsgi_app) browser.post('/_amethod_method', {}) json_response = json.loads(browser.raw_html) assert json_response == { 'success': True, 'widgets': { 'main': '<main><script type="text/javascript"></script>', 'coactive1': '<coactive1><script type="text/javascript"></script>', 'coactive2': '<coactive2><script type="text/javascript"></script>' } }
def arguments_to_remote_methods(self, fixture): """A RemoteMethod can get arguments from a query string or submitted form values, depending on the scenario.""" def callable_object(**kwargs): fixture.method_kwargs = kwargs return '' remote_method = RemoteMethod('amethod', callable_object, MethodResult(), immutable=fixture.immutable) wsgi_app = fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) kwargs_sent = {'a': 'AAA', 'b': 'BBB'} if fixture.immutable: browser.open('/_amethod_method?a=AAA&b=BBB') else: browser.post('/_amethod_method', kwargs_sent) vassert(fixture.method_kwargs == kwargs_sent)
def test_idempotent_remote_methods(web_fixture, remote_method_fixture): """A RemoteMethod that is idempotent is accessible via GET (instead of POST).""" def callable_object(): return 'value returned from method' remote_method = RemoteMethod(web_fixture.view, 'amethod', callable_object, MethodResult(), idempotent=True) wsgi_app = remote_method_fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) # GET, since the method is idempotent browser.open('/_amethod_method') assert browser.raw_html == 'value returned from method' # POSTing to the URL, is not supported browser.post('/_amethod_method', {}, status=405)
def test_exception_handling_for_json(web_fixture, remote_method_fixture, json_result_scenario): """How exceptions are handled with JsonResult.""" fixture = json_result_scenario def fail(): raise Exception('exception text') remote_method = RemoteMethod(web_fixture.view, 'amethod', fail, default_result=fixture.method_result) wsgi_app = remote_method_fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) browser.post('/_amethod_method', {}) assert browser.raw_html == fixture.exception_response assert browser.last_response.charset == fixture.expected_charset assert browser.last_response.content_type == fixture.expected_content_type
def test_different_kinds_of_result(web_fixture, remote_method_fixture, result_scenarios): """Different kinds of MethodResult can be specified for a method.""" fixture = result_scenarios def callable_object(): return fixture.value_to_return remote_method = RemoteMethod(web_fixture.view, 'amethod', callable_object, default_result=fixture.method_result) wsgi_app = remote_method_fixture.new_wsgi_app(remote_method=remote_method) browser = Browser(wsgi_app) browser.post('/_amethod_method', {}) assert fixture.results_match(fixture.expected_response, browser.raw_html) assert browser.last_response.charset == fixture.expected_charset assert browser.last_response.content_type == fixture.expected_content_type
def test_regenerating_method_results(reahl_system_fixture, web_fixture, remote_method_fixture, regenerate_method_result_scenarios): """If a MethodResult is set up to replay_request=True, the view it is part of (and thus itself) is recreated before the (new incarnation of the) MethodResult generates its actual response. Replaying the request means recreating all Widgets on the current View as well as the MethodResult itself. The construction of any of these objects may happen differently because of the changes made during the RemoteMethod's execution. Replaying the request ensures that the MethodResult reflects such changes, yet ensures that the RemoteMethod is not executed twice. """ wsgi_app = remote_method_fixture.new_wsgi_app( remote_method=regenerate_method_result_scenarios.remote_method) browser = Browser(wsgi_app) import sqlalchemy.orm @stubclass(sqlalchemy.orm.Session) class TransactionStub(object): is_active = True def commit(self): pass def rollback(self): pass def wrapped_nested_transaction(): return web_fixture.nested_transaction web_fixture.nested_transaction = TransactionStub() with replaced(Session().begin_nested, wrapped_nested_transaction): with CallMonitor(web_fixture.nested_transaction.commit) as monitor: browser.post('/_amethod_method', {}) assert browser.raw_html == regenerate_method_result_scenarios.expected_response assert monitor.times_called == 2