udata 14.0.3.dev1__py3-none-any.whl → 14.7.3.dev4__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.
- udata/api/__init__.py +2 -0
- udata/api_fields.py +120 -19
- udata/app.py +18 -20
- udata/auth/__init__.py +4 -7
- udata/auth/forms.py +3 -3
- udata/auth/views.py +13 -6
- udata/commands/dcat.py +1 -1
- udata/commands/serve.py +3 -11
- udata/core/activity/api.py +5 -6
- udata/core/badges/tests/test_tasks.py +0 -2
- udata/core/csv.py +5 -0
- udata/core/dataservices/api.py +8 -1
- udata/core/dataservices/apiv2.py +3 -6
- udata/core/dataservices/models.py +5 -2
- udata/core/dataservices/rdf.py +2 -1
- udata/core/dataservices/tasks.py +6 -2
- udata/core/dataset/api.py +30 -4
- udata/core/dataset/api_fields.py +1 -1
- udata/core/dataset/apiv2.py +1 -1
- udata/core/dataset/constants.py +2 -9
- udata/core/dataset/models.py +21 -9
- udata/core/dataset/permissions.py +31 -0
- udata/core/dataset/rdf.py +18 -16
- udata/core/dataset/tasks.py +16 -7
- udata/core/discussions/api.py +15 -1
- udata/core/discussions/models.py +6 -0
- udata/core/legal/__init__.py +0 -0
- udata/core/legal/mails.py +128 -0
- udata/core/organization/api.py +16 -5
- udata/core/organization/api_fields.py +3 -3
- udata/core/organization/apiv2.py +3 -4
- udata/core/organization/mails.py +1 -1
- udata/core/organization/models.py +40 -7
- udata/core/organization/notifications.py +84 -0
- udata/core/organization/permissions.py +1 -1
- udata/core/organization/tasks.py +3 -0
- udata/core/pages/models.py +49 -0
- udata/core/pages/tests/test_api.py +165 -1
- udata/core/post/api.py +25 -70
- udata/core/post/constants.py +8 -0
- udata/core/post/models.py +109 -17
- udata/core/post/tests/test_api.py +140 -3
- udata/core/post/tests/test_models.py +24 -0
- udata/core/reports/api.py +18 -0
- udata/core/reports/models.py +42 -2
- udata/core/reuse/api.py +8 -0
- udata/core/reuse/apiv2.py +3 -6
- udata/core/reuse/models.py +1 -1
- udata/core/spatial/forms.py +2 -2
- udata/core/topic/models.py +8 -2
- udata/core/user/api.py +10 -3
- udata/core/user/api_fields.py +3 -3
- udata/core/user/models.py +33 -8
- udata/features/notifications/api.py +7 -18
- udata/features/notifications/models.py +59 -0
- udata/features/notifications/tasks.py +25 -0
- udata/features/transfer/actions.py +2 -0
- udata/features/transfer/models.py +17 -0
- udata/features/transfer/notifications.py +96 -0
- udata/flask_mongoengine/engine.py +0 -4
- udata/flask_mongoengine/pagination.py +1 -1
- udata/frontend/markdown.py +2 -1
- udata/harvest/actions.py +20 -0
- udata/harvest/api.py +24 -7
- udata/harvest/backends/base.py +27 -1
- udata/harvest/backends/ckan/harvesters.py +21 -4
- udata/harvest/backends/dcat.py +4 -1
- udata/harvest/commands.py +33 -0
- udata/harvest/filters.py +17 -6
- udata/harvest/models.py +16 -0
- udata/harvest/permissions.py +27 -0
- udata/harvest/tests/ckan/test_ckan_backend.py +33 -0
- udata/harvest/tests/test_actions.py +46 -2
- udata/harvest/tests/test_api.py +161 -6
- udata/harvest/tests/test_base_backend.py +86 -1
- udata/harvest/tests/test_dcat_backend.py +68 -3
- udata/harvest/tests/test_filters.py +6 -0
- udata/i18n.py +1 -4
- udata/mail.py +14 -0
- udata/migrations/2021-08-17-harvest-integrity.py +23 -16
- udata/migrations/2025-10-31-create-membership-request-notifications.py +55 -0
- udata/migrations/2025-12-04-add-uuid-to-discussion-messages.py +28 -0
- udata/migrations/2025-12-16-create-transfer-request-notifications.py +69 -0
- udata/migrations/2026-01-14-add-default-kind-to-posts.py +17 -0
- udata/mongo/slug_fields.py +1 -1
- udata/rdf.py +65 -11
- udata/routing.py +2 -2
- udata/settings.py +11 -0
- udata/tasks.py +2 -0
- udata/templates/mail/message.html +3 -1
- udata/tests/api/__init__.py +7 -17
- udata/tests/api/test_activities_api.py +36 -0
- udata/tests/api/test_datasets_api.py +69 -0
- udata/tests/api/test_organizations_api.py +0 -3
- udata/tests/api/test_reports_api.py +157 -0
- udata/tests/api/test_user_api.py +1 -1
- udata/tests/apiv2/test_dataservices.py +14 -0
- udata/tests/apiv2/test_organizations.py +9 -0
- udata/tests/apiv2/test_reuses.py +11 -0
- udata/tests/cli/test_cli_base.py +0 -1
- udata/tests/dataservice/test_dataservice_tasks.py +29 -0
- udata/tests/dataset/test_dataset_model.py +13 -1
- udata/tests/dataset/test_dataset_rdf.py +164 -5
- udata/tests/dataset/test_dataset_tasks.py +25 -0
- udata/tests/frontend/test_auth.py +58 -1
- udata/tests/frontend/test_csv.py +0 -3
- udata/tests/helpers.py +31 -27
- udata/tests/organization/test_notifications.py +67 -2
- udata/tests/search/test_search_integration.py +70 -0
- udata/tests/site/test_site_csv_exports.py +22 -10
- udata/tests/test_activity.py +9 -9
- udata/tests/test_api_fields.py +10 -0
- udata/tests/test_discussions.py +5 -5
- udata/tests/test_legal_mails.py +359 -0
- udata/tests/test_notifications.py +15 -57
- udata/tests/test_notifications_task.py +43 -0
- udata/tests/test_owned.py +81 -1
- udata/tests/test_transfer.py +181 -2
- udata/tests/test_uris.py +33 -0
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +309 -158
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +313 -160
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +312 -160
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +475 -202
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +317 -162
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +315 -161
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +323 -164
- udata/translations/udata.pot +169 -124
- udata/uris.py +0 -2
- udata/utils.py +23 -0
- udata-14.7.3.dev4.dist-info/METADATA +109 -0
- {udata-14.0.3.dev1.dist-info → udata-14.7.3.dev4.dist-info}/RECORD +142 -135
- udata/core/post/forms.py +0 -30
- udata/flask_mongoengine/json.py +0 -38
- udata/templates/mail/base.html +0 -105
- udata/templates/mail/base.txt +0 -6
- udata/templates/mail/button.html +0 -3
- udata/templates/mail/layouts/1-column.html +0 -19
- udata/templates/mail/layouts/2-columns.html +0 -20
- udata/templates/mail/layouts/center-panel.html +0 -16
- udata-14.0.3.dev1.dist-info/METADATA +0 -132
- {udata-14.0.3.dev1.dist-info → udata-14.7.3.dev4.dist-info}/WHEEL +0 -0
- {udata-14.0.3.dev1.dist-info → udata-14.7.3.dev4.dist-info}/entry_points.txt +0 -0
- {udata-14.0.3.dev1.dist-info → udata-14.7.3.dev4.dist-info}/licenses/LICENSE +0 -0
- {udata-14.0.3.dev1.dist-info → udata-14.7.3.dev4.dist-info}/top_level.txt +0 -0
udata/tests/helpers.py
CHANGED
|
@@ -4,7 +4,7 @@ from datetime import timedelta
|
|
|
4
4
|
from io import BytesIO
|
|
5
5
|
from urllib.parse import parse_qs, urlparse
|
|
6
6
|
|
|
7
|
-
import
|
|
7
|
+
import pytest
|
|
8
8
|
from flask import current_app, json
|
|
9
9
|
from flask_security.babel import FsDomain
|
|
10
10
|
from PIL import Image
|
|
@@ -12,6 +12,11 @@ from PIL import Image
|
|
|
12
12
|
from udata.core.spatial.factories import GeoZoneFactory
|
|
13
13
|
from udata.mail import mail_sent
|
|
14
14
|
|
|
15
|
+
requires_search_service = pytest.mark.skipif(
|
|
16
|
+
not os.environ.get("UDATA_TEST_SEARCH_INTEGRATION"),
|
|
17
|
+
reason="Set UDATA_TEST_SEARCH_INTEGRATION=1 to run search integration tests",
|
|
18
|
+
)
|
|
19
|
+
|
|
15
20
|
|
|
16
21
|
def assert_equal_dates(datetime1, datetime2, limit=1): # Seconds.
|
|
17
22
|
"""Lax date comparison, avoid comparing milliseconds and seconds."""
|
|
@@ -35,51 +40,50 @@ def assert_json_equal(first, second):
|
|
|
35
40
|
|
|
36
41
|
|
|
37
42
|
@contextmanager
|
|
38
|
-
def mock_signals(
|
|
43
|
+
def mock_signals(*signals):
|
|
39
44
|
__tracebackhide__ = True
|
|
40
|
-
specs = []
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
callbacks_by_signal = {}
|
|
47
|
+
calls_kwargs_by_signal = {}
|
|
44
48
|
|
|
45
|
-
for
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
for requestSignal in signals:
|
|
50
|
+
# We capture requestSignal with a default argument
|
|
51
|
+
def callback(*args, requestSignal=requestSignal, **kwargs):
|
|
52
|
+
calls_kwargs_by_signal.setdefault(requestSignal, [])
|
|
53
|
+
calls_kwargs_by_signal[requestSignal].append(kwargs)
|
|
54
|
+
|
|
55
|
+
callbacks_by_signal[requestSignal] = callback
|
|
56
|
+
requestSignal.connect(callback, weak=False)
|
|
49
57
|
|
|
50
|
-
yield
|
|
58
|
+
yield calls_kwargs_by_signal
|
|
51
59
|
|
|
52
|
-
for
|
|
53
|
-
|
|
54
|
-
signal_name = getattr(signal, "name", str(signal))
|
|
55
|
-
callback(signal_name, mock_handler)
|
|
60
|
+
for sig in signals:
|
|
61
|
+
sig.disconnect(callbacks_by_signal[sig])
|
|
56
62
|
|
|
57
63
|
|
|
58
64
|
@contextmanager
|
|
59
65
|
def assert_emit(*signals, assertions_callback=None):
|
|
60
66
|
__tracebackhide__ = True
|
|
61
|
-
msg = 'Signal "{0}" should have been emitted'
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
assert handler.called, msg.format(name)
|
|
65
|
-
if assertions_callback is not None:
|
|
66
|
-
assertions_callback(handler.call_args)
|
|
67
|
-
|
|
68
|
-
with mock_signals(callback, *signals):
|
|
68
|
+
with mock_signals(*signals) as calls_kwargs_by_signal:
|
|
69
69
|
yield
|
|
70
70
|
|
|
71
|
+
for signal in signals:
|
|
72
|
+
assert signal in calls_kwargs_by_signal, f'Signal "{signal}" should have been emitted'
|
|
73
|
+
if assertions_callback is not None:
|
|
74
|
+
for kwargs in calls_kwargs_by_signal[signal]:
|
|
75
|
+
assertions_callback(kwargs)
|
|
76
|
+
|
|
71
77
|
|
|
72
78
|
@contextmanager
|
|
73
79
|
def assert_not_emit(*signals):
|
|
74
80
|
__tracebackhide__ = True
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def callback(name, handler):
|
|
78
|
-
assert not handler.called, msg.format(name)
|
|
79
|
-
|
|
80
|
-
with mock_signals(callback, *signals):
|
|
81
|
+
with mock_signals(*signals) as calls_args_by_signal:
|
|
81
82
|
yield
|
|
82
83
|
|
|
84
|
+
for signal in signals:
|
|
85
|
+
assert signal not in calls_args_by_signal, f'Signal "{signal}" should not have been emitted'
|
|
86
|
+
|
|
83
87
|
|
|
84
88
|
@contextmanager
|
|
85
89
|
def capture_mails():
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from udata.core.organization.factories import OrganizationFactory
|
|
2
|
-
from udata.core.organization.notifications import
|
|
2
|
+
from udata.core.organization.notifications import (
|
|
3
|
+
membership_request_notifications,
|
|
4
|
+
)
|
|
3
5
|
from udata.core.user.factories import UserFactory
|
|
6
|
+
from udata.features.notifications.models import Notification
|
|
4
7
|
from udata.models import Member, MembershipRequest
|
|
5
|
-
from udata.tests.api import PytestOnlyDBTestCase
|
|
8
|
+
from udata.tests.api import DBTestCase, PytestOnlyDBTestCase
|
|
6
9
|
from udata.tests.helpers import assert_equal_dates
|
|
7
10
|
|
|
8
11
|
|
|
@@ -27,3 +30,65 @@ class OrganizationNotificationsTest(PytestOnlyDBTestCase):
|
|
|
27
30
|
assert details["user"]["id"] == applicant.id
|
|
28
31
|
assert details["user"]["fullname"] == applicant.fullname
|
|
29
32
|
assert details["user"]["avatar"] == str(applicant.avatar)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class MembershipRequestNotificationTest(DBTestCase):
|
|
36
|
+
def test_notification_created_for_admins_only(self):
|
|
37
|
+
"""Notifications are created for all admin users, not editors"""
|
|
38
|
+
admin1 = UserFactory()
|
|
39
|
+
admin2 = UserFactory()
|
|
40
|
+
editor = UserFactory()
|
|
41
|
+
applicant = UserFactory()
|
|
42
|
+
members = [
|
|
43
|
+
Member(user=editor, role="editor"),
|
|
44
|
+
Member(user=admin1, role="admin"),
|
|
45
|
+
Member(user=admin2, role="admin"),
|
|
46
|
+
]
|
|
47
|
+
org = OrganizationFactory(members=members)
|
|
48
|
+
|
|
49
|
+
request = MembershipRequest(user=applicant, comment="test")
|
|
50
|
+
org.add_membership_request(request)
|
|
51
|
+
|
|
52
|
+
notifications = Notification.objects.all()
|
|
53
|
+
assert len(notifications) == 2
|
|
54
|
+
|
|
55
|
+
admin_users = [notif.user for notif in notifications]
|
|
56
|
+
self.assertIn(admin1, admin_users)
|
|
57
|
+
self.assertIn(admin2, admin_users)
|
|
58
|
+
|
|
59
|
+
for notification in notifications:
|
|
60
|
+
assert notification.details.request_organization == org
|
|
61
|
+
assert notification.details.request_user == applicant
|
|
62
|
+
assert_equal_dates(notification.created_at, request.created)
|
|
63
|
+
|
|
64
|
+
def test_no_duplicate_notifications(self):
|
|
65
|
+
"""Duplicate notifications are not created on subsequent saves"""
|
|
66
|
+
admin = UserFactory()
|
|
67
|
+
applicant = UserFactory()
|
|
68
|
+
org = OrganizationFactory(members=[Member(user=admin, role="admin")])
|
|
69
|
+
|
|
70
|
+
request = MembershipRequest(user=applicant, comment="test")
|
|
71
|
+
org.add_membership_request(request)
|
|
72
|
+
org.add_membership_request(request)
|
|
73
|
+
|
|
74
|
+
assert Notification.objects.count() == 1
|
|
75
|
+
|
|
76
|
+
def test_multiple_requests_create_separate_notifications(self):
|
|
77
|
+
"""Multiple requests from different users create separate notifications"""
|
|
78
|
+
admin = UserFactory()
|
|
79
|
+
applicant1 = UserFactory()
|
|
80
|
+
applicant2 = UserFactory()
|
|
81
|
+
org = OrganizationFactory(members=[Member(user=admin, role="admin")])
|
|
82
|
+
|
|
83
|
+
request1 = MembershipRequest(user=applicant1, comment="test 1")
|
|
84
|
+
org.add_membership_request(request1)
|
|
85
|
+
|
|
86
|
+
request2 = MembershipRequest(user=applicant2, comment="test 2")
|
|
87
|
+
org.add_membership_request(request2)
|
|
88
|
+
|
|
89
|
+
notifications = Notification.objects.all()
|
|
90
|
+
assert len(notifications) == 2
|
|
91
|
+
|
|
92
|
+
request_users = [notif.details.request_user for notif in notifications]
|
|
93
|
+
self.assertIn(applicant1, request_users)
|
|
94
|
+
self.assertIn(applicant2, request_users)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from udata.core.dataset.factories import DatasetFactory
|
|
6
|
+
from udata.core.organization.factories import OrganizationFactory
|
|
7
|
+
from udata.core.reuse.factories import VisibleReuseFactory
|
|
8
|
+
from udata.tests.api import APITestCase
|
|
9
|
+
from udata.tests.helpers import requires_search_service
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@requires_search_service
|
|
13
|
+
@pytest.mark.options(SEARCH_SERVICE_API_URL="http://localhost:5000/api/1/", AUTO_INDEX=True)
|
|
14
|
+
class SearchIntegrationTest(APITestCase):
|
|
15
|
+
"""Integration tests that require a running search-service and Elasticsearch."""
|
|
16
|
+
|
|
17
|
+
def test_dataset_fuzzy_search(self):
|
|
18
|
+
"""
|
|
19
|
+
Test that Elasticsearch fuzzy search works.
|
|
20
|
+
|
|
21
|
+
A typo in the search query ("spectakulaire" instead of "spectaculaire")
|
|
22
|
+
should still find the dataset thanks to ES fuzzy matching.
|
|
23
|
+
"""
|
|
24
|
+
DatasetFactory(title="Données spectaculaires sur les transports")
|
|
25
|
+
|
|
26
|
+
# Small delay to let ES index the document
|
|
27
|
+
time.sleep(1)
|
|
28
|
+
|
|
29
|
+
# Search with a typo - only ES fuzzy search can handle this
|
|
30
|
+
response = self.get("/api/2/datasets/search/?q=spectakulaire")
|
|
31
|
+
self.assert200(response)
|
|
32
|
+
assert response.json["total"] >= 1
|
|
33
|
+
|
|
34
|
+
titles = [d["title"] for d in response.json["data"]]
|
|
35
|
+
assert "Données spectaculaires sur les transports" in titles
|
|
36
|
+
|
|
37
|
+
def test_reuse_search_with_organization_filter(self):
|
|
38
|
+
"""
|
|
39
|
+
Regression test for: 500 Server Error when None values are passed to search service.
|
|
40
|
+
|
|
41
|
+
When searching reuses with only an organization filter, other params should not be
|
|
42
|
+
sent as literal 'None' strings (e.g. ?q=None&tag=None).
|
|
43
|
+
"""
|
|
44
|
+
org = OrganizationFactory()
|
|
45
|
+
reuse = VisibleReuseFactory(organization=org)
|
|
46
|
+
|
|
47
|
+
time.sleep(1)
|
|
48
|
+
|
|
49
|
+
response = self.get(f"/api/2/reuses/search/?organization={org.id}")
|
|
50
|
+
self.assert200(response)
|
|
51
|
+
assert response.json["total"] >= 1
|
|
52
|
+
ids = [r["id"] for r in response.json["data"]]
|
|
53
|
+
assert str(reuse.id) in ids
|
|
54
|
+
|
|
55
|
+
def test_organization_search_with_query(self):
|
|
56
|
+
"""
|
|
57
|
+
Regression test for: 500 Server Error when None values are passed to search service.
|
|
58
|
+
|
|
59
|
+
When searching organizations, other params should not be sent as literal
|
|
60
|
+
'None' strings (e.g. ?badge=None).
|
|
61
|
+
"""
|
|
62
|
+
org = OrganizationFactory(name="Organisation Unique Test")
|
|
63
|
+
|
|
64
|
+
time.sleep(1)
|
|
65
|
+
|
|
66
|
+
response = self.get("/api/2/organizations/search/?q=unique")
|
|
67
|
+
self.assert200(response)
|
|
68
|
+
assert response.json["total"] >= 1
|
|
69
|
+
ids = [o["id"] for o in response.json["data"]]
|
|
70
|
+
assert str(org.id) in ids
|
|
@@ -7,6 +7,7 @@ from flask import url_for
|
|
|
7
7
|
from udata.core import csv
|
|
8
8
|
from udata.core.dataservices.factories import DataserviceFactory
|
|
9
9
|
from udata.core.dataset import tasks as dataset_tasks
|
|
10
|
+
from udata.core.dataset.constants import SPD
|
|
10
11
|
from udata.core.dataset.factories import DatasetFactory, ResourceFactory
|
|
11
12
|
from udata.core.organization.factories import OrganizationFactory
|
|
12
13
|
from udata.core.reuse.factories import ReuseFactory
|
|
@@ -25,7 +26,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
25
26
|
|
|
26
27
|
self.assert200(response)
|
|
27
28
|
self.assertEqual(response.mimetype, "text/csv")
|
|
28
|
-
self.assertEqual(response.charset, "utf-8")
|
|
29
29
|
|
|
30
30
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
31
31
|
reader = csv.get_reader(csvfile)
|
|
@@ -73,7 +73,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
73
73
|
|
|
74
74
|
self.assert200(response)
|
|
75
75
|
self.assertEqual(response.mimetype, "text/csv")
|
|
76
|
-
self.assertEqual(response.charset, "utf-8")
|
|
77
76
|
|
|
78
77
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
79
78
|
reader = csv.get_reader(csvfile)
|
|
@@ -99,6 +98,27 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
99
98
|
self.assertNotIn(str(dataset.id), ids)
|
|
100
99
|
self.assertNotIn(str(hidden_dataset.id), ids)
|
|
101
100
|
|
|
101
|
+
def test_datasets_csv_with_badge_filter(self):
|
|
102
|
+
self.app.config["EXPORT_CSV_MODELS"] = []
|
|
103
|
+
dataset_with_badge = DatasetFactory(resources=[ResourceFactory()])
|
|
104
|
+
dataset_with_badge.add_badge(SPD)
|
|
105
|
+
dataset_without_badge = DatasetFactory(resources=[ResourceFactory()])
|
|
106
|
+
|
|
107
|
+
response = self.get(url_for("api.site_datasets_csv", badge=SPD))
|
|
108
|
+
|
|
109
|
+
self.assert200(response)
|
|
110
|
+
|
|
111
|
+
csvfile = StringIO(response.data.decode("utf8"))
|
|
112
|
+
reader = csv.get_reader(csvfile)
|
|
113
|
+
next(reader) # skip header
|
|
114
|
+
|
|
115
|
+
rows = list(reader)
|
|
116
|
+
ids = [row[0] for row in rows]
|
|
117
|
+
|
|
118
|
+
self.assertEqual(len(rows), 1)
|
|
119
|
+
self.assertIn(str(dataset_with_badge.id), ids)
|
|
120
|
+
self.assertNotIn(str(dataset_without_badge.id), ids)
|
|
121
|
+
|
|
102
122
|
def test_resources_csv(self):
|
|
103
123
|
self.app.config["EXPORT_CSV_MODELS"] = []
|
|
104
124
|
datasets = [
|
|
@@ -110,7 +130,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
110
130
|
|
|
111
131
|
self.assert200(response)
|
|
112
132
|
self.assertEqual(response.mimetype, "text/csv")
|
|
113
|
-
self.assertEqual(response.charset, "utf-8")
|
|
114
133
|
|
|
115
134
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
116
135
|
reader = csv.get_reader(csvfile)
|
|
@@ -164,7 +183,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
164
183
|
|
|
165
184
|
self.assert200(response)
|
|
166
185
|
self.assertEqual(response.mimetype, "text/csv")
|
|
167
|
-
self.assertEqual(response.charset, "utf-8")
|
|
168
186
|
|
|
169
187
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
170
188
|
reader = csv.get_reader(csvfile)
|
|
@@ -200,7 +218,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
200
218
|
|
|
201
219
|
self.assert200(response)
|
|
202
220
|
self.assertEqual(response.mimetype, "text/csv")
|
|
203
|
-
self.assertEqual(response.charset, "utf-8")
|
|
204
221
|
|
|
205
222
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
206
223
|
reader = csv.get_reader(csvfile)
|
|
@@ -245,7 +262,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
245
262
|
|
|
246
263
|
self.assert200(response)
|
|
247
264
|
self.assertEqual(response.mimetype, "text/csv")
|
|
248
|
-
self.assertEqual(response.charset, "utf-8")
|
|
249
265
|
|
|
250
266
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
251
267
|
reader = csv.get_reader(csvfile)
|
|
@@ -293,7 +309,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
293
309
|
|
|
294
310
|
self.assert200(response)
|
|
295
311
|
self.assertEqual(response.mimetype, "text/csv")
|
|
296
|
-
self.assertEqual(response.charset, "utf-8")
|
|
297
312
|
|
|
298
313
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
299
314
|
reader = csv.get_reader(csvfile)
|
|
@@ -332,7 +347,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
332
347
|
|
|
333
348
|
self.assert200(response)
|
|
334
349
|
self.assertEqual(response.mimetype, "text/csv")
|
|
335
|
-
self.assertEqual(response.charset, "utf-8")
|
|
336
350
|
|
|
337
351
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
338
352
|
reader = csv.get_reader(csvfile)
|
|
@@ -379,7 +393,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
379
393
|
|
|
380
394
|
self.assert200(response)
|
|
381
395
|
self.assertEqual(response.mimetype, "text/csv")
|
|
382
|
-
self.assertEqual(response.charset, "utf-8")
|
|
383
396
|
|
|
384
397
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
385
398
|
reader = csv.get_reader(csvfile)
|
|
@@ -424,7 +437,6 @@ class SiteCsvExportsTest(APITestCase):
|
|
|
424
437
|
|
|
425
438
|
self.assert200(response)
|
|
426
439
|
self.assertEqual(response.mimetype, "text/csv")
|
|
427
|
-
self.assertEqual(response.charset, "utf-8")
|
|
428
440
|
|
|
429
441
|
csvfile = StringIO(response.data.decode("utf8"))
|
|
430
442
|
reader = csv.get_reader(csvfile)
|
udata/tests/test_activity.py
CHANGED
|
@@ -211,9 +211,9 @@ class AuditableTest(APITestCase):
|
|
|
211
211
|
not_auditable="original",
|
|
212
212
|
)
|
|
213
213
|
|
|
214
|
-
def check_signal_update(
|
|
214
|
+
def check_signal_update(kwargs):
|
|
215
215
|
self.assertEqual(
|
|
216
|
-
|
|
216
|
+
kwargs["changed_fields"],
|
|
217
217
|
[
|
|
218
218
|
"name",
|
|
219
219
|
"tags",
|
|
@@ -224,13 +224,13 @@ class AuditableTest(APITestCase):
|
|
|
224
224
|
"embedded_list.1.name",
|
|
225
225
|
],
|
|
226
226
|
)
|
|
227
|
-
self.assertEqual(
|
|
228
|
-
self.assertEqual(
|
|
229
|
-
self.assertEqual(
|
|
230
|
-
self.assertEqual(
|
|
231
|
-
self.assertEqual(
|
|
232
|
-
self.assertEqual(
|
|
233
|
-
self.assertEqual(
|
|
227
|
+
self.assertEqual(kwargs["previous"]["name"], "fake")
|
|
228
|
+
self.assertEqual(kwargs["previous"]["tags"], ["some", "tags"])
|
|
229
|
+
self.assertEqual(kwargs["previous"]["some_date"], date(2020, 1, 1))
|
|
230
|
+
self.assertEqual(kwargs["previous"]["daterange_embedded.start"], date(2020, 1, 1))
|
|
231
|
+
self.assertEqual(kwargs["previous"]["daterange_embedded.end"], date(2020, 12, 31))
|
|
232
|
+
self.assertEqual(kwargs["previous"]["some_list"], ["some", "list"])
|
|
233
|
+
self.assertEqual(kwargs["previous"]["embedded_list.1.name"], "fake_embedded_1")
|
|
234
234
|
|
|
235
235
|
with assert_emit(FakeAuditableSubject.on_update, assertions_callback=check_signal_update):
|
|
236
236
|
fake.name = "different"
|
udata/tests/test_api_fields.py
CHANGED
|
@@ -354,3 +354,13 @@ class ApplyPaginationTest(PytestOnlyDBTestCase):
|
|
|
354
354
|
results: DBPaginator = Fake.apply_pagination(Fake.apply_sort_filters(Fake.objects))
|
|
355
355
|
assert results.page_size == 5
|
|
356
356
|
assert results.page == 3
|
|
357
|
+
|
|
358
|
+
def test_negative_page_size_returns_404(self, app) -> None:
|
|
359
|
+
"""Negative page_size should return a 404 error."""
|
|
360
|
+
from werkzeug.exceptions import NotFound
|
|
361
|
+
|
|
362
|
+
FakeFactory()
|
|
363
|
+
|
|
364
|
+
with app.test_request_context("/foobar", query_string={"page": 1, "page_size": -5}):
|
|
365
|
+
with pytest.raises(NotFound):
|
|
366
|
+
Fake.apply_pagination(Fake.apply_sort_filters(Fake.objects))
|
udata/tests/test_discussions.py
CHANGED
|
@@ -142,11 +142,11 @@ class DiscussionsTest(APITestCase):
|
|
|
142
142
|
with assert_not_emit(on_new_discussion):
|
|
143
143
|
discussion_id = None
|
|
144
144
|
|
|
145
|
-
def check_signal(
|
|
145
|
+
def check_signal(kwargs):
|
|
146
146
|
self.assertIsNotNone(discussion_id)
|
|
147
147
|
self.assertIn(
|
|
148
|
-
f"https://data.gouv.fr/datasets/{dataset.slug}/discussions
|
|
149
|
-
|
|
148
|
+
f"https://data.gouv.fr/datasets/{dataset.slug}/discussions?discussion_id={discussion_id}",
|
|
149
|
+
kwargs["message"],
|
|
150
150
|
)
|
|
151
151
|
|
|
152
152
|
with assert_emit(on_new_potential_spam, assertions_callback=check_signal):
|
|
@@ -620,8 +620,8 @@ class DiscussionsTest(APITestCase):
|
|
|
620
620
|
self.login()
|
|
621
621
|
with assert_not_emit(on_new_discussion_comment):
|
|
622
622
|
|
|
623
|
-
def check_signal(
|
|
624
|
-
self.assertIn(discussion.url_for(),
|
|
623
|
+
def check_signal(kwargs):
|
|
624
|
+
self.assertIn(discussion.url_for(), kwargs["message"])
|
|
625
625
|
|
|
626
626
|
with assert_emit(on_new_potential_spam, assertions_callback=check_signal):
|
|
627
627
|
response = self.post(
|