🚨(backend) fix linter warning too many lines

The linter was complaining about too many lines
in test_api_document_accesses.py. We split the
test file into two files to fix the warning.
We move as well the test_api_document tests to
the documents folder.
This commit is contained in:
Anthony LC
2024-08-16 10:23:20 +02:00
committed by Anthony LC
parent 143f1a02eb
commit a925b0bedf
4 changed files with 230 additions and 217 deletions

View File

@@ -0,0 +1,790 @@
"""
Test document accesses API endpoints for users in impress's core app.
"""
import random
from uuid import uuid4
import pytest
from rest_framework.test import APIClient
from core import factories, models
from core.api import serializers
from core.tests.conftest import TEAM, USER, VIA
pytestmark = pytest.mark.django_db
def test_api_document_accesses_list_anonymous():
"""Anonymous users should not be allowed to list document accesses."""
document = factories.DocumentFactory()
factories.UserDocumentAccessFactory.create_batch(2, document=document)
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/accesses/")
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_document_accesses_list_authenticated_unrelated():
"""
Authenticated users should not be allowed to list document accesses for a document
to which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
factories.UserDocumentAccessFactory.create_batch(3, document=document)
# Accesses for other documents to which the user is related should not be listed either
other_access = factories.UserDocumentAccessFactory(user=user)
factories.UserDocumentAccessFactory(document=other_access.document)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/accesses/",
)
assert response.status_code == 200
assert response.json() == {
"count": 0,
"next": None,
"previous": None,
"results": [],
}
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_list_authenticated_related(via, mock_user_get_teams):
"""
Authenticated users should be able to list document accesses for a document
to which they are directly related, whatever their role in the document.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
user_access = models.DocumentAccess.objects.create(
document=document,
user=user,
role=random.choice(models.RoleChoices.choices)[0],
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
user_access = models.DocumentAccess.objects.create(
document=document,
team="lasuite",
role=random.choice(models.RoleChoices.choices)[0],
)
access1 = factories.TeamDocumentAccessFactory(document=document)
access2 = factories.UserDocumentAccessFactory(document=document)
# Accesses for other documents to which the user is related should not be listed either
other_access = factories.UserDocumentAccessFactory(user=user)
factories.UserDocumentAccessFactory(document=other_access.document)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/accesses/",
)
access2_user = serializers.UserSerializer(instance=access2.user).data
base_user = serializers.UserSerializer(instance=user).data
assert response.status_code == 200
content = response.json()
assert len(content["results"]) == 3
assert sorted(content["results"], key=lambda x: x["id"]) == sorted(
[
{
"id": str(user_access.id),
"user": base_user if via == "user" else None,
"team": "lasuite" if via == "team" else "",
"role": user_access.role,
"abilities": user_access.get_abilities(user),
},
{
"id": str(access1.id),
"user": None,
"team": access1.team,
"role": access1.role,
"abilities": access1.get_abilities(user),
},
{
"id": str(access2.id),
"user": access2_user,
"team": "",
"role": access2.role,
"abilities": access2.get_abilities(user),
},
],
key=lambda x: x["id"],
)
def test_api_document_accesses_retrieve_anonymous():
"""
Anonymous users should not be allowed to retrieve a document access.
"""
access = factories.UserDocumentAccessFactory()
response = APIClient().get(
f"/api/v1.0/documents/{access.document_id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_document_accesses_retrieve_authenticated_unrelated():
"""
Authenticated users should not be allowed to retrieve a document access for
a document to which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
access = factories.UserDocumentAccessFactory(document=document)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 403
assert response.json() == {
"detail": "You do not have permission to perform this action."
}
# Accesses related to another document should be excluded even if the user is related to it
for access in [
factories.UserDocumentAccessFactory(),
factories.UserDocumentAccessFactory(user=user),
]:
response = client.get(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_retrieve_authenticated_related(via, mock_user_get_teams):
"""
A user who is related to a document should be allowed to retrieve the
associated document user accesses.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(document=document, team="lasuite")
access = factories.UserDocumentAccessFactory(document=document)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
access_user = serializers.UserSerializer(instance=access.user).data
assert response.status_code == 200
assert response.json() == {
"id": str(access.id),
"user": access_user,
"team": "",
"role": access.role,
"abilities": access.get_abilities(user),
}
def test_api_document_accesses_update_anonymous():
"""Anonymous users should not be allowed to update a document access."""
access = factories.UserDocumentAccessFactory()
old_values = serializers.DocumentAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"user": factories.UserFactory().id,
"role": random.choice(models.RoleChoices.choices)[0],
}
api_client = APIClient()
for field, value in new_values.items():
response = api_client.put(
f"/api/v1.0/documents/{access.document_id!s}/accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
assert response.status_code == 401
access.refresh_from_db()
updated_values = serializers.DocumentAccessSerializer(instance=access).data
assert updated_values == old_values
def test_api_document_accesses_update_authenticated_unrelated():
"""
Authenticated users should not be allowed to update a document access for a document to which
they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
access = factories.UserDocumentAccessFactory()
old_values = serializers.DocumentAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"user": factories.UserFactory().id,
"role": random.choice(models.RoleChoices.choices)[0],
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/documents/{access.document_id!s}/accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
assert response.status_code == 403
access.refresh_from_db()
updated_values = serializers.DocumentAccessSerializer(instance=access).data
assert updated_values == old_values
@pytest.mark.parametrize("role", ["reader", "editor"])
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_update_authenticated_reader_or_editor(
via, role, mock_user_get_teams
):
"""Readers or editors of a document should not be allowed to update its accesses."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role=role)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role=role
)
access = factories.UserDocumentAccessFactory(document=document)
old_values = serializers.DocumentAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"user": factories.UserFactory().id,
"role": random.choice(models.RoleChoices.choices)[0],
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/documents/{access.document_id!s}/accesses/{access.id!s}/",
{**old_values, field: value},
format="json",
)
assert response.status_code == 403
access.refresh_from_db()
updated_values = serializers.DocumentAccessSerializer(instance=access).data
assert updated_values == old_values
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_update_administrator_except_owner(
via, mock_user_get_teams
):
"""
A user who is a direct administrator in a document should be allowed to update a user
access for this document, as long as they don't try to set the role to owner.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(
document=document, user=user, role="administrator"
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="administrator"
)
access = factories.UserDocumentAccessFactory(
document=document,
role=random.choice(["administrator", "editor", "reader"]),
)
old_values = serializers.DocumentAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"user_id": factories.UserFactory().id,
"role": random.choice(["administrator", "editor", "reader"]),
}
for field, value in new_values.items():
new_data = {**old_values, field: value}
response = client.put(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
data=new_data,
format="json",
)
if (
new_data["role"] == old_values["role"]
): # we are not really updating the role
assert response.status_code == 403
else:
assert response.status_code == 200
access.refresh_from_db()
updated_values = serializers.DocumentAccessSerializer(instance=access).data
if field == "role":
assert updated_values == {**old_values, "role": new_values["role"]}
else:
assert updated_values == old_values
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_update_administrator_from_owner(
via, mock_user_get_teams
):
"""
A user who is an administrator in a document, should not be allowed to update
the user access of an "owner" for this document.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(
document=document, user=user, role="administrator"
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="administrator"
)
other_user = factories.UserFactory()
access = factories.UserDocumentAccessFactory(
document=document, user=other_user, role="owner"
)
old_values = serializers.DocumentAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"user_id": factories.UserFactory().id,
"role": random.choice(models.RoleChoices.choices)[0],
}
for field, value in new_values.items():
response = client.put(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
data={**old_values, field: value},
format="json",
)
assert response.status_code == 403
access.refresh_from_db()
updated_values = serializers.DocumentAccessSerializer(instance=access).data
assert updated_values == old_values
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_update_administrator_to_owner(via, mock_user_get_teams):
"""
A user who is an administrator in a document, should not be allowed to update
the user access of another user to grant document ownership.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(
document=document, user=user, role="administrator"
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="administrator"
)
other_user = factories.UserFactory()
access = factories.UserDocumentAccessFactory(
document=document,
user=other_user,
role=random.choice(["administrator", "editor", "reader"]),
)
old_values = serializers.DocumentAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"user_id": factories.UserFactory().id,
"role": "owner",
}
for field, value in new_values.items():
new_data = {**old_values, field: value}
response = client.put(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
data=new_data,
format="json",
)
# We are not allowed or not really updating the role
if field == "role" or new_data["role"] == old_values["role"]:
assert response.status_code == 403
else:
assert response.status_code == 200
access.refresh_from_db()
updated_values = serializers.DocumentAccessSerializer(instance=access).data
assert updated_values == old_values
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_update_owner(via, mock_user_get_teams):
"""
A user who is an owner in a document should be allowed to update
a user access for this document whatever the role.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="owner"
)
factories.UserFactory()
access = factories.UserDocumentAccessFactory(
document=document,
)
old_values = serializers.DocumentAccessSerializer(instance=access).data
new_values = {
"id": uuid4(),
"user_id": factories.UserFactory().id,
"role": random.choice(models.RoleChoices.choices)[0],
}
for field, value in new_values.items():
new_data = {**old_values, field: value}
response = client.put(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
data=new_data,
format="json",
)
if (
new_data["role"] == old_values["role"]
): # we are not really updating the role
assert response.status_code == 403
else:
assert response.status_code == 200
access.refresh_from_db()
updated_values = serializers.DocumentAccessSerializer(instance=access).data
if field == "role":
assert updated_values == {**old_values, "role": new_values["role"]}
else:
assert updated_values == old_values
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_update_owner_self(via, mock_user_get_teams):
"""
A user who is owner of a document should be allowed to update
their own user access provided there are other owners in the document.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
access = factories.UserDocumentAccessFactory(
document=document, user=user, role="owner"
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
access = factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="owner"
)
old_values = serializers.DocumentAccessSerializer(instance=access).data
new_role = random.choice(["administrator", "editor", "reader"])
response = client.put(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
data={**old_values, "role": new_role},
format="json",
)
assert response.status_code == 403
access.refresh_from_db()
assert access.role == "owner"
# Add another owner and it should now work
factories.UserDocumentAccessFactory(document=document, role="owner")
response = client.put(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
data={
**old_values,
"role": new_role,
"user_id": old_values.get("user", {}).get("id")
if old_values.get("user") is not None
else None,
},
format="json",
)
assert response.status_code == 200
access.refresh_from_db()
assert access.role == new_role
# Delete
def test_api_document_accesses_delete_anonymous():
"""Anonymous users should not be allowed to destroy a document access."""
access = factories.UserDocumentAccessFactory()
response = APIClient().delete(
f"/api/v1.0/documents/{access.document_id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 401
assert models.DocumentAccess.objects.count() == 1
def test_api_document_accesses_delete_authenticated():
"""
Authenticated users should not be allowed to delete a document access for a
document to which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
access = factories.UserDocumentAccessFactory()
response = client.delete(
f"/api/v1.0/documents/{access.document_id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 403
assert models.DocumentAccess.objects.count() == 1
@pytest.mark.parametrize("role", ["reader", "editor"])
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_delete_reader_or_editor(via, role, mock_user_get_teams):
"""
Authenticated users should not be allowed to delete a document access for a
document in which they are a simple reader or editor.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role=role)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role=role
)
access = factories.UserDocumentAccessFactory(document=document)
assert models.DocumentAccess.objects.count() == 2
assert models.DocumentAccess.objects.filter(user=access.user).exists()
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 403
assert models.DocumentAccess.objects.count() == 2
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_delete_administrators_except_owners(
via, mock_user_get_teams
):
"""
Users who are administrators in a document should be allowed to delete an access
from the document provided it is not ownership.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(
document=document, user=user, role="administrator"
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="administrator"
)
access = factories.UserDocumentAccessFactory(
document=document, role=random.choice(["reader", "editor", "administrator"])
)
assert models.DocumentAccess.objects.count() == 2
assert models.DocumentAccess.objects.filter(user=access.user).exists()
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 204
assert models.DocumentAccess.objects.count() == 1
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_delete_administrator_on_owners(via, mock_user_get_teams):
"""
Users who are administrators in a document should not be allowed to delete an ownership
access from the document.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(
document=document, user=user, role="administrator"
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="administrator"
)
access = factories.UserDocumentAccessFactory(document=document, role="owner")
assert models.DocumentAccess.objects.count() == 2
assert models.DocumentAccess.objects.filter(user=access.user).exists()
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 403
assert models.DocumentAccess.objects.count() == 2
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_delete_owners(via, mock_user_get_teams):
"""
Users should be able to delete the document access of another user
for a document of which they are owner.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="owner"
)
access = factories.UserDocumentAccessFactory(document=document)
assert models.DocumentAccess.objects.count() == 2
assert models.DocumentAccess.objects.filter(user=access.user).exists()
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 204
assert models.DocumentAccess.objects.count() == 1
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_delete_owners_last_owner(via, mock_user_get_teams):
"""
It should not be possible to delete the last owner access from a document
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
access = factories.UserDocumentAccessFactory(
document=document, user=user, role="owner"
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
access = factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="owner"
)
assert models.DocumentAccess.objects.count() == 1
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/accesses/{access.id!s}/",
)
assert response.status_code == 403
assert models.DocumentAccess.objects.count() == 1

View File

@@ -0,0 +1,230 @@
"""
Test document accesses API endpoints for users in impress's core app.
"""
import random
from django.core import mail
import pytest
from rest_framework.test import APIClient
from core import factories, models
from core.api import serializers
from core.tests.conftest import TEAM, USER, VIA
pytestmark = pytest.mark.django_db
def test_api_document_accesses_create_anonymous():
"""Anonymous users should not be allowed to create document accesses."""
user = factories.UserFactory()
document = factories.DocumentFactory()
response = APIClient().post(
f"/api/v1.0/documents/{document.id!s}/accesses/",
{
"user": str(user.id),
"document": str(document.id),
"role": random.choice(models.RoleChoices.choices)[0],
},
format="json",
)
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
assert models.DocumentAccess.objects.exists() is False
def test_api_document_accesses_create_authenticated_unrelated():
"""
Authenticated users should not be allowed to create document accesses for a document to
which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
other_user = factories.UserFactory()
document = factories.DocumentFactory()
response = client.post(
f"/api/v1.0/documents/{document.id!s}/accesses/",
{
"user": str(other_user.id),
},
format="json",
)
assert response.status_code == 403
assert not models.DocumentAccess.objects.filter(user=other_user).exists()
@pytest.mark.parametrize("role", ["reader", "editor"])
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_create_authenticated_reader_or_editor(
via, role, mock_user_get_teams
):
"""Readers or editors of a document should not be allowed to create document accesses."""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role=role)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role=role
)
other_user = factories.UserFactory()
for new_role in [role[0] for role in models.RoleChoices.choices]:
response = client.post(
f"/api/v1.0/documents/{document.id!s}/accesses/",
{
"user": str(other_user.id),
"role": new_role,
},
format="json",
)
assert response.status_code == 403
assert not models.DocumentAccess.objects.filter(user=other_user).exists()
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_create_authenticated_administrator(
via, mock_user_get_teams
):
"""
Administrators of a document should be able to create document accesses
except for the "owner" role.
An email should be sent to the accesses to notify them of the adding.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(
document=document, user=user, role="administrator"
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="administrator"
)
other_user = factories.UserFactory()
# It should not be allowed to create an owner access
response = client.post(
f"/api/v1.0/documents/{document.id!s}/accesses/",
{
"user": str(other_user.id),
"role": "owner",
},
format="json",
)
assert response.status_code == 403
assert response.json() == {
"detail": "Only owners of a resource can assign other users as owners."
}
# It should be allowed to create a lower access
role = random.choice(
[role[0] for role in models.RoleChoices.choices if role[0] != "owner"]
)
assert len(mail.outbox) == 0
response = client.post(
f"/api/v1.0/documents/{document.id!s}/accesses/",
{
"user_id": str(other_user.id),
"role": role,
},
format="json",
)
assert response.status_code == 201
assert models.DocumentAccess.objects.filter(user=other_user).count() == 1
new_document_access = models.DocumentAccess.objects.filter(user=other_user).get()
other_user = serializers.UserSerializer(instance=other_user).data
assert response.json() == {
"abilities": new_document_access.get_abilities(user),
"id": str(new_document_access.id),
"team": "",
"role": role,
"user": other_user,
}
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert email.to == [other_user["email"]]
email_content = " ".join(email.body.split())
assert "Invitation to join Docs!" in email_content
assert "docs/" + str(document.id) + "/" in email_content
@pytest.mark.parametrize("via", VIA)
def test_api_document_accesses_create_authenticated_owner(via, mock_user_get_teams):
"""
Owners of a document should be able to create document accesses whatever the role.
An email should be sent to the accesses to notify them of the adding.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role="owner"
)
other_user = factories.UserFactory()
role = random.choice([role[0] for role in models.RoleChoices.choices])
assert len(mail.outbox) == 0
response = client.post(
f"/api/v1.0/documents/{document.id!s}/accesses/",
{
"user_id": str(other_user.id),
"role": role,
},
format="json",
)
assert response.status_code == 201
assert models.DocumentAccess.objects.filter(user=other_user).count() == 1
new_document_access = models.DocumentAccess.objects.filter(user=other_user).get()
other_user = serializers.UserSerializer(instance=other_user).data
assert response.json() == {
"id": str(new_document_access.id),
"user": other_user,
"team": "",
"role": role,
"abilities": new_document_access.get_abilities(user),
}
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert email.to == [other_user["email"]]
email_content = " ".join(email.body.split())
assert "Invitation to join Docs!" in email_content
assert "docs/" + str(document.id) + "/" in email_content

