udata 13.0.1.dev12__py3-none-any.whl → 14.4.1.dev7__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 +2 -8
- udata/api_fields.py +35 -4
- udata/app.py +30 -50
- udata/auth/__init__.py +29 -6
- udata/auth/forms.py +8 -6
- udata/auth/views.py +6 -3
- udata/commands/__init__.py +2 -14
- udata/commands/db.py +13 -25
- udata/commands/info.py +0 -16
- udata/commands/serve.py +3 -11
- udata/commands/tests/test_fixtures.py +9 -9
- udata/core/access_type/api.py +1 -1
- udata/core/access_type/constants.py +12 -8
- udata/core/activity/api.py +5 -6
- udata/core/avatars/api.py +43 -0
- udata/core/avatars/test_avatar_api.py +30 -0
- udata/core/badges/tests/test_commands.py +6 -6
- udata/core/csv.py +5 -0
- udata/core/dataservices/models.py +15 -3
- udata/core/dataservices/tasks.py +7 -0
- udata/core/dataset/api.py +2 -0
- udata/core/dataset/models.py +2 -2
- udata/core/dataset/permissions.py +31 -0
- udata/core/dataset/tasks.py +50 -10
- udata/core/discussions/models.py +1 -0
- udata/core/metrics/__init__.py +0 -6
- udata/core/organization/api.py +8 -5
- udata/core/organization/mails.py +1 -1
- udata/core/organization/models.py +9 -1
- udata/core/organization/notifications.py +84 -0
- udata/core/organization/permissions.py +1 -1
- udata/core/organization/tasks.py +3 -0
- udata/core/pages/tests/test_api.py +32 -0
- udata/core/post/api.py +24 -69
- udata/core/post/models.py +84 -16
- udata/core/post/tests/test_api.py +24 -1
- udata/core/reports/api.py +18 -0
- udata/core/reports/models.py +42 -2
- udata/core/reuse/models.py +1 -1
- udata/core/reuse/tasks.py +7 -0
- udata/core/site/models.py +2 -6
- udata/core/spatial/commands.py +2 -4
- udata/core/spatial/forms.py +2 -2
- udata/core/spatial/models.py +0 -10
- udata/core/spatial/tests/test_api.py +1 -36
- udata/core/user/models.py +15 -2
- udata/cors.py +2 -5
- udata/db/migrations.py +279 -0
- udata/features/notifications/api.py +7 -18
- udata/features/notifications/models.py +56 -0
- udata/features/notifications/tasks.py +25 -0
- udata/flask_mongoengine/engine.py +0 -4
- udata/frontend/__init__.py +3 -122
- udata/frontend/markdown.py +2 -1
- udata/harvest/actions.py +24 -9
- udata/harvest/api.py +30 -22
- udata/harvest/backends/__init__.py +21 -9
- udata/harvest/backends/base.py +29 -3
- udata/harvest/backends/ckan/harvesters.py +13 -2
- udata/harvest/backends/dcat.py +3 -0
- udata/harvest/backends/maaf.py +1 -0
- udata/harvest/commands.py +39 -4
- udata/harvest/filters.py +17 -6
- udata/harvest/forms.py +9 -6
- udata/harvest/models.py +16 -0
- udata/harvest/permissions.py +27 -0
- udata/harvest/tasks.py +3 -5
- udata/harvest/tests/ckan/test_ckan_backend.py +35 -2
- udata/harvest/tests/ckan/test_ckan_backend_errors.py +1 -1
- udata/harvest/tests/ckan/test_ckan_backend_filters.py +1 -1
- udata/harvest/tests/ckan/test_dkan_backend.py +1 -1
- udata/harvest/tests/dcat/udata.xml +6 -6
- udata/harvest/tests/factories.py +1 -1
- udata/harvest/tests/test_actions.py +63 -8
- udata/harvest/tests/test_api.py +278 -123
- udata/harvest/tests/test_base_backend.py +88 -1
- udata/harvest/tests/test_dcat_backend.py +60 -13
- udata/harvest/tests/test_filters.py +6 -0
- udata/i18n.py +11 -273
- udata/mail.py +5 -1
- udata/migrations/2025-10-31-create-membership-request-notifications.py +55 -0
- udata/migrations/2025-11-13-delete-user-email-index.py +25 -0
- udata/migrations/2025-12-04-add-uuid-to-discussion-messages.py +28 -0
- udata/models/__init__.py +0 -8
- udata/mongo/slug_fields.py +1 -1
- udata/rdf.py +45 -6
- udata/routing.py +2 -10
- udata/sentry.py +4 -10
- udata/settings.py +23 -17
- udata/tasks.py +4 -3
- udata/templates/mail/message.html +5 -31
- udata/tests/__init__.py +28 -12
- udata/tests/api/__init__.py +108 -21
- udata/tests/api/test_activities_api.py +36 -0
- udata/tests/api/test_auth_api.py +121 -95
- udata/tests/api/test_base_api.py +7 -4
- udata/tests/api/test_dataservices_api.py +29 -1
- udata/tests/api/test_datasets_api.py +45 -21
- udata/tests/api/test_organizations_api.py +192 -197
- udata/tests/api/test_reports_api.py +157 -0
- udata/tests/api/test_reuses_api.py +147 -147
- udata/tests/api/test_security_api.py +12 -12
- udata/tests/api/test_swagger.py +4 -4
- udata/tests/api/test_tags_api.py +8 -8
- udata/tests/api/test_user_api.py +13 -1
- udata/tests/apiv2/test_swagger.py +4 -4
- udata/tests/apiv2/test_topics.py +1 -1
- udata/tests/cli/test_cli_base.py +8 -9
- udata/tests/dataset/test_dataset_commands.py +4 -4
- udata/tests/dataset/test_dataset_model.py +66 -26
- udata/tests/dataset/test_dataset_rdf.py +99 -5
- udata/tests/dataset/test_resource_preview.py +0 -1
- udata/tests/frontend/test_auth.py +24 -1
- udata/tests/frontend/test_csv.py +0 -3
- udata/tests/helpers.py +37 -27
- udata/tests/organization/test_notifications.py +67 -2
- udata/tests/plugin.py +6 -261
- udata/tests/site/test_site_csv_exports.py +22 -10
- udata/tests/test_activity.py +9 -9
- udata/tests/test_cors.py +1 -1
- udata/tests/test_dcat_commands.py +2 -2
- udata/tests/test_discussions.py +5 -5
- udata/tests/test_migrations.py +181 -481
- 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_storages.py +25 -19
- udata/tests/test_topics.py +77 -61
- udata/tests/test_uris.py +33 -0
- udata/tests/workers/test_jobs_commands.py +23 -23
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +187 -108
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +187 -108
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +187 -108
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +188 -109
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +187 -108
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +187 -108
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +187 -108
- udata/translations/udata.pot +215 -106
- udata/uris.py +0 -2
- udata/utils.py +5 -0
- udata-14.4.1.dev7.dist-info/METADATA +109 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/RECORD +153 -166
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/entry_points.txt +3 -5
- udata/core/followers/views.py +0 -15
- udata/core/post/forms.py +0 -30
- udata/entrypoints.py +0 -93
- udata/features/identicon/__init__.py +0 -0
- udata/features/identicon/api.py +0 -13
- udata/features/identicon/backends.py +0 -131
- udata/features/identicon/tests/__init__.py +0 -0
- udata/features/identicon/tests/test_backends.py +0 -18
- udata/features/territories/__init__.py +0 -49
- udata/features/territories/api.py +0 -25
- udata/features/territories/models.py +0 -51
- udata/flask_mongoengine/json.py +0 -38
- udata/migrations/__init__.py +0 -367
- 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/tests/cli/test_db_cli.py +0 -68
- udata/tests/features/territories/__init__.py +0 -20
- udata/tests/features/territories/test_territories_api.py +0 -185
- udata/tests/frontend/test_hooks.py +0 -149
- udata-13.0.1.dev12.dist-info/METADATA +0 -133
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/WHEEL +0 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/licenses/LICENSE +0 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/top_level.txt +0 -0
|
@@ -1,75 +1,33 @@
|
|
|
1
|
-
from datetime import datetime
|
|
2
|
-
|
|
3
|
-
import pytz
|
|
4
1
|
from flask import url_for
|
|
5
2
|
|
|
6
|
-
from udata.core.
|
|
7
|
-
from udata.
|
|
8
|
-
|
|
9
|
-
from .api import APITestCase, DBTestCase
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class NotificationsMixin(object):
|
|
13
|
-
def setUp(self):
|
|
14
|
-
actions._providers = {}
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class NotificationsActionsTest(NotificationsMixin, DBTestCase):
|
|
18
|
-
def test_registered_provider_is_listed(self):
|
|
19
|
-
def fake_provider(user):
|
|
20
|
-
return []
|
|
21
|
-
|
|
22
|
-
actions.register_provider("fake", fake_provider)
|
|
23
|
-
|
|
24
|
-
self.assertIn("fake", actions.list_providers())
|
|
3
|
+
from udata.core.organization.factories import OrganizationFactory
|
|
4
|
+
from udata.core.organization.models import Member
|
|
25
5
|
|
|
26
|
-
|
|
27
|
-
@actions.notifier("fake")
|
|
28
|
-
def fake_provider(user):
|
|
29
|
-
return []
|
|
6
|
+
from .api import APITestCase
|
|
30
7
|
|
|
31
|
-
self.assertIn("fake", actions.list_providers())
|
|
32
8
|
|
|
33
|
-
|
|
34
|
-
dt = datetime.utcnow()
|
|
35
|
-
|
|
36
|
-
def fake_provider(user):
|
|
37
|
-
return [(dt, {"some": "value"})]
|
|
38
|
-
|
|
39
|
-
actions.register_provider("fake", fake_provider)
|
|
40
|
-
|
|
41
|
-
user = UserFactory()
|
|
42
|
-
notifs = actions.get_notifications(user)
|
|
43
|
-
|
|
44
|
-
self.assertEqual(len(notifs), 1)
|
|
45
|
-
self.assertEqual(notifs[0]["type"], "fake")
|
|
46
|
-
self.assertEqual(notifs[0]["details"], {"some": "value"})
|
|
47
|
-
self.assertEqualDates(notifs[0]["created_on"], dt)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
class NotificationsAPITest(NotificationsMixin, APITestCase):
|
|
9
|
+
class NotificationsAPITest(APITestCase):
|
|
51
10
|
def test_no_notifications(self):
|
|
52
11
|
self.login()
|
|
53
12
|
response = self.get(url_for("api.notifications"))
|
|
54
13
|
self.assert200(response)
|
|
55
14
|
|
|
56
|
-
self.assertEqual(
|
|
15
|
+
self.assertEqual(response.json["total"], 0)
|
|
57
16
|
|
|
58
17
|
def test_has_notifications(self):
|
|
18
|
+
admin = self.login()
|
|
59
19
|
self.login()
|
|
60
|
-
|
|
20
|
+
organization = OrganizationFactory(members=[Member(user=admin, role="admin")])
|
|
21
|
+
data = {"comment": "a comment"}
|
|
61
22
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return [(dt, {"some": "value"}), (dt, {"another": "value"})]
|
|
23
|
+
response = self.post(url_for("api.request_membership", org=organization), data)
|
|
24
|
+
self.assert201(response)
|
|
65
25
|
|
|
26
|
+
self.login(admin)
|
|
66
27
|
response = self.get(url_for("api.notifications"))
|
|
67
28
|
self.assert200(response)
|
|
68
29
|
|
|
69
|
-
self.assertEqual(
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
self.assertEqual(notification["type"], "fake")
|
|
74
|
-
self.assertEqual(response.json[0]["details"], {"some": "value"})
|
|
75
|
-
self.assertEqual(response.json[1]["details"], {"another": "value"})
|
|
30
|
+
self.assertEqual(response.json["total"], 1)
|
|
31
|
+
self.assertEqual(
|
|
32
|
+
response.json["data"][0]["details"]["request_organization"]["id"], str(organization.id)
|
|
33
|
+
)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from datetime import datetime, timedelta
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from flask import current_app
|
|
5
|
+
|
|
6
|
+
from udata.core.organization.models import Member, MembershipRequest, Organization
|
|
7
|
+
from udata.core.user.factories import UserFactory
|
|
8
|
+
from udata.features.notifications import tasks
|
|
9
|
+
from udata.features.notifications.models import Notification
|
|
10
|
+
from udata.tests.api import APITestCase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class UserTasksTest(APITestCase):
|
|
14
|
+
@pytest.mark.options(DAYS_AFTER_NOTIFICATION_EXPIRED=3)
|
|
15
|
+
def test_notify_inactive_users(self):
|
|
16
|
+
self.login()
|
|
17
|
+
member = Member(user=self.user, role="admin")
|
|
18
|
+
org = Organization.objects.create(
|
|
19
|
+
name="with transfert", description="XXX", members=[member]
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
notification_handled_date = (
|
|
23
|
+
datetime.utcnow()
|
|
24
|
+
- timedelta(days=current_app.config["DAYS_AFTER_NOTIFICATION_EXPIRED"])
|
|
25
|
+
- timedelta(days=1) # add margin
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
applicant = UserFactory()
|
|
29
|
+
|
|
30
|
+
request = MembershipRequest(user=applicant, comment="test")
|
|
31
|
+
org.add_membership_request(request)
|
|
32
|
+
|
|
33
|
+
assert Notification.objects.count() == 1
|
|
34
|
+
|
|
35
|
+
request.status = "accepted"
|
|
36
|
+
request.handled_by = self.user
|
|
37
|
+
request.handled_on = notification_handled_date
|
|
38
|
+
org.save()
|
|
39
|
+
MembershipRequest.after_handle.send(request, org=org)
|
|
40
|
+
|
|
41
|
+
tasks.delete_expired_notifications()
|
|
42
|
+
|
|
43
|
+
assert Notification.objects.count() == 0
|
udata/tests/test_owned.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
from mongoengine import post_save
|
|
2
2
|
|
|
3
3
|
import udata.core.owned as owned
|
|
4
|
+
from udata.core.dataset.permissions import OwnableReadPermission
|
|
4
5
|
from udata.core.organization.factories import OrganizationFactory
|
|
5
6
|
from udata.core.organization.models import Organization
|
|
6
7
|
from udata.core.user.factories import AdminFactory, UserFactory
|
|
7
8
|
from udata.core.user.models import User
|
|
8
9
|
from udata.models import Member
|
|
9
10
|
from udata.mongo import db
|
|
10
|
-
from udata.tests.api import DBTestCase
|
|
11
|
+
from udata.tests.api import APITestCase, DBTestCase
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class CustomQuerySet(owned.OwnedQuerySet):
|
|
@@ -265,3 +266,82 @@ class OwnedQuerysetTest(DBTestCase):
|
|
|
265
266
|
name="private_owned_by_other_user"
|
|
266
267
|
)
|
|
267
268
|
self.assertEqual(len(result), 0)
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class OwnableReadPermissionTest(APITestCase):
|
|
272
|
+
def setUp(self):
|
|
273
|
+
super().setUp()
|
|
274
|
+
from flask import g
|
|
275
|
+
from flask_principal import AnonymousIdentity
|
|
276
|
+
|
|
277
|
+
g.identity = AnonymousIdentity()
|
|
278
|
+
|
|
279
|
+
def test_public_object_visible_by_anonymous(self):
|
|
280
|
+
"""Public objects should be visible by anonymous users."""
|
|
281
|
+
obj = Owned.objects.create(owner=UserFactory(), private=False)
|
|
282
|
+
assert OwnableReadPermission(obj).can() is True
|
|
283
|
+
|
|
284
|
+
def test_public_object_visible_by_authenticated(self):
|
|
285
|
+
"""Public objects should be visible by authenticated users."""
|
|
286
|
+
obj = Owned.objects.create(owner=UserFactory(), private=False)
|
|
287
|
+
self.login()
|
|
288
|
+
assert OwnableReadPermission(obj).can() is True
|
|
289
|
+
|
|
290
|
+
def test_private_object_not_visible_by_anonymous(self):
|
|
291
|
+
"""Private objects should not be visible by anonymous users."""
|
|
292
|
+
obj = Owned.objects.create(owner=UserFactory(), private=True)
|
|
293
|
+
assert OwnableReadPermission(obj).can() is False
|
|
294
|
+
|
|
295
|
+
def test_private_object_not_visible_by_other_user(self):
|
|
296
|
+
"""Private objects should not be visible by other users."""
|
|
297
|
+
obj = Owned.objects.create(owner=UserFactory(), private=True)
|
|
298
|
+
self.login()
|
|
299
|
+
assert OwnableReadPermission(obj).can() is False
|
|
300
|
+
|
|
301
|
+
def test_private_object_visible_by_owner(self):
|
|
302
|
+
"""Private objects should be visible by their owner."""
|
|
303
|
+
owner = UserFactory()
|
|
304
|
+
obj = Owned.objects.create(owner=owner, private=True)
|
|
305
|
+
self.login(owner)
|
|
306
|
+
assert OwnableReadPermission(obj).can() is True
|
|
307
|
+
|
|
308
|
+
def test_private_object_visible_by_org_admin(self):
|
|
309
|
+
"""Private objects should be visible by organization admins."""
|
|
310
|
+
admin = UserFactory()
|
|
311
|
+
org = OrganizationFactory(members=[Member(user=admin, role="admin")])
|
|
312
|
+
obj = Owned.objects.create(organization=org, private=True)
|
|
313
|
+
self.login(admin)
|
|
314
|
+
assert OwnableReadPermission(obj).can() is True
|
|
315
|
+
|
|
316
|
+
def test_private_object_visible_by_org_editor(self):
|
|
317
|
+
"""Private objects should be visible by organization editors."""
|
|
318
|
+
editor = UserFactory()
|
|
319
|
+
org = OrganizationFactory(members=[Member(user=editor, role="editor")])
|
|
320
|
+
obj = Owned.objects.create(organization=org, private=True)
|
|
321
|
+
self.login(editor)
|
|
322
|
+
assert OwnableReadPermission(obj).can() is True
|
|
323
|
+
|
|
324
|
+
def test_private_object_not_visible_by_other_org_member(self):
|
|
325
|
+
"""Private objects should not be visible by members of other organizations."""
|
|
326
|
+
member = UserFactory()
|
|
327
|
+
OrganizationFactory(members=[Member(user=member, role="admin")])
|
|
328
|
+
org = OrganizationFactory()
|
|
329
|
+
obj = Owned.objects.create(organization=org, private=True)
|
|
330
|
+
self.login(member)
|
|
331
|
+
assert OwnableReadPermission(obj).can() is False
|
|
332
|
+
|
|
333
|
+
def test_private_object_visible_by_admin(self):
|
|
334
|
+
"""Private objects should be visible by sysadmins."""
|
|
335
|
+
admin = AdminFactory()
|
|
336
|
+
obj = Owned.objects.create(owner=UserFactory(), private=True)
|
|
337
|
+
self.login(admin)
|
|
338
|
+
assert OwnableReadPermission(obj).can() is True
|
|
339
|
+
|
|
340
|
+
def test_object_without_private_attribute(self):
|
|
341
|
+
"""Objects without private attribute should be visible by everyone."""
|
|
342
|
+
|
|
343
|
+
class OwnedWithoutPrivate(owned.Owned, db.Document):
|
|
344
|
+
name = db.StringField()
|
|
345
|
+
|
|
346
|
+
obj = OwnedWithoutPrivate.objects.create(owner=UserFactory())
|
|
347
|
+
assert OwnableReadPermission(obj).can() is True
|
udata/tests/test_storages.py
CHANGED
|
@@ -13,7 +13,7 @@ from udata.core.storages import utils
|
|
|
13
13
|
from udata.core.storages.api import META, chunk_filename
|
|
14
14
|
from udata.core.storages.tasks import purge_chunks
|
|
15
15
|
from udata.tests import PytestOnlyTestCase
|
|
16
|
-
from udata.tests.api import
|
|
16
|
+
from udata.tests.api import PytestOnlyAPITestCase
|
|
17
17
|
from udata.utils import faker
|
|
18
18
|
|
|
19
19
|
from .helpers import assert200, assert400
|
|
@@ -108,12 +108,13 @@ class ConfigurableAllowedExtensionsTest(PytestOnlyTestCase):
|
|
|
108
108
|
|
|
109
109
|
|
|
110
110
|
@pytest.mark.usefixtures("instance_path")
|
|
111
|
-
class StorageUploadViewTest(
|
|
112
|
-
def test_standard_upload(self
|
|
113
|
-
|
|
114
|
-
response =
|
|
115
|
-
url_for("
|
|
111
|
+
class StorageUploadViewTest(PytestOnlyAPITestCase):
|
|
112
|
+
def test_standard_upload(self):
|
|
113
|
+
self.login()
|
|
114
|
+
response = self.post(
|
|
115
|
+
url_for("storage.upload", name="resources"),
|
|
116
116
|
{"file": (BytesIO(b"aaa"), "Test with spaces.TXT")},
|
|
117
|
+
json=False,
|
|
117
118
|
)
|
|
118
119
|
|
|
119
120
|
assert200(response)
|
|
@@ -128,14 +129,14 @@ class StorageUploadViewTest(PytestOnlyDBTestCase):
|
|
|
128
129
|
assert response.json["url"] == expected
|
|
129
130
|
assert response.json["mime"] == "text/plain"
|
|
130
131
|
|
|
131
|
-
def test_chunked_upload(self
|
|
132
|
-
|
|
133
|
-
url = url_for("
|
|
132
|
+
def test_chunked_upload(self):
|
|
133
|
+
self.login()
|
|
134
|
+
url = url_for("storage.upload", name="tmp")
|
|
134
135
|
uuid = str(uuid4())
|
|
135
136
|
parts = 4
|
|
136
137
|
|
|
137
138
|
for i in range(parts):
|
|
138
|
-
response =
|
|
139
|
+
response = self.post(
|
|
139
140
|
url,
|
|
140
141
|
{
|
|
141
142
|
"file": (BytesIO(b"a"), "blob"),
|
|
@@ -147,6 +148,7 @@ class StorageUploadViewTest(PytestOnlyDBTestCase):
|
|
|
147
148
|
"totalparts": parts,
|
|
148
149
|
"chunksize": 1,
|
|
149
150
|
},
|
|
151
|
+
json=False,
|
|
150
152
|
)
|
|
151
153
|
|
|
152
154
|
assert200(response)
|
|
@@ -157,7 +159,7 @@ class StorageUploadViewTest(PytestOnlyDBTestCase):
|
|
|
157
159
|
assert "sha1" not in response.json
|
|
158
160
|
assert "url" not in response.json
|
|
159
161
|
|
|
160
|
-
response =
|
|
162
|
+
response = self.post(
|
|
161
163
|
url,
|
|
162
164
|
{
|
|
163
165
|
"uuid": uuid,
|
|
@@ -165,6 +167,7 @@ class StorageUploadViewTest(PytestOnlyDBTestCase):
|
|
|
165
167
|
"totalfilesize": parts,
|
|
166
168
|
"totalparts": parts,
|
|
167
169
|
},
|
|
170
|
+
json=False,
|
|
168
171
|
)
|
|
169
172
|
assert "filename" in response.json
|
|
170
173
|
assert "url" in response.json
|
|
@@ -180,13 +183,13 @@ class StorageUploadViewTest(PytestOnlyDBTestCase):
|
|
|
180
183
|
assert storages.tmp.read(filename) == b"aaaa"
|
|
181
184
|
assert list(storages.chunks.list_files()) == []
|
|
182
185
|
|
|
183
|
-
def test_chunked_upload_bad_chunk(self
|
|
184
|
-
|
|
185
|
-
url = url_for("
|
|
186
|
+
def test_chunked_upload_bad_chunk(self):
|
|
187
|
+
self.login()
|
|
188
|
+
url = url_for("storage.upload", name="tmp")
|
|
186
189
|
uuid = str(uuid4())
|
|
187
190
|
parts = 4
|
|
188
191
|
|
|
189
|
-
response =
|
|
192
|
+
response = self.post(
|
|
190
193
|
url,
|
|
191
194
|
{
|
|
192
195
|
"file": (BytesIO(b"a"), "blob"),
|
|
@@ -198,6 +201,7 @@ class StorageUploadViewTest(PytestOnlyDBTestCase):
|
|
|
198
201
|
"totalparts": parts,
|
|
199
202
|
"chunksize": 10, # Does not match
|
|
200
203
|
},
|
|
204
|
+
json=False,
|
|
201
205
|
)
|
|
202
206
|
|
|
203
207
|
assert400(response)
|
|
@@ -210,10 +214,12 @@ class StorageUploadViewTest(PytestOnlyDBTestCase):
|
|
|
210
214
|
|
|
211
215
|
assert list(storages.chunks.list_files()) == []
|
|
212
216
|
|
|
213
|
-
def test_upload_resource_bad_request(self
|
|
214
|
-
|
|
215
|
-
response =
|
|
216
|
-
url_for("
|
|
217
|
+
def test_upload_resource_bad_request(self):
|
|
218
|
+
self.login()
|
|
219
|
+
response = self.post(
|
|
220
|
+
url_for("storage.upload", name="tmp"),
|
|
221
|
+
{"bad": (BytesIO(b"aaa"), "test.txt")},
|
|
222
|
+
json=False,
|
|
217
223
|
)
|
|
218
224
|
|
|
219
225
|
assert400(response)
|
udata/tests/test_topics.py
CHANGED
|
@@ -16,6 +16,7 @@ from udata.core.topic.factories import (
|
|
|
16
16
|
TopicWithElementsFactory,
|
|
17
17
|
)
|
|
18
18
|
from udata.core.topic.models import Topic, TopicElement
|
|
19
|
+
from udata.core.user.factories import UserFactory
|
|
19
20
|
from udata.search import reindex
|
|
20
21
|
from udata.tests.api import PytestOnlyDBTestCase
|
|
21
22
|
from udata.tests.helpers import assert_emit
|
|
@@ -55,26 +56,32 @@ class TopicModelTest(PytestOnlyDBTestCase):
|
|
|
55
56
|
TopicWithElementsFactory()
|
|
56
57
|
job_reindex.assert_called()
|
|
57
58
|
|
|
58
|
-
def test_topic_activities(self,
|
|
59
|
+
def test_topic_activities(self, app, mocker):
|
|
59
60
|
# A user must be authenticated for activities to be emitted
|
|
60
|
-
|
|
61
|
+
from flask_login import login_user
|
|
62
|
+
|
|
63
|
+
user = UserFactory()
|
|
61
64
|
|
|
62
65
|
mock_created = mocker.patch.object(UserCreatedTopic, "emit")
|
|
63
66
|
mock_updated = mocker.patch.object(UserUpdatedTopic, "emit")
|
|
64
67
|
|
|
65
|
-
with
|
|
66
|
-
|
|
67
|
-
mock_created.assert_called()
|
|
68
|
+
with app.test_request_context():
|
|
69
|
+
login_user(user)
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
mock_updated.assert_called()
|
|
71
|
+
with assert_emit(Topic.on_create):
|
|
72
|
+
topic = TopicFactory(owner=user)
|
|
73
|
+
mock_created.assert_called()
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
with assert_emit(Topic.on_update):
|
|
76
|
+
topic.name = "new name"
|
|
77
|
+
topic.save()
|
|
78
|
+
mock_updated.assert_called()
|
|
79
|
+
|
|
80
|
+
def test_topic_element_activities(self, app, mocker):
|
|
75
81
|
# A user must be authenticated for activities to be emitted
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
from flask_login import login_user
|
|
83
|
+
|
|
84
|
+
user = UserFactory()
|
|
78
85
|
|
|
79
86
|
mock_topic_created = mocker.patch.object(UserCreatedTopic, "emit")
|
|
80
87
|
mock_topic_updated = mocker.patch.object(UserUpdatedTopic, "emit")
|
|
@@ -82,55 +89,64 @@ class TopicModelTest(PytestOnlyDBTestCase):
|
|
|
82
89
|
mock_element_updated = mocker.patch.object(UserUpdatedTopicElement, "emit")
|
|
83
90
|
mock_element_deleted = mocker.patch.object(UserDeletedTopicElement, "emit")
|
|
84
91
|
|
|
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
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
92
|
+
with app.test_request_context():
|
|
93
|
+
login_user(user)
|
|
94
|
+
|
|
95
|
+
topic = TopicFactory(owner=user)
|
|
96
|
+
|
|
97
|
+
# Reset mocks after topic creation since it emits activities
|
|
98
|
+
mock_topic_created.reset_mock()
|
|
99
|
+
mock_topic_updated.reset_mock()
|
|
100
|
+
|
|
101
|
+
# Test TopicElement creation
|
|
102
|
+
element = TopicElementDatasetFactory(topic=topic)
|
|
103
|
+
mock_element_created.assert_called_once()
|
|
104
|
+
mock_topic_created.assert_not_called()
|
|
105
|
+
mock_topic_updated.assert_not_called()
|
|
106
|
+
mock_element_updated.assert_not_called()
|
|
107
|
+
mock_element_deleted.assert_not_called()
|
|
108
|
+
|
|
109
|
+
call_args = mock_element_created.call_args
|
|
110
|
+
assert call_args[0][0] == topic # related_to
|
|
111
|
+
assert call_args[0][1] == topic.organization # organization
|
|
112
|
+
assert call_args[1]["extras"]["element_id"] == str(element.id)
|
|
113
|
+
|
|
114
|
+
mock_element_created.reset_mock()
|
|
115
|
+
|
|
116
|
+
# Test TopicElement update
|
|
117
|
+
element.title = "Updated title"
|
|
118
|
+
element.extras = {"key": "value"}
|
|
119
|
+
element.save()
|
|
120
|
+
mock_element_updated.assert_called_once()
|
|
121
|
+
mock_topic_created.assert_not_called()
|
|
122
|
+
mock_topic_updated.assert_not_called()
|
|
123
|
+
mock_element_created.assert_not_called()
|
|
124
|
+
mock_element_deleted.assert_not_called()
|
|
125
|
+
|
|
126
|
+
call_args = mock_element_updated.call_args
|
|
127
|
+
assert call_args[0][0] == topic # related_to
|
|
128
|
+
assert call_args[0][1] == topic.organization # organization
|
|
129
|
+
assert call_args[0][2] == ["title", "extras"] # changed_fields
|
|
130
|
+
assert call_args[1]["extras"]["element_id"] == str(element.id)
|
|
131
|
+
|
|
132
|
+
mock_element_updated.reset_mock()
|
|
133
|
+
|
|
134
|
+
# Test TopicElement deletion
|
|
135
|
+
element_id = element.id
|
|
136
|
+
element.delete()
|
|
137
|
+
|
|
138
|
+
# Deletion should only trigger delete activity
|
|
139
|
+
mock_element_deleted.assert_called_once()
|
|
140
|
+
mock_element_updated.assert_not_called()
|
|
141
|
+
mock_topic_created.assert_not_called()
|
|
142
|
+
mock_topic_updated.assert_not_called()
|
|
143
|
+
mock_element_created.assert_not_called()
|
|
144
|
+
|
|
145
|
+
# Verify delete activity arguments
|
|
146
|
+
delete_call_args = mock_element_deleted.call_args
|
|
147
|
+
assert delete_call_args[0][0] == topic # related_to
|
|
148
|
+
assert delete_call_args[0][1] == topic.organization # organization
|
|
149
|
+
assert delete_call_args[1]["extras"]["element_id"] == str(element_id)
|
|
134
150
|
|
|
135
151
|
def test_topic_element_wrong_class(self):
|
|
136
152
|
# use a model instance that is not supported
|
udata/tests/test_uris.py
CHANGED
|
@@ -2,6 +2,7 @@ import pytest
|
|
|
2
2
|
|
|
3
3
|
from udata import uris
|
|
4
4
|
from udata.settings import Defaults
|
|
5
|
+
from udata.tests import PytestOnlyTestCase
|
|
5
6
|
|
|
6
7
|
PUBLIC_HOSTS = [
|
|
7
8
|
"http://foo.com/blah_blah",
|
|
@@ -289,3 +290,35 @@ def test_with_credentials(url):
|
|
|
289
290
|
def test_with_credentials_disabled(url):
|
|
290
291
|
with pytest.raises(uris.ValidationError, match="Credentials in URL are not allowed"):
|
|
291
292
|
uris.validate(url, credentials=False)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@pytest.mark.options(CDATA_BASE_URL="http://localhost:3000/")
|
|
296
|
+
class CdataUrlTest(PytestOnlyTestCase):
|
|
297
|
+
def test_cdata_url_without_base_url(self, app):
|
|
298
|
+
app.config["CDATA_BASE_URL"] = None
|
|
299
|
+
assert uris.cdata_url("test") is None
|
|
300
|
+
|
|
301
|
+
def test_cdata_url_with_simple_uri(self):
|
|
302
|
+
assert uris.cdata_url("test") == "http://localhost:3000/test"
|
|
303
|
+
|
|
304
|
+
@pytest.mark.options(MAIL_CAMPAIGN="mail")
|
|
305
|
+
def test_cdata_url_with_mail_campaign(self):
|
|
306
|
+
assert (
|
|
307
|
+
uris.cdata_url("test", _mailCampaign=True)
|
|
308
|
+
== "http://localhost:3000/test?mtm_campaign=mail"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
def test_cdata_url_with_trailing_slash(self):
|
|
312
|
+
assert uris.cdata_url("test/") == "http://localhost:3000/test"
|
|
313
|
+
|
|
314
|
+
def test_cdata_url_with_append(self):
|
|
315
|
+
assert (
|
|
316
|
+
uris.cdata_url("test/", append="/discussions")
|
|
317
|
+
== "http://localhost:3000/test/discussions"
|
|
318
|
+
)
|
|
319
|
+
|
|
320
|
+
def test_cdata_url_with_append_and_kwargs(self):
|
|
321
|
+
assert (
|
|
322
|
+
uris.cdata_url("test/", append="/discussions", discussion_id="disc_id")
|
|
323
|
+
== "http://localhost:3000/test/discussions?discussion_id=disc_id"
|
|
324
|
+
)
|