You can contribute to extend the content of the Software Heritage archive by submitting an origin
save request. To do so, fill the required info in the form below:
Processing "save code now" request ...
A "Save code now" request takes the following parameters:
- Origin type: the type of version control system the software origin is using.
Currently, the supported types are:
- Origin url: the url of the remote repository for the software origin.
In order to avoid saving errors from Software Heritage, you should provide the clone/checkout url
as given by the provider hosting the software origin.
It can easily be found in the
web interface used to browse the software origin.
For instance, if you want to save a git
origin into the archive, you should check that the command $ git clone <origin_url>
does not return an error before submitting a request.
Once submitted, your save request can either be:
- accepted: a visit to the provided origin will then be scheduled by Software Heritage in order to
load its content into the archive as soon as possible
- rejected: the provided origin url is blacklisted and no visit will be scheduled
- put in pending state: a manual review will then be performed in order to determine if the
origin can be safely loaded or not into the archive
Once a save request has been accepted, you can follow its current status in the
submitted save requests list.
+
+ If you submitted requests while authenticated, you will be able
+ to only display your own requests.
Date |
Type |
Url |
Request |
Status |
Info |
|
{% endblock %}
\ No newline at end of file
diff --git a/swh/web/tests/api/views/test_origin_save.py b/swh/web/tests/api/views/test_origin_save.py
index 5034beb2..9573d33d 100644
--- a/swh/web/tests/api/views/test_origin_save.py
+++ b/swh/web/tests/api/views/test_origin_save.py
@@ -1,511 +1,530 @@
# Copyright (C) 2018-2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
from datetime import datetime, timedelta
import pytest
+from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.utils import timezone
from swh.web.auth.utils import SWH_AMBASSADOR_PERMISSION
from swh.web.common.models import (
SAVE_REQUEST_ACCEPTED,
SAVE_REQUEST_PENDING,
SAVE_REQUEST_REJECTED,
SAVE_TASK_FAILED,
SAVE_TASK_NOT_CREATED,
SAVE_TASK_NOT_YET_SCHEDULED,
SAVE_TASK_SCHEDULED,
SAVE_TASK_SUCCEEDED,
VISIT_STATUS_FAILED,
VISIT_STATUS_FULL,
SaveAuthorizedOrigin,
SaveOriginRequest,
SaveUnauthorizedOrigin,
)
from swh.web.common.typing import OriginExistenceCheckInfo
from swh.web.common.utils import reverse
from swh.web.settings.tests import save_origin_rate_post
from swh.web.tests.utils import (
check_api_get_responses,
check_api_post_response,
check_api_post_responses,
)
pytestmark = pytest.mark.django_db
@pytest.fixture(autouse=True)
def populated_db():
SaveAuthorizedOrigin.objects.create(url="https://github.com/"),
SaveAuthorizedOrigin.objects.create(url="https://gitlab.com/"),
SaveUnauthorizedOrigin.objects.create(url="https://github.com/user/illegal_repo")
SaveUnauthorizedOrigin.objects.create(url="https://gitlab.com/user_to_exclude")
def test_invalid_visit_type(api_client):
url = reverse(
"api-1-save-origin",
url_args={
"visit_type": "foo",
"origin_url": "https://github.com/torvalds/linux",
},
)
check_api_get_responses(api_client, url, status_code=400)
def test_invalid_origin_url(api_client):
url = reverse(
"api-1-save-origin", url_args={"visit_type": "git", "origin_url": "bar"}
)
check_api_get_responses(api_client, url, status_code=400)
def check_created_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status,
scheduler_task_status=None,
scheduler_task_run_status=None,
expected_task_status=None,
visit_date=None,
):
mock_scheduler = mocker.patch("swh.web.common.origin_save.scheduler")
mock_origin_exists = mocker.patch("swh.web.common.origin_save.origin_exists")
mock_origin_exists.return_value = OriginExistenceCheckInfo(
origin_url=origin_url, exists=True, last_modified=None, content_length=None
)
if scheduler_task_status is None:
mock_scheduler.get_tasks.return_value = []
else:
mock_scheduler.get_tasks.return_value = [
{
"priority": "high",
"policy": "oneshot",
"type": "load-git",
"arguments": {"kwargs": {"repo_url": origin_url}, "args": []},
"status": scheduler_task_status,
"id": 1,
}
]
if scheduler_task_run_status is None:
mock_scheduler.get_task_runs.return_value = []
else:
mock_scheduler.get_task_runs.return_value = [
{
"backend_id": "f00c712c-e820-41ce-a07c-9bf8df914205",
"ended": datetime.now(tz=timezone.utc) + timedelta(minutes=5),
"id": 1,
"metadata": {},
"scheduled": datetime.now(tz=timezone.utc),
"started": None,
"status": scheduler_task_run_status,
"task": 1,
}
]
mock_scheduler.create_tasks.return_value = [
{
"priority": "high",
"policy": "oneshot",
"type": "load-git",
"arguments": {"kwargs": {"repo_url": origin_url}, "args": []},
"status": "next_run_not_scheduled",
"id": 1,
}
]
url = reverse(
"api-1-save-origin", url_args={"visit_type": "git", "origin_url": origin_url}
)
mock_visit_date = mocker.patch(
("swh.web.common.origin_save._get_visit_info_for_save_request")
)
mock_visit_date.return_value = (visit_date, None)
if expected_request_status != SAVE_REQUEST_REJECTED:
response = check_api_post_responses(api_client, url, data=None, status_code=200)
assert response.data["save_request_status"] == expected_request_status
assert response.data["save_task_status"] == expected_task_status
else:
check_api_post_responses(api_client, url, data=None, status_code=403)
def check_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status,
expected_task_status,
scheduler_task_status="next_run_not_scheduled",
scheduler_task_run_status=None,
visit_date=None,
visit_status=None,
):
mock_scheduler = mocker.patch("swh.web.common.origin_save.scheduler")
mock_scheduler.get_tasks.return_value = [
{
"priority": "high",
"policy": "oneshot",
"type": "load-git",
"arguments": {"kwargs": {"repo_url": origin_url}, "args": []},
"status": scheduler_task_status,
"id": 1,
}
]
if scheduler_task_run_status is None:
mock_scheduler.get_task_runs.return_value = []
else:
mock_scheduler.get_task_runs.return_value = [
{
"backend_id": "f00c712c-e820-41ce-a07c-9bf8df914205",
"ended": datetime.now(tz=timezone.utc) + timedelta(minutes=5),
"id": 1,
"metadata": {},
"scheduled": datetime.now(tz=timezone.utc),
"started": None,
"status": scheduler_task_run_status,
"task": 1,
}
]
url = reverse(
"api-1-save-origin", url_args={"visit_type": "git", "origin_url": origin_url}
)
mock_visit_date = mocker.patch(
("swh.web.common.origin_save._get_visit_info_for_save_request")
)
mock_visit_date.return_value = (visit_date, visit_status)
response = check_api_get_responses(api_client, url, status_code=200)
save_request_data = response.data[0]
assert save_request_data["save_request_status"] == expected_request_status
assert save_request_data["save_task_status"] == expected_task_status
assert save_request_data["visit_status"] == visit_status
# Check that save task status is still available when
# the scheduler task has been archived
mock_scheduler.get_tasks.return_value = []
response = check_api_get_responses(api_client, url, status_code=200)
save_request_data = response.data[0]
assert save_request_data["save_task_status"] == expected_task_status
assert save_request_data["visit_status"] == visit_status
def test_save_request_rejected(api_client, mocker):
origin_url = "https://github.com/user/illegal_repo"
check_created_save_request_status(
api_client, mocker, origin_url, expected_request_status=SAVE_REQUEST_REJECTED,
)
check_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status=SAVE_REQUEST_REJECTED,
expected_task_status=SAVE_TASK_NOT_CREATED,
)
def test_save_request_pending(api_client, mocker):
origin_url = "https://unkwownforge.com/user/repo"
check_created_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status=SAVE_REQUEST_PENDING,
expected_task_status=SAVE_TASK_NOT_CREATED,
)
check_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status=SAVE_REQUEST_PENDING,
expected_task_status=SAVE_TASK_NOT_CREATED,
)
def test_save_request_succeed(api_client, mocker):
origin_url = "https://github.com/Kitware/CMake"
check_created_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_NOT_YET_SCHEDULED,
)
check_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_SCHEDULED,
scheduler_task_status="next_run_scheduled",
scheduler_task_run_status="scheduled",
)
check_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_SUCCEEDED,
scheduler_task_status="completed",
scheduler_task_run_status="eventful",
visit_date=None,
)
visit_date = datetime.now(tz=timezone.utc) + timedelta(hours=1)
check_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_SUCCEEDED,
scheduler_task_status="completed",
scheduler_task_run_status="eventful",
visit_date=visit_date,
visit_status=VISIT_STATUS_FULL,
)
def test_save_request_failed(api_client, mocker):
origin_url = "https://gitlab.com/inkscape/inkscape"
check_created_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_NOT_YET_SCHEDULED,
)
check_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_SCHEDULED,
scheduler_task_status="next_run_scheduled",
scheduler_task_run_status="scheduled",
)
check_save_request_status(
api_client,
mocker,
origin_url,
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_FAILED,
scheduler_task_status="disabled",
scheduler_task_run_status="failed",
visit_status=VISIT_STATUS_FAILED,
)
def test_create_save_request_only_when_needed(api_client, mocker):
origin_url = "https://github.com/webpack/webpack"
SaveOriginRequest.objects.create(
visit_type="git",
origin_url=origin_url,
status=SAVE_REQUEST_ACCEPTED,
loading_task_id=56,
)
check_created_save_request_status(
api_client,
mocker,
origin_url,
scheduler_task_status="next_run_not_scheduled",
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_NOT_YET_SCHEDULED,
)
sors = list(
SaveOriginRequest.objects.filter(visit_type="git", origin_url=origin_url)
)
assert len(sors) == 1
check_created_save_request_status(
api_client,
mocker,
origin_url,
scheduler_task_status="next_run_scheduled",
scheduler_task_run_status="scheduled",
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_SCHEDULED,
)
sors = list(
SaveOriginRequest.objects.filter(visit_type="git", origin_url=origin_url)
)
assert len(sors) == 1
visit_date = datetime.now(tz=timezone.utc) + timedelta(hours=1)
check_created_save_request_status(
api_client,
mocker,
origin_url,
scheduler_task_status="completed",
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_NOT_YET_SCHEDULED,
visit_date=visit_date,
)
sors = list(
SaveOriginRequest.objects.filter(visit_type="git", origin_url=origin_url)
)
# check_api_post_responses sends two POST requests to check YAML and JSON response
assert len(sors) == 3
check_created_save_request_status(
api_client,
mocker,
origin_url,
scheduler_task_status="disabled",
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_NOT_YET_SCHEDULED,
)
sors = list(
SaveOriginRequest.objects.filter(visit_type="git", origin_url=origin_url)
)
assert len(sors) == 5
def test_get_save_requests_unknown_origin(api_client):
unknown_origin_url = "https://gitlab.com/foo/bar"
url = reverse(
"api-1-save-origin",
url_args={"visit_type": "git", "origin_url": unknown_origin_url},
)
response = check_api_get_responses(api_client, url, status_code=404)
assert response.data == {
"exception": "NotFoundExc",
"reason": (
"No save requests found for visit of type git on origin with url %s."
)
% unknown_origin_url,
}
_visit_type = "git"
_origin_url = "https://github.com/python/cpython"
def test_save_requests_rate_limit(api_client, mocker):
create_save_origin_request = mocker.patch(
"swh.web.api.views.origin_save.create_save_origin_request"
)
def _save_request_dict(*args, **kwargs):
return {
"id": 1,
"visit_type": _visit_type,
"origin_url": _origin_url,
"save_request_date": datetime.now().isoformat(),
"save_request_status": SAVE_REQUEST_ACCEPTED,
"save_task_status": SAVE_TASK_NOT_YET_SCHEDULED,
"visit_date": None,
"visit_status": None,
}
create_save_origin_request.side_effect = _save_request_dict
url = reverse(
"api-1-save-origin",
url_args={"visit_type": _visit_type, "origin_url": _origin_url},
)
for _ in range(save_origin_rate_post):
check_api_post_response(api_client, url, status_code=200)
check_api_post_response(api_client, url, status_code=429)
def test_save_request_form_server_error(api_client, mocker):
create_save_origin_request = mocker.patch(
"swh.web.api.views.origin_save.create_save_origin_request"
)
create_save_origin_request.side_effect = Exception("Server error")
url = reverse(
"api-1-save-origin",
url_args={"visit_type": _visit_type, "origin_url": _origin_url},
)
check_api_post_responses(api_client, url, status_code=500)
@pytest.fixture
def origin_to_review():
return "https://git.example.org/user/project"
def test_create_save_request_pending_review_anonymous_user(
api_client, origin_to_review
):
url = reverse(
"api-1-save-origin",
url_args={"visit_type": "git", "origin_url": origin_to_review},
)
response = check_api_post_responses(api_client, url, status_code=200)
assert response.data["save_request_status"] == SAVE_REQUEST_PENDING
with pytest.raises(ObjectDoesNotExist):
SaveAuthorizedOrigin.objects.get(url=origin_to_review)
def test_create_save_request_accepted_ambassador_user(
api_client, origin_to_review, keycloak_oidc, mocker
):
keycloak_oidc.realm_permissions = [SWH_AMBASSADOR_PERMISSION]
oidc_profile = keycloak_oidc.login()
api_client.credentials(HTTP_AUTHORIZATION=f"Bearer {oidc_profile['refresh_token']}")
check_created_save_request_status(
api_client,
mocker,
origin_to_review,
expected_request_status=SAVE_REQUEST_ACCEPTED,
expected_task_status=SAVE_TASK_NOT_YET_SCHEDULED,
)
assert SaveAuthorizedOrigin.objects.get(url=origin_to_review)
def test_create_save_request_anonymous_user_no_user_id(api_client):
origin_url = "https://some.git.hosters/user/repo"
url = reverse(
"api-1-save-origin", url_args={"visit_type": "git", "origin_url": origin_url},
)
check_api_post_responses(api_client, url, status_code=200)
sor = SaveOriginRequest.objects.get(origin_url=origin_url)
- assert sor.user_id is None
+ assert sor.user_ids is None
def test_create_save_request_authenticated_user_id(
api_client, origin_to_review, keycloak_oidc, mocker
):
oidc_profile = keycloak_oidc.login()
api_client.credentials(HTTP_AUTHORIZATION=f"Bearer {oidc_profile['refresh_token']}")
origin_url = "https://some.git.hosters/user/repo2"
url = reverse(
"api-1-save-origin", url_args={"visit_type": "git", "origin_url": origin_url},
)
response = check_api_post_response(api_client, url, status_code=200)
assert response.wsgi_request.user.id is not None
user_id = str(response.wsgi_request.user.id)
- sor = SaveOriginRequest.objects.get(user_id=user_id)
- assert sor.user_id == user_id
+ sor = SaveOriginRequest.objects.get(user_ids=f'"{user_id}"')
+ assert sor.user_ids == f'"{user_id}"'
+
+
+def test_create_pending_save_request_multiple_authenticated_users(api_client):
+ origin_url = "https://some.git.hosters/user/repo3"
+ first_user = User.objects.create_user(username="first_user", password="")
+ second_user = User.objects.create_user(username="second_user", password="")
+ url = reverse(
+ "api-1-save-origin", url_args={"visit_type": "git", "origin_url": origin_url},
+ )
+
+ api_client.force_login(first_user)
+ check_api_post_response(api_client, url, status_code=200)
+
+ api_client.force_login(second_user)
+ check_api_post_response(api_client, url, status_code=200)
+
+ assert SaveOriginRequest.objects.get(user_ids__contains=f'"{first_user.id}"')
+ assert SaveOriginRequest.objects.get(user_ids__contains=f'"{second_user.id}"')
diff --git a/swh/web/tests/create_test_users.py b/swh/web/tests/create_test_users.py
new file mode 100644
index 00000000..dfdd24d6
--- /dev/null
+++ b/swh/web/tests/create_test_users.py
@@ -0,0 +1,16 @@
+# Copyright (C) 2021 The Software Heritage developers
+# See the AUTHORS file at the top-level directory of this distribution
+# License: GNU Affero General Public License version 3, or any later version
+# See top-level LICENSE file for more information
+
+
+from django.contrib.auth import get_user_model
+
+User = get_user_model()
+
+username = "user"
+password = "user"
+email = "user@swh-web.org"
+
+if not User.objects.filter(username=username).exists():
+ User.objects.create_user(username, email, password)
diff --git a/swh/web/tests/misc/test_origin_save.py b/swh/web/tests/misc/test_origin_save.py
index 399d594f..61222be6 100644
--- a/swh/web/tests/misc/test_origin_save.py
+++ b/swh/web/tests/misc/test_origin_save.py
@@ -1,96 +1,151 @@
# Copyright (C) 2019-2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
from datetime import datetime, timedelta, timezone
import json
import pytest
+from swh.auth.django.utils import oidc_user_from_profile
from swh.web.common.models import SaveOriginRequest
from swh.web.common.origin_save import SAVE_REQUEST_ACCEPTED, SAVE_TASK_SUCCEEDED
from swh.web.common.utils import reverse
from swh.web.tests.utils import check_http_get_response
def test_old_save_url_redirection(client):
url = reverse("browse-origin-save")
redirect_url = reverse("origin-save")
resp = check_http_get_response(client, url, status_code=302)
assert resp["location"] == redirect_url
@pytest.mark.django_db
-def test_save_origin_requests_list(client, mocker):
+def test_save_origin_requests_list(client, mocker, keycloak_oidc):
visit_types = ("git", "svn", "hg")
nb_origins_per_type = 10
for visit_type in visit_types:
for i in range(nb_origins_per_type):
SaveOriginRequest.objects.create(
request_date=datetime.now(tz=timezone.utc),
visit_type=visit_type,
origin_url=f"https://{visit_type}.example.org/project{i}",
status=SAVE_REQUEST_ACCEPTED,
visit_date=datetime.now(tz=timezone.utc) + timedelta(hours=1),
loading_task_id=i,
loading_task_status=SAVE_TASK_SUCCEEDED,
)
mock_scheduler = mocker.patch("swh.web.common.origin_save.scheduler")
mock_scheduler.get_tasks.return_value = []
mock_scheduler.get_task_runs.return_value = []
# retrieve all save requests in 3 pages, sorted in descending order
# of request creation
for i, visit_type in enumerate(reversed(visit_types)):
url = reverse(
"origin-save-requests-list",
url_args={"status": "all"},
query_params={
"draw": i + 1,
"search[value]": "",
"order[0][column]": "0",
"columns[0][name]": "request_date",
"order[0][dir]": "desc",
"length": nb_origins_per_type,
"start": i * nb_origins_per_type,
},
)
resp = check_http_get_response(
client, url, status_code=200, content_type="application/json"
)
sors = json.loads(resp.content.decode("utf-8"))
assert sors["draw"] == i + 1
assert sors["recordsFiltered"] == len(visit_types) * nb_origins_per_type
assert sors["recordsTotal"] == len(visit_types) * nb_origins_per_type
assert len(sors["data"]) == nb_origins_per_type
assert all(d["visit_type"] == visit_type for d in sors["data"])
# retrieve save requests filtered by visit type in a single page
for i, visit_type in enumerate(reversed(visit_types)):
url = reverse(
"origin-save-requests-list",
url_args={"status": "all"},
query_params={
"draw": i + 1,
"search[value]": visit_type,
"order[0][column]": "0",
"columns[0][name]": "request_date",
"order[0][dir]": "desc",
"length": nb_origins_per_type,
"start": 0,
},
)
resp = check_http_get_response(
client, url, status_code=200, content_type="application/json"
)
sors = json.loads(resp.content.decode("utf-8"))
assert sors["draw"] == i + 1
assert sors["recordsFiltered"] == nb_origins_per_type
assert sors["recordsTotal"] == len(visit_types) * nb_origins_per_type
assert len(sors["data"]) == nb_origins_per_type
assert all(d["visit_type"] == visit_type for d in sors["data"])
+
+
+@pytest.mark.django_db
+def test_save_origin_requests_list_user_filter(client, mocker, keycloak_oidc):
+
+ # anonymous user created a save request
+ sor = SaveOriginRequest.objects.create(
+ request_date=datetime.now(tz=timezone.utc),
+ visit_type="svn",
+ origin_url="https://svn.example.org/user/project",
+ status=SAVE_REQUEST_ACCEPTED,
+ visit_date=datetime.now(tz=timezone.utc) + timedelta(hours=1),
+ loading_task_id=1,
+ loading_task_status=SAVE_TASK_SUCCEEDED,
+ )
+
+ # authenticated user created a save request
+ user = oidc_user_from_profile(keycloak_oidc, keycloak_oidc.login())
+ client.login(code="", code_verifier="", redirect_uri="")
+
+ sor = SaveOriginRequest.objects.create(
+ request_date=datetime.now(tz=timezone.utc),
+ visit_type="git",
+ origin_url="https://git.example.org/user/project",
+ status=SAVE_REQUEST_ACCEPTED,
+ visit_date=datetime.now(tz=timezone.utc) + timedelta(hours=1),
+ loading_task_id=2,
+ loading_task_status=SAVE_TASK_SUCCEEDED,
+ user_ids=f'"{user.id}"',
+ )
+
+ # filter save requests according to user id
+ url = reverse(
+ "origin-save-requests-list",
+ url_args={"status": "all"},
+ query_params={
+ "draw": 1,
+ "search[value]": "",
+ "order[0][column]": "0",
+ "columns[0][name]": "request_date",
+ "order[0][dir]": "desc",
+ "length": 10,
+ "start": "0",
+ "user_requests_only": "1",
+ },
+ )
+
+ resp = check_http_get_response(
+ client, url, status_code=200, content_type="application/json"
+ )
+ sors = json.loads(resp.content.decode("utf-8"))
+ assert sors["recordsFiltered"] == 1
+ assert sors["recordsTotal"] == 2
+ assert sors["data"][0] == sor.to_dict()
diff --git a/swh/web/tests/test_migrations.py b/swh/web/tests/test_migrations.py
index d61a1d02..3cb68884 100644
--- a/swh/web/tests/test_migrations.py
+++ b/swh/web/tests/test_migrations.py
@@ -1,38 +1,53 @@
# Copyright (C) 2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU General Public License version 3, or any later version
# See top-level LICENSE file for more information
APP_NAME = "swh_web_common"
MIGRATION_0008 = "0008_save-code-now_indexes_20210106_1327"
MIGRATION_0009 = "0009_saveoriginrequest_visit_status"
MIGRATION_0010 = "0010_saveoriginrequest_user_id"
+MIGRATION_0011 = "0011_saveoriginrequest_user_ids"
def test_migrations_09_add_visit_status_to_sor_model(migrator):
"""Ensures the migration adds the visit_status field to SaveOriginRequest table"""
old_state = migrator.apply_initial_migration((APP_NAME, MIGRATION_0008),)
old_model = old_state.apps.get_model(APP_NAME, "SaveOriginRequest")
assert hasattr(old_model, "visit_status") is False
new_state = migrator.apply_tested_migration((APP_NAME, MIGRATION_0009))
new_model = new_state.apps.get_model(APP_NAME, "SaveOriginRequest")
assert hasattr(new_model, "visit_status") is True
def test_migrations_10_add_user_id_to_sor_model(migrator):
"""Ensures the migration adds the user_id field to SaveOriginRequest table"""
old_state = migrator.apply_initial_migration((APP_NAME, MIGRATION_0009),)
old_model = old_state.apps.get_model(APP_NAME, "SaveOriginRequest")
assert hasattr(old_model, "user_id") is False
new_state = migrator.apply_tested_migration((APP_NAME, MIGRATION_0010))
new_model = new_state.apps.get_model(APP_NAME, "SaveOriginRequest")
assert hasattr(new_model, "user_id") is True
+
+
+def test_migrations_11_add_user_ids_to_sor_model(migrator):
+ """Ensures the migration adds the user_id field to SaveOriginRequest table"""
+
+ old_state = migrator.apply_initial_migration((APP_NAME, MIGRATION_0010),)
+ old_model = old_state.apps.get_model(APP_NAME, "SaveOriginRequest")
+
+ assert hasattr(old_model, "user_ids") is False
+
+ new_state = migrator.apply_tested_migration((APP_NAME, MIGRATION_0011))
+ new_model = new_state.apps.get_model(APP_NAME, "SaveOriginRequest")
+
+ assert hasattr(new_model, "user_ids") is True