From c43c54953951d6c567611488a9311115c733950a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Arranz?= Date: Fri, 13 Jul 2018 20:53:38 +0200 Subject: [PATCH 1/9] Allow normal users to create datasets and organizations when passing unittests --- test.ini | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/test.ini b/test.ini index c5014ce..2fc3608 100644 --- a/test.ini +++ b/test.ini @@ -6,14 +6,27 @@ port = 5000 [app:main] use = config:./ckan/test-core.ini +ckan.site_id = ckanext.privatedatasets.test ckan.site_url = http://localhost:5000 +package_new_return_url = http://localhost:5000/dataset/?test=new +package_edit_return_url = http://localhost:5000/dataset/?test=edit + +ckan.cache_validation_enabled = True +ckan.cache_enabled = False +ckan.tests.functional.test_cache.expires = 1800 +ckan.tests.functional.test_cache.TestCacheBasics.test_get_cache_expires.expires = 3600 # Disable test css as we aren't using it and creates requests to URLs # raising ValidationErrors. See #46 -ckan.template_head_end = <-- template_head_end --> +ckan.template_head_end = ckan.legacy_templates = no ckan.plugins = privatedatasets +ckan.auth.create_unowned_dataset = true +ckan.auth.create_dataset_if_not_in_organization = true +ckan.auth.user_create_groups = true +ckan.auth.user_create_organizations = true + ckan.privatedatasets.parser = ckanext.privatedatasets.parsers.fiware:FiWareNotificationParser ckan.privatedatasets.show_acquire_url_on_create = True ckan.privatedatasets.show_acquire_url_on_edit = True @@ -23,3 +36,43 @@ ckan.datastore.write_url = postgresql://ckan_default:pass@127.0.0.1:5432/datasto ckan.datastore.read_url = postgresql://datastore_default:pass@127.0.0.1:5432/datastore_test ckan.storage_path=data/storage + +# Logging configuration +[loggers] +keys = root, ckan, ckanext, sqlalchemy + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console + +[logger_ckan] +qualname = ckan +handlers = console +level = INFO +propagate = 0 + +[logger_ckanext] +qualname = ckanext +handlers = console +level = DEBUG +propagate = 0 + +[logger_sqlalchemy] +handlers = console +qualname = sqlalchemy.engine +level = WARN + +[handler_console] +class = StreamHandler +args = (sys.stdout,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(asctime)s %(levelname)-5.5s [%(name)s] %(message)s From f1341686c080c21bcff876e0da79771f5b982302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Arranz?= Date: Sat, 14 Jul 2018 12:31:35 +0200 Subject: [PATCH 2/9] Migrate from IRoute to IBlueprint --- MANIFEST.in | 3 +- .../controllers/ui_controller.py | 51 -------- ckanext/privatedatasets/plugin.py | 28 ++--- .../templates/user/dashboard.html | 4 +- .../templates/user/dashboard_acquired.html | 6 +- .../snippets/package_basic_fields.html | 107 +++++++++++++++++ .../snippets/acquire_button.html | 15 +++ .../templates_2.8/snippets/package_item.html | 82 +++++++++++++ .../templates_2.8/user/dashboard.html | 49 ++++++++ .../user/dashboard_acquired.html | 19 +++ .../tests/test_controller_ui.py | 110 ------------------ ckanext/privatedatasets/tests/test_plugin.py | 21 ++-- .../privatedatasets/tests/test_selenium.py | 4 +- ckanext/privatedatasets/views.py | 45 +++++++ 14 files changed, 351 insertions(+), 193 deletions(-) delete mode 100644 ckanext/privatedatasets/controllers/ui_controller.py create mode 100644 ckanext/privatedatasets/templates_2.8/package/snippets/package_basic_fields.html create mode 100644 ckanext/privatedatasets/templates_2.8/snippets/acquire_button.html create mode 100755 ckanext/privatedatasets/templates_2.8/snippets/package_item.html create mode 100644 ckanext/privatedatasets/templates_2.8/user/dashboard.html create mode 100644 ckanext/privatedatasets/templates_2.8/user/dashboard_acquired.html delete mode 100644 ckanext/privatedatasets/tests/test_controller_ui.py create mode 100644 ckanext/privatedatasets/views.py diff --git a/MANIFEST.in b/MANIFEST.in index 597f7a8..3543c0f 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,2 +1,3 @@ recursive-include ckanext/privatedatasets/templates * -recursive-include ckanext/privatedatasets/fanstatic * \ No newline at end of file +recursive-include ckanext/privatedatasets/templates_2.8 * +recursive-include ckanext/privatedatasets/fanstatic * diff --git a/ckanext/privatedatasets/controllers/ui_controller.py b/ckanext/privatedatasets/controllers/ui_controller.py deleted file mode 100644 index 573ae25..0000000 --- a/ckanext/privatedatasets/controllers/ui_controller.py +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid - -# This file is part of CKAN Private Dataset Extension. - -# CKAN Private Dataset Extension is free software: you can redistribute it and/or -# modify it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# CKAN Private Dataset Extension is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. - -# You should have received a copy of the GNU Affero General Public License -# along with CKAN Private Dataset Extension. If not, see . - -import ckanext.privatedatasets.constants as constants -import ckan.lib.base as base -import ckan.model as model -import ckan.plugins as plugins -import logging - -from ckan.common import _ - -log = logging.getLogger(__name__) - - -class AcquiredDatasetsControllerUI(base.BaseController): - - def user_acquired_datasets(self): - - c = plugins.toolkit.c - context = { - 'model': model, - 'session': model.Session, - 'user': plugins.toolkit.c.user, - } - - # Get user information - try: - c.user_dict = plugins.toolkit.get_action('user_show')(context.copy(), {'user_obj': c.userobj}) - c.user_dict['acquired_datasets'] = plugins.toolkit.get_action(constants.ACQUISITIONS_LIST)(context.copy(), None) - except plugins.toolkit.ObjectNotFound: - plugins.toolkit.abort(404, _('User not found')) - except plugins.toolkit.NotAuthorized: - plugins.toolkit.abort(401, _('Not authorized to see this page')) - - return plugins.toolkit.render('user/dashboard_acquired.html') diff --git a/ckanext/privatedatasets/plugin.py b/ckanext/privatedatasets/plugin.py index 38ba9a8..908b691 100755 --- a/ckanext/privatedatasets/plugin.py +++ b/ckanext/privatedatasets/plugin.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- # Copyright (c) 2014 - 2017 CoNWeT Lab., Universidad Politécnica de Madrid +# Copyright (c) 2018 Future Internet Consulting and Development Solutions S.L. # This file is part of CKAN Private Dataset Extension. @@ -17,15 +18,16 @@ # You should have received a copy of the GNU Affero General Public License # along with CKAN Private Dataset Extension. If not, see . -from __future__ import absolute_import +from __future__ import absolute_import, unicode_literals from ckan import model, plugins as p from ckan.lib import search from ckan.lib.plugins import DefaultPermissionLabels from ckan.plugins import toolkit as tk +from flask import Blueprint from ckanext.privatedatasets import auth, actions, constants, converters_validators as conv_val, db, helpers - +from ckanext.privatedatasets.views import acquired_datasets HIDDEN_FIELDS = [constants.ALLOWED_USERS, constants.SEARCHABLE] @@ -35,7 +37,7 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm, DefaultPermissio p.implements(p.IDatasetForm) p.implements(p.IAuthFunctions) p.implements(p.IConfigurer) - p.implements(p.IRoutes, inherit=True) + p.implements(p.IBlueprint) p.implements(p.IActions) p.implements(p.IPackageController, inherit=True) p.implements(p.ITemplateHelpers) @@ -128,22 +130,22 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm, DefaultPermissio def update_config(self, config): # Add this plugin's templates dir to CKAN's extra_template_paths, so # that CKAN will use this plugin's custom templates. - tk.add_template_directory(config, 'templates') + if p.toolkit.check_ckan_version(min_version='2.8'): + tk.add_template_directory(config, 'templates_2.8') + else: + tk.add_template_directory(config, 'templates') # Register this plugin's fanstatic directory with CKAN. - tk.add_resource('fanstatic', 'privatedatasets') + tk.add_resource(b'fanstatic', b'privatedatasets') ###################################################################### - ############################## IROUTES ############################### + ############################# IBLUEPRINT ############################# ###################################################################### - def before_map(self, m): - # DataSet acquired notification - m.connect('user_acquired_datasets', '/dashboard/acquired', ckan_icon='shopping-cart', - controller='ckanext.privatedatasets.controllers.ui_controller:AcquiredDatasetsControllerUI', - action='user_acquired_datasets', conditions=dict(method=['GET'])) - - return m + def get_blueprint(self): + blueprint = Blueprint('privatedatasets', self.__module__) + blueprint.add_url_rule('/dashboard/acquired', 'acquired_datasets', acquired_datasets) + return blueprint ###################################################################### ############################## IACTIONS ############################## diff --git a/ckanext/privatedatasets/templates/user/dashboard.html b/ckanext/privatedatasets/templates/user/dashboard.html index b172e88..1d54bfc 100644 --- a/ckanext/privatedatasets/templates/user/dashboard.html +++ b/ckanext/privatedatasets/templates/user/dashboard.html @@ -18,7 +18,7 @@ @@ -46,4 +46,4 @@ {% endblock %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/ckanext/privatedatasets/templates/user/dashboard_acquired.html b/ckanext/privatedatasets/templates/user/dashboard_acquired.html index ac791e7..a9fc325 100644 --- a/ckanext/privatedatasets/templates/user/dashboard_acquired.html +++ b/ckanext/privatedatasets/templates/user/dashboard_acquired.html @@ -8,12 +8,12 @@ {% block primary_content_inner %}

