def _get_webactions(self): if 'Manager' in api.user.get_roles(): # Manager may always list all actions return get_storage().list() else: # Other users may only see their own return get_storage().list(owner=api.user.get_current().id)
def test_delete_webaction(self): storage = get_storage() action = create(Builder('webaction')) self.assertEqual(1, len(storage._actions)) storage.delete(action['action_id']) self.assertEqual(0, len(storage._actions)) self.assertEqual({}, dict(storage._actions))
def test_update_webaction(self): self.login(self.manager) storage = get_storage() with freeze(datetime(2019, 12, 31, 17, 45)): action = create(Builder('webaction').titled(u'Open in ExternalApp') .having( title=u'Open in ExternalApp', target_url='http://example.org/endpoint', )) action_id = action['action_id'] self.assertEqual(action, storage.get(action_id)) with freeze(datetime(2020, 7, 31, 19, 15)): storage.update(action_id, {'title': u'My new title'}) self.assertIsInstance(storage.get(action_id), dict) self.assertEqual({ 'action_id': 0, 'title': u'My new title', 'target_url': 'http://example.org/endpoint', 'display': 'actions-menu', 'mode': 'self', 'order': 0, 'scope': 'global', 'created': datetime(2019, 12, 31, 17, 45), 'modified': datetime(2020, 7, 31, 19, 15), 'owner': 'admin', }, storage.get(action_id))
def test_can_update_webaction(self, browser): self.login(self.webaction_manager, browser=browser) with freeze(datetime(2019, 12, 31, 17, 45)): action = create(Builder('webaction') .having( title=u'Open in ExternalApp', target_url='http://example.org/endpoint', )) url = '%s/@webactions/%s' % (self.portal.absolute_url(), action['action_id']) with freeze(datetime(2020, 7, 31, 19, 15)): browser.open(url, method='PATCH', data=json.dumps({'title': u'My new title'}), headers=self.HEADERS) self.assertEqual(204, browser.status_code) self.assertEqual('', browser.contents) storage = get_storage() self.assertEqual({ 'action_id': 0, 'title': u'My new title', 'target_url': 'http://example.org/endpoint', 'display': 'actions-menu', 'mode': 'self', 'order': 0, 'scope': 'global', 'created': datetime(2019, 12, 31, 17, 45), 'modified': datetime(2020, 7, 31, 19, 15), 'owner': 'webaction.manager', }, storage.get(action['action_id']))
def update(self, action): action_delta = json_body(self.request) # Reject any fields that aren't user-controlled or unknown errors = get_unknown_fields(action_delta, IWebActionSchema) if errors: raise BadRequest(errors) scrub_json_payload(action_delta) # Validate on a copy action_copy = action.copy() action_copy.update(action_delta) errors = get_validation_errors(action_copy, IPersistedWebActionSchema) if errors: raise BadRequest(errors) # If validation succeeded, update actual action storage = get_storage() try: storage.update(action['action_id'], action_delta) except ActionAlreadyExists as exc: raise BadRequest([('unique_name', exc)]) self.request.response.setStatus(204) return _no_content_marker
def applyChanges(self, data): # Based on z3c.form.form.applyChanges, but without firing events # and modifying the webaction in its storage. webaction = self.get_webaction() changes = {} webaction_data = {} for name, field in self.fields.items(): # If the field is not in the data, then go on to the next one try: new_value = data[name] except KeyError: continue # If the value is NOT_CHANGED, ignore it, since the # widget/converter sent a strong message not to do so. if new_value is NOT_CHANGED: continue if self.field_value_has_changed(field.field, new_value, webaction): # Only update the data if it changed # TODO: Should we possibly be using toFieldValue here? webaction_data[name] = new_value # Record the change using information required later changes.setdefault(field.interface, []).append(name) if changes: storage = get_storage() storage.update(self.get_action_id(), webaction_data) return changes
def test_only_saves_modified_fields(self, browser): self.login(self.webaction_manager, browser) webaction_before = get_storage().list()[0] with freeze(datetime(2018, 4, 22)): browser.open(self.get_edit_url()) browser.fill({"Title": "changed title"}) browser.click_on("Save") webaction = get_storage().list()[0] self.assertEqual(['Data successfully updated.'], info_messages()) self.assertNotEqual(webaction_before.pop("title"), webaction.get("title")) self.assertEqual("changed title", webaction.pop("title")) self.assertNotEqual(webaction_before.pop("modified"), webaction.get("modified")) self.assertEqual(datetime(2018, 4, 22), webaction.pop("modified")) self.assertDictEqual(webaction_before, webaction)
def test_cant_add_webaction_with_same_unique_name_twice(self): storage = get_storage() new_action = { 'title': u'Open in ExternalApp', 'target_url': 'http://example.org/endpoint', 'display': 'actions-menu', 'mode': 'self', 'order': 0, 'scope': 'global', 'unique_name': u'open-in-external-app-title-action', } with freeze(datetime(2019, 12, 31, 17, 45)): action_id = storage.add(new_action) self.assertEqual(1, len(storage.list())) action_from_storage = storage.get(action_id) self.assertEqual(u'Open in ExternalApp', action_from_storage['title']) # Same unique_name, just a different title. Should be rejected. new_action['title'] = u'Launch in ExternalApp' with self.assertRaises(ActionAlreadyExists) as cm: storage.add(new_action) self.assertEqual( "An action with the unique_name u'open-in-external-app-title-action' already exists", str(cm.exception))
def test_cant_add_webaction_with_same_unique_name_twice(self): storage = get_storage() new_action = { 'title': u'Open in ExternalApp', 'target_url': 'http://example.org/endpoint', 'display': 'actions-menu', 'mode': 'self', 'order': 0, 'scope': 'global', 'unique_name': u'open-in-external-app-title-action', } with freeze(datetime(2019, 12, 31, 17, 45)): action_id = storage.add(new_action) self.assertEqual(1, len(storage.list())) action_from_storage = storage.get(action_id) self.assertEqual(u'Open in ExternalApp', action_from_storage['title']) # Same unique_name, just a different title. Should be rejected. new_action['title'] = u'Launch in ExternalApp' with self.assertRaises(ActionAlreadyExists) as cm: storage.add(new_action) self.assertEqual( "An action with the unique_name u'open-in-external-app-title-action' already exists", cm.exception.message)
def test_webaction_provider_respects_portal_type(self): self.login(self.regular_user) storage = get_storage() storage.update(0, {"types": ['opengever.document.document']}) storage.update(1, {"types": ['opengever.document.document', 'opengever.dossier.businesscasedossier']}) provider = getMultiAdapter((self.dossier, self.request), IWebActionsProvider) expected_data = {'actions-menu': [self.action2_data, self.action3_data]} self.assertItemsEqual(expected_data, provider.get_webactions()) self.clear_request_cache() provider = getMultiAdapter((self.document, self.request), IWebActionsProvider) expected_data = {'actions-menu': [self.action1_data, self.action2_data, self.action3_data]} self.assertDictEqual(expected_data, provider.get_webactions()) self.clear_request_cache() provider = getMultiAdapter((self.leaf_repofolder, self.request), IWebActionsProvider) expected_data = {'actions-menu': [self.action3_data]} self.assertDictEqual(expected_data, provider.get_webactions())
def test_can_update_webaction(self, browser): self.login(self.webaction_manager, browser=browser) with freeze(datetime(2019, 12, 31, 17, 45)): action = create( Builder('webaction').having( title=u'Open in ExternalApp', target_url='http://example.org/endpoint', )) url = '%s/@webactions/%s' % (self.portal.absolute_url(), action['action_id']) with freeze(datetime(2020, 7, 31, 19, 15)): browser.open(url, method='PATCH', data=json.dumps({'title': u'My new title'}), headers=self.HEADERS) self.assertEqual(204, browser.status_code) self.assertEqual('', browser.contents) storage = get_storage() self.assertEqual( { 'action_id': 0, 'title': u'My new title', 'target_url': 'http://example.org/endpoint', 'display': 'actions-menu', 'mode': 'self', 'order': 0, 'scope': 'global', 'created': datetime(2019, 12, 31, 17, 45), 'modified': datetime(2020, 7, 31, 19, 15), 'owner': 'webaction.manager', }, storage.get(action['action_id']))
def delete(self, action): storage = get_storage() # We can't have a KeyError here because action has already been # verified as existing by locator storage.delete(action['action_id']) self.request.response.setStatus(204) return _no_content_marker
def test_returns_only_actions_owned_by_user(self): self.login(self.webaction_manager) storage = get_storage() storage._actions[1]["owner"] = self.regular_user.id expected_data = [self.action1_data, self.action3_data] self.assertEqual(expected_data, self.get_webactions())
def test_webaction_provider_only_returns_enabled_actions(self): self.login(self.regular_user) provider = getMultiAdapter((self.dossier, self.request), IWebActionsProvider) storage = get_storage() storage.update(1, {"enabled": False}) expected_data = {'actions-menu': [self.action1_data, self.action3_data]} self.assertDictEqual(expected_data, provider.get_webactions())
def test_no_modifications(self, browser): self.login(self.webaction_manager, browser) browser.open(self.get_edit_url()) browser.click_on("Save") webaction = get_storage().list()[0] self.assertEqual(['No changes were applied.'], info_messages()) self.assertEqual(datetime(2018, 4, 20), webaction.get("modified"))
def test_new_action_id_is_based_on_a_counter_in_storage(self): storage = get_storage() storage._storage['next_id'] = 42 self.assertEqual(42, storage.issue_new_action_id()) self.assertEqual(43, storage._storage['next_id']) new_action = create(Builder('webaction')) self.assertEqual(43, new_action['action_id'])
def test_returns_all_actions_for_managers(self): self.login(self.manager) storage = get_storage() storage._actions[1]["owner"] = self.regular_user.id self.action2_data["owner"] = self.regular_user.id expected_data = [self.action1_data, self.action2_data, self.action3_data] self.assertEqual(expected_data, self.get_webactions())
def test_initialization_is_idempotent(self): storage = get_storage() action = create(Builder('webaction')) # Multiple initializations shouldn't remove existing data storage.initialize_storage() action_from_storage = storage.get(0) self.assertEqual(action, action_from_storage) self.assertEqual(1, storage._storage['next_id'])
def test_webactions_are_html_escaped(self, browser): storage = get_storage() storage.update(1, {"title": u"<bold>Action 2 with html</bold>"}) self.login(self.regular_user, browser=browser) browser.open(self.dossier) viewlet = browser.css('#webactions-title-buttons').first self.assertIn(u"\xc4ction 1", viewlet.innerHTML) self.assertIn("<bold>Action 2 with html</bold>", viewlet.innerHTML)
def test_only_webactions_with_display_actions_buttons_are_displayed( self, browser): self.login(self.regular_user, browser) storage = get_storage() storage.update(0, {"display": "title-buttons"}) browser.open(self.task, view='tabbedview_view-overview') self.assertEquals(['Action 2'], browser.css('ul.webactions_buttons a').text)
def test_webactions_are_html_escaped(self, browser): self.login(self.regular_user, browser) storage = get_storage() storage.update(1, {"title": u"<bold>Action with HTML</bold>"}) browser.open(self.task, view='tabbedview_view-overview') self.assertIn('<bold>Action with HTML</bold>', browser.css('ul.webactions_buttons a').first.innerHTML)
def test_form_is_prefilled_with_missing_values(self, browser): # Enabled has a truthy missing value which needs to be prefilled # in the add form. self.login(self.webaction_manager, browser) storage = get_storage() self.assertEqual(0, len(storage.list())) browser.open(api.portal.getSite(), view="manage-webactions-add") form = browser.find_form_by_field("Enabled") self.assertEqual(form.find_field("Enabled").value, 'selected')
def test_update_rejects_non_user_controlled_fields(self): storage = get_storage() create(Builder('webaction')) with self.assertRaises(ValidationError) as cm: storage.update(0, {'action_id': 42}) self.assertEqual( "WebAction doesn't conform to schema (First error: ('action_id', UnknownField('action_id'))).", str(cm.exception))
def test_update_rejects_non_user_controlled_fields(self): storage = get_storage() create(Builder('webaction')) with self.assertRaises(ValidationError) as cm: storage.update(0, {'action_id': 42}) self.assertEqual( "WebAction doesn't conform to schema (First error: ('action_id', UnknownField('action_id'))).", cm.exception.message)
def test_webaction_provider_only_returns_actions_with_global_scope(self): self.login(self.regular_user) provider = getMultiAdapter((self.dossier, self.request), IWebActionsProvider) storage = get_storage() # XXX We can't use storage.update here, as only the global scope is # implemented and allowed for now storage._actions[0]["scope"] = "context" storage._actions[2]["scope"] = "recursive" expected_data = {'actions-menu': [self.action2_data]} self.assertDictEqual(expected_data, provider.get_webactions())
def test_add_webaction_with_missing_fields_raises(self): storage = get_storage() action = {} with self.assertRaises(ValidationError) as cm: storage.add(action) self.assertEqual( "WebAction doesn't conform to schema (First error: " "('title', RequiredMissing('title'))).", cm.exception.message)
def locate_action(self): action_id = self._parse_action_id() if action_id is not None: storage = get_storage() try: action = storage.get(action_id) except KeyError: raise NotFound self._check_ownership(action) return action
def test_add_webaction_with_missing_fields_raises(self): storage = get_storage() action = {} with self.assertRaises(ValidationError) as cm: storage.add(action) self.assertEqual( "WebAction doesn't conform to schema (First error: " "('title', RequiredMissing('title'))).", str(cm.exception))
def test_only_webactions_with_display_title_buttons_are_shown(self, browser): storage = get_storage() storage.update(1, {"display": "action-buttons"}) self.login(self.regular_user, browser=browser) browser.open(self.dossier) viewlet = browser.css('#webactions-title-buttons') webactions = viewlet.css("a") self.assertEqual(len(webactions), 1) self.assertEqual([u"\xc4ction 1"], map(lambda action: action.get("title"), webactions))
def test_only_webactions_with_display_add_menu_are_shown_in_factory_menus(self): self.login(self.administrator) storage = get_storage() storage.update(1, {"display": "action-buttons"}) for obj in self.contentish_objects: menu = FactoriesMenu(obj) menu_items = menu.getMenuItems(obj, self.dossier.REQUEST) self.assertIn(u'\xc4ction 1', [item.get('title') for item in menu_items]) self.assertNotIn("Action 2", [item.get('title') for item in menu_items])
def test_only_webactions_with_display_user_menu_are_shown_in_usermenu(self, browser): self.login(self.regular_user, browser) storage = get_storage() storage.update(0, {"display": "actions-menu"}) browser.visit(self.dossier) webactions = browser.css('#portal-personaltools .category-webactions > a') self.assertEqual(1, len(webactions), 'Expect 1 webaction link in personal bar') self.assertEqual(['Action 2'], webactions.text)
def test_manager_may_delete_any_action(self, browser): self.login(self.manager, browser=browser) not_my_action = create(Builder('webaction') .owned_by('someone-else')) url = '%s/@webactions/%s' % (self.portal.absolute_url(), not_my_action['action_id']) browser.open(url, method='DELETE', headers=self.HEADERS) self.assertEqual(204, browser.status_code) storage = get_storage() self.assertEqual([], storage.list())
def test_returns_all_actions_for_managers(self): self.login(self.manager) storage = get_storage() storage._actions[1]["owner"] = self.regular_user.id self.action2_data["owner"] = self.regular_user.id expected_data = [ self.action1_data, self.action2_data, self.action3_data ] self.assertEqual(expected_data, self.get_webactions())
def test_manager_may_delete_any_action(self, browser): self.login(self.manager, browser=browser) not_my_action = create(Builder('webaction').owned_by('someone-else')) url = '%s/@webactions/%s' % (self.portal.absolute_url(), not_my_action['action_id']) browser.open(url, method='DELETE', headers=self.HEADERS) self.assertEqual(204, browser.status_code) storage = get_storage() self.assertEqual([], storage.list())
def test_delete_webaction(self, browser): self.login(self.webaction_manager, browser=browser) action = create(Builder('webaction')) url = '%s/@webactions/%s' % (self.portal.absolute_url(), action['action_id']) browser.open(url, method='DELETE', headers=self.HEADERS) self.assertEqual(204, browser.status_code) self.assertEqual('', browser.contents) storage = get_storage() self.assertEqual([], storage.list())
def test_webactions_are_html_escaped(self, browser): storage = get_storage() storage.update(1, {"title": u"<bold>Action 2 with html</bold>"}) self.login(self.administrator, browser=browser) for obj in self.contentish_objects: browser.open(obj) action_menu = browser.css('#contentActionMenus a') webaction_items = action_menu.css('.webaction') self.assertIn(u'\xc4ction 1', webaction_items[0].innerHTML) self.assertIn("<bold>Action 2 with html</bold>", webaction_items[1].innerHTML)
def test_webactions_are_html_escaped(self, browser): storage = get_storage() storage.update(1, {"title": u"<bold>Action 2 with html</bold>"}) self.login(self.regular_user, browser=browser) browser.open(self.dossier) action_menu = browser.css('#contentActionMenus a') webaction_items = action_menu.css('.actionicon-object_webaction') self.assertIn(u'\xc4ction 1', webaction_items[1].innerHTML) self.assertIn("<bold>Action 2 with html</bold>", webaction_items[0].innerHTML)
def test_invariants_are_validated_on_final_resulting_object(self): """When updating, we need to make sure that invariants are validated on the final object that would result from the change (an invariant might impose a constraint that involves a field that already exists on the object and isn't touched by the update, and one that is being changed by the update). """ self.login(self.manager) storage = get_storage() # We start with an action in the 'title-buttons' display location, # which requires an icon, and an value for icon_name property. # This is valid so far, no invariants are violated. with freeze(datetime(2019, 12, 31, 17, 45)): action = create(Builder('webaction').titled(u'Open in ExternalApp') .having( icon_name='fa-helicopter', display='title-buttons', )) action_id = action['action_id'] # But if we now attempt to change the display location to the # 'actions-menu', which doesn't allow for an icon, the invariant will # be violated. with self.assertRaises(ValidationError) as cm: storage.update(action_id, {'display': 'actions-menu'}) self.assertEqual( "WebAction doesn't conform to schema (First error: " "(None, Invalid(\"Display location 'actions-menu' doesn't allow an icon.\",))).", cm.exception.message) # If we however change both the properties covered by the invariant in # the same update, the change succeeds: with freeze(datetime(2020, 7, 31, 19, 15)): storage.update(action_id, {'display': 'actions-menu', 'icon_name': None}) self.assertEqual({ 'action_id': 0, 'title': u'Open in ExternalApp', 'target_url': 'http://example.org/endpoint', 'icon_name': None, 'display': 'actions-menu', 'mode': 'self', 'order': 0, 'scope': 'global', 'created': datetime(2019, 12, 31, 17, 45), 'modified': datetime(2020, 7, 31, 19, 15), 'owner': 'admin', }, storage.get(action_id))