udata 12.0.2.dev10__py3-none-any.whl → 13.0.1.dev21__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of udata might be problematic. Click here for more details.
- udata/api/__init__.py +1 -0
- udata/api_fields.py +10 -4
- udata/app.py +11 -10
- udata/auth/__init__.py +9 -10
- udata/auth/mails.py +137 -45
- udata/auth/views.py +5 -12
- udata/commands/__init__.py +2 -4
- udata/commands/info.py +1 -3
- udata/commands/tests/test_fixtures.py +6 -3
- udata/core/access_type/api.py +18 -0
- udata/core/access_type/constants.py +98 -0
- udata/core/access_type/models.py +44 -0
- udata/core/activity/models.py +1 -1
- udata/core/badges/models.py +1 -1
- udata/core/badges/tasks.py +35 -1
- udata/core/badges/tests/test_commands.py +2 -4
- udata/core/badges/tests/test_model.py +2 -2
- udata/core/badges/tests/test_tasks.py +55 -0
- udata/core/constants.py +1 -0
- udata/core/contact_point/models.py +8 -0
- udata/core/dataservices/api.py +10 -12
- udata/core/dataservices/apiv2.py +3 -1
- udata/core/dataservices/constants.py +0 -29
- udata/core/dataservices/models.py +44 -44
- udata/core/dataservices/rdf.py +2 -1
- udata/core/dataservices/search.py +5 -9
- udata/core/dataservices/tasks.py +33 -0
- udata/core/dataset/api.py +15 -24
- udata/core/dataset/api_fields.py +11 -0
- udata/core/dataset/apiv2.py +11 -0
- udata/core/dataset/constants.py +0 -1
- udata/core/dataset/forms.py +29 -0
- udata/core/dataset/models.py +24 -42
- udata/core/dataset/rdf.py +2 -1
- udata/core/dataset/search.py +2 -2
- udata/core/dataset/tasks.py +86 -8
- udata/core/discussions/mails.py +63 -0
- udata/core/discussions/tasks.py +4 -18
- udata/core/metrics/__init__.py +0 -6
- udata/core/organization/api.py +20 -14
- udata/core/organization/mails.py +144 -0
- udata/core/organization/models.py +2 -1
- udata/core/organization/rdf.py +3 -3
- udata/core/organization/search.py +1 -1
- udata/core/organization/tasks.py +21 -49
- udata/core/pages/tests/test_api.py +0 -2
- udata/core/reuse/api.py +29 -3
- udata/core/reuse/mails.py +21 -0
- udata/core/reuse/models.py +10 -1
- udata/core/reuse/search.py +1 -1
- udata/core/reuse/tasks.py +2 -3
- udata/core/site/api.py +27 -19
- udata/core/site/models.py +2 -6
- udata/core/site/rdf.py +2 -2
- udata/core/spatial/tests/test_api.py +17 -20
- udata/core/spatial/tests/test_models.py +3 -3
- udata/core/user/mails.py +54 -0
- udata/core/user/models.py +2 -3
- udata/core/user/tasks.py +8 -23
- udata/core/user/tests/test_user_model.py +2 -6
- udata/entrypoints.py +0 -6
- udata/features/identicon/tests/test_backends.py +3 -13
- udata/forms/fields.py +3 -3
- udata/forms/widgets.py +2 -2
- udata/frontend/__init__.py +3 -32
- udata/harvest/actions.py +4 -9
- udata/harvest/api.py +5 -14
- udata/harvest/backends/__init__.py +20 -11
- udata/harvest/backends/base.py +2 -2
- udata/harvest/backends/ckan/harvesters.py +2 -1
- udata/harvest/backends/dcat.py +3 -0
- udata/harvest/backends/maaf.py +1 -0
- udata/harvest/commands.py +6 -4
- udata/harvest/forms.py +9 -6
- udata/harvest/tasks.py +3 -5
- udata/harvest/tests/ckan/test_ckan_backend.py +300 -337
- udata/harvest/tests/ckan/test_ckan_backend_errors.py +94 -99
- udata/harvest/tests/ckan/test_ckan_backend_filters.py +128 -122
- udata/harvest/tests/ckan/test_dkan_backend.py +39 -51
- udata/harvest/tests/dcat/bnodes.xml +17 -1
- udata/harvest/tests/dcat/datara--5a26b0f6-0ccf-46ad-ac58-734054b91977.rdf.xml +255 -0
- udata/harvest/tests/dcat/datara--f40c3860-7236-4b30-a141-23b8ae33f7b2.rdf.xml +289 -0
- udata/harvest/tests/factories.py +1 -1
- udata/harvest/tests/test_actions.py +11 -9
- udata/harvest/tests/test_api.py +4 -5
- udata/harvest/tests/test_base_backend.py +5 -4
- udata/harvest/tests/test_dcat_backend.py +72 -16
- udata/harvest/tests/test_models.py +2 -4
- udata/harvest/tests/test_notifications.py +2 -4
- udata/harvest/tests/test_tasks.py +2 -3
- udata/mail.py +90 -53
- udata/migrations/2025-01-05-dataservices-fields-changes.py +8 -14
- udata/migrations/2025-10-21-remove-ckan-harvest-modified-at.py +28 -0
- udata/migrations/2025-10-29-harvesters-sources-integrity.py +27 -0
- udata/models/__init__.py +0 -2
- udata/mongo/extras_fields.py +4 -3
- udata/mongo/taglist_field.py +3 -3
- udata/rdf.py +65 -20
- udata/sentry.py +3 -4
- udata/settings.py +15 -13
- udata/tags.py +5 -5
- udata/tasks.py +3 -3
- udata/templates/mail/message.html +65 -0
- udata/templates/mail/message.txt +16 -0
- udata/tests/__init__.py +40 -58
- udata/tests/api/__init__.py +87 -2
- udata/tests/api/test_activities_api.py +17 -23
- udata/tests/api/test_auth_api.py +2 -4
- udata/tests/api/test_contact_points.py +48 -54
- udata/tests/api/test_dataservices_api.py +65 -97
- udata/tests/api/test_datasets_api.py +171 -56
- udata/tests/api/test_me_api.py +4 -6
- udata/tests/api/test_organizations_api.py +19 -38
- udata/tests/api/test_reports_api.py +0 -4
- udata/tests/api/test_reuses_api.py +99 -23
- udata/tests/api/test_security_api.py +124 -0
- udata/tests/api/test_swagger.py +2 -3
- udata/tests/api/test_tags_api.py +6 -7
- udata/tests/api/test_transfer_api.py +0 -2
- udata/tests/api/test_user_api.py +8 -10
- udata/tests/apiv2/test_datasets.py +0 -4
- udata/tests/apiv2/test_me_api.py +0 -2
- udata/tests/apiv2/test_organizations.py +0 -2
- udata/tests/apiv2/test_swagger.py +2 -3
- udata/tests/apiv2/test_topics.py +0 -2
- udata/tests/cli/test_cli_base.py +14 -12
- udata/tests/cli/test_db_cli.py +51 -54
- udata/tests/contact_point/test_contact_point_models.py +2 -2
- udata/tests/dataservice/test_csv_adapter.py +2 -5
- udata/tests/dataservice/test_dataservice_rdf.py +64 -4
- udata/tests/dataservice/test_dataservice_tasks.py +36 -38
- udata/tests/dataset/test_csv_adapter.py +2 -5
- udata/tests/dataset/test_dataset_actions.py +2 -4
- udata/tests/dataset/test_dataset_commands.py +2 -4
- udata/tests/dataset/test_dataset_events.py +3 -3
- udata/tests/dataset/test_dataset_model.py +6 -7
- udata/tests/dataset/test_dataset_rdf.py +205 -16
- udata/tests/dataset/test_dataset_recommendations.py +2 -2
- udata/tests/dataset/test_dataset_tasks.py +66 -68
- udata/tests/dataset/test_resource_preview.py +39 -48
- udata/tests/dataset/test_transport_tasks.py +2 -2
- udata/tests/features/territories/__init__.py +0 -6
- udata/tests/features/territories/test_territories_api.py +25 -24
- udata/tests/forms/test_current_user_field.py +2 -2
- udata/tests/forms/test_dict_field.py +2 -4
- udata/tests/forms/test_extras_fields.py +2 -3
- udata/tests/forms/test_image_field.py +2 -2
- udata/tests/forms/test_model_field.py +2 -4
- udata/tests/forms/test_publish_as_field.py +2 -4
- udata/tests/forms/test_user_forms.py +26 -29
- udata/tests/frontend/test_auth.py +2 -3
- udata/tests/frontend/test_csv.py +5 -6
- udata/tests/frontend/test_error_handlers.py +2 -3
- udata/tests/frontend/test_hooks.py +5 -7
- udata/tests/frontend/test_markdown.py +3 -4
- udata/tests/helpers.py +2 -7
- udata/tests/metrics/test_metrics.py +52 -48
- udata/tests/metrics/test_tasks.py +154 -150
- udata/tests/organization/test_csv_adapter.py +2 -5
- udata/tests/organization/test_notifications.py +2 -4
- udata/tests/organization/test_organization_model.py +3 -4
- udata/tests/organization/test_organization_rdf.py +6 -12
- udata/tests/plugin.py +6 -110
- udata/tests/reuse/test_reuse_model.py +3 -4
- udata/tests/site/test_site_api.py +0 -2
- udata/tests/site/test_site_csv_exports.py +0 -2
- udata/tests/site/test_site_metrics.py +2 -4
- udata/tests/site/test_site_model.py +2 -2
- udata/tests/site/test_site_rdf.py +85 -29
- udata/tests/test_activity.py +3 -3
- udata/tests/test_api_fields.py +6 -9
- udata/tests/test_cors.py +0 -2
- udata/tests/test_dcat_commands.py +2 -3
- udata/tests/test_discussions.py +2 -7
- udata/tests/test_mail.py +150 -114
- udata/tests/test_migrations.py +413 -419
- udata/tests/test_model.py +10 -11
- udata/tests/test_notifications.py +2 -3
- udata/tests/test_owned.py +3 -3
- udata/tests/test_rdf.py +19 -15
- udata/tests/test_routing.py +5 -5
- udata/tests/test_storages.py +6 -5
- udata/tests/test_tags.py +2 -4
- udata/tests/test_topics.py +2 -4
- udata/tests/test_transfer.py +4 -5
- udata/tests/topic/test_topic_tasks.py +25 -27
- udata/tests/user/test_user_rdf.py +2 -8
- udata/tests/user/test_user_tasks.py +3 -5
- udata/tests/workers/test_jobs_commands.py +2 -2
- udata/tests/workers/test_tasks_routing.py +27 -27
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +369 -435
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +371 -437
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +369 -435
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +381 -447
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +371 -437
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +371 -437
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +372 -438
- udata/translations/udata.pot +379 -440
- udata/utils.py +66 -4
- {udata-12.0.2.dev10.dist-info → udata-13.0.1.dev21.dist-info}/METADATA +1 -4
- {udata-12.0.2.dev10.dist-info → udata-13.0.1.dev21.dist-info}/RECORD +212 -256
- udata/linkchecker/__init__.py +0 -0
- udata/linkchecker/backends.py +0 -31
- udata/linkchecker/checker.py +0 -75
- udata/linkchecker/commands.py +0 -21
- udata/linkchecker/models.py +0 -9
- udata/linkchecker/tasks.py +0 -55
- udata/templates/mail/account_deleted.html +0 -5
- udata/templates/mail/account_deleted.txt +0 -6
- udata/templates/mail/account_inactivity.html +0 -40
- udata/templates/mail/account_inactivity.txt +0 -31
- udata/templates/mail/badge_added_association.html +0 -33
- udata/templates/mail/badge_added_association.txt +0 -11
- udata/templates/mail/badge_added_certified.html +0 -33
- udata/templates/mail/badge_added_certified.txt +0 -11
- udata/templates/mail/badge_added_company.html +0 -33
- udata/templates/mail/badge_added_company.txt +0 -11
- udata/templates/mail/badge_added_local_authority.html +0 -33
- udata/templates/mail/badge_added_local_authority.txt +0 -11
- udata/templates/mail/badge_added_public_service.html +0 -33
- udata/templates/mail/badge_added_public_service.txt +0 -11
- udata/templates/mail/discussion_closed.html +0 -47
- udata/templates/mail/discussion_closed.txt +0 -16
- udata/templates/mail/inactive_account_deleted.html +0 -5
- udata/templates/mail/inactive_account_deleted.txt +0 -6
- udata/templates/mail/membership_refused.html +0 -20
- udata/templates/mail/membership_refused.txt +0 -11
- udata/templates/mail/membership_request.html +0 -46
- udata/templates/mail/membership_request.txt +0 -12
- udata/templates/mail/new_discussion.html +0 -44
- udata/templates/mail/new_discussion.txt +0 -15
- udata/templates/mail/new_discussion_comment.html +0 -45
- udata/templates/mail/new_discussion_comment.txt +0 -16
- udata/templates/mail/new_member.html +0 -27
- udata/templates/mail/new_member.txt +0 -11
- udata/templates/mail/new_reuse.html +0 -37
- udata/templates/mail/new_reuse.txt +0 -9
- udata/templates/mail/test.html +0 -6
- udata/templates/mail/test.txt +0 -6
- udata/templates/mail/user_mail_card.html +0 -26
- udata/templates/security/email/base.html +0 -105
- udata/templates/security/email/base.txt +0 -6
- udata/templates/security/email/button.html +0 -3
- udata/templates/security/email/change_notice.html +0 -22
- udata/templates/security/email/change_notice.txt +0 -8
- udata/templates/security/email/confirmation_instructions.html +0 -20
- udata/templates/security/email/confirmation_instructions.txt +0 -7
- udata/templates/security/email/login_instructions.html +0 -19
- udata/templates/security/email/login_instructions.txt +0 -7
- udata/templates/security/email/reset_instructions.html +0 -24
- udata/templates/security/email/reset_instructions.txt +0 -9
- udata/templates/security/email/reset_notice.html +0 -11
- udata/templates/security/email/reset_notice.txt +0 -4
- udata/templates/security/email/welcome.html +0 -24
- udata/templates/security/email/welcome.txt +0 -9
- udata/templates/security/email/welcome_existing.html +0 -32
- udata/templates/security/email/welcome_existing.txt +0 -14
- udata/terms.md +0 -6
- udata/tests/frontend/__init__.py +0 -23
- udata/tests/metrics/conftest.py +0 -15
- udata/tests/test_linkchecker.py +0 -277
- {udata-12.0.2.dev10.dist-info → udata-13.0.1.dev21.dist-info}/WHEEL +0 -0
- {udata-12.0.2.dev10.dist-info → udata-13.0.1.dev21.dist-info}/entry_points.txt +0 -0
- {udata-12.0.2.dev10.dist-info → udata-13.0.1.dev21.dist-info}/licenses/LICENSE +0 -0
- {udata-12.0.2.dev10.dist-info → udata-13.0.1.dev21.dist-info}/top_level.txt +0 -0
|
@@ -2,11 +2,7 @@ import pytest
|
|
|
2
2
|
|
|
3
3
|
from udata.harvest import actions
|
|
4
4
|
from udata.harvest.tests.factories import HarvestSourceFactory
|
|
5
|
-
|
|
6
|
-
pytestmark = [
|
|
7
|
-
pytest.mark.usefixtures("clean_db"),
|
|
8
|
-
pytest.mark.options(PLUGINS=["ckan"]),
|
|
9
|
-
]
|
|
5
|
+
from udata.tests.api import PytestOnlyDBTestCase
|
|
10
6
|
|
|
11
7
|
CKAN_URL = "https://harvest.me/"
|
|
12
8
|
API_URL = "{}api/3/action/package_list".format(CKAN_URL)
|
|
@@ -17,124 +13,123 @@ API_URL = "{}api/3/action/package_list".format(CKAN_URL)
|
|
|
17
13
|
STATUS_CODE = (400, 500)
|
|
18
14
|
|
|
19
15
|
|
|
20
|
-
@pytest.mark.
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
actions.run(source)
|
|
29
|
-
|
|
30
|
-
source.reload()
|
|
31
|
-
|
|
32
|
-
job = source.get_last_job()
|
|
33
|
-
assert len(job.items) == 0
|
|
34
|
-
assert len(job.errors) == 1
|
|
35
|
-
error = job.errors[0]
|
|
36
|
-
# HTML is detected and does not clutter the message
|
|
37
|
-
assert html not in error.message
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
@pytest.mark.parametrize("code", STATUS_CODE)
|
|
41
|
-
def test_plain_text_error(rmock, code):
|
|
42
|
-
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
43
|
-
|
|
44
|
-
rmock.get(
|
|
45
|
-
API_URL, text='"Some error"', status_code=code, headers={"Content-Type": "text/plain"}
|
|
46
|
-
)
|
|
16
|
+
@pytest.mark.options(HARVESTER_BACKENDS=["ckan"])
|
|
17
|
+
class CkanBackendErrorsTest(PytestOnlyDBTestCase):
|
|
18
|
+
@pytest.mark.parametrize("code", STATUS_CODE)
|
|
19
|
+
def test_html_error(self, rmock, code):
|
|
20
|
+
# Happens with wrong source URL (html is returned instead of json)
|
|
21
|
+
html = "<html><body>Error</body></html>"
|
|
22
|
+
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
47
23
|
|
|
48
|
-
|
|
24
|
+
rmock.get(API_URL, text=html, status_code=code, headers={"Content-Type": "text/html"})
|
|
49
25
|
|
|
50
|
-
|
|
26
|
+
actions.run(source)
|
|
51
27
|
|
|
52
|
-
|
|
53
|
-
assert len(job.items) == 0
|
|
54
|
-
assert len(job.errors) == 1
|
|
55
|
-
error = job.errors[0]
|
|
56
|
-
# Raw quoted string is properly unquoted
|
|
57
|
-
http_message = "Server Error" if code == 500 else "Client Error"
|
|
58
|
-
assert (
|
|
59
|
-
error.message
|
|
60
|
-
== f"{code} {http_message}: None for url: https://harvest.me/api/3/action/package_list"
|
|
61
|
-
)
|
|
28
|
+
source.reload()
|
|
62
29
|
|
|
30
|
+
job = source.get_last_job()
|
|
31
|
+
assert len(job.items) == 0
|
|
32
|
+
assert len(job.errors) == 1
|
|
33
|
+
error = job.errors[0]
|
|
34
|
+
# HTML is detected and does not clutter the message
|
|
35
|
+
assert html not in error.message
|
|
63
36
|
|
|
64
|
-
|
|
65
|
-
|
|
37
|
+
@pytest.mark.parametrize("code", STATUS_CODE)
|
|
38
|
+
def test_plain_text_error(self, rmock, code):
|
|
39
|
+
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
66
40
|
|
|
67
|
-
|
|
41
|
+
rmock.get(
|
|
42
|
+
API_URL, text='"Some error"', status_code=code, headers={"Content-Type": "text/plain"}
|
|
43
|
+
)
|
|
68
44
|
|
|
69
|
-
|
|
45
|
+
actions.run(source)
|
|
70
46
|
|
|
71
|
-
|
|
47
|
+
source.reload()
|
|
72
48
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
49
|
+
job = source.get_last_job()
|
|
50
|
+
assert len(job.items) == 0
|
|
51
|
+
assert len(job.errors) == 1
|
|
52
|
+
error = job.errors[0]
|
|
53
|
+
# Raw quoted string is properly unquoted
|
|
54
|
+
http_message = "Server Error" if code == 500 else "Client Error"
|
|
55
|
+
assert (
|
|
56
|
+
error.message
|
|
57
|
+
== f"{code} {http_message}: None for url: https://harvest.me/api/3/action/package_list"
|
|
58
|
+
)
|
|
79
59
|
|
|
60
|
+
def test_200_plain_text_error(self, rmock):
|
|
61
|
+
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
80
62
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
63
|
+
rmock.get(
|
|
64
|
+
API_URL, text='"Some error"', status_code=200, headers={"Content-Type": "text/plain"}
|
|
65
|
+
)
|
|
84
66
|
|
|
85
|
-
|
|
67
|
+
actions.run(source)
|
|
86
68
|
|
|
87
|
-
|
|
69
|
+
source.reload()
|
|
88
70
|
|
|
89
|
-
|
|
71
|
+
job = source.get_last_job()
|
|
72
|
+
assert len(job.items) == 0
|
|
73
|
+
assert len(job.errors) == 1
|
|
74
|
+
error = job.errors[0]
|
|
75
|
+
# Raw quoted string is properly unquoted
|
|
76
|
+
assert error.message == "Some error"
|
|
90
77
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
error = job.errors[0]
|
|
95
|
-
assert error.message == "an error"
|
|
78
|
+
def test_standard_api_json_error(self, rmock):
|
|
79
|
+
json = {"success": False, "error": "an error"}
|
|
80
|
+
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
96
81
|
|
|
82
|
+
rmock.get(API_URL, json=json, status_code=200, headers={"Content-Type": "application/json"})
|
|
97
83
|
|
|
98
|
-
|
|
99
|
-
json = {
|
|
100
|
-
"success": False,
|
|
101
|
-
"error": {
|
|
102
|
-
"message": "an error",
|
|
103
|
-
},
|
|
104
|
-
}
|
|
105
|
-
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
84
|
+
actions.run(source)
|
|
106
85
|
|
|
107
|
-
|
|
86
|
+
source.reload()
|
|
108
87
|
|
|
109
|
-
|
|
88
|
+
job = source.get_last_job()
|
|
89
|
+
assert len(job.items) == 0
|
|
90
|
+
assert len(job.errors) == 1
|
|
91
|
+
error = job.errors[0]
|
|
92
|
+
assert error.message == "an error"
|
|
110
93
|
|
|
111
|
-
|
|
94
|
+
def test_standard_api_json_error_with_details(self, rmock):
|
|
95
|
+
json = {
|
|
96
|
+
"success": False,
|
|
97
|
+
"error": {
|
|
98
|
+
"message": "an error",
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
112
102
|
|
|
113
|
-
|
|
114
|
-
assert len(job.items) == 0
|
|
115
|
-
assert len(job.errors) == 1
|
|
116
|
-
error = job.errors[0]
|
|
117
|
-
assert error.message == "an error"
|
|
103
|
+
rmock.get(API_URL, json=json, status_code=200, headers={"Content-Type": "application/json"})
|
|
118
104
|
|
|
105
|
+
actions.run(source)
|
|
119
106
|
|
|
120
|
-
|
|
121
|
-
json = {
|
|
122
|
-
"success": False,
|
|
123
|
-
"error": {
|
|
124
|
-
"message": "Access denied",
|
|
125
|
-
"__type": "Authorization Error",
|
|
126
|
-
},
|
|
127
|
-
}
|
|
128
|
-
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
107
|
+
source.reload()
|
|
129
108
|
|
|
130
|
-
|
|
109
|
+
job = source.get_last_job()
|
|
110
|
+
assert len(job.items) == 0
|
|
111
|
+
assert len(job.errors) == 1
|
|
112
|
+
error = job.errors[0]
|
|
113
|
+
assert error.message == "an error"
|
|
131
114
|
|
|
132
|
-
|
|
115
|
+
def test_standard_api_json_error_with_details_and_type(self, rmock):
|
|
116
|
+
json = {
|
|
117
|
+
"success": False,
|
|
118
|
+
"error": {
|
|
119
|
+
"message": "Access denied",
|
|
120
|
+
"__type": "Authorization Error",
|
|
121
|
+
},
|
|
122
|
+
}
|
|
123
|
+
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
133
124
|
|
|
134
|
-
|
|
125
|
+
rmock.get(API_URL, json=json, status_code=200, headers={"Content-Type": "application/json"})
|
|
135
126
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
127
|
+
actions.run(source)
|
|
128
|
+
|
|
129
|
+
source.reload()
|
|
130
|
+
|
|
131
|
+
job = source.get_last_job()
|
|
132
|
+
assert len(job.items) == 0
|
|
133
|
+
assert len(job.errors) == 1
|
|
134
|
+
error = job.errors[0]
|
|
135
|
+
assert error.message == "Authorization Error: Access denied"
|
|
@@ -4,127 +4,133 @@ import pytest
|
|
|
4
4
|
|
|
5
5
|
from udata.harvest import actions
|
|
6
6
|
from udata.harvest.tests.factories import HarvestSourceFactory
|
|
7
|
+
from udata.tests.api import PytestOnlyDBTestCase
|
|
7
8
|
from udata.utils import faker
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
"
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
10
|
+
|
|
11
|
+
@pytest.mark.options(HARVESTER_BACKENDS=["ckan"])
|
|
12
|
+
class CkanBackendFilterTest(PytestOnlyDBTestCase):
|
|
13
|
+
def test_include_org_filter(self, ckan, rmock):
|
|
14
|
+
source = HarvestSourceFactory(
|
|
15
|
+
backend="ckan",
|
|
16
|
+
url=ckan.BASE_URL,
|
|
17
|
+
config={"filters": [{"key": "organization", "value": "organization_name"}]},
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
rmock.get(
|
|
21
|
+
ckan.PACKAGE_SEARCH_URL,
|
|
22
|
+
json={"success": True, "result": {"results": []}},
|
|
23
|
+
status_code=200,
|
|
24
|
+
headers={"Content-Type": "application/json"},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
actions.run(source)
|
|
28
|
+
source.reload()
|
|
29
|
+
|
|
30
|
+
assert rmock.call_count == 1
|
|
31
|
+
params = {"q": "organization:organization_name", "rows": 1000}
|
|
32
|
+
assert (
|
|
33
|
+
rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
def test_exclude_org_filter(self, ckan, rmock):
|
|
37
|
+
source = HarvestSourceFactory(
|
|
38
|
+
backend="ckan",
|
|
39
|
+
url=ckan.BASE_URL,
|
|
40
|
+
config={
|
|
41
|
+
"filters": [
|
|
42
|
+
{"key": "organization", "value": "organization_name", "type": "exclude"}
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
rmock.get(
|
|
48
|
+
ckan.PACKAGE_SEARCH_URL,
|
|
49
|
+
json={"success": True, "result": {"results": []}},
|
|
50
|
+
status_code=200,
|
|
51
|
+
headers={"Content-Type": "application/json"},
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
actions.run(source)
|
|
55
|
+
source.reload()
|
|
56
|
+
|
|
57
|
+
assert rmock.call_count == 1
|
|
58
|
+
|
|
59
|
+
params = {"q": "-organization:organization_name", "rows": 1000}
|
|
60
|
+
assert (
|
|
61
|
+
rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def test_tag_filter(self, ckan, rmock):
|
|
65
|
+
tag = faker.word()
|
|
66
|
+
source = HarvestSourceFactory(
|
|
67
|
+
backend="ckan", url=ckan.BASE_URL, config={"filters": [{"key": "tags", "value": tag}]}
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
rmock.get(
|
|
71
|
+
ckan.PACKAGE_SEARCH_URL,
|
|
72
|
+
json={"success": True, "result": {"results": []}},
|
|
73
|
+
status_code=200,
|
|
74
|
+
headers={"Content-Type": "application/json"},
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
actions.run(source)
|
|
78
|
+
source.reload()
|
|
79
|
+
|
|
80
|
+
assert rmock.call_count == 1
|
|
81
|
+
params = {"q": f"tags:{tag}", "rows": 1000}
|
|
82
|
+
assert (
|
|
83
|
+
rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
def test_exclude_tag_filter(self, ckan, rmock):
|
|
87
|
+
tag = faker.word()
|
|
88
|
+
source = HarvestSourceFactory(
|
|
89
|
+
backend="ckan",
|
|
90
|
+
url=ckan.BASE_URL,
|
|
91
|
+
config={"filters": [{"key": "tags", "value": tag, "type": "exclude"}]},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
rmock.get(
|
|
95
|
+
ckan.PACKAGE_SEARCH_URL,
|
|
96
|
+
json={"success": True, "result": {"results": []}},
|
|
97
|
+
status_code=200,
|
|
98
|
+
headers={"Content-Type": "application/json"},
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
actions.run(source)
|
|
102
|
+
source.reload()
|
|
103
|
+
|
|
104
|
+
assert rmock.call_count == 1
|
|
105
|
+
params = {"q": f"-tags:{tag}", "rows": 1000}
|
|
106
|
+
assert (
|
|
107
|
+
rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
def test_can_have_multiple_filters(self, ckan, rmock):
|
|
111
|
+
source = HarvestSourceFactory(
|
|
112
|
+
backend="ckan",
|
|
113
|
+
url=ckan.BASE_URL,
|
|
114
|
+
config={
|
|
115
|
+
"filters": [
|
|
116
|
+
{"key": "organization", "value": "organization_name"},
|
|
117
|
+
{"key": "tags", "value": "tag-2", "type": "exclude"},
|
|
118
|
+
]
|
|
119
|
+
},
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
rmock.get(
|
|
123
|
+
ckan.PACKAGE_SEARCH_URL,
|
|
124
|
+
json={"success": True, "result": {"results": []}},
|
|
125
|
+
status_code=200,
|
|
126
|
+
headers={"Content-Type": "application/json"},
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
actions.run(source)
|
|
130
|
+
source.reload()
|
|
131
|
+
|
|
132
|
+
assert rmock.call_count == 1
|
|
133
|
+
params = {"q": "organization:organization_name AND -tags:tag-2", "rows": 1000}
|
|
134
|
+
assert (
|
|
135
|
+
rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
136
|
+
)
|
|
@@ -4,13 +4,11 @@ from datetime import datetime
|
|
|
4
4
|
|
|
5
5
|
import pytest
|
|
6
6
|
|
|
7
|
-
from udata.app import create_app
|
|
8
7
|
from udata.core.organization.factories import OrganizationFactory
|
|
9
8
|
from udata.harvest import actions
|
|
10
9
|
from udata.harvest.tests.factories import HarvestSourceFactory
|
|
11
10
|
from udata.models import Dataset
|
|
12
|
-
from udata.
|
|
13
|
-
from udata.tests.plugin import drop_db
|
|
11
|
+
from udata.tests.api import PytestOnlyDBTestCase
|
|
14
12
|
|
|
15
13
|
|
|
16
14
|
def data_path(filename):
|
|
@@ -18,51 +16,41 @@ def data_path(filename):
|
|
|
18
16
|
return os.path.join(os.path.dirname(__file__), "data", filename)
|
|
19
17
|
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
assert source.get_last_job().status == "done"
|
|
60
|
-
|
|
61
|
-
datasets = Dataset.objects.filter(organization=org)
|
|
62
|
-
assert len(datasets) > 0
|
|
63
|
-
|
|
64
|
-
dataset = datasets.get(**{"harvest__remote_id": "04be6288-696d-4331-850d-a144871a7e3a"})
|
|
65
|
-
assert dataset.harvest.created_at == datetime(2019, 12, 10, 0, 0)
|
|
66
|
-
assert dataset.harvest.modified_at == datetime(2019, 9, 30, 0, 0)
|
|
67
|
-
assert len(dataset.resources) == 2
|
|
68
|
-
assert "xlsx" in [r.format for r in dataset.resources]
|
|
19
|
+
@pytest.mark.options(HARVESTER_BACKENDS=["dkan"])
|
|
20
|
+
class DkanBackendTest(PytestOnlyDBTestCase):
|
|
21
|
+
def test_dkan_french_w_license(self, rmock):
|
|
22
|
+
"""CKAN Harvester should accept the minimum dataset payload"""
|
|
23
|
+
DKAN_URL = "https://harvest.me/"
|
|
24
|
+
API_URL = "{}api/3/action/".format(DKAN_URL)
|
|
25
|
+
PACKAGE_LIST_URL = "{}package_list".format(API_URL)
|
|
26
|
+
PACKAGE_SHOW_URL = "{}package_show".format(API_URL)
|
|
27
|
+
|
|
28
|
+
with open(data_path("dkan-french-w-license.json")) as ifile:
|
|
29
|
+
data = json.loads(ifile.read())
|
|
30
|
+
|
|
31
|
+
org = OrganizationFactory()
|
|
32
|
+
source = HarvestSourceFactory(backend="dkan", url=DKAN_URL, organization=org)
|
|
33
|
+
rmock.get(
|
|
34
|
+
PACKAGE_LIST_URL,
|
|
35
|
+
json={"success": True, "result": ["fake-name"]},
|
|
36
|
+
status_code=200,
|
|
37
|
+
headers={"Content-Type": "application/json"},
|
|
38
|
+
)
|
|
39
|
+
rmock.get(
|
|
40
|
+
PACKAGE_SHOW_URL,
|
|
41
|
+
json=data,
|
|
42
|
+
status_code=200,
|
|
43
|
+
headers={"Content-Type": "application/json"},
|
|
44
|
+
)
|
|
45
|
+
actions.run(source)
|
|
46
|
+
source.reload()
|
|
47
|
+
assert source.get_last_job().status == "done"
|
|
48
|
+
|
|
49
|
+
datasets = Dataset.objects.filter(organization=org)
|
|
50
|
+
assert len(datasets) > 0
|
|
51
|
+
|
|
52
|
+
dataset = datasets.get(**{"harvest__remote_id": "04be6288-696d-4331-850d-a144871a7e3a"})
|
|
53
|
+
assert dataset.harvest.created_at == datetime(2019, 12, 10, 0, 0)
|
|
54
|
+
assert dataset.harvest.modified_at is None
|
|
55
|
+
assert len(dataset.resources) == 2
|
|
56
|
+
assert "xlsx" in [r.format for r in dataset.resources]
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
xmlns:dcterms="http://purl.org/dc/terms/"
|
|
12
12
|
xmlns:vcard="http://www.w3.org/2006/vcard/ns#"
|
|
13
13
|
xmlns:schema="http://schema.org/"
|
|
14
|
+
xmlns:skos="http://www.w3.org/2004/02/skos/core#"
|
|
14
15
|
>
|
|
15
16
|
<dcat:Catalog rdf:about="http://data.test.org/">
|
|
16
17
|
<dcat:dataset>
|
|
@@ -45,7 +46,6 @@
|
|
|
45
46
|
<dcterms:title>Sample DCAT Catalog</dcterms:title>
|
|
46
47
|
<dcat:dataset>
|
|
47
48
|
<dcat:Dataset>
|
|
48
|
-
<dcat:theme>Theme 2</dcat:theme>
|
|
49
49
|
<dcat:contactPoint rdf:resource="http://data.test.org/contacts/1"/>
|
|
50
50
|
<dcat:landingPage>http://data.test.org/datasets/1</dcat:landingPage>
|
|
51
51
|
<dcat:keyword>Tag 3</dcat:keyword>
|
|
@@ -75,6 +75,17 @@
|
|
|
75
75
|
<dcat:distribution rdf:resource="http://data.test.org/datasets/1/resources/1"/>
|
|
76
76
|
<dcterms:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#dateTime">2016-12-14T18:59:02.737480</dcterms:issued>
|
|
77
77
|
<dcterms:identifier>1</dcterms:identifier>
|
|
78
|
+
<dcat:theme>
|
|
79
|
+
<skos:Concept>
|
|
80
|
+
<skos:prefLabel xml:lang="fr">Répartition des espèces</skos:prefLabel>
|
|
81
|
+
<skos:inScheme>
|
|
82
|
+
<skos:ConceptScheme>
|
|
83
|
+
<dcterms:title xml:lang="fr">GEMET - INSPIRE themes, version 1.0</dcterms:title>
|
|
84
|
+
<dcterms:issued rdf:datatype="http://www.w3.org/2001/XMLSchema#date">2018-07-27</dcterms:issued>
|
|
85
|
+
</skos:ConceptScheme>
|
|
86
|
+
</skos:inScheme>
|
|
87
|
+
</skos:Concept>
|
|
88
|
+
</dcat:theme>
|
|
78
89
|
</dcat:Dataset>
|
|
79
90
|
</dcat:dataset>
|
|
80
91
|
<dcat:dataset>
|
|
@@ -110,6 +121,7 @@
|
|
|
110
121
|
</dct:spatial>
|
|
111
122
|
<dcterms:identifier>2</dcterms:identifier>
|
|
112
123
|
<dct:conformsTo rdf:nodeID="Ne0189e93917c4f67a412fc44883322e7"/>
|
|
124
|
+
<dcat:theme rdf:resource="http://bnode.namespace.voc/theme/hy"/>
|
|
113
125
|
</dcat:Dataset>
|
|
114
126
|
</dcat:dataset>
|
|
115
127
|
<dcat:service>
|
|
@@ -185,4 +197,8 @@
|
|
|
185
197
|
<dct:type rdf:resource="http://inspire.ec.europa.eu/glossary/SpatialReferenceSystem"/>
|
|
186
198
|
<dct:title xml:lang="fr">RGF93 / Lambert-93 (EPSG:2154)</dct:title>
|
|
187
199
|
</rdf:Description>
|
|
200
|
+
<skos:Concept rdf:about="http://bnode.namespace.voc/theme/hy">
|
|
201
|
+
<skos:inScheme rdf:resource="http://inspire.ec.europa.eu/theme"/>
|
|
202
|
+
<skos:prefLabel>Hydrographie</skos:prefLabel>
|
|
203
|
+
</skos:Concept>
|
|
188
204
|
</rdf:RDF>
|