udata 10.8.1.dev36652__py2.py3-none-any.whl → 10.8.2__py2.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/__init__.py +1 -1
- udata/app.py +0 -2
- udata/commands/db.py +22 -9
- udata/core/dataset/models.py +5 -3
- udata/core/discussions/api.py +2 -2
- udata/core/jobs/api.py +3 -3
- udata/core/metrics/helpers.py +10 -0
- udata/core/metrics/tasks.py +144 -1
- udata/core/organization/api.py +2 -2
- udata/core/post/api.py +1 -1
- udata/core/user/api.py +1 -1
- udata/features/identicon/api.py +1 -1
- udata/harvest/actions.py +24 -28
- udata/harvest/api.py +28 -36
- udata/harvest/backends/ckan/__init__.py +3 -0
- udata/harvest/backends/ckan/harvesters.py +274 -0
- udata/harvest/backends/ckan/schemas/__init__.py +0 -0
- udata/harvest/backends/ckan/schemas/ckan.py +86 -0
- udata/harvest/backends/ckan/schemas/dkan.py +98 -0
- udata/harvest/commands.py +7 -7
- udata/harvest/tasks.py +1 -1
- udata/harvest/tests/ckan/conftest.py +67 -0
- udata/harvest/tests/ckan/data/dkan-french-w-license.json +226 -0
- udata/harvest/tests/ckan/test_ckan_backend.py +697 -0
- udata/harvest/tests/ckan/test_ckan_backend_errors.py +140 -0
- udata/harvest/tests/ckan/test_ckan_backend_filters.py +130 -0
- udata/harvest/tests/ckan/test_dkan_backend.py +68 -0
- udata/harvest/tests/test_actions.py +27 -32
- udata/harvest/tests/test_api.py +23 -18
- udata/harvest/tests/test_dcat_backend.py +29 -29
- udata/migrations/2025-07-30-purge-old-harvest-dynamic-fields.py +29 -0
- udata/mongo/slug_fields.py +25 -8
- udata/routing.py +6 -0
- udata/static/chunks/{11.b6f741fcc366abfad9c4.js → 11.51d706fb9521c16976bc.js} +3 -3
- udata/static/chunks/{11.b6f741fcc366abfad9c4.js.map → 11.51d706fb9521c16976bc.js.map} +1 -1
- udata/static/chunks/{13.2d06442dd9a05d9777b5.js → 13.39e106d56f794ebd06a0.js} +2 -2
- udata/static/chunks/{13.2d06442dd9a05d9777b5.js.map → 13.39e106d56f794ebd06a0.js.map} +1 -1
- udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js → 17.70cbb4a91b002338007e.js} +2 -2
- udata/static/chunks/{17.e8e4caaad5cb0cc0bacc.js.map → 17.70cbb4a91b002338007e.js.map} +1 -1
- udata/static/chunks/{19.f03a102365af4315f9db.js → 19.a348a5fff8fe2801e52a.js} +3 -3
- udata/static/chunks/{19.f03a102365af4315f9db.js.map → 19.a348a5fff8fe2801e52a.js.map} +1 -1
- udata/static/chunks/{5.0fa1408dae4e76b87b2e.js → 5.343ca020a2d38cec1a14.js} +3 -3
- udata/static/chunks/{5.0fa1408dae4e76b87b2e.js.map → 5.343ca020a2d38cec1a14.js.map} +1 -1
- udata/static/chunks/{6.d663709d877baa44a71e.js → 6.a3b07de9dd2ca2d24e85.js} +3 -3
- udata/static/chunks/{6.d663709d877baa44a71e.js.map → 6.a3b07de9dd2ca2d24e85.js.map} +1 -1
- udata/static/chunks/{8.778091d55cd8ea39af6b.js → 8.462bb3029de008497675.js} +2 -2
- udata/static/chunks/{8.778091d55cd8ea39af6b.js.map → 8.462bb3029de008497675.js.map} +1 -1
- udata/static/common.js +1 -1
- udata/static/common.js.map +1 -1
- udata/tests/api/test_datasets_api.py +0 -46
- udata/tests/api/test_organizations_api.py +5 -0
- udata/tests/cli/test_db_cli.py +12 -0
- udata/tests/dataset/test_dataset_model.py +0 -16
- udata/tests/metrics/__init__.py +0 -0
- udata/tests/metrics/conftest.py +15 -0
- udata/tests/metrics/helpers.py +58 -0
- udata/tests/metrics/test_metrics.py +67 -0
- udata/tests/metrics/test_tasks.py +171 -0
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +72 -65
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +72 -65
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +72 -65
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +72 -65
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +72 -65
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +72 -65
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +72 -65
- udata/translations/udata.pot +74 -70
- {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/METADATA +16 -2
- {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/RECORD +79 -62
- {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/entry_points.txt +2 -0
- {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/LICENSE +0 -0
- {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/WHEEL +0 -0
- {udata-10.8.1.dev36652.dist-info → udata-10.8.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from udata.harvest import actions
|
|
4
|
+
from udata.harvest.tests.factories import HarvestSourceFactory
|
|
5
|
+
|
|
6
|
+
pytestmark = [
|
|
7
|
+
pytest.mark.usefixtures("clean_db"),
|
|
8
|
+
pytest.mark.options(PLUGINS=["ckan"]),
|
|
9
|
+
]
|
|
10
|
+
|
|
11
|
+
CKAN_URL = "https://harvest.me/"
|
|
12
|
+
API_URL = "{}api/3/action/package_list".format(CKAN_URL)
|
|
13
|
+
|
|
14
|
+
# We test against success and error status code
|
|
15
|
+
# because CKAN API always return 200
|
|
16
|
+
# but some other cases may happen outside the API
|
|
17
|
+
STATUS_CODE = (400, 500)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.mark.parametrize("code", STATUS_CODE)
|
|
21
|
+
def test_html_error(rmock, code):
|
|
22
|
+
# Happens with wrong source URL (html is returned instead of json)
|
|
23
|
+
html = "<html><body>Error</body></html>"
|
|
24
|
+
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
25
|
+
|
|
26
|
+
rmock.get(API_URL, text=html, status_code=code, headers={"Content-Type": "text/html"})
|
|
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
|
+
)
|
|
47
|
+
|
|
48
|
+
actions.run(source)
|
|
49
|
+
|
|
50
|
+
source.reload()
|
|
51
|
+
|
|
52
|
+
job = source.get_last_job()
|
|
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
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def test_200_plain_text_error(rmock):
|
|
65
|
+
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
66
|
+
|
|
67
|
+
rmock.get(API_URL, text='"Some error"', status_code=200, headers={"Content-Type": "text/plain"})
|
|
68
|
+
|
|
69
|
+
actions.run(source)
|
|
70
|
+
|
|
71
|
+
source.reload()
|
|
72
|
+
|
|
73
|
+
job = source.get_last_job()
|
|
74
|
+
assert len(job.items) == 0
|
|
75
|
+
assert len(job.errors) == 1
|
|
76
|
+
error = job.errors[0]
|
|
77
|
+
# Raw quoted string is properly unquoted
|
|
78
|
+
assert error.message == "Some error"
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_standard_api_json_error(rmock):
|
|
82
|
+
json = {"success": False, "error": "an error"}
|
|
83
|
+
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
84
|
+
|
|
85
|
+
rmock.get(API_URL, json=json, status_code=200, headers={"Content-Type": "application/json"})
|
|
86
|
+
|
|
87
|
+
actions.run(source)
|
|
88
|
+
|
|
89
|
+
source.reload()
|
|
90
|
+
|
|
91
|
+
job = source.get_last_job()
|
|
92
|
+
assert len(job.items) == 0
|
|
93
|
+
assert len(job.errors) == 1
|
|
94
|
+
error = job.errors[0]
|
|
95
|
+
assert error.message == "an error"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_standard_api_json_error_with_details(rmock):
|
|
99
|
+
json = {
|
|
100
|
+
"success": False,
|
|
101
|
+
"error": {
|
|
102
|
+
"message": "an error",
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
source = HarvestSourceFactory(backend="ckan", url=CKAN_URL)
|
|
106
|
+
|
|
107
|
+
rmock.get(API_URL, json=json, status_code=200, headers={"Content-Type": "application/json"})
|
|
108
|
+
|
|
109
|
+
actions.run(source)
|
|
110
|
+
|
|
111
|
+
source.reload()
|
|
112
|
+
|
|
113
|
+
job = source.get_last_job()
|
|
114
|
+
assert len(job.items) == 0
|
|
115
|
+
assert len(job.errors) == 1
|
|
116
|
+
error = job.errors[0]
|
|
117
|
+
assert error.message == "an error"
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def test_standard_api_json_error_with_details_and_type(rmock):
|
|
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)
|
|
129
|
+
|
|
130
|
+
rmock.get(API_URL, json=json, status_code=200, headers={"Content-Type": "application/json"})
|
|
131
|
+
|
|
132
|
+
actions.run(source)
|
|
133
|
+
|
|
134
|
+
source.reload()
|
|
135
|
+
|
|
136
|
+
job = source.get_last_job()
|
|
137
|
+
assert len(job.items) == 0
|
|
138
|
+
assert len(job.errors) == 1
|
|
139
|
+
error = job.errors[0]
|
|
140
|
+
assert error.message == "Authorization Error: Access denied"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import urllib
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
from udata.harvest import actions
|
|
6
|
+
from udata.harvest.tests.factories import HarvestSourceFactory
|
|
7
|
+
from udata.utils import faker
|
|
8
|
+
|
|
9
|
+
pytestmark = [
|
|
10
|
+
pytest.mark.usefixtures("clean_db"),
|
|
11
|
+
pytest.mark.options(PLUGINS=["ckan"]),
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_include_org_filter(ckan, rmock):
|
|
16
|
+
source = HarvestSourceFactory(
|
|
17
|
+
backend="ckan",
|
|
18
|
+
url=ckan.BASE_URL,
|
|
19
|
+
config={"filters": [{"key": "organization", "value": "organization_name"}]},
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
rmock.get(
|
|
23
|
+
ckan.PACKAGE_SEARCH_URL,
|
|
24
|
+
json={"success": True, "result": {"results": []}},
|
|
25
|
+
status_code=200,
|
|
26
|
+
headers={"Content-Type": "application/json"},
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
actions.run(source)
|
|
30
|
+
source.reload()
|
|
31
|
+
|
|
32
|
+
assert rmock.call_count == 1
|
|
33
|
+
params = {"q": "organization:organization_name", "rows": 1000}
|
|
34
|
+
assert rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_exclude_org_filter(ckan, rmock):
|
|
38
|
+
source = HarvestSourceFactory(
|
|
39
|
+
backend="ckan",
|
|
40
|
+
url=ckan.BASE_URL,
|
|
41
|
+
config={
|
|
42
|
+
"filters": [{"key": "organization", "value": "organization_name", "type": "exclude"}]
|
|
43
|
+
},
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
rmock.get(
|
|
47
|
+
ckan.PACKAGE_SEARCH_URL,
|
|
48
|
+
json={"success": True, "result": {"results": []}},
|
|
49
|
+
status_code=200,
|
|
50
|
+
headers={"Content-Type": "application/json"},
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
actions.run(source)
|
|
54
|
+
source.reload()
|
|
55
|
+
|
|
56
|
+
assert rmock.call_count == 1
|
|
57
|
+
|
|
58
|
+
params = {"q": "-organization:organization_name", "rows": 1000}
|
|
59
|
+
assert rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_tag_filter(ckan, rmock):
|
|
63
|
+
tag = faker.word()
|
|
64
|
+
source = HarvestSourceFactory(
|
|
65
|
+
backend="ckan", url=ckan.BASE_URL, config={"filters": [{"key": "tags", "value": tag}]}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
rmock.get(
|
|
69
|
+
ckan.PACKAGE_SEARCH_URL,
|
|
70
|
+
json={"success": True, "result": {"results": []}},
|
|
71
|
+
status_code=200,
|
|
72
|
+
headers={"Content-Type": "application/json"},
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
actions.run(source)
|
|
76
|
+
source.reload()
|
|
77
|
+
|
|
78
|
+
assert rmock.call_count == 1
|
|
79
|
+
params = {"q": f"tags:{tag}", "rows": 1000}
|
|
80
|
+
assert rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_exclude_tag_filter(ckan, rmock):
|
|
84
|
+
tag = faker.word()
|
|
85
|
+
source = HarvestSourceFactory(
|
|
86
|
+
backend="ckan",
|
|
87
|
+
url=ckan.BASE_URL,
|
|
88
|
+
config={"filters": [{"key": "tags", "value": tag, "type": "exclude"}]},
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
rmock.get(
|
|
92
|
+
ckan.PACKAGE_SEARCH_URL,
|
|
93
|
+
json={"success": True, "result": {"results": []}},
|
|
94
|
+
status_code=200,
|
|
95
|
+
headers={"Content-Type": "application/json"},
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
actions.run(source)
|
|
99
|
+
source.reload()
|
|
100
|
+
|
|
101
|
+
assert rmock.call_count == 1
|
|
102
|
+
params = {"q": f"-tags:{tag}", "rows": 1000}
|
|
103
|
+
assert rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_can_have_multiple_filters(ckan, rmock):
|
|
107
|
+
source = HarvestSourceFactory(
|
|
108
|
+
backend="ckan",
|
|
109
|
+
url=ckan.BASE_URL,
|
|
110
|
+
config={
|
|
111
|
+
"filters": [
|
|
112
|
+
{"key": "organization", "value": "organization_name"},
|
|
113
|
+
{"key": "tags", "value": "tag-2", "type": "exclude"},
|
|
114
|
+
]
|
|
115
|
+
},
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
rmock.get(
|
|
119
|
+
ckan.PACKAGE_SEARCH_URL,
|
|
120
|
+
json={"success": True, "result": {"results": []}},
|
|
121
|
+
status_code=200,
|
|
122
|
+
headers={"Content-Type": "application/json"},
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
actions.run(source)
|
|
126
|
+
source.reload()
|
|
127
|
+
|
|
128
|
+
assert rmock.call_count == 1
|
|
129
|
+
params = {"q": "organization:organization_name AND -tags:tag-2", "rows": 1000}
|
|
130
|
+
assert rmock.last_request.url == f"{ckan.PACKAGE_SEARCH_URL}?{urllib.parse.urlencode(params)}"
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
|
|
7
|
+
from udata.app import create_app
|
|
8
|
+
from udata.core.organization.factories import OrganizationFactory
|
|
9
|
+
from udata.harvest import actions
|
|
10
|
+
from udata.harvest.tests.factories import HarvestSourceFactory
|
|
11
|
+
from udata.models import Dataset
|
|
12
|
+
from udata.settings import Defaults, Testing
|
|
13
|
+
from udata.tests.plugin import drop_db
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def data_path(filename):
|
|
17
|
+
"""Get a test data path"""
|
|
18
|
+
return os.path.join(os.path.dirname(__file__), "data", filename)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class DkanSettings(Testing):
|
|
22
|
+
PLUGINS = ["dkan"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@pytest.fixture(scope="module")
|
|
26
|
+
def app(request):
|
|
27
|
+
"""Create an udata app once for the module."""
|
|
28
|
+
app = create_app(Defaults, override=DkanSettings)
|
|
29
|
+
with app.app_context():
|
|
30
|
+
drop_db(app)
|
|
31
|
+
yield app
|
|
32
|
+
with app.app_context():
|
|
33
|
+
drop_db(app)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_dkan_french_w_license(app, rmock):
|
|
37
|
+
"""CKAN Harvester should accept the minimum dataset payload"""
|
|
38
|
+
DKAN_URL = "https://harvest.me/"
|
|
39
|
+
API_URL = "{}api/3/action/".format(DKAN_URL)
|
|
40
|
+
PACKAGE_LIST_URL = "{}package_list".format(API_URL)
|
|
41
|
+
PACKAGE_SHOW_URL = "{}package_show".format(API_URL)
|
|
42
|
+
|
|
43
|
+
with open(data_path("dkan-french-w-license.json")) as ifile:
|
|
44
|
+
data = json.loads(ifile.read())
|
|
45
|
+
|
|
46
|
+
org = OrganizationFactory()
|
|
47
|
+
source = HarvestSourceFactory(backend="dkan", url=DKAN_URL, organization=org)
|
|
48
|
+
rmock.get(
|
|
49
|
+
PACKAGE_LIST_URL,
|
|
50
|
+
json={"success": True, "result": ["fake-name"]},
|
|
51
|
+
status_code=200,
|
|
52
|
+
headers={"Content-Type": "application/json"},
|
|
53
|
+
)
|
|
54
|
+
rmock.get(
|
|
55
|
+
PACKAGE_SHOW_URL, json=data, status_code=200, headers={"Content-Type": "application/json"}
|
|
56
|
+
)
|
|
57
|
+
actions.run(source)
|
|
58
|
+
source.reload()
|
|
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]
|
|
@@ -152,7 +152,7 @@ class HarvestActionsTest:
|
|
|
152
152
|
data["url"] = new_url
|
|
153
153
|
|
|
154
154
|
with assert_emit(signals.harvest_source_updated):
|
|
155
|
-
source = actions.update_source(source
|
|
155
|
+
source = actions.update_source(source, data)
|
|
156
156
|
|
|
157
157
|
assert source.url == new_url
|
|
158
158
|
source.reload()
|
|
@@ -162,14 +162,14 @@ class HarvestActionsTest:
|
|
|
162
162
|
def test_validate_source(self, mock):
|
|
163
163
|
source = HarvestSourceFactory()
|
|
164
164
|
|
|
165
|
-
actions.validate_source(source
|
|
165
|
+
actions.validate_source(source)
|
|
166
166
|
|
|
167
167
|
source.reload()
|
|
168
168
|
assert source.validation.state == VALIDATION_ACCEPTED
|
|
169
169
|
assert source.validation.on is not None
|
|
170
170
|
assert source.validation.by is None
|
|
171
171
|
assert source.validation.comment is None
|
|
172
|
-
mock.assert_called_once_with(source
|
|
172
|
+
mock.assert_called_once_with(source)
|
|
173
173
|
|
|
174
174
|
assert source.periodic_task is not None
|
|
175
175
|
|
|
@@ -177,7 +177,7 @@ class HarvestActionsTest:
|
|
|
177
177
|
def test_validate_source_with_comment(self, mock):
|
|
178
178
|
source = HarvestSourceFactory()
|
|
179
179
|
|
|
180
|
-
actions.validate_source(source
|
|
180
|
+
actions.validate_source(source, "comment")
|
|
181
181
|
|
|
182
182
|
source.reload()
|
|
183
183
|
|
|
@@ -185,12 +185,12 @@ class HarvestActionsTest:
|
|
|
185
185
|
assert source.validation.on is not None
|
|
186
186
|
assert source.validation.by is None
|
|
187
187
|
assert source.validation.comment == "comment"
|
|
188
|
-
mock.assert_called_once_with(source
|
|
188
|
+
mock.assert_called_once_with(source)
|
|
189
189
|
|
|
190
190
|
def test_reject_source(self):
|
|
191
191
|
source = HarvestSourceFactory()
|
|
192
192
|
|
|
193
|
-
actions.reject_source(source
|
|
193
|
+
actions.reject_source(source, "comment")
|
|
194
194
|
|
|
195
195
|
source.reload()
|
|
196
196
|
assert source.validation.state == VALIDATION_REFUSED
|
|
@@ -200,20 +200,16 @@ class HarvestActionsTest:
|
|
|
200
200
|
|
|
201
201
|
def test_get_source_by_slug(self):
|
|
202
202
|
source = HarvestSourceFactory()
|
|
203
|
-
assert
|
|
203
|
+
assert HarvestSource.get(source.slug) == source
|
|
204
204
|
|
|
205
205
|
def test_get_source_by_id(self):
|
|
206
206
|
source = HarvestSourceFactory()
|
|
207
|
-
assert
|
|
208
|
-
|
|
209
|
-
def test_get_source_by_objectid(self):
|
|
210
|
-
source = HarvestSourceFactory()
|
|
211
|
-
assert actions.get_source(source.id) == source
|
|
207
|
+
assert HarvestSource.get(str(source.id)) == source
|
|
212
208
|
|
|
213
209
|
def test_delete_source_by_slug(self):
|
|
214
210
|
source = HarvestSourceFactory()
|
|
215
211
|
with assert_emit(signals.harvest_source_deleted):
|
|
216
|
-
deleted_source = actions.delete_source(source
|
|
212
|
+
deleted_source = actions.delete_source(source)
|
|
217
213
|
|
|
218
214
|
assert deleted_source.deleted is not None
|
|
219
215
|
assert deleted_source.id == source.id
|
|
@@ -223,7 +219,7 @@ class HarvestActionsTest:
|
|
|
223
219
|
def test_delete_source_by_id(self):
|
|
224
220
|
source = HarvestSourceFactory()
|
|
225
221
|
with assert_emit(signals.harvest_source_deleted):
|
|
226
|
-
deleted_source = actions.delete_source(
|
|
222
|
+
deleted_source = actions.delete_source(source)
|
|
227
223
|
|
|
228
224
|
assert deleted_source.deleted is not None
|
|
229
225
|
assert deleted_source.id == source.id
|
|
@@ -233,19 +229,18 @@ class HarvestActionsTest:
|
|
|
233
229
|
def test_delete_source_by_objectid(self):
|
|
234
230
|
source = HarvestSourceFactory()
|
|
235
231
|
with assert_emit(signals.harvest_source_deleted):
|
|
236
|
-
deleted_source = actions.delete_source(source
|
|
232
|
+
deleted_source = actions.delete_source(source)
|
|
237
233
|
|
|
238
234
|
assert deleted_source.deleted is not None
|
|
239
235
|
assert deleted_source.id == source.id
|
|
240
236
|
deleted_sources = HarvestSource.objects(deleted__exists=True)
|
|
241
237
|
assert len(deleted_sources) == 1
|
|
242
238
|
|
|
243
|
-
|
|
244
|
-
def test_clean_source(self, by_attr):
|
|
239
|
+
def test_clean_source(self):
|
|
245
240
|
source = HarvestSourceFactory()
|
|
246
241
|
for _ in range(5):
|
|
247
242
|
DatasetFactory(harvest=HarvestDatasetMetadata(source_id=str(source.id)))
|
|
248
|
-
actions.clean_source(
|
|
243
|
+
actions.clean_source(source)
|
|
249
244
|
datasets = Dataset.objects.filter(harvest__source_id=str(source.id))
|
|
250
245
|
assert len(datasets) == 5
|
|
251
246
|
for dataset in datasets:
|
|
@@ -262,7 +257,7 @@ class HarvestActionsTest:
|
|
|
262
257
|
def test_schedule(self):
|
|
263
258
|
source = HarvestSourceFactory()
|
|
264
259
|
with assert_emit(signals.harvest_source_scheduled):
|
|
265
|
-
source = actions.schedule(
|
|
260
|
+
source = actions.schedule(source, hour=0)
|
|
266
261
|
|
|
267
262
|
assert len(PeriodicTask.objects) == 1
|
|
268
263
|
periodic_task = source.periodic_task
|
|
@@ -279,7 +274,7 @@ class HarvestActionsTest:
|
|
|
279
274
|
def test_schedule_from_cron(self):
|
|
280
275
|
source = HarvestSourceFactory()
|
|
281
276
|
with assert_emit(signals.harvest_source_scheduled):
|
|
282
|
-
source = actions.schedule(
|
|
277
|
+
source = actions.schedule(source, "0 1 2 3 sunday")
|
|
283
278
|
|
|
284
279
|
assert len(PeriodicTask.objects) == 1
|
|
285
280
|
periodic_task = source.periodic_task
|
|
@@ -296,10 +291,10 @@ class HarvestActionsTest:
|
|
|
296
291
|
def test_reschedule(self):
|
|
297
292
|
source = HarvestSourceFactory()
|
|
298
293
|
with assert_emit(signals.harvest_source_scheduled):
|
|
299
|
-
source = actions.schedule(
|
|
294
|
+
source = actions.schedule(source, hour=0)
|
|
300
295
|
|
|
301
296
|
with assert_emit(signals.harvest_source_scheduled):
|
|
302
|
-
source = actions.schedule(
|
|
297
|
+
source = actions.schedule(source, minute=0)
|
|
303
298
|
|
|
304
299
|
assert len(PeriodicTask.objects) == 1
|
|
305
300
|
periodic_task = source.periodic_task
|
|
@@ -323,7 +318,7 @@ class HarvestActionsTest:
|
|
|
323
318
|
)
|
|
324
319
|
source = HarvestSourceFactory(periodic_task=periodic_task)
|
|
325
320
|
with assert_emit(signals.harvest_source_unscheduled):
|
|
326
|
-
actions.unschedule(
|
|
321
|
+
actions.unschedule(source)
|
|
327
322
|
|
|
328
323
|
source.reload()
|
|
329
324
|
assert len(PeriodicTask.objects) == 0
|
|
@@ -460,7 +455,7 @@ class ExecutionTestMixin(MockBackendsMixin):
|
|
|
460
455
|
org = OrganizationFactory()
|
|
461
456
|
source = HarvestSourceFactory(backend="factory", organization=org)
|
|
462
457
|
with assert_emit(signals.before_harvest_job, signals.after_harvest_job):
|
|
463
|
-
self.action(source
|
|
458
|
+
self.action(source)
|
|
464
459
|
|
|
465
460
|
source.reload()
|
|
466
461
|
assert len(HarvestJob.objects(source=source)) == 1
|
|
@@ -496,7 +491,7 @@ class ExecutionTestMixin(MockBackendsMixin):
|
|
|
496
491
|
|
|
497
492
|
source = HarvestSourceFactory(backend="factory")
|
|
498
493
|
with assert_emit(signals.before_harvest_job), mock_initialize.connected_to(init):
|
|
499
|
-
self.action(source
|
|
494
|
+
self.action(source)
|
|
500
495
|
|
|
501
496
|
source.reload()
|
|
502
497
|
assert len(HarvestJob.objects(source=source)) == 1
|
|
@@ -523,7 +518,7 @@ class ExecutionTestMixin(MockBackendsMixin):
|
|
|
523
518
|
assert_emit(signals.before_harvest_job, signals.after_harvest_job),
|
|
524
519
|
mock_process.connected_to(process),
|
|
525
520
|
):
|
|
526
|
-
self.action(source
|
|
521
|
+
self.action(source)
|
|
527
522
|
|
|
528
523
|
source.reload()
|
|
529
524
|
assert len(HarvestJob.objects(source=source)) == 1
|
|
@@ -559,7 +554,7 @@ class ExecutionTestMixin(MockBackendsMixin):
|
|
|
559
554
|
def test_empty(self):
|
|
560
555
|
source = HarvestSourceFactory(backend="factory", config={"count": 0})
|
|
561
556
|
with assert_emit(signals.before_harvest_job, signals.after_harvest_job):
|
|
562
|
-
self.action(source
|
|
557
|
+
self.action(source)
|
|
563
558
|
|
|
564
559
|
source.reload()
|
|
565
560
|
assert len(HarvestJob.objects(source=source)) == 1
|
|
@@ -580,7 +575,7 @@ class ExecutionTestMixin(MockBackendsMixin):
|
|
|
580
575
|
org = OrganizationFactory()
|
|
581
576
|
source = HarvestSourceFactory(backend="factory", organization=org, config={"count": 10})
|
|
582
577
|
|
|
583
|
-
self.action(source
|
|
578
|
+
self.action(source)
|
|
584
579
|
assert len(Dataset.objects) == 5
|
|
585
580
|
|
|
586
581
|
|
|
@@ -599,7 +594,7 @@ class HarvestPreviewTest(MockBackendsMixin):
|
|
|
599
594
|
org = OrganizationFactory()
|
|
600
595
|
source = HarvestSourceFactory(backend="factory", organization=org)
|
|
601
596
|
|
|
602
|
-
job = actions.preview(source
|
|
597
|
+
job = actions.preview(source)
|
|
603
598
|
|
|
604
599
|
assert job.status == "done"
|
|
605
600
|
assert job.errors == []
|
|
@@ -628,7 +623,7 @@ class HarvestPreviewTest(MockBackendsMixin):
|
|
|
628
623
|
org = OrganizationFactory()
|
|
629
624
|
source = HarvestSourceFactory(backend="factory", organization=org, config={"count": 10})
|
|
630
625
|
|
|
631
|
-
job = actions.preview(source
|
|
626
|
+
job = actions.preview(source)
|
|
632
627
|
|
|
633
628
|
assert len(job.items) == 5
|
|
634
629
|
|
|
@@ -639,7 +634,7 @@ class HarvestPreviewTest(MockBackendsMixin):
|
|
|
639
634
|
source = HarvestSourceFactory(backend="factory")
|
|
640
635
|
|
|
641
636
|
with mock_initialize.connected_to(init):
|
|
642
|
-
job = actions.preview(source
|
|
637
|
+
job = actions.preview(source)
|
|
643
638
|
|
|
644
639
|
assert job.status == "failed"
|
|
645
640
|
assert len(job.errors) == 1
|
|
@@ -660,7 +655,7 @@ class HarvestPreviewTest(MockBackendsMixin):
|
|
|
660
655
|
source = HarvestSourceFactory(backend="factory")
|
|
661
656
|
|
|
662
657
|
with mock_process.connected_to(process):
|
|
663
|
-
job = actions.preview(source
|
|
658
|
+
job = actions.preview(source)
|
|
664
659
|
|
|
665
660
|
assert job.status == "done-errors"
|
|
666
661
|
assert job.started is not None
|