View File

@@ -0,0 +1,589 @@
"""
Unit tests for the Invitation model
"""
import random
import time
from django.core import mail
import pytest
from rest_framework import status
from rest_framework.test import APIClient
from core import factories, models
from core.tests.conftest import TEAM, USER, VIA
pytestmark = pytest.mark.django_db
def test_api_document_invitations__create__anonymous():
"""Anonymous users should not be able to create invitations."""
document = factories.DocumentFactory()
invitation_values = {
"email": "guest@example.com",
"role": random.choice(models.RoleChoices.choices)[0],
}
response = APIClient().post(
f"/api/v1.0/documents/{document.id}/invitations/",
invitation_values,
format="json",
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_document_invitations__create__authenticated_outsider():
"""Users outside of document should not be permitted to invite to document."""
user = factories.UserFactory()
document = factories.DocumentFactory()
invitation_values = {
"email": "guest@example.com",
"role": random.choice(models.RoleChoices.choices)[0],
}
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id}/invitations/",
invitation_values,
format="json",
)
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.parametrize(
"inviting,invited,is_allowed",
(
["reader", "reader", False],
["reader", "editor", False],
["reader", "administrator", False],
["reader", "owner", False],
["editor", "reader", False],
["editor", "editor", False],
["editor", "administrator", False],
["editor", "owner", False],
["administrator", "reader", True],
["administrator", "editor", True],
["administrator", "administrator", True],
["administrator", "owner", False],
["owner", "reader", True],
["owner", "editor", True],
["owner", "administrator", True],
["owner", "owner", True],
),
)
@pytest.mark.parametrize("via", VIA)
def test_api_document_invitations__create__privileged_members(
via, inviting, invited, is_allowed, mock_user_get_teams
):
"""
Only owners and administrators should be able to invite new users.
Only owners can invite owners.
"""
user = factories.UserFactory()
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role=inviting)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role=inviting
)
invitation_values = {
"email": "guest@example.com",
"role": invited,
}
assert len(mail.outbox) == 0
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id}/invitations/",
invitation_values,
format="json",
)
if is_allowed:
assert response.status_code == status.HTTP_201_CREATED
assert models.Invitation.objects.count() == 1
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert email.to == ["guest@example.com"]
email_content = " ".join(email.body.split())
assert "Invitation to join Docs!" in email_content
else:
assert response.status_code == status.HTTP_403_FORBIDDEN
assert models.Invitation.objects.exists() is False
def test_api_document_invitations__create__email_from_content_language():
"""
The email generated is from the language set in the Content-Language header
"""
user = factories.UserFactory()
document = factories.DocumentFactory()
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")
invitation_values = {
"email": "guest@example.com",
"role": "reader",
}
assert len(mail.outbox) == 0
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id}/invitations/",
invitation_values,
format="json",
headers={"Content-Language": "fr-fr"},
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["email"] == "guest@example.com"
assert models.Invitation.objects.count() == 1
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert email.to == ["guest@example.com"]
email_content = " ".join(email.body.split())
assert "Invitation à rejoindre Docs !" in email_content
def test_api_document_invitations__create__email_from_content_language_not_supported():
"""
If the language from the Content-Language is not supported
it will display the default language, English.
"""
user = factories.UserFactory()
document = factories.DocumentFactory()
factories.UserDocumentAccessFactory(document=document, user=user, role="owner")
invitation_values = {
"email": "guest@example.com",
"role": "reader",
}
assert len(mail.outbox) == 0
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id}/invitations/",
invitation_values,
format="json",
headers={"Content-Language": "not-supported"},
)
assert response.status_code == status.HTTP_201_CREATED
assert response.json()["email"] == "guest@example.com"
assert models.Invitation.objects.count() == 1
assert len(mail.outbox) == 1
email = mail.outbox[0]
assert email.to == ["guest@example.com"]
email_content = " ".join(email.body.split())
assert "Invitation to join Docs!" in email_content
def test_api_document_invitations__create__issuer_and_document_override():
"""It should not be possible to set the "document" and "issuer" fields."""
user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, "owner")])
other_document = factories.DocumentFactory(users=[(user, "owner")])
invitation_values = {
"document": str(other_document.id),
"issuer": str(factories.UserFactory().id),
"email": "guest@example.com",
"role": random.choice(models.RoleChoices.choices)[0],
}
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id}/invitations/",
invitation_values,
format="json",
)
assert response.status_code == status.HTTP_201_CREATED
# document and issuer automatically set
assert response.json()["document"] == str(document.id)
assert response.json()["issuer"] == str(user.id)
def test_api_document_invitations__create__cannot_duplicate_invitation():
"""An email should not be invited multiple times to the same document."""
existing_invitation = factories.InvitationFactory()
document = existing_invitation.document
# Grant privileged role on the Document to the user
user = factories.UserFactory()
models.DocumentAccess.objects.create(
document=document, user=user, role="administrator"
)
# Create a new invitation to the same document with the exact same email address
invitation_values = {
"email": existing_invitation.email,
"role": random.choice(["administrator", "editor", "reader"]),
}
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id}/invitations/",
invitation_values,
format="json",
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["__all__"] == [
"Document invitation with this Email address and Document already exists."
]
def test_api_document_invitations__create__cannot_invite_existing_users():
"""
It should not be possible to invite already existing users.
"""
user = factories.UserFactory()
document = factories.DocumentFactory(users=[(user, "owner")])
existing_user = factories.UserFactory()
# Build an invitation to the email of an exising identity in the db
invitation_values = {
"email": existing_user.email,
"role": random.choice(models.RoleChoices.choices)[0],
}
client = APIClient()
client.force_login(user)
response = client.post(
f"/api/v1.0/documents/{document.id}/invitations/",
invitation_values,
format="json",
)
assert response.status_code == status.HTTP_400_BAD_REQUEST
assert response.json()["email"] == [
"This email is already associated to a registered user."
]
def test_api_document_invitations__list__anonymous_user():
"""Anonymous users should not be able to list invitations."""
document = factories.DocumentFactory()
response = APIClient().get(f"/api/v1.0/documents/{document.id}/invitations/")
assert response.status_code == status.HTTP_401_UNAUTHORIZED
@pytest.mark.parametrize("via", VIA)
def test_api_document_invitations__list__authenticated(
via, mock_user_get_teams, django_assert_num_queries
):
"""
Authenticated users should be able to list invitations for documents to which they are
related, whatever the role and including invitations issued by other users.
"""
user = factories.UserFactory()
other_user = factories.UserFactory()
document = factories.DocumentFactory()
role = random.choice(models.RoleChoices.choices)[0]
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role=role)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role=role
)
invitation = factories.InvitationFactory(
document=document, role="administrator", issuer=user
)
other_invitations = factories.InvitationFactory.create_batch(
2, document=document, role="reader", issuer=other_user
)
# invitations from other documents should not be listed
other_document = factories.DocumentFactory()
factories.InvitationFactory.create_batch(2, document=other_document, role="reader")
client = APIClient()
client.force_login(user)
with django_assert_num_queries(3):
response = client.get(
f"/api/v1.0/documents/{document.id}/invitations/",
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["count"] == 3
assert sorted(response.json()["results"], key=lambda x: x["created_at"]) == sorted(
[
{
"id": str(i.id),
"created_at": i.created_at.isoformat().replace("+00:00", "Z"),
"email": str(i.email),
"document": str(document.id),
"role": i.role,
"issuer": str(i.issuer.id),
"is_expired": False,
"abilities": {
"destroy": role in ["administrator", "owner"],
"update": False,
"partial_update": False,
"retrieve": True,
},
}
for i in [invitation, *other_invitations]
],
key=lambda x: x["created_at"],
)
def test_api_document_invitations__list__expired_invitations_still_listed(settings):
"""
Expired invitations are still listed.
"""
user = factories.UserFactory()
other_user = factories.UserFactory()
document = factories.DocumentFactory(
users=[(user, "administrator"), (other_user, "owner")]
)
# override settings to accelerate validation expiration
settings.INVITATION_VALIDITY_DURATION = 1 # second
expired_invitation = factories.InvitationFactory(
document=document,
role="reader",
issuer=user,
)
time.sleep(1)
client = APIClient()
client.force_login(user)
response = client.get(
f"/api/v1.0/documents/{document.id}/invitations/",
)
assert response.status_code == status.HTTP_200_OK
assert response.json()["count"] == 1
assert sorted(response.json()["results"], key=lambda x: x["created_at"]) == sorted(
[
{
"id": str(expired_invitation.id),
"created_at": expired_invitation.created_at.isoformat().replace(
"+00:00", "Z"
),
"email": str(expired_invitation.email),
"document": str(document.id),
"role": expired_invitation.role,
"issuer": str(expired_invitation.issuer.id),
"is_expired": True,
"abilities": {
"destroy": True,
"update": False,
"partial_update": False,
"retrieve": True,
},
},
],
key=lambda x: x["created_at"],
)
def test_api_document_invitations__retrieve__anonymous_user():
"""
Anonymous users should not be able to retrieve invitations.
"""
invitation = factories.InvitationFactory()
response = APIClient().get(
f"/api/v1.0/documents/{invitation.document.id}/invitations/{invitation.id}/",
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_api_document_invitations__retrieve__unrelated_user():
"""
Authenticated unrelated users should not be able to retrieve invitations.
"""
user = factories.UserFactory()
invitation = factories.InvitationFactory()
client = APIClient()
client.force_login(user)
response = client.get(
f"/api/v1.0/documents/{invitation.document.id!s}/invitations/{invitation.id!s}/",
)
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.parametrize("via", VIA)
def test_api_document_invitations__retrieve__document_member(via, mock_user_get_teams):
"""
Authenticated users related to the document should be able to retrieve invitations
whatever their role in the document.
"""
user = factories.UserFactory()
invitation = factories.InvitationFactory()
role = random.choice(models.RoleChoices.choices)[0]
if via == USER:
factories.UserDocumentAccessFactory(
document=invitation.document, user=user, role=role
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=invitation.document, team="lasuite", role=role
)
client = APIClient()
client.force_login(user)
response = client.get(
f"/api/v1.0/documents/{invitation.document.id}/invitations/{invitation.id}/",
)
assert response.status_code == status.HTTP_200_OK
assert response.json() == {
"id": str(invitation.id),
"created_at": invitation.created_at.isoformat().replace("+00:00", "Z"),
"email": invitation.email,
"document": str(invitation.document.id),
"role": str(invitation.role),
"issuer": str(invitation.issuer.id),
"is_expired": False,
"abilities": {
"destroy": role in ["administrator", "owner"],
"update": False,
"partial_update": False,
"retrieve": True,
},
}
@pytest.mark.parametrize("via", VIA)
@pytest.mark.parametrize(
"method",
["put", "patch"],
)
def test_api_document_invitations__update__forbidden(method, via, mock_user_get_teams):
"""
Update of invitations is currently forbidden.
"""
user = factories.UserFactory()
invitation = factories.InvitationFactory()
if via == USER:
factories.UserDocumentAccessFactory(
document=invitation.document, user=user, role="owner"
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=invitation.document, team="lasuite", role="owner"
)
client = APIClient()
client.force_login(user)
url = f"/api/v1.0/documents/{invitation.document.id}/invitations/{invitation.id}/"
if method == "put":
response = client.put(url)
if method == "patch":
response = client.patch(url)
assert response.status_code == status.HTTP_405_METHOD_NOT_ALLOWED
assert response.json()["detail"] == f'Method "{method.upper()}" not allowed.'
def test_api_document_invitations__delete__anonymous():
"""Anonymous user should not be able to delete invitations."""
invitation = factories.InvitationFactory()
response = APIClient().delete(
f"/api/v1.0/documents/{invitation.document.id}/invitations/{invitation.id}/",
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
def test_api_document_invitations__delete__authenticated_outsider():
"""Members unrelated to a document should not be allowed to cancel invitations."""
user = factories.UserFactory()
document = factories.DocumentFactory()
invitation = factories.InvitationFactory(document=document)
client = APIClient()
client.force_login(user)
response = client.delete(
f"/api/v1.0/documents/{document.id}/invitations/{invitation.id}/",
)
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.parametrize("via", VIA)
@pytest.mark.parametrize("role", ["owner", "administrator"])
def test_api_document_invitations__delete__privileged_members(
role, via, mock_user_get_teams
):
"""Privileged member should be able to cancel invitation."""
user = factories.UserFactory()
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role=role)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role=role
)
invitation = factories.InvitationFactory(document=document)
client = APIClient()
client.force_login(user)
response = client.delete(
f"/api/v1.0/documents/{document.id}/invitations/{invitation.id}/",
)
assert response.status_code == status.HTTP_204_NO_CONTENT
@pytest.mark.parametrize("role", ["reader", "editor"])
@pytest.mark.parametrize("via", VIA)
def test_api_document_invitations_delete_readers_or_editors(
via, role, mock_user_get_teams
):
"""Readers or editors should not be able to cancel invitation."""
user = factories.UserFactory()
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role=role)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role=role
)
invitation = factories.InvitationFactory(document=document)
client = APIClient()
client.force_login(user)
response = client.delete(
f"/api/v1.0/documents/{document.id}/invitations/{invitation.id}/",
)
assert response.status_code == status.HTTP_403_FORBIDDEN
assert (
response.json()["detail"]
== "You do not have permission to perform this action."
)