{{ _('Acquired Datasets') }}

- {% if c.user_dict.acquired_datasets %} - {% snippet 'snippets/package_list.html', packages=c.user_dict.acquired_datasets %} + {% if acquired_datasets %} + {% snippet 'snippets/package_list.html', packages=acquired_datasets %} {% else %}

{{ _('You haven\'t acquired any datasets.') }} {% link_for _('Acquire one now?'), controller='package', action='search' %}

{% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/ckanext/privatedatasets/templates_2.8/package/snippets/package_basic_fields.html b/ckanext/privatedatasets/templates_2.8/package/snippets/package_basic_fields.html new file mode 100644 index 0000000..b240f46 --- /dev/null +++ b/ckanext/privatedatasets/templates_2.8/package/snippets/package_basic_fields.html @@ -0,0 +1,107 @@ +{% ckan_extends %} + +{% block package_basic_fields_org %} + + {% resource 'privatedatasets/allowed_users.js' %} + + {# if we have a default group then this wants remembering #} + {% if data.group_id %} + + {% endif %} + + {% set dataset_is_draft = data.get('state', 'draft').startswith('draft') or data.get('state', 'none') == 'none' %} + {% set dataset_has_organization = data.owner_org or data.group_id %} + {% set organizations_available = h.organizations_available('create_dataset') %} + {% set user_is_sysadmin = h.check_access('sysadmin') %} + {% set show_organizations_selector = organizations_available and (user_is_sysadmin or dataset_is_draft) %} + {% set editing = 'id' in data %} + + {% if show_organizations_selector and show_visibility_selector %} +
+ {% endif %} + + {% if show_organizations_selector %} + {% set existing_org = data.owner_org or data.group_id %} +
+ +
+ +
+
+ {% endif %} + + {% block package_metadata_fields_visibility %} +
+ +
+ + + + {% trans %} + Private datasets can only be accessed by certain users, while public datasets can be accessed by anyone. + {% endtrans %} + +
+
+ {% endblock %} + + {% block package_metadata_fields_protected %} +
+ +
+ + + + {% trans %} + Searchable datasets can be searched by anyone, while not-searchable datasets can only be accessed by entering directly its URL. + {% endtrans %} + +
+
+ {% endblock %} + + + {% if show_organizations_selector and show_visibility_selector %} +
+ {% endif %} + + {% set users_attrs = {'data-module': 'autocomplete', 'data-module-tags': '', 'data-module-source': '/api/2/util/user/autocomplete?q=?'} %} + {{ form.input('allowed_users_str', label=_('Allowed Users'), id='field-allowed_users_str', placeholder=_('Allowed Users'), value=h.get_allowed_users_str(data.allowed_users), error=errors.custom_text, classes=['control-full'], attrs=users_attrs) }} + + + {% if editing and h.show_acquire_url_on_edit() or not editing and h.show_acquire_url_on_create() %} + {{ form.input('acquire_url', label=_('Acquire URL'), id='field-acquire_url', placeholder=_('http://example.com/acquire/'), value=data.acquire_url, error=errors.custom_text, classes=['control-medium']) }} + {% else %} + + {% endif %} + + {% if data.id and h.check_access('package_delete', {'id': data.id}) and data.state != 'active' %} +
+ +
+ +
+
+ {% endif %} + +{% endblock %} diff --git a/ckanext/privatedatasets/templates_2.8/snippets/acquire_button.html b/ckanext/privatedatasets/templates_2.8/snippets/acquire_button.html new file mode 100644 index 0000000..ca49b29 --- /dev/null +++ b/ckanext/privatedatasets/templates_2.8/snippets/acquire_button.html @@ -0,0 +1,15 @@ +{# + +Displays a Get Access button to request access to a private dataset. + +ulr_dest - target url + +Example: + + {% snippet 'snippets/acquire_button.html', url_dest=url %} + +#} + + + {{ _('Acquire') }} + diff --git a/ckanext/privatedatasets/templates_2.8/snippets/package_item.html b/ckanext/privatedatasets/templates_2.8/snippets/package_item.html new file mode 100755 index 0000000..5db5776 --- /dev/null +++ b/ckanext/privatedatasets/templates_2.8/snippets/package_item.html @@ -0,0 +1,82 @@ +{# +Displays a single of dataset. + +package - A package to display. +item_class - The class name to use on the list item. +hide_resources - If true hides the resources (default: false). +banner - If true displays a popular banner (default: false). +truncate - The length to trucate the description to (default: 180) +truncate_title - The length to truncate the title to (default: 80). + +Example: + + {% snippet 'snippets/package_item.html', package=c.datasets[0] %} + +#} +{% set truncate = truncate or 180 %} +{% set truncate_title = truncate_title or 80 %} +{% set title = package.title or package.name %} +{% set notes = h.markdown_extract(package.notes, extract_length=truncate) %} +{% set acquired = h.is_dataset_acquired(package) %} +{% set owner = h.is_owner(package) %} + +{% resource 'privatedatasets/custom.css' %} + +
  • + {% block package_item_content %} +
    +

    + {% if package.private and not h.can_read(package) %} + + + {{ _('Private') }} + + {% endif %} + {% if acquired and not owner %} + + + {{ _('Acquired') }} + + {% endif %} + {% if owner %} + + + {{ _('Owner') }} + + {% endif %} + + + {% if package.private and not h.can_read(package) %} + {{ _(h.truncate(title, truncate_title)) }} +
    + {{ h.acquire_button(package) }} + {% else %} + {{ h.link_to(h.truncate(title, truncate_title), h.url_for(controller='package', action='read', id=package.name)) }} + {% endif %} + + + {% if package.get('state', '').startswith('draft') %} + {{ _('Draft') }} + {% elif package.get('state', '').startswith('deleted') %} + {{ _('Deleted') }} + {% endif %} + {{ h.popular('recent views', package.tracking_summary.recent, min=10) if package.tracking_summary }} +

    + {% if banner %} + + {% endif %} + {% if notes %} +
    {{ notes|urlize }}
    + {% endif %} +
    + {% if package.resources and not hide_resources %} +
      + {% for resource in h.dict_list_reduce(package.resources, 'format') %} +
    • + {{ resource }} +
    • + {% endfor %} +
    + {% endif %} + {% endblock %} +
  • diff --git a/ckanext/privatedatasets/templates_2.8/user/dashboard.html b/ckanext/privatedatasets/templates_2.8/user/dashboard.html new file mode 100644 index 0000000..4c1f864 --- /dev/null +++ b/ckanext/privatedatasets/templates_2.8/user/dashboard.html @@ -0,0 +1,49 @@ +{% extends "user/edit_base.html" %} + +{% set user = g.userobj %} + +{% block breadcrumb_content %} +
  • {{ _('Dashboard') }}
  • +{% endblock %} + +{% block secondary %}{% endblock %} + +{% block primary %} +
    + {% block page_header %} + + {% endblock %} +
    + {% if self.page_primary_action() | trim %} +
    + {% block page_primary_action %}{% endblock %} +
    + {% endif %} + {% block primary_content_inner %} +
    + {% snippet 'user/snippets/followee_dropdown.html', context=dashboard_activity_stream_context, followees=followee_list %} +

    + {% block page_heading %} + {{ _('News feed') }} + {% endblock %} + {{ _("Activity from items that I'm following") }} +

    + {% block activity_stream %} + {{ dashboard_activity_stream|safe }} + {% endblock %} +
    + {% endblock %} +
    +
    +{% endblock %} diff --git a/ckanext/privatedatasets/templates_2.8/user/dashboard_acquired.html b/ckanext/privatedatasets/templates_2.8/user/dashboard_acquired.html new file mode 100644 index 0000000..a9fc325 --- /dev/null +++ b/ckanext/privatedatasets/templates_2.8/user/dashboard_acquired.html @@ -0,0 +1,19 @@ +{% extends "user/dashboard.html" %} + +{% block dashboard_activity_stream_context %}{% endblock %} + +{% block page_primary_action %} + {% link_for _('Acquire Dataset'), controller='package', action='search', class_="btn btn-primary", icon="shopping-cart" %} +{% endblock %} + +{% block primary_content_inner %} +

    {{ _('Acquired Datasets') }}

    + {% if acquired_datasets %} + {% snippet 'snippets/package_list.html', packages=acquired_datasets %} + {% else %} +

    + {{ _('You haven\'t acquired any datasets.') }} + {% link_for _('Acquire one now?'), controller='package', action='search' %} +

    + {% endif %} +{% endblock %} diff --git a/ckanext/privatedatasets/tests/test_controller_ui.py b/ckanext/privatedatasets/tests/test_controller_ui.py deleted file mode 100644 index 3f61cff..0000000 --- a/ckanext/privatedatasets/tests/test_controller_ui.py +++ /dev/null @@ -1,110 +0,0 @@ -# -*- coding: utf-8 -*- - -# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid - -# This file is part of CKAN Private Dataset Extension. - -# CKAN Private Dataset Extension is free software: you can redistribute it and/or -# modify it under the terms of the GNU Affero General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. - -# CKAN Private Dataset Extension is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. - -# You should have received a copy of the GNU Affero General Public License -# along with CKAN Private Dataset Extension. If not, see . - -import ckanext.privatedatasets.controllers.ui_controller as controller -import unittest - -from mock import MagicMock, ANY -from parameterized import parameterized - - -class UIControllerTest(unittest.TestCase): - - def setUp(self): - - # Get the instance - self.instanceUI = controller.AcquiredDatasetsControllerUI() - - # Load the mocks - self._plugins = controller.plugins - controller.plugins = MagicMock() - - self._model = controller.model - controller.model = MagicMock() - - # Set exceptions - controller.plugins.toolkit.ObjectNotFound = self._plugins.toolkit.ObjectNotFound - controller.plugins.toolkit.NotAuthorized = self._plugins.toolkit.NotAuthorized - - def tearDown(self): - # Unmock - controller.plugins = self._plugins - controller.model = self._model - - @parameterized.expand([ - (controller.plugins.toolkit.ObjectNotFound, 404), - (controller.plugins.toolkit.NotAuthorized, 401) - ]) - def test_exceptions_loading_users(self, exception, expected_status): - - # Configure the mock - user_show = MagicMock(side_effect=exception) - controller.plugins.toolkit.get_action = MagicMock(return_value=user_show) - - # Call the function - self.instanceUI.user_acquired_datasets() - - # Assertations - expected_context = { - 'model': controller.model, - 'session': controller.model.Session, - 'user': controller.plugins.toolkit.c.user - } - - user_show.assert_called_once_with(expected_context, {'user_obj': controller.plugins.toolkit.c.userobj}) - controller.plugins.toolkit.abort.assert_called_once_with(expected_status, ANY) - - def test_no_error_loading_users(self): - - user = 'example_user_test' - controller.plugins.toolkit.c.user = user - - # actions - default_user = {'user_name': 'test', 'another_val': 'example value'} - user_show = MagicMock(return_value=default_user) - acquisitions_list = MagicMock() - def _get_action(action): - if action == 'user_show': - return user_show - else: - return acquisitions_list - - controller.plugins.toolkit.get_action = MagicMock(side_effect=_get_action) - - # Call the function - returned = self.instanceUI.user_acquired_datasets() - - # User_show called correctly - expected_context = { - 'model': controller.model, - 'session': controller.model.Session, - 'user': controller.plugins.toolkit.c.user - } - - user_show.assert_called_once_with(expected_context, {'user_obj': controller.plugins.toolkit.c.userobj}) - - # Query called correctry - expected_user = default_user.copy() - expected_user['acquired_datasets'] = acquisitions_list.return_value - acquisitions_list.assert_called_with(expected_context, None) - self.assertEquals(expected_user, controller.plugins.toolkit.c.user_dict) - - # Check that the render method has been called and that its result has been returned - self.assertEquals(controller.plugins.toolkit.render.return_value, returned) - controller.plugins.toolkit.render.assert_called_once_with('user/dashboard_acquired.html') diff --git a/ckanext/privatedatasets/tests/test_plugin.py b/ckanext/privatedatasets/tests/test_plugin.py index 03c38c5..49502c1 100644 --- a/ckanext/privatedatasets/tests/test_plugin.py +++ b/ckanext/privatedatasets/tests/test_plugin.py @@ -19,11 +19,13 @@ import unittest import copy -import ckanext.privatedatasets.plugin as plugin +from flask import Blueprint from mock import MagicMock from parameterized import parameterized +import ckanext.privatedatasets.plugin as plugin + class PluginTest(unittest.TestCase): @@ -51,7 +53,7 @@ class PluginTest(unittest.TestCase): (plugin.p.IDatasetForm,), (plugin.p.IAuthFunctions,), (plugin.p.IConfigurer,), - (plugin.p.IRoutes,), + (plugin.p.IBlueprint,), (plugin.p.IActions,), (plugin.p.IPackageController,), (plugin.p.ITemplateHelpers,) @@ -83,18 +85,15 @@ class PluginTest(unittest.TestCase): self.privateDatasets.update_config(config) # Test that functions are called as expected - plugin.tk.add_template_directory.assert_called_once_with(config, 'templates') + if self._tk.check_ckan_version(min_version='2.8'): + plugin.tk.add_template_directory.assert_called_once_with(config, 'templates_2.8') + else: + plugin.tk.add_template_directory.assert_called_once_with(config, 'templates') plugin.tk.add_resource('fanstatic', 'privatedatasets') - def test_map(self): + def test_get_blueprint(self): # Call the method - m = MagicMock() - self.privateDatasets.before_map(m) - - # Test that the connect method has been called - m.connect.assert_any_call('user_acquired_datasets', '/dashboard/acquired', ckan_icon='shopping-cart', - controller='ckanext.privatedatasets.controllers.ui_controller:AcquiredDatasetsControllerUI', - action='user_acquired_datasets', conditions=dict(method=['GET'])) + self.assertIsInstance(self.privateDatasets.get_blueprint(), Blueprint) @parameterized.expand([ ('package_acquired', plugin.actions.package_acquired), diff --git a/ckanext/privatedatasets/tests/test_selenium.py b/ckanext/privatedatasets/tests/test_selenium.py index a624fe6..3c7726b 100644 --- a/ckanext/privatedatasets/tests/test_selenium.py +++ b/ckanext/privatedatasets/tests/test_selenium.py @@ -47,7 +47,7 @@ class TestSelenium(unittest.TestCase): @classmethod def setUpClass(cls): env = os.environ.copy() - env['DEBUG'] = 'True' + env['DEBUG'] = 'False' cls._process = Popen(['paster', 'serve', 'test.ini'], env=env) @classmethod @@ -260,7 +260,7 @@ class TestSelenium(unittest.TestCase): def check_acquired(self, dataset, dataset_url, acquired, private): driver = self.driver driver.get(self.base_url + 'dashboard') - driver.find_element_by_link_text('Acquired Datasets').click() + WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Acquired Datasets'))).click() if acquired and private: # This message could not be shown when the user has acquired at least one dataset diff --git a/ckanext/privatedatasets/views.py b/ckanext/privatedatasets/views.py new file mode 100644 index 0000000..05f6ea2 --- /dev/null +++ b/ckanext/privatedatasets/views.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2018 Future Internet Consulting and Development Solutions S.L. + +# This file is part of CKAN Private Dataset Extension. + +# CKAN Private Dataset Extension is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# CKAN Private Dataset Extension is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with CKAN Private Dataset Extension. If not, see . + +from __future__ import absolute_import, unicode_literals + +from ckan import logic +from ckan.common import _, g +from ckan.lib import base +from ckan.plugins import toolkit + +from ckanext.privatedatasets import constants + + +def acquired_datasets(): + context = {'for_view': True, 'user': g.user, 'auth_user_obj': g.userobj} + data_dict = {'user_obj': g.userobj} + try: + user_dict = logic.get_action('user_show')(context, data_dict) + acquired_datasets = toolkit.get_action(constants.ACQUISITIONS_LIST)(context, None) + except logic.NotFound: + base.abort(404, _('User not found')) + except logic.NotAuthorized: + base.abort(403, _('Not authorized to see this page')) + + extra_vars = { + 'user_dict': user_dict, + 'acquired_datasets': acquired_datasets, + } + return base.render('user/dashboard_acquired.html', extra_vars) From 730f5c7b6e226da589917f09f23d744626312d36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Arranz?= Date: Sat, 14 Jul 2018 23:37:33 +0200 Subject: [PATCH 3/9] CKAN 2.8 requires 8 characters passwords --- .../privatedatasets/tests/test_selenium.py | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/ckanext/privatedatasets/tests/test_selenium.py b/ckanext/privatedatasets/tests/test_selenium.py index 3c7726b..ecb0337 100644 --- a/ckanext/privatedatasets/tests/test_selenium.py +++ b/ckanext/privatedatasets/tests/test_selenium.py @@ -93,7 +93,7 @@ class TestSelenium(unittest.TestCase): self.driver.delete_all_cookies() self.driver.get(self.base_url) - def register(self, username, fullname, mail, password): + def register(self, username, fullname, mail): driver = self.driver driver.get(self.base_url) driver.find_element_by_link_text('Register').click() @@ -104,13 +104,13 @@ class TestSelenium(unittest.TestCase): driver.find_element_by_id('field-email').clear() driver.find_element_by_id('field-email').send_keys(mail) driver.find_element_by_id('field-password').clear() - driver.find_element_by_id('field-password').send_keys(password) + driver.find_element_by_id('field-password').send_keys("1234" + username) driver.find_element_by_id('field-confirm-password').clear() - driver.find_element_by_id('field-confirm-password').send_keys(password) + driver.find_element_by_id('field-confirm-password').send_keys("1234" + username) driver.find_element_by_name('save').click() self.logout() - def login(self, username, password): + def login(self, username): driver = self.driver driver.get(self.base_url) login_btn = WebDriverWait(driver, 15).until( @@ -121,7 +121,7 @@ class TestSelenium(unittest.TestCase): WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "field-login"))).clear() driver.find_element_by_id('field-login').send_keys(username) driver.find_element_by_id('field-password').clear() - driver.find_element_by_id('field-password').send_keys(password) + driver.find_element_by_id('field-password').send_keys("1234" + username) driver.find_element_by_id('field-remember').click() driver.find_element_by_css_selector('button.btn.btn-primary').click() @@ -275,7 +275,7 @@ class TestSelenium(unittest.TestCase): self.assertNotEquals(None, re.search('You haven\'t acquired any datasets.', driver.page_source)) def default_register(self, user): - self.register(user, user, '%s@conwet.com' % user, user) + self.register(user, user, '%s@conwet.com' % user) @parameterized.expand([ (['user1', 'user2', 'user3'], True, True, [], 'http://store.conwet.com/'), @@ -293,7 +293,7 @@ class TestSelenium(unittest.TestCase): self.default_register(user) # The first user creates a dataset - self.login(users[0], users[0]) + self.login(users[0]) pkg_name = 'Dataset 1' url = get_dataset_url(pkg_name) self.create_ds(pkg_name, 'Example description', ['tag1', 'tag2', 'tag3'], private, searchable, @@ -308,7 +308,7 @@ class TestSelenium(unittest.TestCase): rest_users = users[1:] for user in rest_users: self.logout() - self.login(user, user) + self.login(user) acquired = user in allowed_users self.check_user_access(pkg_name, url, False, acquired, False, private, searchable, acquire_url) @@ -336,7 +336,7 @@ class TestSelenium(unittest.TestCase): self.default_register(user) # Create the dataset - self.login(user, user) + self.login(user) pkg_name = 'Dataset 2' # Go the page to create the dataset @@ -360,7 +360,7 @@ class TestSelenium(unittest.TestCase): # Create a default user user = 'user1' self.default_register(user) - self.login(user, user) + self.login(user) # Enter the acquired dataset tab driver = self.driver @@ -402,7 +402,7 @@ class TestSelenium(unittest.TestCase): # Create a default user user = 'user1' self.default_register(user) - self.login(user, user) + self.login(user) acquire_url = 'http://upm.es' dataset_default_name = 'Dataset %d' @@ -458,7 +458,7 @@ class TestSelenium(unittest.TestCase): for user in users: self.default_register(user) - self.login(users[0], users[0]) + self.login(users[0]) # Create the organizations for org in orgs: @@ -477,7 +477,7 @@ class TestSelenium(unittest.TestCase): rest_users = users[1:] for user in rest_users: self.logout() - self.login(user, user) + self.login(user) acquired = user in adquiring_users in_org = user in orgs[0]['users'] self.check_user_access(pkg_name, url, False, acquired, in_org, private, searchable, acquire_url) @@ -492,7 +492,7 @@ class TestSelenium(unittest.TestCase): self.default_register(user) # The user creates a dataset - self.login(user, user) + self.login(user) pkg_name = 'Dataset 1' description = 'Example Description' tags = ['tag1', 'tag2', 'tag3'] From 9d869399893e8336eabb9749f7572fbadf855f62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Arranz?= Date: Sun, 15 Jul 2018 00:42:58 +0200 Subject: [PATCH 4/9] Restore IRoutes for CKAN 2.7 --- ckanext/privatedatasets/plugin.py | 12 +++++++++++- .../privatedatasets/templates/user/dashboard.html | 2 +- ckanext/privatedatasets/views.py | 10 ++++++++-- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/ckanext/privatedatasets/plugin.py b/ckanext/privatedatasets/plugin.py index 908b691..eb165c4 100755 --- a/ckanext/privatedatasets/plugin.py +++ b/ckanext/privatedatasets/plugin.py @@ -38,6 +38,7 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm, DefaultPermissio p.implements(p.IAuthFunctions) p.implements(p.IConfigurer) p.implements(p.IBlueprint) + p.implements(p.IRoutes, inherit=True) p.implements(p.IActions) p.implements(p.IPackageController, inherit=True) p.implements(p.ITemplateHelpers) @@ -142,9 +143,18 @@ class PrivateDatasets(p.SingletonPlugin, tk.DefaultDatasetForm, DefaultPermissio ############################# IBLUEPRINT ############################# ###################################################################### + # Deprecated but Required for CKAN 2.7 + def before_map(self, m): + if p.toolkit.check_ckan_version(max_version='2.7.99'): + m.connect('user_acquired_datasets', '/dashboard/acquired', ckan_icon='shopping-cart', + controller='ckanext.privatedatasets.views:AcquiredDatasetsControllerUI', + action='acquired_datasets', conditions=dict(method=['GET'])) + return m + def get_blueprint(self): blueprint = Blueprint('privatedatasets', self.__module__) - blueprint.add_url_rule('/dashboard/acquired', 'acquired_datasets', acquired_datasets) + if p.toolkit.check_ckan_version(min_version='2.8'): + blueprint.add_url_rule('/dashboard/acquired', 'acquired_datasets', acquired_datasets) return blueprint ###################################################################### diff --git a/ckanext/privatedatasets/templates/user/dashboard.html b/ckanext/privatedatasets/templates/user/dashboard.html index 1d54bfc..0d66542 100644 --- a/ckanext/privatedatasets/templates/user/dashboard.html +++ b/ckanext/privatedatasets/templates/user/dashboard.html @@ -18,7 +18,7 @@ diff --git a/ckanext/privatedatasets/views.py b/ckanext/privatedatasets/views.py index 05f6ea2..e92a8b7 100644 --- a/ckanext/privatedatasets/views.py +++ b/ckanext/privatedatasets/views.py @@ -19,7 +19,7 @@ from __future__ import absolute_import, unicode_literals -from ckan import logic +from ckan import logic, model from ckan.common import _, g from ckan.lib import base from ckan.plugins import toolkit @@ -28,7 +28,7 @@ from ckanext.privatedatasets import constants def acquired_datasets(): - context = {'for_view': True, 'user': g.user, 'auth_user_obj': g.userobj} + context = {'auth_user_obj': g.userobj, 'for_view': True, 'model': model, 'session': model.Session, 'user': g.user} data_dict = {'user_obj': g.userobj} try: user_dict = logic.get_action('user_show')(context, data_dict) @@ -43,3 +43,9 @@ def acquired_datasets(): 'acquired_datasets': acquired_datasets, } return base.render('user/dashboard_acquired.html', extra_vars) + + +class AcquiredDatasetsControllerUI(base.BaseController): + + def acquired_datasets(self): + return acquired_datasets() From 56401296d340af15e009646e8ec49b6753d55e76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Arranz?= Date: Sun, 15 Jul 2018 00:52:59 +0200 Subject: [PATCH 5/9] CI: Fix some problems selecting tags and users using selenium --- .../privatedatasets/tests/test_selenium.py | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/ckanext/privatedatasets/tests/test_selenium.py b/ckanext/privatedatasets/tests/test_selenium.py index ecb0337..8b8effb 100644 --- a/ckanext/privatedatasets/tests/test_selenium.py +++ b/ckanext/privatedatasets/tests/test_selenium.py @@ -25,6 +25,7 @@ import os import unittest import re from subprocess import Popen +import time import ckan.lib.search.index as search_index import ckan.model as model @@ -32,6 +33,7 @@ from parameterized import parameterized import requests from selenium import webdriver from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import Select, WebDriverWait from selenium.webdriver.support import expected_conditions as EC @@ -130,7 +132,12 @@ class TestSelenium(unittest.TestCase): driver.get(self.base_url) WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Organizations'))).click() WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Add Organization'))).click() - WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, 'field-name'))).clear() + WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, 'field-name'))) + + # Wait a bit to let ckan add javascript hooks + time.sleep(0.2) + + driver.find_element_by_id('field-name').clear() driver.find_element_by_id('field-name').send_keys(name) driver.find_element_by_id('field-description').clear() driver.find_element_by_id('field-description').send_keys(description) @@ -140,19 +147,26 @@ class TestSelenium(unittest.TestCase): WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Manage'))).click() WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Members'))).click() for user in users: - driver.find_element_by_link_text('Add Member').click() - driver.find_element_by_id('username').send_keys(user) + WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, 'Add Member'))).click() + WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "s2id_autogen1"))).send_keys(user + Keys.RETURN) driver.find_element_by_name('submit').click() def fill_ds_general_info(self, name, description, tags, private, searchable, allowed_users, acquire_url): # FIRST PAGE: Dataset properties driver = self.driver - WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "field-title"))).clear() + WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "field-title"))) + + # Wait a bit to let ckan add javascript hooks + time.sleep(0.2) + + driver.find_element_by_id('field-title').clear() driver.find_element_by_id('field-title').send_keys(name) driver.find_element_by_id('field-notes').clear() driver.find_element_by_id('field-notes').send_keys(description) - driver.find_element_by_id('field-tags').clear() - driver.find_element_by_id('field-tags').send_keys(','.join(tags)) + # field-tags + for tag in tags: + driver.find_element_by_id('s2id_autogen1').send_keys(tag) + driver.find_element_by_id('s2id_autogen1').send_keys(Keys.RETURN) Select(driver.find_element_by_id('field-private')).select_by_visible_text('Private' if private else 'Public') # WARN: The organization is set by default @@ -160,8 +174,10 @@ class TestSelenium(unittest.TestCase): # If the dataset is public, these fields will be disabled (we'll check it) if private: Select(driver.find_element_by_id('field-searchable')).select_by_visible_text('True' if searchable else 'False') - driver.find_element_by_id('field-allowed_users_str').clear() - driver.find_element_by_id('field-allowed_users_str').send_keys(','.join(allowed_users)) + # field-allowed_users + for user in allowed_users: + driver.find_element_by_id('s2id_autogen4').send_keys(user) + driver.find_element_by_id('s2id_autogen4').send_keys(Keys.RETURN) driver.find_element_by_id('field-acquire_url').clear() if acquire_url: driver.find_element_by_id('field-acquire_url').send_keys(acquire_url) From c8bba3c15ff69df70aa1b74637b5b7890abbf8f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Arranz?= Date: Sun, 15 Jul 2018 15:08:21 +0200 Subject: [PATCH 6/9] Upgrade selenium --- .travis.yml | 7 ++++++- .../privatedatasets/tests/test_selenium.py | 20 ++++++++----------- setup.py | 2 +- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.travis.yml b/.travis.yml index 62f7f11..5ab0865 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,12 @@ services: - redis-server - postgresql addons: - firefox: "46.0" + firefox: "60.1.0esr" +before_install: + - wget https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux64.tar.gz + - mkdir geckodriver + - tar -xzf geckodriver-v0.21.0-linux64.tar.gz -C geckodriver + - export PATH=$PATH:$PWD/geckodriver install: - bash bin/travis-build.bash before_script: diff --git a/ckanext/privatedatasets/tests/test_selenium.py b/ckanext/privatedatasets/tests/test_selenium.py index 8b8effb..c2923a8 100644 --- a/ckanext/privatedatasets/tests/test_selenium.py +++ b/ckanext/privatedatasets/tests/test_selenium.py @@ -34,8 +34,8 @@ import requests from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys -from selenium.webdriver.support.ui import Select, WebDriverWait from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import Select, WebDriverWait import ckanext.privatedatasets.db as db @@ -48,13 +48,20 @@ class TestSelenium(unittest.TestCase): @classmethod def setUpClass(cls): + # Run CKAN env = os.environ.copy() env['DEBUG'] = 'False' cls._process = Popen(['paster', 'serve', 'test.ini'], env=env) + # Init Selenium + cls.driver = webdriver.Firefox() + cls.base_url = 'http://localhost:5000/' + cls.driver.set_window_size(1024, 768) + @classmethod def tearDownClass(cls): cls._process.terminate() + #cls.driver.quit() def clearBBDD(self): # Clean Solr @@ -73,19 +80,8 @@ class TestSelenium(unittest.TestCase): def setUp(self): self.clearBBDD() - if 'WEB_DRIVER_URL' in os.environ and 'CKAN_SERVER_URL' in os.environ: - self.driver = webdriver.Remote(os.environ['WEB_DRIVER_URL'], webdriver.DesiredCapabilities.FIREFOX.copy()) - self.base_url = os.environ['CKAN_SERVER_URL'] - else: - - self.driver = webdriver.Firefox() - self.base_url = 'http://localhost:5000/' - - self.driver.set_window_size(1024, 768) - def tearDown(self): self.clearBBDD() - self.driver.quit() def assert_fields_disabled(self, fields): for field in fields: diff --git a/setup.py b/setup.py index 436dc2c..138dbc3 100644 --- a/setup.py +++ b/setup.py @@ -48,7 +48,7 @@ setup( ], tests_require=[ 'parameterized', - 'selenium==2.52.0' + 'selenium==3.13.0' ], test_suite='nosetests', entry_points=''' From 2430c0d46ac059871c75d8dd204a2c82e7344148 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Arranz?= Date: Sun, 15 Jul 2018 19:00:07 +0200 Subject: [PATCH 7/9] Fixes --- .../privatedatasets/tests/test_selenium.py | 44 ++++++++++++------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/ckanext/privatedatasets/tests/test_selenium.py b/ckanext/privatedatasets/tests/test_selenium.py index c2923a8..79f2acd 100644 --- a/ckanext/privatedatasets/tests/test_selenium.py +++ b/ckanext/privatedatasets/tests/test_selenium.py @@ -32,6 +32,7 @@ import ckan.model as model from parameterized import parameterized import requests from selenium import webdriver +from selenium.common.exceptions import NoAlertPresentException from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from selenium.webdriver.support import expected_conditions as EC @@ -61,7 +62,7 @@ class TestSelenium(unittest.TestCase): @classmethod def tearDownClass(cls): cls._process.terminate() - #cls.driver.quit() + cls.driver.quit() def clearBBDD(self): # Clean Solr @@ -81,6 +82,15 @@ class TestSelenium(unittest.TestCase): self.clearBBDD() def tearDown(self): + self.driver.get(self.base_url) + try: + # Accept any "Are you sure to leave?" alert + self.driver.switch_to.alert.accept() + self.driver.switch_to.default_content() + except NoAlertPresentException: + pass + WebDriverWait(self.driver, 10).until(lambda driver: self.base_url == driver.current_url) + self.driver.delete_all_cookies() self.clearBBDD() def assert_fields_disabled(self, fields): @@ -161,8 +171,7 @@ class TestSelenium(unittest.TestCase): driver.find_element_by_id('field-notes').send_keys(description) # field-tags for tag in tags: - driver.find_element_by_id('s2id_autogen1').send_keys(tag) - driver.find_element_by_id('s2id_autogen1').send_keys(Keys.RETURN) + driver.find_element_by_id('s2id_autogen1').send_keys(tag + Keys.RETURN) Select(driver.find_element_by_id('field-private')).select_by_visible_text('Private' if private else 'Public') # WARN: The organization is set by default @@ -172,8 +181,7 @@ class TestSelenium(unittest.TestCase): Select(driver.find_element_by_id('field-searchable')).select_by_visible_text('True' if searchable else 'False') # field-allowed_users for user in allowed_users: - driver.find_element_by_id('s2id_autogen4').send_keys(user) - driver.find_element_by_id('s2id_autogen4').send_keys(Keys.RETURN) + driver.find_element_by_css_selector('#s2id_field-allowed_users_str .select2-input').send_keys(user + Keys.RETURN) driver.find_element_by_id('field-acquire_url').clear() if acquire_url: driver.find_element_by_id('field-acquire_url').send_keys(acquire_url) @@ -192,6 +200,9 @@ class TestSelenium(unittest.TestCase): # SECOND PAGE: Add Resources WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.ID, "field-name"))) + # Wait a bit to let ckan add javascript hooks + time.sleep(0.2) + try: # The link button is only clicked if it's present driver.find_element_by_link_text('Link').click() @@ -204,8 +215,7 @@ class TestSelenium(unittest.TestCase): driver.find_element_by_id('field-name').send_keys(resource_name) driver.find_element_by_id('field-description').clear() driver.find_element_by_id('field-description').send_keys(resource_description) - driver.find_element_by_id('s2id_autogen1').clear() - driver.find_element_by_id('s2id_autogen1').send_keys(resource_format + '\n') + driver.find_element_by_id('s2id_autogen1').send_keys(resource_format + Keys.RETURN) driver.find_element_by_css_selector('button.btn.btn-primary').click() def modify_ds(self, url, name, description, tags, private, searchable, allowed_users, acquire_url): @@ -230,7 +240,7 @@ class TestSelenium(unittest.TestCase): # if len(current_users) == 1 and current_users[0] == '': # current_users = [] # Check the array - self.assertEquals(len(allowed_users), len(current_users)) + self.assertEqual(len(allowed_users), len(current_users)) for user in current_users: self.assertIn(user, allowed_users) else: @@ -254,7 +264,7 @@ class TestSelenium(unittest.TestCase): # When a user cannot access a dataset, the link is no longer provided else: # If the dataset is not searchable and the user is not the owner, a link to it could not be found in the dataset search page - self.assertEquals(None, re.search(dataset_url, driver.page_source)) + self.assertEqual(None, re.search(dataset_url, driver.page_source)) # Access the dataset driver.get(self.base_url + 'dataset/' + dataset_url) @@ -264,10 +274,10 @@ class TestSelenium(unittest.TestCase): xpath = '//*[@id="content"]/div[2]/article/div/h1' msg = '404 Not Found' - self.assertEquals(driver.find_element_by_xpath(xpath).text, msg) + self.assertEqual(driver.find_element_by_xpath(xpath).text, msg) else: - self.assertEquals(self.base_url + 'dataset/%s' % dataset_url, driver.current_url) + self.assertEqual(self.base_url + 'dataset/%s' % dataset_url, driver.current_url) def check_acquired(self, dataset, dataset_url, acquired, private): driver = self.driver @@ -276,13 +286,13 @@ class TestSelenium(unittest.TestCase): if acquired and private: # This message could not be shown when the user has acquired at least one dataset - self.assertEquals(None, re.search('You haven\'t acquired any datasets.', driver.page_source)) + self.assertEqual(None, re.search('You haven\'t acquired any datasets.', driver.page_source)) # Access the dataset driver.find_element_by_link_text(dataset).click() - self.assertEquals(self.base_url + 'dataset/%s' % dataset_url, driver.current_url) + self.assertEqual(self.base_url + 'dataset/%s' % dataset_url, driver.current_url) else: # If the user has not acquired the dataset, a link to this dataset could not be in the acquired dataset list - self.assertEquals(None, re.search(dataset_url, driver.page_source)) + self.assertEqual(None, re.search(dataset_url, driver.page_source)) # When a user has not acquired any dataset, a message will be shown to inform the user self.assertNotEquals(None, re.search('You haven\'t acquired any datasets.', driver.page_source)) @@ -362,7 +372,7 @@ class TestSelenium(unittest.TestCase): # Check the error message msg_error = WebDriverWait(self.driver, 10).until(EC.presence_of_element_located((By.XPATH, '//div[@id=\'content\']/div[3]/div/section/div/form/div/ul/li'))).text - self.assertEquals(expected_msg, msg_error) + self.assertEqual(expected_msg, msg_error) @parameterized.expand([ ('Acquire Dataset', 'dataset'), @@ -378,7 +388,7 @@ class TestSelenium(unittest.TestCase): driver = self.driver driver.get(self.base_url + 'dashboard/acquired') WebDriverWait(self.driver, 10).until(EC.element_to_be_clickable((By.LINK_TEXT, link))).click() - self.assertEquals(self.base_url + 'dataset', self.base_url + expected_url) + self.assertEqual(self.base_url + 'dataset', self.base_url + expected_url) @parameterized.expand([ @@ -509,7 +519,7 @@ class TestSelenium(unittest.TestCase): description = 'Example Description' tags = ['tag1', 'tag2', 'tag3'] url = get_dataset_url(pkg_name) - self.create_ds(pkg_name, 'Example description', ['tag1', 'tag2', 'tag3'], True, True, + self.create_ds(pkg_name, 'Example description', [], True, True, [], 'http://example.com', 'http://upm.es', 'UPM Main', 'Example Description', 'CSV') self.modify_ds(url, pkg_name, description, tags, False, None, None, None) From a6eb52bb9deebc13881bb7ba9699fae86e9b00fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Arranz?= Date: Sun, 15 Jul 2018 23:50:14 +0200 Subject: [PATCH 8/9] Unittest for views --- .../privatedatasets/controllers/__init__.py | 0 ckanext/privatedatasets/tests/test_views.py | 86 +++++++++++++++++++ ckanext/privatedatasets/views.py | 2 +- 3 files changed, 87 insertions(+), 1 deletion(-) delete mode 100644 ckanext/privatedatasets/controllers/__init__.py create mode 100644 ckanext/privatedatasets/tests/test_views.py diff --git a/ckanext/privatedatasets/controllers/__init__.py b/ckanext/privatedatasets/controllers/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/ckanext/privatedatasets/tests/test_views.py b/ckanext/privatedatasets/tests/test_views.py new file mode 100644 index 0000000..f090f00 --- /dev/null +++ b/ckanext/privatedatasets/tests/test_views.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2014 CoNWeT Lab., Universidad Politécnica de Madrid +# Copyright (c) 2018 Future Internet Consulting and Development Solutions S.L. + +# This file is part of CKAN Private Dataset Extension. + +# CKAN Private Dataset Extension is free software: you can redistribute it and/or +# modify it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. + +# CKAN Private Dataset Extension is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with CKAN Private Dataset Extension. If not, see . + +import unittest + +from mock import ANY, DEFAULT, MagicMock, patch +from parameterized import parameterized + +from ckanext.privatedatasets import views + + +class ViewsTest(unittest.TestCase): + + @parameterized.expand([ + ('NotFound', 404), + ('NotAuthorized', 403), + ]) + @patch.multiple("ckanext.privatedatasets.views", base=DEFAULT, toolkit=DEFAULT, model=DEFAULT, g=DEFAULT, logic=DEFAULT, _=DEFAULT) + def test_exceptions_loading_users(self, exception, expected_status, base, toolkit, model, g, logic, _): + + # Configure the mocks + setattr(logic, exception, ValueError) + toolkit.get_action().side_effect=getattr(logic, exception) + base.abort.side_effect = TypeError + + # Call the function + with self.assertRaises(TypeError): + views.acquired_datasets() + + # Assertations + expected_context = { + 'auth_user_obj': g.userobj, + 'for_view': True, + 'model': model, + 'session': model.Session, + 'user': g.user, + } + + toolkit.get_action().assert_called_once_with(expected_context, {'user_obj': g.userobj}) + base.abort.assert_called_once_with(expected_status, ANY) + + @patch.multiple("ckanext.privatedatasets.views", base=DEFAULT, toolkit=DEFAULT, model=DEFAULT, g=DEFAULT, logic=DEFAULT) + def test_no_error_loading_users(self, base, toolkit, model, g, logic): + + # actions + default_user = {'user_name': 'test', 'another_val': 'example value'} + user_show = MagicMock(return_value=default_user) + acquisitions_list = MagicMock() + + toolkit.get_action = MagicMock(side_effect=lambda action: user_show if action == 'user_show' else acquisitions_list) + + # Call the function + returned = views.acquired_datasets() + + # User_show called correctly + expected_context = { + 'auth_user_obj': g.userobj, + 'for_view': True, + 'model': model, + 'session': model.Session, + 'user': g.user, + } + + user_show.assert_called_once_with(expected_context, {'user_obj': g.userobj}) + acquisitions_list.assert_called_with(expected_context, None) + + # Check that the render method has been called + base.render.assert_called_once_with('user/dashboard_acquired.html', {'user_dict': default_user, 'acquired_datasets': acquisitions_list()}) + self.assertEqual(returned, base.render()) diff --git a/ckanext/privatedatasets/views.py b/ckanext/privatedatasets/views.py index e92a8b7..49fbfd8 100644 --- a/ckanext/privatedatasets/views.py +++ b/ckanext/privatedatasets/views.py @@ -31,7 +31,7 @@ def acquired_datasets(): context = {'auth_user_obj': g.userobj, 'for_view': True, 'model': model, 'session': model.Session, 'user': g.user} data_dict = {'user_obj': g.userobj} try: - user_dict = logic.get_action('user_show')(context, data_dict) + user_dict = toolkit.get_action('user_show')(context, data_dict) acquired_datasets = toolkit.get_action(constants.ACQUISITIONS_LIST)(context, None) except logic.NotFound: base.abort(404, _('User not found')) From 6edcb9862983ddf15c0f8342582a6675b29a3baa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=81lvaro=20Arranz?= Date: Mon, 16 Jul 2018 09:02:47 +0200 Subject: [PATCH 9/9] Improve code coverage --- ckanext/privatedatasets/tests/test_selenium.py | 4 ++-- ckanext/privatedatasets/tests/test_views.py | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/ckanext/privatedatasets/tests/test_selenium.py b/ckanext/privatedatasets/tests/test_selenium.py index 79f2acd..44ae530 100644 --- a/ckanext/privatedatasets/tests/test_selenium.py +++ b/ckanext/privatedatasets/tests/test_selenium.py @@ -83,7 +83,7 @@ class TestSelenium(unittest.TestCase): def tearDown(self): self.driver.get(self.base_url) - try: + try: # pragma: no cover # Accept any "Are you sure to leave?" alert self.driver.switch_to.alert.accept() self.driver.switch_to.default_content() @@ -206,7 +206,7 @@ class TestSelenium(unittest.TestCase): try: # The link button is only clicked if it's present driver.find_element_by_link_text('Link').click() - except Exception: + except Exception: # pragma: no cover pass driver.find_element_by_id('field-image-url').clear() diff --git a/ckanext/privatedatasets/tests/test_views.py b/ckanext/privatedatasets/tests/test_views.py index f090f00..1680d0e 100644 --- a/ckanext/privatedatasets/tests/test_views.py +++ b/ckanext/privatedatasets/tests/test_views.py @@ -37,7 +37,7 @@ class ViewsTest(unittest.TestCase): # Configure the mocks setattr(logic, exception, ValueError) - toolkit.get_action().side_effect=getattr(logic, exception) + toolkit.get_action().side_effect = getattr(logic, exception) base.abort.side_effect = TypeError # Call the function @@ -84,3 +84,12 @@ class ViewsTest(unittest.TestCase): # Check that the render method has been called base.render.assert_called_once_with('user/dashboard_acquired.html', {'user_dict': default_user, 'acquired_datasets': acquisitions_list()}) self.assertEqual(returned, base.render()) + + @patch("ckanext.privatedatasets.views.acquired_datasets") + def test_there_is_a_controller_for_ckan_27(self, acquired_datasets): + controller = views.AcquiredDatasetsControllerUI() + + response = controller.acquired_datasets() + + acquired_datasets.assert_called_once_with() + self.assertEqual(response, acquired_datasets())