View File

@@ -0,0 +1,527 @@
"""
Test document versions API endpoints for users in impress's core app.
"""
import random
import time
import pytest
from rest_framework.test import APIClient
from core import factories, models
from core.tests.conftest import TEAM, USER, VIA
pytestmark = pytest.mark.django_db
def test_api_document_versions_list_anonymous_public():
"""
Anonymous users should not be allowed to list document versions for a public document.
"""
document = factories.DocumentFactory(is_public=True)
factories.UserDocumentAccessFactory.create_batch(2, document=document)
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/versions/")
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_document_versions_list_anonymous_private():
"""
Anonymous users should not be allowed to find document versions for a private document.
"""
document = factories.DocumentFactory(is_public=False)
factories.UserDocumentAccessFactory.create_batch(2, document=document)
response = APIClient().get(f"/api/v1.0/documents/{document.id!s}/versions/")
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
def test_api_document_versions_list_authenticated_unrelated_public():
"""
Authenticated users should not be allowed to list document versions for a public document
to which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory(is_public=True)
factories.UserDocumentAccessFactory.create_batch(3, document=document)
# The versions of another document to which the user is related should not be listed either
factories.UserDocumentAccessFactory(user=user)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/versions/",
)
assert response.status_code == 403
assert response.json() == {
"detail": "You do not have permission to perform this action."
}
def test_api_document_versions_list_authenticated_unrelated_private():
"""
Authenticated users should not be allowed to find document versions for a private document
to which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory(is_public=False)
factories.UserDocumentAccessFactory.create_batch(3, document=document)
# The versions of another document to which the user is related should not be listed either
factories.UserDocumentAccessFactory(user=user)
response = client.get(
f"/api/v1.0/documents/{document.id!s}/versions/",
)
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
@pytest.mark.parametrize("via", VIA)
def test_api_document_versions_list_authenticated_related(via, mock_user_get_teams):
"""
Authenticated users should be able to list document versions for a document
to which they are directly related, whatever their role in the document.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
models.DocumentAccess.objects.create(
document=document,
user=user,
role=random.choice(models.RoleChoices.choices)[0],
)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
models.DocumentAccess.objects.create(
document=document,
team="lasuite",
role=random.choice(models.RoleChoices.choices)[0],
)
# Other versions of documents to which the user has access should not be listed
factories.UserDocumentAccessFactory(user=user)
# A version created before the user got access should be hidden
response = client.get(
f"/api/v1.0/documents/{document.id!s}/versions/",
)
assert response.status_code == 200
content = response.json()
assert len(content["results"]) == 0
assert content["count"] == 0
# Add a new version to the document
document.content = "new content"
document.save()
response = client.get(
f"/api/v1.0/documents/{document.id!s}/versions/",
)
assert response.status_code == 200
content = response.json()
assert len(content["results"]) == 1
assert content["count"] == 1
def test_api_document_versions_retrieve_anonymous_public():
"""
Anonymous users should not be allowed to retrieve specific versions for a public document.
"""
document = factories.DocumentFactory(is_public=True)
version_id = document.get_versions_slice()["versions"][0]["version_id"]
url = f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/"
response = APIClient().get(url)
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_document_versions_retrieve_anonymous_private():
"""
Anonymous users should not be allowed to find specific versions for a private document.
"""
document = factories.DocumentFactory(is_public=False)
version_id = document.get_versions_slice()["versions"][0]["version_id"]
url = f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/"
response = APIClient().get(url)
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
def test_api_document_versions_retrieve_authenticated_unrelated_public():
"""
Authenticated users should not be allowed to retrieve specific versions for a public
document to which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory(is_public=True)
version_id = document.get_versions_slice()["versions"][0]["version_id"]
response = client.get(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/",
)
assert response.status_code == 403
assert response.json() == {
"detail": "You do not have permission to perform this action."
}
def test_api_document_versions_retrieve_authenticated_unrelated_private():
"""
Authenticated users should not be allowed to find specific versions for a private document
to which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory(is_public=False)
version_id = document.get_versions_slice()["versions"][0]["version_id"]
response = client.get(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/",
)
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
@pytest.mark.parametrize("via", VIA)
def test_api_document_versions_retrieve_authenticated_related(via, mock_user_get_teams):
"""
A user who is related to a document should be allowed to retrieve the
associated document user accesses.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
version_id = document.get_versions_slice()["versions"][0]["version_id"]
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(document=document, team="lasuite")
# Versions created before the document was shared should not be available to the user
response = client.get(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/",
)
assert response.status_code == 404
# Create a new version should make it available to the user
time.sleep(1) # minio stores datetimes with the precision of a second
document.content = "new content"
document.save()
version_id = document.get_versions_slice()["versions"][0]["version_id"]
response = client.get(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/",
)
assert response.status_code == 200
assert response.json()["content"] == "new content"
def test_api_document_versions_create_anonymous():
"""Anonymous users should not be allowed to create document versions."""
document = factories.DocumentFactory()
response = APIClient().post(
f"/api/v1.0/documents/{document.id!s}/versions/",
{"foo": "bar"},
format="json",
)
assert response.status_code == 401
assert response.json() == {
"detail": "Authentication credentials were not provided."
}
def test_api_document_versions_create_authenticated_unrelated():
"""
Authenticated users should not be allowed to create document versions for a document to
which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
response = client.post(
f"/api/v1.0/documents/{document.id!s}/versions/",
{"foo": "bar"},
format="json",
)
assert response.status_code == 405
@pytest.mark.parametrize("via", VIA)
def test_api_document_versions_create_authenticated_related(via, mock_user_get_teams):
"""
Authenticated users related to a document should not be allowed to create document versions
whatever their role.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(document=document, team="lasuite")
response = client.post(
f"/api/v1.0/documents/{document.id!s}/versions/",
{"foo": "bar"},
format="json",
)
assert response.status_code == 405
def test_api_document_versions_update_anonymous():
"""Anonymous users should not be allowed to update a document version."""
access = factories.UserDocumentAccessFactory()
version_id = access.document.get_versions_slice()["versions"][0]["version_id"]
response = APIClient().put(
f"/api/v1.0/documents/{access.document_id!s}/versions/{version_id:s}/",
{"foo": "bar"},
format="json",
)
assert response.status_code == 401
def test_api_document_versions_update_authenticated_unrelated():
"""
Authenticated users should not be allowed to update a document version for a document to which
they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
access = factories.UserDocumentAccessFactory()
version_id = access.document.get_versions_slice()["versions"][0]["version_id"]
response = client.put(
f"/api/v1.0/documents/{access.document_id!s}/versions/{version_id:s}/",
{"foo": "bar"},
format="json",
)
assert response.status_code == 405
@pytest.mark.parametrize("via", VIA)
def test_api_document_versions_update_authenticated_related(via, mock_user_get_teams):
"""
Authenticated users with access to a document should not be able to update its versions
whatever their role.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
version_id = document.get_versions_slice()["versions"][0]["version_id"]
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(document=document, team="lasuite")
response = client.put(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id!s}/",
{"foo": "bar"},
format="json",
)
assert response.status_code == 405
# Delete
def test_api_document_versions_delete_anonymous():
"""Anonymous users should not be allowed to destroy a document version."""
access = factories.UserDocumentAccessFactory()
response = APIClient().delete(
f"/api/v1.0/documents/{access.document_id!s}/versions/{access.id!s}/",
)
assert response.status_code == 401
def test_api_document_versions_delete_authenticated_public():
"""
Authenticated users should not be allowed to delete a document version for a
public document to which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory(is_public=True)
version_id = document.get_versions_slice()["versions"][0]["version_id"]
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/",
)
assert response.status_code == 403
def test_api_document_versions_delete_authenticated_private():
"""
Authenticated users should not be allowed to find a document version to delete it
for a private document to which they are not related.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory(is_public=False)
version_id = document.get_versions_slice()["versions"][0]["version_id"]
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/",
)
assert response.status_code == 404
assert response.json() == {"detail": "Not found."}
@pytest.mark.parametrize("role", ["reader", "editor"])
@pytest.mark.parametrize("via", VIA)
def test_api_document_versions_delete_reader_or_editor(via, role, mock_user_get_teams):
"""
Authenticated users should not be allowed to delete a document version for a
document in which they are a simple reader or editor.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role=role)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role=role
)
# Create a new version should make it available to the user
time.sleep(1) # minio stores datetimes with the precision of a second
document.content = "new content"
document.save()
versions = document.get_versions_slice()["versions"]
assert len(versions) == 2
version_id = versions[1]["version_id"]
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/",
)
assert response.status_code == 403
version_id = versions[0]["version_id"]
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/",
)
assert response.status_code == 403
versions = document.get_versions_slice()["versions"]
assert len(versions) == 2
@pytest.mark.parametrize("via", VIA)
def test_api_document_versions_delete_administrator_or_owner(via, mock_user_get_teams):
"""
Users who are administrator or owner of a document should be allowed to delete a version.
"""
user = factories.UserFactory()
client = APIClient()
client.force_login(user)
document = factories.DocumentFactory()
role = random.choice(["administrator", "owner"])
if via == USER:
factories.UserDocumentAccessFactory(document=document, user=user, role=role)
elif via == TEAM:
mock_user_get_teams.return_value = ["lasuite", "unknown"]
factories.TeamDocumentAccessFactory(
document=document, team="lasuite", role=role
)
# Create a new version should make it available to the user
time.sleep(1) # minio stores datetimes with the precision of a second
document.content = "new content"
document.save()
versions = document.get_versions_slice()["versions"]
assert len(versions) == 2
version_id = versions[1]["version_id"]
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/",
)
# 404 because the version was created before the user was given access to the document
assert response.status_code == 404
version_id = versions[0]["version_id"]
response = client.delete(
f"/api/v1.0/documents/{document.id!s}/versions/{version_id:s}/",
)
assert response.status_code == 204
versions = document.get_versions_slice()["versions"]
assert len(versions) == 1